diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index 08e9d72..a8a8fb1 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './fragment' +export {default} from './suspense' diff --git a/examples/hello-world/src/suspense/index.tsx b/examples/hello-world/src/suspense/index.tsx new file mode 100644 index 0000000..a4f5393 --- /dev/null +++ b/examples/hello-world/src/suspense/index.tsx @@ -0,0 +1,14 @@ +import {Suspense} from 'react' + +export default function App() { + return ( + loading}> + + + ) +} + +function Child() { + debugger + throw new Promise((resolve) => setTimeout(resolve, 1000)) +} diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index bea6426..4bad91e 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use wasm_bindgen::{JsCast, JsValue}; use shared::{derive_from_js_value, is_dev, log, shallow_equal}; -use web_sys::js_sys::{Function, Object}; +use web_sys::js_sys::{Function, Object, Reflect}; use crate::child_fiber::{clone_child_fiblers, mount_child_fibers, reconcile_child_fibers}; use crate::fiber::{FiberNode, MemoizedState}; @@ -12,6 +12,7 @@ use crate::fiber_context::{prepare_to_read_context, propagate_context_change, pu use crate::fiber_flags::Flags; use crate::fiber_hooks::{bailout_hook, render_with_hooks}; use crate::fiber_lanes::{include_some_lanes, Lane}; +use crate::suspense_context::push_suspense_handler; use crate::update_queue::{process_update_queue, ReturnOfProcessUpdateQueue}; use crate::work_tags::WorkTag; @@ -122,9 +123,190 @@ pub fn begin_work( )), WorkTag::MemoComponent => update_memo_component(work_in_progress.clone(), render_lane), WorkTag::Fragment => Ok(update_fragment(work_in_progress.clone())), + WorkTag::SuspenseComponent => Ok(update_suspense_component(work_in_progress.clone())), + WorkTag::OffscreenComponent => Ok(update_offscreen_component(work_in_progress)), }; } +fn mount_suspense_fallback_children( + work_in_progress: Rc>, + primary_children: JsValue, + fallback_children: JsValue, +) -> Rc> { + let primary_child_props = Object::new(); + Reflect::set(&primary_child_props, &"mode".into(), &"hidden".into()); + Reflect::set(&primary_child_props, &"children".into(), &primary_children); + + let primary_child_fragment = Rc::new(RefCell::new(FiberNode::create_fiber_from_offscreen( + primary_child_props.into(), + ))); + let fallback_child_fragment = Rc::new(RefCell::new(FiberNode::create_fiber_from_fragment( + fallback_children, + JsValue::null(), + ))); + + fallback_child_fragment.borrow_mut().flags |= Flags::Placement; + + primary_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + fallback_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + primary_child_fragment.borrow_mut().sibling = Some(fallback_child_fragment.clone()); + work_in_progress.borrow_mut().child = Some(primary_child_fragment.clone()); + + fallback_child_fragment +} + +fn update_suspense_fallback_children( + work_in_progress: Rc>, + primary_children: JsValue, + fallback_children: JsValue, +) -> Rc> { + let current = { work_in_progress.borrow().alternate.clone().unwrap() }; + let current_primary_child_fragment = current.borrow().child.clone().unwrap(); + let current_fallback_child_fragment = current_primary_child_fragment.borrow().sibling.clone(); + + let primary_child_props = Object::new(); + Reflect::set(&primary_child_props, &"mode".into(), &"hidden".into()); + Reflect::set(&primary_child_props, &"children".into(), &primary_children); + + let primary_child_fragment = FiberNode::create_work_in_progress( + current_primary_child_fragment, + primary_child_props.into(), + ); + + let mut fallback_child_fragment; + + if current_fallback_child_fragment.is_some() { + fallback_child_fragment = FiberNode::create_work_in_progress( + current_fallback_child_fragment.unwrap(), + fallback_children, + ); + } else { + fallback_child_fragment = Rc::new(RefCell::new(FiberNode::create_fiber_from_fragment( + fallback_children, + JsValue::null(), + ))); + fallback_child_fragment.borrow_mut().flags |= Flags::Placement; + } + + primary_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + fallback_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + primary_child_fragment.borrow_mut().sibling = Some(fallback_child_fragment.clone()); + work_in_progress.borrow_mut().child = Some(primary_child_fragment.clone()); + + fallback_child_fragment +} + +fn mount_suspense_primary_children( + work_in_progress: Rc>, + primary_children: JsValue, +) -> Rc> { + let primary_child_props = Object::new(); + Reflect::set(&primary_child_props, &"mode".into(), &"visible".into()); + Reflect::set(&primary_child_props, &"children".into(), &primary_children); + + let primary_child_fragment = Rc::new(RefCell::new(FiberNode::create_fiber_from_offscreen( + primary_child_props.into(), + ))); + + primary_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + work_in_progress.borrow_mut().child = Some(primary_child_fragment.clone()); + + primary_child_fragment +} + +fn update_suspense_primary_children( + work_in_progress: Rc>, + primary_children: JsValue, +) -> Rc> { + let current = { work_in_progress.borrow().alternate.clone().unwrap() }; + let current_primary_child_fragment = current.borrow().child.clone().unwrap(); + let current_fallback_child_fragment = current_primary_child_fragment.borrow().sibling.clone(); + + let primary_child_props = Object::new(); + Reflect::set(&primary_child_props, &"mode".into(), &"visible".into()); + Reflect::set(&primary_child_props, &"children".into(), &primary_children); + + let primary_child_fragment = FiberNode::create_work_in_progress( + current_primary_child_fragment.clone(), + primary_child_props.into(), + ); + + primary_child_fragment.borrow_mut()._return = Some(work_in_progress.clone()); + primary_child_fragment.borrow_mut().sibling = None; + work_in_progress.borrow_mut().child = Some(primary_child_fragment.clone()); + + if current_fallback_child_fragment.is_some() { + let current_fallback_child_fragment = current_fallback_child_fragment.unwrap(); + let mut deletions = &work_in_progress.borrow().deletions; + if deletions.is_empty() { + work_in_progress.borrow_mut().deletions = vec![current_fallback_child_fragment]; + work_in_progress.borrow_mut().flags != Flags::ChildDeletion; + } else { + let deletions = &mut work_in_progress.borrow_mut().deletions; + deletions.push(current_primary_child_fragment) + } + } + + primary_child_fragment +} + +fn update_suspense_component( + work_in_progress: Rc>, +) -> Option>> { + let current = { work_in_progress.borrow().alternate.clone() }; + let next_props = { work_in_progress.borrow().pending_props.clone() }; + + let mut show_fallback = false; + let did_suspend = + (work_in_progress.borrow().flags.clone() & Flags::DidCapture) != Flags::NoFlags; + + if did_suspend { + show_fallback = true; + work_in_progress.borrow_mut().flags -= Flags::DidCapture; + } + + let next_primary_children = derive_from_js_value(&next_props, "children"); + let next_fallback_children = derive_from_js_value(&next_props, "fallback"); + push_suspense_handler(work_in_progress.clone()); + log!("show_fallback {:?}", show_fallback); + if current.is_none() { + if show_fallback { + return Some(mount_suspense_fallback_children( + work_in_progress.clone(), + next_primary_children.clone(), + next_fallback_children.clone(), + )); + } else { + return Some(mount_suspense_primary_children( + work_in_progress.clone(), + next_primary_children.clone(), + )); + } + } else { + if show_fallback { + return Some(update_suspense_fallback_children( + work_in_progress.clone(), + next_primary_children.clone(), + next_fallback_children.clone(), + )); + } else { + return Some(update_suspense_primary_children( + work_in_progress.clone(), + next_primary_children.clone(), + )); + } + } +} + +fn update_offscreen_component( + work_in_progress: Rc>, +) -> Option>> { + let next_props = work_in_progress.borrow().pending_props.clone(); + let next_children = derive_from_js_value(&next_props, "children"); + reconcile_children(work_in_progress.clone(), Some(next_children)); + work_in_progress.borrow().child.clone() +} + fn update_fragment(work_in_progress: Rc>) -> Option>> { let next_children = work_in_progress.borrow().pending_props.clone(); reconcile_children(work_in_progress.clone(), Some(next_children)); diff --git a/packages/react-reconciler/src/complete_work.rs b/packages/react-reconciler/src/complete_work.rs index 6c473aa..cb56386 100644 --- a/packages/react-reconciler/src/complete_work.rs +++ b/packages/react-reconciler/src/complete_work.rs @@ -11,6 +11,7 @@ use crate::fiber::{FiberNode, StateNode}; use crate::fiber_context::pop_provider; use crate::fiber_flags::Flags; use crate::fiber_lanes::{merge_lanes, Lane}; +use crate::suspense_context::pop_suspense_handler; use crate::work_tags::WorkTag; use crate::HostConfig; @@ -212,6 +213,30 @@ impl CompleteWork { self.bubble_properties(work_in_progress.clone()); None } + // WorkTag::SuspenseComponent => { + // pop_suspense_handler(); + // let offscreen_fiber = work_in_progress.borrow().child.clone().unwrap(); + // let is_hidden = + // derive_from_js_value(&offscreen_fiber.borrow().pending_props, "mode") + // .as_string() + // .unwrap() + // == "hidden"; + // let current_offscreen_fiber = offscreen_fiber.borrow().alternate.clone(); + // if current_offscreen_fiber.is_some() { + // let current_offscreen_fiber = current_offscreen_fiber.unwrap(); + // let was_hidden = derive_from_js_value( + // ¤t_offscreen_fiber.borrow().pending_props, + // "mode", + // ) + // .as_string() + // .unwrap() + // == "hidden"; + // if is_hidden != was_hidden { + // offscreen_fiber.borrow_mut().flags != + // } + // } + // None + // } _ => { self.bubble_properties(work_in_progress.clone()); None diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index a566141..415cb56 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -9,7 +9,9 @@ use scheduler::Task; use wasm_bindgen::JsValue; use web_sys::js_sys::Reflect; -use shared::{derive_from_js_value, log, type_of, REACT_MEMO_TYPE, REACT_PROVIDER_TYPE}; +use shared::{ + derive_from_js_value, log, type_of, REACT_MEMO_TYPE, REACT_PROVIDER_TYPE, REACT_SUSPENSE_TYPE, +}; use crate::fiber_context::ContextItem; use crate::fiber_flags::Flags; @@ -81,9 +83,12 @@ impl Debug for FiberNode { WorkTag::HostRoot => { write!( f, - "{:?}(subtreeFlags:{:?})", + "{:?}(flags:{:?},subtreeFlags:{:?}),lanes:{:?},childLanes:{:?})", WorkTag::HostRoot, - self.subtree_flags + self.flags, + self.subtree_flags, + self.lanes, + self.child_lanes ) .expect("print error"); } @@ -142,6 +147,15 @@ impl FiberNode { } } + pub fn create_fiber_from_offscreen(pending_props: JsValue) -> FiberNode { + FiberNode::new( + WorkTag::OffscreenComponent, + pending_props, + JsValue::null(), + JsValue::null(), + ) + } + pub fn create_fiber_from_fragment(elements: JsValue, key: JsValue) -> FiberNode { FiberNode::new(WorkTag::Fragment, elements, key, JsValue::null()) } @@ -153,7 +167,10 @@ impl FiberNode { let _ref = derive_from_js_value(ele, "ref"); let mut fiber_tag = WorkTag::FunctionComponent; - if _type.is_string() { + + if _type == REACT_SUSPENSE_TYPE { + fiber_tag = WorkTag::SuspenseComponent + } else if _type.is_string() { fiber_tag = WorkTag::HostComponent } else if type_of(&_type, "object") { let _typeof = derive_from_js_value(&_type, "$$typeof"); @@ -276,6 +293,8 @@ pub struct FiberRootNode { pub finished_work: Option>>, pub pending_lanes: Lane, pub finished_lanes: Lane, + pub suspended_lanes: Lane, + pub pinged_lanes: Lane, pub callback_node: Option, pub callback_priority: Lane, pub pending_passive_effects: Rc>, @@ -295,6 +314,8 @@ impl FiberRootNode { })), callback_node: None, callback_priority: Lane::NoLane, + pinged_lanes: Lane::NoLane, + suspended_lanes: Lane::NoLane, } } diff --git a/packages/react-reconciler/src/fiber_flags.rs b/packages/react-reconciler/src/fiber_flags.rs index 07cc8e6..06e221f 100644 --- a/packages/react-reconciler/src/fiber_flags.rs +++ b/packages/react-reconciler/src/fiber_flags.rs @@ -2,17 +2,21 @@ use bitflags::bitflags; bitflags! { #[derive(Debug, Clone)] - pub struct Flags: u8 { - const NoFlags = 0b00000000; - const Placement = 0b00000010; - const Update = 0b00000100; - const ChildDeletion = 0b00010000; - const PassiveEffect = 0b00100000; - const Ref = 0b01000000; - const LayoutMask = 0b01000000; // Ref - // effect hook - const HookHasEffect = 0b00100001; - const Passive = 0b00000010; + pub struct Flags: u16 { + const NoFlags = 0b00000000; + const Placement = 0b00000001; + const Update = 0b00000010; + const ChildDeletion = 0b00000100; + const PassiveEffect = 0b00001000; + const Ref = 0b00010000; + const Visibility = 0b00100000; + const DidCapture = 0b01000000; + const ShouldCapture = 0b1000000000000; + + const LayoutMask = 0b00010000; // Ref + // HookEffectTags + const HookHasEffect = 0b0001; + const Passive = 0b0010; // useEffect } } @@ -29,3 +33,7 @@ pub fn get_mutation_mask() -> Flags { pub fn get_passive_mask() -> Flags { Flags::PassiveEffect | Flags::ChildDeletion } + +pub fn get_host_effect_mask() -> Flags { + get_mutation_mask() | Flags::LayoutMask | get_passive_mask() | Flags::DidCapture +} diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index 916419f..d9f46be 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,5 +1,10 @@ use bitflags::bitflags; use scheduler::{unstable_get_current_priority_level, Priority}; +use std::cell::RefCell; +use std::pin; +use std::rc::Rc; + +use crate::fiber::FiberRootNode; bitflags! { #[derive(Debug, Clone)] @@ -68,3 +73,14 @@ pub fn include_some_lanes(set: Lane, subset: Lane) -> bool { pub fn remove_lanes(set: Lane, subset: Lane) -> Lane { return set - subset; } + +pub fn mark_root_pinged(root: Rc>, pinged_lane: Lane) { + let suspended_lanes: Lane = { root.borrow().suspended_lanes.clone() }; + root.borrow_mut().pinged_lanes |= suspended_lanes & pinged_lane; +} + +pub fn mark_root_suspended(root: Rc>, suspended_lane: Lane) { + let suspended_lanes = { root.borrow().suspended_lanes.clone() }; + root.borrow_mut().suspended_lanes |= suspended_lane; + root.borrow_mut().pinged_lanes -= suspended_lanes; +} diff --git a/packages/react-reconciler/src/fiber_throw.rs b/packages/react-reconciler/src/fiber_throw.rs new file mode 100644 index 0000000..b206495 --- /dev/null +++ b/packages/react-reconciler/src/fiber_throw.rs @@ -0,0 +1,38 @@ +use std::{cell::RefCell, rc::Rc}; + +use shared::{derive_from_js_value, type_of}; +use wasm_bindgen::{prelude::Closure, JsCast, JsValue}; +use web_sys::js_sys::Function; + +use crate::{ + fiber::FiberRootNode, fiber_flags::Flags, fiber_lanes::Lane, + suspense_context::get_suspense_handler, work_loop::ensure_root_is_scheduled, +}; + +fn attach_ping_listener(root: Rc>, wakeable: JsValue, lane: Lane) { + let then_value = derive_from_js_value(&wakeable, "then"); + let then = then_value.dyn_ref::().unwrap(); + let closure = Closure::wrap(Box::new(move || { + root.clone().borrow_mut().mark_root_updated(lane.clone()); + ensure_root_is_scheduled(root.clone()); + }) as Box); + let ping = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + then.call2(&wakeable, &ping, &ping) + .expect("failed to call then function"); +} + +pub fn throw_exception(root: Rc>, value: JsValue, lane: Lane) { + if !value.is_null() + && type_of(&value, "object") + && derive_from_js_value(&value, "then").is_function() + { + let suspense_boundary = get_suspense_handler(); + if suspense_boundary.is_some() { + let suspense_boundary = suspense_boundary.unwrap(); + suspense_boundary.borrow_mut().flags |= Flags::ShouldCapture; + } + + attach_ping_listener(root, value, lane) + } +} diff --git a/packages/react-reconciler/src/fiber_unwind_work.rs b/packages/react-reconciler/src/fiber_unwind_work.rs new file mode 100644 index 0000000..d52b72d --- /dev/null +++ b/packages/react-reconciler/src/fiber_unwind_work.rs @@ -0,0 +1,34 @@ +use std::{cell::RefCell, rc::Rc}; + +use shared::derive_from_js_value; + +use crate::{ + fiber::FiberNode, + fiber_context::pop_provider, + fiber_flags::Flags, + suspense_context::pop_suspense_handler, + work_tags::WorkTag::{ContextProvider, SuspenseComponent}, +}; + +pub fn unwind_work(wip: Rc>) -> Option>> { + let flags = wip.borrow().flags.clone(); + let tag = wip.borrow().tag.clone(); + match tag { + SuspenseComponent => { + pop_suspense_handler(); + if (flags.clone() & Flags::ShouldCapture) != Flags::NoFlags + && (flags.clone() & Flags::DidCapture) == Flags::NoFlags + { + wip.borrow_mut().flags = (flags - Flags::ShouldCapture) | Flags::DidCapture; + return Some(wip.clone()); + } + None + } + ContextProvider => { + let context = derive_from_js_value(&wip.borrow()._type, "_context"); + pop_provider(&context); + None + } + _ => None, + } +} diff --git a/packages/react-reconciler/src/hook_effect_tags.rs b/packages/react-reconciler/src/hook_effect_tags.rs deleted file mode 100644 index f6f5678..0000000 --- a/packages/react-reconciler/src/hook_effect_tags.rs +++ /dev/null @@ -1,9 +0,0 @@ -use bitflags::bitflags; - -bitflags! { - #[derive(Debug, Clone)] - pub struct HookEffectTags: u8 { - const HookHasEffect = 0b0001; - const Passive = 0b0010; // useEffect - } -} diff --git a/packages/react-reconciler/src/lib.rs b/packages/react-reconciler/src/lib.rs index b1f86db..d63aef5 100644 --- a/packages/react-reconciler/src/lib.rs +++ b/packages/react-reconciler/src/lib.rs @@ -21,8 +21,11 @@ mod fiber_context; mod fiber_flags; mod fiber_hooks; pub mod fiber_lanes; -mod hook_effect_tags; +mod fiber_throw; +mod fiber_unwind_work; +mod suspense_context; mod sync_task_queue; +mod thenable; mod update_queue; mod work_loop; pub mod work_tags; diff --git a/packages/react-reconciler/src/suspense_context.rs b/packages/react-reconciler/src/suspense_context.rs new file mode 100644 index 0000000..5f7afe1 --- /dev/null +++ b/packages/react-reconciler/src/suspense_context.rs @@ -0,0 +1,22 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::fiber::FiberNode; + +static mut SUSPENSE_HANDLER_STACK: Vec>> = vec![]; + +pub fn get_suspense_handler() -> Option>> { + unsafe { + if SUSPENSE_HANDLER_STACK.len() <= 0 { + return None; + } + return Some(SUSPENSE_HANDLER_STACK[SUSPENSE_HANDLER_STACK.len() - 1].clone()); + } +} + +pub fn push_suspense_handler(handler: Rc>) { + unsafe { SUSPENSE_HANDLER_STACK.push(handler) } +} + +pub fn pop_suspense_handler() -> Option>> { + unsafe { SUSPENSE_HANDLER_STACK.pop() } +} diff --git a/packages/react-reconciler/src/thenable.rs b/packages/react-reconciler/src/thenable.rs new file mode 100644 index 0000000..fbfdd14 --- /dev/null +++ b/packages/react-reconciler/src/thenable.rs @@ -0,0 +1,6 @@ +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + type SuspenseException; +} diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 87ce5c9..3d710b4 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -1,7 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -use bitflags::bitflags; use wasm_bindgen::closure::Closure; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::Function; @@ -18,35 +17,30 @@ use crate::commit_work::{ commit_hook_effect_list_unmount, commit_layout_effects, commit_mutation_effects, }; use crate::fiber::{FiberNode, FiberRootNode, PendingPassiveEffects, StateNode}; -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::fiber_flags::{get_host_effect_mask, get_mutation_mask, get_passive_mask, Flags}; +use crate::fiber_lanes::{ + get_highest_priority, lanes_to_scheduler_priority, mark_root_suspended, merge_lanes, Lane, +}; +use crate::fiber_throw::throw_exception; +use crate::fiber_unwind_work::unwind_work; use crate::sync_task_queue::{flush_sync_callbacks, schedule_sync_callback}; use crate::work_tags::WorkTag; use crate::{COMPLETE_WORK, HOST_CONFIG}; -bitflags! { - #[derive(Debug, Clone)] - pub struct ExecutionContext: u8 { - const NoContext = 0b0000; - const RenderContext = 0b0010; - const CommitContext = 0b0100; - const ChildDeletion = 0b00010000; - } -} - -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 mut WORK_IN_PROGRESS_ROOT_EXIT_STATUS: u8 = ROOT_IN_PROGRESS; +static mut WORK_IN_PROGRESS_SUSPENDED_REASON: u8 = NOT_SUSPENDED; +static mut WORK_IN_PROGRESS_THROWN_VALUE: Option = None; +static ROOT_IN_PROGRESS: u8 = 0; static ROOT_INCOMPLETE: u8 = 1; static ROOT_COMPLETED: u8 = 2; +static ROOT_DID_NOT_COMPLETE: u8 = 3; + +static NOT_SUSPENDED: u8 = 0; +static SUSPENDED_ON_DATA: u8 = 6; pub fn schedule_update_on_fiber(fiber: Rc>, lane: Lane) { if is_dev() { @@ -103,7 +97,7 @@ pub fn mark_update_lane_from_fiber_to_root( None } -fn ensure_root_is_scheduled(root: Rc>) { +pub fn ensure_root_is_scheduled(root: Rc>) { let root_cloned = root.clone(); let update_lanes = root_cloned.borrow().get_next_lanes(); let existing_callback = root_cloned.borrow().callback_node.clone(); @@ -162,7 +156,7 @@ fn ensure_root_is_scheduled(root: Rc>) { root.borrow_mut().callback_priority = cur_priority; } -fn render_root(root: Rc>, lanes: Lane, should_time_slice: bool) -> u8 { +fn render_root(root: Rc>, lane: Lane, should_time_slice: bool) -> u8 { if is_dev() { log!( "Start {:?} render", @@ -174,15 +168,32 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: ); } - let prev_execution_context: ExecutionContext; - unsafe { - prev_execution_context = EXECUTION_CONTEXT.clone(); - EXECUTION_CONTEXT |= ExecutionContext::RenderContext; + if unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE != lane } { + prepare_fresh_stack(root.clone(), lane.clone()); } - prepare_fresh_stack(root.clone(), lanes.clone()); - loop { + unsafe { + if WORK_IN_PROGRESS_SUSPENDED_REASON != NOT_SUSPENDED && WORK_IN_PROGRESS.is_some() { + let thrown_value = WORK_IN_PROGRESS_THROWN_VALUE.clone().unwrap(); + + WORK_IN_PROGRESS_SUSPENDED_REASON = NOT_SUSPENDED; + WORK_IN_PROGRESS_THROWN_VALUE = None; + + // TODO + mark_update_lane_from_fiber_to_root( + WORK_IN_PROGRESS.clone().unwrap(), + lane.clone(), + ); + + throw_and_unwind_work_loop( + root.clone(), + WORK_IN_PROGRESS.clone().unwrap(), + thrown_value, + lane.clone(), + ); + } + } match if should_time_slice { work_loop_concurrent() } else { @@ -191,10 +202,7 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: Ok(_) => { break; } - Err(e) => unsafe { - log!("work_loop error {:?}", e); - WORK_IN_PROGRESS = None - }, + Err(e) => handle_throw(root.clone(), e), }; } @@ -203,7 +211,6 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: // 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() { @@ -219,15 +226,6 @@ fn render_root(root: Rc>, lanes: Lane, should_time_slice: } fn perform_concurrent_work_on_root(root: Rc>, did_timeout: bool) -> JsValue { - unsafe { - if EXECUTION_CONTEXT.clone() - & (ExecutionContext::RenderContext | ExecutionContext::CommitContext) - != ExecutionContext::NoContext - { - panic!("No in React work process {:?}", EXECUTION_CONTEXT) - } - } - // 开始执行具体工作前,保证上一次的useEffct都执行了 // 同时要注意useEffect执行时触发的更新优先级是否大于当前更新的优先级 let did_flush_passive_effects = @@ -288,7 +286,7 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout } fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { - let next_lane = get_highest_priority(root.borrow().pending_lanes.clone()); + let next_lane = root.borrow().get_next_lanes(); if next_lane != Lane::SyncLane { ensure_root_is_scheduled(root.clone()); @@ -309,8 +307,12 @@ fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { }; root.clone().borrow_mut().finished_work = finished_work; root.clone().borrow_mut().finished_lanes = lanes; - + unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; commit_root(root); + } else if exit_status == ROOT_DID_NOT_COMPLETE { + unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; + mark_root_suspended(root.clone(), next_lane); + ensure_root_is_scheduled(root.clone()); } else { todo!("Unsupported status of sync render") } @@ -318,12 +320,6 @@ fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { fn flush_passive_effects(pending_passive_effects: Rc>) -> bool { unsafe { - if EXECUTION_CONTEXT - .contains(ExecutionContext::RenderContext | ExecutionContext::CommitContext) - { - log!("Cannot execute useEffect callback in React work loop") - } - let mut did_flush_passive_effects = false; for effect in &pending_passive_effects.borrow().unmount { did_flush_passive_effects = true; @@ -388,12 +384,6 @@ fn commit_root(root: Rc>) { let root_has_effect = get_mutation_mask().contains(flags); if subtree_has_effect || root_has_effect { - let prev_execution_context: ExecutionContext; - unsafe { - prev_execution_context = EXECUTION_CONTEXT.clone(); - EXECUTION_CONTEXT |= ExecutionContext::CommitContext; - } - // effect // 1/3: Before Mutation @@ -406,10 +396,6 @@ fn commit_root(root: Rc>) { // 3/3: Layout commit_layout_effects(finished_work.clone(), root.clone()); - - unsafe { - EXECUTION_CONTEXT = prev_execution_context; - } } else { cloned.borrow_mut().current = finished_work.clone(); } @@ -428,6 +414,10 @@ fn prepare_fresh_stack(root: Rc>, lane: Lane) { JsValue::null(), )); WORK_IN_PROGRESS_ROOT_RENDER_LANE = lane; + + WORK_IN_PROGRESS_ROOT_EXIT_STATUS = ROOT_IN_PROGRESS; + WORK_IN_PROGRESS_SUSPENDED_REASON = NOT_SUSPENDED; + WORK_IN_PROGRESS_THROWN_VALUE = None; } } @@ -503,4 +493,52 @@ fn complete_unit_of_work(fiber: Rc>) { } } } -// } + +fn handle_throw(root: Rc>, thrown_value: JsValue) { + unsafe { + WORK_IN_PROGRESS_SUSPENDED_REASON = SUSPENDED_ON_DATA; + WORK_IN_PROGRESS_THROWN_VALUE = Some(thrown_value); + } +} + +fn throw_and_unwind_work_loop( + root: Rc>, + unit_of_work: Rc>, + thrown_value: JsValue, + lane: Lane, +) { + throw_exception(root.clone(), thrown_value, lane.clone()); + unwind_unit_of_work(unit_of_work); +} + +fn unwind_unit_of_work(unit_of_work: Rc>) { + let mut incomplete_work = Some(unit_of_work); + loop { + let unwrapped_work = incomplete_work.clone().unwrap(); + let next = unwind_work(unwrapped_work.clone()); + if next.is_some() { + let next = next.unwrap(); + next.borrow_mut().flags &= get_host_effect_mask(); + unsafe { WORK_IN_PROGRESS = Some(next) }; + return; + } + + let return_fiber = unwrapped_work.borrow()._return.clone(); + if return_fiber.is_some() { + let return_fiber = return_fiber.clone().unwrap(); + // Todo why + return_fiber.borrow_mut().deletions = vec![]; + } + + incomplete_work = return_fiber.clone(); + + if incomplete_work.is_none() { + break; + } + } + + unsafe { + WORK_IN_PROGRESS = None; + WORK_IN_PROGRESS_ROOT_EXIT_STATUS = ROOT_DID_NOT_COMPLETE; + } +} diff --git a/packages/react-reconciler/src/work_tags.rs b/packages/react-reconciler/src/work_tags.rs index 5bc9754..1246990 100644 --- a/packages/react-reconciler/src/work_tags.rs +++ b/packages/react-reconciler/src/work_tags.rs @@ -6,5 +6,7 @@ pub enum WorkTag { HostText = 6, Fragment = 7, ContextProvider = 8, + SuspenseComponent = 13, + OffscreenComponent = 14, MemoComponent = 15, } diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index 8e7dee8..5c67af1 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -6,6 +6,7 @@ pub static REACT_ELEMENT_TYPE: &str = "react.element"; pub static REACT_CONTEXT_TYPE: &str = "react.context"; pub static REACT_PROVIDER_TYPE: &str = "react.provider"; pub static REACT_MEMO_TYPE: &str = "react.memo"; +pub static REACT_SUSPENSE_TYPE: &str = "react.suspense"; pub static REACT_FRAGMENT_TYPE: &str = "react.fragment"; #[macro_export] diff --git a/scripts/build.js b/scripts/build.js index b552538..fa31a30 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -71,16 +71,18 @@ fs.writeFileSync( : 'import {updateDispatcher} from "react";\n') + reactDomIndexBgData ) -// add Fragment -const reactIndexFilename = `${cwd}/dist/react/jsx-dev-runtime.js` +// add Suspense + Fragment +const reactIndexFilename = `${cwd}/dist/react/index.js` const reactIndexData = fs.readFileSync(reactIndexFilename) fs.writeFileSync( reactIndexFilename, - reactIndexData + `export const Fragment='react.fragment';\n` + reactIndexData + + `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` ) -const reactTsIndexFilename = `${cwd}/dist/react/jsx-dev-runtime.d.ts` +const reactTsIndexFilename = `${cwd}/dist/react/index.d.ts` const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) fs.writeFileSync( reactTsIndexFilename, - reactTsIndexData + `export const Fragment: string;\n` + reactTsIndexData + + `export const Suspense: string;\nexport const Fragment: string;\n` )