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
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)]
static MyComp: FC<()> = |ctx| {
html! {
<div>
<p> "hello world" </p>
</div>
}
};
@@ -89,7 +72,4 @@ fn test() {
let mut ctx = Context { props: &() };
let f = MyComp.render(&mut ctx);
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"
toolshed = "0.8.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());
ctx.view(html! {
<div>
<h1> "This is the header bar" </h1>
<h1> "Idnt it awesome" </h1>
<button onclick={move |_| handler1()}> "Click me" </button>
</div>
ctx.view(|bump| {
builder::div(bump)
.child(VNode::Component(VComponent::new(
Bottom,
//
(),
)))
.finish()
})
};
@@ -29,12 +31,3 @@ static Bottom: FC<()> = |ctx, props| {
</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 mut vdom = VirtualDom::new_with_props(Example, p1);
vdom.progress()?;
// vdom.progress()?;
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.
/// Components take a "properties" object
pub trait Properties {
pub trait Properties // where
// Self: Sized,
{
fn call(&self, ptr: *const ()) {}
}
+8 -10
View File
@@ -3,7 +3,8 @@ use crate::{inner::Scope, nodes::VNode};
use bumpalo::Bump;
use hooks::Hook;
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.
@@ -27,15 +28,17 @@ use std::{
// todo: force lifetime of source into T as a valid lifetime too
// it's definitely possible, just needs some more messing around
pub struct Context<'src> {
pub idx: AtomicUsize,
pub(crate) scope: &'src Scope,
/// Direct access to the properties used to create this component.
// pub props: &'src PropType,
pub idx: AtomicUsize,
// Borrowed from scope
pub(crate) arena: &'src typed_arena::Arena<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
// todo @jon remove this
@@ -49,11 +52,6 @@ impl<'a> Context<'a> {
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
pub fn schedule_update(&self) -> impl Fn() -> () {
todo!("Subscription API is not ready yet");
@@ -75,8 +73,8 @@ impl<'a> Context<'a> {
/// ctx.view(lazy_tree)
/// }
///```
pub fn view(self, v: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
todo!()
pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
lazy_nodes(self.bump.borrow())
}
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> })
});
dom.progress()?;
// dom.progress()?;
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.
//! Currently implemented:
//! - use_ref
//! - use_state
//! - [x] use_ref
//! - [x] use_state
//! - [ ] use_reducer
//! - [ ] use_effect
pub use use_ref_def::use_ref;
pub use use_state_def::use_state;
mod use_state_def {
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> {
new_val: Rc<RefCell<Option<T>>>,
@@ -79,7 +81,7 @@ mod use_state_def {
mod use_ref_def {
use crate::inner::*;
use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc};
use std::{cell::RefCell, ops::DerefMut};
pub struct UseRef<T: 'static> {
_current: RefCell<T>,
+3
View File
@@ -68,6 +68,8 @@
pub mod component;
pub mod context;
pub mod debug_renderer;
pub mod diff;
pub mod error;
pub mod events;
pub mod hooks;
pub mod nodebuilder;
@@ -85,6 +87,7 @@ pub(crate) mod inner {
pub use crate::component::{Component, Properties};
use crate::context::hooks::Hook;
pub use crate::context::Context;
pub use crate::error::{Error, Result};
use crate::nodes;
pub use crate::scope::Scope;
pub use crate::virtual_dom::VirtualDom;
+18 -33
View File
@@ -232,56 +232,41 @@ mod vtext {
impl<'a> VText<'a> {
// / 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
// S: Into<String>,
// {
// VText { text: text.into() }
// }
// S: Into<str>,
{
VText { text: text.into() }
}
}
}
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
mod vcomponent {
use crate::prelude::Properties;
use crate::inner::{Properties, FC};
use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
use super::VNode;
pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
runner: Box<dyn Properties + 'src>,
props: Box<dyn Properties>,
// props: Box<dyn Properties + 'src>,
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> {
// /// Construct a VComponent directly from a function component
// /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack
// pub fn from_fn<P: Properties>(f: FC<P>, props: P) -> Self {
// // // Props needs to be static
// // let props_id = std::any::TypeId::of::<P>();
pub fn new<P: Properties + 'static>(caller: FC<P>, props: P) -> Self {
let caller = caller as *const ();
let props = Box::new(props);
// // // Cast the caller down
// // Self { props_id }
// Self {}
// }
Self {
_p: PhantomData {},
props,
caller,
}
}
}
}
+58 -41
View File
@@ -1,6 +1,6 @@
use crate::context::hooks::Hook;
use crate::inner::*;
use crate::nodes::VNode;
use crate::{context::hooks::Hook, diff::diff};
use bumpalo::Bump;
use generational_arena::Index;
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.
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope {
// TODO @Jon
// These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage
// TODO @Jon
// could also use ourborous
hooks: RefCell<Vec<*mut Hook>>,
hook_arena: typed_arena::Arena<Hook>,
// Map to the parent
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,
caller: *const i32,
}
impl Scope {
// 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
let props_type = TypeId::of::<T>();
let hook_arena = typed_arena::Arena::new();
@@ -38,56 +51,60 @@ impl Scope {
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 {
hook_arena,
hooks,
props_type,
caller,
active_frame,
listeners,
parent,
}
}
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,
frames,
cur_node,
}
}
/// 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
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 {
// todo
// fn call<'a, T: Properties + 'static>(&'a mut self, val: T) {
// if self.props_type == TypeId::of::<T>() {
// /*
// SAFETY ALERT
let ctx = Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
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,
// we transmute the function back using the props as reference.
/*
SAFETY ALERT
// 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")
// }
// }
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);
}
}
+96 -59
View File
@@ -40,7 +40,7 @@ use std::{
pub struct VirtualDom<P: Properties> {
/// 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.
components: Arena<Scope>,
pub(crate) components: Arena<Scope>,
/// The index of the root component.
base_scope: Index,
@@ -48,7 +48,8 @@ pub struct VirtualDom<P: Properties> {
/// Components generate lifecycle events
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.
@@ -83,7 +84,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
let base_scope = components.insert(Scope::new(root, None));
// 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
let event_queue = vec![first_event];
@@ -92,83 +93,119 @@ impl<P: Properties + 'static> VirtualDom<P> {
components,
base_scope,
event_queue,
root_props,
_p: PhantomData {},
}
}
/// Pop an event off the even queue and process it
pub fn progress(&mut self) -> Result<(), ()> {
let LifecycleEvent { index, event_type } = self.event_queue.pop().ok_or(())?;
/// Pop an event off the event queue and process it
pub fn progress(&mut self) -> Result<()> {
let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
let scope = self.components.get(index).ok_or(())?;
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(())
process_event(self, event)
}
/// Update the root props, causing a full event cycle
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() {}
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
///
///
///
///
///
///
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
fn new_context<T: Properties>(&self) -> Context {
// fn new_context<T: Properties>(&self) -> Context<T> {
todo!()
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 } => {}
}
/// Stop writing to the current buffer and start writing to the new one.
/// This should be done inbetween CallbackEvent handling, but not between lifecycle events.
pub fn swap_buffers(&mut self) {}
Ok(())
}
pub struct LifecycleEvent {
pub index: Index,
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
/// 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 {
Mount { to: Option<Index>, under: usize },
PropsChanged,
Mount {
to: Option<Index>,
under: usize,
props: Box<dyn Properties>,
},
PropsChanged {
props: Box<dyn Properties>,
},
Mounted,
Removed,
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),
},
}
}
}