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
+1421 -191

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;
+17 -32
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(text: &'a str) -> Self
// pub fn new<S>(text: S) -> 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,
// } }
}
} }
} }
+60 -43
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];
let ctx = Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
idx: 0.into(),
bump,
};
/*
SAFETY ALERT
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,
we transmute the function back using the props as reference.
we could do a better check to make sure that the TypeID is correct
--
This is safe because we check that the generic type matches before casting.
*/
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);
} }
mod bad_unsafety {
// todo
// fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
// if self.props_type == TypeId::of::<T>() {
// /*
// SAFETY ALERT
// 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,
// we transmute the function back using the props as reference.
// This is safe because we check that the generic type matches before casting.
// */
// let caller = unsafe { std::mem::transmute::<*const i32, FC<T>>(self.caller) };
// // let ctx = self.create_context::<T>();
// // // TODO: do something with these nodes
// // let nodes = caller(ctx);
// } else {
// panic!("Do not try to use `call` on Scopes with the wrong props type")
// }
// }
} }
+98 -61
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.
/// Function is asynchronous to allow for async components to finish their work.
pub async fn progess_completely() {}
/// Create a new context object for a given component and scope
fn new_context<T: Properties>(&self) -> Context {
// fn new_context<T: Properties>(&self) -> Context<T> {
todo!()
} }
/// Stop writing to the current buffer and start writing to the new one. /// Using mutable access to the Virtual Dom, progress a given lifecycle event
/// This should be done inbetween CallbackEvent handling, but not between lifecycle events. ///
pub fn swap_buffers(&mut self) {} ///
///
///
///
///
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)?;
match event_type {
// Component needs to be mounted to the virtual dom
LifecycleType::Mount { to, under, props } => {
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 } => {}
}
Ok(())
} }
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),
},
}
}
} }