Feat: event loop
This commit is contained in:
1 parent
8295ac4b3d
commit
ea2aa4b0c9
39 files changed
+495
-287
No files matched your search
@@ -2,7 +2,7 @@
|
||||
//! for components to be used within Nodes.
|
||||
//!
|
||||
|
||||
use crate::inner::*;
|
||||
use crate::innerlude::*;
|
||||
|
||||
/// The `Component` trait refers to any struct or funciton that can be used as a component
|
||||
/// We automatically implement Component for FC<T>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
use crate::{inner::Scope, nodes::VNode};
|
||||
use crate::{innerlude::Scope, nodes::VNode};
|
||||
use bumpalo::Bump;
|
||||
use hooks::Hook;
|
||||
use std::{
|
||||
|
||||
+33
-12
@@ -1,15 +1,4 @@
|
||||
use crate::prelude::VNode;
|
||||
|
||||
// use crate::{
|
||||
// cached_set::{CacheId, CachedSet},
|
||||
// change_list::ChangeListBuilder,
|
||||
// events::EventsRegistry,
|
||||
// node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
|
||||
// };
|
||||
// use fxhash::{FxHashMap, FxHashSet};
|
||||
// use std::cmp::Ordering;
|
||||
// use std::u32;
|
||||
// use wasm_bindgen::UnwrapThrowExt;
|
||||
use crate::innerlude::{VNode, VText};
|
||||
|
||||
// Diff the `old` node with the `new` node. Emits instructions to modify a
|
||||
// physical DOM node that reflects `old` into something that reflects `new`.
|
||||
@@ -28,6 +17,38 @@ pub(crate) fn diff(
|
||||
new: &VNode,
|
||||
// cached_roots: &mut FxHashSet<CacheId>,
|
||||
) {
|
||||
match (old, new) {
|
||||
// This case occurs when we generate two text nodes that are the sa
|
||||
(VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
|
||||
//
|
||||
if old_text != new_text {}
|
||||
}
|
||||
|
||||
// compare elements
|
||||
// if different, schedule different types of update
|
||||
(VNode::Element(_), VNode::Element(_)) => {}
|
||||
(VNode::Element(_), VNode::Text(_)) => {}
|
||||
(VNode::Element(_), VNode::Component(_)) => {}
|
||||
(VNode::Text(_), VNode::Element(_)) => {}
|
||||
(VNode::Text(_), VNode::Component(_)) => {}
|
||||
|
||||
(VNode::Component(_), VNode::Element(_)) => {}
|
||||
(VNode::Component(_), VNode::Text(_)) => {}
|
||||
|
||||
// No immediate change to dom. If props changed though, queue a "props changed" update
|
||||
// However, mark these for a
|
||||
(VNode::Component(_), VNode::Component(_)) => {}
|
||||
//
|
||||
(VNode::Suspended, _) | (_, VNode::Suspended) => {
|
||||
todo!("Suspended components not currently available")
|
||||
} // (VNode::Element(_), VNode::Suspended) => {}
|
||||
// (VNode::Text(_), VNode::Suspended) => {}
|
||||
// (VNode::Component(_), VNode::Suspended) => {}
|
||||
// (VNode::Suspended, VNode::Element(_)) => {}
|
||||
// (VNode::Suspended, VNode::Text(_)) => {}
|
||||
// (VNode::Suspended, VNode::Suspended) => {}
|
||||
// (VNode::Suspended, VNode::Component(_)) => {}
|
||||
}
|
||||
todo!()
|
||||
// match (&new.kind, &old.kind) {
|
||||
// (
|
||||
|
||||
+23
-24
@@ -7,38 +7,37 @@ pub enum Error {
|
||||
#[error("No event to progress")]
|
||||
NoEvent,
|
||||
|
||||
#[error("Out of compute credits")]
|
||||
OutOfCredits,
|
||||
// #[error("Out of compute credits")]
|
||||
// OutOfCredits,
|
||||
|
||||
/// Used when errors need to propogate but are too unique to be typed
|
||||
#[error("{0}")]
|
||||
Unique(String),
|
||||
// /// Used when errors need to propogate but are too unique to be typed
|
||||
// #[error("{0}")]
|
||||
// Unique(String),
|
||||
|
||||
#[error("GraphQL error: {0}")]
|
||||
GraphQL(String),
|
||||
// #[error("GraphQL error: {0}")]
|
||||
// GraphQL(String),
|
||||
|
||||
// TODO(haze): Remove, or make a better error. This is pretty much useless
|
||||
#[error("GraphQL response mismatch. Got {found} but expected {expected}")]
|
||||
GraphQLMisMatch {
|
||||
expected: &'static str,
|
||||
found: String,
|
||||
},
|
||||
// // TODO(haze): Remove, or make a better error. This is pretty much useless
|
||||
// #[error("GraphQL response mismatch. Got {found} but expected {expected}")]
|
||||
// GraphQLMisMatch {
|
||||
// expected: &'static str,
|
||||
// found: String,
|
||||
// },
|
||||
|
||||
#[error("Difference detected in SystemTime! {0}")]
|
||||
SystemTime(#[from] std::time::SystemTimeError),
|
||||
// #[error("Difference detected in SystemTime! {0}")]
|
||||
// SystemTime(#[from] std::time::SystemTimeError),
|
||||
|
||||
#[error("Failed to parse Integer")]
|
||||
ParseInt(#[from] std::num::ParseIntError),
|
||||
// #[error("Failed to parse Integer")]
|
||||
// ParseInt(#[from] std::num::ParseIntError),
|
||||
|
||||
#[error("")]
|
||||
MissingAuthentication,
|
||||
// #[error("")]
|
||||
// MissingAuthentication,
|
||||
|
||||
#[error("Failed to create experiment run: {0}")]
|
||||
FailedToCreateExperimentRun(String),
|
||||
|
||||
#[error("Could not find shared behavior with ID {0}")]
|
||||
MissingSharedBehavior(String),
|
||||
// #[error("Failed to create experiment run: {0}")]
|
||||
// FailedToCreateExperimentRun(String),
|
||||
|
||||
// #[error("Could not find shared behavior with ID {0}")]
|
||||
// MissingSharedBehavior(String),
|
||||
#[error("I/O Error: {0}")]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
@@ -4,6 +4,19 @@
|
||||
//! 3rd party renderers are responsible for forming this virtual events from events
|
||||
//!
|
||||
//! The goal here is to provide a consistent event interface across all renderer types
|
||||
use generational_arena::Index;
|
||||
|
||||
pub struct EventTrigger {
|
||||
pub component_id: Index,
|
||||
pub listener_id: u32,
|
||||
pub event: VirtualEvent,
|
||||
}
|
||||
|
||||
impl EventTrigger {
|
||||
pub fn new() -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum VirtualEvent {
|
||||
ClipboardEvent,
|
||||
|
||||
@@ -9,7 +9,7 @@ pub use use_ref_def::use_ref;
|
||||
pub use use_state_def::use_state;
|
||||
|
||||
mod use_state_def {
|
||||
use crate::inner::*;
|
||||
use crate::innerlude::*;
|
||||
use std::{cell::RefCell, ops::DerefMut, rc::Rc};
|
||||
|
||||
struct UseState<T: 'static> {
|
||||
@@ -80,7 +80,7 @@ mod use_state_def {
|
||||
}
|
||||
|
||||
mod use_ref_def {
|
||||
use crate::inner::*;
|
||||
use crate::innerlude::*;
|
||||
use std::{cell::RefCell, ops::DerefMut};
|
||||
|
||||
pub struct UseRef<T: 'static> {
|
||||
|
||||
@@ -83,7 +83,7 @@ pub mod builder {
|
||||
}
|
||||
|
||||
// types used internally that are important
|
||||
pub(crate) mod inner {
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::component::{Component, Properties};
|
||||
use crate::context::hooks::Hook;
|
||||
pub use crate::context::Context;
|
||||
@@ -121,7 +121,7 @@ pub mod prelude {
|
||||
|
||||
// pub use nodes::iterables::IterableNodes;
|
||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||
pub use crate::inner::FC;
|
||||
pub use crate::innerlude::FC;
|
||||
|
||||
// TODO @Jon, fix this
|
||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||
|
||||
@@ -1071,11 +1071,9 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
|
||||
/// ```
|
||||
pub fn on<'a, 'b, F: 'static>(
|
||||
bump: &'a Bump,
|
||||
event: &'a str,
|
||||
callback: impl Fn(()) + 'static,
|
||||
) -> Listener<'a>
|
||||
// F: Fn(()) + 'b,
|
||||
{
|
||||
event: &'static str,
|
||||
callback: impl Fn(()) + 'a,
|
||||
) -> Listener<'a> {
|
||||
Listener {
|
||||
event,
|
||||
callback: bump.alloc(callback),
|
||||
|
||||
@@ -174,11 +174,11 @@ mod velement {
|
||||
}
|
||||
|
||||
/// An event listener.
|
||||
pub struct Listener<'a> {
|
||||
pub struct Listener<'bump> {
|
||||
/// The type of event to listen for.
|
||||
pub(crate) event: &'a str,
|
||||
pub(crate) event: &'static str,
|
||||
/// The callback to invoke when the event happens.
|
||||
pub(crate) callback: &'a (dyn Fn(())),
|
||||
pub(crate) callback: &'bump (dyn Fn(())),
|
||||
}
|
||||
|
||||
/// The key for keyed children.
|
||||
@@ -226,8 +226,8 @@ mod velement {
|
||||
|
||||
mod vtext {
|
||||
#[derive(PartialEq)]
|
||||
pub struct VText<'a> {
|
||||
pub text: &'a str,
|
||||
pub struct VText<'bump> {
|
||||
pub text: &'bump str,
|
||||
}
|
||||
|
||||
impl<'a> VText<'a> {
|
||||
@@ -245,7 +245,7 @@ mod vtext {
|
||||
/// Virtual Components for custom user-defined components
|
||||
/// Only supports the functional syntax
|
||||
mod vcomponent {
|
||||
use crate::inner::{Properties, FC};
|
||||
use crate::innerlude::{Properties, FC};
|
||||
use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
|
||||
|
||||
use super::VNode;
|
||||
|
||||
+17
-13
@@ -1,4 +1,4 @@
|
||||
use crate::inner::*;
|
||||
use crate::innerlude::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::{context::hooks::Hook, diff::diff};
|
||||
use bumpalo::Bump;
|
||||
@@ -19,26 +19,26 @@ pub struct Scope {
|
||||
// These hooks are actually references into the hook arena
|
||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||
// could also use ourborous
|
||||
hooks: RefCell<Vec<*mut Hook>>,
|
||||
hook_arena: typed_arena::Arena<Hook>,
|
||||
pub hooks: RefCell<Vec<*mut Hook>>,
|
||||
pub hook_arena: typed_arena::Arena<Hook>,
|
||||
|
||||
// Map to the parent
|
||||
parent: Option<Index>,
|
||||
pub parent: Option<Index>,
|
||||
|
||||
// todo, do better with the active frame stuff
|
||||
frames: [Bump; 2],
|
||||
pub frames: [Bump; 2],
|
||||
|
||||
// somehow build this vnode with a lifetime tied to self
|
||||
cur_node: *mut VNode<'static>,
|
||||
pub cur_node: *mut VNode<'static>,
|
||||
|
||||
active_frame: u8,
|
||||
pub active_frame: u8,
|
||||
|
||||
// IE Which listeners need to be woken up?
|
||||
listeners: Vec<Box<dyn Fn()>>,
|
||||
pub listeners: Vec<Box<dyn Fn()>>,
|
||||
|
||||
//
|
||||
props_type: TypeId,
|
||||
caller: *const i32,
|
||||
pub props_type: TypeId,
|
||||
pub caller: *const i32,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
@@ -76,7 +76,7 @@ impl Scope {
|
||||
/// Create a new context and run the component with references from the Virtual Dom
|
||||
/// This function downcasts the function pointer based on the stored props_type
|
||||
///
|
||||
/// Props is ?Sized because we borrow the props
|
||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
||||
pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
|
||||
let bump = &self.frames[0];
|
||||
|
||||
@@ -96,7 +96,7 @@ impl Scope {
|
||||
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
||||
we transmute the function back using the props as reference.
|
||||
|
||||
we could do a better check to make sure that the TypeID is correct
|
||||
we could do a better check to make sure that the TypeID is correct before casting
|
||||
--
|
||||
This is safe because we check that the generic type matches before casting.
|
||||
*/
|
||||
@@ -104,7 +104,11 @@ impl Scope {
|
||||
let new_nodes = caller(ctx, props);
|
||||
let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node };
|
||||
|
||||
// perform the diff, dumping into the change list
|
||||
// TODO: Iterate through the new nodes
|
||||
// move any listeners into ourself
|
||||
|
||||
// perform the diff, dumping into the mutable change list
|
||||
// this doesnt perform any "diff compression" where an event and a re-render
|
||||
crate::diff::diff(old_nodes, &new_nodes);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,9 @@ mod validation {
|
||||
// look them up by their unique id.
|
||||
// When the DomUpdater sees that the element no longer exists it will drop all of it's
|
||||
// Rc'd Closures for those events.
|
||||
// It doesn't quite make sense to keep this here, perhaps just in the html crate?
|
||||
// Dioxus itself shouldn't be concerned with the attribute names
|
||||
// a ftk!
|
||||
static SELF_CLOSING_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
[
|
||||
"area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command",
|
||||
|
||||
@@ -1,29 +1,5 @@
|
||||
/*
|
||||
The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
|
||||
|
||||
The Dioxus VDom uses the same underlying mechanics as Dodrio (double buffering, bump dom, etc).
|
||||
Instead of making the allocator very obvious, we choose to parametrize over the DomTree trait. For our purposes,
|
||||
the DomTree trait is simply an abstraction over a lazy dom builder, much like the iterator trait.
|
||||
|
||||
This means we can accept DomTree anywhere as well as return it. All components therefore look like this:
|
||||
```ignore
|
||||
function Component(ctx: Context<()>) -> VNode {
|
||||
ctx.view(html! {<div> "hello world" </div>})
|
||||
}
|
||||
```
|
||||
It's not quite as sexy as statics, but there's only so much you can do. The goal is to get statics working with the FC macro,
|
||||
so types don't get in the way of you and your component writing. Fortunately, this is all generic enough to be split out
|
||||
into its own lib (IE, lazy loading wasm chunks by function (exciting stuff!))
|
||||
|
||||
```ignore
|
||||
#[fc] // gets translated into a function.
|
||||
static Component: FC = |ctx| {
|
||||
ctx.view(html! {<div> "hello world" </div>})
|
||||
}
|
||||
```
|
||||
*/
|
||||
use crate::inner::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::{events::EventTrigger, innerlude::*};
|
||||
use any::Any;
|
||||
use bumpalo::Bump;
|
||||
use generational_arena::{Arena, Index};
|
||||
@@ -46,13 +22,12 @@ pub struct VirtualDom<P: Properties> {
|
||||
base_scope: Index,
|
||||
|
||||
/// Components generate lifecycle events
|
||||
event_queue: Vec<LifecycleEvent>,
|
||||
event_queue: Vec<LifecycleEvent>, // wrap in a lock or something so
|
||||
|
||||
// mark the root props with P, even though they're held by the root component
|
||||
_p: PhantomData<P>,
|
||||
}
|
||||
|
||||
/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
|
||||
impl VirtualDom<()> {
|
||||
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
|
||||
///
|
||||
@@ -63,7 +38,6 @@ impl VirtualDom<()> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the VirtualDom for any Properties
|
||||
impl<P: Properties + 'static> VirtualDom<P> {
|
||||
/// Start a new VirtualDom instance with a dependent props.
|
||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||
@@ -97,15 +71,69 @@ impl<P: Properties + 'static> VirtualDom<P> {
|
||||
}
|
||||
}
|
||||
|
||||
/// With access to the virtual dom, schedule an update to the Root component's props
|
||||
pub fn update_props(&mut self, new_props: P) {
|
||||
self.event_queue.push(LifecycleEvent {
|
||||
event_type: LifecycleType::PropsChanged {
|
||||
props: Box::new(new_props),
|
||||
},
|
||||
index: self.base_scope,
|
||||
});
|
||||
}
|
||||
|
||||
/// Pop an event off the event queue and process it
|
||||
/// Update the root props, and progress
|
||||
/// Takes a bump arena to allocate into, making the diff phase as fast as possible
|
||||
///
|
||||
pub fn progress(&mut self) -> Result<()> {
|
||||
let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
|
||||
|
||||
process_event(self, event)
|
||||
}
|
||||
|
||||
/// Update the root props, causing a full event cycle
|
||||
pub fn update_props(&mut self, new_props: P) {}
|
||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||
///
|
||||
/// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
|
||||
/// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
|
||||
/// change list.
|
||||
///
|
||||
/// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
|
||||
/// listeners.
|
||||
///
|
||||
/// ```ignore
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
/// ```
|
||||
pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
|
||||
let EventTrigger {
|
||||
component_id,
|
||||
listener_id,
|
||||
event,
|
||||
} = evt;
|
||||
|
||||
let component = self
|
||||
.components
|
||||
.get(component_id)
|
||||
// todo: update this with a dedicated error type so implementors know what went wrong
|
||||
.expect("Component should exist if an event was triggered");
|
||||
|
||||
let listener = component
|
||||
.listeners
|
||||
.get(listener_id as usize)
|
||||
.expect("Listener should exist if it was triggered")
|
||||
.as_ref();
|
||||
|
||||
// Run the callback
|
||||
// Theoretically, this should
|
||||
listener();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn progress_completely(&mut self) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
|
||||
@@ -159,6 +187,9 @@ fn process_event<P: Properties>(
|
||||
// Event from renderer was fired with a given listener ID
|
||||
//
|
||||
LifecycleType::Callback { listener_id } => {}
|
||||
|
||||
// Run any post-render callbacks on a component
|
||||
LifecycleType::Rendered => {}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -182,6 +213,7 @@ pub enum LifecycleType {
|
||||
PropsChanged {
|
||||
props: Box<dyn Properties>,
|
||||
},
|
||||
Rendered,
|
||||
Mounted,
|
||||
Removed,
|
||||
Messaged,
|
||||
|
||||
Reference in new issue
Block a user