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`
)