likes
comments
collection
share

从零实现 React v18,但 WASM 版 - [14] 实现 Scheduler

作者站长头像
站长
· 阅读数 54

模仿 big-react,使用 Rust 和 WebAssembly,从零实现 React v18 的核心功能。深入理解 React 源码的同时,还锻炼了 Rust 的技能,简直赢麻了!

代码地址:github.com/ParadeTo/bi…

本文对应 tag:v14

Scheduler 简介

Scheduler 是 React 中负责任务调度的一个包,它是实现时间分片的基础,后续要实现的 useEffect 也用到了它,所以这篇文章我们先来实现 WASM 版本的 Scheduler

关于 Scheduler 的介绍可以查看之前写的这篇文章,下面简单介绍下他的实现。

Scheduler 中维护有两个小顶堆 timerQueuetaskQueue,其中已经就绪的 task(startTime <= currentTime)会被放入 taskQueue 堆中,未就绪的 task(通过传入 delay 使得 startTime > currentTime )会被放入 timeQueue。比如下面这个例子,task1 会被放入 taskQueuetask2 会被放入 timerQueue

const task1 = Scheduler.unstable_scheduleCallback(2, function func1() {
  console.log('2')
})

const task2 = Scheduler.unstable_scheduleCallback(
  1,
  function func2() {
    console.log('1')
  },
  {delay: 100}
)

之后通过 MessageChannel 开启一个宏任务来处理 taskQueue 中的任务,当处理时间超过 5ms 时会中断,然后开启一个新的宏任务来继续处理剩下的任务,如此循环直到堆中任务完成。而 timerQueue 中的任务会定时检查是否已经就绪,如果就绪,就依次弹出放入 taskQueue 中。

本次修改详见这里,下面挑一些重点解释下。

小顶堆的实现

为了方便编写单元测试,实现了一个泛型版本的小顶堆:

...
pub fn push<T: Ord>(heap: &mut Vec<T>, value: T) {
    heap.push(value);
    sift_up(heap, heap.len() - 1);
}
...
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;
    }
}
...

不过这个泛型 TOrd 所约束,需要实现 Ord trait, 比如像这样:

struct Task {
    id: u32
}

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

static mut

Scheduler 的实现中定义了大量的 static mut,导致代码中出现了很多 unsafe 代码块。很显然不是一个好的做法,但是这么做的好处是实现方式跟 React 的比较像,方便抄代码,此外更重要的一个原因是如果不使用 static mut,而是定义一个 Scheduler struct,把这些 static mut 作为其属性,会遇到别的问题。

比如当把 perform_work_until_deadline 作为宏任务的回调函数时,需要改为 self.perform_work_until_deadline,而这样编译是通不过的:

pub fn schedule_perform_work_until_deadline() {
    let perform_work_closure =
        // Will fail to compile if it is changed to self.perform_work_until_deadline
        Closure::wrap(Box::new(perform_work_until_deadline) as Box<dyn FnMut()>);

即使改成闭包也是不行的:

pub fn schedule_perform_work_until_deadline() {
    let perform_work_closure =
        Closure::wrap(Box::new(|| self.perform_work_until_deadline()) as Box<dyn FnMut()>);

所以目前来看是不得已为止,而使用 unsafe 绕过 Rust 的安全检查后,会有一些奇怪的行为,比如下面这个例子:

static mut MY_V: Vec<Task> = vec![];

#[derive(Debug)]
struct Task {
    name: String
}

fn peek<'a>(v: &'a mut Vec<Task>) -> &'a Task {
    &v[0]
}

fn pop<'a>(v: &'a mut Vec<Task>) -> Task {
    let t = v.swap_remove(0);
    t
}

fn main() {
    unsafe {
        MY_V = vec![Task {
            name: "ayou".to_string()
        }];

        let t = peek(&mut MY_V);

        // 1
        // pop(&mut MY_V);
        // 2
        let a = pop(&mut MY_V);

        println!("{:?}", t.name);
    };
}

代码 1 和 2,最后的输出竟然是不一样的,代码 1 输出 "\0\0\0\0",而代码 2 输出正常,而他们的区别只在于返回的值是否赋值给了一个变量。

至于为什么会有这样的差异暂时还没有搞得很清楚,好在测试发现目前没有别的问题了,接下来可以实现 useEffect 了。

跪求 star 并关注公众号“前端游”。

转载自:https://juejin.cn/post/7369453809973116967
评论
请登录