diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 918688f..a9e1b84 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './useCallback' +export {default} from './perf' diff --git a/examples/hello-world/src/perf/index.tsx b/examples/hello-world/src/perf/index.tsx new file mode 100644 index 0000000..28e779c --- /dev/null +++ b/examples/hello-world/src/perf/index.tsx @@ -0,0 +1,32 @@ +import {useState} from 'react' + +function Child({num}) { + console.log('Child Render') + return
Child {num}
+} + +function Parent() { + const [num, setNum] = useState(1) + console.log('Parent render') + return ( +
setNum(2)}> + +
+ //
setNum(2)}> + // Parent {num} + // + //
+ ) +} + +export default function App() { + console.log('App render') + return ( +
+ App + +
+ ) +} + +//https://juejin.cn/post/7073692220313829407?searchId=20240719185830A176472F8B81316DB83C diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index d77842b..d2ecadb 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -3,21 +3,103 @@ use std::rc::Rc; use wasm_bindgen::JsValue; -use shared::derive_from_js_value; +use shared::{derive_from_js_value, is_dev, log}; use web_sys::js_sys::Object; -use crate::child_fiber::{mount_child_fibers, reconcile_child_fibers}; +use crate::child_fiber::{clone_child_fiblers, mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_flags::Flags; -use crate::fiber_hooks::render_with_hooks; -use crate::fiber_lanes::Lane; +use crate::fiber_hooks::{bailout_hook, render_with_hooks}; +use crate::fiber_lanes::{include_some_lanes, Lane}; use crate::update_queue::{process_update_queue, ReturnOfProcessUpdateQueue}; use crate::work_tags::WorkTag; +static mut DID_RECEIVE_UPDATE: bool = false; + +pub fn mark_wip_received_update() { + unsafe { DID_RECEIVE_UPDATE = true }; +} + +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); + } + return None; + } + if is_dev() { + log!("bailout current fiber {:?}", wip); + } + clone_child_fiblers(wip.clone()); + wip.borrow().child.clone() +} + +fn check_scheduled_update_or_context(current: Rc>, render_lane: Lane) -> bool { + let update_lanes = current.borrow().lanes.clone(); + if include_some_lanes(update_lanes, render_lane) { + return true; + } + false +} + pub fn begin_work( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { + unsafe { + DID_RECEIVE_UPDATE = false; + }; + let current = { work_in_progress.borrow().alternate.clone() }; + + if current.is_some() { + let current = current.clone().unwrap(); + let old_props = current.borrow().memoized_props.clone(); + let old_type = current.borrow()._type.clone(); + let new_props = work_in_progress.borrow().pending_props.clone(); + let new_type = work_in_progress.borrow()._type.clone(); + if !Object::is(&old_props, &new_props) || !Object::is(&old_type, &new_type) { + unsafe { DID_RECEIVE_UPDATE = true } + } else { + let has_scheduled_update_or_context = + check_scheduled_update_or_context(current.clone(), render_lane.clone()); + // The current fiber lane is not included in render_lane + // TODO context + if !has_scheduled_update_or_context { + unsafe { DID_RECEIVE_UPDATE = false } + // // if current.is_some() { + // let c = current.clone(); + // log!( + // "current tag:{:?} lanes:{:?} child_lanes:{:?} render_lane:{:?}", + // c.borrow().tag, + // c.borrow().lanes, + // c.borrow().child_lanes, + // render_lane + // ); + // // } + return Ok(bailout_on_already_finished_work( + work_in_progress, + render_lane, + )); + } + } + } + + work_in_progress.borrow_mut().lanes = Lane::NoLane; + // if current.is_some() { + // let current = current.clone().unwrap(); + // current.borrow_mut().lanes = Lane::NoLane; + // } + let tag = work_in_progress.clone().borrow().tag.clone(); return match tag { WorkTag::FunctionComponent => { @@ -33,7 +115,17 @@ fn update_function_component( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { - let next_children = render_with_hooks(work_in_progress.clone(), render_lane)?; + let next_children = render_with_hooks(work_in_progress.clone(), render_lane.clone())?; + + let current = work_in_progress.borrow().alternate.clone(); + if current.is_some() && unsafe { !DID_RECEIVE_UPDATE } { + bailout_hook(work_in_progress.clone(), render_lane.clone()); + return Ok(bailout_on_already_finished_work( + work_in_progress, + render_lane, + )); + } + reconcile_children(work_in_progress.clone(), Some(next_children)); Ok(work_in_progress.clone().borrow().child.clone()) } @@ -61,7 +153,7 @@ fn update_host_root( { let ReturnOfProcessUpdateQueue { memoized_state, .. } = - process_update_queue(base_state, pending, render_lane); + process_update_queue(base_state, pending, render_lane, None); work_in_progress.clone().borrow_mut().memoized_state = memoized_state; } diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index f2df818..5d996e2 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -182,6 +182,7 @@ fn reconcile_single_text_node( Rc::new(RefCell::new(created)) } +#[derive(Clone, Debug)] struct Key(JsValue); impl PartialEq for Key { @@ -212,7 +213,7 @@ fn update_from_map( should_track_effects: bool, ) -> Option>> { let key_to_use; - if type_of(element, "string") || type_of(element, "null") { + if type_of(element, "string") || type_of(element, "null") || type_of(element, "number") { key_to_use = JsValue::from(index); } else { let key = derive_from_js_value(element, "key"); @@ -224,6 +225,7 @@ fn update_from_map( let before = existing_children.get(&Key(key_to_use.clone())).clone(); if type_of(element, "null") || type_of(element, "string") || type_of(element, "number") { let props = create_props_with_content(element.clone()); + // log!("update_from_map {:?}", props); if before.is_some() { let before = (*before.clone().unwrap()).clone(); existing_children.remove(&Key(key_to_use.clone())); @@ -268,7 +270,6 @@ fn update_from_map( element, )))); } - // panic!("update_from_map unsupported"); None } @@ -296,10 +297,11 @@ fn reconcile_children_array( existing_children.insert(Key(key_to_use), current_rc.clone()); current = current_rc.borrow().sibling.clone(); } - + // log!("existing_children {:?}", existing_children.keys()); let length = new_child.length(); for i in 0..length { let after = new_child.get(i); + // log!("after {:?}", after); let new_fiber = update_from_map( return_fiber.clone(), &mut existing_children, @@ -307,7 +309,7 @@ fn reconcile_children_array( &after, should_track_effects, ); - + // log!("new_fiber {:?}", new_fiber); if new_fiber.is_none() { continue; } @@ -348,7 +350,11 @@ fn reconcile_children_array( for (_, fiber) in existing_children { delete_child(return_fiber.clone(), fiber, should_track_effects); } - + log!( + "first_new_fiber {:?} {:?}", + first_new_fiber, + first_new_fiber.clone().unwrap().borrow().sibling + ); first_new_fiber } @@ -398,6 +404,35 @@ fn _reconcile_child_fibers( delete_remaining_children(return_fiber, current_first_child, should_track_effects) } +pub fn clone_child_fiblers(wip: Rc>) { + if wip.borrow().child.is_none() { + return; + } + + let mut current_child = { wip.borrow().child.clone().unwrap() }; + let pending_props = { current_child.borrow().pending_props.clone() }; + let mut new_child = FiberNode::create_work_in_progress(current_child.clone(), pending_props); + wip.borrow_mut().child = Some(new_child.clone()); + new_child.borrow_mut()._return = Some(wip.clone()); + + while current_child.borrow().sibling.is_some() { + let sibling = { current_child.borrow().sibling.clone().unwrap() }; + let pending_props = { sibling.borrow().pending_props.clone() }; + let new_slibing = FiberNode::create_work_in_progress(sibling.clone(), pending_props); + new_slibing.borrow_mut()._return = Some(wip.clone()); + // log!( + // "new {:?} {:?} {:?}", + // new_slibing, + // new_slibing.borrow()._return, + // sibling.borrow()._return + // ); + new_child.borrow_mut().sibling = Some(new_slibing.clone()); + + current_child = sibling; + new_child = new_slibing; + } +} + pub fn reconcile_child_fibers( return_fiber: Rc>, current_first_child: Option>>, diff --git a/packages/react-reconciler/src/commit_work.rs b/packages/react-reconciler/src/commit_work.rs index b3739c8..96ac6fe 100644 --- a/packages/react-reconciler/src/commit_work.rs +++ b/packages/react-reconciler/src/commit_work.rs @@ -222,6 +222,11 @@ fn commit_mutation_effects_on_fiber( finished_work.borrow_mut().flags -= Flags::ChildDeletion; } + // log!( + // "finished_work {:?} {:?}", + // finished_work, + // finished_work.borrow().alternate + // ); if flags.contains(Flags::Update) { commit_update(finished_work.clone()); finished_work.borrow_mut().flags -= Flags::Update; @@ -285,6 +290,7 @@ fn commit_update(finished_work: Rc>) { WorkTag::HostText => { let new_content = derive_from_js_value(&cloned.borrow().pending_props, "content"); let state_node = FiberNode::derive_state_node(finished_work.clone()); + log!("commit_update {:?} {:?}", state_node, new_content); if let Some(state_node) = state_node.clone() { unsafe { HOST_CONFIG diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index fdfb179..415e9bf 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -9,6 +9,7 @@ use shared::{derive_from_js_value, log}; use crate::fiber::{FiberNode, StateNode}; use crate::fiber_flags::Flags; +use crate::fiber_lanes::{merge_lanes, Lane}; use crate::work_tags::WorkTag; use crate::HostConfig; @@ -97,14 +98,24 @@ impl CompleteWork { fn bubble_properties(&self, complete_work: Rc>) { let mut subtree_flags = Flags::NoFlags; + let mut new_child_lanes = Lane::NoLane; { let mut child = complete_work.clone().borrow().child.clone(); + while child.is_some() { let child_rc = child.clone().unwrap().clone(); { let child_borrowed = child_rc.borrow(); subtree_flags |= child_borrowed.subtree_flags.clone(); subtree_flags |= child_borrowed.flags.clone(); + + new_child_lanes = merge_lanes( + new_child_lanes, + merge_lanes( + child_borrowed.lanes.clone(), + child_borrowed.child_lanes.clone(), + ), + ) } { child_rc.borrow_mut()._return = Some(complete_work.clone()); @@ -114,6 +125,7 @@ impl CompleteWork { } complete_work.clone().borrow_mut().subtree_flags |= subtree_flags.clone(); + complete_work.clone().borrow_mut().child_lanes |= new_child_lanes.clone(); } fn mark_update(fiber: Rc>) { @@ -131,15 +143,18 @@ impl CompleteWork { match tag { WorkTag::FunctionComponent => { self.bubble_properties(work_in_progress.clone()); + // log!("bubble_properties function {:?}", work_in_progress.clone()); None } WorkTag::HostRoot => { self.bubble_properties(work_in_progress.clone()); + // log!("bubble_properties HostRoot {:?}", work_in_progress.clone()); None } WorkTag::HostComponent => { if current.is_some() && work_in_progress_cloned.borrow().state_node.is_some() { - log!("update properties"); + // todo compare + // CompleteWork::mark_update(work_in_progress.clone()); let current = current.unwrap(); if !Object::is( ¤t.borrow()._ref, @@ -167,6 +182,10 @@ impl CompleteWork { } self.bubble_properties(work_in_progress.clone()); + // log!( + // "bubble_properties HostComponent {:?}", + // work_in_progress.clone() + // ); None } WorkTag::HostText => { @@ -175,8 +194,14 @@ impl CompleteWork { ¤t.clone().unwrap().clone().borrow().memoized_props, "content", ); - let new_test = derive_from_js_value(&new_props, "content"); - if !Object::is(&old_text, &new_test) { + let new_text = derive_from_js_value(&new_props, "content"); + // log!( + // "complete host {:?} {:?} {:?}", + // work_in_progress_cloned, + // old_text, + // new_text + // ); + if !Object::is(&old_text, &new_text) { CompleteWork::mark_update(work_in_progress.clone()); } } else { diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index f02cae9..d29bc6e 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -47,6 +47,7 @@ impl MemoizedState { pub struct FiberNode { pub lanes: Lane, + pub child_lanes: Lane, pub index: u32, pub tag: WorkTag, pub pending_props: JsValue, @@ -72,10 +73,11 @@ impl Debug for FiberNode { WorkTag::FunctionComponent => { write!( f, - "{:?}(flags:{:?}, subtreeFlags:{:?})", + "{:?}(flags:{:?}, subtreeFlags:{:?}, lanes:{:?})", self._type.as_ref(), self.flags, - self.subtree_flags + self.subtree_flags, + self.lanes ) .expect("print error"); } @@ -131,6 +133,7 @@ impl FiberNode { subtree_flags: Flags::NoFlags, deletions: vec![], lanes: Lane::NoLane, + child_lanes: Lane::NoLane, _ref, } } @@ -177,7 +180,7 @@ impl FiberNode { return if w.is_none() { let wip = { - let c = c_rc.borrow(); + let c = { c_rc.borrow() }; let mut wip = FiberNode::new(c.tag.clone(), pending_props, c.key.clone(), c._ref.clone()); wip._type = c._type.clone(); @@ -186,6 +189,8 @@ impl FiberNode { wip.update_queue = c.update_queue.clone(); wip.flags = c.flags.clone(); wip.child = c.child.clone(); + wip.lanes = c.lanes.clone(); + wip.child_lanes = c.child_lanes.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); wip.alternate = Some(current); @@ -193,8 +198,7 @@ impl FiberNode { }; let wip_rc = Rc::new(RefCell::new(wip)); { - let mut fibler_node = c_rc.borrow_mut(); - fibler_node.alternate = Some(wip_rc.clone()); + c_rc.clone().borrow_mut().alternate = Some(wip_rc.clone()); } wip_rc } else { @@ -212,6 +216,8 @@ impl FiberNode { wip.update_queue = c.update_queue.clone(); wip.flags = c.flags.clone(); wip.child = c.child.clone(); + wip.lanes = c.lanes.clone(); + wip.child_lanes = c.child_lanes.clone(); wip.memoized_props = c.memoized_props.clone(); wip.memoized_state = c.memoized_state.clone(); wip._ref = c._ref.clone(); diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 3e041ec..ad46d63 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -5,11 +5,12 @@ use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; -use shared::log; +use shared::{is_dev, log}; +use crate::begin_work::mark_wip_received_update; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_flags::Flags; -use crate::fiber_lanes::{merge_lanes, request_update_lane, Lane}; +use crate::fiber_lanes::{merge_lanes, remove_lanes, request_update_lane, Lane}; use crate::update_queue::{ create_update, create_update_queue, enqueue_update, process_update_queue, ReturnOfProcessUpdateQueue, Update, UpdateQueue, @@ -83,6 +84,15 @@ impl Hook { } } +pub fn bailout_hook(wip: Rc>, render_lane: Lane) { + let current = { wip.borrow().alternate.clone().unwrap() }; + let update_queue = { current.borrow().update_queue.clone() }; + let lanes = { current.borrow().lanes.clone() }; + wip.borrow_mut().update_queue = update_queue; + wip.borrow_mut().flags -= Flags::PassiveEffect; + current.borrow_mut().lanes = remove_lanes(lanes, render_lane); +} + fn update_hooks_to_dispatcher(is_update: bool) { let object = Object::new(); @@ -335,7 +345,7 @@ fn mount_state(initial_state: &JsValue) -> Result, JsValue> { closure.forget(); queue.clone().borrow_mut().dispatch = Some(function.clone()); - + queue.clone().borrow_mut().last_rendered_state = Some(memoized_state.clone()); Ok(vec![memoized_state, function.into()]) } @@ -379,31 +389,47 @@ fn update_state(_: &JsValue) -> Result, JsValue> { } if base_queue.is_some() { - log!("base state is {:?}", base_state); + let pre_state = hook.as_ref().unwrap().borrow().memoized_state.clone(); + let ReturnOfProcessUpdateQueue { memoized_state, base_state: new_base_state, base_queue: new_base_queue, - skipped_update_lanes, - } = process_update_queue(base_state, base_queue, unsafe { RENDER_LANE.clone() }); - unsafe { - let lanes = { - CURRENTLY_RENDERING_FIBER - .clone() - .unwrap() - .borrow() - .lanes - .clone() - }; - CURRENTLY_RENDERING_FIBER - .clone() - .unwrap() - .borrow_mut() - .lanes = merge_lanes(lanes, skipped_update_lanes) - }; - hook_cloned.borrow_mut().memoized_state = memoized_state; + } = process_update_queue( + base_state, + base_queue, + unsafe { RENDER_LANE.clone() }, + Some(|update: Rc>| { + let skipped_lane = update.borrow().lane.clone(); + let fiber = unsafe { CURRENTLY_RENDERING_FIBER.clone().unwrap().clone() }; + let lanes = { fiber.borrow().lanes.clone() }; + fiber.borrow_mut().lanes = merge_lanes(lanes, skipped_lane); + }), + ); + + if !(memoized_state.is_none() && pre_state.is_none()) { + let memoized_state = memoized_state.clone().unwrap(); + let pre_state = pre_state.unwrap(); + if let MemoizedState::MemoizedJsValue(ms_value) = memoized_state { + if let MemoizedState::MemoizedJsValue(ps_value) = pre_state { + if !Object::is(&ms_value, &ps_value) { + mark_wip_received_update(); + } + } + } + } + + hook_cloned.borrow_mut().memoized_state = memoized_state.clone(); hook_cloned.borrow_mut().base_state = new_base_state; hook_cloned.borrow_mut().base_queue = new_base_queue; + + queue.clone().unwrap().borrow_mut().last_rendered_state = Some(match memoized_state { + Some(m) => match m { + MemoizedState::MemoizedJsValue(js_value) => js_value, + _ => todo!(), + }, + None => todo!(), + }); } Ok(vec![ @@ -421,23 +447,58 @@ fn update_state(_: &JsValue) -> Result, JsValue> { ]) } +pub fn basic_state_reducer(state: &JsValue, action: &JsValue) -> Result { + if action.is_function() { + let function = action.dyn_ref::().unwrap(); + return function.call1(&JsValue::null(), state); + } + Ok(action.into()) +} + fn dispatch_set_state( fiber: Rc>, update_queue: Rc>, action: &JsValue, ) { let lane = request_update_lane(); - let update = create_update(action.clone(), lane.clone()); - enqueue_update(update_queue.clone(), update); - unsafe { - schedule_update_on_fiber(fiber.clone(), lane); + let mut update = create_update(action.clone(), lane.clone()); + let current = { fiber.borrow().alternate.clone() }; + log!( + "dispatch_set_state {:?} {:?}", + fiber.borrow().lanes.clone(), + if current.is_none() { + Lane::NoLane + } else { + current.clone().unwrap().borrow().lanes.clone() + } + ); + if fiber.borrow().lanes == Lane::NoLane + && (current.is_none() || current.unwrap().borrow().lanes == Lane::NoLane) + { + log!("sdadgasd"); + let current_state = update_queue.borrow().last_rendered_state.clone(); + if current_state.is_none() { + panic!("current state is none") + } + let current_state = current_state.unwrap(); + let eager_state = basic_state_reducer(¤t_state, &action); + // if not ok, the update will be handled in render phase, means the error will be handled in render phase + if eager_state.is_ok() { + let eager_state = eager_state.unwrap(); + update.has_eager_state = true; + update.eager_state = Some(eager_state.clone()); + if Object::is(¤t_state, &eager_state) { + enqueue_update(update_queue.clone(), update, fiber.clone(), Lane::NoLane); + if is_dev() { + log!("Hit eager state") + } + return; + } + } } -} -fn create_fc_update_queue() -> Rc> { - let update_queue = create_update_queue(); - update_queue.borrow_mut().last_effect = None; - update_queue + enqueue_update(update_queue.clone(), update, fiber.clone(), lane.clone()); + schedule_update_on_fiber(fiber.clone(), lane); } fn push_effect( @@ -452,7 +513,7 @@ fn push_effect( let fiber = unsafe { CURRENTLY_RENDERING_FIBER.clone().unwrap() }; let update_queue = { fiber.borrow().update_queue.clone() }; if update_queue.is_none() { - let update_queue = create_fc_update_queue(); + let update_queue = create_update_queue(); fiber.borrow_mut().update_queue = Some(update_queue.clone()); effect.borrow_mut().next = Option::from(effect.clone()); update_queue.borrow_mut().last_effect = Option::from(effect.clone()); diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index 96e5337..916419f 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -60,3 +60,11 @@ pub fn lanes_to_scheduler_priority(lanes: Lane) -> Priority { } Priority::IdlePriority } + +pub fn include_some_lanes(set: Lane, subset: Lane) -> bool { + return (set & subset) != Lane::NoLane; +} + +pub fn remove_lanes(set: Lane, subset: Lane) -> Lane { + return set - subset; +} diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 454510a..81e8d52 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -74,9 +74,12 @@ impl Reconciler { let host_root_fiber = Rc::clone(&root).borrow().current.clone(); let root_render_priority = Lane::SyncLane; let update = create_update(element.clone(), root_render_priority.clone()); + let update_queue = { host_root_fiber.borrow().update_queue.clone().unwrap() }; enqueue_update( - host_root_fiber.borrow().update_queue.clone().unwrap(), + update_queue, update, + host_root_fiber.clone(), + root_render_priority.clone(), ); unsafe { HOST_CONFIG = Some(self.host_config.clone()); diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 46d7763..4c804d3 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -7,7 +7,7 @@ use web_sys::js_sys::Function; use shared::log; use crate::fiber::{FiberNode, MemoizedState}; -use crate::fiber_hooks::Effect; +use crate::fiber_hooks::{basic_state_reducer, Effect}; use crate::fiber_lanes::{is_subset_of_lanes, merge_lanes, Lane}; #[derive(Clone, Debug)] @@ -18,6 +18,8 @@ pub struct Update { pub action: Option, pub lane: Lane, pub next: Option>>, + pub has_eager_state: bool, + pub eager_state: Option, } #[derive(Clone, Debug)] @@ -30,6 +32,7 @@ pub struct UpdateQueue { pub shared: UpdateType, pub dispatch: Option, pub last_effect: Option>>, + pub last_rendered_state: Option, } pub fn create_update(action: JsValue, lane: Lane) -> Update { @@ -37,10 +40,17 @@ pub fn create_update(action: JsValue, lane: Lane) -> Update { action: Some(action), lane, next: None, + has_eager_state: false, + eager_state: None, } } -pub fn enqueue_update(update_queue: Rc>, mut update: Update) { +pub fn enqueue_update( + update_queue: Rc>, + mut update: Update, + fiber: Rc>, + lane: Lane, +) { let pending = update_queue.borrow().shared.pending.clone(); let update_rc = Rc::new(RefCell::new(update)); let update_option = Option::from(update_rc.clone()); @@ -52,6 +62,15 @@ pub fn enqueue_update(update_queue: Rc>, mut update: Update pending.borrow_mut().next = update_option.clone(); } update_queue.borrow_mut().shared.pending = update_option.clone(); + + let fiber_lane = { fiber.borrow().lanes.clone() }; + fiber.borrow_mut().lanes = merge_lanes(fiber_lane, lane.clone()); + let alternate = fiber.borrow().alternate.clone(); + if alternate.is_some() { + let alternate = alternate.unwrap(); + let lanes = { alternate.borrow().lanes.clone() }; + alternate.borrow_mut().lanes = merge_lanes(lanes, lane); + } } pub fn create_update_queue() -> Rc> { @@ -59,6 +78,7 @@ pub fn create_update_queue() -> Rc> { shared: UpdateType { pending: None }, dispatch: None, last_effect: None, + last_rendered_state: None, })) } @@ -66,32 +86,35 @@ pub struct ReturnOfProcessUpdateQueue { pub memoized_state: Option, pub base_state: Option, pub base_queue: Option>>, - pub skipped_update_lanes: Lane, } pub fn process_update_queue( base_state: Option, pending_update: Option>>, render_lanes: Lane, + on_skip_update: Option>) -> ()>, ) -> ReturnOfProcessUpdateQueue { let mut result = ReturnOfProcessUpdateQueue { memoized_state: base_state.clone(), base_state: base_state.clone(), base_queue: None, - skipped_update_lanes: Lane::NoLane, }; if pending_update.is_some() { - let mut update_option = pending_update.clone(); + let update_option = pending_update.clone().unwrap(); + let first = update_option.borrow().next.clone(); + let mut pending = update_option.borrow().next.clone(); + // 更新后的baseState(有跳过情况下与memoizedState不同) - let mut new_base_state: Option = base_state; + let mut new_base_state: Option = base_state.clone(); // 更新后的baseQueue第一个节点 let mut new_base_queue_first: Option>> = None; // 更新后的baseQueue最后一个节点 let mut new_base_queue_last: Option>> = None; + let mut new_state = base_state.clone(); loop { - let mut update = update_option.clone().unwrap(); + let mut update = pending.clone().unwrap(); let update_lane = update.borrow().lane.clone(); if !is_subset_of_lanes(render_lanes.clone(), update_lane.clone()) { // underpriority @@ -99,6 +122,12 @@ pub fn process_update_queue( update.borrow().action.clone().unwrap(), update_lane.clone(), ))); + + if on_skip_update.is_some() { + let function = on_skip_update.unwrap(); + function(clone.clone()); + } + if new_base_queue_last.is_none() { new_base_queue_first = Some(clone.clone()); new_base_queue_last = Some(clone.clone()); @@ -106,7 +135,6 @@ pub fn process_update_queue( } else { new_base_queue_last.clone().unwrap().borrow_mut().next = Some(clone.clone()); } - result.skipped_update_lanes = merge_lanes(result.skipped_update_lanes, update_lane); } else { if new_base_queue_last.is_some() { let clone = Rc::new(RefCell::new(create_update( @@ -117,38 +145,86 @@ pub fn process_update_queue( new_base_queue_last = Some(clone.clone()) } - result.memoized_state = match update.borrow().action.clone() { - None => None, - Some(action) => { - let f = action.dyn_ref::(); - match f { - None => Some(MemoizedState::MemoizedJsValue(action.clone())), - Some(f) => match result.memoized_state.as_ref() { - Some(memoized_state) => { - if let MemoizedState::MemoizedJsValue(base_state) = - memoized_state - { - Some(MemoizedState::MemoizedJsValue( - f.call1(&JsValue::null(), base_state).unwrap(), - )) - } else { - log!("process_update_queue, base_state is not JsValue"); - None + if update.borrow().has_eager_state { + new_state = Some(MemoizedState::MemoizedJsValue( + update.borrow().eager_state.clone().unwrap(), + )); + } else { + // let b = match base_state.clone() { + // Some(s) => match s { + // MemoizedState::MemoizedJsValue(js_value) => Some(js_value), + // _ => None, + // }, + // None => None, + // }; + // new_state = if b.is_none() { + // None + // } else { + // Some(MemoizedState::MemoizedJsValue( + // basic_state_reducer( + // b.as_ref().unwrap(), + // &update.borrow().action.clone().unwrap(), + // ) + // .unwrap(), + // )) + // }; + new_state = match update.borrow().action.clone() { + None => None, + Some(action) => { + let f = action.dyn_ref::(); + match f { + None => Some(MemoizedState::MemoizedJsValue(action.clone())), + Some(f) => match result.memoized_state.as_ref() { + Some(memoized_state) => { + if let MemoizedState::MemoizedJsValue(base_state) = + memoized_state + { + Some(MemoizedState::MemoizedJsValue( + f.call1(&JsValue::null(), base_state).unwrap(), + )) + } else { + log!("process_update_queue, base_state is not JsValue"); + None + } } - } - None => Some(MemoizedState::MemoizedJsValue( - f.call1(&JsValue::null(), &JsValue::undefined()).unwrap(), - )), - }, + None => Some(MemoizedState::MemoizedJsValue( + f.call1(&JsValue::null(), &JsValue::undefined()).unwrap(), + )), + }, + } } - } - }; + }; + } + + // result.memoized_state = match update.borrow().action.clone() { + // None => None, + // Some(action) => { + // let f = action.dyn_ref::(); + // match f { + // None => Some(MemoizedState::MemoizedJsValue(action.clone())), + // Some(f) => match result.memoized_state.as_ref() { + // Some(memoized_state) => { + // if let MemoizedState::MemoizedJsValue(base_state) = + // memoized_state + // { + // Some(MemoizedState::MemoizedJsValue( + // f.call1(&JsValue::null(), base_state).unwrap(), + // )) + // } else { + // log!("process_update_queue, base_state is not JsValue"); + // None + // } + // } + // None => Some(MemoizedState::MemoizedJsValue( + // f.call1(&JsValue::null(), &JsValue::undefined()).unwrap(), + // )), + // }, + // } + // } + // }; } - update_option = update.clone().borrow().next.clone(); - if Rc::ptr_eq( - &update_option.clone().unwrap(), - &pending_update.clone().unwrap(), - ) { + pending = update.clone().borrow().next.clone(); + if Rc::ptr_eq(&pending.clone().unwrap(), &first.clone().unwrap()) { break; } } @@ -159,6 +235,7 @@ pub fn process_update_queue( new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_last.clone(); } + result.memoized_state = new_state; result.base_state = new_base_state; result.base_queue = new_base_queue_last.clone(); } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 40bcab6..0f710e9 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -68,27 +68,26 @@ pub fn mark_update_lane_from_fiber_to_root( let mut node = Rc::clone(&fiber); let mut parent = Rc::clone(&fiber).borrow()._return.clone(); - let node_lanes = { node.borrow().lanes.clone() }; - node.borrow_mut().lanes = merge_lanes(node_lanes, lane.clone()); - let alternate = node.borrow().alternate.clone(); - if alternate.is_some() { - let alternate = alternate.unwrap(); - let alternate_lanes = { alternate.borrow().lanes.clone() }; - alternate.borrow_mut().lanes = merge_lanes(alternate_lanes, lane); - } - while parent.is_some() { + let p = parent.clone().unwrap(); + let child_lanes = { p.borrow().child_lanes.clone() }; + p.borrow_mut().child_lanes = merge_lanes(child_lanes, lane.clone()); + + let alternate = p.borrow().alternate.clone(); + if alternate.is_some() { + let alternate = alternate.unwrap(); + let child_lanes = { alternate.borrow().child_lanes.clone() }; + alternate.borrow_mut().child_lanes = merge_lanes(child_lanes, lane.clone()); + } + node = parent.clone().unwrap(); - let rc = Rc::clone(&parent.unwrap()); - let rc_ref = rc.borrow(); - let next = match rc_ref._return.as_ref() { + parent = match parent.clone().unwrap().borrow()._return.as_ref() { None => None, Some(node) => { let a = node.clone(); Some(a) } }; - parent = next; } let fiber_node_rc = Rc::clone(&node); @@ -490,7 +489,7 @@ fn complete_unit_of_work(fiber: Rc>) { } let _return = node.clone().unwrap().clone().borrow()._return.clone(); - + // log!("complete_unit_of_work {:?} {:?}", node, _return); if _return.is_none() { // node = None; // self.work_in_progress = None; diff --git a/readme.md b/readme.md index 7815002..ec81a58 100644 --- a/readme.md +++ b/readme.md @@ -39,3 +39,7 @@ [从零实现 React v18,但 WASM 版 - [15] 实现 useEffect](https://www.paradeto.com/2024/05/24/big-react-wasm-15/) [从零实现 React v18,但 WASM 版 - [16] 实现 React Noop](https://www.paradeto.com/2024/06/06/big-react-wasm-16/) + +[从零实现 React v18,但 WASM 版 - [17] 实现 Concurrent 模式](https://www.paradeto.com/2024/06/19/big-react-wasm-17/) + +[从零实现 React v18,但 WASM 版 - [18] 实现 useRef, useCallback, useMemo](https://www.paradeto.com/2024/07/10/big-react-wasm-18/)