Feat: major overhaul, wire up event system

This commit is contained in:
Jonathan Kelley committed 2021-02-12 03:07:35 -05:00
1 parent f47651b32a
commit 8295ac4b3d
20 files changed
+1418 -188

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }
+12
View File
@@ -245,3 +245,15 @@ A few notes:
## Concurrency ## Concurrency
I don't even know yet I don't even know yet
## Execution Model
+3
View File
@@ -0,0 +1,3 @@
# The Server-Side-Rendering Guide
This guide will help you build your first app that leverages server-side-rendering to display the user interface.
+3
View File
@@ -0,0 +1,3 @@
# The WASM Guide
This guide will help you build your first app that leverages WASM in the browser to display the user interface.
+3
View File
@@ -0,0 +1,3 @@
# The Desktop Guide
This guide will help you build your first app that leverages webview for desktop to display the user interface.
+1 -21
View File
@@ -55,28 +55,11 @@ impl<T: Properties> Comp for FC<T> {
} }
} }
// impl<T: Properties, F: Fn(&Context<T>) -> VNode> Comp for F {
// type Props = T;
// fn render(&self, ctx: &mut Context<T>) -> VNode {
// let g = self(ctx);
// g
// }
// fn builder(&self) -> T {
// T::new()
// }
// }
impl Properties for () {
fn new() -> Self {
()
}
}
#[allow(unused, non_upper_case_globals)] #[allow(unused, non_upper_case_globals)]
static MyComp: FC<()> = |ctx| { static MyComp: FC<()> = |ctx| {
html! { html! {
<div> <div>
<p> "hello world" </p>
</div> </div>
} }
}; };
@@ -89,7 +72,4 @@ fn test() {
let mut ctx = Context { props: &() }; let mut ctx = Context { props: &() };
let f = MyComp.render(&mut ctx); let f = MyComp.render(&mut ctx);
let props = MyComp.builder(); let props = MyComp.builder();
// let f = my_comp.render(&mut ctx);
// let props = my_comp.builder();
} }
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }
+1
View File
@@ -28,3 +28,4 @@ owning_ref = "0.4.1"
typed-arena = "2.0.1" typed-arena = "2.0.1"
toolshed = "0.8.1" toolshed = "0.8.1"
id-arena = "2.2.1" id-arena = "2.2.1"
thiserror = "1.0.23"
+8 -15
View File
@@ -12,12 +12,14 @@ static Header: FC<()> = |ctx, props| {
let handler1 = move || println!("Value is {}", inner.current()); let handler1 = move || println!("Value is {}", inner.current());
ctx.view(html! { ctx.view(|bump| {
<div> builder::div(bump)
<h1> "This is the header bar" </h1> .child(VNode::Component(VComponent::new(
<h1> "Idnt it awesome" </h1> Bottom,
<button onclick={move |_| handler1()}> "Click me" </button> //
</div> (),
)))
.finish()
}) })
}; };
@@ -29,12 +31,3 @@ static Bottom: FC<()> = |ctx, props| {
</div> </div>
}) })
}; };
static Example: FC<()> = |ctx, props| {
ctx.view(html! {
<div>
<h1> "BROSKI!" </h1>
<h1> "DRO!" </h1>
</div>
})
};
+1 -1
View File
@@ -11,7 +11,7 @@ fn main() -> Result<(), ()> {
let p1 = Props { name: "bob".into() }; let p1 = Props { name: "bob".into() };
let mut vdom = VirtualDom::new_with_props(Example, p1); let mut vdom = VirtualDom::new_with_props(Example, p1);
vdom.progress()?; // vdom.progress()?;
Ok(()) Ok(())
} }
+3 -1
View File
@@ -23,7 +23,9 @@ impl<P: Properties> Component for FC<P> {
/// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. /// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields.
/// Components take a "properties" object /// Components take a "properties" object
pub trait Properties { pub trait Properties // where
// Self: Sized,
{
fn call(&self, ptr: *const ()) {} fn call(&self, ptr: *const ()) {}
} }
+8 -10
View File
@@ -3,7 +3,8 @@ use crate::{inner::Scope, nodes::VNode};
use bumpalo::Bump; use bumpalo::Bump;
use hooks::Hook; use hooks::Hook;
use std::{ use std::{
any::TypeId, cell::RefCell, future::Future, marker::PhantomData, sync::atomic::AtomicUsize, any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
sync::atomic::AtomicUsize,
}; };
/// Components in Dioxus use the "Context" object to interact with their lifecycle. /// Components in Dioxus use the "Context" object to interact with their lifecycle.
@@ -27,15 +28,17 @@ use std::{
// todo: force lifetime of source into T as a valid lifetime too // todo: force lifetime of source into T as a valid lifetime too
// it's definitely possible, just needs some more messing around // it's definitely possible, just needs some more messing around
pub struct Context<'src> { pub struct Context<'src> {
pub idx: AtomicUsize,
pub(crate) scope: &'src Scope, pub(crate) scope: &'src Scope,
/// Direct access to the properties used to create this component. /// Direct access to the properties used to create this component.
// pub props: &'src PropType, // pub props: &'src PropType,
pub idx: AtomicUsize,
// Borrowed from scope // Borrowed from scope
pub(crate) arena: &'src typed_arena::Arena<Hook>, pub(crate) arena: &'src typed_arena::Arena<Hook>,
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>, pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
pub(crate) components: &'src generational_arena::Arena<Scope>, pub(crate) bump: &'src Bump,
// pub(crate) components: &'src generational_arena::Arena<Scope>,
// holder for the src lifetime // holder for the src lifetime
// todo @jon remove this // todo @jon remove this
@@ -49,11 +52,6 @@ impl<'a> Context<'a> {
todo!("Children API not yet implemented for component Context") todo!("Children API not yet implemented for component Context")
} }
/// Access a parent context
pub fn parent_context<C>(&self) -> C {
todo!("Context API is not ready yet")
}
/// Create a subscription that schedules a future render for the reference component /// Create a subscription that schedules a future render for the reference component
pub fn schedule_update(&self) -> impl Fn() -> () { pub fn schedule_update(&self) -> impl Fn() -> () {
todo!("Subscription API is not ready yet"); todo!("Subscription API is not ready yet");
@@ -75,8 +73,8 @@ impl<'a> Context<'a> {
/// ctx.view(lazy_tree) /// ctx.view(lazy_tree)
/// } /// }
///``` ///```
pub fn view(self, v: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> { pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
todo!() lazy_nodes(self.bump.borrow())
} }
pub fn callback(&self, f: impl Fn(()) + 'static) {} pub fn callback(&self, f: impl Fn(()) + 'static) {}
+1 -1
View File
@@ -33,7 +33,7 @@ mod tests {
ctx.view(html! { <div>"hello world" </div> }) ctx.view(html! { <div>"hello world" </div> })
}); });
dom.progress()?; // dom.progress()?;
Ok(()) Ok(())
} }
} }
File diff suppressed because it is too large. Load diff
+44
View File
@@ -0,0 +1,44 @@
use thiserror::Error as ThisError;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("No event to progress")]
NoEvent,
#[error("Out of compute credits")]
OutOfCredits,
/// Used when errors need to propogate but are too unique to be typed
#[error("{0}")]
Unique(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,
},
#[error("Difference detected in SystemTime! {0}")]
SystemTime(#[from] std::time::SystemTimeError),
#[error("Failed to parse Integer")]
ParseInt(#[from] std::num::ParseIntError),
#[error("")]
MissingAuthentication,
#[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),
}
+6 -4
View File
@@ -1,14 +1,16 @@
//! Useful, foundational hooks that 3rd parties can implement. //! Useful, foundational hooks that 3rd parties can implement.
//! Currently implemented: //! Currently implemented:
//! - use_ref //! - [x] use_ref
//! - use_state //! - [x] use_state
//! - [ ] use_reducer
//! - [ ] use_effect
pub use use_ref_def::use_ref; pub use use_ref_def::use_ref;
pub use use_state_def::use_state; pub use use_state_def::use_state;
mod use_state_def { mod use_state_def {
use crate::inner::*; use crate::inner::*;
use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; use std::{cell::RefCell, ops::DerefMut, rc::Rc};
struct UseState<T: 'static> { struct UseState<T: 'static> {
new_val: Rc<RefCell<Option<T>>>, new_val: Rc<RefCell<Option<T>>>,
@@ -79,7 +81,7 @@ mod use_state_def {
mod use_ref_def { mod use_ref_def {
use crate::inner::*; use crate::inner::*;
use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; use std::{cell::RefCell, ops::DerefMut};
pub struct UseRef<T: 'static> { pub struct UseRef<T: 'static> {
_current: RefCell<T>, _current: RefCell<T>,
+3
View File
@@ -68,6 +68,8 @@
pub mod component; pub mod component;
pub mod context; pub mod context;
pub mod debug_renderer; pub mod debug_renderer;
pub mod diff;
pub mod error;
pub mod events; pub mod events;
pub mod hooks; pub mod hooks;
pub mod nodebuilder; pub mod nodebuilder;
@@ -85,6 +87,7 @@ pub(crate) mod inner {
pub use crate::component::{Component, Properties}; pub use crate::component::{Component, Properties};
use crate::context::hooks::Hook; use crate::context::hooks::Hook;
pub use crate::context::Context; pub use crate::context::Context;
pub use crate::error::{Error, Result};
use crate::nodes; use crate::nodes;
pub use crate::scope::Scope; pub use crate::scope::Scope;
pub use crate::virtual_dom::VirtualDom; pub use crate::virtual_dom::VirtualDom;
+18 -33
View File
@@ -232,56 +232,41 @@ mod vtext {
impl<'a> VText<'a> { impl<'a> VText<'a> {
// / Create an new `VText` instance with the specified text. // / Create an new `VText` instance with the specified text.
// pub fn new<S>(text: S) -> Self pub fn new(text: &'a str) -> Self
// pub fn new<S>(text: S) -> Self
// where // where
// S: Into<String>, // S: Into<str>,
// { {
// VText { text: text.into() } VText { text: text.into() }
// } }
} }
} }
/// Virtual Components for custom user-defined components /// Virtual Components for custom user-defined components
/// Only supports the functional syntax /// Only supports the functional syntax
mod vcomponent { mod vcomponent {
use crate::prelude::Properties; use crate::inner::{Properties, FC};
use std::{any::TypeId, fmt, future::Future, marker::PhantomData}; use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
use super::VNode; use super::VNode;
pub struct VComponent<'src> { pub struct VComponent<'src> {
_p: PhantomData<&'src ()>, _p: PhantomData<&'src ()>,
runner: Box<dyn Properties + 'src>, props: Box<dyn Properties>,
// props: Box<dyn Properties + 'src>,
caller: *const (), caller: *const (),
props_type: TypeId,
} }
// impl<'src> PartialEq for CallerSource<'src> {
// fn eq(&self, other: &Self) -> bool {
// todo!()
// }
// }
// caller: Box<dyn Fn
// should we box the caller?
// probably, so we can call it again
//
// props_id: TypeId,
// callerIDs are unsafely coerced to function pointers
// This is okay because #1, we store the props_id and verify and 2# the html! macro rejects components not made this way
//
// Manually constructing the VComponent is not possible from 3rd party crates
impl<'a> VComponent<'a> { impl<'a> VComponent<'a> {
// /// Construct a VComponent directly from a function component pub fn new<P: Properties + 'static>(caller: FC<P>, props: P) -> Self {
// /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack let caller = caller as *const ();
// pub fn from_fn<P: Properties>(f: FC<P>, props: P) -> Self { let props = Box::new(props);
// // // Props needs to be static
// // let props_id = std::any::TypeId::of::<P>();
// // // Cast the caller down Self {
_p: PhantomData {},
// // Self { props_id } props,
// Self {} caller,
// } }
}
} }
} }
+58 -41
View File
@@ -1,6 +1,6 @@
use crate::context::hooks::Hook;
use crate::inner::*; use crate::inner::*;
use crate::nodes::VNode; use crate::nodes::VNode;
use crate::{context::hooks::Hook, diff::diff};
use bumpalo::Bump; use bumpalo::Bump;
use generational_arena::Index; use generational_arena::Index;
use std::{ use std::{
@@ -15,22 +15,35 @@ use std::{
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope { pub struct Scope {
// TODO @Jon
// These hooks are actually references into the hook arena // These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage // These two could be combined with "OwningRef" to remove unsafe usage
// TODO @Jon // could also use ourborous
hooks: RefCell<Vec<*mut Hook>>, hooks: RefCell<Vec<*mut Hook>>,
hook_arena: typed_arena::Arena<Hook>, hook_arena: typed_arena::Arena<Hook>,
// Map to the parent // Map to the parent
parent: Option<Index>, parent: Option<Index>,
// todo, do better with the active frame stuff
frames: [Bump; 2],
// somehow build this vnode with a lifetime tied to self
cur_node: *mut VNode<'static>,
active_frame: u8,
// IE Which listeners need to be woken up?
listeners: Vec<Box<dyn Fn()>>,
//
props_type: TypeId, props_type: TypeId,
caller: *const i32, caller: *const i32,
} }
impl Scope { impl Scope {
// create a new scope from a function // create a new scope from a function
pub fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self { pub(crate) fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
// Capture the props type // Capture the props type
let props_type = TypeId::of::<T>(); let props_type = TypeId::of::<T>();
let hook_arena = typed_arena::Arena::new(); let hook_arena = typed_arena::Arena::new();
@@ -38,56 +51,60 @@ impl Scope {
let caller = f as *const i32; let caller = f as *const i32;
let frames = [Bump::new(), Bump::new()];
let listeners = Vec::new();
let active_frame = 1;
let new = frames[0].alloc(VNode::Text(VText::new("")));
let cur_node = new as *mut _;
Self { Self {
hook_arena, hook_arena,
hooks, hooks,
props_type, props_type,
caller, caller,
active_frame,
listeners,
parent, parent,
} frames,
} cur_node,
pub fn create_context<'a, T: Properties>(
&'a mut self,
components: &'a generational_arena::Arena<Scope>,
props: &'a T,
) -> Context {
//
Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
idx: 0.into(),
components,
} }
} }
/// Create a new context and run the component with references from the Virtual Dom /// 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 /// This function downcasts the function pointer based on the stored props_type
fn run<T: 'static>(&self, f: FC<T>) {} ///
} /// Props is ?Sized because we borrow the props
pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
let bump = &self.frames[0];
mod bad_unsafety { let ctx = Context {
// todo scope: &*self,
// fn call<'a, T: Properties + 'static>(&'a mut self, val: T) { _p: PhantomData {},
// if self.props_type == TypeId::of::<T>() { arena: &self.hook_arena,
// /* hooks: &self.hooks,
// SAFETY ALERT idx: 0.into(),
bump,
};
// This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html /*
// We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called, SAFETY ALERT
// we transmute the function back using the props as reference.
// This is safe because we check that the generic type matches before casting. This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
// */ We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
// let caller = unsafe { std::mem::transmute::<*const i32, FC<T>>(self.caller) }; we transmute the function back using the props as reference.
// // let ctx = self.create_context::<T>();
// // // TODO: do something with these nodes we could do a better check to make sure that the TypeID is correct
// // let nodes = caller(ctx); --
// } else { This is safe because we check that the generic type matches before casting.
// panic!("Do not try to use `call` on Scopes with the wrong props type") */
// } let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
// } 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
crate::diff::diff(old_nodes, &new_nodes);
}
} }
+96 -59
View File
@@ -40,7 +40,7 @@ use std::{
pub struct VirtualDom<P: Properties> { pub struct VirtualDom<P: Properties> {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with /// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena. /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
components: Arena<Scope>, pub(crate) components: Arena<Scope>,
/// The index of the root component. /// The index of the root component.
base_scope: Index, base_scope: Index,
@@ -48,7 +48,8 @@ pub struct VirtualDom<P: Properties> {
/// Components generate lifecycle events /// Components generate lifecycle events
event_queue: Vec<LifecycleEvent>, event_queue: Vec<LifecycleEvent>,
root_props: P, // 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. /// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
@@ -83,7 +84,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
let base_scope = components.insert(Scope::new(root, None)); let base_scope = components.insert(Scope::new(root, None));
// Create a new mount event with no root container // Create a new mount event with no root container
let first_event = LifecycleEvent::mount(base_scope, None, 0); let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
// Create an event queue with a mount for the base scope // Create an event queue with a mount for the base scope
let event_queue = vec![first_event]; let event_queue = vec![first_event];
@@ -92,83 +93,119 @@ impl<P: Properties + 'static> VirtualDom<P> {
components, components,
base_scope, base_scope,
event_queue, event_queue,
root_props, _p: PhantomData {},
} }
} }
/// Pop an event off the even queue and process it /// Pop an event off the event queue and process it
pub fn progress(&mut self) -> Result<(), ()> { pub fn progress(&mut self) -> Result<()> {
let LifecycleEvent { index, event_type } = self.event_queue.pop().ok_or(())?; let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
let scope = self.components.get(index).ok_or(())?; process_event(self, event)
match event_type {
// Component needs to be mounted to the virtual dom
LifecycleType::Mount { to, under } => {
// todo! run the FC with the bump allocator
// Run it with its properties
if let Some(other) = to {
// mount to another component
let p = ();
} else {
// mount to the root
}
}
// The parent for this component generated new props and the component needs update
LifecycleType::PropsChanged {} => {}
// Component was successfully mounted to the dom
LifecycleType::Mounted {} => {}
// Component was removed from the DOM
// Run any destructors and cleanup for the hooks and the dump the component
LifecycleType::Removed {} => {
let f = self.components.remove(index);
}
// Component was messaged via the internal subscription service
LifecycleType::Messaged => {}
}
Ok(())
} }
/// Update the root props, causing a full event cycle /// Update the root props, causing a full event cycle
pub fn update_props(&mut self, new_props: P) {} pub fn update_props(&mut self, new_props: P) {}
}
/// Run through every event in the event queue until the events are empty. /// Using mutable access to the Virtual Dom, progress a given lifecycle event
/// Function is asynchronous to allow for async components to finish their work. ///
pub async fn progess_completely() {} ///
///
///
///
///
fn process_event<P: Properties>(
dom: &mut VirtualDom<P>,
LifecycleEvent { index, event_type }: LifecycleEvent,
) -> Result<()> {
let scope = dom.components.get(index).ok_or(Error::NoEvent)?;
/// Create a new context object for a given component and scope match event_type {
fn new_context<T: Properties>(&self) -> Context { // Component needs to be mounted to the virtual dom
// fn new_context<T: Properties>(&self) -> Context<T> { LifecycleType::Mount { to, under, props } => {
todo!() if let Some(other) = to {
// mount to another component
} else {
// mount to the root
}
let g = props.as_ref();
scope.run(g);
// scope.run(runner, props, dom);
}
// The parent for this component generated new props and the component needs update
LifecycleType::PropsChanged { props } => {
//
}
// Component was successfully mounted to the dom
LifecycleType::Mounted {} => {
//
}
// Component was removed from the DOM
// Run any destructors and cleanup for the hooks and the dump the component
LifecycleType::Removed {} => {
let f = dom.components.remove(index);
}
// Component was messaged via the internal subscription service
LifecycleType::Messaged => {
//
}
// Event from renderer was fired with a given listener ID
//
LifecycleType::Callback { listener_id } => {}
} }
/// Stop writing to the current buffer and start writing to the new one. Ok(())
/// This should be done inbetween CallbackEvent handling, but not between lifecycle events.
pub fn swap_buffers(&mut self) {}
} }
pub struct LifecycleEvent { pub struct LifecycleEvent {
pub index: Index, pub index: Index,
pub event_type: LifecycleType, pub event_type: LifecycleType,
} }
impl LifecycleEvent {
fn mount(which: Index, to: Option<Index>, under: usize) -> Self {
Self {
index: which,
event_type: LifecycleType::Mount { to, under },
}
}
}
/// The internal lifecycle event system is managed by these /// The internal lifecycle event system is managed by these
/// Right now, we box the properties and but them in the enum
/// Later, we could directly call the chain of children without boxing
/// We could try to reuse the boxes somehow
pub enum LifecycleType { pub enum LifecycleType {
Mount { to: Option<Index>, under: usize }, Mount {
PropsChanged, to: Option<Index>,
under: usize,
props: Box<dyn Properties>,
},
PropsChanged {
props: Box<dyn Properties>,
},
Mounted, Mounted,
Removed, Removed,
Messaged, Messaged,
Callback {
listener_id: i32,
},
}
impl LifecycleEvent {
// helper method for shortcutting to the enum type
// probably not necessary
fn mount<P: Properties + 'static>(
which: Index,
to: Option<Index>,
under: usize,
props: P,
) -> Self {
Self {
index: which,
event_type: LifecycleType::Mount {
to,
under,
props: Box::new(props),
},
}
}
} }