Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blog 14 scheduler #13

Open
wants to merge 6 commits into
base: blog-13
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ members = [
"packages/react",
"packages/react-dom",
"packages/react-reconciler",
"packages/scheduler",
"packages/shared"
]
25 changes: 6 additions & 19 deletions examples/hello-world/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,17 @@ import {useState} from 'react'
function App() {
const [num, updateNum] = useState(0);

const isOdd = num % 2 === 1;

const before = [
<li key={1}>1</li>,
<li>2</li>,
<li>3</li>,
<li key={4}>4</li>
];
const after = [
// <li key={4}>4</li>,
<li>2</li>,
<li>3</li>,
<li key={1}>1</li>
];

const listToUse = isOdd ? after : before;
console.log(num, listToUse)
return (
<ul
onClick={(e) => {
updateNum(num => num + 1);
// 注意观察多次更新只会触发一次render阶段,这就是batchedUpdates(批处理),也是我们基础调度能力的体现
updateNum((num: number) => num + 1);
updateNum((num: number) => num + 2);
updateNum((num: number) => num + 3);
updateNum((num: number) => num + 4);
}}
>
{listToUse}
num值为:{num}
</ul>
);
}
Expand Down
43 changes: 39 additions & 4 deletions examples/hello-world/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
import {createRoot} from 'react-dom'
import App from './App.tsx'
// import App from './App.tsx'
//
// const root = createRoot(document.getElementById("root"))
// root.render(<App/>)
import {Priority, scheduleCallback, shouldYieldToHost} from 'react-dom'

const root = createRoot(document.getElementById("root"))
root.render(<App/>)

// scheduleCallback(2, function func1() {
// console.log('1')
// })
//
// const taskId = scheduleCallback(1, function func2() {
// console.log('2')
// })

// cancelCallback(taskId)


function func2(didTimeout) {
console.log(didTimeout)
if (!didTimeout) console.log(2)
}

function func1() {
console.log(1)
return func2
}

scheduleCallback(Priority.NormalPriority, func1)

function work() {
while (!shouldYieldToHost()) {
console.log('work')
}
console.log('yield to host')
}

scheduleCallback(1, function func2() {
work()
})

1 change: 1 addition & 0 deletions packages/react-dom/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ wasm-bindgen = "0.2.84"
web-sys = { version = "0.3.69", features = ["console", "Window", "Document", "Text", "Element", "EventListener"] }
react-reconciler = { path = "../react-reconciler" }
shared = { path = "../shared" }
scheduler = { path = "../scheduler" }
# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
Expand Down
20 changes: 20 additions & 0 deletions packages/react-dom/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::rc::Rc;

use js_sys::{Array, Function};
use wasm_bindgen::prelude::*;
use web_sys::Node;

use react_reconciler::Reconciler;
use scheduler::{Priority, unstable_cancel_callback, unstable_schedule_callback as origin_unstable_schedule_callback, unstable_should_yield_to_host};

use crate::host_config::ReactDomHostConfig;
use crate::renderer::Renderer;
Expand All @@ -28,3 +30,21 @@ pub fn create_root(container: &JsValue) -> Renderer {
let renderer = Renderer::new(root, reconciler, container);
renderer
}

#[wasm_bindgen(js_name = scheduleCallback, variadic)]
pub fn unstable_schedule_callback(priority_level: Priority, callback: Function, delay: &JsValue) -> u32 {
let delay = delay.dyn_ref::<Array>().unwrap();
let d = delay.get(0).as_f64().unwrap_or_else(|| 0.0);
origin_unstable_schedule_callback(priority_level, callback, d)
}

#[wasm_bindgen(js_name = cancelCallback)]
pub fn cancel_callback(id: u32) {
unstable_cancel_callback(id)
}

#[wasm_bindgen(js_name = shouldYieldToHost)]
pub fn should_yield_to_host() -> bool {
unstable_should_yield_to_host()
}

2 changes: 1 addition & 1 deletion packages/react-reconciler/src/child_fiber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ fn update_from_map(
}
}
let before = existing_children.get(&Key(key_to_use.clone())).clone();
if type_of(element, "string") {
if type_of(element, "string") || type_of(element, "number") {
let props = create_props_with_content(element.clone());
if before.is_some() {
let before = (*before.clone().unwrap()).clone();
Expand Down
13 changes: 0 additions & 13 deletions packages/react-reconciler/tests/web.rs

This file was deleted.

1 change: 1 addition & 0 deletions packages/scheduler/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
13 changes: 13 additions & 0 deletions packages/scheduler/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "scheduler"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
wasm-bindgen = "0.2.84"
web-sys = { version = "0.3.69", features = ["MessagePort", "MessageChannel"] }
shared = { path = "../shared" }


165 changes: 165 additions & 0 deletions packages/scheduler/src/heap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// 向堆中插入元素
pub fn push<T: Ord>(heap: &mut Vec<T>, value: T) {
heap.push(value);
sift_up(heap, heap.len() - 1);
}

// 从堆中取出最小的元素
pub fn pop<T: Ord>(heap: &mut Vec<T>) -> Option<T> {
if heap.is_empty() {
return None;
}
let last_index = heap.len() - 1;
heap.swap(0, last_index);
let result = heap.pop();
if !heap.is_empty() {
sift_down(heap, 0);
}
result
}

// 向上调整堆
fn sift_up<T: Ord>(heap: &mut Vec<T>, mut index: usize) {
while index != 0 {
let parent = (index - 1) / 2;
if heap[parent] <= heap[index] {
break;
}
heap.swap(parent, index);
index = parent;
}
}

// 向下调整堆
fn sift_down<T: Ord>(heap: &mut Vec<T>, mut index: usize) {
let len = heap.len();
loop {
let left_child = index * 2 + 1;
let right_child = left_child + 1;

// 找出当前节点和它的子节点中最小的节点
let mut smallest = index;
if left_child < len && heap[left_child] < heap[smallest] {
smallest = left_child;
}
if right_child < len && heap[right_child] < heap[smallest] {
smallest = right_child;
}

// 如果当前节点是最小的,那么堆已经是正确的了
if smallest == index {
break;
}

// 否则,交换当前节点和最小的节点
heap.swap(index, smallest);
index = smallest;
}
}

pub fn peek<T: Ord>(heap: &Vec<T>) -> Option<&T> {
heap.get(0)
}

pub fn is_empty<T: Ord>(heap: &Vec<T>) -> bool {
heap.is_empty()
}

pub fn peek_mut<T: Ord>(heap: &mut Vec<T>) -> Option<&mut T> {
if heap.is_empty() {
None
} else {
Some(&mut heap[0])
}
}


#[cfg(test)]
mod tests {
use std::cmp::Ordering;

use crate::heap::{pop, push};

#[derive(Clone)]
struct Task {
id: u32,
sort_index: f64,
}

impl Task {
fn new(id: u32, sort_index: f64) -> Self {
Self { id, sort_index }
}
}

impl std::fmt::Debug for Task {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Task {{ id: {}, sort_index: {} }}",
self.id, self.sort_index
)
}
}

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<Ordering> {
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)
}
}

#[test]
fn test_min_heap() {
let mut heap = vec![];

let task3 = Task::new(3, 3.0);
let task2 = Task::new(2, 2.0);
let task1 = Task::new(1, 1.0);
let task4 = Task::new(4, 4.0);

push(&mut heap, task3);
push(&mut heap, task2);
push(&mut heap, task1);
push(&mut heap, task4);

// 按预期顺序弹出任务
assert_eq!(pop(&mut heap).unwrap().id == 1, true);
assert_eq!(pop(&mut heap).unwrap().id == 2, true);
assert_eq!(pop(&mut heap).unwrap().id == 3, true);
assert_eq!(pop(&mut heap).unwrap().id == 4, true);

// 堆应该为空
assert!(heap.pop().is_none());
}
}
Loading