Feat: event loop

This commit is contained in:
Jonathan Kelley committed 2021-02-12 16:11:33 -05:00
1 parent 8295ac4b3d
commit ea2aa4b0c9
39 files changed
+495 -287

No files matched your search

+1 -1
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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),
}
+13
View File
@@ -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,
+2 -2
View File
@@ -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> {
+2 -2
View File
@@ -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
+3 -5
View File
@@ -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),
+6 -6
View File
@@ -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
View File
@@ -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);
}
}
+3
View File
@@ -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",
+63 -31
View File
@@ -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,