feat: mutations
This commit is contained in:
1 parent
f2334c17be
commit
fac42339c2
17 files changed
+464
-275
No files matched your search
@@ -4,6 +4,7 @@ pub mod dioxus {
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub trait Properties {
|
pub trait Properties {
|
||||||
type Builder;
|
type Builder;
|
||||||
|
const IS_STATIC: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -662,6 +662,11 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
false => quote! { self == other },
|
false => quote! { self == other },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let is_static = match are_there_generics {
|
||||||
|
true => quote! { false },
|
||||||
|
false => quote! { true },
|
||||||
|
};
|
||||||
|
|
||||||
Ok(quote! {
|
Ok(quote! {
|
||||||
impl #impl_generics #name #ty_generics #where_clause {
|
impl #impl_generics #name #ty_generics #where_clause {
|
||||||
#[doc = #builder_method_doc]
|
#[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{
|
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
|
||||||
type Builder = #builder_name #generics_with_empty;
|
type Builder = #builder_name #generics_with_empty;
|
||||||
|
const IS_STATIC: bool = #is_static;
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
#name::builder()
|
#name::builder()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ impl PartialEq for ChildProps {
|
|||||||
}
|
}
|
||||||
impl Properties for ChildProps {
|
impl Properties for ChildProps {
|
||||||
type Builder = ();
|
type Builder = ();
|
||||||
|
const IS_STATIC: bool = true;
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
()
|
()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,8 +66,6 @@ impl SharedResources {
|
|||||||
// elements are super cheap - the value takes no space
|
// elements are super cheap - the value takes no space
|
||||||
let raw_elements = Slab::with_capacity(2000);
|
let raw_elements = Slab::with_capacity(2000);
|
||||||
|
|
||||||
// let pending_events = Rc::new(RefCell::new(Vec::new()));
|
|
||||||
|
|
||||||
let (sender, receiver) = futures_channel::mpsc::unbounded();
|
let (sender, receiver) = futures_channel::mpsc::unbounded();
|
||||||
|
|
||||||
let heuristics = HeuristicsEngine::new();
|
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
|
/// 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> {
|
pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
|
||||||
let inner = &*self.components.get();
|
let inner = unsafe { &*self.components.get() };
|
||||||
inner.get(idx.0)
|
inner.get(idx.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +165,19 @@ impl SharedResources {
|
|||||||
self.async_tasks.borrow_mut().push(task);
|
self.async_tasks.borrow_mut().push(task);
|
||||||
TaskHandle {}
|
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 {}
|
pub struct TaskHandle {}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
|||||||
/// ```
|
/// ```
|
||||||
pub trait Properties: Sized {
|
pub trait Properties: Sized {
|
||||||
type Builder;
|
type Builder;
|
||||||
const IS_STATIC: bool = false;
|
const IS_STATIC: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
|
|
||||||
/// Memoization can only happen if the props are 'static
|
/// Memoization can only happen if the props are 'static
|
||||||
|
|||||||
+41
-62
@@ -69,43 +69,13 @@ use futures_util::Future;
|
|||||||
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
|
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
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::*;
|
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 struct DiffMachine<'r, 'bump> {
|
||||||
// pub real_dom: &'real dyn RealDom,
|
|
||||||
pub vdom: &'bump SharedResources,
|
pub vdom: &'bump SharedResources,
|
||||||
|
|
||||||
pub edits: &'r mut Vec<DomEdit<'bump>>,
|
pub edits: Mutations<'bump>,
|
||||||
|
|
||||||
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
||||||
|
|
||||||
@@ -114,11 +84,13 @@ pub struct DiffMachine<'r, 'bump> {
|
|||||||
// will be used later for garbage collection
|
// will be used later for garbage collection
|
||||||
// we check every seen node and then schedule its eventual deletion
|
// we check every seen node and then schedule its eventual deletion
|
||||||
pub seen_scopes: FxHashSet<ScopeId>,
|
pub seen_scopes: FxHashSet<ScopeId>,
|
||||||
|
|
||||||
|
_r: PhantomData<&'r ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
edits: &'r mut Vec<DomEdit<'bump>>,
|
edits: Mutations<'bump>,
|
||||||
cur_scope: ScopeId,
|
cur_scope: ScopeId,
|
||||||
shared: &'bump SharedResources,
|
shared: &'bump SharedResources,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@@ -128,6 +100,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
vdom: shared,
|
vdom: shared,
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
seen_scopes: 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.
|
/// This will PANIC if any component elements are passed in.
|
||||||
pub fn new_headless(
|
pub fn new_headless(
|
||||||
edits: &'r mut Vec<DomEdit<'bump>>,
|
// edits: &'r mut Vec<DomEdit<'bump>>,
|
||||||
shared: &'bump SharedResources,
|
shared: &'bump SharedResources,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
edits,
|
edits: Mutations { edits: Vec::new() },
|
||||||
scope_stack: smallvec![ScopeId(0)],
|
scope_stack: smallvec![ScopeId(0)],
|
||||||
vdom: shared,
|
vdom: shared,
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
seen_scopes: 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() {
|
if old.attributes.len() == new.attributes.len() {
|
||||||
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
|
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
|
||||||
if old_attr.value != new_attr.value {
|
if old_attr.value != new_attr.value {
|
||||||
please_commit(&mut self.edits);
|
please_commit(&mut self.edits.edits);
|
||||||
self.edit_set_attribute(new_attr);
|
self.edit_set_attribute(new_attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: provide some sort of report on how "good" the diffing was
|
// 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 {
|
for attribute in old.attributes {
|
||||||
self.edit_remove_attribute(attribute);
|
self.edit_remove_attribute(attribute);
|
||||||
}
|
}
|
||||||
@@ -238,7 +212,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
if old.listeners.len() == new.listeners.len() {
|
if old.listeners.len() == new.listeners.len() {
|
||||||
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
|
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
|
||||||
if old_l.event != new_l.event {
|
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_remove_event_listener(old_l.event);
|
||||||
self.edit_new_event_listener(new_l, cur_scope);
|
self.edit_new_event_listener(new_l, cur_scope);
|
||||||
}
|
}
|
||||||
@@ -246,7 +220,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
self.fix_listener(new_l);
|
self.fix_listener(new_l);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
please_commit(&mut self.edits);
|
please_commit(&mut self.edits.edits);
|
||||||
for listener in old.listeners {
|
for listener in old.listeners {
|
||||||
self.edit_remove_event_listener(listener.event);
|
self.edit_remove_event_listener(listener.event);
|
||||||
}
|
}
|
||||||
@@ -421,6 +395,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
dom_id.set(Some(real_id));
|
dom_id.set(Some(real_id));
|
||||||
|
|
||||||
let cur_scope = self.current_scope().unwrap();
|
let cur_scope = self.current_scope().unwrap();
|
||||||
|
|
||||||
listeners.iter().for_each(|listener| {
|
listeners.iter().for_each(|listener| {
|
||||||
self.fix_listener(listener);
|
self.fix_listener(listener);
|
||||||
listener.mounted_node.set(Some(real_id));
|
listener.mounted_node.set(Some(real_id));
|
||||||
@@ -464,7 +439,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VNodeKind::Component(vcomponent) => {
|
VNodeKind::Component(vcomponent) => {
|
||||||
log::debug!("Mounting a new component");
|
|
||||||
let caller = vcomponent.caller.clone();
|
let caller = vcomponent.caller.clone();
|
||||||
|
|
||||||
let parent_idx = self.scope_stack.last().unwrap().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
|
// Actually initialize the caller's slot with the right address
|
||||||
vcomponent.ass_scope.set(Some(new_idx));
|
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:
|
// TODO:
|
||||||
// Noderefs
|
// add noderefs to current noderef list Noderefs
|
||||||
// Effects
|
// add effects to current effect list Effects
|
||||||
|
|
||||||
let new_component = self.get_scope_mut(&new_idx).unwrap();
|
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
|
// failed to run. this is the first time the component ran, and it failed
|
||||||
// we manually set its head node to an empty fragment
|
// we manually set its head node to an empty fragment
|
||||||
panic!("failing components not yet implemented");
|
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
|
// Navigation
|
||||||
pub(crate) fn edit_push_root(&mut self, root: ElementId) {
|
pub(crate) fn edit_push_root(&mut self, root: ElementId) {
|
||||||
let id = root.as_u64();
|
let id = root.as_u64();
|
||||||
self.edits.push(PushRoot { id });
|
self.edits.edits.push(PushRoot { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn edit_pop(&mut self) {
|
pub(crate) fn edit_pop(&mut self) {
|
||||||
self.edits.push(PopRoot {});
|
self.edits.edits.push(PopRoot {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Nodes to the dom
|
// Add Nodes to the dom
|
||||||
// add m nodes from the stack
|
// add m nodes from the stack
|
||||||
pub(crate) fn edit_append_children(&mut self, many: u32) {
|
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
|
// 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
|
// 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) {
|
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) {
|
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) {
|
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
|
// Remove Nodesfrom the dom
|
||||||
pub(crate) fn edit_remove(&mut self) {
|
pub(crate) fn edit_remove(&mut self) {
|
||||||
self.edits.push(Remove);
|
self.edits.edits.push(Remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) {
|
pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
self.edits.push(CreateTextNode { text, id });
|
self.edits.edits.push(CreateTextNode { text, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn edit_create_element(
|
pub(crate) fn edit_create_element(
|
||||||
@@ -1413,15 +1392,15 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
) {
|
) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
match ns {
|
match ns {
|
||||||
Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }),
|
Some(ns) => self.edits.edits.push(CreateElementNs { id, ns, tag }),
|
||||||
None => self.edits.push(CreateElement { id, 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
|
// 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) {
|
pub(crate) fn edit_create_placeholder(&mut self, id: ElementId) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
self.edits.push(CreatePlaceholder { id });
|
self.edits.edits.push(CreatePlaceholder { id });
|
||||||
}
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
@@ -1434,7 +1413,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
|
|
||||||
let element_id = mounted_node.get().unwrap().as_u64();
|
let element_id = mounted_node.get().unwrap().as_u64();
|
||||||
|
|
||||||
self.edits.push(NewEventListener {
|
self.edits.edits.push(NewEventListener {
|
||||||
scope,
|
scope,
|
||||||
event_name: event,
|
event_name: event,
|
||||||
mounted_node_id: element_id,
|
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) {
|
pub(crate) fn edit_remove_event_listener(&mut self, event: &'static str) {
|
||||||
self.edits.push(RemoveEventListener { event });
|
self.edits.edits.push(RemoveEventListener { event });
|
||||||
}
|
}
|
||||||
|
|
||||||
// modify
|
// modify
|
||||||
pub(crate) fn edit_set_text(&mut self, text: &'bump str) {
|
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) {
|
pub(crate) fn edit_set_attribute(&mut self, attribute: &'bump Attribute) {
|
||||||
@@ -1461,7 +1440,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
// field: &'static str,
|
// field: &'static str,
|
||||||
// value: &'bump str,
|
// value: &'bump str,
|
||||||
// ns: Option<&'static str>,
|
// ns: Option<&'static str>,
|
||||||
self.edits.push(SetAttribute {
|
self.edits.edits.push(SetAttribute {
|
||||||
field: name,
|
field: name,
|
||||||
value,
|
value,
|
||||||
ns: *namespace,
|
ns: *namespace,
|
||||||
@@ -1484,7 +1463,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
// field: &'static str,
|
// field: &'static str,
|
||||||
// value: &'bump str,
|
// value: &'bump str,
|
||||||
// ns: Option<&'static str>,
|
// ns: Option<&'static str>,
|
||||||
self.edits.push(SetAttribute {
|
self.edits.edits.push(SetAttribute {
|
||||||
field: name,
|
field: name,
|
||||||
value,
|
value,
|
||||||
ns: Some(namespace),
|
ns: Some(namespace),
|
||||||
@@ -1493,7 +1472,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
|
|||||||
|
|
||||||
pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
|
pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
|
||||||
let name = attribute.name;
|
let name = attribute.name;
|
||||||
self.edits.push(RemoveAttribute { name });
|
self.edits.edits.push(RemoveAttribute { name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,21 +28,15 @@ pub struct EventTrigger {
|
|||||||
/// The priority of the event
|
/// The priority of the event
|
||||||
pub priority: EventPriority,
|
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 {
|
pub struct EventKey {
|
||||||
/// The originator of the event trigger
|
/// The originator of the event trigger
|
||||||
pub originator: ScopeId,
|
pub originator: ScopeId,
|
||||||
/// The priority of the event
|
/// The priority of the event
|
||||||
pub priority: EventPriority,
|
pub priority: EventPriority,
|
||||||
|
/// The height of the scope (used for ordering)
|
||||||
|
pub height: u32,
|
||||||
// TODO: add the time that the event was queued
|
// TODO: add the time that the event was queued
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,9 +111,6 @@ pub enum VirtualEvent {
|
|||||||
/// be processed.
|
/// be processed.
|
||||||
GarbageCollection,
|
GarbageCollection,
|
||||||
|
|
||||||
///
|
|
||||||
DiffComponent,
|
|
||||||
|
|
||||||
/// A type of "immediate" event scheduled by components
|
/// A type of "immediate" event scheduled by components
|
||||||
ScheduledUpdate {
|
ScheduledUpdate {
|
||||||
height: u32,
|
height: u32,
|
||||||
@@ -135,9 +126,11 @@ pub enum VirtualEvent {
|
|||||||
// This type exists for the task/concurrency system to signal that a task is ready.
|
// 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
|
// However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
|
||||||
// own re-run.
|
// 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
|
// they have the lowest priority
|
||||||
SuspenseEvent {
|
SuspenseEvent {
|
||||||
@@ -188,7 +181,6 @@ impl std::fmt::Debug for VirtualEvent {
|
|||||||
VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
|
VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
|
||||||
VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
|
VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
|
||||||
VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
|
VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
|
||||||
VirtualEvent::DiffComponent { .. } => "DiffComponent",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
f.debug_struct("VirtualEvent").field("type", &name).finish()
|
f.debug_struct("VirtualEvent").field("type", &name).finish()
|
||||||
|
|||||||
@@ -127,7 +127,9 @@ where
|
|||||||
*slot.as_ref().borrow_mut() = Some(output);
|
*slot.as_ref().borrow_mut() = Some(output);
|
||||||
updater(update_id);
|
updater(update_id);
|
||||||
EventTrigger {
|
EventTrigger {
|
||||||
event: VirtualEvent::AsyncEvent {},
|
event: VirtualEvent::AsyncEvent {
|
||||||
|
should_rerender: false,
|
||||||
|
},
|
||||||
originator,
|
originator,
|
||||||
priority: EventPriority::Low,
|
priority: EventPriority::Low,
|
||||||
real_node_id: None,
|
real_node_id: None,
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
|
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
|
||||||
EventTrigger, LazyNodes, NodeFactory, Properties, RealDom, ScopeId, SuspendedContext, VNode,
|
EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VNodeKind,
|
||||||
VNodeKind, VirtualDom, VirtualEvent, FC,
|
VirtualDom, VirtualEvent, FC,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
|
|||||||
+21
-14
@@ -152,15 +152,16 @@ pub struct VComponent<'src> {
|
|||||||
|
|
||||||
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
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 is_static: bool,
|
||||||
|
|
||||||
|
pub can_memoize: bool,
|
||||||
|
|
||||||
// a pointer into the bump arena (given by the 'src lifetime)
|
// a pointer into the bump arena (given by the 'src lifetime)
|
||||||
pub(crate) raw_props: *const (),
|
pub(crate) raw_props: *const (),
|
||||||
|
|
||||||
// a pointer to the raw fn typ
|
// a pointer to the raw fn typ
|
||||||
// pub(crate) user_fc: BumpB\,
|
|
||||||
pub(crate) user_fc: *const (),
|
pub(crate) user_fc: *const (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,20 +366,19 @@ impl<'a> NodeFactory<'a> {
|
|||||||
P: Properties + 'a,
|
P: Properties + 'a,
|
||||||
V: 'a + AsRef<[VNode<'a>]>,
|
V: 'a + AsRef<[VNode<'a>]>,
|
||||||
{
|
{
|
||||||
// TODO
|
let bump = self.bump();
|
||||||
// It's somewhat wrong to go about props like this
|
|
||||||
|
|
||||||
// We don't want the fat part of the fat pointer
|
// We don't want the fat part of the fat pointer
|
||||||
// This function does static dispatch so we don't need any VTable stuff
|
// 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 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 raw_props = props as *mut P as *mut ();
|
||||||
let user_fc = component as *const ();
|
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| {
|
move |other: &VComponent| {
|
||||||
if user_fc == other.user_fc {
|
if user_fc == other.user_fc {
|
||||||
// Safety
|
// Safety
|
||||||
@@ -404,13 +404,19 @@ impl<'a> NodeFactory<'a> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// create a closure to drop the props
|
// 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 {
|
move || unsafe {
|
||||||
let real_other = raw_props as *mut _ as *mut P;
|
if !has_dropped {
|
||||||
let b = BumpBox::from_raw(real_other);
|
let real_other = raw_props as *mut _ as *mut P;
|
||||||
std::mem::drop(b);
|
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();
|
let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
|
||||||
|
|
||||||
@@ -418,14 +424,15 @@ impl<'a> NodeFactory<'a> {
|
|||||||
|
|
||||||
VNode {
|
VNode {
|
||||||
key,
|
key,
|
||||||
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
kind: VNodeKind::Component(bump.alloc_with(|| VComponent {
|
||||||
user_fc,
|
user_fc,
|
||||||
comparator,
|
comparator,
|
||||||
raw_props,
|
raw_props,
|
||||||
children,
|
children,
|
||||||
caller: NodeFactory::create_component_caller(component, raw_props),
|
caller: NodeFactory::create_component_caller(component, raw_props),
|
||||||
is_static,
|
is_static,
|
||||||
drop_props,
|
drop_props: RefCell::new(Some(drop_props)),
|
||||||
|
can_memoize: P::IS_STATIC,
|
||||||
ass_scope: Cell::new(None),
|
ass_scope: Cell::new(None),
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-20
@@ -37,6 +37,7 @@ pub struct Scope {
|
|||||||
|
|
||||||
// Listeners
|
// Listeners
|
||||||
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
||||||
|
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||||
|
|
||||||
// State
|
// State
|
||||||
pub(crate) hooks: HookList,
|
pub(crate) hooks: HookList,
|
||||||
@@ -94,6 +95,7 @@ impl Scope {
|
|||||||
hooks: Default::default(),
|
hooks: Default::default(),
|
||||||
shared_contexts: Default::default(),
|
shared_contexts: Default::default(),
|
||||||
listeners: Default::default(),
|
listeners: Default::default(),
|
||||||
|
borrowed_props: Default::default(),
|
||||||
descendents: Default::default(),
|
descendents: Default::default(),
|
||||||
pending_garbage: Default::default(),
|
pending_garbage: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -114,27 +116,8 @@ impl Scope {
|
|||||||
// Cycle to the next frame and then reset it
|
// Cycle to the next frame and then reset it
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
// Remove all the outdated listeners
|
// 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");
|
self.ensure_drop_safety();
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Safety:
|
// Safety:
|
||||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
// - 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
|
// A safe wrapper around calling listeners
|
||||||
// calling listeners will invalidate the list of 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
|
// The listener list will be completely drained because the next frame will write over previous listeners
|
||||||
|
|||||||
@@ -26,16 +26,3 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
|
|||||||
// Some(self.cmp(other))
|
// 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!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+268
-124
@@ -29,7 +29,7 @@ use std::any::Any;
|
|||||||
|
|
||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
use std::cell::{Ref, RefCell, RefMut};
|
use std::cell::{Ref, RefCell, RefMut};
|
||||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
|
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
/// 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)
|
/// Should always be the first (gen=0, id=0)
|
||||||
pub base_scope: ScopeId,
|
pub base_scope: ScopeId,
|
||||||
|
|
||||||
|
active_fibers: Vec<Fiber<'static>>,
|
||||||
|
|
||||||
pending_events: BTreeMap<EventKey, EventTrigger>,
|
pending_events: BTreeMap<EventKey, EventTrigger>,
|
||||||
|
|
||||||
// for managing the props that were used to create the dom
|
// for managing the props that were used to create the dom
|
||||||
@@ -144,6 +146,7 @@ impl VirtualDom {
|
|||||||
base_scope,
|
base_scope,
|
||||||
_root_props: root_props,
|
_root_props: root_props,
|
||||||
shared: components,
|
shared: components,
|
||||||
|
active_fibers: Vec::new(),
|
||||||
pending_events: BTreeMap::new(),
|
pending_events: BTreeMap::new(),
|
||||||
_root_prop_type: TypeId::of::<P>(),
|
_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
|
/// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
|
||||||
/// through "run"
|
/// through "run"
|
||||||
///
|
///
|
||||||
pub fn rebuild<'s>(&'s mut self, edits: &'_ mut Vec<DomEdit<'s>>) -> Result<()> {
|
pub fn rebuild<'s>(&'s mut self) -> Result<Vec<DomEdit<'s>>> {
|
||||||
let mut diff_machine = DiffMachine::new(edits, self.base_scope, &self.shared);
|
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
|
let cur_component = diff_machine
|
||||||
.get_scope_mut(&self.base_scope)
|
.get_scope_mut(&self.base_scope)
|
||||||
@@ -214,7 +219,7 @@ impl VirtualDom {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn select_next_event(&mut self) -> Option<EventTrigger> {
|
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
|
// drain the in-flight events so that we can sort them out with the current events
|
||||||
while let Ok(Some(trigger)) = receiver.try_next() {
|
while let Ok(Some(trigger)) = receiver.try_next() {
|
||||||
log::info!("scooping event from receiver");
|
log::info!("retrieving event from receiver");
|
||||||
self.pending_events.insert(trigger.make_key(), trigger);
|
let key = self.shared.make_trigger_key(&trigger);
|
||||||
|
self.pending_events.insert(key, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.pending_events.is_empty() {
|
if self.pending_events.is_empty() {
|
||||||
@@ -242,83 +248,172 @@ impl VirtualDom {
|
|||||||
futures_util::future::Either::Right((trigger, _)) => trigger,
|
futures_util::future::Either::Right((trigger, _)) => trigger,
|
||||||
}
|
}
|
||||||
.unwrap();
|
.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();
|
Some(trigger)
|
||||||
// trigger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the cooperartive, fiber-based scheduler
|
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
|
||||||
// is "awaited" and will always return some edits for the real dom to apply
|
///
|
||||||
//
|
/// This method will not wait for any suspended tasks, completely skipping over
|
||||||
// Right now, we'll happily partially render component trees
|
pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
|
||||||
//
|
//
|
||||||
// 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();
|
|
||||||
|
|
||||||
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 {
|
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 {
|
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::ClipboardEvent(_)
|
||||||
VirtualEvent::GarbageCollection => {
|
| VirtualEvent::CompositionEvent(_)
|
||||||
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
| VirtualEvent::KeyboardEvent(_)
|
||||||
|
| VirtualEvent::FocusEvent(_)
|
||||||
let mut garbage_list = scope.consume_garbage();
|
| VirtualEvent::FormEvent(_)
|
||||||
|
| VirtualEvent::SelectionEvent(_)
|
||||||
let mut scopes_to_kill = Vec::new();
|
| VirtualEvent::TouchEvent(_)
|
||||||
while let Some(node) = garbage_list.pop() {
|
| VirtualEvent::UIEvent(_)
|
||||||
match &node.kind {
|
| VirtualEvent::WheelEvent(_)
|
||||||
VNodeKind::Text(_) => {
|
| VirtualEvent::MediaEvent(_)
|
||||||
self.shared.collect_garbage(node.direct_id());
|
| VirtualEvent::AnimationEvent(_)
|
||||||
}
|
| VirtualEvent::TransitionEvent(_)
|
||||||
VNodeKind::Anchor(_) => {
|
| VirtualEvent::ToggleEvent(_)
|
||||||
self.shared.collect_garbage(node.direct_id());
|
| VirtualEvent::MouseEvent(_)
|
||||||
}
|
| VirtualEvent::PointerEvent(_) => {
|
||||||
VNodeKind::Suspended(_) => {
|
let scope_id = &trigger.originator;
|
||||||
self.shared.collect_garbage(node.direct_id());
|
let scope = unsafe { self.shared.get_scope_mut(*scope_id) };
|
||||||
}
|
match scope {
|
||||||
|
Some(scope) => {
|
||||||
VNodeKind::Element(el) => {
|
scope.call_listener(trigger)?;
|
||||||
self.shared.collect_garbage(node.direct_id());
|
}
|
||||||
for child in el.children {
|
None => {
|
||||||
garbage_list.push(child);
|
log::warn!("No scope found for event: {:#?}", scope_id);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// Run the component
|
||||||
VirtualEvent::ScheduledUpdate { height: u32 } => {
|
VirtualEvent::ScheduledUpdate { height: u32 } => {
|
||||||
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
||||||
|
|
||||||
match scope.run_scope() {
|
match scope.run_scope() {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let event = VirtualEvent::DiffComponent;
|
todo!();
|
||||||
let trigger = EventTrigger {
|
// let event = VirtualEvent::DiffComponent;
|
||||||
event,
|
// let trigger = EventTrigger {
|
||||||
originator: trigger.originator,
|
// event,
|
||||||
priority: EventPriority::High,
|
// originator: trigger.originator,
|
||||||
real_node_id: None,
|
// priority: EventPriority::High,
|
||||||
};
|
// real_node_id: None,
|
||||||
self.shared.task_sender.unbounded_send(trigger);
|
// };
|
||||||
|
// self.shared.task_sender.unbounded_send(trigger);
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
log::error!("failed to run this component!");
|
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);
|
// if realdom.must_commit() {
|
||||||
realdom.wait_until_ready().await;
|
// // 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> {
|
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
|
||||||
todo!()
|
self.shared.task_sender.clone()
|
||||||
// std::rc::Rc::new(move |_| {
|
|
||||||
// //
|
|
||||||
// })
|
|
||||||
// todo()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,8 +553,38 @@ impl VirtualDom {
|
|||||||
unsafe impl Sync for VirtualDom {}
|
unsafe impl Sync for VirtualDom {}
|
||||||
unsafe impl Send for VirtualDom {}
|
unsafe impl Send for VirtualDom {}
|
||||||
|
|
||||||
fn select_next_event(
|
struct Fiber<'a> {
|
||||||
pending_events: &mut BTreeMap<EventKey, EventTrigger>,
|
trigger: EventTrigger,
|
||||||
) -> Option<EventTrigger> {
|
|
||||||
None
|
// 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>>,
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@ use dioxus::{
|
|||||||
arena::SharedResources,
|
arena::SharedResources,
|
||||||
diff::{CreateMeta, DiffMachine},
|
diff::{CreateMeta, DiffMachine},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
util::DebugDom,
|
|
||||||
DomEdit,
|
DomEdit,
|
||||||
};
|
};
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
+17
-17
@@ -88,26 +88,26 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
|||||||
|
|
||||||
let channel = vir.get_event_sender();
|
let channel = vir.get_event_sender();
|
||||||
struct WebviewBridge {}
|
struct WebviewBridge {}
|
||||||
impl RealDom for WebviewBridge {
|
// impl RealDom for WebviewBridge {
|
||||||
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
// fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
||||||
todo!()
|
// todo!()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn must_commit(&self) -> bool {
|
// fn must_commit(&self) -> bool {
|
||||||
false
|
// 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>(
|
// fn wait_until_ready<'s>(
|
||||||
&'s mut self,
|
// &'s mut self,
|
||||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
|
// ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
|
||||||
//
|
// //
|
||||||
Box::pin(async {
|
// Box::pin(async {
|
||||||
//
|
// //
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
let mut real_dom = WebviewBridge {};
|
let mut real_dom = WebviewBridge {};
|
||||||
// async_std::task::spawn_local(vir.run(&mut real_dom));
|
// async_std::task::spawn_local(vir.run(&mut real_dom));
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ pub async fn run_with_props<T: Properties + 'static>(
|
|||||||
Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
|
Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
|
||||||
);
|
);
|
||||||
|
|
||||||
dom.run(&mut websys_dom).await?;
|
dom.run_with_deadline(&mut websys_dom).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user