wip: got the structure figured out!
This commit is contained in:
1 parent
85e2dc259a
commit
97745c6a7f
2 files changed
+235
-80
No files matched your search
@@ -5,6 +5,8 @@ use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
|
||||
use std::pin::Pin;
|
||||
|
||||
use futures_util::Future;
|
||||
|
||||
use crate::innerlude::*;
|
||||
|
||||
/// The "Mutations" object holds the changes that need to be made to the DOM.
|
||||
@@ -45,16 +47,33 @@ impl<'a> NodeRefMutation<'a> {
|
||||
pub struct Scheduler {
|
||||
current_priority: EventPriority,
|
||||
|
||||
pending_events: VecDeque<EventTrigger>,
|
||||
pending_immediates: VecDeque<EventTrigger>,
|
||||
pending_tasks: VecDeque<EventTrigger>,
|
||||
|
||||
garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
shared: SharedResources,
|
||||
|
||||
dirty_scopes: [HashSet<DirtyScope>; 3],
|
||||
|
||||
fibers: Vec<Fiber<'static>>,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(shared: SharedResources) -> Self {
|
||||
Self {
|
||||
shared,
|
||||
|
||||
// a storage for our receiver to dump into
|
||||
pending_events: VecDeque::new(),
|
||||
pending_immediates: VecDeque::new(),
|
||||
pending_tasks: VecDeque::new(),
|
||||
|
||||
fibers: Vec::new(),
|
||||
|
||||
garbage_scopes: HashSet::new(),
|
||||
|
||||
current_priority: EventPriority::Low,
|
||||
|
||||
// low, medium, high
|
||||
@@ -62,6 +81,64 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
// channels don't have these methods, so we just implement our own wrapper
|
||||
pub fn next_event(&mut self) -> Option<EventTrigger> {
|
||||
// pop the last event off the internal queue
|
||||
self.pending_events.pop_back().or_else(|| {
|
||||
self.shared
|
||||
.ui_event_receiver
|
||||
.borrow_mut()
|
||||
.try_next()
|
||||
.ok()
|
||||
.and_then(|f| f)
|
||||
})
|
||||
}
|
||||
|
||||
// waits for a trigger, canceling early if the deadline is reached
|
||||
// returns true if the deadline was reached
|
||||
pub async fn wait_for_any_trigger(&mut self, deadline: &mut impl Future<Output = ()>) -> bool {
|
||||
todo!()
|
||||
// match raw_trigger {
|
||||
// Ok(Some(trigger)) => trigger,
|
||||
// _ => {
|
||||
// // nothing to do - let's clean up any garbage we might have
|
||||
// self.scheduler.clean_up_garbage();
|
||||
|
||||
// // Continuously poll the future pool and the event receiver for work
|
||||
// let mut tasks = self.shared.async_tasks.borrow_mut();
|
||||
// let tasks_tasks = tasks.next();
|
||||
|
||||
// // if the new event generates work more important than our current fiber, we should consider switching
|
||||
// // only switch if it impacts different scopes.
|
||||
// let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut();
|
||||
// let ui_reciv_task = ui_receiver.next();
|
||||
|
||||
// // right now, this polling method will only catch batched set_states that don't get awaited.
|
||||
// // However, in the future, we might be interested in batching set_states across await points
|
||||
// let immediate_tasks = ();
|
||||
|
||||
// futures_util::pin_mut!(tasks_tasks);
|
||||
// futures_util::pin_mut!(ui_reciv_task);
|
||||
|
||||
// // Poll the event receiver and the future pool for work
|
||||
// // Abort early if our deadline has ran out
|
||||
// let mut deadline = (&mut deadline_future).fuse();
|
||||
|
||||
// let trig = futures_util::select! {
|
||||
// trigger = tasks_tasks => trigger,
|
||||
// trigger = ui_reciv_task => trigger,
|
||||
|
||||
// // abort if we're out of time
|
||||
// _ = deadline => { return Ok(Mutations::new()); }
|
||||
// // _ = deadline => { return Ok(diff_machine.mutations); }
|
||||
// };
|
||||
|
||||
// trig.unwrap()
|
||||
// }
|
||||
// };
|
||||
//
|
||||
}
|
||||
|
||||
pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
|
||||
//
|
||||
|
||||
@@ -75,9 +152,68 @@ impl Scheduler {
|
||||
// .or_insert_with(|| new_priority);
|
||||
}
|
||||
|
||||
pub fn has_work() {}
|
||||
pub fn has_work(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn progress_work(&mut self, machine: &mut DiffMachine) {}
|
||||
// returns true if the deadline is reached
|
||||
// returns false if the deadline is not reached
|
||||
pub fn progress_work(
|
||||
&mut self,
|
||||
machine: &mut DiffMachine,
|
||||
is_deadline_reached: &mut impl FnMut() -> bool,
|
||||
) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn clean_up_garbage(&mut self) {
|
||||
let mut scopes_to_kill = Vec::new();
|
||||
|
||||
let mut garbage_list = Vec::new();
|
||||
for scope in self.garbage_scopes.drain() {
|
||||
let scope = self.shared.get_scope_mut(scope).unwrap();
|
||||
for node in scope.consume_garbage() {
|
||||
garbage_list.push(node);
|
||||
}
|
||||
|
||||
while let Some(node) = garbage_list.pop() {
|
||||
match &node.kind {
|
||||
VNodeKind::Text(_) => {
|
||||
self.shared.collect_garbage(node.direct_id());
|
||||
}
|
||||
VNodeKind::Anchor(_) => {
|
||||
self.shared.collect_garbage(node.direct_id());
|
||||
}
|
||||
VNodeKind::Suspended(_) => {
|
||||
self.shared.collect_garbage(node.direct_id());
|
||||
}
|
||||
|
||||
VNodeKind::Element(el) => {
|
||||
self.shared.collect_garbage(node.direct_id());
|
||||
for child in el.children {
|
||||
garbage_list.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
VNodeKind::Fragment(frag) => {
|
||||
for child in frag.children {
|
||||
garbage_list.push(child);
|
||||
}
|
||||
}
|
||||
|
||||
VNodeKind::Component(comp) => {
|
||||
// TODO: run the hook destructors and then even delete the scope
|
||||
|
||||
let scope_id = comp.ass_scope.get().unwrap();
|
||||
let scope = self.shared.get_scope(scope_id).unwrap();
|
||||
let root = scope.root();
|
||||
garbage_list.push(root);
|
||||
scopes_to_kill.push(scope_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
|
||||
@@ -142,8 +142,8 @@ impl VirtualDom {
|
||||
Self {
|
||||
base_scope,
|
||||
_root_props: root_props,
|
||||
scheduler: Scheduler::new(components.clone()),
|
||||
shared: components,
|
||||
scheduler: Scheduler::new(),
|
||||
_root_prop_type: TypeId::of::<P>(),
|
||||
}
|
||||
}
|
||||
@@ -324,106 +324,125 @@ impl VirtualDom {
|
||||
and listeners hold references to hook data, it is wrong to run a scope that is already being diffed.
|
||||
*/
|
||||
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared);
|
||||
// let mut diff_machine = DiffMachine::new(Mutations::new(), self.base_scope, &self.shared);
|
||||
|
||||
// 1. Drain the existing immediates.
|
||||
//
|
||||
// These are generated by async tasks that we never got a chance to finish.
|
||||
// All of these get scheduled with the lowest priority.
|
||||
while let Ok(Some(dirty_scope)) = self.shared.immediate_receiver.borrow_mut().try_next() {
|
||||
self.scheduler
|
||||
.add_dirty_scope(dirty_scope, EventPriority::Low);
|
||||
}
|
||||
// Configure our deadline
|
||||
use futures_util::FutureExt;
|
||||
let mut deadline_future = deadline.shared();
|
||||
|
||||
// 2. Drain the event queue, calling whatever listeners need to be called
|
||||
//
|
||||
while let Ok(Some(trigger)) = self.shared.ui_event_receiver.borrow_mut().try_next() {
|
||||
match &trigger.event {
|
||||
VirtualEvent::AsyncEvent { .. } => {}
|
||||
let mut is_ready_deadline = deadline_future.clone();
|
||||
let mut is_ready = || -> bool { (&mut is_ready_deadline).now_or_never().is_some() };
|
||||
|
||||
// This suspense system works, but it's not the most elegant solution.
|
||||
// TODO: Replace this system
|
||||
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
||||
// Safety: this handler is the only thing that can mutate shared items at this moment in tim
|
||||
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
||||
loop {
|
||||
// 1. Drain the existing immediates.
|
||||
//
|
||||
// These are generated by async tasks that we never got a chance to finish.
|
||||
// All of these get scheduled with the lowest priority.
|
||||
while let Ok(Some(dirty_scope)) = self.shared.immediate_receiver.borrow_mut().try_next()
|
||||
{
|
||||
self.scheduler
|
||||
.add_dirty_scope(dirty_scope, EventPriority::Low);
|
||||
}
|
||||
|
||||
// safety: we are sure that there are no other references to the inner content of suspense hooks
|
||||
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||
// 2. Drain the event queue, calling whatever listeners need to be called
|
||||
//
|
||||
// First, check if there's any set_states in the queue that we can mark as low priority
|
||||
// Next, check the UI event receiver so we can mark those scopes as medium/high priority
|
||||
// Next, work on any fibers.
|
||||
// Once the fiber work is finished
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
let cx = Context { scope, props: &() };
|
||||
let scx = SuspendedContext { inner: cx };
|
||||
while let Some(trigger) = self.scheduler.next_event() {
|
||||
match &trigger.event {
|
||||
VirtualEvent::AsyncEvent { .. } => {}
|
||||
|
||||
// generate the new node!
|
||||
let nodes: Option<VNode> = (&hook.callback)(scx);
|
||||
// This suspense system works, but it's not the most elegant solution.
|
||||
// TODO: Replace this system
|
||||
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
||||
todo!();
|
||||
// // Safety: this handler is the only thing that can mutate shared items at this moment in tim
|
||||
// let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
||||
|
||||
if let Some(nodes) = nodes {
|
||||
// allocate inside the finished frame - not the WIP frame
|
||||
let nodes = scope.frames.finished_frame().bump.alloc(nodes);
|
||||
// // safety: we are sure that there are no other references to the inner content of suspense hooks
|
||||
// let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||
|
||||
// push the old node's root onto the stack
|
||||
let real_id = domnode.get().ok_or(Error::NotMounted)?;
|
||||
diff_machine.edit_push_root(real_id);
|
||||
// let cx = Context { scope, props: &() };
|
||||
// let scx = SuspendedContext { inner: cx };
|
||||
|
||||
// push these new nodes onto the diff machines stack
|
||||
let meta = diff_machine.create_vnode(&*nodes);
|
||||
// // generate the new node!
|
||||
// let nodes: Option<VNode> = (&hook.callback)(scx);
|
||||
|
||||
// replace the placeholder with the new nodes we just pushed on the stack
|
||||
diff_machine.edit_replace_with(1, meta.added_to_stack);
|
||||
} else {
|
||||
log::warn!(
|
||||
"Suspense event came through, but there were no generated nodes >:(."
|
||||
);
|
||||
// if let Some(nodes) = nodes {
|
||||
// // allocate inside the finished frame - not the WIP frame
|
||||
// let nodes = scope.frames.finished_frame().bump.alloc(nodes);
|
||||
|
||||
// // push the old node's root onto the stack
|
||||
// let real_id = domnode.get().ok_or(Error::NotMounted)?;
|
||||
// diff_machine.edit_push_root(real_id);
|
||||
|
||||
// // push these new nodes onto the diff machines stack
|
||||
// let meta = diff_machine.create_vnode(&*nodes);
|
||||
|
||||
// // replace the placeholder with the new nodes we just pushed on the stack
|
||||
// diff_machine.edit_replace_with(1, meta.added_to_stack);
|
||||
// } else {
|
||||
// log::warn!(
|
||||
// "Suspense event came through, but there were no generated nodes >:(."
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
VirtualEvent::ClipboardEvent(_)
|
||||
| VirtualEvent::CompositionEvent(_)
|
||||
| VirtualEvent::KeyboardEvent(_)
|
||||
| VirtualEvent::FocusEvent(_)
|
||||
| VirtualEvent::FormEvent(_)
|
||||
| VirtualEvent::SelectionEvent(_)
|
||||
| VirtualEvent::TouchEvent(_)
|
||||
| VirtualEvent::UIEvent(_)
|
||||
| VirtualEvent::WheelEvent(_)
|
||||
| VirtualEvent::MediaEvent(_)
|
||||
| VirtualEvent::AnimationEvent(_)
|
||||
| VirtualEvent::TransitionEvent(_)
|
||||
| VirtualEvent::ToggleEvent(_)
|
||||
| VirtualEvent::MouseEvent(_)
|
||||
| VirtualEvent::PointerEvent(_) => {
|
||||
if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
|
||||
if let Some(element) = trigger.real_node_id {
|
||||
scope.call_listener(trigger.event, element)?;
|
||||
VirtualEvent::ClipboardEvent(_)
|
||||
| VirtualEvent::CompositionEvent(_)
|
||||
| VirtualEvent::KeyboardEvent(_)
|
||||
| VirtualEvent::FocusEvent(_)
|
||||
| VirtualEvent::FormEvent(_)
|
||||
| VirtualEvent::SelectionEvent(_)
|
||||
| VirtualEvent::TouchEvent(_)
|
||||
| VirtualEvent::UIEvent(_)
|
||||
| VirtualEvent::WheelEvent(_)
|
||||
| VirtualEvent::MediaEvent(_)
|
||||
| VirtualEvent::AnimationEvent(_)
|
||||
| VirtualEvent::TransitionEvent(_)
|
||||
| VirtualEvent::ToggleEvent(_)
|
||||
| VirtualEvent::MouseEvent(_)
|
||||
| VirtualEvent::PointerEvent(_) => {
|
||||
if let Some(scope) = self.shared.get_scope_mut(trigger.originator) {
|
||||
if let Some(element) = trigger.real_node_id {
|
||||
scope.call_listener(trigger.event, element)?;
|
||||
|
||||
// Drain the immediates into the dirty scopes, setting the appropiate priorities
|
||||
while let Ok(Some(dirty_scope)) =
|
||||
self.shared.immediate_receiver.borrow_mut().try_next()
|
||||
{
|
||||
self.scheduler
|
||||
.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
// Drain the immediates into the dirty scopes, setting the appropiate priorities
|
||||
while let Ok(Some(dirty_scope)) =
|
||||
self.shared.immediate_receiver.borrow_mut().try_next()
|
||||
{
|
||||
self.scheduler
|
||||
.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Work through the fibers, and wait for any future work to be ready
|
||||
// 3. Work through the fibers, and wait for any future work to be ready
|
||||
let mut machine = DiffMachine::new_headless(&self.shared);
|
||||
self.scheduler.progress_work(&mut machine, &mut is_ready);
|
||||
|
||||
// Configure our deadline
|
||||
use futures_util::FutureExt;
|
||||
let mut deadline_future = deadline.boxed_local();
|
||||
let mut is_ready = || -> bool { (&mut deadline_future).now_or_never().is_some() };
|
||||
|
||||
loop {
|
||||
if is_ready() {
|
||||
// 4. Wait for any new triggers before looping again
|
||||
if self
|
||||
.scheduler
|
||||
.wait_for_any_trigger(&mut deadline_future)
|
||||
.await
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
self.scheduler
|
||||
}
|
||||
|
||||
Ok(diff_machine.mutations)
|
||||
todo!()
|
||||
// Ok(diff_machine.mutations)
|
||||
}
|
||||
|
||||
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
|
||||
|
||||
Reference in new issue
Block a user