wip: overhaul event system
This commit is contained in:
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
+458
-441
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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>>,
|
||||
}
|
||||
|
||||
@@ -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>);
|
||||
Reference in new issue
Block a user