mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
Feat: ensure mutabality is okay when not double-using the components
This commit is contained in:
parent
b3c96a5996
commit
305ff919ef
13 changed files with 120 additions and 114 deletions
|
@ -10,22 +10,34 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
# todo: use wast for faster load/compile
|
||||
dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
|
||||
|
||||
# Backs some static data
|
||||
once_cell = "1.5.2"
|
||||
|
||||
# Backs the scope creation and reutilization
|
||||
# Backs scopes and graphs between parent and children
|
||||
generational-arena = { version = "0.2.8", features = ["serde"] }
|
||||
|
||||
# Bumpalo backs the VNode creation
|
||||
bumpalo = { version = "3.6.0", features = ["collections"] }
|
||||
|
||||
# all the arenas 👿
|
||||
# Backs hook data - might move away from this arena
|
||||
typed-arena = "2.0.1"
|
||||
id-arena = "2.2.1"
|
||||
|
||||
# custom error type
|
||||
thiserror = "1.0.23"
|
||||
|
||||
# faster hashmaps
|
||||
fxhash = "0.2.1"
|
||||
|
||||
# Used in diffing
|
||||
longest-increasing-subsequence = "0.1.0"
|
||||
serde = { version = "1.0.123", features = ["derive"] }
|
||||
|
||||
# internall used
|
||||
log = "0.4.14"
|
||||
|
||||
serde = { version = "1.0.123", features = ["derive"], optional=true }
|
||||
|
||||
# todo, remove
|
||||
uuid = {version = "0.8.2", features=["serde", "v4"]}
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website.
|
||||
|
||||
We reserve the "dioxus" name and aggregate all the various renderers under it. If you want just a single dioxus renderer, then chose from "dioxus-web", "dioxus-desktop", etc.
|
||||
|
||||
## Internals
|
||||
Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use dioxus_core::virtual_dom::VirtualDom;
|
||||
use dioxus_core::{component::Properties, prelude::*};
|
||||
|
||||
fn main() -> Result<(), ()> {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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,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,
|
||||
|
||||
|
|
|
@ -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::*;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
// use crate::prelude::*;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
// type VirtualNode = VNode;
|
||||
|
||||
/// Test a basic usage of a virtual dom + text renderer combo
|
||||
#[test]
|
||||
fn simple_integration() {
|
||||
// let dom = VirtualDom::new(|ctx| html! { <div>Hello World!</div> });
|
||||
// let mut renderer = TextRenderer::new(dom);
|
||||
// let output = renderer.render();
|
||||
}
|
||||
fn simple_integration() {}
|
||||
|
|
|
@ -6,11 +6,9 @@ use dioxus::prelude::Properties;
|
|||
use fxhash::FxHashMap;
|
||||
use web_sys::{window, Document, Element, Event, Node};
|
||||
|
||||
use dioxus::virtual_dom::VirtualDom;
|
||||
pub use dioxus_core as dioxus;
|
||||
use dioxus_core::{
|
||||
events::EventTrigger,
|
||||
prelude::{VirtualDom, FC},
|
||||
};
|
||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||
use futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
|
||||
pub mod interpreter;
|
||||
|
|
Loading…
Reference in a new issue