From 6535c7b16d8e8ffe559f8b9171730a4d9d8e61f2 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Fri, 20 Sep 2024 09:52:37 +0800 Subject: [PATCH 1/6] add blogs --- readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/readme.md b/readme.md index 0786952..b0fa33b 100644 --- a/readme.md +++ b/readme.md @@ -55,3 +55,7 @@ [从零实现 React v18,但 WASM 版 - [23] 实现 Fragment](https://www.paradeto.com/2024/08/01/big-react-wasm-23/) [从零实现 React v18,但 WASM 版 - [24] 实现 Suspense(一):渲染 Fallback](https://www.paradeto.com/2024/08/01/big-react-wasm-24/) + +[从零实现 React v18,但 WASM 版 - [25] 实现 Suspense(二):结合 use hooks 获取数据](https://www.paradeto.com/2024/08/01/big-react-wasm-25/) + +[从零实现 React v18,但 WASM 版 - [26] 实现 React.lazy](https://www.paradeto.com/2024/08/01/big-react-wasm-26/) From c2a4a734a0884b959050eb12eca2223b1f1b75b6 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Mon, 23 Sep 2024 20:21:06 +0800 Subject: [PATCH 2/6] add useTransition demo --- examples/hello-world/src/App.tsx | 2 +- .../src/useTransition/AboutTab.tsx | 3 + .../src/useTransition/ContactTab.tsx | 11 ++++ .../src/useTransition/PostsTab.tsx | 13 ++++ .../src/useTransition/TabButton.tsx | 13 ++++ .../hello-world/src/useTransition/index.tsx | 35 +++++++++++ packages/react-reconciler/src/fiber_hooks.rs | 62 +++++++++++++++++++ packages/react-reconciler/src/fiber_lanes.rs | 6 ++ packages/react-reconciler/src/work_loop.rs | 3 + packages/react/src/current_batch_config.rs | 6 ++ packages/react/src/current_dispatcher.rs | 5 ++ packages/react/src/lib.rs | 7 +++ scripts/build.js | 33 +++++----- 13 files changed, 184 insertions(+), 15 deletions(-) create mode 100644 examples/hello-world/src/useTransition/AboutTab.tsx create mode 100644 examples/hello-world/src/useTransition/ContactTab.tsx create mode 100644 examples/hello-world/src/useTransition/PostsTab.tsx create mode 100644 examples/hello-world/src/useTransition/TabButton.tsx create mode 100644 examples/hello-world/src/useTransition/index.tsx create mode 100644 packages/react/src/current_batch_config.rs diff --git a/examples/hello-world/src/App.tsx b/examples/hello-world/src/App.tsx index eb18dd9..1808250 100644 --- a/examples/hello-world/src/App.tsx +++ b/examples/hello-world/src/App.tsx @@ -1 +1 @@ -export {default} from './lazy' +export {default} from './useTransition' diff --git a/examples/hello-world/src/useTransition/AboutTab.tsx b/examples/hello-world/src/useTransition/AboutTab.tsx new file mode 100644 index 0000000..fc18b9f --- /dev/null +++ b/examples/hello-world/src/useTransition/AboutTab.tsx @@ -0,0 +1,3 @@ +export default function AboutTab() { + return

It's me.

+} diff --git a/examples/hello-world/src/useTransition/ContactTab.tsx b/examples/hello-world/src/useTransition/ContactTab.tsx new file mode 100644 index 0000000..13f3b36 --- /dev/null +++ b/examples/hello-world/src/useTransition/ContactTab.tsx @@ -0,0 +1,11 @@ +export default function ContactTab() { + return ( + <> +

Contact:

+ + + ) +} diff --git a/examples/hello-world/src/useTransition/PostsTab.tsx b/examples/hello-world/src/useTransition/PostsTab.tsx new file mode 100644 index 0000000..c0193c5 --- /dev/null +++ b/examples/hello-world/src/useTransition/PostsTab.tsx @@ -0,0 +1,13 @@ +const PostsTab = function PostsTab() { + const items = [] + for (let i = 0; i < 500; i++) { + items.push() + } + return
    {items}
+} +function SlowPost({index}) { + const startTime = performance.now() + while (performance.now() - startTime < 4) {} + return
  • 博文 #{index + 1}
  • +} +export default PostsTab diff --git a/examples/hello-world/src/useTransition/TabButton.tsx b/examples/hello-world/src/useTransition/TabButton.tsx new file mode 100644 index 0000000..e546a23 --- /dev/null +++ b/examples/hello-world/src/useTransition/TabButton.tsx @@ -0,0 +1,13 @@ +export default function TabButton({children, isActive, onClick}) { + if (isActive) { + return {children} + } + return ( + + ) +} diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx new file mode 100644 index 0000000..4b0c63e --- /dev/null +++ b/examples/hello-world/src/useTransition/index.tsx @@ -0,0 +1,35 @@ +import {useState, useTransition} from 'react' +import TabButton from './TabButton' +import AboutTab from './AboutTab' +import PostsTab from './PostsTab' +import ContactTab from './ContactTab' +export default function App() { + const [isPending, startTransition] = useTransition() + const [tab, setTab] = useState('about') + console.log('hello') + function selectTab(nextTab) { + startTransition(() => { + setTab(nextTab) + }) + } + return ( +
    + selectTab('about')}> + 首页 + + selectTab('posts')}> + 博客 (render慢) + + selectTab('contact')}> + 联系我 + +
    + {isPending && 'loading'} + {tab === 'about' && } + {tab === 'posts' && } + {tab === 'contact' && } +
    + ) +} diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index b489b3f..fc200cf 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; +use react::current_batch_config::REACT_CURRENT_BATCH_CONFIG; use wasm_bindgen::prelude::{wasm_bindgen, Closure}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::{Array, Function, Object, Reflect}; @@ -157,6 +158,18 @@ fn update_hooks_to_dispatcher(is_update: bool) { .clone(); use_context_closure.forget(); + // use_transition + let use_transition_closure = Closure::wrap(Box::new(if is_update { + update_transition + } else { + mount_transition + }) as Box Vec>); + let use_transition = use_transition_closure + .as_ref() + .unchecked_ref::() + .clone(); + use_transition_closure.forget(); + // use let use_closure = Closure::wrap(Box::new(_use) as Box Result>); @@ -171,6 +184,8 @@ fn update_hooks_to_dispatcher(is_update: bool) { .expect("TODO: panic set use_callback"); Reflect::set(&object, &"use_context".into(), &use_context) .expect("TODO: panic set use_context"); + Reflect::set(&object, &"use_transition".into(), &use_transition) + .expect("TODO: panic set use_transition"); Reflect::set(&object, &"use".into(), &use_fn).expect("TODO: panic set use"); updateDispatcher(&object.into()); @@ -774,3 +789,50 @@ pub fn reset_hooks_on_unwind(wip: Rc>) { WORK_IN_PROGRESS_HOOK = None; } } + +fn mount_transition() -> Vec { + let result = mount_state(&JsValue::from(false)).unwrap(); + let is_pending = result[0].as_bool().unwrap(); + let set_pending = result[1].clone().dyn_into::().unwrap(); + let hook = mount_work_in_progress_hook(); + let set_pending_cloned = set_pending.clone(); + let closure = Closure::wrap(Box::new(move |callback: Function| { + start_transition(set_pending_cloned.clone(), callback); + }) as Box); + let start: Function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + hook.as_ref().unwrap().clone().borrow_mut().memoized_state = + Some(MemoizedState::MemoizedJsValue(start.clone().into())); + + vec![JsValue::from_bool(is_pending), start.into()] +} + +fn update_transition() -> Vec { + let result = update_state(&JsValue::undefined()).unwrap(); + let is_pending = result[0].as_bool().unwrap(); + let hook = update_work_in_progress_hook(); + if let MemoizedState::MemoizedJsValue(start) = hook + .as_ref() + .unwrap() + .clone() + .borrow() + .memoized_state + .as_ref() + .unwrap() + { + return vec![JsValue::from_bool(is_pending), start.into()]; + } + panic!("update_transition") +} + +fn start_transition(set_pending: Function, callback: Function) { + set_pending.call1(&JsValue::null(), &JsValue::from_bool(true)); + let prev_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition }; + + // low priority + unsafe { REACT_CURRENT_BATCH_CONFIG.transition = 1 }; + callback.call0(&JsValue::null()); + set_pending.call1(&JsValue::null(), &JsValue::from_bool(false)); + + unsafe { REACT_CURRENT_BATCH_CONFIG.transition = prev_transition }; +} diff --git a/packages/react-reconciler/src/fiber_lanes.rs b/packages/react-reconciler/src/fiber_lanes.rs index 1a511bc..b113d15 100644 --- a/packages/react-reconciler/src/fiber_lanes.rs +++ b/packages/react-reconciler/src/fiber_lanes.rs @@ -1,4 +1,5 @@ use bitflags::bitflags; +use react::current_batch_config::REACT_CURRENT_BATCH_CONFIG; use scheduler::{unstable_get_current_priority_level, Priority}; use std::cell::RefCell; use std::hash::{Hash, Hasher}; @@ -13,6 +14,7 @@ bitflags! { const SyncLane = 0b0000000000000000000000000000001; // onClick const InputContinuousLane = 0b0000000000000000000000000000010; // Continuous Trigger, example: onScroll const DefaultLane = 0b0000000000000000000000000000100; // useEffect + const TransitionLane = 0b0000000000000000000000000001000; const IdleLane = 0b1000000000000000000000000000000; } } @@ -46,6 +48,10 @@ pub fn is_subset_of_lanes(set: Lane, subset: Lane) -> bool { } pub fn request_update_lane() -> Lane { + let is_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition } != 0; + if is_transition { + return Lane::TransitionLane; + } let current_scheduler_priority_level = unstable_get_current_priority_level(); let update_lane = scheduler_priority_to_lane(current_scheduler_priority_level); update_lane diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 5a965ee..8d444eb 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -142,6 +142,9 @@ pub fn ensure_root_is_scheduled(root: Rc>) { .schedule_microtask(Box::new(|| flush_sync_callbacks())); } } else { + if is_dev() { + log!("Schedule in macrotask, priority {:?}", update_lanes); + } let scheduler_priority = lanes_to_scheduler_priority(cur_priority.clone()); let closure = Closure::wrap(Box::new(move |did_timeout_js_value: JsValue| { let did_timeout = did_timeout_js_value.as_bool().unwrap(); diff --git a/packages/react/src/current_batch_config.rs b/packages/react/src/current_batch_config.rs new file mode 100644 index 0000000..6d87e20 --- /dev/null +++ b/packages/react/src/current_batch_config.rs @@ -0,0 +1,6 @@ +pub struct ReactCurrentBatchConfig { + pub transition: u8, +} + +pub static mut REACT_CURRENT_BATCH_CONFIG: ReactCurrentBatchConfig = + ReactCurrentBatchConfig { transition: 0 }; diff --git a/packages/react/src/current_dispatcher.rs b/packages/react/src/current_dispatcher.rs index c41bd75..28cdbe6 100644 --- a/packages/react/src/current_dispatcher.rs +++ b/packages/react/src/current_dispatcher.rs @@ -10,6 +10,7 @@ pub struct Dispatcher { pub use_memo: Function, pub use_callback: Function, pub use_context: Function, + pub use_transition: Function, pub _use: Function, } @@ -23,6 +24,7 @@ impl Dispatcher { use_memo: Function, use_callback: Function, use_context: Function, + use_transition: Function, _use: Function, ) -> Self { Dispatcher { @@ -32,6 +34,7 @@ impl Dispatcher { use_memo, use_callback, use_context, + use_transition, _use, } } @@ -58,6 +61,7 @@ pub unsafe fn update_dispatcher(args: &JsValue) { let use_memo = derive_function_from_js_value(args, "use_memo"); let use_callback = derive_function_from_js_value(args, "use_callback"); let use_context = derive_function_from_js_value(args, "use_context"); + let use_transition = derive_function_from_js_value(args, "use_transition"); let _use = derive_function_from_js_value(args, "use"); CURRENT_DISPATCHER.current = Some(Box::new(Dispatcher::new( use_state, @@ -66,6 +70,7 @@ pub unsafe fn update_dispatcher(args: &JsValue) { use_memo, use_callback, use_context, + use_transition, _use, ))) } diff --git a/packages/react/src/lib.rs b/packages/react/src/lib.rs index de09020..8aa70a4 100644 --- a/packages/react/src/lib.rs +++ b/packages/react/src/lib.rs @@ -9,6 +9,7 @@ use shared::{ use crate::current_dispatcher::CURRENT_DISPATCHER; +pub mod current_batch_config; pub mod current_dispatcher; mod lazy; @@ -158,6 +159,12 @@ pub unsafe fn _use(usable: &JsValue) -> Result { _use.call1(&JsValue::null(), usable) } +#[wasm_bindgen(js_name = useTransition)] +pub unsafe fn use_transition() -> Result { + let use_transition = &CURRENT_DISPATCHER.current.as_ref().unwrap().use_transition; + use_transition.call0(&JsValue::null()) +} + #[wasm_bindgen(js_name = createContext)] pub unsafe fn create_context(default_value: &JsValue) -> JsValue { let context = Object::new(); diff --git a/scripts/build.js b/scripts/build.js index bad3355..0b0f6ec 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -72,17 +72,22 @@ const reactDomIndexBgData = fs.readFileSync(reactDomIndexFilename) fs.writeFileSync(reactDomIndexFilename, code1 + reactDomIndexBgData) // add Suspense + Fragment -const reactIndexFilename = `${cwd}/dist/react/index.js` -const reactIndexData = fs.readFileSync(reactIndexFilename) -fs.writeFileSync( - reactIndexFilename, - reactIndexData + - `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` -) -const reactTsIndexFilename = `${cwd}/dist/react/index.d.ts` -const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) -fs.writeFileSync( - reactTsIndexFilename, - reactTsIndexData + - `export const Suspense: string;\nexport const Fragment: string;\n` -) +;[ + {filename: 'index.js', tsFilename: 'index.d.ts'}, + {filename: 'jsx-dev-runtime.js', tsFilename: 'jsx-dev-runtime.d.ts'}, +].forEach(({filename, tsFilename}) => { + const reactIndexFilename = `${cwd}/dist/react/${filename}` + const reactIndexData = fs.readFileSync(reactIndexFilename) + fs.writeFileSync( + reactIndexFilename, + reactIndexData + + `export const Suspense='react.suspense';\nexport const Fragment='react.fragment';\n` + ) + const reactTsIndexFilename = `${cwd}/dist/react/${tsFilename}` + const reactTsIndexData = fs.readFileSync(reactTsIndexFilename) + fs.writeFileSync( + reactTsIndexFilename, + reactTsIndexData + + `export const Suspense: string;\nexport const Fragment: string;\n` + ) +}) From d130d2d5a20781e17859eb3e8145f9fcb7dedc67 Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Wed, 25 Sep 2024 14:04:09 +0800 Subject: [PATCH 3/6] temp commit --- .../src/useTransition/PostsTab.tsx | 4 +- .../hello-world/src/useTransition/index.tsx | 2 +- packages/react-dom/src/host_config.rs | 48 +- packages/react-dom/src/synthetic_event.rs | 1 - packages/react-reconciler/src/begin_work.rs | 8 +- packages/react-reconciler/src/child_fiber.rs | 10 +- packages/react-reconciler/src/fiber.rs | 4 +- packages/react-reconciler/src/fiber_hooks.rs | 31 +- packages/react-reconciler/src/update_queue.rs | 3 +- packages/react-reconciler/src/work_loop.rs | 22 +- packages/react/src/current_batch_config.rs | 2 +- packages/scheduler/src/lib.rs | 84 +-- packages/scheduler/src/lib_copy.rs | 487 ++++++++++++++++++ packages/shared/src/lib.rs | 5 +- 14 files changed, 612 insertions(+), 99 deletions(-) create mode 100644 packages/scheduler/src/lib_copy.rs diff --git a/examples/hello-world/src/useTransition/PostsTab.tsx b/examples/hello-world/src/useTransition/PostsTab.tsx index c0193c5..e415937 100644 --- a/examples/hello-world/src/useTransition/PostsTab.tsx +++ b/examples/hello-world/src/useTransition/PostsTab.tsx @@ -1,13 +1,13 @@ const PostsTab = function PostsTab() { const items = [] - for (let i = 0; i < 500; i++) { + for (let i = 0; i < 50; i++) { items.push() } return
      {items}
    } function SlowPost({index}) { const startTime = performance.now() - while (performance.now() - startTime < 4) {} + while (performance.now() - startTime < 8) {} return
  • 博文 #{index + 1}
  • } export default PostsTab diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx index 4b0c63e..1c3a1da 100644 --- a/examples/hello-world/src/useTransition/index.tsx +++ b/examples/hello-world/src/useTransition/index.tsx @@ -6,7 +6,7 @@ import ContactTab from './ContactTab' export default function App() { const [isPending, startTransition] = useTransition() const [tab, setTab] = useState('about') - console.log('hello') + console.log('tab', tab, isPending) function selectTab(nextTab) { startTransition(() => { setTab(nextTab) diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index a1eac2e..24692e2 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -88,15 +88,15 @@ impl HostConfig for ReactDomHostConfig { let c = child.clone().downcast::().unwrap(); match p.append_child(&c) { Ok(_) => { - log!( - "append_initial_child {:?} {:?}", - p, - if c.first_child().is_some() { - c.first_child().clone().unwrap().text_content() - } else { - c.text_content() - } - ); + // log!( + // "append_initial_child {:?} {:?}", + // p, + // if c.first_child().is_some() { + // c.first_child().clone().unwrap().text_content() + // } else { + // c.text_content() + // } + // ); } Err(_) => { log!("Failed to append_initial_child {:?} {:?}", p, c); @@ -113,7 +113,7 @@ impl HostConfig for ReactDomHostConfig { let c = child.clone().downcast::().unwrap(); match p.remove_child(&c) { Ok(_) => { - log!("remove_child {:?} {:?}", p, c); + // log!("remove_child {:?} {:?}", p, c); } Err(e) => { log!("Failed to remove_child {:?} {:?} {:?} ", e, p, c); @@ -132,20 +132,20 @@ impl HostConfig for ReactDomHostConfig { let child = child.clone().downcast::().unwrap(); match parent.insert_before(&child, Some(&before)) { Ok(_) => { - log!( - "insert_child_to_container {:?} {:?} {:?}", - parent, - if before.first_child().is_some() { - before.first_child().clone().unwrap().text_content() - } else { - before.text_content() - }, - if child.first_child().is_some() { - child.first_child().clone().unwrap().text_content() - } else { - child.text_content() - } - ); + // log!( + // "insert_child_to_container {:?} {:?} {:?}", + // parent, + // if before.first_child().is_some() { + // before.first_child().clone().unwrap().text_content() + // } else { + // before.text_content() + // }, + // if child.first_child().is_some() { + // child.first_child().clone().unwrap().text_content() + // } else { + // child.text_content() + // } + // ); } Err(_) => { log!( diff --git a/packages/react-dom/src/synthetic_event.rs b/packages/react-dom/src/synthetic_event.rs index fecc212..5b2ffd8 100644 --- a/packages/react-dom/src/synthetic_event.rs +++ b/packages/react-dom/src/synthetic_event.rs @@ -81,7 +81,6 @@ fn trigger_event_flow(paths: Vec, se: &Event) { fn dispatch_event(container: &Element, event_type: String, e: &Event) { if e.target().is_none() { - log!("Target is none"); return; } diff --git a/packages/react-reconciler/src/begin_work.rs b/packages/react-reconciler/src/begin_work.rs index 4d32fd0..984fb4d 100644 --- a/packages/react-reconciler/src/begin_work.rs +++ b/packages/react-reconciler/src/begin_work.rs @@ -51,7 +51,6 @@ pub fn begin_work( work_in_progress: Rc>, render_lane: Lane, ) -> Result>>, JsValue> { - log!("begin_work {:?}", work_in_progress.clone()); unsafe { DID_RECEIVE_UPDATE = false; }; @@ -96,7 +95,7 @@ pub fn begin_work( } } - work_in_progress.borrow_mut().lanes = Lane::NoLane; + work_in_progress.borrow_mut().lanes -= render_lane.clone(); // if current.is_some() { // let current = current.clone().unwrap(); // current.borrow_mut().lanes = Lane::NoLane; @@ -280,7 +279,7 @@ fn update_suspense_component( 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( @@ -405,9 +404,6 @@ fn update_function_component( render_with_hooks(work_in_progress.clone(), Component, render_lane.clone())?; let current = { work_in_progress.borrow().alternate.clone() }; - log!("{:?} {:?}", work_in_progress.clone(), unsafe { - DID_RECEIVE_UPDATE - }); if current.is_some() && unsafe { !DID_RECEIVE_UPDATE } { bailout_hook(work_in_progress.clone(), render_lane.clone()); return Ok(bailout_on_already_finished_work( diff --git a/packages/react-reconciler/src/child_fiber.rs b/packages/react-reconciler/src/child_fiber.rs index be41696..3c5ebcc 100644 --- a/packages/react-reconciler/src/child_fiber.rs +++ b/packages/react-reconciler/src/child_fiber.rs @@ -389,11 +389,11 @@ fn reconcile_children_array( for (_, fiber) in existing_children { delete_child(return_fiber.clone(), fiber, should_track_effects); } - log!( - "first_new_fiber {:?} {:?}", - first_new_fiber, - first_new_fiber.clone().unwrap().borrow().sibling - ); + // log!( + // "first_new_fiber {:?} {:?}", + // first_new_fiber, + // first_new_fiber.clone().unwrap().borrow().sibling + // ); first_new_fiber } diff --git a/packages/react-reconciler/src/fiber.rs b/packages/react-reconciler/src/fiber.rs index 664c47d..00f05ef 100644 --- a/packages/react-reconciler/src/fiber.rs +++ b/packages/react-reconciler/src/fiber.rs @@ -115,7 +115,7 @@ impl Debug for FiberNode { self.flags, self.subtree_flags, self.lanes, - self.child_lanes + self.child_lanes, ) .expect("print error"); } @@ -299,7 +299,7 @@ pub struct FiberRootNode { pub finished_lanes: Lane, pub suspended_lanes: Lane, pub pinged_lanes: Lane, // Records the processed suspended lanes, comes from suspended lanes - pub callback_node: Option, + pub callback_node: Option>>, pub callback_priority: Lane, pub pending_passive_effects: Rc>, pub ping_cache: Option>>>>, diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index fc200cf..76ade19 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -279,10 +279,16 @@ fn update_work_in_progress_hook() -> Option>> { .clone(); match current { - None => None, + None => { + log!("1"); + None + } Some(current) => match current.clone().borrow().memoized_state.clone() { Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), - _ => None, + _ => { + log!("2"); + None + } }, } } @@ -293,13 +299,23 @@ fn update_work_in_progress_hook() -> Option>> { None => match CURRENTLY_RENDERING_FIBER.clone() { Some(current) => match current.clone().borrow().memoized_state.clone() { Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), - _ => None, + _ => { + log!("3"); + None + } }, - _ => None, + _ => { + log!("4"); + None + } }, Some(work_in_progress_hook) => work_in_progress_hook.clone().borrow().next.clone(), }; - + log!( + "next_current_hook {:?} {:?}", + next_current_hook, + next_work_in_progress_hook + ); if next_work_in_progress_hook.is_some() { WORK_IN_PROGRESS_HOOK = next_work_in_progress_hook.clone(); CURRENT_HOOK = next_current_hook.clone(); @@ -619,7 +635,6 @@ fn update_effect(create: Function, deps: JsValue) { .unwrap() .borrow_mut() .flags |= Flags::PassiveEffect; - log!("CURRENTLY_RENDERING_FIBER.as_ref().unwrap().borrow_mut()"); hook.as_ref().unwrap().clone().borrow_mut().memoized_state = Some(MemoizedState::Effect(push_effect( @@ -803,7 +818,7 @@ fn mount_transition() -> Vec { closure.forget(); hook.as_ref().unwrap().clone().borrow_mut().memoized_state = Some(MemoizedState::MemoizedJsValue(start.clone().into())); - + log!("mount_transition"); vec![JsValue::from_bool(is_pending), start.into()] } @@ -830,7 +845,7 @@ fn start_transition(set_pending: Function, callback: Function) { let prev_transition = unsafe { REACT_CURRENT_BATCH_CONFIG.transition }; // low priority - unsafe { REACT_CURRENT_BATCH_CONFIG.transition = 1 }; + unsafe { REACT_CURRENT_BATCH_CONFIG.transition = Lane::TransitionLane.bits() }; callback.call0(&JsValue::null()); set_pending.call1(&JsValue::null(), &JsValue::from_bool(false)); diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index b3e6c8d..791f1e7 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -82,6 +82,7 @@ pub fn create_update_queue() -> Rc> { })) } +#[derive(Debug)] pub struct ReturnOfProcessUpdateQueue { pub memoized_state: Option, pub base_state: Option, @@ -239,6 +240,6 @@ pub fn process_update_queue( result.base_state = new_base_state; result.base_queue = new_base_queue_last.clone(); } - + log!("tab result {:?}", result); result } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 8d444eb..8c36dd2 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -123,6 +123,7 @@ pub fn ensure_root_is_scheduled(root: Rc>) { } if existing_callback.is_some() { + log!("unstable_cancel_callback {:?}", existing_callback); unstable_cancel_callback(existing_callback.unwrap()) } @@ -147,6 +148,7 @@ pub fn ensure_root_is_scheduled(root: Rc>) { } let scheduler_priority = lanes_to_scheduler_priority(cur_priority.clone()); let closure = Closure::wrap(Box::new(move |did_timeout_js_value: JsValue| { + log!("did_timeout_js_value1 {:?}", did_timeout_js_value); let did_timeout = did_timeout_js_value.as_bool().unwrap(); perform_concurrent_work_on_root(root_cloned.clone(), did_timeout) }) as Box JsValue>); @@ -175,6 +177,11 @@ fn render_root(root: Rc>, lane: Lane, should_time_slice: } if unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE != lane } { + log!( + "WORK_IN_PROGRESS_ROOT_RENDER_LANE {:?} {:?}", + unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE.clone() }, + lane + ); prepare_fresh_stack(root.clone(), lane.clone()); } @@ -213,11 +220,11 @@ fn render_root(root: Rc>, lane: Lane, should_time_slice: } log!("render over {:?}", *root.clone().borrow()); - // log!("render over {:?}", unsafe { WORK_IN_PROGRESS.clone() }); - // log!("render over"); + + // log!("WORK_IN_PROGRESS {:?}", unsafe { WORK_IN_PROGRESS.clone() }); unsafe { - WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; + // WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; if should_time_slice && WORK_IN_PROGRESS.is_some() { return ROOT_INCOMPLETE; @@ -256,7 +263,9 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout ensure_root_is_scheduled(root.clone()); if exit_status == ROOT_INCOMPLETE { - if root.borrow().callback_node.as_ref().unwrap().id != cur_callback_node.unwrap().id { + if root.borrow().callback_node.clone().unwrap().borrow().id + != cur_callback_node.unwrap().borrow().id + { // 调度了更高优更新,这个更新已经被取消了 return JsValue::undefined(); } @@ -282,7 +291,7 @@ fn perform_concurrent_work_on_root(root: Rc>, did_timeout }; 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 { todo!("Unsupported status of concurrent render") @@ -316,7 +325,7 @@ fn perform_sync_work_on_root(root: Rc>, lanes: Lane) { 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 }; + // unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane }; mark_root_suspended(root.clone(), next_lane); ensure_root_is_scheduled(root.clone()); } else { @@ -439,7 +448,6 @@ fn work_loop_sync() -> Result<(), JsValue> { fn work_loop_concurrent() -> Result<(), JsValue> { unsafe { while WORK_IN_PROGRESS.is_some() && !unstable_should_yield_to_host() { - log!("work_loop_concurrent"); perform_unit_of_work(WORK_IN_PROGRESS.clone().unwrap())?; } } diff --git a/packages/react/src/current_batch_config.rs b/packages/react/src/current_batch_config.rs index 6d87e20..63e1d2a 100644 --- a/packages/react/src/current_batch_config.rs +++ b/packages/react/src/current_batch_config.rs @@ -1,5 +1,5 @@ pub struct ReactCurrentBatchConfig { - pub transition: u8, + pub transition: u32, } pub static mut REACT_CURRENT_BATCH_CONFIG: ReactCurrentBatchConfig = diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index fbc551d..6a9b544 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -1,4 +1,6 @@ +use std::cell::RefCell; use std::cmp::{Ordering, PartialEq}; +use std::rc::Rc; use shared::log; use wasm_bindgen::prelude::*; @@ -11,8 +13,8 @@ mod heap; static FRAME_YIELD_MS: f64 = 5.0; static mut TASK_ID_COUNTER: u32 = 1; -static mut TASK_QUEUE: Vec = vec![]; -static mut TIMER_QUEUE: Vec = vec![]; +static mut TASK_QUEUE: Vec>> = vec![]; +static mut TIMER_QUEUE: Vec>> = vec![]; static mut IS_HOST_TIMEOUT_SCHEDULED: bool = false; static mut IS_HOST_CALLBACK_SCHEDULED: bool = false; static mut IS_PERFORMING_WORK: bool = false; @@ -23,7 +25,7 @@ static mut MESSAGE_CHANNEL: Option = None; // static mut MESSAGE_CHANNEL_LISTENED: bool = false; static mut START_TIME: f64 = -1.0; static mut CURRENT_PRIORITY_LEVEL: Priority = Priority::NormalPriority; -static mut CURRENT_TASK: Option<&Task> = None; +static mut CURRENT_TASK: Option<&Rc>> = None; static mut PORT1: Option = None; static mut PORT2: Option = None; @@ -243,19 +245,20 @@ fn main() { */ fn advance_timers(current_time: f64) { unsafe { - let mut timer = peek_mut(&mut TIMER_QUEUE); + let mut timer = peek(&TIMER_QUEUE); while timer.is_some() { let task = timer.unwrap(); - if task.callback.is_null() { + if task.borrow().callback.is_null() { pop(&mut TIMER_QUEUE); - } else if task.start_time <= current_time { + } else if task.borrow().start_time <= current_time { let t = pop(&mut TIMER_QUEUE); - task.sort_index = task.expiration_time; + let expiration_time = { task.borrow().expiration_time }; + task.borrow_mut().sort_index = expiration_time; push(&mut TASK_QUEUE, task.clone()); } else { return; } - timer = peek_mut(&mut TIMER_QUEUE); + timer = peek(&mut TIMER_QUEUE); } } } @@ -307,35 +310,36 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result current_time + if t.borrow().expiration_time > current_time && (!has_time_remaining || unstable_should_yield_to_host()) { break; } - let callback = t.callback.clone(); + let callback = t.borrow().callback.clone(); + log!("-----task {:?}", t); if callback.is_function() { - t.callback = JsValue::null(); - CURRENT_PRIORITY_LEVEL = t.priority_level.clone(); - let did_user_callback_timeout = t.expiration_time <= current_time; + t.borrow_mut().callback = JsValue::null(); + CURRENT_PRIORITY_LEVEL = t.borrow().priority_level.clone(); + let did_user_callback_timeout = t.borrow().expiration_time <= current_time; let continuation_callback = callback .dyn_ref::() .unwrap() .call1(&JsValue::null(), &JsValue::from(did_user_callback_timeout))?; current_time = unstable_now(); - + log!("continuation_callback {:?}", continuation_callback); if continuation_callback.is_function() { - t.callback = continuation_callback; + t.borrow_mut().callback = continuation_callback; } else { if match peek(&TASK_QUEUE) { None => false, - Some(task) => task == t, + Some(task) => task.borrow().id == t.borrow().id, } { pop(&mut TASK_QUEUE); } @@ -343,10 +347,11 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result Result>) { + let id = t.borrow().id; + unsafe { - for mut task in &mut TASK_QUEUE { - if task.id == id { - task.callback = JsValue::null(); + for task in &TASK_QUEUE { + if task.borrow().id == id { + task.borrow_mut().callback = JsValue::null(); + log!("TASK_QUEUE {:?}", TASK_QUEUE.clone()); } } - for mut task in &mut TIMER_QUEUE { - if task.id == id { - task.callback = JsValue::null(); + for task in &TIMER_QUEUE { + if task.borrow().id == id { + task.borrow_mut().callback = JsValue::null(); } } } @@ -430,7 +437,7 @@ pub fn unstable_schedule_callback( priority_level: Priority, callback: Function, delay: f64, -) -> Task { +) -> Rc> { let current_time = unstable_now(); let mut start_time = current_time; @@ -440,16 +447,16 @@ pub fn unstable_schedule_callback( let timeout = get_priority_timeout(priority_level.clone()); let expiration_time = start_time + timeout; - let mut new_task = Task::new( + let mut new_task = Rc::new(RefCell::new(Task::new( callback, priority_level.clone(), start_time, expiration_time, - ); + ))); let cloned = new_task.clone(); unsafe { if start_time > current_time { - new_task.sort_index = start_time; + new_task.borrow_mut().sort_index = start_time; push(&mut TIMER_QUEUE, new_task.clone()); if peek(&mut TASK_QUEUE).is_none() { @@ -465,7 +472,7 @@ pub fn unstable_schedule_callback( } } } else { - new_task.sort_index = expiration_time; + new_task.borrow_mut().sort_index = expiration_time; push(&mut TASK_QUEUE, new_task); if !IS_HOST_CALLBACK_SCHEDULED && !IS_PERFORMING_WORK { @@ -478,7 +485,10 @@ pub fn unstable_schedule_callback( cloned } -pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> Task { +pub fn unstable_schedule_callback_no_delay( + priority_level: Priority, + callback: Function, +) -> Rc> { unstable_schedule_callback(priority_level, callback, 0.0) } diff --git a/packages/scheduler/src/lib_copy.rs b/packages/scheduler/src/lib_copy.rs new file mode 100644 index 0000000..fbc551d --- /dev/null +++ b/packages/scheduler/src/lib_copy.rs @@ -0,0 +1,487 @@ +use std::cmp::{Ordering, PartialEq}; + +use shared::log; +use wasm_bindgen::prelude::*; +use web_sys::js_sys::{global, Function}; +use web_sys::{MessageChannel, MessagePort}; + +use crate::heap::{peek, peek_mut, pop, push}; + +mod heap; + +static FRAME_YIELD_MS: f64 = 5.0; +static mut TASK_ID_COUNTER: u32 = 1; +static mut TASK_QUEUE: Vec = vec![]; +static mut TIMER_QUEUE: Vec = vec![]; +static mut IS_HOST_TIMEOUT_SCHEDULED: bool = false; +static mut IS_HOST_CALLBACK_SCHEDULED: bool = false; +static mut IS_PERFORMING_WORK: bool = false; +static mut TASK_TIMEOUT_ID: f64 = -1.0; +static mut SCHEDULED_HOST_CALLBACK: Option bool> = None; +static mut IS_MESSAGE_LOOP_RUNNING: bool = false; +static mut MESSAGE_CHANNEL: Option = None; +// static mut MESSAGE_CHANNEL_LISTENED: bool = false; +static mut START_TIME: f64 = -1.0; +static mut CURRENT_PRIORITY_LEVEL: Priority = Priority::NormalPriority; +static mut CURRENT_TASK: Option<&Task> = None; +static mut PORT1: Option = None; +static mut PORT2: Option = None; + +#[derive(Clone, Debug)] +#[wasm_bindgen] +pub enum Priority { + ImmediatePriority = 1, + UserBlockingPriority = 2, + NormalPriority = 3, + LowPriority = 4, + IdlePriority = 5, +} + +#[wasm_bindgen] +extern "C" { + type Performance; + type Global; + #[wasm_bindgen(static_method_of = Performance, catch, js_namespace = performance, js_name = now)] + fn now() -> Result; + #[wasm_bindgen] + fn clearTimeout(id: f64); + #[wasm_bindgen] + fn setTimeout(closure: &Function, timeout: f64) -> f64; + #[wasm_bindgen(js_namespace = Date, js_name = now)] + fn date_now() -> f64; + + #[wasm_bindgen] + fn setImmediate(f: &Function); + + #[wasm_bindgen(method, getter, js_name = setImmediate)] + fn hasSetImmediate(this: &Global) -> JsValue; +} + +#[derive(Clone, Debug)] +pub struct Task { + pub id: u32, + callback: JsValue, + priority_level: Priority, + start_time: f64, + expiration_time: f64, + sort_index: f64, +} + +impl Task { + fn new( + callback: Function, + priority_level: Priority, + start_time: f64, + expiration_time: f64, + ) -> Self { + unsafe { + let s = Self { + id: TASK_ID_COUNTER, + callback: JsValue::from(callback), + priority_level, + start_time, + expiration_time, + sort_index: -1.0, + }; + TASK_ID_COUNTER += TASK_ID_COUNTER; + s + } + } +} + +impl Eq for Task {} + +impl PartialEq for Task { + fn eq(&self, other: &Self) -> bool { + self.id.cmp(&other.id) == Ordering::Equal + } +} + +impl PartialOrd for Task { + fn partial_cmp(&self, other: &Self) -> Option { + let mut sort_index_ordering; + + if self.sort_index.is_nan() { + if other.sort_index.is_nan() { + sort_index_ordering = Ordering::Equal + } else { + sort_index_ordering = Ordering::Less + } + } else if other.sort_index.is_nan() { + sort_index_ordering = (Ordering::Greater) + } else { + sort_index_ordering = self.sort_index.partial_cmp(&other.sort_index).unwrap() + } + + if sort_index_ordering != Ordering::Equal { + return Some(sort_index_ordering); + } + return self.id.partial_cmp(&other.id); + } +} + +impl Ord for Task { + fn cmp(&self, other: &Self) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +fn unstable_now() -> f64 { + Performance::now().unwrap_or_else(|_| date_now()) +} + +fn get_priority_timeout(priority_level: Priority) -> f64 { + match priority_level { + Priority::NormalPriority => 5000.0, + Priority::ImmediatePriority => -1.0, + Priority::UserBlockingPriority => 250.0, + Priority::IdlePriority => 1073741823.0, + Priority::LowPriority => 10000.0, + } +} + +fn cancel_host_timeout() { + unsafe { + clearTimeout(TASK_TIMEOUT_ID); + TASK_TIMEOUT_ID = -1.0; + } +} + +pub fn schedule_perform_work_until_deadline() { + let perform_work_closure = + Closure::wrap(Box::new(perform_work_until_deadline) as Box); + let perform_work_function = perform_work_closure + .as_ref() + .unchecked_ref::() + .clone(); + // let schedule_closure = Closure::wrap(Box::new(schedule_perform_work_until_deadline) as Box); + + if global() + .unchecked_into::() + .hasSetImmediate() + .is_function() + { + setImmediate(&perform_work_function); + } else if let Ok(message_channel) = MessageChannel::new() { + unsafe { + if PORT1.is_none() { + PORT1 = Some(message_channel.port1()); + PORT2 = Some(message_channel.port2()) + } + PORT1 + .as_ref() + .unwrap() + .set_onmessage(Some(&perform_work_function)); + PORT2 + .as_ref() + .unwrap() + .post_message(&JsValue::null()) + .expect("port post message panic"); + } + } else { + setTimeout(&perform_work_function, 0.0); + } + + perform_work_closure.forget(); +} + +fn perform_work_until_deadline() { + unsafe { + if SCHEDULED_HOST_CALLBACK.is_some() { + let scheduled_host_callback = SCHEDULED_HOST_CALLBACK.unwrap(); + let current_time = unstable_now(); + + START_TIME = current_time; + let has_time_remaining = true; + let has_more_work = scheduled_host_callback(has_time_remaining, current_time); + if has_more_work { + schedule_perform_work_until_deadline(); + } else { + IS_MESSAGE_LOOP_RUNNING = false; + SCHEDULED_HOST_CALLBACK = None; + } + } else { + IS_MESSAGE_LOOP_RUNNING = false + } + } +} + +/** +static mut MY_V: Vec> = vec![]; + +#[derive(Debug)] +struct Task { + id: f64, +} + +fn peek<'a>(v: &'a mut Vec>) -> &'a Box { + &v[0] +} + +fn pop<'a>(v: &'a mut Vec>) -> Box { + let t = v.swap_remove(0); + t +} + +fn main() { + unsafe { + MY_V = vec![Box::new(Task { + id: 10000.0 + })]; + + let t = peek(&mut MY_V); + + println!("{:?}", t); + + pop(&mut MY_V); + // let a = pop(&mut MY_V); + + println!("{:?}", t); + }; +} + + */ +fn advance_timers(current_time: f64) { + unsafe { + let mut timer = peek_mut(&mut TIMER_QUEUE); + while timer.is_some() { + let task = timer.unwrap(); + if task.callback.is_null() { + pop(&mut TIMER_QUEUE); + } else if task.start_time <= current_time { + let t = pop(&mut TIMER_QUEUE); + task.sort_index = task.expiration_time; + push(&mut TASK_QUEUE, task.clone()); + } else { + return; + } + timer = peek_mut(&mut TIMER_QUEUE); + } + } +} + +fn flush_work(has_time_remaining: bool, initial_time: f64) -> bool { + unsafe { + IS_HOST_CALLBACK_SCHEDULED = false; + if IS_HOST_TIMEOUT_SCHEDULED { + IS_HOST_TIMEOUT_SCHEDULED = false; + cancel_host_timeout(); + } + + IS_PERFORMING_WORK = true; + let previous_priority_level = CURRENT_PRIORITY_LEVEL.clone(); + + let has_more = work_loop(has_time_remaining, initial_time).unwrap(); + // .unwrap_or_else(|_| { + // log!("work_loop error"); + // false + // }); + + CURRENT_TASK = None; + CURRENT_PRIORITY_LEVEL = previous_priority_level.clone(); + IS_PERFORMING_WORK = false; + + return has_more; + } +} + +pub fn unstable_should_yield_to_host() -> bool { + unsafe { + let time_elapsed = unstable_now() - START_TIME; + if time_elapsed < FRAME_YIELD_MS { + return false; + } + } + return true; +} + +pub fn unstable_run_with_priority(priority_level: Priority, event_handler: &Function) { + let previous_priority_level = unsafe { CURRENT_PRIORITY_LEVEL.clone() }; + unsafe { CURRENT_PRIORITY_LEVEL = priority_level.clone() }; + + event_handler.call0(&JsValue::null()); + unsafe { CURRENT_PRIORITY_LEVEL = previous_priority_level.clone() }; +} + +fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result { + unsafe { + let mut current_time = initial_time; + advance_timers(current_time); + let mut current_task = peek_mut(&mut TASK_QUEUE); + + CURRENT_TASK = peek(&mut TASK_QUEUE); + while current_task.is_some() { + let mut t = current_task.unwrap(); + + if t.expiration_time > current_time + && (!has_time_remaining || unstable_should_yield_to_host()) + { + break; + } + + let callback = t.callback.clone(); + if callback.is_function() { + t.callback = JsValue::null(); + CURRENT_PRIORITY_LEVEL = t.priority_level.clone(); + let did_user_callback_timeout = t.expiration_time <= current_time; + let continuation_callback = callback + .dyn_ref::() + .unwrap() + .call1(&JsValue::null(), &JsValue::from(did_user_callback_timeout))?; + current_time = unstable_now(); + + if continuation_callback.is_function() { + t.callback = continuation_callback; + } else { + if match peek(&TASK_QUEUE) { + None => false, + Some(task) => task == t, + } { + pop(&mut TASK_QUEUE); + } + } + + advance_timers(current_time); + } else { + pop(&mut TASK_QUEUE); + } + + current_task = peek_mut(&mut TASK_QUEUE); + CURRENT_TASK = peek(&TASK_QUEUE); + } + + if CURRENT_TASK.is_some() { + return Ok(true); + } else { + let first_timer = peek(&mut TIMER_QUEUE); + if first_timer.is_some() { + let task = first_timer.unwrap(); + + request_host_timeout(handle_timeout, task.start_time - current_time); + } + + return Ok(false); + } + } +} + +fn request_host_callback(callback: fn(bool, f64) -> bool) { + unsafe { + SCHEDULED_HOST_CALLBACK = Some(callback); + if !IS_MESSAGE_LOOP_RUNNING { + IS_MESSAGE_LOOP_RUNNING = true; + schedule_perform_work_until_deadline(); + } + } +} + +fn handle_timeout(current_time: f64) { + unsafe { + IS_HOST_TIMEOUT_SCHEDULED = false; + advance_timers(current_time); + + if !IS_HOST_TIMEOUT_SCHEDULED { + if peek(&mut TASK_QUEUE).is_some() { + IS_HOST_CALLBACK_SCHEDULED = true; + request_host_callback(flush_work); + } else { + let first_timer = peek(&mut TIMER_QUEUE); + if first_timer.is_some() { + let first_timer_task = first_timer.unwrap(); + request_host_timeout( + handle_timeout, + first_timer_task.start_time - current_time, + ); + } + } + } + } +} + +fn request_host_timeout(callback: fn(f64), ms: f64) { + unsafe { + let closure = Closure::wrap(Box::new(move || { + callback(unstable_now()); + }) as Box); + let function = closure.as_ref().unchecked_ref::().clone(); + closure.forget(); + TASK_TIMEOUT_ID = setTimeout(&function, ms); + } +} + +pub fn unstable_cancel_callback(task: Task) { + let id = task.id; + unsafe { + for mut task in &mut TASK_QUEUE { + if task.id == id { + task.callback = JsValue::null(); + } + } + + for mut task in &mut TIMER_QUEUE { + if task.id == id { + task.callback = JsValue::null(); + } + } + } +} + +pub fn unstable_schedule_callback( + priority_level: Priority, + callback: Function, + delay: f64, +) -> Task { + let current_time = unstable_now(); + let mut start_time = current_time; + + if delay > 0.0 { + start_time += delay; + } + + let timeout = get_priority_timeout(priority_level.clone()); + let expiration_time = start_time + timeout; + let mut new_task = Task::new( + callback, + priority_level.clone(), + start_time, + expiration_time, + ); + let cloned = new_task.clone(); + unsafe { + if start_time > current_time { + new_task.sort_index = start_time; + push(&mut TIMER_QUEUE, new_task.clone()); + + if peek(&mut TASK_QUEUE).is_none() { + if let Some(task) = peek(&mut TIMER_QUEUE) { + if task == &new_task { + if IS_HOST_TIMEOUT_SCHEDULED { + cancel_host_timeout(); + } else { + IS_HOST_TIMEOUT_SCHEDULED = true; + } + request_host_timeout(handle_timeout, start_time - current_time); + } + } + } + } else { + new_task.sort_index = expiration_time; + push(&mut TASK_QUEUE, new_task); + + if !IS_HOST_CALLBACK_SCHEDULED && !IS_PERFORMING_WORK { + IS_HOST_CALLBACK_SCHEDULED = true; + request_host_callback(flush_work); + } + } + } + + cloned +} + +pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> Task { + unstable_schedule_callback(priority_level, callback, 0.0) +} + +pub fn unstable_get_current_priority_level() -> Priority { + unsafe { CURRENT_PRIORITY_LEVEL.clone() } +} diff --git a/packages/shared/src/lib.rs b/packages/shared/src/lib.rs index c121773..19ed72b 100644 --- a/packages/shared/src/lib.rs +++ b/packages/shared/src/lib.rs @@ -20,10 +20,7 @@ macro_rules! log { pub fn derive_from_js_value(js_value: &JsValue, str: &str) -> JsValue { match Reflect::get(&js_value, &JsValue::from_str(str)) { Ok(v) => v, - Err(_) => { - log!("derive {} from {:?} error", str, js_value); - JsValue::undefined() - } + Err(_) => JsValue::undefined(), } } From 6fa8c0b49e684ccee5afc02beb3a81df8f1f5e9a Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 26 Sep 2024 12:22:16 +0800 Subject: [PATCH 4/6] fix process_update_queue bug --- .../src/useTransition/PostsTab.tsx | 2 +- .../hello-world/src/useTransition/index.tsx | 3 +- packages/react-reconciler/src/fiber_hooks.rs | 6 +-- packages/react-reconciler/src/update_queue.rs | 48 +++++++++++++++---- packages/react-reconciler/src/work_loop.rs | 7 +-- 5 files changed, 48 insertions(+), 18 deletions(-) diff --git a/examples/hello-world/src/useTransition/PostsTab.tsx b/examples/hello-world/src/useTransition/PostsTab.tsx index e415937..6bebc5c 100644 --- a/examples/hello-world/src/useTransition/PostsTab.tsx +++ b/examples/hello-world/src/useTransition/PostsTab.tsx @@ -7,7 +7,7 @@ const PostsTab = function PostsTab() { } function SlowPost({index}) { const startTime = performance.now() - while (performance.now() - startTime < 8) {} + while (performance.now() - startTime < 32) {} return
  • 博文 #{index + 1}
  • } export default PostsTab diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx index 1c3a1da..26cdc9f 100644 --- a/examples/hello-world/src/useTransition/index.tsx +++ b/examples/hello-world/src/useTransition/index.tsx @@ -6,12 +6,13 @@ import ContactTab from './ContactTab' export default function App() { const [isPending, startTransition] = useTransition() const [tab, setTab] = useState('about') - console.log('tab', tab, isPending) + console.log('tab', tab) function selectTab(nextTab) { startTransition(() => { setTab(nextTab) }) } + return (
    selectTab('about')}> diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index 76ade19..cec7f46 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -311,11 +311,7 @@ fn update_work_in_progress_hook() -> Option>> { }, Some(work_in_progress_hook) => work_in_progress_hook.clone().borrow().next.clone(), }; - log!( - "next_current_hook {:?} {:?}", - next_current_hook, - next_work_in_progress_hook - ); + if next_work_in_progress_hook.is_some() { WORK_IN_PROGRESS_HOOK = next_work_in_progress_hook.clone(); CURRENT_HOOK = next_current_hook.clone(); diff --git a/packages/react-reconciler/src/update_queue.rs b/packages/react-reconciler/src/update_queue.rs index 791f1e7..1239762 100644 --- a/packages/react-reconciler/src/update_queue.rs +++ b/packages/react-reconciler/src/update_queue.rs @@ -1,19 +1,19 @@ use std::cell::RefCell; use std::rc::Rc; +use shared::log; +use std::fmt::{write, Debug, Formatter}; use wasm_bindgen::{JsCast, JsValue}; use web_sys::js_sys::Function; -use shared::log; - use crate::fiber::{FiberNode, MemoizedState}; -use crate::fiber_hooks::{basic_state_reducer, Effect}; +use crate::fiber_hooks::Effect; use crate::fiber_lanes::{is_subset_of_lanes, merge_lanes, Lane}; #[derive(Clone, Debug)] pub struct UpdateAction; -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct Update { pub action: Option, pub lane: Lane, @@ -22,6 +22,12 @@ pub struct Update { pub eager_state: Option, } +impl Debug for Update { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?} {:?}", self.action, self.lane) + } +} + #[derive(Clone, Debug)] pub struct UpdateType { pub pending: Option>>, @@ -82,13 +88,37 @@ pub fn create_update_queue() -> Rc> { })) } -#[derive(Debug)] pub struct ReturnOfProcessUpdateQueue { pub memoized_state: Option, pub base_state: Option, pub base_queue: Option>>, } +impl Debug for ReturnOfProcessUpdateQueue { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "memoized_state:{:?} base_state:{:?}", + self.memoized_state, self.base_state + ); + + if self.base_queue.is_some() { + let base_queue = self.base_queue.clone().unwrap(); + write!(f, " base_queue:{:?}", base_queue); + let mut next_option = base_queue.borrow().next.clone(); + while next_option.is_some() { + let next = next_option.clone().unwrap(); + if Rc::ptr_eq(&next, &base_queue) { + break; + } + write!(f, "---> {:?}", next); + next_option = next.borrow().next.clone(); + } + } + Ok(()) + } +} + pub fn process_update_queue( base_state: Option, pending_update: Option>>, @@ -117,8 +147,9 @@ pub fn process_update_queue( loop { let mut update = pending.clone().unwrap(); let update_lane = update.borrow().lane.clone(); + // log!("update {:?} render_lanes {:?}", update, render_lanes); if !is_subset_of_lanes(render_lanes.clone(), update_lane.clone()) { - // underpriority + // log!("underpriority update {:?}", update); let clone = Rc::new(RefCell::new(create_update( update.borrow().action.clone().unwrap(), update_lane.clone(), @@ -135,6 +166,7 @@ pub fn process_update_queue( new_base_state = result.memoized_state.clone(); } else { new_base_queue_last.clone().unwrap().borrow_mut().next = Some(clone.clone()); + new_base_queue_last = Some(clone.clone()); } } else { if new_base_queue_last.is_some() { @@ -233,13 +265,13 @@ pub fn process_update_queue( if new_base_queue_last.is_none() { new_base_state = new_state.clone(); } else { - new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_last.clone(); + new_base_queue_last.clone().unwrap().borrow_mut().next = new_base_queue_first.clone(); } result.memoized_state = new_state; result.base_state = new_base_state; result.base_queue = new_base_queue_last.clone(); } - log!("tab result {:?}", result); + // log!("result:{:?}", result); result } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index 8c36dd2..a95bf6e 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -215,14 +215,15 @@ fn render_root(root: Rc>, lane: Lane, should_time_slice: Ok(_) => { break; } - Err(e) => handle_throw(root.clone(), e), + Err(e) => { + log!("e {:?}", e); + handle_throw(root.clone(), e) + } }; } log!("render over {:?}", *root.clone().borrow()); - // log!("WORK_IN_PROGRESS {:?}", unsafe { WORK_IN_PROGRESS.clone() }); - unsafe { // WORK_IN_PROGRESS_ROOT_RENDER_LANE = Lane::NoLane; From c57a25d6be50688da442740ce58a6d455b19f6ac Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 26 Sep 2024 14:34:28 +0800 Subject: [PATCH 5/6] rollback some changes --- .../src/useTransition/AboutTab.tsx | 2 +- .../src/useTransition/ContactTab.tsx | 4 +- .../hello-world/src/useTransition/index.tsx | 3 +- packages/react-dom/src/host_config.rs | 48 +- packages/react-reconciler/src/fiber_hooks.rs | 21 +- packages/react-reconciler/src/work_loop.rs | 6 - packages/scheduler/src/lib.rs | 5 - packages/scheduler/src/lib_copy.rs | 487 ------------------ 8 files changed, 32 insertions(+), 544 deletions(-) delete mode 100644 packages/scheduler/src/lib_copy.rs diff --git a/examples/hello-world/src/useTransition/AboutTab.tsx b/examples/hello-world/src/useTransition/AboutTab.tsx index fc18b9f..8620727 100644 --- a/examples/hello-world/src/useTransition/AboutTab.tsx +++ b/examples/hello-world/src/useTransition/AboutTab.tsx @@ -1,3 +1,3 @@ export default function AboutTab() { - return

    It's me.

    + return

    It's me, ayou.

    } diff --git a/examples/hello-world/src/useTransition/ContactTab.tsx b/examples/hello-world/src/useTransition/ContactTab.tsx index 13f3b36..83ebbc8 100644 --- a/examples/hello-world/src/useTransition/ContactTab.tsx +++ b/examples/hello-world/src/useTransition/ContactTab.tsx @@ -3,8 +3,8 @@ export default function ContactTab() { <>

    Contact:

      -
    • B Site:ayou
    • -
    • Wechat:ayou
    • +
    • E-mail:ayou@xx.com
    • +
    • Wechat:a22you
    ) diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx index 26cdc9f..cd387b7 100644 --- a/examples/hello-world/src/useTransition/index.tsx +++ b/examples/hello-world/src/useTransition/index.tsx @@ -6,7 +6,7 @@ import ContactTab from './ContactTab' export default function App() { const [isPending, startTransition] = useTransition() const [tab, setTab] = useState('about') - console.log('tab', tab) + function selectTab(nextTab) { startTransition(() => { setTab(nextTab) @@ -27,7 +27,6 @@ export default function App() { 联系我

    - {isPending && 'loading'} {tab === 'about' && } {tab === 'posts' && } {tab === 'contact' && } diff --git a/packages/react-dom/src/host_config.rs b/packages/react-dom/src/host_config.rs index 24692e2..a1eac2e 100644 --- a/packages/react-dom/src/host_config.rs +++ b/packages/react-dom/src/host_config.rs @@ -88,15 +88,15 @@ impl HostConfig for ReactDomHostConfig { let c = child.clone().downcast::().unwrap(); match p.append_child(&c) { Ok(_) => { - // log!( - // "append_initial_child {:?} {:?}", - // p, - // if c.first_child().is_some() { - // c.first_child().clone().unwrap().text_content() - // } else { - // c.text_content() - // } - // ); + log!( + "append_initial_child {:?} {:?}", + p, + if c.first_child().is_some() { + c.first_child().clone().unwrap().text_content() + } else { + c.text_content() + } + ); } Err(_) => { log!("Failed to append_initial_child {:?} {:?}", p, c); @@ -113,7 +113,7 @@ impl HostConfig for ReactDomHostConfig { let c = child.clone().downcast::().unwrap(); match p.remove_child(&c) { Ok(_) => { - // log!("remove_child {:?} {:?}", p, c); + log!("remove_child {:?} {:?}", p, c); } Err(e) => { log!("Failed to remove_child {:?} {:?} {:?} ", e, p, c); @@ -132,20 +132,20 @@ impl HostConfig for ReactDomHostConfig { let child = child.clone().downcast::().unwrap(); match parent.insert_before(&child, Some(&before)) { Ok(_) => { - // log!( - // "insert_child_to_container {:?} {:?} {:?}", - // parent, - // if before.first_child().is_some() { - // before.first_child().clone().unwrap().text_content() - // } else { - // before.text_content() - // }, - // if child.first_child().is_some() { - // child.first_child().clone().unwrap().text_content() - // } else { - // child.text_content() - // } - // ); + log!( + "insert_child_to_container {:?} {:?} {:?}", + parent, + if before.first_child().is_some() { + before.first_child().clone().unwrap().text_content() + } else { + before.text_content() + }, + if child.first_child().is_some() { + child.first_child().clone().unwrap().text_content() + } else { + child.text_content() + } + ); } Err(_) => { log!( diff --git a/packages/react-reconciler/src/fiber_hooks.rs b/packages/react-reconciler/src/fiber_hooks.rs index cec7f46..aaf206e 100644 --- a/packages/react-reconciler/src/fiber_hooks.rs +++ b/packages/react-reconciler/src/fiber_hooks.rs @@ -279,16 +279,10 @@ fn update_work_in_progress_hook() -> Option>> { .clone(); match current { - None => { - log!("1"); - None - } + None => None, Some(current) => match current.clone().borrow().memoized_state.clone() { Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), - _ => { - log!("2"); - None - } + _ => None, }, } } @@ -299,15 +293,9 @@ fn update_work_in_progress_hook() -> Option>> { None => match CURRENTLY_RENDERING_FIBER.clone() { Some(current) => match current.clone().borrow().memoized_state.clone() { Some(MemoizedState::Hook(memoized_state)) => Some(memoized_state.clone()), - _ => { - log!("3"); - None - } + _ => None, }, - _ => { - log!("4"); - None - } + _ => None, }, Some(work_in_progress_hook) => work_in_progress_hook.clone().borrow().next.clone(), }; @@ -814,7 +802,6 @@ fn mount_transition() -> Vec { closure.forget(); hook.as_ref().unwrap().clone().borrow_mut().memoized_state = Some(MemoizedState::MemoizedJsValue(start.clone().into())); - log!("mount_transition"); vec![JsValue::from_bool(is_pending), start.into()] } diff --git a/packages/react-reconciler/src/work_loop.rs b/packages/react-reconciler/src/work_loop.rs index a95bf6e..0c67ca0 100644 --- a/packages/react-reconciler/src/work_loop.rs +++ b/packages/react-reconciler/src/work_loop.rs @@ -123,7 +123,6 @@ pub fn ensure_root_is_scheduled(root: Rc>) { } if existing_callback.is_some() { - log!("unstable_cancel_callback {:?}", existing_callback); unstable_cancel_callback(existing_callback.unwrap()) } @@ -177,11 +176,6 @@ fn render_root(root: Rc>, lane: Lane, should_time_slice: } if unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE != lane } { - log!( - "WORK_IN_PROGRESS_ROOT_RENDER_LANE {:?} {:?}", - unsafe { WORK_IN_PROGRESS_ROOT_RENDER_LANE.clone() }, - lane - ); prepare_fresh_stack(root.clone(), lane.clone()); } diff --git a/packages/scheduler/src/lib.rs b/packages/scheduler/src/lib.rs index 6a9b544..f59368b 100644 --- a/packages/scheduler/src/lib.rs +++ b/packages/scheduler/src/lib.rs @@ -323,7 +323,6 @@ fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result Result Result>) { for task in &TASK_QUEUE { if task.borrow().id == id { task.borrow_mut().callback = JsValue::null(); - log!("TASK_QUEUE {:?}", TASK_QUEUE.clone()); } } diff --git a/packages/scheduler/src/lib_copy.rs b/packages/scheduler/src/lib_copy.rs deleted file mode 100644 index fbc551d..0000000 --- a/packages/scheduler/src/lib_copy.rs +++ /dev/null @@ -1,487 +0,0 @@ -use std::cmp::{Ordering, PartialEq}; - -use shared::log; -use wasm_bindgen::prelude::*; -use web_sys::js_sys::{global, Function}; -use web_sys::{MessageChannel, MessagePort}; - -use crate::heap::{peek, peek_mut, pop, push}; - -mod heap; - -static FRAME_YIELD_MS: f64 = 5.0; -static mut TASK_ID_COUNTER: u32 = 1; -static mut TASK_QUEUE: Vec = vec![]; -static mut TIMER_QUEUE: Vec = vec![]; -static mut IS_HOST_TIMEOUT_SCHEDULED: bool = false; -static mut IS_HOST_CALLBACK_SCHEDULED: bool = false; -static mut IS_PERFORMING_WORK: bool = false; -static mut TASK_TIMEOUT_ID: f64 = -1.0; -static mut SCHEDULED_HOST_CALLBACK: Option bool> = None; -static mut IS_MESSAGE_LOOP_RUNNING: bool = false; -static mut MESSAGE_CHANNEL: Option = None; -// static mut MESSAGE_CHANNEL_LISTENED: bool = false; -static mut START_TIME: f64 = -1.0; -static mut CURRENT_PRIORITY_LEVEL: Priority = Priority::NormalPriority; -static mut CURRENT_TASK: Option<&Task> = None; -static mut PORT1: Option = None; -static mut PORT2: Option = None; - -#[derive(Clone, Debug)] -#[wasm_bindgen] -pub enum Priority { - ImmediatePriority = 1, - UserBlockingPriority = 2, - NormalPriority = 3, - LowPriority = 4, - IdlePriority = 5, -} - -#[wasm_bindgen] -extern "C" { - type Performance; - type Global; - #[wasm_bindgen(static_method_of = Performance, catch, js_namespace = performance, js_name = now)] - fn now() -> Result; - #[wasm_bindgen] - fn clearTimeout(id: f64); - #[wasm_bindgen] - fn setTimeout(closure: &Function, timeout: f64) -> f64; - #[wasm_bindgen(js_namespace = Date, js_name = now)] - fn date_now() -> f64; - - #[wasm_bindgen] - fn setImmediate(f: &Function); - - #[wasm_bindgen(method, getter, js_name = setImmediate)] - fn hasSetImmediate(this: &Global) -> JsValue; -} - -#[derive(Clone, Debug)] -pub struct Task { - pub id: u32, - callback: JsValue, - priority_level: Priority, - start_time: f64, - expiration_time: f64, - sort_index: f64, -} - -impl Task { - fn new( - callback: Function, - priority_level: Priority, - start_time: f64, - expiration_time: f64, - ) -> Self { - unsafe { - let s = Self { - id: TASK_ID_COUNTER, - callback: JsValue::from(callback), - priority_level, - start_time, - expiration_time, - sort_index: -1.0, - }; - TASK_ID_COUNTER += TASK_ID_COUNTER; - s - } - } -} - -impl Eq for Task {} - -impl PartialEq for Task { - fn eq(&self, other: &Self) -> bool { - self.id.cmp(&other.id) == Ordering::Equal - } -} - -impl PartialOrd for Task { - fn partial_cmp(&self, other: &Self) -> Option { - let mut sort_index_ordering; - - if self.sort_index.is_nan() { - if other.sort_index.is_nan() { - sort_index_ordering = Ordering::Equal - } else { - sort_index_ordering = Ordering::Less - } - } else if other.sort_index.is_nan() { - sort_index_ordering = (Ordering::Greater) - } else { - sort_index_ordering = self.sort_index.partial_cmp(&other.sort_index).unwrap() - } - - if sort_index_ordering != Ordering::Equal { - return Some(sort_index_ordering); - } - return self.id.partial_cmp(&other.id); - } -} - -impl Ord for Task { - fn cmp(&self, other: &Self) -> Ordering { - self.partial_cmp(other).unwrap_or(Ordering::Equal) - } -} - -fn unstable_now() -> f64 { - Performance::now().unwrap_or_else(|_| date_now()) -} - -fn get_priority_timeout(priority_level: Priority) -> f64 { - match priority_level { - Priority::NormalPriority => 5000.0, - Priority::ImmediatePriority => -1.0, - Priority::UserBlockingPriority => 250.0, - Priority::IdlePriority => 1073741823.0, - Priority::LowPriority => 10000.0, - } -} - -fn cancel_host_timeout() { - unsafe { - clearTimeout(TASK_TIMEOUT_ID); - TASK_TIMEOUT_ID = -1.0; - } -} - -pub fn schedule_perform_work_until_deadline() { - let perform_work_closure = - Closure::wrap(Box::new(perform_work_until_deadline) as Box); - let perform_work_function = perform_work_closure - .as_ref() - .unchecked_ref::() - .clone(); - // let schedule_closure = Closure::wrap(Box::new(schedule_perform_work_until_deadline) as Box); - - if global() - .unchecked_into::() - .hasSetImmediate() - .is_function() - { - setImmediate(&perform_work_function); - } else if let Ok(message_channel) = MessageChannel::new() { - unsafe { - if PORT1.is_none() { - PORT1 = Some(message_channel.port1()); - PORT2 = Some(message_channel.port2()) - } - PORT1 - .as_ref() - .unwrap() - .set_onmessage(Some(&perform_work_function)); - PORT2 - .as_ref() - .unwrap() - .post_message(&JsValue::null()) - .expect("port post message panic"); - } - } else { - setTimeout(&perform_work_function, 0.0); - } - - perform_work_closure.forget(); -} - -fn perform_work_until_deadline() { - unsafe { - if SCHEDULED_HOST_CALLBACK.is_some() { - let scheduled_host_callback = SCHEDULED_HOST_CALLBACK.unwrap(); - let current_time = unstable_now(); - - START_TIME = current_time; - let has_time_remaining = true; - let has_more_work = scheduled_host_callback(has_time_remaining, current_time); - if has_more_work { - schedule_perform_work_until_deadline(); - } else { - IS_MESSAGE_LOOP_RUNNING = false; - SCHEDULED_HOST_CALLBACK = None; - } - } else { - IS_MESSAGE_LOOP_RUNNING = false - } - } -} - -/** -static mut MY_V: Vec> = vec![]; - -#[derive(Debug)] -struct Task { - id: f64, -} - -fn peek<'a>(v: &'a mut Vec>) -> &'a Box { - &v[0] -} - -fn pop<'a>(v: &'a mut Vec>) -> Box { - let t = v.swap_remove(0); - t -} - -fn main() { - unsafe { - MY_V = vec![Box::new(Task { - id: 10000.0 - })]; - - let t = peek(&mut MY_V); - - println!("{:?}", t); - - pop(&mut MY_V); - // let a = pop(&mut MY_V); - - println!("{:?}", t); - }; -} - - */ -fn advance_timers(current_time: f64) { - unsafe { - let mut timer = peek_mut(&mut TIMER_QUEUE); - while timer.is_some() { - let task = timer.unwrap(); - if task.callback.is_null() { - pop(&mut TIMER_QUEUE); - } else if task.start_time <= current_time { - let t = pop(&mut TIMER_QUEUE); - task.sort_index = task.expiration_time; - push(&mut TASK_QUEUE, task.clone()); - } else { - return; - } - timer = peek_mut(&mut TIMER_QUEUE); - } - } -} - -fn flush_work(has_time_remaining: bool, initial_time: f64) -> bool { - unsafe { - IS_HOST_CALLBACK_SCHEDULED = false; - if IS_HOST_TIMEOUT_SCHEDULED { - IS_HOST_TIMEOUT_SCHEDULED = false; - cancel_host_timeout(); - } - - IS_PERFORMING_WORK = true; - let previous_priority_level = CURRENT_PRIORITY_LEVEL.clone(); - - let has_more = work_loop(has_time_remaining, initial_time).unwrap(); - // .unwrap_or_else(|_| { - // log!("work_loop error"); - // false - // }); - - CURRENT_TASK = None; - CURRENT_PRIORITY_LEVEL = previous_priority_level.clone(); - IS_PERFORMING_WORK = false; - - return has_more; - } -} - -pub fn unstable_should_yield_to_host() -> bool { - unsafe { - let time_elapsed = unstable_now() - START_TIME; - if time_elapsed < FRAME_YIELD_MS { - return false; - } - } - return true; -} - -pub fn unstable_run_with_priority(priority_level: Priority, event_handler: &Function) { - let previous_priority_level = unsafe { CURRENT_PRIORITY_LEVEL.clone() }; - unsafe { CURRENT_PRIORITY_LEVEL = priority_level.clone() }; - - event_handler.call0(&JsValue::null()); - unsafe { CURRENT_PRIORITY_LEVEL = previous_priority_level.clone() }; -} - -fn work_loop(has_time_remaining: bool, initial_time: f64) -> Result { - unsafe { - let mut current_time = initial_time; - advance_timers(current_time); - let mut current_task = peek_mut(&mut TASK_QUEUE); - - CURRENT_TASK = peek(&mut TASK_QUEUE); - while current_task.is_some() { - let mut t = current_task.unwrap(); - - if t.expiration_time > current_time - && (!has_time_remaining || unstable_should_yield_to_host()) - { - break; - } - - let callback = t.callback.clone(); - if callback.is_function() { - t.callback = JsValue::null(); - CURRENT_PRIORITY_LEVEL = t.priority_level.clone(); - let did_user_callback_timeout = t.expiration_time <= current_time; - let continuation_callback = callback - .dyn_ref::() - .unwrap() - .call1(&JsValue::null(), &JsValue::from(did_user_callback_timeout))?; - current_time = unstable_now(); - - if continuation_callback.is_function() { - t.callback = continuation_callback; - } else { - if match peek(&TASK_QUEUE) { - None => false, - Some(task) => task == t, - } { - pop(&mut TASK_QUEUE); - } - } - - advance_timers(current_time); - } else { - pop(&mut TASK_QUEUE); - } - - current_task = peek_mut(&mut TASK_QUEUE); - CURRENT_TASK = peek(&TASK_QUEUE); - } - - if CURRENT_TASK.is_some() { - return Ok(true); - } else { - let first_timer = peek(&mut TIMER_QUEUE); - if first_timer.is_some() { - let task = first_timer.unwrap(); - - request_host_timeout(handle_timeout, task.start_time - current_time); - } - - return Ok(false); - } - } -} - -fn request_host_callback(callback: fn(bool, f64) -> bool) { - unsafe { - SCHEDULED_HOST_CALLBACK = Some(callback); - if !IS_MESSAGE_LOOP_RUNNING { - IS_MESSAGE_LOOP_RUNNING = true; - schedule_perform_work_until_deadline(); - } - } -} - -fn handle_timeout(current_time: f64) { - unsafe { - IS_HOST_TIMEOUT_SCHEDULED = false; - advance_timers(current_time); - - if !IS_HOST_TIMEOUT_SCHEDULED { - if peek(&mut TASK_QUEUE).is_some() { - IS_HOST_CALLBACK_SCHEDULED = true; - request_host_callback(flush_work); - } else { - let first_timer = peek(&mut TIMER_QUEUE); - if first_timer.is_some() { - let first_timer_task = first_timer.unwrap(); - request_host_timeout( - handle_timeout, - first_timer_task.start_time - current_time, - ); - } - } - } - } -} - -fn request_host_timeout(callback: fn(f64), ms: f64) { - unsafe { - let closure = Closure::wrap(Box::new(move || { - callback(unstable_now()); - }) as Box); - let function = closure.as_ref().unchecked_ref::().clone(); - closure.forget(); - TASK_TIMEOUT_ID = setTimeout(&function, ms); - } -} - -pub fn unstable_cancel_callback(task: Task) { - let id = task.id; - unsafe { - for mut task in &mut TASK_QUEUE { - if task.id == id { - task.callback = JsValue::null(); - } - } - - for mut task in &mut TIMER_QUEUE { - if task.id == id { - task.callback = JsValue::null(); - } - } - } -} - -pub fn unstable_schedule_callback( - priority_level: Priority, - callback: Function, - delay: f64, -) -> Task { - let current_time = unstable_now(); - let mut start_time = current_time; - - if delay > 0.0 { - start_time += delay; - } - - let timeout = get_priority_timeout(priority_level.clone()); - let expiration_time = start_time + timeout; - let mut new_task = Task::new( - callback, - priority_level.clone(), - start_time, - expiration_time, - ); - let cloned = new_task.clone(); - unsafe { - if start_time > current_time { - new_task.sort_index = start_time; - push(&mut TIMER_QUEUE, new_task.clone()); - - if peek(&mut TASK_QUEUE).is_none() { - if let Some(task) = peek(&mut TIMER_QUEUE) { - if task == &new_task { - if IS_HOST_TIMEOUT_SCHEDULED { - cancel_host_timeout(); - } else { - IS_HOST_TIMEOUT_SCHEDULED = true; - } - request_host_timeout(handle_timeout, start_time - current_time); - } - } - } - } else { - new_task.sort_index = expiration_time; - push(&mut TASK_QUEUE, new_task); - - if !IS_HOST_CALLBACK_SCHEDULED && !IS_PERFORMING_WORK { - IS_HOST_CALLBACK_SCHEDULED = true; - request_host_callback(flush_work); - } - } - } - - cloned -} - -pub fn unstable_schedule_callback_no_delay(priority_level: Priority, callback: Function) -> Task { - unstable_schedule_callback(priority_level, callback, 0.0) -} - -pub fn unstable_get_current_priority_level() -> Priority { - unsafe { CURRENT_PRIORITY_LEVEL.clone() } -} From c875ca5c0ebbabd9dee8349fbdda3e21105eba4a Mon Sep 17 00:00:00 2001 From: youxingzhi Date: Thu, 26 Sep 2024 14:42:36 +0800 Subject: [PATCH 6/6] update useTransition demo --- .../src/useTransition/AboutTab.tsx | 2 +- .../src/useTransition/ContactTab.tsx | 6 +- .../src/useTransition/PostsTab.tsx | 24 +++++--- .../hello-world/src/useTransition/index.tsx | 18 +++--- .../hello-world/src/useTransition/style.css | 55 +++++++++++++++++++ 5 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 examples/hello-world/src/useTransition/style.css diff --git a/examples/hello-world/src/useTransition/AboutTab.tsx b/examples/hello-world/src/useTransition/AboutTab.tsx index 8620727..4b91b29 100644 --- a/examples/hello-world/src/useTransition/AboutTab.tsx +++ b/examples/hello-world/src/useTransition/AboutTab.tsx @@ -1,3 +1,3 @@ export default function AboutTab() { - return

    It's me, ayou.

    + return

    Welcome to my profile!

    } diff --git a/examples/hello-world/src/useTransition/ContactTab.tsx b/examples/hello-world/src/useTransition/ContactTab.tsx index 83ebbc8..b6cad8a 100644 --- a/examples/hello-world/src/useTransition/ContactTab.tsx +++ b/examples/hello-world/src/useTransition/ContactTab.tsx @@ -1,10 +1,10 @@ export default function ContactTab() { return ( <> -

    Contact:

    +

    You can find me online here:

      -
    • E-mail:ayou@xx.com
    • -
    • Wechat:a22you
    • +
    • admin@mysite.com
    • +
    • +123456789
    ) diff --git a/examples/hello-world/src/useTransition/PostsTab.tsx b/examples/hello-world/src/useTransition/PostsTab.tsx index 6bebc5c..01fca7b 100644 --- a/examples/hello-world/src/useTransition/PostsTab.tsx +++ b/examples/hello-world/src/useTransition/PostsTab.tsx @@ -1,13 +1,23 @@ -const PostsTab = function PostsTab() { - const items = [] - for (let i = 0; i < 50; i++) { +import {memo} from 'react' + +const PostsTab = memo(function PostsTab() { + // Log once. The actual slowdown is inside SlowPost. + console.log('[ARTIFICIALLY SLOW] Rendering 500 ') + + let items = [] + for (let i = 0; i < 500; i++) { items.push() } return
      {items}
    -} +}) + function SlowPost({index}) { - const startTime = performance.now() - while (performance.now() - startTime < 32) {} - return
  • 博文 #{index + 1}
  • + let startTime = performance.now() + while (performance.now() - startTime < 1) { + // Do nothing for 1 ms per item to emulate extremely slow code + } + + return
  • Post #{index + 1}
  • } + export default PostsTab diff --git a/examples/hello-world/src/useTransition/index.tsx b/examples/hello-world/src/useTransition/index.tsx index cd387b7..a0c5704 100644 --- a/examples/hello-world/src/useTransition/index.tsx +++ b/examples/hello-world/src/useTransition/index.tsx @@ -1,9 +1,11 @@ import {useState, useTransition} from 'react' -import TabButton from './TabButton' -import AboutTab from './AboutTab' -import PostsTab from './PostsTab' -import ContactTab from './ContactTab' -export default function App() { +import TabButton from './TabButton.js' +import AboutTab from './AboutTab.js' +import PostsTab from './PostsTab.js' +import ContactTab from './ContactTab.js' +import './style.css' + +export default function TabContainer() { const [isPending, startTransition] = useTransition() const [tab, setTab] = useState('about') @@ -16,15 +18,15 @@ export default function App() { return (
    selectTab('about')}> - 首页 + About selectTab('posts')}> - 博客 (render慢) + Posts (slow) selectTab('contact')}> - 联系我 + Contact
    {tab === 'about' && } diff --git a/examples/hello-world/src/useTransition/style.css b/examples/hello-world/src/useTransition/style.css new file mode 100644 index 0000000..7890858 --- /dev/null +++ b/examples/hello-world/src/useTransition/style.css @@ -0,0 +1,55 @@ +* { + box-sizing: border-box; +} + +body { + font-family: sans-serif; + margin: 20px; + padding: 0; +} + +h1 { + margin-top: 0; + font-size: 22px; +} + +h2 { + margin-top: 0; + font-size: 20px; +} + +h3 { + margin-top: 0; + font-size: 18px; +} + +h4 { + margin-top: 0; + font-size: 16px; +} + +h5 { + margin-top: 0; + font-size: 14px; +} + +h6 { + margin-top: 0; + font-size: 12px; +} + +code { + font-size: 1.2em; +} + +ul { + padding-inline-start: 20px; +} + +button { + margin-right: 10px; +} +b { + display: inline-block; + margin-right: 10px; +}