From 2a97548d0ef700c9145d79b9c9697847d98d316a Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 25 Jul 2024 12:11:59 +0800 Subject: [PATCH 1/2] add utils function for context --- packages/react-reconciler/src/fiber.rs | 9 + .../react-reconciler/src/fiber_context.rs | 188 +++++++++++++++++- 2 files changed, 196 insertions(+), 1 deletion(-) diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 29be5dd..d587c03 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -11,6 +11,7 @@ use web_sys::js_sys::Reflect; use shared::{derive_from_js_value, log, type_of, REACT_PROVIDER_TYPE}; +use crate::fiber_context::ContextItem; use crate::fiber_flags::Flags; use crate::fiber_hooks::{Effect, Hook}; use crate::fiber_lanes::{get_highest_priority, merge_lanes, Lane}; @@ -45,6 +46,12 @@ impl MemoizedState { } } +#[derive(Clone)] +pub struct FiberDependencies { + pub first_context: Option>>, + pub lanes: Lane, +} + pub struct FiberNode { pub lanes: Lane, pub child_lanes: Lane, @@ -65,6 +72,7 @@ pub struct FiberNode { pub memoized_props: JsValue, pub memoized_state: Option, pub deletions: Vec>>, + pub dependencies: Option>>, } impl Debug for FiberNode { @@ -146,6 +154,7 @@ impl FiberNode { lanes: Lane::NoLane, child_lanes: Lane::NoLane, _ref, + dependencies: None, } } diff --git a/packages/react-reconciler/src/fiber_context.rs b/packages/react-reconciler/src/fiber_context.rs index 2c9e808..207d101 100644 --- a/packages/react-reconciler/src/fiber_context.rs +++ b/packages/react-reconciler/src/fiber_context.rs @@ -1,8 +1,26 @@ +use std::{cell::RefCell, rc::Rc}; + +use shared::derive_from_js_value; use wasm_bindgen::JsValue; -use web_sys::js_sys::Reflect; +use web_sys::js_sys::{Object, Reflect}; + +use crate::{ + begin_work::mark_wip_received_update, + fiber::{FiberDependencies, FiberNode}, + fiber_lanes::{include_some_lanes, is_subset_of_lanes, merge_lanes, Lane}, + work_tags::WorkTag, +}; static mut PREV_CONTEXT_VALUE: JsValue = JsValue::null(); static mut PREV_CONTEXT_VALUE_STACK: Vec = vec![]; +static mut LAST_CONTEXT_DEP: Option>> = None; + +#[derive(Clone)] +pub struct ContextItem { + context: JsValue, + memoized_state: JsValue, + next: Option>>, +} pub fn push_provider(context: &JsValue, new_value: JsValue) { unsafe { @@ -23,3 +41,171 @@ pub fn pop_provider(context: &JsValue) { } } } + +pub fn prepare_to_read_context(wip: Rc>, render_lane: Lane) { + unsafe { LAST_CONTEXT_DEP = None }; + + let deps = { wip.borrow_mut().dependencies.clone() }; + if deps.is_some() { + let mut deps = deps.unwrap(); + if deps.first_context.is_some() { + if include_some_lanes(deps.lanes, render_lane) { + mark_wip_received_update() + } + deps.first_context = None; + } + } +} + +pub fn read_context(consumer: Option>>, context: JsValue) -> JsValue { + if consumer.is_none() { + panic!("Can only call useContext in Function Component"); + } + let consumer = consumer.unwrap(); + let value = derive_from_js_value(&context, "_currentValue"); + + let context_item = Rc::new(RefCell::new(ContextItem { + context, + next: None, + memoized_state: value.clone(), + })); + + if unsafe { LAST_CONTEXT_DEP.is_none() } { + unsafe { LAST_CONTEXT_DEP = Some(context_item.clone()) }; + consumer.borrow_mut().dependencies = Some(Rc::new(RefCell::new(FiberDependencies { + first_context: Some(context_item), + lanes: Lane::NoLane, + }))); + } else { + let next = Some(context_item.clone()); + unsafe { + LAST_CONTEXT_DEP.clone().unwrap().borrow_mut().next = next.clone(); + LAST_CONTEXT_DEP = next; + } + } + + value +} + +// DFS +pub fn propagate_context_change(wip: Rc>, context: JsValue, render_lane: Lane) { + let mut fiber = { wip.borrow().child.clone() }; + if fiber.is_some() { + fiber.as_ref().unwrap().borrow_mut()._return = Some(wip.clone()); + } + + while fiber.is_some() { + let mut next_fiber = None; + let fiber_unwrapped = fiber.unwrap(); + let deps = { fiber_unwrapped.borrow().dependencies.clone() }; + if deps.is_some() { + let deps = deps.unwrap(); + next_fiber = { fiber_unwrapped.borrow().child.clone() }; + let mut context_item = deps.borrow().first_context; + while context_item.is_some() { + let context_item_unwrapped = context_item.unwrap(); + if Object::is(&context_item_unwrapped.borrow().context, &context) { + // find the FiberNode which depend on wip(context.Provider) + let lanes = { fiber_unwrapped.borrow().lanes.clone() }; + fiber_unwrapped.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); + let alternate = { fiber_unwrapped.borrow().alternate.clone() }; + if alternate.is_some() { + let alternate = alternate.unwrap(); + let lanes = { alternate.borrow().lanes.clone() }; + alternate.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); + } + // update ancestors' child_lanes + schedule_context_work_on_parent_path( + fiber_unwrapped.borrow()._return.clone(), + wip.clone(), + render_lane.clone(), + ); + let lanes = { deps.borrow().lanes.clone() }; + deps.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); + break; + } + context_item = context_item_unwrapped.borrow().next; + } + } else if fiber_unwrapped.borrow().tag == WorkTag::ContextProvider { + /* + * const ctx = createContext() + * // propagate context change + *
+ * // stop here + *
+ * + *
+ *
+ */ + next_fiber = if Object::is(&fiber_unwrapped.borrow()._type, &wip.borrow()._type) { + None + } else { + fiber_unwrapped.borrow().child.clone() + }; + } else { + next_fiber = fiber_unwrapped.borrow().child.clone(); + } + + if next_fiber.is_some() { + next_fiber.unwrap().borrow_mut()._return = fiber; + } else { + // Leaf Node + next_fiber = fiber; + while next_fiber.is_some() { + let next_fiber_unwrapped = next_fiber.unwrap(); + if Rc::ptr_eq(&next_fiber_unwrapped, &wip) { + next_fiber = None; + break; + } + + let sibling = next_fiber_unwrapped.borrow().sibling; + if sibling.is_some() { + let sibling_unwrapped = sibling.unwrap(); + sibling_unwrapped.borrow_mut()._return = next_fiber_unwrapped.borrow()._return; + next_fiber = sibling; + break; + } + next_fiber = next_fiber_unwrapped.borrow()._return; + } + } + + fiber = next_fiber; + } +} + +fn schedule_context_work_on_parent_path( + from: Option>>, + to: Rc>, + render_lane: Lane, +) { + let mut node = from; + + while node.is_some() { + let node_unwrapped = node.unwrap(); + let alternate = { node_unwrapped.borrow().alternate }; + let child_lanes = { node_unwrapped.borrow().child_lanes.clone() }; + + if !is_subset_of_lanes(child_lanes, render_lane) { + node_unwrapped.borrow_mut().child_lanes = merge_lanes(child_lanes, render_lane); + if alternate.is_some() { + let alternate_unwrapped = alternate.unwrap(); + let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; + alternate_unwrapped.borrow_mut().child_lanes = + merge_lanes(child_lanes, render_lane); + } + } else if alternate.is_some() { + let alternate_unwrapped = alternate.unwrap(); + let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; + if !is_subset_of_lanes(child_lanes, render_lane) { + alternate_unwrapped.borrow_mut().child_lanes = + merge_lanes(child_lanes, render_lane); + } + } + + if Rc::ptr_eq(&node_unwrapped, &to) { + break; + } + + node = node_unwrapped.borrow()._return.clone(); + } +} From 8c416ee4e57c07f580ec145d0c039db6fc59d688 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 25 Jul 2024 17:04:54 +0800 Subject: [PATCH 2/2] context perf opt --- packages/react-reconciler/src/begin_work.rs | 93 ++++++++++++++++--- packages/react-reconciler/src/fiber.rs | 16 +++- .../react-reconciler/src/fiber_context.rs | 54 +++++------ packages/react-reconciler/src/fiber_hooks.rs | 12 +-- 4 files changed, 126 insertions(+), 49 deletions(-) diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index f208e26..bce4278 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -8,7 +8,7 @@ use web_sys::js_sys::Object; use crate::child_fiber::{clone_child_fiblers, mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; -use crate::fiber_context::push_provider; +use crate::fiber_context::{prepare_to_read_context, propagate_context_change, push_provider}; use crate::fiber_flags::Flags; use crate::fiber_hooks::{bailout_hook, render_with_hooks}; use crate::fiber_lanes::{include_some_lanes, Lane}; @@ -25,13 +25,6 @@ fn bailout_on_already_finished_work( wip: Rc>, render_lane: Lane, ) -> Option>> { - // log!( - // "tag:{:?} child_lanes:{:?} render_lanes:{:?} result:{:?}", - // wip.borrow().tag, - // wip.borrow().child_lanes.clone(), - // render_lane, - // wip.borrow().child_lanes.clone() & render_lane.clone() - // ); if !include_some_lanes(wip.borrow().child_lanes.clone(), render_lane) { if is_dev() { log!("bailout the whole subtree {:?}", wip); @@ -57,6 +50,7 @@ pub fn begin_work( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { + log!("begin_work {:?}", work_in_progress); unsafe { DID_RECEIVE_UPDATE = false; }; @@ -87,6 +81,18 @@ pub fn begin_work( // render_lane // ); // // } + match work_in_progress.borrow().tag { + WorkTag::ContextProvider => { + let new_value = derive_from_js_value( + &work_in_progress.borrow().memoized_props, + "value", + ); + let context = + derive_from_js_value(&work_in_progress.borrow()._type, "_context"); + push_provider(&context, new_value); + } + _ => {} + } return Ok(bailout_on_already_finished_work( work_in_progress, render_lane, @@ -101,25 +107,48 @@ pub fn begin_work( // current.borrow_mut().lanes = Lane::NoLane; // } - let tag = work_in_progress.clone().borrow().tag.clone(); + let tag = { work_in_progress.clone().borrow().tag.clone() }; return match tag { WorkTag::FunctionComponent => { - update_function_component(work_in_progress.clone(), render_lane) + let Component = { work_in_progress.borrow()._type.clone() }; + update_function_component(work_in_progress.clone(), Component, render_lane) } WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone(), render_lane)), WorkTag::HostComponent => Ok(update_host_component(work_in_progress.clone())), WorkTag::HostText => Ok(None), - WorkTag::ContextProvider => Ok(update_context_provider(work_in_progress.clone())), + WorkTag::ContextProvider => Ok(update_context_provider( + work_in_progress.clone(), + render_lane, + )), }; } fn update_context_provider( work_in_progress: Rc>, + render_lane: Lane, ) -> Option>> { let provider_type = { work_in_progress.borrow()._type.clone() }; let context = derive_from_js_value(&provider_type, "_context"); let new_props = { work_in_progress.borrow().pending_props.clone() }; + let old_props = { work_in_progress.borrow().memoized_props.clone() }; + let new_value = derive_from_js_value(&new_props, "value"); + push_provider(&context, derive_from_js_value(&new_props, "value")); + + if !old_props.is_null() { + let old_value = derive_from_js_value(&old_props, "value"); + if Object::is(&old_value, &new_value) + && Object::is( + &derive_from_js_value(&old_props, "children"), + &derive_from_js_value(&new_props, "children"), + ) + { + return bailout_on_already_finished_work(work_in_progress.clone(), render_lane); + } else { + propagate_context_change(work_in_progress.clone(), context, render_lane); + } + } + let next_children = derive_from_js_value(&new_props, "children"); reconcile_children(work_in_progress.clone(), Some(next_children)); work_in_progress.clone().borrow().child.clone() @@ -127,11 +156,17 @@ fn update_context_provider( fn update_function_component( work_in_progress: Rc>, + Component: JsValue, render_lane: Lane, ) -> Result>>, JsValue> { - let next_children = render_with_hooks(work_in_progress.clone(), render_lane.clone())?; + prepare_to_read_context(work_in_progress.clone(), render_lane.clone()); + let next_children = + render_with_hooks(work_in_progress.clone(), Component, render_lane.clone())?; let current = work_in_progress.borrow().alternate.clone(); + log!("{:?} {:?}", work_in_progress.clone(), unsafe { + DID_RECEIVE_UPDATE + }); if current.is_some() && unsafe { !DID_RECEIVE_UPDATE } { bailout_hook(work_in_progress.clone(), render_lane.clone()); return Ok(bailout_on_already_finished_work( @@ -165,10 +200,28 @@ fn update_host_root( .clone(); } + let prev_children = { work_in_progress_cloned.borrow().memoized_state.clone() }; + { + work_in_progress + .clone() + .borrow_mut() + .update_queue + .clone() + .unwrap() + .borrow_mut() + .shared + .pending = None; let ReturnOfProcessUpdateQueue { memoized_state, .. } = - process_update_queue(base_state, pending, render_lane, None); - work_in_progress.clone().borrow_mut().memoized_state = memoized_state; + process_update_queue(base_state, pending, render_lane.clone(), None); + work_in_progress.clone().borrow_mut().memoized_state = memoized_state.clone(); + let current = { work_in_progress.borrow().alternate.clone() }; + if current.is_some() { + let current = current.unwrap(); + if current.borrow().memoized_state.is_none() { + current.borrow_mut().memoized_state = memoized_state; + } + } } let next_children = work_in_progress_cloned.borrow().memoized_state.clone(); @@ -176,6 +229,18 @@ fn update_host_root( panic!("update_host_root next_children is none") } + // let prev_children = prev_children.unwrap(); + if let Some(MemoizedState::MemoizedJsValue(prev_children)) = prev_children { + if let Some(MemoizedState::MemoizedJsValue(next_children)) = next_children.clone() { + if Object::is(&prev_children, &next_children) { + return bailout_on_already_finished_work( + work_in_progress.clone(), + render_lane.clone(), + ); + } + } + } + if let MemoizedState::MemoizedJsValue(next_children) = next_children.unwrap() { reconcile_children(work_in_progress.clone(), Some(next_children)); } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index d587c03..c261740 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -46,7 +46,7 @@ impl MemoizedState { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct FiberDependencies { pub first_context: Option>>, pub lanes: Lane, @@ -217,6 +217,13 @@ impl FiberNode { wip.child_lanes = c.child_lanes.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); + wip.dependencies = match c.dependencies.clone() { + Some(d) => Some(Rc::new(RefCell::new(FiberDependencies { + lanes: d.borrow().lanes.clone(), + first_context: d.borrow().first_context.clone(), + }))), + None => None, + }; wip.alternate = Some(current); wip }; @@ -244,6 +251,13 @@ impl FiberNode { wip.child_lanes = c.child_lanes.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); + wip.dependencies = match c.dependencies.clone() { + Some(d) => Some(Rc::new(RefCell::new(FiberDependencies { + lanes: d.borrow().lanes.clone(), + first_context: d.borrow().first_context.clone(), + }))), + None => None, + }; wip._ref = c._ref.clone(); } w.clone() diff --git a/packages/react-reconciler/src/fiber_context.rs b/packages/react-reconciler/src/fiber_context.rs index 207d101..4f4053a 100644 --- a/packages/react-reconciler/src/fiber_context.rs +++ b/packages/react-reconciler/src/fiber_context.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc::Rc}; -use shared::derive_from_js_value; +use shared::{derive_from_js_value, log}; use wasm_bindgen::JsValue; use web_sys::js_sys::{Object, Reflect}; @@ -15,7 +15,7 @@ static mut PREV_CONTEXT_VALUE: JsValue = JsValue::null(); static mut PREV_CONTEXT_VALUE_STACK: Vec = vec![]; static mut LAST_CONTEXT_DEP: Option>> = None; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ContextItem { context: JsValue, memoized_state: JsValue, @@ -45,14 +45,15 @@ pub fn pop_provider(context: &JsValue) { pub fn prepare_to_read_context(wip: Rc>, render_lane: Lane) { unsafe { LAST_CONTEXT_DEP = None }; - let deps = { wip.borrow_mut().dependencies.clone() }; + let deps = { wip.borrow().dependencies.clone() }; + if deps.is_some() { - let mut deps = deps.unwrap(); - if deps.first_context.is_some() { - if include_some_lanes(deps.lanes, render_lane) { + let deps = deps.unwrap(); + if deps.borrow().first_context.is_some() { + if include_some_lanes(deps.borrow().lanes.clone(), render_lane) { mark_wip_received_update() } - deps.first_context = None; + deps.borrow_mut().first_context = None; } } } @@ -83,7 +84,6 @@ pub fn read_context(consumer: Option>>, context: JsValue) LAST_CONTEXT_DEP = next; } } - value } @@ -96,12 +96,12 @@ pub fn propagate_context_change(wip: Rc>, context: JsValue, r while fiber.is_some() { let mut next_fiber = None; - let fiber_unwrapped = fiber.unwrap(); + let fiber_unwrapped = fiber.clone().unwrap(); let deps = { fiber_unwrapped.borrow().dependencies.clone() }; if deps.is_some() { let deps = deps.unwrap(); - next_fiber = { fiber_unwrapped.borrow().child.clone() }; - let mut context_item = deps.borrow().first_context; + next_fiber = fiber_unwrapped.borrow().child.clone(); + let mut context_item = deps.borrow().first_context.clone(); while context_item.is_some() { let context_item_unwrapped = context_item.unwrap(); if Object::is(&context_item_unwrapped.borrow().context, &context) { @@ -124,7 +124,7 @@ pub fn propagate_context_change(wip: Rc>, context: JsValue, r deps.borrow_mut().lanes = merge_lanes(lanes, render_lane.clone()); break; } - context_item = context_item_unwrapped.borrow().next; + context_item = context_item_unwrapped.borrow().next.clone(); } } else if fiber_unwrapped.borrow().tag == WorkTag::ContextProvider { /* @@ -147,10 +147,10 @@ pub fn propagate_context_change(wip: Rc>, context: JsValue, r } if next_fiber.is_some() { - next_fiber.unwrap().borrow_mut()._return = fiber; + next_fiber.clone().unwrap().borrow_mut()._return = fiber; } else { // Leaf Node - next_fiber = fiber; + next_fiber = fiber.clone(); while next_fiber.is_some() { let next_fiber_unwrapped = next_fiber.unwrap(); if Rc::ptr_eq(&next_fiber_unwrapped, &wip) { @@ -158,18 +158,19 @@ pub fn propagate_context_change(wip: Rc>, context: JsValue, r break; } - let sibling = next_fiber_unwrapped.borrow().sibling; + let sibling = next_fiber_unwrapped.borrow().sibling.clone(); if sibling.is_some() { - let sibling_unwrapped = sibling.unwrap(); - sibling_unwrapped.borrow_mut()._return = next_fiber_unwrapped.borrow()._return; - next_fiber = sibling; + let sibling_unwrapped = sibling.clone().unwrap(); + sibling_unwrapped.borrow_mut()._return = + next_fiber_unwrapped.borrow()._return.clone(); + next_fiber = sibling.clone(); break; } - next_fiber = next_fiber_unwrapped.borrow()._return; + next_fiber = next_fiber_unwrapped.borrow()._return.clone(); } } - fiber = next_fiber; + fiber = next_fiber.clone(); } } @@ -182,23 +183,24 @@ fn schedule_context_work_on_parent_path( while node.is_some() { let node_unwrapped = node.unwrap(); - let alternate = { node_unwrapped.borrow().alternate }; + let alternate = { node_unwrapped.borrow().alternate.clone() }; let child_lanes = { node_unwrapped.borrow().child_lanes.clone() }; - if !is_subset_of_lanes(child_lanes, render_lane) { - node_unwrapped.borrow_mut().child_lanes = merge_lanes(child_lanes, render_lane); + if !is_subset_of_lanes(child_lanes.clone(), render_lane.clone()) { + node_unwrapped.borrow_mut().child_lanes = + merge_lanes(child_lanes.clone(), render_lane.clone()); if alternate.is_some() { let alternate_unwrapped = alternate.unwrap(); let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; alternate_unwrapped.borrow_mut().child_lanes = - merge_lanes(child_lanes, render_lane); + merge_lanes(child_lanes.clone(), render_lane.clone()); } } else if alternate.is_some() { let alternate_unwrapped = alternate.unwrap(); let child_lanes = { alternate_unwrapped.borrow().child_lanes.clone() }; - if !is_subset_of_lanes(child_lanes, render_lane) { + if !is_subset_of_lanes(child_lanes.clone(), render_lane.clone()) { alternate_unwrapped.borrow_mut().child_lanes = - merge_lanes(child_lanes, render_lane); + merge_lanes(child_lanes.clone(), render_lane.clone()); } } diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index e280e0d..67b8a92 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -9,6 +9,7 @@ use shared::{derive_from_js_value, is_dev, log}; use crate::begin_work::mark_wip_received_update; use crate::fiber::{FiberNode, MemoizedState}; +use crate::fiber_context::read_context as read_context_origin; use crate::fiber_flags::Flags; use crate::fiber_lanes::{merge_lanes, remove_lanes, request_update_lane, Lane}; use crate::update_queue::{ @@ -169,6 +170,7 @@ fn update_hooks_to_dispatcher(is_update: bool) { pub fn render_with_hooks( work_in_progress: Rc>, + Component: JsValue, lane: Lane, ) -> Result { unsafe { @@ -189,15 +191,13 @@ pub fn render_with_hooks( update_hooks_to_dispatcher(false); } - let _type; let props; { let work_in_progress_borrow = work_in_progress_cloned.borrow(); - _type = work_in_progress_borrow._type.clone(); props = work_in_progress_borrow.pending_props.clone(); } - let component = JsValue::dyn_ref::(&_type).unwrap(); + let component = JsValue::dyn_ref::(&Component).unwrap(); let children = component.call1(&JsValue::null(), &props); unsafe { @@ -754,9 +754,5 @@ fn update_callback(callback: Function, deps: JsValue) -> JsValue { fn read_context(context: JsValue) -> JsValue { let consumer = unsafe { CURRENTLY_RENDERING_FIBER.clone() }; - if consumer.is_none() { - panic!("Can only call useContext in Function Component"); - } - let value = derive_from_js_value(&context, "_currentValue"); - value + read_context_origin(consumer, context) }