diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 3b79989..ad1bd41 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,37 +1,55 @@ -import {useEffect, useState} from 'react' - -function App() { - const [num, updateNum] = useState(0); - return ( - - ); -} +import {useState, useEffect} from 'react' +// function App() { +// const [num, updateNum] = useState(0) +// const len = 100 -function Child1({num}: { num: number }) { - useEffect(() => { - console.log('child1 create') - return () => { - console.log('child1 destroy') - } - }, [num]); - return
child1 {num}
; -} +// console.log('num', num) +// return ( +// +// ) +// } + +// function Child({i}) { +// return

i am child {i}

+// } + +// export default App -function Child2({num}: { num: number }) { - useEffect(() => { - console.log('child2 create') - return () => { - console.log('child2 destroy') - } - }, [num]); - return
child2 {num}
; +const Item = ({i, children}) => { + for (let i = 0; i < 999999; i++) {} + return {children} } -export default App +export default () => { + const [count, updateCount] = useState(0) + + const onClick = () => { + updateCount(2) + } + + useEffect(() => { + const button = document.querySelector('button') + setTimeout(() => updateCount((num) => num + 1), 1000) + setTimeout(() => button.click(), 1100) + }, []) + + return ( +
+ +
+ {Array.from(new Array(1000)).map((v, index) => ( + {count} + ))} +
+
+ ) +} diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index 775168b..95cc852 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,21 +1,7 @@ import {createRoot} from 'react-dom' -import {useEffect} from 'react' +import App from './App' const root = createRoot(document.getElementById('root')) -function Parent() { - useEffect(() => { - return () => console.log('Unmount parent') - }) - return -} - -function Child() { - useEffect(() => { - return () => console.log('Unmount child') - }) - return 'Child' -} - -root.render() +root.render() // console.log(root.getChildrenAsJSX()) diff --git a/packages/react-dom/src/lib.rs b/packages/react-dom/src/lib.rs index 243bd6a..be32ec4 100644 --- a/packages/react-dom/src/lib.rs +++ b/packages/react-dom/src/lib.rs @@ -36,6 +36,7 @@ pub fn create_root(container: &JsValue) -> Renderer { } }; + // TODO cache the container // let mut root; // unsafe { // if CONTAINER_TO_ROOT.is_none() { diff --git a/packages/react-dom/src/renderer.rs b/packages/react-dom/src/renderer.rs index 9e17638..0004e5b 100644 --- a/packages/react-dom/src/renderer.rs +++ b/packages/react-dom/src/renderer.rs @@ -6,6 +6,7 @@ use wasm_bindgen::JsValue; use react_reconciler::fiber::FiberRootNode; use react_reconciler::Reconciler; +use web_sys::Element; use crate::synthetic_event::init_event; @@ -28,6 +29,15 @@ impl Renderer { container: container.clone(), } } + + // fn clear_container_dom(&self) { + // let ele = self.container.dyn_ref::().unwrap(); + // if !ele.has_child_nodes() { + // return; + // } + + // ele.child_nodes + // } } #[wasm_bindgen] @@ -37,4 +47,9 @@ impl Renderer { self.reconciler .update_container(element.clone(), self.root.clone()) } + + pub fn unmount(&self) -> JsValue { + self.reconciler + .update_container(JsValue::null(), self.root.clone()) + } } diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index b0854d0..dd89207 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -1,9 +1,11 @@ use gloo::events::EventListener; -use wasm_bindgen::{JsCast, JsValue}; +use scheduler::{unstable_cancel_callback, unstable_run_with_priority, Priority}; use wasm_bindgen::closure::Closure; -use web_sys::{Element, Event}; +use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Function, Object, Reflect}; +use web_sys::{Element, Event}; +use react_reconciler::fiber_lanes::{lanes_to_scheduler_priority, Lane}; use shared::{derive_from_js_value, is_dev, log}; static VALID_EVENT_TYPE_LIST: [&str; 1] = ["click"]; @@ -23,8 +25,18 @@ impl Paths { } } +fn event_type_to_event_priority(event_type: &str) -> Priority { + let lane = match event_type { + "click" | "keydown" | "keyup" => Lane::SyncLane, + "scroll" => Lane::InputContinuousLane, + _ => Lane::DefaultLane, + }; + lanes_to_scheduler_priority(lane) +} + fn create_synthetic_event(e: Event) -> Event { - Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false)).expect("TODO: panic set __stopPropagation"); + Reflect::set(&*e, &"__stopPropagation".into(), &JsValue::from_bool(false)) + .expect("TODO: panic set __stopPropagation"); let e_cloned = e.clone(); let origin_stop_propagation = derive_from_js_value(&*e, "stopPropagation"); @@ -33,21 +45,31 @@ fn create_synthetic_event(e: Event) -> Event { &*e_cloned, &"__stopPropagation".into(), &JsValue::from_bool(true), - ).expect("TODO: panic __stopPropagation"); + ) + .expect("TODO: panic __stopPropagation"); if origin_stop_propagation.is_function() { let origin_stop_propagation = origin_stop_propagation.dyn_ref::().unwrap(); - origin_stop_propagation.call0(&JsValue::null()).expect("TODO: panic origin_stop_propagation"); + origin_stop_propagation + .call0(&JsValue::null()) + .expect("TODO: panic origin_stop_propagation"); } }) as Box); let function = closure.as_ref().unchecked_ref::().clone(); closure.forget(); - Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into()).expect("TODO: panic set stopPropagation"); + Reflect::set(&*e.clone(), &"stopPropagation".into(), &function.into()) + .expect("TODO: panic set stopPropagation"); e } fn trigger_event_flow(paths: Vec, se: &Event) { for callback in paths { - callback.call1(&JsValue::null(), se).expect("TODO: panic call callback"); + unstable_run_with_priority( + event_type_to_event_priority(se.type_().as_str()), + &callback.bind1(&JsValue::null(), se), + ); + // callback + // .call1(&JsValue::null(), se) + // .expect("TODO: panic call callback"); if derive_from_js_value(se, "__stopPropagation") .as_bool() .unwrap() @@ -158,16 +180,18 @@ pub fn update_fiber_props(node: Element, props: &JsValue) -> Element { for callback_name in callback_name_list.clone().unwrap() { if props.is_object() && props - .dyn_ref::() - .unwrap() - .has_own_property(&callback_name.into()) + .dyn_ref::() + .unwrap() + .has_own_property(&callback_name.into()) { let callback = derive_from_js_value(props, callback_name); - Reflect::set(&element_event_props, &callback_name.into(), &callback).expect("TODO: panic set callback_name"); + Reflect::set(&element_event_props, &callback_name.into(), &callback) + .expect("TODO: panic set callback_name"); } } } - Reflect::set(&node, &ELEMENT_EVENT_PROPS_KEY.into(), &element_event_props).expect("TODO: set ELEMENT_EVENT_PROPS_KEY"); + Reflect::set(&node, &ELEMENT_EVENT_PROPS_KEY.into(), &element_event_props) + .expect("TODO: set ELEMENT_EVENT_PROPS_KEY"); node } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 02863e3..8bf815d 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -9,7 +9,7 @@ use crate::child_fiber::{mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_hooks::render_with_hooks; use crate::fiber_lanes::Lane; -use crate::update_queue::process_update_queue; +use crate::update_queue::{process_update_queue, ReturnOfProcessUpdateQueue}; use crate::work_tags::WorkTag; pub fn begin_work( @@ -18,7 +18,9 @@ pub fn begin_work( ) -> Result>>, JsValue> { let tag = work_in_progress.clone().borrow().tag.clone(); return match tag { - WorkTag::FunctionComponent => update_function_component(work_in_progress.clone(), render_lane), + WorkTag::FunctionComponent => { + update_function_component(work_in_progress.clone(), 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), @@ -34,20 +36,31 @@ fn update_function_component( Ok(work_in_progress.clone().borrow().child.clone()) } -fn update_host_root(work_in_progress: Rc>, render_lane: Lane) -> Option>> { +fn update_host_root( + work_in_progress: Rc>, + render_lane: Lane, +) -> Option>> { let work_in_progress_cloned = work_in_progress.clone(); let base_state; - let update_queue; + let mut pending; { let work_in_progress_borrowed = work_in_progress_cloned.borrow(); base_state = work_in_progress_borrowed.memoized_state.clone(); - update_queue = work_in_progress_borrowed.update_queue.clone(); + pending = work_in_progress_borrowed + .update_queue + .clone() + .unwrap() + .borrow() + .shared + .pending + .clone(); } { - work_in_progress.clone().borrow_mut().memoized_state = - process_update_queue(base_state, update_queue, work_in_progress.clone(), render_lane); + let ReturnOfProcessUpdateQueue { memoized_state, .. } = + process_update_queue(base_state, pending, render_lane); + work_in_progress.clone().borrow_mut().memoized_state = memoized_state; } let next_children = work_in_progress_cloned.borrow().memoized_state.clone(); diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 60c3f75..9c801ec 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -5,6 +5,7 @@ use std::fmt::{Debug, Formatter}; use std::ops::Deref; use std::rc::Rc; +use scheduler::Task; use wasm_bindgen::JsValue; use web_sys::js_sys::Reflect; @@ -12,7 +13,7 @@ use shared::{derive_from_js_value, log, type_of}; use crate::fiber_flags::Flags; use crate::fiber_hooks::{Effect, Hook}; -use crate::fiber_lanes::{Lane, merge_lanes}; +use crate::fiber_lanes::{get_highest_priority, merge_lanes, Lane}; use crate::update_queue::{Update, UpdateQueue}; use crate::work_tags::WorkTag; @@ -75,7 +76,7 @@ impl Debug for FiberNode { self.flags, self.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostRoot => { write!( @@ -84,7 +85,7 @@ impl Debug for FiberNode { WorkTag::HostRoot, self.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostComponent => { write!( @@ -92,7 +93,7 @@ impl Debug for FiberNode { "{:?}(key:{:?}, flags:{:?}, subtreeFlags:{:?})", self._type, self.key, self.flags, self.subtree_flags ) - .expect("print error"); + .expect("print error"); } WorkTag::HostText => { write!( @@ -103,7 +104,7 @@ impl Debug for FiberNode { .unwrap(), self.flags ) - .expect("print error"); + .expect("print error"); } }) } @@ -232,7 +233,9 @@ pub struct FiberRootNode { pub current: Rc>, pub finished_work: Option>>, pub pending_lanes: Lane, - pub finished_lane: Lane, + pub finished_lanes: Lane, + pub callback_node: Option, + pub callback_priority: Lane, pub pending_passive_effects: Rc>, } @@ -243,11 +246,13 @@ impl FiberRootNode { current: host_root_fiber, finished_work: None, pending_lanes: Lane::NoLane, - finished_lane: Lane::NoLane, + finished_lanes: Lane::NoLane, pending_passive_effects: Rc::new(RefCell::new(PendingPassiveEffects { unmount: vec![], update: vec![], })), + callback_node: None, + callback_priority: Lane::NoLane, } } @@ -258,6 +263,21 @@ impl FiberRootNode { pub fn mark_root_updated(&mut self, lane: Lane) { self.pending_lanes = merge_lanes(self.pending_lanes.clone(), lane) } + + pub fn get_next_lanes(&self) -> Lane { + let pending_lanes = self.pending_lanes.clone(); + if pending_lanes == Lane::NoLane { + return Lane::NoLane; + } + + let next_lanes = get_highest_priority(pending_lanes); + + if next_lanes == Lane::NoLane { + return Lane::NoLane; + } + + next_lanes + } } struct QueueItem { @@ -279,9 +299,9 @@ impl Debug for FiberRootNode { queue.push_back(QueueItem::new(Rc::clone(&node), 0)); while let Some(QueueItem { - node: current, - depth, - }) = queue.pop_front() + node: current, + depth, + }) = queue.pop_front() { let current_ref = current.borrow(); @@ -300,9 +320,9 @@ impl Debug for FiberRootNode { } if let Some(QueueItem { - node: next, - depth: next_depth, - }) = queue.front() + node: next, + depth: next_depth, + }) = queue.front() { if *next_depth != depth { writeln!(f, "").expect("print error"); diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index bf7253c..b916b51 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,17 +1,18 @@ use std::cell::RefCell; use std::rc::Rc; +use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; -use wasm_bindgen::prelude::{Closure, wasm_bindgen}; use web_sys::js_sys::{Array, Function, Object, Reflect}; use shared::log; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_flags::Flags; -use crate::fiber_lanes::{Lane, request_update_lane}; +use crate::fiber_lanes::{merge_lanes, request_update_lane, Lane}; use crate::update_queue::{ - create_update, create_update_queue, enqueue_update, process_update_queue, UpdateQueue, + create_update, create_update_queue, enqueue_update, process_update_queue, + ReturnOfProcessUpdateQueue, Update, UpdateQueue, }; use crate::work_loop::schedule_update_on_fiber; @@ -55,7 +56,12 @@ impl Effect { #[derive(Debug, Clone)] pub struct Hook { memoized_state: Option, + // 对于state,保存update相关数据 update_queue: Option>>, + // 对于state,保存开始更新前就存在的updateList(上次更新遗留) + base_queue: Option>>, + // 对于state,基于baseState开始计算更新,与memoizedState的区别在于上次更新是否存在跳过 + base_state: Option, next: Option>>, } @@ -63,11 +69,15 @@ impl Hook { fn new( memoized_state: Option, update_queue: Option>>, + base_queue: Option>>, + base_state: Option, next: Option>>, ) -> Self { Hook { memoized_state, update_queue, + base_queue, + base_state, next, } } @@ -148,7 +158,7 @@ pub fn render_with_hooks( } fn mount_work_in_progress_hook() -> Option>> { - let hook = Rc::new(RefCell::new(Hook::new(None, None, None))); + let hook = Rc::new(RefCell::new(Hook::new(None, None, None, None, None))); unsafe { if WORK_IN_PROGRESS_HOOK.is_none() { if CURRENTLY_RENDERING_FIBER.is_none() { @@ -236,6 +246,8 @@ fn update_work_in_progress_hook() -> Option>> { let new_hook = Rc::new(RefCell::new(Hook::new( current_hook.memoized_state.clone(), current_hook.update_queue.clone(), + current_hook.base_queue.clone(), + current_hook.base_state.clone(), None, ))); @@ -285,7 +297,7 @@ fn mount_state(initial_state: &JsValue) -> Result, JsValue> { let closure = Closure::wrap(Box::new(move |action: &JsValue| { dispatch_set_state(fiber.clone(), (*q_rc_cloned).clone(), action) }) as Box); - let function = closure.as_ref().unchecked_ref::().clone(); + let function: Function = closure.as_ref().unchecked_ref::().clone(); closure.forget(); queue.clone().borrow_mut().dispatch = Some(function.clone()); @@ -302,15 +314,62 @@ fn update_state(_: &JsValue) -> Result, JsValue> { let hook_cloned = hook.clone().unwrap().clone(); let queue = hook_cloned.borrow().update_queue.clone(); - let base_state = hook_cloned.borrow().memoized_state.clone(); + let base_state = hook_cloned.borrow().base_state.clone(); + + let mut base_queue = unsafe { CURRENT_HOOK.clone().unwrap().borrow().base_queue.clone() }; + let pending = queue.clone().unwrap().borrow().shared.pending.clone(); + + if pending.is_some() { + if base_queue.is_some() { + let base_queue = base_queue.clone().unwrap(); + let pending = pending.clone().unwrap(); + // baseQueue = b2 -> b0 -> b1 -> b2 + // pending = p2 -> p0 -> p1 -> p2 + + // b0 + let base_first = base_queue.borrow().next.clone(); + // p0 + let pending_first = pending.borrow().next.clone(); + // baseQueue = b2 -> p0 -> p1 -> p2 + base_queue.borrow_mut().next = pending_first; + // pending = p2 -> b0 -> b1 -> b2 + pending.borrow_mut().next = base_first; + // 拼接完成后:先pending,再baseQueue + // baseQueue = b2 -> p0 -> p1 -> p2 -> b0 -> b1 -> b2 + } + // pending保存在current中,因为commit阶段不完成,current不会变为wip + // 所以可以保证多次render阶段(只要不进入commit)都能从current恢复pending + unsafe { CURRENT_HOOK.clone().unwrap().borrow_mut().base_queue = pending.clone() }; + base_queue = pending; + queue.clone().unwrap().borrow_mut().shared.pending = None; + } - unsafe { - hook_cloned.borrow_mut().memoized_state = process_update_queue( - base_state, - queue.clone(), - CURRENTLY_RENDERING_FIBER.clone().unwrap(), - RENDER_LANE.clone(), - ); + if base_queue.is_some() { + log!("base state is {:?}", base_state); + 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; + hook_cloned.borrow_mut().base_state = new_base_state; + hook_cloned.borrow_mut().base_queue = new_base_queue; } Ok(vec![ @@ -421,7 +480,12 @@ fn update_effect(create: Function, deps: JsValue) { if are_hook_inputs_equal(&prev_deps, &next_deps) { hook.as_ref().unwrap().borrow_mut().memoized_state = - Some(MemoizedState::Effect(push_effect(Flags::Passive, create, destroy, next_deps))); + Some(MemoizedState::Effect(push_effect( + Flags::Passive, + create, + destroy, + next_deps, + ))); return; } } @@ -430,7 +494,11 @@ fn update_effect(create: Function, deps: JsValue) { } } - CURRENTLY_RENDERING_FIBER.as_ref().unwrap().borrow_mut().flags |= Flags::PassiveEffect; + CURRENTLY_RENDERING_FIBER + .as_ref() + .unwrap() + .borrow_mut() + .flags |= Flags::PassiveEffect; log!("CURRENTLY_RENDERING_FIBER.as_ref().unwrap().borrow_mut()"); hook.as_ref().unwrap().clone().borrow_mut().memoized_state = diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index a8216c7..96e5337 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,14 +1,25 @@ use bitflags::bitflags; +use scheduler::{unstable_get_current_priority_level, Priority}; bitflags! { - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct Lane: u8 { - const NoLane = 0b0000000000000000000000000000000; - const SyncLane = 0b0000000000000000000000000000001; - // const AsyncLane = 0b0000000000000000000000000000010; + #[derive(Debug, Clone)] + pub struct Lane: u32 { + const NoLane = 0b0000000000000000000000000000000; + const SyncLane = 0b0000000000000000000000000000001; // onClick + const InputContinuousLane = 0b0000000000000000000000000000010; // Continuous Trigger, example: onScroll + const DefaultLane = 0b0000000000000000000000000000100; // useEffect + const IdleLane = 0b1000000000000000000000000000000; } } +impl PartialEq for Lane { + fn eq(&self, other: &Self) -> bool { + self.bits() == other.bits() + } +} + +impl Eq for Lane {} + pub fn get_highest_priority(lanes: Lane) -> Lane { let lanes = lanes.bits(); let highest_priority = lanes & (lanes.wrapping_neg()); @@ -19,7 +30,33 @@ pub fn merge_lanes(lane_a: Lane, lane_b: Lane) -> Lane { lane_a | lane_b } +pub fn is_subset_of_lanes(set: Lane, subset: Lane) -> bool { + (set & subset.clone()) == subset +} + pub fn request_update_lane() -> Lane { - Lane::SyncLane + let current_scheduler_priority_level = unstable_get_current_priority_level(); + let update_lane = scheduler_priority_to_lane(current_scheduler_priority_level); + update_lane } +pub fn scheduler_priority_to_lane(scheduler_priority: Priority) -> Lane { + match scheduler_priority { + Priority::ImmediatePriority => Lane::SyncLane, + Priority::UserBlockingPriority => Lane::InputContinuousLane, + Priority::NormalPriority => Lane::DefaultLane, + _ => Lane::NoLane, + } +} + +pub fn lanes_to_scheduler_priority(lanes: Lane) -> Priority { + let lane = get_highest_priority(lanes); + if lane == Lane::SyncLane { + return Priority::ImmediatePriority; + } else if lane == Lane::InputContinuousLane { + return Priority::UserBlockingPriority; + } else if lane == Lane::DefaultLane { + return Priority::NormalPriority; + } + Priority::IdlePriority +} diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 6f589ca..ee5e21b 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -20,12 +20,12 @@ mod complete_work; pub mod fiber; mod fiber_flags; mod fiber_hooks; +pub mod fiber_lanes; +mod hook_effect_tags; +mod sync_task_queue; mod update_queue; mod work_loop; mod work_tags; -mod fiber_lanes; -mod sync_task_queue; -mod hook_effect_tags; pub static mut HOST_CONFIG: Option> = None; static mut COMPLETE_WORK: Option = None; diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 708394c..46d7763 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -8,7 +8,7 @@ use shared::log; use crate::fiber::{FiberNode, MemoizedState}; use crate::fiber_hooks::Effect; -use crate::fiber_lanes::Lane; +use crate::fiber_lanes::{is_subset_of_lanes, merge_lanes, Lane}; #[derive(Clone, Debug)] pub struct UpdateAction; @@ -62,32 +62,71 @@ pub fn create_update_queue() -> Rc> { })) } +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( - mut base_state: Option, - update_queue: Option>>, - fiber: Rc>, - render_lane: Lane, -) -> Option { - if update_queue.is_some() { - let update_queue = update_queue.clone().unwrap().clone(); - let pending = update_queue.borrow().shared.pending.clone(); - update_queue.borrow_mut().shared.pending = None; - if pending.is_some() { - let pending_update = pending.clone().unwrap(); - let mut update = pending_update.clone(); - loop { - let update_lane = update.borrow().lane.clone(); - if render_lane == update_lane { - let action = update.borrow().action.clone(); - match action { - None => {} - Some(action) => { - let f = action.dyn_ref::(); - base_state = match f { - None => Some(MemoizedState::MemoizedJsValue(action.clone())), - Some(f) => { + base_state: Option, + pending_update: Option>>, + render_lanes: Lane, +) -> 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(); + // 更新后的baseState(有跳过情况下与memoizedState不同) + let mut new_base_state: Option = base_state; + // 更新后的baseQueue第一个节点 + let mut new_base_queue_first: Option>> = None; + // 更新后的baseQueue最后一个节点 + let mut new_base_queue_last: Option>> = None; + + loop { + let mut update = update_option.clone().unwrap(); + let update_lane = update.borrow().lane.clone(); + if !is_subset_of_lanes(render_lanes.clone(), update_lane.clone()) { + // underpriority + let clone = Rc::new(RefCell::new(create_update( + update.borrow().action.clone().unwrap(), + update_lane.clone(), + ))); + if new_base_queue_last.is_none() { + new_base_queue_first = Some(clone.clone()); + new_base_queue_last = Some(clone.clone()); + new_base_state = result.memoized_state.clone(); + } 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( + update.borrow().action.clone().unwrap(), + update_lane.clone(), + ))); + new_base_queue_last.clone().unwrap().borrow_mut().next = Some(clone.clone()); + 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) = - base_state.as_ref().unwrap() + memoized_state { Some(MemoizedState::MemoizedJsValue( f.call1(&JsValue::null(), base_state).unwrap(), @@ -97,20 +136,32 @@ pub fn process_update_queue( None } } - } + None => Some(MemoizedState::MemoizedJsValue( + f.call1(&JsValue::null(), &JsValue::undefined()).unwrap(), + )), + }, } } - } - let next = update.clone().borrow().next.clone(); - if next.is_none() || Rc::ptr_eq(&next.clone().unwrap(), &pending_update.clone()) { - break; - } - update = next.unwrap(); + }; + } + update_option = update.clone().borrow().next.clone(); + if Rc::ptr_eq( + &update_option.clone().unwrap(), + &pending_update.clone().unwrap(), + ) { + break; } } - } else { - log!("{:?} process_update_queue, update_queue is empty", fiber) + + if new_base_queue_last.is_none() { + new_base_state = result.memoized_state.clone(); + } else { + new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_last.clone(); + } + + result.base_state = new_base_state; + result.base_queue = new_base_queue_last.clone(); } - base_state + result } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 9946138..16524b9 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -2,21 +2,24 @@ use std::cell::RefCell; use std::rc::Rc; use bitflags::bitflags; -use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen::closure::Closure; +use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::Function; -use scheduler::{Priority, unstable_schedule_callback_no_delay}; +use scheduler::{ + unstable_cancel_callback, unstable_schedule_callback_no_delay, unstable_should_yield_to_host, + Priority, +}; use shared::{is_dev, log}; -use crate::{COMMIT_WORK, COMPLETE_WORK, HOST_CONFIG, HostConfig}; use crate::begin_work::begin_work; use crate::commit_work::CommitWork; use crate::fiber::{FiberNode, FiberRootNode, PendingPassiveEffects, StateNode}; -use crate::fiber_flags::{Flags, get_mutation_mask, get_passive_mask}; -use crate::fiber_lanes::{get_highest_priority, Lane, merge_lanes}; +use crate::fiber_flags::{get_mutation_mask, get_passive_mask, Flags}; +use crate::fiber_lanes::{get_highest_priority, lanes_to_scheduler_priority, merge_lanes, Lane}; use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; use crate::work_tags::WorkTag; +use crate::{COMMIT_WORK, COMPLETE_WORK, HOST_CONFIG}; bitflags! { #[derive(Debug, Clone)] @@ -28,11 +31,20 @@ bitflags! { } } +impl PartialEq for ExecutionContext { + fn eq(&self, other: &Self) -> bool { + self.bits() == other.bits() + } +} + static mut WORK_IN_PROGRESS: Option>> = None; static mut WORK_IN_PROGRESS_ROOT_RENDER_LANE: Lane = Lane::NoLane; static mut EXECUTION_CONTEXT: ExecutionContext = ExecutionContext::NoContext; static mut ROOT_DOES_HAVE_PASSIVE_EFFECTS: bool = false; +static ROOT_INCOMPLETE: u8 = 1; +static ROOT_COMPLETED: u8 = 2; + pub fn schedule_update_on_fiber(fiber: Rc>, lane: Lane) { if is_dev() { log!("schedule_update_on_fiber, {:?} {:?}", fiber, lane); @@ -91,32 +103,73 @@ pub fn mark_update_lane_from_fiber_to_root( fn ensure_root_is_scheduled(root: Rc>) { let root_cloned = root.clone(); - let update_lane = get_highest_priority(root.borrow().pending_lanes.clone()); - if update_lane == Lane::NoLane { + let update_lanes = root_cloned.borrow().get_next_lanes(); + let existing_callback = root_cloned.borrow().callback_node.clone(); + if update_lanes == Lane::NoLane { + if existing_callback.is_some() { + unstable_cancel_callback(existing_callback.unwrap()) + } + root.borrow_mut().callback_node = None; + root.borrow_mut().callback_priority = Lane::NoLane; return; } - if update_lane == Lane::SyncLane { + + let cur_priority = get_highest_priority(update_lanes.clone()); + let prev_priority = root.borrow().callback_priority.clone(); + + if cur_priority == prev_priority { + // 有更新在进行,比较该更新与正在进行的更新的优先级 + // 如果优先级相同,则不需要调度新的,退出调度 + return; + } + + if existing_callback.is_some() { + unstable_cancel_callback(existing_callback.unwrap()) + } + + let mut new_callback_node = None; + // 如果使用Scheduler调度,则会存在新的callbackNode,用React微任务调度不会存在 + if cur_priority == Lane::SyncLane { if is_dev() { - log!("Schedule in microtask, priority {:?}", update_lane); + log!("Schedule in microtask, priority {:?}", update_lanes); } + schedule_sync_callback(Box::new(move || { + perform_sync_work_on_root(root_cloned.clone(), update_lanes.clone()); + })); + unsafe { + HOST_CONFIG + .as_ref() + .unwrap() + .schedule_microtask(Box::new(|| flush_sync_callbacks())); + } + } else { + let scheduler_priority = lanes_to_scheduler_priority(cur_priority.clone()); + let closure = Closure::wrap(Box::new(move |did_timeout_js_value: JsValue| { + let did_timeout = did_timeout_js_value.as_bool().unwrap(); + perform_concurrent_work_on_root(root_cloned.clone(), did_timeout) + }) as Box JsValue>); + let function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + new_callback_node = Some(unstable_schedule_callback_no_delay( + scheduler_priority, + function, + )) } - schedule_sync_callback(Box::new(move || { - perform_sync_work_on_root(root_cloned.clone(), update_lane.clone()); - })); - unsafe { - HOST_CONFIG - .as_ref() - .unwrap() - .schedule_microtask(Box::new(|| flush_sync_callbacks())); - } + + root.borrow_mut().callback_node = new_callback_node; + root.borrow_mut().callback_priority = cur_priority; } -fn perform_sync_work_on_root(root: Rc>, lane: Lane) { - let next_lane = get_highest_priority(root.borrow().pending_lanes.clone()); - log!("perform_sync_work_on_root {:?}", next_lane); - if next_lane != Lane::SyncLane { - ensure_root_is_scheduled(root.clone()); - return; +fn render_root(root: Rc>, lanes: Lane, should_time_slice: bool) -> u8 { + if is_dev() { + log!( + "Start {:?} render", + if should_time_slice { + "concurrent" + } else { + "sync" + } + ); } let prev_execution_context: ExecutionContext; @@ -125,10 +178,14 @@ fn perform_sync_work_on_root(root: Rc>, lane: Lane) { EXECUTION_CONTEXT |= ExecutionContext::RenderContext; } - prepare_fresh_stack(root.clone(), lane.clone()); + prepare_fresh_stack(root.clone(), lanes.clone()); loop { - match work_loop() { + match if should_time_slice { + work_loop_concurrent() + } else { + work_loop_sync() + } { Ok(_) => { break; } @@ -139,28 +196,125 @@ fn perform_sync_work_on_root(root: Rc>, lane: Lane) { }; } - log!("{:?}", *root.clone().borrow()); + // log!("render over {:?}", *root.clone().borrow()); + log!("render over {:?}", unsafe { WORK_IN_PROGRESS.clone() }); + // log!("render over"); unsafe { EXECUTION_CONTEXT = prev_execution_context; WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; + + if should_time_slice && WORK_IN_PROGRESS.is_some() { + return ROOT_INCOMPLETE; + } + + if !should_time_slice && WORK_IN_PROGRESS.is_some() { + log!("The WIP is not null when render finishing") + } } - let finished_work = { - root.clone() - .borrow() - .current - .clone() - .borrow() - .alternate - .clone() - }; - root.clone().borrow_mut().finished_work = finished_work; - root.clone().borrow_mut().finished_lane = lane; - - commit_root(root); + + ROOT_COMPLETED } -fn flush_passive_effects(pending_passive_effects: Rc>) { +fn perform_concurrent_work_on_root(root: Rc>, did_timeout: bool) -> JsValue { + unsafe { + if EXECUTION_CONTEXT.clone() + & (ExecutionContext::RenderContext | ExecutionContext::CommitContext) + != ExecutionContext::NoContext + { + panic!("No in React work process {:?}", EXECUTION_CONTEXT) + } + } + + // 开始执行具体工作前,保证上一次的useEffct都执行了 + // 同时要注意useEffect执行时触发的更新优先级是否大于当前更新的优先级 + let did_flush_passive_effects = + flush_passive_effects(root.borrow().pending_passive_effects.clone()); + let cur_callback_node = root.borrow().callback_node.clone(); + + // 这个分支好像走不到 + // if did_flush_passive_effects { + // if root.borrow().callback_node.unwrap().id != cur_callback_node.unwrap().id { + // // 调度了更高优更新,这个更新已经被取消了 + // return null; + // } + // } + + let lanes = root.borrow().get_next_lanes(); + if lanes == Lane::NoLane { + return JsValue::undefined(); + } + + let should_time_slice = !did_timeout; + let exit_status = render_root(root.clone(), lanes.clone(), should_time_slice); + + ensure_root_is_scheduled(root.clone()); + if exit_status == ROOT_INCOMPLETE { + if root.borrow().callback_node.as_ref().unwrap().id != cur_callback_node.unwrap().id { + // 调度了更高优更新,这个更新已经被取消了 + return JsValue::undefined(); + } + let root_cloned = root.clone(); + let closure = Closure::wrap(Box::new(move |did_timeout_js_value: JsValue| { + let did_timeout = did_timeout_js_value.as_bool().unwrap(); + perform_concurrent_work_on_root(root_cloned.clone(), did_timeout) + }) as Box JsValue>); + let function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + return function.into(); + } + + if exit_status == ROOT_COMPLETED { + let finished_work = { + root.clone() + .borrow() + .current + .clone() + .borrow() + .alternate + .clone() + }; + root.clone().borrow_mut().finished_work = finished_work; + root.clone().borrow_mut().finished_lanes = lanes; + + commit_root(root); + } else { + todo!("Unsupported status of concurrent render") + } + + JsValue::undefined() +} + +fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { + let next_lane = get_highest_priority(root.borrow().pending_lanes.clone()); + + if next_lane != Lane::SyncLane { + ensure_root_is_scheduled(root.clone()); + return; + } + + let exit_status = render_root(root.clone(), lanes.clone(), false); + + if exit_status == ROOT_COMPLETED { + let finished_work = { + root.clone() + .borrow() + .current + .clone() + .borrow() + .alternate + .clone() + }; + root.clone().borrow_mut().finished_work = finished_work; + root.clone().borrow_mut().finished_lanes = lanes; + + commit_root(root); + } else { + todo!("Unsupported status of sync render") + } +} + +fn flush_passive_effects(pending_passive_effects: Rc>) -> bool { unsafe { if EXECUTION_CONTEXT .contains(ExecutionContext::RenderContext | ExecutionContext::CommitContext) @@ -168,24 +322,30 @@ fn flush_passive_effects(pending_passive_effects: Rc>) { if cloned.borrow().finished_work.is_none() { return; } - let lane = root.borrow().finished_lane.clone(); + let lanes = root.borrow().finished_lanes.clone(); let finished_work = cloned.borrow().finished_work.clone().unwrap(); cloned.borrow_mut().finished_work = None; - cloned.borrow_mut().finished_lane = Lane::NoLane; + cloned.borrow_mut().finished_lanes = Lane::NoLane; + cloned.borrow_mut().callback_node = None; + cloned.borrow_mut().callback_priority = Lane::NoLane; - cloned.borrow_mut().mark_root_finished(lane.clone()); + cloned.borrow_mut().mark_root_finished(lanes.clone()); - if lane == Lane::NoLane { + if lanes == Lane::NoLane { log!("Commit phase finished lane should not be NoLane") } let subtree_flags = finished_work.borrow().subtree_flags.clone(); let flags = finished_work.borrow().flags.clone(); - + // useEffect let root_cloned = root.clone(); let passive_mask = get_passive_mask(); @@ -246,7 +408,7 @@ fn commit_root(root: Rc>) { cloned.borrow_mut().current = finished_work.clone(); unsafe { - EXECUTION_CONTEXT |= prev_execution_context; + EXECUTION_CONTEXT = prev_execution_context; } } else { cloned.borrow_mut().current = finished_work.clone(); @@ -255,6 +417,7 @@ fn commit_root(root: Rc>) { unsafe { ROOT_DOES_HAVE_PASSIVE_EFFECTS = false; } + ensure_root_is_scheduled(root); } fn prepare_fresh_stack(root: Rc>, lane: Lane) { @@ -268,7 +431,7 @@ fn prepare_fresh_stack(root: Rc>, lane: Lane) { } } -fn work_loop() -> Result<(), JsValue> { +fn work_loop_sync() -> Result<(), JsValue> { unsafe { while WORK_IN_PROGRESS.is_some() { perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; @@ -277,6 +440,16 @@ fn work_loop() -> Result<(), JsValue> { Ok(()) } +fn work_loop_concurrent() -> Result<(), JsValue> { + unsafe { + while WORK_IN_PROGRESS.is_some() && !unstable_should_yield_to_host() { + log!("work_loop_concurrent"); + perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; + } + } + Ok(()) +} + fn perform_unit_of_work(fiber: Rc>) -> Result<(), JsValue> { let next = begin_work(fiber.clone(), unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE.clone() diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index 540febd..bbd5020 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -1,9 +1,9 @@ -use std::any::Any; use std::cmp::{Ordering, PartialEq}; +use shared::log; use wasm_bindgen::prelude::*; +use web_sys::js_sys::{global, Function}; use web_sys::{MessageChannel, MessagePort}; -use web_sys::js_sys::{Function, global}; use crate::heap::{peek, peek_mut, pop, push}; @@ -58,8 +58,8 @@ extern "C" { } #[derive(Clone, Debug)] -struct Task { - id: u32, +pub struct Task { + pub id: u32, callback: JsValue, priority_level: Priority, start_time: f64, @@ -288,6 +288,7 @@ fn flush_work(has_time_remaining: bool, initial_time: f64) -> bool { pub fn unstable_should_yield_to_host() -> bool { unsafe { let time_elapsed = unstable_now() - START_TIME; + log!("start_time: {:?}, now: {:?}", START_TIME, unstable_now()); if time_elapsed < FRAME_YIELD_MS { return false; } @@ -295,6 +296,14 @@ pub fn unstable_should_yield_to_host() -> bool { return true; } +pub fn unstable_run_with_priority(priority_level: Priority, event_handler: &Function) { + let previous_priority_level = unsafe { CURRENT_PRIORITY_LEVEL.clone() }; + unsafe { CURRENT_PRIORITY_LEVEL = priority_level.clone() }; + + event_handler.call0(&JsValue::null()); + unsafe { CURRENT_PRIORITY_LEVEL = previous_priority_level.clone() }; +} + fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result { unsafe { let mut current_time = initial_time; @@ -327,9 +336,7 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result false, - Some(task) => { - task == t - } + Some(task) => task == t, } { pop(&mut TASK_QUEUE); } @@ -403,7 +410,8 @@ fn request_host_timeout(callback: fn(f64), ms: f64) { } } -pub fn unstable_cancel_callback(id: u32) { +pub fn unstable_cancel_callback(task: Task) { + let id = task.id; unsafe { for mut task in &mut TASK_QUEUE { if task.id == id { @@ -419,7 +427,11 @@ pub fn unstable_cancel_callback(id: u32) { } } -pub fn unstable_schedule_callback(priority_level: Priority, callback: Function, delay: f64) -> u32 { +pub fn unstable_schedule_callback( + priority_level: Priority, + callback: Function, + delay: f64, +) -> Task { let current_time = unstable_now(); let mut start_time = current_time; @@ -435,7 +447,7 @@ pub fn unstable_schedule_callback(priority_level: Priority, callback: Function, start_time, expiration_time, ); - let id = new_task.id; + let cloned = new_task.clone(); unsafe { if start_time > current_time { new_task.sort_index = start_time; @@ -464,9 +476,13 @@ pub fn unstable_schedule_callback(priority_level: Priority, callback: Function, } } - id + cloned } -pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> u32 { +pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> Task { unstable_schedule_callback(priority_level, callback, 0.0) } + +pub fn unstable_get_current_priority_level() -> Priority { + unsafe { CURRENT_PRIORITY_LEVEL.clone() } +} diff --git a/readme.md b/readme.md index 34860a4..7815002 100644 --- a/readme.md +++ b/readme.md @@ -33,3 +33,9 @@ [从零实现 React v18,但 WASM 版 - [12] 实现多节点更新流程](https://www.paradeto.com/2024/05/07/big-react-wasm-12/) [从零实现 React v18,但 WASM 版 - [13] 引入 Lane 模型,实现 Batch Update](https://www.paradeto.com/2024/05/11/big-react-wasm-13/) + +[从零实现 React v18,但 WASM 版 - [14] 实现 Scheduler](https://www.paradeto.com/2024/05/16/big-react-wasm-14/) + +[从零实现 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/)