From f38c246d3bcac88b892a1b4f5cb6ad98505deaee Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 10 May 2024 11:06:35 +0800 Subject: [PATCH 1/3] blog-13: schedule_microtask --- examples/hello-world/src/main.tsx | 3 +- packages/react-dom/src/host_config.rs | 55 +++++++++++++++++++++++++++ packages/react-dom/src/lib.rs | 17 +++++++++ packages/react-reconciler/src/lib.rs | 4 +- 4 files changed, 75 insertions(+), 4 deletions(-) diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index fd0b0ef..25cf44b 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,6 +1,5 @@ import {createRoot} from 'react-dom' -import App from "./App.tsx"; const root = createRoot(document.getElementById("root")) -root.render() +// root.render() diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index 1e15926..d2a1098 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -1,8 +1,11 @@ use std::any::Any; +use std::cell::RefCell; use std::rc::Rc; +use js_sys::{Function, global, Promise}; use js_sys::JSON::stringify; use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; use web_sys::{Node, window}; use react_reconciler::HostConfig; @@ -31,6 +34,20 @@ pub fn to_string(js_value: &JsValue) -> String { }) } +#[wasm_bindgen] +extern "C" { + type Global; + + #[wasm_bindgen] + fn queueMicrotask(closure: &JsValue); + + #[wasm_bindgen] + fn setTimeout(closure: &JsValue, timeout: i32); + + #[wasm_bindgen(method, getter, js_name = queueMicrotask)] + fn hasQueueMicrotask(this: &Global) -> JsValue; +} + impl HostConfig for ReactDomHostConfig { fn create_text_instance(&self, content: &JsValue) -> Rc { let window = window().expect("no global `window` exists"); @@ -135,4 +152,42 @@ impl HostConfig for ReactDomHostConfig { } } } + + fn schedule_microtask(&self, callback: Box) { + let closure = Rc::new(RefCell::new(Some(Closure::wrap(callback)))); + + if global() + .unchecked_into::() + .hasQueueMicrotask() + .is_function() + { + let closure_clone = closure.clone(); + queueMicrotask(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::()); + closure_clone.borrow_mut().take().unwrap_throw().forget(); + } else if js_sys::Reflect::get(&*global(), &JsValue::from_str("Promise")) + .map(|value| value.is_function()) + .unwrap_or(false) + { + let promise = Promise::resolve(&JsValue::NULL); + let closure_clone = closure.clone(); + + let c = Closure::wrap(Box::new(move |v| { // Access the underlying JsValue and cast it to a Function + // Access the underlying JsValue and cast it to a Function + let b = closure_clone.borrow_mut(); + let function = b.as_ref().unwrap().as_ref().unchecked_ref::(); + // Call the function with no arguments + let _ = function.call0(&JsValue::NULL); + }) as Box); + let _ = promise.then(&c); + c.forget(); + } else { + let closure_clone = closure.clone(); + setTimeout(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::(), 0); + closure_clone.borrow_mut().take().unwrap_throw().forget(); + } + + // if let Some(closure) = closure.borrow_mut().take() { + // closure.forget(); + // } + } } diff --git a/packages/react-dom/src/lib.rs b/packages/react-dom/src/lib.rs index 84b5cd4..c6a4592 100644 --- a/packages/react-dom/src/lib.rs +++ b/packages/react-dom/src/lib.rs @@ -4,6 +4,7 @@ use wasm_bindgen::prelude::*; use web_sys::Node; use react_reconciler::Reconciler; +use shared::log; use crate::host_config::ReactDomHostConfig; use crate::renderer::Renderer; @@ -14,10 +15,26 @@ mod renderer; mod synthetic_event; mod utils; +struct T { + name: String, +} + +impl T { + fn new() -> Self { + T { name: "ayou".to_string() } + } + + fn callback(&self) { + log!("{:?}", self.name); + } +} + #[wasm_bindgen(js_name = createRoot)] pub fn create_root(container: &JsValue) -> Renderer { set_panic_hook(); let reconciler = Reconciler::new(Rc::new(ReactDomHostConfig)); + let t = Box::new(T::new()); + reconciler.host_config.schedule_microtask(Box::new(move || t.callback())); let node = match container.clone().dyn_into::() { Ok(node) => node, Err(_) => { diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 3a7cb69..19a35c7 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -28,17 +28,17 @@ pub trait HostConfig { fn append_child_to_container(&self, child: Rc, parent: Rc); fn remove_child(&self, child: Rc, container: Rc); fn commit_text_update(&self, text_instance: Rc, content: String); - fn insert_child_to_container( &self, child: Rc, container: Rc, before: Rc, ); + fn schedule_microtask(&self, callback: Box); } pub struct Reconciler { - host_config: Rc, + pub host_config: Rc, } impl Reconciler { From 399f3ec96451654c08a603bfac2ebb81e6e7de60 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 10 May 2024 18:27:47 +0800 Subject: [PATCH 2/3] blog-13: schedule_sync_callback --- packages/react-dom/src/host_config.rs | 9 +-- packages/react-reconciler/src/begin_work.rs | 13 ++-- packages/react-reconciler/src/fiber.rs | 19 ++++- packages/react-reconciler/src/fiber_hooks.rs | 20 ++--- packages/react-reconciler/src/fiber_lanes.rs | 25 +++++++ packages/react-reconciler/src/lib.rs | 18 +++-- packages/react-reconciler/src/main.rs | 9 +++ .../react-reconciler/src/sync_task_queue.rs | 18 +++++ packages/react-reconciler/src/update_queue.rs | 59 ++++++++++----- packages/react-reconciler/src/work_loop.rs | 74 +++++++++++++++---- packages/shared/src/lib.rs | 3 +- 11 files changed, 198 insertions(+), 69 deletions(-) create mode 100644 packages/react-reconciler/src/fiber_lanes.rs create mode 100644 packages/react-reconciler/src/main.rs create mode 100644 packages/react-reconciler/src/sync_task_queue.rs diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index d2a1098..f7e898b 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -170,12 +170,9 @@ impl HostConfig for ReactDomHostConfig { { let promise = Promise::resolve(&JsValue::NULL); let closure_clone = closure.clone(); - - let c = Closure::wrap(Box::new(move |v| { // Access the underlying JsValue and cast it to a Function - // Access the underlying JsValue and cast it to a Function + let c = Closure::wrap(Box::new(move |_v| { let b = closure_clone.borrow_mut(); let function = b.as_ref().unwrap().as_ref().unchecked_ref::(); - // Call the function with no arguments let _ = function.call0(&JsValue::NULL); }) as Box); let _ = promise.then(&c); @@ -185,9 +182,5 @@ impl HostConfig for ReactDomHostConfig { setTimeout(&closure_clone.borrow_mut().as_ref().unwrap().as_ref().unchecked_ref::(), 0); closure_clone.borrow_mut().take().unwrap_throw().forget(); } - - // if let Some(closure) = closure.borrow_mut().take() { - // closure.forget(); - // } } } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 6560b28..02863e3 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -8,16 +8,18 @@ use shared::derive_from_js_value; 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::work_tags::WorkTag; pub fn begin_work( work_in_progress: Rc>, + render_lane: Lane, ) -> Result>>, JsValue> { let tag = work_in_progress.clone().borrow().tag.clone(); return match tag { - WorkTag::FunctionComponent => update_function_component(work_in_progress.clone()), - WorkTag::HostRoot => Ok(update_host_root(work_in_progress.clone())), + 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), }; @@ -25,13 +27,14 @@ pub fn begin_work( fn update_function_component( work_in_progress: Rc>, + render_lane: Lane, ) -> Result>>, JsValue> { - let next_children = render_with_hooks(work_in_progress.clone())?; + let next_children = render_with_hooks(work_in_progress.clone(), render_lane)?; reconcile_children(work_in_progress.clone(), Some(next_children)); Ok(work_in_progress.clone().borrow().child.clone()) } -fn update_host_root(work_in_progress: Rc>) -> 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; @@ -44,7 +47,7 @@ fn update_host_root(work_in_progress: Rc>) -> Option, pub current: Rc>, pub finished_work: Option>>, + pub pending_lanes: Lane, + pub finished_lane: Lane, } impl FiberRootNode { @@ -235,8 +240,18 @@ impl FiberRootNode { container, current: host_root_fiber, finished_work: None, + pending_lanes: Lane::NoLane, + finished_lane: Lane::NoLane, } } + + pub fn mark_root_finished(&mut self, lane: Lane) { + self.pending_lanes &= !lane; + } + + pub fn mark_root_updated(&mut self, lane: Lane) { + self.pending_lanes = merge_lanes(self.pending_lanes.clone(), lane) + } } struct QueueItem { @@ -263,7 +278,7 @@ impl Debug for FiberRootNode { while let Some(QueueItem { node: current, depth }) = queue.pop_front() { let current_ref = current.borrow(); - write!(f, "{:?}", current_ref); + write!(f, "{:?}", current_ref).expect("TODO: panic print FiberNode"); if let Some(ref child) = current_ref.child { queue.push_back(QueueItem::new(Rc::clone(child), depth + 1)); diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 2929b24..939df10 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -8,10 +8,11 @@ use web_sys::js_sys::{Function, Object, Reflect}; use shared::log; use crate::fiber::{FiberNode, MemoizedState}; +use crate::fiber_lanes::{Lane, request_update_lane}; use crate::update_queue::{ create_update, create_update_queue, enqueue_update, process_update_queue, UpdateQueue, }; -use crate::work_loop::WorkLoop; +use crate::WORK_LOOP; #[wasm_bindgen] extern "C" { @@ -21,7 +22,7 @@ extern "C" { static mut CURRENTLY_RENDERING_FIBER: Option>> = None; static mut WORK_IN_PROGRESS_HOOK: Option>> = None; static mut CURRENT_HOOK: Option>> = None; -pub static mut WORK_LOOP: Option>> = None; +static mut RENDER_LANE: Lane = Lane::NoLane; #[derive(Debug, Clone)] pub struct Hook { @@ -56,9 +57,10 @@ fn update_hooks_to_dispatcher(is_update: bool) { updateDispatcher(&object.into()); } -pub fn render_with_hooks(work_in_progress: Rc>) -> Result { +pub fn render_with_hooks(work_in_progress: Rc>, lane: Lane) -> Result { unsafe { CURRENTLY_RENDERING_FIBER = Some(work_in_progress.clone()); + RENDER_LANE = lane; } let work_in_progress_cloned = work_in_progress.clone(); @@ -89,6 +91,7 @@ pub fn render_with_hooks(work_in_progress: Rc>) -> Result Result, JsValue> { base_state, queue.clone(), CURRENTLY_RENDERING_FIBER.clone().unwrap(), + RENDER_LANE.clone(), ); } log!("memoized_state {:?}", hook_cloned.borrow().memoized_state); @@ -281,14 +285,10 @@ fn dispatch_set_state( update_queue: Rc>, action: &JsValue, ) { - let update = create_update(action.clone()); + let lane = request_update_lane(); + let update = create_update(action.clone(), lane.clone()); enqueue_update(update_queue.clone(), update); unsafe { - WORK_LOOP - .as_ref() - .unwrap() - .clone() - .borrow() - .schedule_update_on_fiber(fiber.clone()); + WORK_LOOP.schedule_update_on_fiber(fiber.clone(), lane); } } diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs new file mode 100644 index 0000000..a8216c7 --- /dev/null +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -0,0 +1,25 @@ +use bitflags::bitflags; + +bitflags! { + #[derive(Debug, Clone, PartialEq, Eq)] + pub struct Lane: u8 { + const NoLane = 0b0000000000000000000000000000000; + const SyncLane = 0b0000000000000000000000000000001; + // const AsyncLane = 0b0000000000000000000000000000010; + } +} + +pub fn get_highest_priority(lanes: Lane) -> Lane { + let lanes = lanes.bits(); + let highest_priority = lanes & (lanes.wrapping_neg()); + Lane::from_bits_truncate(highest_priority) +} + +pub fn merge_lanes(lane_a: Lane, lane_b: Lane) -> Lane { + lane_a | lane_b +} + +pub fn request_update_lane() -> Lane { + Lane::SyncLane +} + diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 19a35c7..2cf58ce 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -5,7 +5,8 @@ use std::rc::Rc; use wasm_bindgen::JsValue; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; -use crate::fiber_hooks::WORK_LOOP; +// use crate::fiber_hooks::{WORK_LOOP as Fiber_HOOKS}; +use crate::fiber_lanes::Lane; use crate::update_queue::{create_update, create_update_queue, enqueue_update}; use crate::work_loop::WorkLoop; use crate::work_tags::WorkTag; @@ -20,6 +21,10 @@ mod fiber_hooks; mod update_queue; mod work_loop; mod work_tags; +mod fiber_lanes; +mod sync_task_queue; + +pub static mut WORK_LOOP: WorkLoop = WorkLoop { complete_work: None, host_config: None }; pub trait HostConfig { fn create_text_instance(&self, content: &JsValue) -> Rc; @@ -63,19 +68,16 @@ impl Reconciler { pub fn update_container(&self, element: JsValue, root: Rc>) -> JsValue { let host_root_fiber = Rc::clone(&root).borrow().current.clone(); - let update = create_update(element.clone()); + let root_render_priority = Lane::SyncLane; + let update = create_update(element.clone(), root_render_priority.clone()); enqueue_update( host_root_fiber.borrow().update_queue.clone().unwrap(), update, ); - let work_loop = Rc::new(RefCell::new(WorkLoop::new(self.host_config.clone()))); unsafe { - WORK_LOOP = Some(work_loop.clone()); + WORK_LOOP = WorkLoop::new(self.host_config.clone()); + WORK_LOOP.schedule_update_on_fiber(host_root_fiber, root_render_priority); } - work_loop - .clone() - .borrow() - .schedule_update_on_fiber(host_root_fiber); element.clone() } } diff --git a/packages/react-reconciler/src/main.rs b/packages/react-reconciler/src/main.rs new file mode 100644 index 0000000..9270a6c --- /dev/null +++ b/packages/react-reconciler/src/main.rs @@ -0,0 +1,9 @@ +use crate::fiber_lanes::Lane; + +mod fiber_lanes; + +fn main() { + let mut a = Lane::NoLane | Lane::SyncLane; + println!("{:?}", a); + println!("{:?}", a == !Lane::NoLane) +} \ No newline at end of file diff --git a/packages/react-reconciler/src/sync_task_queue.rs b/packages/react-reconciler/src/sync_task_queue.rs new file mode 100644 index 0000000..8216fc8 --- /dev/null +++ b/packages/react-reconciler/src/sync_task_queue.rs @@ -0,0 +1,18 @@ +static mut SYNC_QUEUE: Vec> = vec![]; +static mut IS_FLUSHING_SYNC_QUEUE: bool = false; + +pub fn schedule_sync_callback(callback: Box) { + unsafe { SYNC_QUEUE.push(callback) } +} + +pub fn flush_sync_callbacks() { + unsafe { + if !IS_FLUSHING_SYNC_QUEUE && !SYNC_QUEUE.is_empty() { + IS_FLUSHING_SYNC_QUEUE = true; + for callback in SYNC_QUEUE.iter_mut() { + callback(); + } + SYNC_QUEUE = vec![]; + } + } +} diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index e80a856..1e34759 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -7,6 +7,7 @@ use web_sys::js_sys::Function; use shared::log; use crate::fiber::{FiberNode, MemoizedState}; +use crate::fiber_lanes::Lane; #[derive(Clone, Debug)] pub struct UpdateAction; @@ -14,11 +15,13 @@ pub struct UpdateAction; #[derive(Clone, Debug)] pub struct Update { pub action: Option, + pub lane: Lane, + pub next: Option>>, } #[derive(Clone, Debug)] pub struct UpdateType { - pub pending: Option, + pub pending: Option>>, } #[derive(Clone, Debug)] @@ -27,14 +30,16 @@ pub struct UpdateQueue { pub dispatch: Option, } -pub fn create_update(action: JsValue) -> Update { +pub fn create_update(action: JsValue, lane: Lane) -> Update { Update { action: Some(action), + lane, + next: None, } } pub fn enqueue_update(update_queue: Rc>, update: Update) { - update_queue.borrow_mut().shared.pending = Option::from(update); + update_queue.borrow_mut().shared.pending = Option::from(Rc::new(RefCell::new(update))); } pub fn create_update_queue() -> Rc> { @@ -48,33 +53,47 @@ 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 action = pending.unwrap().action; - 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 action = pending.unwrap().action; + 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 { diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 050a6ea..8308d6e 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -3,47 +3,64 @@ use std::rc::Rc; use wasm_bindgen::JsValue; -use shared::log; +use shared::{is_dev, log}; use crate::begin_work::begin_work; use crate::commit_work::CommitWork; use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::fiber_flags::get_mutation_mask; +use crate::fiber_lanes::{get_highest_priority, Lane, merge_lanes}; use crate::HostConfig; +use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; use crate::work_tags::WorkTag; static mut WORK_IN_PROGRESS: Option>> = None; +static mut WORK_IN_PROGRESS_ROOT_RENDER_LANE: Lane = Lane::NoLane; pub struct WorkLoop { - // make the first param to be &self not &mut self - // work_in_progress: Option>>, - complete_work: CompleteWork, + pub(crate) complete_work: Option, + pub(crate) host_config: Option>, } impl WorkLoop { pub fn new(host_config: Rc) -> Self { Self { - // work_in_progress: None, - complete_work: CompleteWork::new(host_config), + complete_work: Some(CompleteWork::new(host_config.clone())), + host_config: Some(host_config), } } - pub fn schedule_update_on_fiber(&self, fiber: Rc>) { - let root = self.mark_update_lane_from_fiber_to_root(fiber); + pub fn schedule_update_on_fiber(&'static mut self, fiber: Rc>, lane: Lane) { + if is_dev() { + log!("schedule_update_on_fiber, {:?} {:?}", fiber, lane); + } + + let root = self.mark_update_lane_from_fiber_to_root(fiber, lane.clone()); if root.is_none() { return; } + root.as_ref().unwrap().borrow_mut().mark_root_updated(lane); self.ensure_root_is_scheduled(root.unwrap()) } pub fn mark_update_lane_from_fiber_to_root( &self, fiber: Rc>, + lane: Lane, ) -> Option>> { let mut node = Rc::clone(&fiber); let mut parent = Rc::clone(&fiber).borrow()._return.clone(); + let node_lanes = { node.borrow().lanes.clone() }; + node.borrow_mut().lanes = merge_lanes(node_lanes, lane.clone()); + let alternate = node.borrow().alternate.clone(); + if alternate.is_some() { + let alternate = alternate.unwrap(); + let alternate_lanes = { alternate.borrow().lanes.clone() }; + alternate.borrow_mut().lanes = merge_lanes(alternate_lanes, lane); + } + while parent.is_some() { node = parent.clone().unwrap(); let rc = Rc::clone(&parent.unwrap()); @@ -71,12 +88,37 @@ impl WorkLoop { None } - fn ensure_root_is_scheduled(&self, root: Rc>) { - self.perform_sync_work_on_root(root); + fn ensure_root_is_scheduled(&'static mut self, root: Rc>) { + let s = self; + let root_cloned = root.clone(); + let host_config = self.host_config.clone(); + let update_lane = get_highest_priority(root.borrow().pending_lanes.clone()); + if update_lane == Lane::NoLane { + return; + } + if update_lane == Lane::SyncLane { + if is_dev() { + log!("Schedule in microtask, priority {:?}", update_lane); + } + } + schedule_sync_callback(Box::new(move || { + s; + log!("{:?} {:?}",root_cloned.clone(), update_lane.clone()); + // self.perform_sync_work_on_root(); + })); + host_config.as_ref().unwrap() + .schedule_microtask(Box::new(|| flush_sync_callbacks())); } - fn perform_sync_work_on_root(&self, root: Rc>) { - self.prepare_fresh_stack(Rc::clone(&root)); + fn perform_sync_work_on_root(&'static mut self, root: Rc>, lane: Lane) { + let next_lane = get_highest_priority(root.borrow().pending_lanes.clone()); + + if next_lane != Lane::SyncLane { + self.ensure_root_is_scheduled(root.clone()); + return; + } + + self.prepare_fresh_stack(root.clone()); loop { match self.work_loop() { @@ -92,6 +134,7 @@ impl WorkLoop { log!("{:?}", *root.clone().borrow()); + unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; } let finished_work = { root.clone() .borrow() @@ -103,6 +146,7 @@ impl WorkLoop { }; root.clone().borrow_mut().finished_work = finished_work; + root.clone().borrow_mut().finished_lane = Lane::NoLane; self.commit_root(root); } @@ -119,7 +163,7 @@ impl WorkLoop { let root_has_effect = get_mutation_mask().contains(finished_work.clone().borrow().flags.clone()); - let commit_work = &mut CommitWork::new(self.complete_work.host_config.clone()); + let commit_work = &mut CommitWork::new(self.host_config.clone().unwrap()); if subtree_has_effect || root_has_effect { commit_work.commit_mutation_effects(finished_work.clone()); cloned.borrow_mut().current = finished_work.clone(); @@ -165,7 +209,7 @@ impl WorkLoop { } fn perform_unit_of_work(&self, fiber: Rc>) -> Result<(), JsValue> { - let next = begin_work(fiber.clone())?; + let next = begin_work(fiber.clone(), unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE.clone() })?; let pending_props = { fiber.clone().borrow().pending_props.clone() }; fiber.clone().borrow_mut().memoized_props = pending_props; if next.is_none() { @@ -183,7 +227,7 @@ impl WorkLoop { loop { let next = self .complete_work - .complete_work(node.clone().unwrap().clone()); + .as_ref().unwrap().complete_work(node.clone().unwrap().clone()); if next.is_some() { // self.work_in_progress = next.clone(); diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 190de33..77fe2d4 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -21,7 +21,8 @@ pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { } pub fn is_dev() -> bool { - env!("ENV") == "dev" + // env!("ENV") == "dev" + true } pub fn type_of(val: &JsValue, _type: &str) -> bool { From 9036d89a039d05d48faacc5d9d2644e457a0acb8 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 10 May 2024 20:09:35 +0800 Subject: [PATCH 3/3] blog-13: fix bugs --- .../react-dom/ReactFunctionComponent-test.js | 9 +- examples/hello-world/src/main.tsx | 3 +- packages/react-dom/src/lib.rs | 17 - packages/react-reconciler/src/fiber_hooks.rs | 4 +- packages/react-reconciler/src/lib.rs | 11 +- .../react-reconciler/src/sync_task_queue.rs | 1 + packages/react-reconciler/src/update_queue.rs | 15 +- packages/react-reconciler/src/work_loop.rs | 352 ++++++++---------- 8 files changed, 194 insertions(+), 218 deletions(-) diff --git a/__tests__/react-dom/ReactFunctionComponent-test.js b/__tests__/react-dom/ReactFunctionComponent-test.js index 055f36c..70ae515 100644 --- a/__tests__/react-dom/ReactFunctionComponent-test.js +++ b/__tests__/react-dom/ReactFunctionComponent-test.js @@ -17,6 +17,12 @@ function FunctionComponent(props) { return
{props.name}
; } +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + describe('ReactFunctionComponent', () => { beforeEach(() => { jest.resetModules(); @@ -25,10 +31,11 @@ describe('ReactFunctionComponent', () => { ReactTestUtils = require('../utils/test-utils') }); - it('should render stateless component', () => { + it('should render stateless component', async () => { const el = document.createElement('div'); // console.log(FunctionComponent.toString()) ReactDOM.createRoot(el).render(); + await sleep(10) expect(el.textContent).toBe('A'); }); diff --git a/examples/hello-world/src/main.tsx b/examples/hello-world/src/main.tsx index 25cf44b..8f70611 100644 --- a/examples/hello-world/src/main.tsx +++ b/examples/hello-world/src/main.tsx @@ -1,5 +1,6 @@ import {createRoot} from 'react-dom' +import App from './App.tsx' const root = createRoot(document.getElementById("root")) -// root.render() +root.render() diff --git a/packages/react-dom/src/lib.rs b/packages/react-dom/src/lib.rs index c6a4592..84b5cd4 100644 --- a/packages/react-dom/src/lib.rs +++ b/packages/react-dom/src/lib.rs @@ -4,7 +4,6 @@ use wasm_bindgen::prelude::*; use web_sys::Node; use react_reconciler::Reconciler; -use shared::log; use crate::host_config::ReactDomHostConfig; use crate::renderer::Renderer; @@ -15,26 +14,10 @@ mod renderer; mod synthetic_event; mod utils; -struct T { - name: String, -} - -impl T { - fn new() -> Self { - T { name: "ayou".to_string() } - } - - fn callback(&self) { - log!("{:?}", self.name); - } -} - #[wasm_bindgen(js_name = createRoot)] pub fn create_root(container: &JsValue) -> Renderer { set_panic_hook(); let reconciler = Reconciler::new(Rc::new(ReactDomHostConfig)); - let t = Box::new(T::new()); - reconciler.host_config.schedule_microtask(Box::new(move || t.callback())); let node = match container.clone().dyn_into::() { Ok(node) => node, Err(_) => { diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 939df10..3a59cea 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -12,7 +12,7 @@ use crate::fiber_lanes::{Lane, request_update_lane}; use crate::update_queue::{ create_update, create_update_queue, enqueue_update, process_update_queue, UpdateQueue, }; -use crate::WORK_LOOP; +use crate::work_loop::schedule_update_on_fiber; #[wasm_bindgen] extern "C" { @@ -289,6 +289,6 @@ fn dispatch_set_state( let update = create_update(action.clone(), lane.clone()); enqueue_update(update_queue.clone(), update); unsafe { - WORK_LOOP.schedule_update_on_fiber(fiber.clone(), lane); + schedule_update_on_fiber(fiber.clone(), lane); } } diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index 2cf58ce..51e159b 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -4,11 +4,12 @@ use std::rc::Rc; use wasm_bindgen::JsValue; +use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; // use crate::fiber_hooks::{WORK_LOOP as Fiber_HOOKS}; use crate::fiber_lanes::Lane; use crate::update_queue::{create_update, create_update_queue, enqueue_update}; -use crate::work_loop::WorkLoop; +use crate::work_loop::schedule_update_on_fiber; use crate::work_tags::WorkTag; mod begin_work; @@ -24,7 +25,8 @@ mod work_tags; mod fiber_lanes; mod sync_task_queue; -pub static mut WORK_LOOP: WorkLoop = WorkLoop { complete_work: None, host_config: None }; +pub static mut HOST_CONFIG: Option> = None; +static mut COMPLETE_WORK: Option = None; pub trait HostConfig { fn create_text_instance(&self, content: &JsValue) -> Rc; @@ -75,8 +77,9 @@ impl Reconciler { update, ); unsafe { - WORK_LOOP = WorkLoop::new(self.host_config.clone()); - WORK_LOOP.schedule_update_on_fiber(host_root_fiber, root_render_priority); + HOST_CONFIG = Some(self.host_config.clone()); + COMPLETE_WORK = Some(CompleteWork::new(self.host_config.clone())); + schedule_update_on_fiber(host_root_fiber, root_render_priority); } element.clone() } diff --git a/packages/react-reconciler/src/sync_task_queue.rs b/packages/react-reconciler/src/sync_task_queue.rs index 8216fc8..c82fdee 100644 --- a/packages/react-reconciler/src/sync_task_queue.rs +++ b/packages/react-reconciler/src/sync_task_queue.rs @@ -13,6 +13,7 @@ pub fn flush_sync_callbacks() { callback(); } SYNC_QUEUE = vec![]; + IS_FLUSHING_SYNC_QUEUE = false; } } } diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 1e34759..9b03528 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -38,8 +38,18 @@ pub fn create_update(action: JsValue, lane: Lane) -> Update { } } -pub fn enqueue_update(update_queue: Rc>, update: Update) { - update_queue.borrow_mut().shared.pending = Option::from(Rc::new(RefCell::new(update))); +pub fn enqueue_update(update_queue: Rc>, mut update: Update) { + let pending = update_queue.borrow().shared.pending.clone(); + let update_rc = Rc::new(RefCell::new(update)); + let update_option = Option::from(update_rc.clone()); + if pending.is_none() { + update_rc.borrow_mut().next = update_option.clone(); + } else { + let pending = pending.clone().unwrap(); + update_rc.borrow_mut().next = { pending.borrow().next.clone() }; + pending.borrow_mut().next = update_option.clone(); + } + update_queue.borrow_mut().shared.pending = update_option.clone(); } pub fn create_update_queue() -> Rc> { @@ -60,7 +70,6 @@ pub fn process_update_queue( let pending = update_queue.borrow().shared.pending.clone(); update_queue.borrow_mut().shared.pending = None; if pending.is_some() { - // let action = pending.unwrap().action; let pending_update = pending.clone().unwrap(); let mut update = pending_update.clone(); loop { diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 8308d6e..abca79d 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -5,244 +5,219 @@ use wasm_bindgen::JsValue; use shared::{is_dev, log}; +use crate::{COMPLETE_WORK, HOST_CONFIG, HostConfig}; use crate::begin_work::begin_work; use crate::commit_work::CommitWork; -use crate::complete_work::CompleteWork; use crate::fiber::{FiberNode, FiberRootNode, StateNode}; use crate::fiber_flags::get_mutation_mask; use crate::fiber_lanes::{get_highest_priority, Lane, merge_lanes}; -use crate::HostConfig; use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; use crate::work_tags::WorkTag; static mut WORK_IN_PROGRESS: Option>> = None; static mut WORK_IN_PROGRESS_ROOT_RENDER_LANE: Lane = Lane::NoLane; -pub struct WorkLoop { - pub(crate) complete_work: Option, - pub(crate) host_config: Option>, -} +pub fn schedule_update_on_fiber(fiber: Rc>, lane: Lane) { + if is_dev() { + log!("schedule_update_on_fiber, {:?} {:?}", fiber, lane); + } -impl WorkLoop { - pub fn new(host_config: Rc) -> Self { - Self { - complete_work: Some(CompleteWork::new(host_config.clone())), - host_config: Some(host_config), - } + let root = mark_update_lane_from_fiber_to_root(fiber, lane.clone()); + if root.is_none() { + return; } + root.as_ref().unwrap().borrow_mut().mark_root_updated(lane); + ensure_root_is_scheduled(root.unwrap()) +} - pub fn schedule_update_on_fiber(&'static mut self, fiber: Rc>, lane: Lane) { - if is_dev() { - log!("schedule_update_on_fiber, {:?} {:?}", fiber, lane); - } +pub fn mark_update_lane_from_fiber_to_root( + fiber: Rc>, + lane: Lane, +) -> Option>> { + let mut node = Rc::clone(&fiber); + let mut parent = Rc::clone(&fiber).borrow()._return.clone(); + + let node_lanes = { node.borrow().lanes.clone() }; + node.borrow_mut().lanes = merge_lanes(node_lanes, lane.clone()); + let alternate = node.borrow().alternate.clone(); + if alternate.is_some() { + let alternate = alternate.unwrap(); + let alternate_lanes = { alternate.borrow().lanes.clone() }; + alternate.borrow_mut().lanes = merge_lanes(alternate_lanes, lane); + } - let root = self.mark_update_lane_from_fiber_to_root(fiber, lane.clone()); - if root.is_none() { - return; - } - root.as_ref().unwrap().borrow_mut().mark_root_updated(lane); - self.ensure_root_is_scheduled(root.unwrap()) + while parent.is_some() { + node = parent.clone().unwrap(); + let rc = Rc::clone(&parent.unwrap()); + let rc_ref = rc.borrow(); + let next = match rc_ref._return.as_ref() { + None => None, + Some(node) => { + let a = node.clone(); + Some(a) + } + }; + parent = next; } - pub fn mark_update_lane_from_fiber_to_root( - &self, - fiber: Rc>, - lane: Lane, - ) -> Option>> { - let mut node = Rc::clone(&fiber); - let mut parent = Rc::clone(&fiber).borrow()._return.clone(); - - let node_lanes = { node.borrow().lanes.clone() }; - node.borrow_mut().lanes = merge_lanes(node_lanes, lane.clone()); - let alternate = node.borrow().alternate.clone(); - if alternate.is_some() { - let alternate = alternate.unwrap(); - let alternate_lanes = { alternate.borrow().lanes.clone() }; - alternate.borrow_mut().lanes = merge_lanes(alternate_lanes, lane); + let fiber_node_rc = Rc::clone(&node); + let fiber_node = fiber_node_rc.borrow(); + if fiber_node.tag == WorkTag::HostRoot { + if let Some(state_node) = fiber_node.state_node.clone() { + if let StateNode::FiberRootNode(fiber_root_node) = &*(state_node.clone()) { + return Some(Rc::clone(fiber_root_node)); + } } + } - while parent.is_some() { - node = parent.clone().unwrap(); - let rc = Rc::clone(&parent.unwrap()); - let rc_ref = rc.borrow(); - let next = match rc_ref._return.as_ref() { - None => None, - Some(node) => { - let a = node.clone(); - Some(a) - } - }; - parent = next; - } + None +} - let fiber_node_rc = Rc::clone(&node); - let fiber_node = fiber_node_rc.borrow(); - if fiber_node.tag == WorkTag::HostRoot { - if let Some(state_node) = fiber_node.state_node.clone() { - if let StateNode::FiberRootNode(fiber_root_node) = &*(state_node.clone()) { - return Some(Rc::clone(fiber_root_node)); - } - } +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 { + return; + } + if update_lane == Lane::SyncLane { + if is_dev() { + log!("Schedule in microtask, priority {:?}", update_lane); } + } + 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())); + } +} - None +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 ensure_root_is_scheduled(&'static mut self, root: Rc>) { - let s = self; - let root_cloned = root.clone(); - let host_config = self.host_config.clone(); - let update_lane = get_highest_priority(root.borrow().pending_lanes.clone()); - if update_lane == Lane::NoLane { - return; - } - if update_lane == Lane::SyncLane { - if is_dev() { - log!("Schedule in microtask, priority {:?}", update_lane); + prepare_fresh_stack(root.clone(), lane.clone()); + + loop { + match work_loop() { + Ok(_) => { + break; } - } - schedule_sync_callback(Box::new(move || { - s; - log!("{:?} {:?}",root_cloned.clone(), update_lane.clone()); - // self.perform_sync_work_on_root(); - })); - host_config.as_ref().unwrap() - .schedule_microtask(Box::new(|| flush_sync_callbacks())); + Err(e) => unsafe { + log!("work_loop error {:?}", e); + WORK_IN_PROGRESS = None + }, + }; } - fn perform_sync_work_on_root(&'static mut self, root: Rc>, lane: Lane) { - let next_lane = get_highest_priority(root.borrow().pending_lanes.clone()); + log!("{:?}", *root.clone().borrow()); + + unsafe { 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); +} - if next_lane != Lane::SyncLane { - self.ensure_root_is_scheduled(root.clone()); - return; - } +fn commit_root(root: Rc>) { + let cloned = root.clone(); + if cloned.borrow().finished_work.is_none() { + return; + } + let lane = root.borrow().finished_lane.clone(); - self.prepare_fresh_stack(root.clone()); + let finished_work = cloned.borrow().finished_work.clone().unwrap(); + cloned.borrow_mut().finished_work = None; + cloned.borrow_mut().finished_lane = Lane::NoLane; - loop { - match self.work_loop() { - Ok(_) => { - break; - } - Err(e) => unsafe { - log!("work_loop error {:?}", e); - WORK_IN_PROGRESS = None - }, - }; - } + cloned.borrow_mut().mark_root_finished(lane.clone()); - log!("{:?}", *root.clone().borrow()); - - unsafe { 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::NoLane; - self.commit_root(root); + if lane == Lane::NoLane { + log!("Commit phase finished lane should not be NoLane") } - fn commit_root(&self, root: Rc>) { - let cloned = root.clone(); - if cloned.borrow().finished_work.is_none() { - return; - } - let finished_work = cloned.borrow().finished_work.clone().unwrap(); - cloned.borrow_mut().finished_work = None; - - let subtree_has_effect = - get_mutation_mask().contains(finished_work.clone().borrow().subtree_flags.clone()); - let root_has_effect = - get_mutation_mask().contains(finished_work.clone().borrow().flags.clone()); - - let commit_work = &mut CommitWork::new(self.host_config.clone().unwrap()); - if subtree_has_effect || root_has_effect { - commit_work.commit_mutation_effects(finished_work.clone()); - cloned.borrow_mut().current = finished_work.clone(); - } else { - cloned.borrow_mut().current = finished_work.clone(); - } + let subtree_has_effect = + get_mutation_mask().contains(finished_work.clone().borrow().subtree_flags.clone()); + let root_has_effect = + get_mutation_mask().contains(finished_work.clone().borrow().flags.clone()); + + let commit_work = &mut CommitWork::new(unsafe { HOST_CONFIG.clone().unwrap() }); + if subtree_has_effect || root_has_effect { + commit_work.commit_mutation_effects(finished_work.clone()); + cloned.borrow_mut().current = finished_work.clone(); + } else { + cloned.borrow_mut().current = finished_work.clone(); } +} - fn prepare_fresh_stack(&self, root: Rc>) { - let root = Rc::clone(&root); - // self.work_in_progress = Some(FiberNode::create_work_in_progress( - // root.borrow().current.clone(), - // Rc::new(JsValue::null()), - // )); - unsafe { - WORK_IN_PROGRESS = Some(FiberNode::create_work_in_progress( - root.borrow().current.clone(), - JsValue::null(), - )); - log!( - "prepare_fresh_stack {:?} {:?}", - WORK_IN_PROGRESS.clone().unwrap().clone().borrow()._type, - WORK_IN_PROGRESS - .clone() - .unwrap() - .clone() - .borrow() - .memoized_state - ); - } +fn prepare_fresh_stack(root: Rc>, lane: Lane) { + let root = root.clone(); + unsafe { + WORK_IN_PROGRESS = Some(FiberNode::create_work_in_progress( + root.borrow().current.clone(), + JsValue::null(), + )); + WORK_IN_PROGRESS_ROOT_RENDER_LANE = lane; } +} - fn work_loop(&self) -> Result<(), JsValue> { - // while self.work_in_progress.is_some() { - // self.perform_unit_of_work(self.work_in_progress.clone().unwrap())?; - // } - unsafe { - while WORK_IN_PROGRESS.is_some() { - self.perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; - } +fn work_loop() -> Result<(), JsValue> { + // while self.work_in_progress.is_some() { + // self.perform_unit_of_work(self.work_in_progress.clone().unwrap())?; + // } + unsafe { + while WORK_IN_PROGRESS.is_some() { + perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; } - Ok(()) } + Ok(()) +} - fn perform_unit_of_work(&self, fiber: Rc>) -> Result<(), JsValue> { - let next = begin_work(fiber.clone(), unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE.clone() })?; - let pending_props = { fiber.clone().borrow().pending_props.clone() }; - fiber.clone().borrow_mut().memoized_props = pending_props; - if next.is_none() { - self.complete_unit_of_work(fiber.clone()); - } else { - // self.work_in_progress = Some(next.unwrap()); - unsafe { WORK_IN_PROGRESS = Some(next.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() })?; + let pending_props = { fiber.clone().borrow().pending_props.clone() }; + fiber.clone().borrow_mut().memoized_props = pending_props; + if next.is_none() { + complete_unit_of_work(fiber.clone()); + } else { + // self.work_in_progress = Some(next.unwrap()); + unsafe { WORK_IN_PROGRESS = Some(next.unwrap()) } } + Ok(()) +} - fn complete_unit_of_work(&self, fiber: Rc>) { - let mut node: Option>> = Some(fiber); +fn complete_unit_of_work(fiber: Rc>) { + let mut node: Option>> = Some(fiber); + unsafe { loop { - let next = self - .complete_work + let next = COMPLETE_WORK .as_ref().unwrap().complete_work(node.clone().unwrap().clone()); if next.is_some() { // self.work_in_progress = next.clone(); - unsafe { - WORK_IN_PROGRESS = next.clone(); - } + WORK_IN_PROGRESS = next.clone(); return; } let sibling = node.clone().unwrap().clone().borrow().sibling.clone(); if sibling.is_some() { // self.work_in_progress = next.clone(); - unsafe { - WORK_IN_PROGRESS = sibling.clone(); - } + WORK_IN_PROGRESS = sibling.clone(); return; } @@ -251,17 +226,14 @@ impl WorkLoop { if _return.is_none() { // node = None; // self.work_in_progress = None; - unsafe { - WORK_IN_PROGRESS = None; - } + WORK_IN_PROGRESS = None; break; } else { node = _return; // self.work_in_progress = node.clone(); - unsafe { - WORK_IN_PROGRESS = node.clone(); - } + WORK_IN_PROGRESS = node.clone(); } } } } +// }