wip: overhaul event system

This commit is contained in:
Jonathan Kelley committed 2021-10-04 01:28:04 -04:00
1 parent 82953f2ac3
commit 7a03c1d2b4
34 files changed
+2488 -1468

No files matched your search

File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+4 -2
View File
@@ -19,6 +19,7 @@ pub mod context;
pub mod diff;
pub mod diff_stack;
pub mod events;
// pub mod events2;
pub mod heuristics;
pub mod hooklist;
pub mod hooks;
@@ -61,8 +62,8 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle,
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, SyntheticEvent,
TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
};
pub mod prelude {
@@ -78,4 +79,5 @@ pub mod exports {
//! Important dependencies that are used by the rest of the library
// the foundation of this library
pub use bumpalo;
pub use futures_channel;
}
-2
View File
@@ -185,7 +185,6 @@ pub enum DomEdit<'bump> {
root: u64,
},
RemoveAllChildren,
CreateTextNode {
text: &'bump str,
id: u64,
@@ -232,7 +231,6 @@ impl DomEdit<'_> {
DomEdit::AppendChildren { .. } => id == "AppendChildren",
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
DomEdit::Remove { .. } => id == "Remove",
DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
DomEdit::CreateElement { .. } => id == "CreateElement",
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
-10
View File
@@ -43,16 +43,6 @@ impl ResourcePool {
inner.get_mut(idx.0)
}
// return a bumpframe with a lifetime attached to the arena borrow
// this is useful for merging lifetimes
pub fn with_scope_vnode<'b>(
&self,
_id: ScopeId,
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
) -> Option<&VNode<'b>> {
todo!()
}
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
let inner = unsafe { &mut *self.components.get() };
Some(inner.remove(id.0))
+15 -8
View File
@@ -106,7 +106,8 @@ pub enum SchedulerMsg {
}
pub enum TaskMsg {
SubmitTask(FiberTask, u64),
// SubmitTask(FiberTask, u64),
// SubmitTask(FiberTask, u64),
ToggleTask(u64),
PauseTask(u64),
ResumeTask(u64),
@@ -163,7 +164,10 @@ pub(crate) struct Scheduler {
}
impl Scheduler {
pub(crate) fn new() -> Self {
pub(crate) fn new(
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
/*
Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
Perhaps this should be configurable from some external config?
@@ -173,7 +177,6 @@ impl Scheduler {
let heuristics = HeuristicsEngine::new();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let task_counter = Rc::new(Cell::new(0));
let channel = EventChannel {
@@ -183,15 +186,19 @@ impl Scheduler {
let sender = sender.clone();
Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
},
// todo: we want to get the futures out of the scheduler message
// the scheduler message should be send/sync
submit_task: {
Rc::new(move |fiber_task| {
let task_id = task_counter.get();
task_counter.set(task_id + 1);
sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
fiber_task, task_id,
)))
.unwrap();
todo!();
// sender
// .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
// fiber_task, task_id,
// )))
// .unwrap();
TaskHandle {
our_id: task_id,
sender: sender.clone(),
+2 -1
View File
@@ -12,7 +12,8 @@ pub struct TestDom {
impl TestDom {
pub fn new() -> TestDom {
let bump = Bump::new();
let scheduler = Scheduler::new();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let scheduler = Scheduler::new(sender, receiver);
TestDom { bump, scheduler }
}
+19 -4
View File
@@ -1,5 +1,12 @@
//! A threadsafe wrapper for the VirtualDom
//!
//! This is an experimental module, and must be explicitly opted-into.
//!
//! It's not guaranteed that this module produces safe results, so use at your own peril.
//!
//! The only real "right" answer to a Send VirtualDom is by ensuring all hook data is Send
//!
//!
use std::sync::{Arc, Mutex, MutexGuard};
use crate::VirtualDom;
@@ -18,9 +25,17 @@ use crate::VirtualDom;
/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play"
/// while the VirtualDom is locked with a non-Send marker.
///
/// Note that calling "wait for work" on the regular VirtualDom is inherently non-Send. If there are async tasks that
/// need to be awaited, they must be done thread-local since we don't place any requirements on user tasks. This can be
/// done with the function "spawn_local" in either tokio or async_std.
/// Calling "wait for work" on the ThreadsafeVirtualDom does indeed work, because this method only accesses `Send` types.
/// Otherwise, the VirtualDom must be unlocked on the current thread to modify any data.
///
/// Dioxus does have the concept of local tasks and non-local tasks.
///
/// For the ThreadsafeVirtualDom, non-Send tasks are not ran - and will error out during a Debug build if one is submitted.
///
///
///
/// When Tasks are submitted to a thread-local executor,
///
pub struct ThreadsafeVirtualDom {
inner: Arc<Mutex<VirtualDom>>,
}
+54 -48
View File
@@ -19,8 +19,10 @@
//! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files.
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::innerlude::*;
use std::{any::Any, rc::Rc, sync::Arc};
use std::{any::Any, rc::Rc};
/// An integrated virtual node system that progresses events and diffs UI trees.
///
@@ -121,6 +123,20 @@ impl VirtualDom {
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
}
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
///
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrive its channel receiver.
pub fn new_with_props_and_scheduler<P: 'static + Send>(
root: FC<P>,
root_props: P,
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
let root_fc = Box::new(root);
let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
@@ -132,7 +148,7 @@ impl VirtualDom {
std::mem::transmute(root(Context { scope }, props))
});
let scheduler = Scheduler::new();
let scheduler = Scheduler::new(sender, receiver);
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
Scope::new(
@@ -343,47 +359,46 @@ impl VirtualDom {
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
todo!("making vdom send right now");
// if self.scheduler.has_any_work() {
// log::debug!("No need to wait for work, we already have some");
// return;
// }
if self.scheduler.has_any_work() {
log::debug!("No need to wait for work, we already have some");
return;
}
// log::debug!("No active work.... waiting for some...");
// use futures_util::StreamExt;
log::debug!("No active work.... waiting for some...");
use futures_util::StreamExt;
// // right now this won't poll events if there is ongoing work
// // in the future we want to prioritize some events over ongoing work
// // this is coming in the "priorities" PR
// right now this won't poll events if there is ongoing work
// in the future we want to prioritize some events over ongoing work
// this is coming in the "priorities" PR
// // Wait for any new events if we have nothing to do
// // todo: poll the events once even if there is work to do to prevent starvation
// futures_util::select! {
// _ = self.scheduler.async_tasks.next() => {}
// msg = self.scheduler.receiver.next() => {
// match msg.unwrap() {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// },
// }
// Wait for any new events if we have nothing to do
// todo: poll the events once even if there is work to do to prevent starvation
futures_util::select! {
_ = self.scheduler.async_tasks.next() => {}
msg = self.scheduler.receiver.next() => {
match msg.unwrap() {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
},
}
// while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
// match msg {
// SchedulerMsg::Task(t) => todo!(),
// SchedulerMsg::Immediate(im) => {
// self.scheduler.dirty_scopes.insert(im);
// }
// SchedulerMsg::UiEvent(evt) => {
// self.scheduler.ui_events.push_back(evt);
// }
// }
// }
while let Ok(Some(msg)) = self.scheduler.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
}
}
}
}
@@ -407,14 +422,5 @@ impl std::fmt::Display for VirtualDom {
}
}
/*
Send safety...
The VirtualDom can only be "send" if the internals exposed to user code are also "send".
IE it's okay to move an Rc from one thread to another thread as long as it's only used internally
*/
// we never actually use the contents of this root caller
struct RootCaller(Rc<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>);