feat: mutations

This commit is contained in:
Jonathan Kelley 2021-08-08 15:15:16 -04:00
parent f2334c17be
commit fac42339c2
17 changed files with 464 additions and 275 deletions

View file

@ -4,6 +4,7 @@ pub mod dioxus {
pub mod prelude {
pub trait Properties {
type Builder;
const IS_STATIC: bool;
fn builder() -> Self::Builder;
unsafe fn memoize(&self, other: &Self) -> bool;
}

View file

@ -662,6 +662,11 @@ Finally, call `.build()` to create the instance of `{name}`.
false => quote! { self == other },
};
let is_static = match are_there_generics {
true => quote! { false },
false => quote! { true },
};
Ok(quote! {
impl #impl_generics #name #ty_generics #where_clause {
#[doc = #builder_method_doc]
@ -693,6 +698,7 @@ Finally, call `.build()` to create the instance of `{name}`.
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
type Builder = #builder_name #generics_with_empty;
const IS_STATIC: bool = #is_static;
fn builder() -> Self::Builder {
#name::builder()
}

View file

@ -67,6 +67,7 @@ impl PartialEq for ChildProps {
}
impl Properties for ChildProps {
type Builder = ();
const IS_STATIC: bool = true;
fn builder() -> Self::Builder {
()
}

View file

@ -66,8 +66,6 @@ impl SharedResources {
// elements are super cheap - the value takes no space
let raw_elements = Slab::with_capacity(2000);
// let pending_events = Rc::new(RefCell::new(Vec::new()));
let (sender, receiver) = futures_channel::mpsc::unbounded();
let heuristics = HeuristicsEngine::new();
@ -106,8 +104,8 @@ impl SharedResources {
}
/// this is unsafe because the caller needs to track which other scopes it's already using
pub unsafe fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
let inner = &*self.components.get();
pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
let inner = unsafe { &*self.components.get() };
inner.get(idx.0)
}
@ -167,6 +165,19 @@ impl SharedResources {
self.async_tasks.borrow_mut().push(task);
TaskHandle {}
}
pub fn make_trigger_key(&self, trigger: &EventTrigger) -> EventKey {
let height = self
.get_scope(trigger.originator)
.map(|f| f.height)
.unwrap();
EventKey {
height,
originator: trigger.originator,
priority: trigger.priority,
}
}
}
pub struct TaskHandle {}

View file

@ -41,7 +41,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
/// ```
pub trait Properties: Sized {
type Builder;
const IS_STATIC: bool = false;
const IS_STATIC: bool;
fn builder() -> Self::Builder;
/// Memoization can only happen if the props are 'static

View file

@ -69,43 +69,13 @@ use futures_util::Future;
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use std::{any::Any, cell::Cell, cmp::Ordering, pin::Pin};
use std::{any::Any, cell::Cell, cmp::Ordering, marker::PhantomData, pin::Pin};
use DomEdit::*;
/// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
/// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows
/// for a form of passive garbage collection where nodes aren't immedately cleaned up.
///
/// The "RealDom" abstracts over the... real dom. The RealDom trait assumes that the renderer maintains a stack of real
/// nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack will receive
/// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
pub trait RealDom {
fn raw_node_as_any(&self) -> &mut dyn Any;
/// Essentially "are we out of time to do more work?"
/// Right now, defaults to "no" giving us unlimited time to process work.
/// This will lead to blocking behavior in the UI, so implementors will want to override this.
fn must_commit(&self) -> bool {
false
}
fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
// pause the virtualdom until the main loop is ready to process more work
fn wait_until_ready<'s>(&'s mut self) -> Pin<Box<dyn Future<Output = ()> + 's>> {
//
Box::pin(async {
//
})
}
}
pub struct DiffMachine<'r, 'bump> {
// pub real_dom: &'real dyn RealDom,
pub vdom: &'bump SharedResources,
pub edits: &'r mut Vec<DomEdit<'bump>>,
pub edits: Mutations<'bump>,
pub scope_stack: SmallVec<[ScopeId; 5]>,
@ -114,11 +84,13 @@ pub struct DiffMachine<'r, 'bump> {
// will be used later for garbage collection
// we check every seen node and then schedule its eventual deletion
pub seen_scopes: FxHashSet<ScopeId>,
_r: PhantomData<&'r ()>,
}
impl<'r, 'bump> DiffMachine<'r, 'bump> {
pub(crate) fn new(
edits: &'r mut Vec<DomEdit<'bump>>,
edits: Mutations<'bump>,
cur_scope: ScopeId,
shared: &'bump SharedResources,
) -> Self {
@ -128,6 +100,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
vdom: shared,
diffed: FxHashSet::default(),
seen_scopes: FxHashSet::default(),
_r: PhantomData,
}
}
@ -136,15 +109,16 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
///
/// This will PANIC if any component elements are passed in.
pub fn new_headless(
edits: &'r mut Vec<DomEdit<'bump>>,
// edits: &'r mut Vec<DomEdit<'bump>>,
shared: &'bump SharedResources,
) -> Self {
Self {
edits,
edits: Mutations { edits: Vec::new() },
scope_stack: smallvec![ScopeId(0)],
vdom: shared,
diffed: FxHashSet::default(),
seen_scopes: FxHashSet::default(),
_r: PhantomData,
}
}
@ -211,13 +185,13 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
if old.attributes.len() == new.attributes.len() {
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
if old_attr.value != new_attr.value {
please_commit(&mut self.edits);
please_commit(&mut self.edits.edits);
self.edit_set_attribute(new_attr);
}
}
} else {
// TODO: provide some sort of report on how "good" the diffing was
please_commit(&mut self.edits);
please_commit(&mut self.edits.edits);
for attribute in old.attributes {
self.edit_remove_attribute(attribute);
}
@ -238,7 +212,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
please_commit(&mut self.edits);
please_commit(&mut self.edits.edits);
self.edit_remove_event_listener(old_l.event);
self.edit_new_event_listener(new_l, cur_scope);
}
@ -246,7 +220,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
self.fix_listener(new_l);
}
} else {
please_commit(&mut self.edits);
please_commit(&mut self.edits.edits);
for listener in old.listeners {
self.edit_remove_event_listener(listener.event);
}
@ -421,6 +395,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
dom_id.set(Some(real_id));
let cur_scope = self.current_scope().unwrap();
listeners.iter().for_each(|listener| {
self.fix_listener(listener);
listener.mounted_node.set(Some(real_id));
@ -464,7 +439,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
}
VNodeKind::Component(vcomponent) => {
log::debug!("Mounting a new component");
let caller = vcomponent.caller.clone();
let parent_idx = self.scope_stack.last().unwrap().clone();
@ -486,9 +460,17 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
// Actually initialize the caller's slot with the right address
vcomponent.ass_scope.set(Some(new_idx));
if !vcomponent.can_memoize {
let cur_scope = self.get_scope_mut(&parent_idx).unwrap();
let extended = *vcomponent as *const VComponent;
let extended: *const VComponent<'static> =
unsafe { std::mem::transmute(extended) };
cur_scope.borrowed_props.borrow_mut().push(extended);
}
// TODO:
// Noderefs
// Effects
// add noderefs to current noderef list Noderefs
// add effects to current effect list Effects
let new_component = self.get_scope_mut(&new_idx).unwrap();
@ -501,9 +483,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
// failed to run. this is the first time the component ran, and it failed
// we manually set its head node to an empty fragment
panic!("failing components not yet implemented");
// new_component.frames.head
// self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
}
}
@ -1367,42 +1346,42 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
// Navigation
pub(crate) fn edit_push_root(&mut self, root: ElementId) {
let id = root.as_u64();
self.edits.push(PushRoot { id });
self.edits.edits.push(PushRoot { id });
}
pub(crate) fn edit_pop(&mut self) {
self.edits.push(PopRoot {});
self.edits.edits.push(PopRoot {});
}
// Add Nodes to the dom
// add m nodes from the stack
pub(crate) fn edit_append_children(&mut self, many: u32) {
self.edits.push(AppendChildren { many });
self.edits.edits.push(AppendChildren { many });
}
// replace the n-m node on the stack with the m nodes
// ends with the last element of the chain on the top of the stack
pub(crate) fn edit_replace_with(&mut self, n: u32, m: u32) {
self.edits.push(ReplaceWith { n, m });
self.edits.edits.push(ReplaceWith { n, m });
}
pub(crate) fn edit_insert_after(&mut self, n: u32) {
self.edits.push(InsertAfter { n });
self.edits.edits.push(InsertAfter { n });
}
pub(crate) fn edit_insert_before(&mut self, n: u32) {
self.edits.push(InsertBefore { n });
self.edits.edits.push(InsertBefore { n });
}
// Remove Nodesfrom the dom
pub(crate) fn edit_remove(&mut self) {
self.edits.push(Remove);
self.edits.edits.push(Remove);
}
// Create
pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) {
let id = id.as_u64();
self.edits.push(CreateTextNode { text, id });
self.edits.edits.push(CreateTextNode { text, id });
}
pub(crate) fn edit_create_element(
@ -1413,15 +1392,15 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
) {
let id = id.as_u64();
match ns {
Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }),
None => self.edits.push(CreateElement { id, tag }),
Some(ns) => self.edits.edits.push(CreateElementNs { id, ns, tag }),
None => self.edits.edits.push(CreateElement { id, tag }),
}
}
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn edit_create_placeholder(&mut self, id: ElementId) {
let id = id.as_u64();
self.edits.push(CreatePlaceholder { id });
self.edits.edits.push(CreatePlaceholder { id });
}
// events
@ -1434,7 +1413,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
let element_id = mounted_node.get().unwrap().as_u64();
self.edits.push(NewEventListener {
self.edits.edits.push(NewEventListener {
scope,
event_name: event,
mounted_node_id: element_id,
@ -1442,12 +1421,12 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
}
pub(crate) fn edit_remove_event_listener(&mut self, event: &'static str) {
self.edits.push(RemoveEventListener { event });
self.edits.edits.push(RemoveEventListener { event });
}
// modify
pub(crate) fn edit_set_text(&mut self, text: &'bump str) {
self.edits.push(SetText { text });
self.edits.edits.push(SetText { text });
}
pub(crate) fn edit_set_attribute(&mut self, attribute: &'bump Attribute) {
@ -1461,7 +1440,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
// field: &'static str,
// value: &'bump str,
// ns: Option<&'static str>,
self.edits.push(SetAttribute {
self.edits.edits.push(SetAttribute {
field: name,
value,
ns: *namespace,
@ -1484,7 +1463,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
// field: &'static str,
// value: &'bump str,
// ns: Option<&'static str>,
self.edits.push(SetAttribute {
self.edits.edits.push(SetAttribute {
field: name,
value,
ns: Some(namespace),
@ -1493,7 +1472,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
let name = attribute.name;
self.edits.push(RemoveAttribute { name });
self.edits.edits.push(RemoveAttribute { name });
}
}

View file

@ -28,21 +28,15 @@ pub struct EventTrigger {
/// The priority of the event
pub priority: EventPriority,
}
impl EventTrigger {
pub fn make_key(&self) -> EventKey {
EventKey {
originator: self.originator,
priority: self.priority,
}
}
}
#[derive(PartialEq, Eq)]
#[derive(PartialEq, Eq, Clone, Copy)]
pub struct EventKey {
/// The originator of the event trigger
pub originator: ScopeId,
/// The priority of the event
pub priority: EventPriority,
/// The height of the scope (used for ordering)
pub height: u32,
// TODO: add the time that the event was queued
}
@ -117,9 +111,6 @@ pub enum VirtualEvent {
/// be processed.
GarbageCollection,
///
DiffComponent,
/// A type of "immediate" event scheduled by components
ScheduledUpdate {
height: u32,
@ -135,9 +126,11 @@ pub enum VirtualEvent {
// This type exists for the task/concurrency system to signal that a task is ready.
// However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
// own re-run.
AsyncEvent {},
AsyncEvent {
should_rerender: bool,
},
// Suspense events are a type of async event
// Suspense events are a type of async event generated when suspended nodes are ready to be processed.
//
// they have the lowest priority
SuspenseEvent {
@ -188,7 +181,6 @@ impl std::fmt::Debug for VirtualEvent {
VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
VirtualEvent::DiffComponent { .. } => "DiffComponent",
};
f.debug_struct("VirtualEvent").field("type", &name).finish()

View file

@ -127,7 +127,9 @@ where
*slot.as_ref().borrow_mut() = Some(output);
updater(update_id);
EventTrigger {
event: VirtualEvent::AsyncEvent {},
event: VirtualEvent::AsyncEvent {
should_rerender: false,
},
originator,
priority: EventPriority::Low,
real_node_id: None,

View file

@ -12,8 +12,8 @@
pub use crate::innerlude::{
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
EventTrigger, LazyNodes, NodeFactory, Properties, RealDom, ScopeId, SuspendedContext, VNode,
VNodeKind, VirtualDom, VirtualEvent, FC,
EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VNodeKind,
VirtualDom, VirtualEvent, FC,
};
pub mod prelude {

View file

@ -152,15 +152,16 @@ pub struct VComponent<'src> {
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
pub(crate) drop_props: Option<&'src dyn FnOnce()>,
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
pub is_static: bool,
pub can_memoize: bool,
// a pointer into the bump arena (given by the 'src lifetime)
pub(crate) raw_props: *const (),
// a pointer to the raw fn typ
// pub(crate) user_fc: BumpB\,
pub(crate) user_fc: *const (),
}
@ -365,20 +366,19 @@ impl<'a> NodeFactory<'a> {
P: Properties + 'a,
V: 'a + AsRef<[VNode<'a>]>,
{
// TODO
// It's somewhat wrong to go about props like this
let bump = self.bump();
// We don't want the fat part of the fat pointer
// This function does static dispatch so we don't need any VTable stuff
let children: &'a V = self.bump().alloc(children);
let children: &'a V = bump.alloc(children);
let children = children.as_ref();
let props = self.bump().alloc(props);
let props = bump.alloc(props);
let raw_props = props as *mut P as *mut ();
let user_fc = component as *const ();
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
move |other: &VComponent| {
if user_fc == other.user_fc {
// Safety
@ -404,13 +404,19 @@ impl<'a> NodeFactory<'a> {
}));
// create a closure to drop the props
let drop_props: Option<&dyn FnOnce()> = Some(self.bump().alloc_with(|| {
let mut has_dropped = false;
let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
move || unsafe {
let real_other = raw_props as *mut _ as *mut P;
let b = BumpBox::from_raw(real_other);
std::mem::drop(b);
if !has_dropped {
let real_other = raw_props as *mut _ as *mut P;
let b = BumpBox::from_raw(real_other);
std::mem::drop(b);
has_dropped = true;
}
}
}));
});
let drop_props = unsafe { BumpBox::from_raw(drop_props) };
let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
@ -418,14 +424,15 @@ impl<'a> NodeFactory<'a> {
VNode {
key,
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
kind: VNodeKind::Component(bump.alloc_with(|| VComponent {
user_fc,
comparator,
raw_props,
children,
caller: NodeFactory::create_component_caller(component, raw_props),
is_static,
drop_props,
drop_props: RefCell::new(Some(drop_props)),
can_memoize: P::IS_STATIC,
ass_scope: Cell::new(None),
})),
}

View file

@ -37,6 +37,7 @@ pub struct Scope {
// Listeners
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
// State
pub(crate) hooks: HookList,
@ -94,6 +95,7 @@ impl Scope {
hooks: Default::default(),
shared_contexts: Default::default(),
listeners: Default::default(),
borrowed_props: Default::default(),
descendents: Default::default(),
pending_garbage: Default::default(),
}
@ -114,27 +116,8 @@ impl Scope {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
if !self.pending_garbage.borrow().is_empty() {
// We have some garbage to clean up
log::error!("Cleaning up garabge");
panic!("cannot run scope while garbage is pending! Please clean up your mess first");
}
log::debug!("reset okay");
// make sure we call the drop implementation on all the listeners
// this is important to not leak memory
self.listeners
.borrow_mut()
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|listener| {
listener.callback.borrow_mut().take();
});
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Referrence)
// right now, we don't drop
self.ensure_drop_safety();
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
@ -164,6 +147,51 @@ impl Scope {
}
}
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
/// causuing UB in our tree.
///
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
/// any possible references to the data in the hook list.
///
/// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
/// all listeners and borrowed props so we can clear them here.
fn ensure_drop_safety(&mut self) {
// make sure all garabge is collected before trying to proceed with anything else
debug_assert!(
self.pending_garbage.borrow().is_empty(),
"clean up your garabge please"
);
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Referrence)
// right now, we don't drop
let vdom = &self.vdom;
self.borrowed_props
.get_mut()
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|comp| {
// First drop the component's undropped references
let scope_id = comp.ass_scope.get().unwrap();
let scope = unsafe { vdom.get_scope_mut(scope_id) }.unwrap();
scope.ensure_drop_safety();
// Now, drop our own reference
let mut dropper = comp.drop_props.borrow_mut().take().unwrap();
dropper();
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
self.listeners
.get_mut()
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|listener| {
listener.callback.borrow_mut().take();
});
}
// A safe wrapper around calling listeners
// calling listeners will invalidate the list of listeners
// The listener list will be completely drained because the next frame will write over previous listeners

View file

@ -26,16 +26,3 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
// Some(self.cmp(other))
// }
// }
pub struct DebugDom {}
impl DebugDom {
pub fn new() -> Self {
Self {}
}
}
impl RealDom for DebugDom {
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
todo!()
}
}

View file

@ -29,7 +29,7 @@ use std::any::Any;
use std::any::TypeId;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
use std::pin::Pin;
/// An integrated virtual node system that progresses events and diffs UI trees.
@ -53,6 +53,8 @@ pub struct VirtualDom {
/// Should always be the first (gen=0, id=0)
pub base_scope: ScopeId,
active_fibers: Vec<Fiber<'static>>,
pending_events: BTreeMap<EventKey, EventTrigger>,
// for managing the props that were used to create the dom
@ -144,6 +146,7 @@ impl VirtualDom {
base_scope,
_root_props: root_props,
shared: components,
active_fibers: Vec::new(),
pending_events: BTreeMap::new(),
_root_prop_type: TypeId::of::<P>(),
}
@ -195,8 +198,10 @@ impl VirtualDom {
/// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
/// through "run"
///
pub fn rebuild<'s>(&'s mut self, edits: &'_ mut Vec<DomEdit<'s>>) -> Result<()> {
let mut diff_machine = DiffMachine::new(edits, self.base_scope, &self.shared);
pub fn rebuild<'s>(&'s mut self) -> Result<Vec<DomEdit<'s>>> {
let mut edits = Vec::new();
let mutations = Mutations { edits: Vec::new() };
let mut diff_machine = DiffMachine::new(mutations, self.base_scope, &self.shared);
let cur_component = diff_machine
.get_scope_mut(&self.base_scope)
@ -214,7 +219,7 @@ impl VirtualDom {
);
}
Ok(())
Ok(edits)
}
async fn select_next_event(&mut self) -> Option<EventTrigger> {
@ -222,8 +227,9 @@ impl VirtualDom {
// drain the in-flight events so that we can sort them out with the current events
while let Ok(Some(trigger)) = receiver.try_next() {
log::info!("scooping event from receiver");
self.pending_events.insert(trigger.make_key(), trigger);
log::info!("retrieving event from receiver");
let key = self.shared.make_trigger_key(&trigger);
self.pending_events.insert(key, trigger);
}
if self.pending_events.is_empty() {
@ -242,83 +248,172 @@ impl VirtualDom {
futures_util::future::Either::Right((trigger, _)) => trigger,
}
.unwrap();
self.pending_events.insert(trigger.make_key(), trigger);
let key = self.shared.make_trigger_key(&trigger);
self.pending_events.insert(key, trigger);
}
todo!()
// pop the most important event off
let key = self.pending_events.keys().next().unwrap().clone();
let trigger = self.pending_events.remove(&key).unwrap();
// let trigger = self.select_next_event().unwrap();
// trigger
Some(trigger)
}
// the cooperartive, fiber-based scheduler
// is "awaited" and will always return some edits for the real dom to apply
//
// Right now, we'll happily partially render component trees
//
// Normally, you would descend through the tree depth-first, but we actually descend breadth-first.
pub async fn run(&mut self, realdom: &mut dyn RealDom) -> Result<()> {
let cur_component = self.base_scope;
let mut edits = Vec::new();
let resources = self.shared.clone();
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
///
/// This method will not wait for any suspended tasks, completely skipping over
pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
//
let mut diff_machine = DiffMachine::new(&mut edits, cur_component, &resources);
todo!()
}
/// Runs the virtualdom with no time limit.
///
/// If there are pending tasks, they will be progressed before returning. This is useful when rendering an application
/// that has suspended nodes or suspended tasks. Be warned - any async tasks running forever will prevent this method
/// from completing. Consider using `run` and specifing a deadline.
pub async fn run_unbounded<'s>(&'s mut self) -> Result<Mutations<'s>> {
self.run_with_deadline(|| false).await
}
/// Run the virtualdom with a time limit.
///
/// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
/// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
/// exhaust the deadline working on them.
///
/// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
/// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
///
/// Due to platform differences in how time is handled, this method accepts a closure that must return true when the
/// deadline is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room
/// into the deadline closure manually.
///
/// The deadline is checked before starting to diff components. This strikes a balance between the overhead of checking
/// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
/// the screen will "jank" up. In debug, this will trigger an alert.
///
/// # Example
///
/// ```no_run
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!( div {"hello"} )));
/// loop {
/// let started = std::time::Instant::now();
/// let deadline = move || std::time::Instant::now() - started > std::time::Duration::from_millis(16);
///
/// let mutations = dom.run_with_deadline(deadline).await;
/// apply_mutations(mutations);
/// }
/// ```
pub async fn run_with_deadline<'s>(
&'s mut self,
mut deadline_exceeded: impl FnMut() -> bool,
) -> Result<Mutations<'s>> {
let cur_component = self.base_scope;
let mut mutations = Mutations { edits: Vec::new() };
let mut diff_machine = DiffMachine::new(mutations, cur_component, &self.shared);
let must_be_re_rendered = HashSet::<ScopeId>::new();
let mut receiver = self.shared.task_receiver.borrow_mut();
//
loop {
let trigger = self.select_next_event().await.unwrap();
if deadline_exceeded() {
break;
}
/*
Strategy:
1. Check if there are any events in the receiver.
2. If there are, process them and create a new fiber.
3. If there are no events, then choose a fiber to work on.
4. If there are no fibers, then wait for the next event from the receiver.
5. While processing a fiber, periodically check if we're out of time
6. If we are almost out of time, then commit our edits to the realdom
7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported)
The user of this method will loop over "run", waiting for edits and then committing them IE
task::spawn_local(async {
let vdom = VirtualDom::new(App);
loop {
let deadline = wait_for_idle().await;
let mutations = vdom.run_with_deadline(deadline);
realdom.apply_edits(mutations.edits);
realdom.apply_refs(mutations.refs);
}
});
let vdom = VirtualDom::new(App);
let realdom = WebsysDom::new(App);
loop {
let deadline = wait_for_idle().await;
let mutations = vdom.run_with_deadline(deadline);
realdom.apply_edits(mutations.edits);
realdom.apply_refs(mutations.refs);
}
```
task::spawn_local(async move {
let vdom = VirtualDom::new(App);
loop {
let mutations = vdom.run_with_deadline(16);
realdom.apply_edits(mutations.edits)?;
realdom.apply_refs(mutations.refs)?;
}
});
event_loop.run(move |event, _, flow| {
});
```
*/
let mut receiver = self.shared.task_receiver.borrow_mut();
// match receiver.try_next() {}
let trigger = receiver.next().await.unwrap();
match &trigger.event {
// Collecting garabge is not currently interruptible.
// If any user event is received, then we run the listener and let it dump "needs updates" into the queue
//
// In the future, it could be though
VirtualEvent::GarbageCollection => {
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
let mut garbage_list = scope.consume_garbage();
let mut scopes_to_kill = Vec::new();
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
// TODO: allow interruption here
if !realdom.must_commit() {
let scope_id = comp.ass_scope.get().unwrap();
let scope = self.get_scope(scope_id).unwrap();
let root = scope.root();
garbage_list.push(root);
scopes_to_kill.push(scope_id);
}
}
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(_) => {
let scope_id = &trigger.originator;
let scope = unsafe { self.shared.get_scope_mut(*scope_id) };
match scope {
Some(scope) => {
scope.call_listener(trigger)?;
}
None => {
log::warn!("No scope found for event: {:#?}", scope_id);
}
}
for scope in scopes_to_kill {
// oy kill em
log::debug!("should be removing scope {:#?}", scope);
}
}
@ -363,74 +458,93 @@ impl VirtualDom {
}
}
// Collecting garabge is not currently interruptible.
//
// In the future, it could be though
VirtualEvent::GarbageCollection => {
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
let mut garbage_list = scope.consume_garbage();
let mut scopes_to_kill = Vec::new();
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.get_scope(scope_id).unwrap();
let root = scope.root();
garbage_list.push(root);
scopes_to_kill.push(scope_id);
}
}
}
for scope in scopes_to_kill {
// oy kill em
log::debug!("should be removing scope {:#?}", scope);
}
}
// Run the component
VirtualEvent::ScheduledUpdate { height: u32 } => {
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
match scope.run_scope() {
Ok(_) => {
let event = VirtualEvent::DiffComponent;
let trigger = EventTrigger {
event,
originator: trigger.originator,
priority: EventPriority::High,
real_node_id: None,
};
self.shared.task_sender.unbounded_send(trigger);
todo!();
// let event = VirtualEvent::DiffComponent;
// let trigger = EventTrigger {
// event,
// originator: trigger.originator,
// priority: EventPriority::High,
// real_node_id: None,
// };
// self.shared.task_sender.unbounded_send(trigger);
}
Err(_) => {
log::error!("failed to run this component!");
}
}
}
VirtualEvent::DiffComponent => {
diff_machine.diff_scope(trigger.originator)?;
}
// Process any user-facing events just by calling their listeners
// This performs no actual work - but the listeners themselves will cause insert new work
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(_) => {
let scope_id = &trigger.originator;
let scope = unsafe { self.shared.get_scope_mut(*scope_id) };
match scope {
Some(scope) => scope.call_listener(trigger)?,
None => {
log::warn!("No scope found for event: {:#?}", scope_id);
}
}
}
}
if realdom.must_commit() || self.pending_events.is_empty() {
realdom.commit_edits(&mut diff_machine.edits);
realdom.wait_until_ready().await;
}
// //
// if realdom.must_commit() {
// // commit these edits and then wait for the next idle period
// realdom.commit_edits(&mut diff_machine.edits).await;
// }
}
Ok(())
Ok(diff_machine.edits)
}
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
todo!()
// std::rc::Rc::new(move |_| {
// //
// })
// todo()
self.shared.task_sender.clone()
}
}
@ -439,8 +553,38 @@ impl VirtualDom {
unsafe impl Sync for VirtualDom {}
unsafe impl Send for VirtualDom {}
fn select_next_event(
pending_events: &mut BTreeMap<EventKey, EventTrigger>,
) -> Option<EventTrigger> {
None
struct Fiber<'a> {
trigger: EventTrigger,
// scopes that haven't been updated yet
pending_scopes: Vec<ScopeId>,
pending_nodes: Vec<*const VNode<'a>>,
// WIP edits
edits: Vec<DomEdit<'a>>,
started: bool,
completed: bool,
}
impl Fiber<'_> {
fn new(trigger: EventTrigger) -> Self {
Self {
trigger,
pending_scopes: Vec::new(),
pending_nodes: Vec::new(),
edits: Vec::new(),
started: false,
completed: false,
}
}
}
/// The "Mutations" object holds the changes that need to be made to the DOM.
pub struct Mutations<'s> {
// todo: apply node refs
// todo: apply effects
pub edits: Vec<DomEdit<'s>>,
}

View file

@ -11,7 +11,6 @@ use dioxus::{
arena::SharedResources,
diff::{CreateMeta, DiffMachine},
prelude::*,
util::DebugDom,
DomEdit,
};
use dioxus_core as dioxus;

View file

@ -0,0 +1,32 @@
use bumpalo::Bump;
use anyhow::{Context, Result};
use dioxus::{
arena::SharedResources,
diff::{CreateMeta, DiffMachine},
prelude::*,
DomEdit,
};
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
#[async_std::test]
async fn event_queue_works() {
static App: FC<()> = |cx| {
cx.render(rsx! {
div { "hello world" }
})
};
let mut dom = VirtualDom::new(App);
let edits = dom.rebuild().unwrap();
async_std::task::spawn_local(async move {
match dom.run_unbounded().await {
Err(_) => todo!(),
Ok(mutations) => {
//
}
}
});
}

View file

@ -88,26 +88,26 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
let channel = vir.get_event_sender();
struct WebviewBridge {}
impl RealDom for WebviewBridge {
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
todo!()
}
// impl RealDom for WebviewBridge {
// fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
// todo!()
// }
fn must_commit(&self) -> bool {
false
}
// fn must_commit(&self) -> bool {
// false
// }
fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
// fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
fn wait_until_ready<'s>(
&'s mut self,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
//
Box::pin(async {
//
})
}
}
// fn wait_until_ready<'s>(
// &'s mut self,
// ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
// //
// Box::pin(async {
// //
// })
// }
// }
let mut real_dom = WebviewBridge {};
// async_std::task::spawn_local(vir.run(&mut real_dom));

View file

@ -96,7 +96,7 @@ pub async fn run_with_props<T: Properties + 'static>(
Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
);
dom.run(&mut websys_dom).await?;
dom.run_with_deadline(&mut websys_dom).await?;
Ok(())
}