Feat: major overhaul, wire up event system
This commit is contained in:
1 parent
f47651b32a
commit
8295ac4b3d
20 files changed
+1418
-188
No files matched your search
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
@@ -245,3 +245,15 @@ A few notes:
|
|||||||
## Concurrency
|
## Concurrency
|
||||||
|
|
||||||
I don't even know yet
|
I don't even know yet
|
||||||
|
|
||||||
|
|
||||||
|
## Execution Model
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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();
|
|
||||||
}
|
}
|
||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
@@ -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"
|
||||||
@@ -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>
|
|
||||||
})
|
|
||||||
};
|
|
||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ()) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {}
|
||||||
|
|||||||
@@ -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
@@ -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),
|
||||||
|
}
|
||||||
@@ -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>,
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user