mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
feat: mutations
This commit is contained in:
parent
f2334c17be
commit
fac42339c2
17 changed files with 464 additions and 275 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ impl PartialEq for ChildProps {
|
|||
}
|
||||
impl Properties for ChildProps {
|
||||
type Builder = ();
|
||||
const IS_STATIC: bool = true;
|
||||
fn builder() -> Self::Builder {
|
||||
()
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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),
|
||||
})),
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>>,
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ use dioxus::{
|
|||
arena::SharedResources,
|
||||
diff::{CreateMeta, DiffMachine},
|
||||
prelude::*,
|
||||
util::DebugDom,
|
||||
DomEdit,
|
||||
};
|
||||
use dioxus_core as dioxus;
|
||||
|
|
32
packages/core/tests/eventsystem.rs
Normal file
32
packages/core/tests/eventsystem.rs
Normal 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) => {
|
||||
//
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue