feat: somewhat working with rc and weak

This commit is contained in:
Jonathan Kelley committed 2021-03-12 14:27:32 -05:00
1 parent ec801eab16
commit d4f1ceaffb
10 files changed
+170 -137

No files matched your search

-5
View File
@@ -71,14 +71,9 @@ impl PartialEq for ChildProps<'_> {
impl<'a> Properties for ChildProps<'a> {
type Builder = ChildPropsBuilder<'a, ((), ())>;
type StaticOutput = ChildProps<'static>;
fn builder() -> <Self as Properties>::Builder {
ChildProps::builder()
}
unsafe fn into_static(self) -> Self::StaticOutput {
unsafe { std::mem::transmute(self) }
}
}
fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree {
-5
View File
@@ -27,12 +27,7 @@ fn main() {}
impl Properties for ExampleProps {
type Builder = ExamplePropsBuilder<((),)>;
type StaticOutput = ExampleProps;
fn builder() -> Self::Builder {
ExampleProps::builder()
}
unsafe fn into_static(self) -> Self {
self
}
}
+3 -3
View File
@@ -12,12 +12,12 @@ static Header: FC<()> = |ctx, props| {
let handler1 = move || println!("Value is {}", inner.current());
ctx.render(|bump| {
builder::ElementBuilder::new(bump, "div")
ctx.render(|c| {
builder::ElementBuilder::new(c, "div")
.child(VNode::Component(VComponent::new(
Bottom,
//
&mut (),
c.bump.alloc(()),
)))
.finish()
})
-5
View File
@@ -27,12 +27,7 @@ static Example: FC<SomeProps> = |ctx, _props| {
// toodo: derive this
impl Properties for SomeProps {
type Builder = SomePropsBuilder<((),)>;
type StaticOutput = SomeProps;
fn builder() -> Self::Builder {
SomeProps::builder()
}
unsafe fn into_static(self) -> Self {
self
}
}
+9 -5
View File
@@ -32,7 +32,7 @@
//!
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{innerlude::*, scope::Scoped};
use crate::{innerlude::*, scope::Scope};
use bumpalo::Bump;
use fxhash::{FxHashMap, FxHashSet};
use generational_arena::Arena;
@@ -60,12 +60,15 @@ use std::{
pub struct DiffMachine<'a> {
pub change_list: EditMachine<'a>,
pub diffed: FxHashSet<ScopeIdx>,
pub lifecycle_events: VecDeque<LifeCycleEvent>,
pub lifecycle_events: VecDeque<LifeCycleEvent<'a>>,
}
#[derive(Debug)]
pub enum LifeCycleEvent {
Mount { caller: Caller, id: Uuid },
// #[derive(Debug)]
pub enum LifeCycleEvent<'a> {
Mount {
caller: Rc<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
id: Uuid,
},
PropsChanged,
SameProps,
Remove,
@@ -260,6 +263,7 @@ impl<'a> DiffMachine<'a> {
self.change_list
.create_text_node("placeholder for vcomponent");
let id = uuid::Uuid::new_v4();
*component.stable_addr.as_ref().borrow_mut() = Some(id);
self.change_list.save_known_root(id);
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
caller: component.caller.clone(),
+3 -2
View File
@@ -521,8 +521,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
// }
// }
pub fn virtual_child<'a, T: Properties>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
VNode::Component(crate::nodes::VComponent::new(f, ctx.bump.alloc(p)))
pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
let propsd: &'a mut _ = ctx.bump.alloc(p);
VNode::Component(crate::nodes::VComponent::new(f, propsd))
}
trait Bany {
+56 -47
View File
@@ -264,35 +264,40 @@ impl<'a> VText<'a> {
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub type StableScopeAddres = Rc<RefCell<Option<usize>>>;
pub type StableScopeAddres = Rc<RefCell<Option<uuid::Uuid>>>;
#[derive(Debug, Clone)]
pub struct VComponent<'src> {
pub stable_addr: StableScopeAddres,
pub raw_props: Rc<*mut dyn Any>,
// pub comparator: Comparator,
pub caller: Caller,
pub caller_ref: *const (),
pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
pub caller: Rc<dyn for<'r> Fn(Context<'r>) -> DomTree + 'src>,
// a pointer into the bump arena (given by the 'src lifetime)
raw_props: *const (),
// a pointer to the raw fn typ
caller_ref: *const (),
_p: PhantomData<&'src ()>,
}
pub trait PropsComparator {}
#[derive(Clone)]
// pub struct Comparator(pub Rc<dyn Fn(&dyn PropsComparator) -> bool>);
pub struct Comparator(pub Rc<dyn Fn(&dyn Any) -> bool>);
impl std::fmt::Debug for Comparator {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
impl std::fmt::Debug for VComponent<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!()
}
}
// pub struct Comparator(pub Rc<dyn Fn(&VComponent) -> bool>);
// impl std::fmt::Debug for Comparator {
// fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Ok(())
// }
// }
#[derive(Clone)]
pub struct Caller(pub Rc<dyn Fn(Context) -> DomTree>);
impl std::fmt::Debug for Caller {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
// pub struct Caller(pub Rc<dyn Fn(Context) -> DomTree>);
// impl std::fmt::Debug for Caller {
// fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// Ok(())
// }
// }
impl<'a> VComponent<'a> {
// use the type parameter on props creation and move it into a portable context
@@ -300,36 +305,40 @@ impl<'a> VComponent<'a> {
// - perform comparisons when diffing (memoization)
// TODO: lift the requirement that props need to be static
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
pub fn new<P: Properties>(caller: FC<P>, props: &mut P) -> Self {
let caller_ref = caller as *const ();
pub fn new<P: Properties + 'a>(component: FC<P>, props: &'a P) -> Self {
let caller_ref = component as *const ();
let props = Rc::new(props);
let raw_props = props as *const P as *const ();
// used for memoization
let p1 = props.clone();
// let props_comparator = move |new_props| -> bool {
// false
// let p = new_props.downcast_ref::<P::StaticOutput>().expect("");
// let r = unsafe { std::mem::transmute::<_, &P>(p) };
// &r == p1.as_ref()
// };
// used for actually rendering the custom component
let p2 = props.clone();
let caller = move |ctx: Context| -> DomTree {
// cast back into the right lifetime
caller(ctx, p2.as_ref())
let props_comparator = move |other: &VComponent| {
// Safety:
// We are guaranteed that the props will be of the same type because
// there is no way to create a VComponent other than this `new` method.
//
// Therefore, if the render functions are identical (by address), then so will be
// props type paramter (because it is the same render function). Therefore, we can be
// sure
if caller_ref == other.caller_ref {
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
real_other == props
} else {
false
}
};
todo!()
// Self {
// caller_ref,
// raw_props: props,
// _p: PhantomData,
// comparator: Comparator(Rc::new(props_comparator)),
// caller: Caller(Rc::new(caller)),
// stable_addr: Rc::new(RefCell::new(None)),
// }
let caller = move |ctx: Context| -> DomTree {
// cast back into the right lifetime
let safe_props: &P = unsafe { &*(raw_props as *const P) };
component(ctx, props)
};
Self {
caller_ref,
raw_props: props as *const P as *const _,
_p: PhantomData,
caller: Rc::new(caller),
comparator: Rc::new(props_comparator),
stable_addr: Rc::new(RefCell::new(None)),
}
}
}
+83 -44
View File
@@ -1,15 +1,22 @@
use crate::innerlude::*;
use bumpalo::Bump;
use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, ops::Deref};
use std::{
any::Any,
cell::RefCell,
collections::HashSet,
marker::PhantomData,
ops::Deref,
rc::{Rc, Weak},
};
pub trait Scoped {
fn run(&mut self);
// fn compare_props(&self, new: &dyn std::any::Any) -> bool;
fn call_listener(&mut self, trigger: EventTrigger);
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
}
// pub trait Scoped {
// fn run(&mut self);
// // fn compare_props(&self, new: &dyn std::any::Any) -> bool;
// fn call_listener(&mut self, trigger: EventTrigger);
// fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
// fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
// }
/// Every component in Dioxus is represented by a `Scope`.
///
@@ -17,7 +24,8 @@ pub trait Scoped {
///
/// 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<P: Properties> {
pub struct Scope {
// pub struct Scope<P: Properties> {
// Map to the parent
pub parent: Option<ScopeIdx>,
@@ -27,12 +35,15 @@ pub struct Scope<P: Properties> {
//
pub children: HashSet<ScopeIdx>,
pub caller: Weak<dyn Fn(Context) -> DomTree + 'static>,
// // the props
// pub props: P,
// and the actual render function
pub caller: Caller,
_p: std::marker::PhantomData<P>,
// pub caller: *const dyn Fn(Context) -> DomTree,
// _p: std::marker::PhantomData<P>,
// _p: std::marker::PhantomData<P>,
// pub raw_caller: FC<P>,
// ==========================
@@ -57,35 +68,62 @@ pub struct Scope<P: Properties> {
// instead of having it as a trait method, we use a single function
// todo: do the unsafety magic stuff to erase the type of p
pub fn create_scoped(
// raw_f: FC<P>,
caller: Caller,
// pub fn create_scoped(
// // raw_f: FC<P>,
// // caller: Caller,
// props: P,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
) -> Box<dyn Scoped> {
Box::new(Scope::<()> {
// raw_caller: raw_f,
_p: Default::default(),
caller,
myidx,
hook_arena: typed_arena::Arena::new(),
hooks: RefCell::new(Vec::new()),
frames: ActiveFrame::new(),
children: HashSet::new(),
listeners: Default::default(),
parent,
// props,
})
}
// // props: P,
// myidx: ScopeIdx,
// parent: Option<ScopeIdx>,
// ) -> Box<dyn Scoped> {
// Box::new(Scope::<()> {
// // raw_caller: raw_f,
// _p: Default::default(),
// // caller,
// myidx,
// hook_arena: typed_arena::Arena::new(),
// hooks: RefCell::new(Vec::new()),
// frames: ActiveFrame::new(),
// children: HashSet::new(),
// listeners: Default::default(),
// parent,
// // props,
// })
// }
impl<P: Properties + 'static> Scoped for Scope<P> {
impl Scope {
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
// we are going to break this lifetime by force in order to save it on ourselves.
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
// This should never happen, but is a good check to keep around
pub fn new<'creator_node>(
caller: Weak<dyn Fn(Context) -> DomTree + 'creator_node>,
myidx: ScopeIdx,
parent: Option<ScopeIdx>,
) -> Self {
// caller has been broken free
// however, it's still weak, so if the original Rc gets killed, we can't touch it
let broken_caller: Weak<dyn Fn(Context) -> DomTree + 'static> =
unsafe { std::mem::transmute(caller) };
Self {
caller: broken_caller,
hook_arena: typed_arena::Arena::new(),
hooks: RefCell::new(Vec::new()),
frames: ActiveFrame::new(),
children: HashSet::new(),
listeners: Default::default(),
parent,
myidx,
}
}
// impl<P: Properties + 'static> Scoped for Scope<P> {
/// Create a new context and run the component with references from the Virtual Dom
/// This function downcasts the function pointer based on the stored props_type
///
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
fn run<'bump>(&'bump mut self) {
pub fn run<'bump>(&'bump mut self) {
let frame = {
let frame = self.frames.next();
frame.bump.reset();
@@ -105,8 +143,9 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
listeners: &self.listeners,
};
todo!()
// Note that the actual modification of the vnode head element occurs during this call
let _: DomTree = (self.caller.0.as_ref())(ctx);
// let _: DomTree = (self.caller.0.as_ref())(ctx);
// let _: DomTree = (self.raw_caller)(ctx, &self.props);
/*
@@ -121,14 +160,14 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
- Public API cannot drop or destructure VNode
*/
frame.head_node = node_slot
.deref()
.borrow_mut()
.take()
.expect("Viewing did not happen");
// frame.head_node = node_slot
// .deref()
// .borrow_mut()
// .take()
// .expect("Viewing did not happen");
}
// fn compare_props(&self, new: &dyn Any) -> bool {
// pub fn compare_props(&self, new: &dyn Any) -> bool {
// new.downcast_ref::<P>()
// .map(|f| &self.props == f)
// .expect("Props should not be of a different type")
@@ -137,7 +176,7 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
// A safe wrapper around calling listeners
// calling listeners will invalidate the list of listeners
// The listener list will be completely drained because the next frame will write over previous listeners
fn call_listener(&mut self, trigger: EventTrigger) {
pub fn call_listener(&mut self, trigger: EventTrigger) {
let EventTrigger {
listener_id,
event: source,
@@ -165,11 +204,11 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
}
}
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
}
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.prev_head_node()
}
}
+16 -15
View File
@@ -1,13 +1,14 @@
// use crate::{changelist::EditList, nodes::VNode};
use crate::innerlude::*;
use crate::{
patch::Edit,
scope::{create_scoped, Scoped},
};
use crate::{patch::Edit, scope::Scope};
use bumpalo::Bump;
use generational_arena::Arena;
use std::{any::TypeId, cell::RefCell, rc::Rc};
use std::{
any::TypeId,
cell::RefCell,
rc::{Rc, Weak},
};
/// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI.
@@ -18,13 +19,15 @@ pub struct VirtualDom {
/// eventually, come up with a better datastructure that reuses boxes for known P types
/// like a generational typemap bump arena
/// -> IE a cache line for each P type with some heuristics on optimizing layout
pub(crate) components: Arena<Box<dyn Scoped>>,
pub(crate) components: Arena<Scope>,
// pub(crate) components: RefCell<Arena<Box<dyn Scoped>>>,
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
/// The index of the root component.
/// Will not be ready if the dom is fresh
pub(crate) base_scope: ScopeIdx,
pub(crate) root_caller: Rc<dyn Fn(Context) -> DomTree + 'static>,
// Type of the original props. This is done so VirtualDom does not need to be generic.
#[doc(hidden)]
_root_prop_type: std::any::TypeId,
@@ -55,6 +58,7 @@ impl VirtualDom {
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
/// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
@@ -63,18 +67,15 @@ impl VirtualDom {
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new();
// Create a reference to the component in the arena
// Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
let caller = Caller(Rc::new(move |ctx| {
//
root(ctx, &root_props)
// DomTree {}
}));
let base_scope = components.insert_with(|id| create_scoped(caller, id, None));
// let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
// the root is kept around
let root_caller: Rc<dyn Fn(Context) -> DomTree + 'static> =
Rc::new(move |ctx| root(ctx, &root_props));
let weak_caller: Weak<dyn Fn(Context) -> DomTree + 'static> = Rc::downgrade(&root_caller);
let base_scope = components.insert_with(move |id| Scope::new(weak_caller, id, None));
Self {
components,
root_caller,
base_scope,
diff_bump: Bump::new(),
_root_prop_type: TypeId::of::<P>(),
-6
View File
@@ -60,13 +60,7 @@ static Example2: FC<ExampleProps> = |ctx, props| {
impl Properties for ExampleProps {
type Builder = ExamplePropsBuilder<((),)>;
type StaticOutput = ExampleProps;
fn builder() -> Self::Builder {
ExampleProps::builder()
}
unsafe fn into_static(self) -> Self::StaticOutput {
todo!()
}
}