Feat: ensure mutabality is okay when not double-using the components

This commit is contained in:
Jonathan Kelley committed 2021-03-12 16:58:30 -05:00
1 parent b3c96a5996
commit 305ff919ef
13 files changed
+120 -114

No files matched your search

+30 -18
View File
@@ -3,42 +3,54 @@
//!
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
use crate::prelude::VirtualDom;
use crate::virtual_dom::VirtualDom;
use crate::{innerlude::Result, prelude::*};
pub struct DebugRenderer {
vdom: VirtualDom,
internal_dom: VirtualDom,
}
impl DebugRenderer {
pub fn new(vdom: VirtualDom) -> Self {
Self { vdom }
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
pub async fn run(&mut self) -> Result<(), ()> {
/// Create a new text-renderer instance from a functional component root.
/// Automatically progresses the creation of the VNode tree to completion.
///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props))
}
/// Create a new text renderer from an existing Virtual DOM.
pub fn from_vdom(dom: VirtualDom) -> Self {
// todo: initialize the event registry properly
Self { internal_dom: dom }
}
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
Ok(())
}
pub fn log_dom(&self) {}
}
#[cfg(old)]
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
use crate::scope::Properties;
#[test]
fn ensure_creation() -> Result<(), ()> {
#[derive(PartialEq)]
struct Creation {}
impl FC for Creation {
fn render(ctx: Context, props: &Self) -> DomTree {
ctx.render(html! { <div>"hello world" </div> })
}
}
static Example: FC<()> = |ctx, props| {
//
ctx.render(html! { <div> "hello world" </div> })
};
let mut dom = VirtualDom::new_with_props(Creation {});
let mut dom = VirtualDom::new(Example);
let machine = DiffMachine::new();
Ok(())
}
+2
View File
@@ -58,6 +58,7 @@ use std::{
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they were both updated via
/// subscriptions and props changes.
pub struct DiffMachine<'a> {
pub create_diffs: bool,
pub change_list: EditMachine<'a>,
pub diffed: FxHashSet<ScopeIdx>,
pub lifecycle_events: VecDeque<LifeCycleEvent<'a>>,
@@ -75,6 +76,7 @@ pub enum LifeCycleEvent<'a> {
impl<'a> DiffMachine<'a> {
pub fn new() -> Self {
Self {
create_diffs: true,
lifecycle_events: VecDeque::new(),
change_list: EditMachine::new(),
diffed: FxHashSet::default(),
+3
View File
@@ -3,6 +3,9 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(ThisError, Debug)]
pub enum Error {
#[error("Fatal Internal Error: {0}")]
FatalInternal(&'static str),
#[error("No event to progress")]
NoEvent,
+1 -2
View File
@@ -135,7 +135,6 @@ pub mod prelude {
pub use crate::component::{fc_to_builder, Properties};
pub use crate::context::Context;
use crate::nodes;
pub use crate::virtual_dom::VirtualDom;
pub use nodes::*;
// pub use nodes::iterables::IterableNodes;
@@ -144,7 +143,6 @@ pub mod prelude {
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
pub type VirtualNode<'a> = VNode<'a>;
// expose our bumpalo type
pub use bumpalo;
@@ -160,5 +158,6 @@ pub mod prelude {
pub use crate::component::ScopeIdx;
pub use crate::diff::DiffMachine;
pub use crate::debug_renderer::DebugRenderer;
pub use crate::hooks::*;
}
+2 -2
View File
@@ -146,7 +146,7 @@ impl<'a> VElement<'a> {
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug)]
pub struct Attribute<'a> {
pub name: &'static str,
pub value: &'a str,
@@ -244,7 +244,7 @@ impl NodeKey {
}
}
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, PartialEq)]
pub struct VText<'bump> {
pub text: &'bump str,
}
+7 -3
View File
@@ -16,16 +16,20 @@
//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
//!
//! # Known Issues
//! ----
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
use crate::innerlude::ScopeIdx;
pub type EditList<'src> = Vec<Edit<'src>>;
/// The `Edit` represents a single modifcation of the renderer tree.
/// todo@ jon: allow serde to be optional
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[serde(tag = "type")]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[derive(Debug)]
pub enum Edit<'src_bump> {
// ========================================================
// Common Ops: The most common operation types
+1 -1
View File
@@ -126,7 +126,7 @@ impl Scope {
/// 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)
pub fn run<'bump>(&'bump mut self) {
pub fn run_scope<'bump>(&'bump mut self) -> Result<()> {
let frame = {
let frame = self.frames.next();
frame.bump.reset();
+48 -65
View File
@@ -1,53 +1,36 @@
// use crate::{changelist::EditList, nodes::VNode};
use crate::innerlude::*;
use crate::{error::Error, innerlude::*};
use crate::{patch::Edit, scope::Scope};
use bumpalo::Bump;
use generational_arena::Arena;
use std::{
any::TypeId,
cell::RefCell,
rc::{Rc, Weak},
};
use thiserror::private::AsDynError;
// We actually allocate the properties for components in their parent's properties
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
pub(crate) type OpaqueComponent<'a> = dyn Fn(Context) -> DomTree + 'a;
/// 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.
pub struct VirtualDom {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
///
/// 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<Scope>,
// pub(crate) components: RefCell<Arena<Box<dyn Scoped>>>,
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
components: Arena<Scope>,
/// The index of the root component.
/// Will not be ready if the dom is fresh
pub(crate) base_scope: ScopeIdx,
base_scope: ScopeIdx,
pub(crate) root_caller: Rc<dyn Fn(Context) -> DomTree + 'static>,
// a strong allocation to the "caller" for the original props
#[doc(hidden)]
_root_caller: Rc<OpaqueComponent<'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,
// ======================
// DIFF RELATED ITEMs
// ======================
// // todo: encapsulate more state into this so we can better reuse it
pub(crate) diff_bump: Bump,
// // be very very very very very careful
// pub change_list: EditMachine<'static>,
// // vdom: &'a VirtualDom,
// vdom: *mut Arena<Box<dyn Scoped>>,
// // vdom: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
// pub cur_idx: ScopeIdx,
// // todo
// // do an indexmap sorted by height
// dirty_nodes: fxhash::FxHashSet<ScopeIdx>,
}
impl VirtualDom {
@@ -67,52 +50,49 @@ impl VirtualDom {
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new();
// 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));
// the root is kept around with a "hard" allocation
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
// we then expose this to the component with a weak allocation
let weak_caller: Weak<OpaqueComponent> = Rc::downgrade(&root_caller);
let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None));
Self {
components,
root_caller,
_root_caller: root_caller,
base_scope,
diff_bump: Bump::new(),
_root_prop_type: TypeId::of::<P>(),
}
}
// consume the top of the diff machine event cycle and dump edits into the edit list
pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> {
Ok(())
}
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
log::debug!("rebuilding...");
// Reset and then build a new diff machine
// The previous edit list cannot be around while &mut is held
// Make sure variance doesnt break this
// bump.reset();
// Diff from the top
let mut diff_machine = DiffMachine::new(); // partial borrow
{
let component = self
.components
.get_mut(self.base_scope)
.expect("failed to acquire base scope");
component.run();
}
self.components
.get_mut(self.base_scope)
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?
.run_scope()?;
// get raw pointer to the arena
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
// // get raw pointer to the arena
// let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
{
let component = self
.components
.get(self.base_scope)
.expect("failed to acquire base scope");
diff_machine.diff_node(component.old_frame(), component.new_frame());
}
// {
// let component = self
// .components
// .get(self.base_scope)
// .expect("failed to acquire base scope");
// diff_machine.diff_node(component.old_frame(), component.new_frame());
// }
// let p = &mut self.components;
// chew down the the lifecycle events until all dirty nodes are computed
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
match event {
@@ -127,13 +107,16 @@ impl VirtualDom {
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
unsafe {
let p = &mut *(very_unsafe_components);
// let p = &mut *(very_unsafe_components);
// let p = &mut self.components;
// todo, hook up the parent/child indexes properly
let idx = p.insert_with(|f| Scope::new(caller, f, None));
let c = p.get_mut(idx).unwrap();
c.run();
diff_machine.diff_node(c.old_frame(), c.new_frame());
let idx = self.components.insert_with(|f| Scope::new(caller, f, None));
let c = self.components.get_mut(idx).unwrap();
// let idx = p.insert_with(|f| Scope::new(caller, f, None));
// let c = p.get_mut(idx).unwrap();
c.run_scope();
// diff_machine.diff_node(c.old_frame(), c.new_frame());
}
}
LifeCycleEvent::PropsChanged => {
@@ -190,7 +173,7 @@ impl VirtualDom {
.expect("Borrowing should not fail");
component.call_listener(event);
component.run();
component.run_scope();
let mut diff_machine = DiffMachine::new();
// let mut diff_machine = DiffMachine::new(&self.diff_bump);