wip: threadsafe
This commit is contained in:
1 parent
71f0df6374
commit
82953f2ac3
22 files changed
+414
-545
No files matched your search
@@ -1,4 +1,8 @@
|
||||
//! Public APIs for managing component state, tasks, and lifecycles.
|
||||
//!
|
||||
//! This module is separate from `Scope` to narrow what exactly is exposed to user code.
|
||||
//!
|
||||
//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
|
||||
|
||||
use crate::innerlude::*;
|
||||
use std::{any::TypeId, ops::Deref, rc::Rc};
|
||||
|
||||
@@ -14,6 +14,7 @@ use std::{
|
||||
fmt::Debug,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -216,13 +217,13 @@ pub mod on {
|
||||
)* ) => {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
pub struct $wrapper(pub Rc<dyn $eventdata>);
|
||||
pub struct $wrapper(pub Arc<dyn $eventdata>);
|
||||
|
||||
// todo: derefing to the event is fine (and easy) but breaks some IDE stuff like (go to source)
|
||||
// going to source in fact takes you to the source of Rc which is... less than useful
|
||||
// Either we ask for this to be changed in Rust-analyzer or manually impkement the trait
|
||||
impl Deref for $wrapper {
|
||||
type Target = Rc<dyn $eventdata>;
|
||||
type Target = Arc<dyn $eventdata>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
@@ -598,7 +599,7 @@ pub mod on {
|
||||
];
|
||||
}
|
||||
|
||||
pub struct GenericEvent(pub Rc<dyn GenericEventInner>);
|
||||
pub struct GenericEvent(pub Arc<dyn GenericEventInner>);
|
||||
|
||||
pub trait GenericEventInner {
|
||||
/// Return a reference to the raw event. User will need to downcast the event to the right platform-specific type.
|
||||
|
||||
@@ -29,6 +29,7 @@ pub mod scheduler;
|
||||
pub mod scope;
|
||||
pub mod tasks;
|
||||
pub mod test_dom;
|
||||
pub mod threadsafe;
|
||||
pub mod util;
|
||||
pub mod virtual_dom;
|
||||
|
||||
@@ -50,6 +51,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::scope::*;
|
||||
pub use crate::tasks::*;
|
||||
pub use crate::test_dom::*;
|
||||
pub use crate::threadsafe::*;
|
||||
pub use crate::util::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
@@ -60,7 +62,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::innerlude::{
|
||||
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
|
||||
Mutations, NodeFactory, Properties, ScopeId, SuspendedContext, SyntheticEvent, TaskHandle,
|
||||
TestDom, UserEvent, VNode, VirtualDom, FC,
|
||||
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::{
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
//! A threadsafe wrapper for the VirtualDom
|
||||
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use crate::VirtualDom;
|
||||
|
||||
/// A threadsafe wrapper for the Dioxus VirtualDom.
|
||||
///
|
||||
/// The Dioxus VirtualDom is not normally `Send` because user code can contain non-`Send` types. However, it is important
|
||||
/// to have a VirtualDom that is `Send` when used in server-side code since very few web frameworks support non-send
|
||||
/// handlers.
|
||||
///
|
||||
/// To address this, we have the `ThreadsafeVirtualDom` type which is a threadsafe wrapper for the VirtualDom. To access
|
||||
/// the VirtualDom, it must be first unlocked using the `lock` method. This locks the VirtualDom through a mutex and
|
||||
/// prevents any user code from leaking out. It is not possible to acquire any non-`Send` types from inside the VirtualDom.
|
||||
///
|
||||
/// The only way data may be accessed through the VirtualDom is from the "root props" method or by accessing a `Scope`
|
||||
/// 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.
|
||||
pub struct ThreadsafeVirtualDom {
|
||||
inner: Arc<Mutex<VirtualDom>>,
|
||||
}
|
||||
|
||||
impl ThreadsafeVirtualDom {
|
||||
pub fn new(inner: VirtualDom) -> Self {
|
||||
let inner = Arc::new(Mutex::new(inner));
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn lock(&self) -> Option<VirtualDomGuard> {
|
||||
let locked = self.inner.lock().unwrap();
|
||||
Some(VirtualDomGuard { guard: locked })
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for ThreadsafeVirtualDom {}
|
||||
|
||||
pub struct VirtualDomGuard<'a> {
|
||||
guard: MutexGuard<'a, VirtualDom>,
|
||||
}
|
||||
|
||||
impl<'a> std::ops::Deref for VirtualDomGuard<'a> {
|
||||
type Target = MutexGuard<'a, VirtualDom>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.guard
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::ops::DerefMut for VirtualDomGuard<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.guard
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,7 @@
|
||||
//! Additional functionality is defined in the respective files.
|
||||
|
||||
use crate::innerlude::*;
|
||||
use futures_util::Future;
|
||||
use std::any::Any;
|
||||
use std::{any::Any, rc::Rc, sync::Arc};
|
||||
|
||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||
///
|
||||
@@ -60,10 +59,10 @@ pub struct VirtualDom {
|
||||
|
||||
root_fc: Box<dyn Any>,
|
||||
|
||||
root_props: Box<dyn Any>,
|
||||
root_props: Rc<dyn Any + Send>,
|
||||
|
||||
// we need to keep the allocation around, but we don't necessarily use it
|
||||
_root_caller: Box<dyn for<'b> Fn(&'b Scope) -> DomTree<'b> + 'static>,
|
||||
_root_caller: RootCaller,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
@@ -121,16 +120,15 @@ impl VirtualDom {
|
||||
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||
/// let mutations = dom.rebuild();
|
||||
/// ```
|
||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||
pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
|
||||
let root_fc = Box::new(root);
|
||||
|
||||
let root_props: Box<dyn Any> = Box::new(root_props);
|
||||
|
||||
let props_ptr = root_props.downcast_ref::<P>().unwrap() as *const P;
|
||||
let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
|
||||
|
||||
let _p = root_props.clone();
|
||||
// Safety: this callback is only valid for the lifetime of the root props
|
||||
let root_caller: Box<dyn Fn(&Scope) -> DomTree> = Box::new(move |scope: &Scope| unsafe {
|
||||
let props: &'_ P = &*(props_ptr as *const P);
|
||||
let root_caller: Rc<dyn Fn(&Scope) -> DomTree> = Rc::new(move |scope: &Scope| unsafe {
|
||||
let props = _p.downcast_ref::<P>().unwrap();
|
||||
std::mem::transmute(root(Context { scope }, props))
|
||||
});
|
||||
|
||||
@@ -148,7 +146,7 @@ impl VirtualDom {
|
||||
});
|
||||
|
||||
Self {
|
||||
_root_caller: root_caller,
|
||||
_root_caller: RootCaller(root_caller),
|
||||
root_fc,
|
||||
base_scope,
|
||||
scheduler,
|
||||
@@ -189,14 +187,14 @@ impl VirtualDom {
|
||||
/// ```
|
||||
pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
|
||||
where
|
||||
P: 'static,
|
||||
P: 'static + Send,
|
||||
{
|
||||
let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
|
||||
|
||||
// Pre-emptively drop any downstream references of the old props
|
||||
root_scope.ensure_drop_safety(&self.scheduler.pool);
|
||||
|
||||
let mut root_props: Box<dyn Any> = Box::new(root_props);
|
||||
let mut root_props: Rc<dyn Any + Send> = Rc::new(root_props);
|
||||
|
||||
if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
|
||||
// Swap the old props and new props
|
||||
@@ -345,46 +343,47 @@ 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) {
|
||||
if self.scheduler.has_any_work() {
|
||||
log::debug!("No need to wait for work, we already have some");
|
||||
return;
|
||||
}
|
||||
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;
|
||||
// }
|
||||
|
||||
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,3 +406,15 @@ impl std::fmt::Display for VirtualDom {
|
||||
renderer.render(self, root, f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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