From 93161285ad81cd80079604a63223eb4780f9319b Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Tue, 11 Jun 2024 14:42:58 +0800 Subject: [PATCH 1/6] update readme --- readme.md | 6 ++++++ 1 file changed, 6 insertions(+) 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/) From 4558db73d6c65b58f72cdf94c951db928076b752 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 17 Jun 2024 12:14:57 +0800 Subject: [PATCH 2/6] temp commit --- packages/react-dom/src/lib.rs | 1 + packages/react-dom/src/renderer.rs | 15 ++ packages/react-reconciler/src/fiber.rs | 46 +++- packages/react-reconciler/src/fiber_lanes.rs | 45 +++- packages/react-reconciler/src/update_queue.rs | 124 +++++---- packages/react-reconciler/src/work_loop.rs | 253 ++++++++++++++---- packages/scheduler/src/lib.rs | 29 +- 7 files changed, 391 insertions(+), 122 deletions(-) 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-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_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index a8216c7..cd16139 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,11 +1,20 @@ 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, Eq)] + 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() } } @@ -19,7 +28,31 @@ 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) == 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); + match lane { + Lane::SyncLane => Priority::ImmediatePriority, + Lane::InputContinuousLane => Priority::UserBlockingPriority, + Lane::DefaultLane => Priority::NormalPriority, + _ => Priority::IdlePriority, + } +} diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 708394c..21acaf7 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, Lane}; #[derive(Clone, Debug)] pub struct UpdateAction; @@ -62,55 +62,87 @@ pub fn create_update_queue() -> Rc> { })) } +struct ReturnOfProcessUpdateQueue { + memoized_state: Option, + base_state: Option, + base_queue: Option>>, + 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) => { - if let MemoizedState::MemoizedJsValue(base_state) = - base_state.as_ref().unwrap() - { - Some(MemoizedState::MemoizedJsValue( - f.call1(&JsValue::null(), base_state).unwrap(), - )) - } else { - log!("process_update_queue, base_state is not JsValue"); - None - } - } - } - } - } - } - let next = update.clone().borrow().next.clone(); - if next.is_none() || Rc::ptr_eq(&next.clone().unwrap(), &pending_update.clone()) { - break; - } - update = next.unwrap(); + pending_update: Option, + render_lanes: Lane, +) -> Option { + let result = ReturnOfProcessUpdateQueue { + memoized_state: base_state, + base_state, + base_queue: None, + skipped_update_lanes: Lane::NoLane, + }; + + if pending_update.is_some() { + let update = pending_update.clone().unwrap(); + // 更新后的baseState(有跳过情况下与memoizedState不同) + let new_base_state = base_state; + // 更新后的baseQueue第一个节点 + let new_base_queue_first: Option = None; + // 更新后的baseQueue最后一个节点 + let new_base_queue_last: Option = None; + + loop { + let update_lane = update.lane; + if !is_subset_of_lanes(render_lanes, update_lane) { + // underpriority + let clone = create_update(update.action.unwrap(), update.lane); } } - } else { - log!("{:?} process_update_queue, update_queue is empty", fiber) } - base_state + None + // 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) => { + // if let MemoizedState::MemoizedJsValue(base_state) = + // base_state.as_ref().unwrap() + // { + // Some(MemoizedState::MemoizedJsValue( + // f.call1(&JsValue::null(), base_state).unwrap(), + // )) + // } else { + // log!("process_update_queue, base_state is not JsValue"); + // None + // } + // } + // } + // } + // } + // } + // let next = update.clone().borrow().next.clone(); + // if next.is_none() || Rc::ptr_eq(&next.clone().unwrap(), &pending_update.clone()) { + // break; + // } + // update = next.unwrap(); + // } + // } + // } else { + // log!("{:?} process_update_queue, update_queue is empty", fiber) + // } + + // base_state } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 9946138..18578f4 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); + 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; } @@ -142,25 +199,111 @@ fn perform_sync_work_on_root(root: Rc>, lane: Lane) { log!("{:?}", *root.clone().borrow()); unsafe { + 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") + } + EXECUTION_CONTEXT = prev_execution_context; WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; } - 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) { + unsafe { + if EXECUTION_CONTEXT.clone() + & (ExecutionContext::RenderContext | ExecutionContext::CommitContext) + != ExecutionContext::NoContext + { + panic!("No in React work process") + } + } + + // 开始执行具体工作前,保证上一次的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; + } + + 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; + } + return perform_concurrent_work_on_root(root, 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 concurrent render") + } +} + +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 +311,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(); @@ -255,6 +406,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 +420,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 +429,15 @@ fn work_loop() -> Result<(), JsValue> { Ok(()) } +fn work_loop_concurrent() -> Result<(), JsValue> { + unsafe { + while WORK_IN_PROGRESS.is_some() && !unstable_should_yield_to_host() { + 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..8c00a01 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -2,8 +2,8 @@ use std::any::Any; use std::cmp::{Ordering, PartialEq}; 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, @@ -327,9 +327,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 +401,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 +418,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 +438,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 +467,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() } +} From e8184e5ddbe86e4df448058b2ebfd4ba45a3b119 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 17 Jun 2024 17:40:24 +0800 Subject: [PATCH 3/6] blog-17, fix bugs --- examples/hello-world/src/App.tsx | 47 +++--- examples/hello-world/src/main.tsx | 18 +-- packages/react-dom/src/synthetic_event.rs | 47 ++++-- packages/react-reconciler/src/begin_work.rs | 27 +++- packages/react-reconciler/src/fiber_hooks.rs | 98 ++++++++++-- packages/react-reconciler/src/fiber_lanes.rs | 18 ++- packages/react-reconciler/src/lib.rs | 6 +- packages/react-reconciler/src/update_queue.rs | 149 ++++++++++-------- packages/react-reconciler/src/work_loop.rs | 8 +- packages/scheduler/src/lib.rs | 9 +- 10 files changed, 269 insertions(+), 158 deletions(-) diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 3b79989..100f439 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,37 +1,26 @@ -import {useEffect, useState} from 'react' +import {useState} from 'react' function App() { - const [num, updateNum] = useState(0); - return ( -
    { - updateNum((num: number) => num + 1); - }} - > - - {num === 1 ? null : } -
- ); -} + const [num, updateNum] = useState(0) + const len = 1 -function Child1({num}: { num: number }) { - useEffect(() => { - console.log('child1 create') - return () => { - console.log('child1 destroy') - } - }, [num]); - return
child1 {num}
; + console.log('num', num) + return ( +
    { + updateNum((num: number) => num + 1) + }}> + {Array(len) + .fill(1) + .map((_, i) => { + return + })} +
+ ) } -function Child2({num}: { num: number }) { - useEffect(() => { - console.log('child2 create') - return () => { - console.log('child2 destroy') - } - }, [num]); - return
child2 {num}
; +function Child({i}) { + return

i am child {i}

} export default App 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/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index b0854d0..e9a0d76 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::Lane; use shared::{derive_from_js_value, is_dev, log}; static VALID_EVENT_TYPE_LIST: [&str; 1] = ["click"]; @@ -23,8 +25,17 @@ impl Paths { } } +// fn event_type_to_event_priority(event_type: &str) -> Priority { +// match event_type { +// "click" | "keydown" | "keyup" => Lane::SyncLane, +// "scroll" => Lane::InputContinuousLane, +// _ => Lane::DefaultLane, +// } +// } + 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 +44,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 +179,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_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 cd16139..96e5337 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -2,7 +2,7 @@ use bitflags::bitflags; use scheduler::{unstable_get_current_priority_level, Priority}; bitflags! { - #[derive(Debug, Clone, Eq)] + #[derive(Debug, Clone)] pub struct Lane: u32 { const NoLane = 0b0000000000000000000000000000000; const SyncLane = 0b0000000000000000000000000000001; // onClick @@ -18,6 +18,8 @@ impl PartialEq for Lane { } } +impl Eq for Lane {} + pub fn get_highest_priority(lanes: Lane) -> Lane { let lanes = lanes.bits(); let highest_priority = lanes & (lanes.wrapping_neg()); @@ -29,7 +31,7 @@ pub fn merge_lanes(lane_a: Lane, lane_b: Lane) -> Lane { } pub fn is_subset_of_lanes(set: Lane, subset: Lane) -> bool { - (set & subset) == subset + (set & subset.clone()) == subset } pub fn request_update_lane() -> Lane { @@ -49,10 +51,12 @@ pub fn scheduler_priority_to_lane(scheduler_priority: Priority) -> Lane { pub fn lanes_to_scheduler_priority(lanes: Lane) -> Priority { let lane = get_highest_priority(lanes); - match lane { - Lane::SyncLane => Priority::ImmediatePriority, - Lane::InputContinuousLane => Priority::UserBlockingPriority, - Lane::DefaultLane => Priority::NormalPriority, - _ => Priority::IdlePriority, + 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 21acaf7..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::{is_subset_of_lanes, Lane}; +use crate::fiber_lanes::{is_subset_of_lanes, merge_lanes, Lane}; #[derive(Clone, Debug)] pub struct UpdateAction; @@ -62,87 +62,106 @@ pub fn create_update_queue() -> Rc> { })) } -struct ReturnOfProcessUpdateQueue { - memoized_state: Option, - base_state: Option, - base_queue: Option>>, - skipped_update_lanes: Lane, +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, - pending_update: Option, + base_state: Option, + pending_update: Option>>, render_lanes: Lane, -) -> Option { - let result = ReturnOfProcessUpdateQueue { - memoized_state: base_state, - base_state, +) -> 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 update = pending_update.clone().unwrap(); + let mut update_option = pending_update.clone(); // 更新后的baseState(有跳过情况下与memoizedState不同) - let new_base_state = base_state; + let mut new_base_state: Option = base_state; // 更新后的baseQueue第一个节点 - let new_base_queue_first: Option = None; + let mut new_base_queue_first: Option>> = None; // 更新后的baseQueue最后一个节点 - let new_base_queue_last: Option = None; + let mut new_base_queue_last: Option>> = None; loop { - let update_lane = update.lane; - if !is_subset_of_lanes(render_lanes, update_lane) { + 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 = create_update(update.action.unwrap(), update.lane); + 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) = + 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(), + ) { + break; } } + + 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(); } - None - // 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) => { - // if let MemoizedState::MemoizedJsValue(base_state) = - // base_state.as_ref().unwrap() - // { - // Some(MemoizedState::MemoizedJsValue( - // f.call1(&JsValue::null(), base_state).unwrap(), - // )) - // } else { - // log!("process_update_queue, base_state is not JsValue"); - // None - // } - // } - // } - // } - // } - // } - // let next = update.clone().borrow().next.clone(); - // if next.is_none() || Rc::ptr_eq(&next.clone().unwrap(), &pending_update.clone()) { - // break; - // } - // update = next.unwrap(); - // } - // } - // } else { - // log!("{:?} process_update_queue, update_queue is empty", fiber) - // } - - // base_state + result } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 18578f4..fe08cc9 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -210,7 +210,9 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: EXECUTION_CONTEXT = prev_execution_context; WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; } - + log!("EXECUTION_CONTEXT is {:?}", unsafe { + EXECUTION_CONTEXT.clone() + }); ROOT_COMPLETED } @@ -220,7 +222,7 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout & (ExecutionContext::RenderContext | ExecutionContext::CommitContext) != ExecutionContext::NoContext { - panic!("No in React work process") + panic!("No in React work process {:?}", EXECUTION_CONTEXT) } } @@ -397,7 +399,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(); diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index 8c00a01..61be5b7 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -1,4 +1,3 @@ -use std::any::Any; use std::cmp::{Ordering, PartialEq}; use wasm_bindgen::prelude::*; @@ -295,6 +294,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; From bc3f9e5aed462444677e1a1fd51f16cffcf8ad5c Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 17 Jun 2024 19:07:55 +0800 Subject: [PATCH 4/6] blog-17, concurrent mode --- examples/hello-world/src/App.tsx | 2 +- packages/react-dom/src/synthetic_event.rs | 31 ++++++++++--------- packages/react-reconciler/src/work_loop.rs | 36 ++++++++++++++-------- packages/scheduler/src/lib.rs | 4 ++- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 100f439..74e78b8 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -2,7 +2,7 @@ import {useState} from 'react' function App() { const [num, updateNum] = useState(0) - const len = 1 + const len = 100 console.log('num', num) return ( diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index e9a0d76..23a6038 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -5,7 +5,7 @@ use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Function, Object, Reflect}; use web_sys::{Element, Event}; -use react_reconciler::fiber_lanes::Lane; +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"]; @@ -25,13 +25,14 @@ impl Paths { } } -// fn event_type_to_event_priority(event_type: &str) -> Priority { -// match event_type { -// "click" | "keydown" | "keyup" => Lane::SyncLane, -// "scroll" => Lane::InputContinuousLane, -// _ => Lane::DefaultLane, -// } -// } +fn event_type_to_event_priority(event_type: &str) -> Priority { + let lane = match event_type { + "click" | "keydown" | "keyup" => Lane::InputContinuousLane, + "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)) @@ -62,13 +63,13 @@ fn create_synthetic_event(e: Event) -> Event { fn trigger_event_flow(paths: Vec, se: &Event) { for callback in paths { - // 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"); + 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() diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index fe08cc9..16524b9 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -146,8 +146,8 @@ fn ensure_root_is_scheduled(root: Rc>) { 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); + 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( @@ -196,9 +196,14 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: }; } - 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; } @@ -206,17 +211,12 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: if !should_time_slice && WORK_IN_PROGRESS.is_some() { log!("The WIP is not null when render finishing") } - - EXECUTION_CONTEXT = prev_execution_context; - WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; } - log!("EXECUTION_CONTEXT is {:?}", unsafe { - EXECUTION_CONTEXT.clone() - }); + ROOT_COMPLETED } -fn perform_concurrent_work_on_root(root: Rc>, did_timeout: bool) { +fn perform_concurrent_work_on_root(root: Rc>, did_timeout: bool) -> JsValue { unsafe { if EXECUTION_CONTEXT.clone() & (ExecutionContext::RenderContext | ExecutionContext::CommitContext) @@ -242,7 +242,7 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout let lanes = root.borrow().get_next_lanes(); if lanes == Lane::NoLane { - return; + return JsValue::undefined(); } let should_time_slice = !did_timeout; @@ -252,9 +252,16 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout if exit_status == ROOT_INCOMPLETE { if root.borrow().callback_node.as_ref().unwrap().id != cur_callback_node.unwrap().id { // 调度了更高优更新,这个更新已经被取消了 - return; + return JsValue::undefined(); } - return perform_concurrent_work_on_root(root, false); + 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 { @@ -274,6 +281,8 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout } else { todo!("Unsupported status of concurrent render") } + + JsValue::undefined() } fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { @@ -434,6 +443,7 @@ fn work_loop_sync() -> Result<(), JsValue> { 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())?; } } diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index 61be5b7..c89d9e6 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -1,5 +1,6 @@ use std::cmp::{Ordering, PartialEq}; +use shared::log; use wasm_bindgen::prelude::*; use web_sys::js_sys::{global, Function}; use web_sys::{MessageChannel, MessagePort}; @@ -8,7 +9,7 @@ use crate::heap::{peek, peek_mut, pop, push}; mod heap; -static FRAME_YIELD_MS: f64 = 5.0; +static FRAME_YIELD_MS: f64 = 8.0; static mut TASK_ID_COUNTER: u32 = 1; static mut TASK_QUEUE: Vec = vec![]; static mut TIMER_QUEUE: Vec = vec![]; @@ -287,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; } From a9463704deb82ce206106ae316415626891392b4 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 19 Jun 2024 16:51:32 +0800 Subject: [PATCH 5/6] change time slice to 5 ms --- packages/scheduler/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index c89d9e6..bbd5020 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -9,7 +9,7 @@ use crate::heap::{peek, peek_mut, pop, push}; mod heap; -static FRAME_YIELD_MS: f64 = 8.0; +static FRAME_YIELD_MS: f64 = 5.0; static mut TASK_ID_COUNTER: u32 = 1; static mut TASK_QUEUE: Vec = vec![]; static mut TIMER_QUEUE: Vec = vec![]; From 004d07841617cf4d027e21c126648960784af713 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 19 Jun 2024 18:23:23 +0800 Subject: [PATCH 6/6] try Update jump in queue --- examples/hello-world/src/App.tsx | 71 ++++++++++++++++------- packages/react-dom/src/synthetic_event.rs | 2 +- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 74e78b8..ad1bd41 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1,26 +1,55 @@ -import {useState} from 'react' +import {useState, useEffect} from 'react' +// function App() { +// const [num, updateNum] = useState(0) +// const len = 100 -function App() { - const [num, updateNum] = useState(0) - const len = 100 +// console.log('num', num) +// return ( +//
    { +// updateNum((num: number) => num + 1) +// }}> +// {Array(len) +// .fill(1) +// .map((_, i) => { +// return +// })} +//
+// ) +// } - console.log('num', num) - return ( -
    { - updateNum((num: number) => num + 1) - }}> - {Array(len) - .fill(1) - .map((_, i) => { - return - })} -
- ) -} +// function Child({i}) { +// return

i am child {i}

+// } -function Child({i}) { - return

i am child {i}

+// export default App + +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/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index 23a6038..dd89207 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -27,7 +27,7 @@ impl Paths { fn event_type_to_event_priority(event_type: &str) -> Priority { let lane = match event_type { - "click" | "keydown" | "keyup" => Lane::InputContinuousLane, + "click" | "keydown" | "keyup" => Lane::SyncLane, "scroll" => Lane::InputContinuousLane, _ => Lane::DefaultLane, };