wip: cleanup public apis
This commit is contained in:
1 parent
8b0eb87c72
commit
927b05f358
13 files changed
+178
-371
No files matched your search
@@ -16,9 +16,6 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
|
|||||||
# Bumpalo is used as a micro heap backing each component
|
# Bumpalo is used as a micro heap backing each component
|
||||||
bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
|
bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
|
||||||
|
|
||||||
# custom error type
|
|
||||||
thiserror = "1"
|
|
||||||
|
|
||||||
# faster hashmaps
|
# faster hashmaps
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
|
|
||||||
|
|||||||
@@ -40,42 +40,3 @@ We have big goals for Dioxus. The final implementation must:
|
|||||||
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
|
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
|
||||||
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
|
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
|
||||||
|
|
||||||
## Optimizations
|
|
||||||
|
|
||||||
- Support a pluggable allocation strategy that makes VNode creation **very** fast
|
|
||||||
- Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
|
|
||||||
- Support advanced diffing strategies (patience, Meyers, etc)
|
|
||||||
|
|
||||||
```rust
|
|
||||||
|
|
||||||
rsx!{ "this is a text node" }
|
|
||||||
|
|
||||||
rsx!{
|
|
||||||
div {}
|
|
||||||
"asd"
|
|
||||||
div {}
|
|
||||||
div {}
|
|
||||||
}
|
|
||||||
rsx!{
|
|
||||||
div {
|
|
||||||
a {}
|
|
||||||
b {}
|
|
||||||
c {}
|
|
||||||
Container {
|
|
||||||
Container {
|
|
||||||
Container {
|
|
||||||
Container {
|
|
||||||
Container {
|
|
||||||
div {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
//! Debug virtual doms!
|
|
||||||
//! This renderer comes built in with dioxus core and shows how to implement a basic renderer.
|
|
||||||
//!
|
|
||||||
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
|
|
||||||
|
|
||||||
use crate::innerlude::RealDom;
|
|
||||||
use crate::{events::EventTrigger, virtual_dom::VirtualDom};
|
|
||||||
use crate::{innerlude::Result, prelude::*};
|
|
||||||
|
|
||||||
pub struct DebugRenderer {
|
|
||||||
internal_dom: VirtualDom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DebugRenderer {
|
|
||||||
/// 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, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 handle_event(&mut self, trigger: EventTrigger) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
|
|
||||||
// this does a "holy" compare - if something is missing in the rhs, it doesn't complain.
|
|
||||||
// it only complains if something shows up that's not in the lhs, *or* if a value is different.
|
|
||||||
// This lets you exclude various fields if you just want to drill in to a specific prop
|
|
||||||
// It leverages the internal diffing mechanism.
|
|
||||||
// If you have a list or "nth" child, you do need to list those children, but you don't need to
|
|
||||||
// fill in their children/attrs/etc
|
|
||||||
// Does not handle children or lifecycles and will always fail the test if they show up in the rhs
|
|
||||||
pub fn compare<F>(&self, other: LazyNodes<F>) -> Result<()>
|
|
||||||
where
|
|
||||||
F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a full compare - everything must match
|
|
||||||
// Ignores listeners and children components
|
|
||||||
pub fn compare_full<F>(&self, other: LazyNodes<F>) -> Result<()>
|
|
||||||
where
|
|
||||||
F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_nodes<F>(&self, other: LazyNodes<F>) -> Result<()>
|
|
||||||
where
|
|
||||||
F: for<'b, 'c> FnOnce(&'b NodeFactory<'c>) -> VNode<'c>,
|
|
||||||
{
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+23
-38
@@ -105,7 +105,7 @@ use DomEdit::*;
|
|||||||
///
|
///
|
||||||
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
|
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
|
||||||
/// stack machines all the way down!
|
/// stack machines all the way down!
|
||||||
pub struct DiffMachine<'bump> {
|
pub(crate) struct DiffMachine<'bump> {
|
||||||
pub vdom: &'bump ResourcePool,
|
pub vdom: &'bump ResourcePool,
|
||||||
pub mutations: Mutations<'bump>,
|
pub mutations: Mutations<'bump>,
|
||||||
pub stack: DiffStack<'bump>,
|
pub stack: DiffStack<'bump>,
|
||||||
@@ -115,7 +115,7 @@ pub struct DiffMachine<'bump> {
|
|||||||
/// a "saved" form of a diff machine
|
/// a "saved" form of a diff machine
|
||||||
/// in regular diff machine, the &'bump reference is a stack borrow, but the
|
/// in regular diff machine, the &'bump reference is a stack borrow, but the
|
||||||
/// bump lifetimes are heap borrows.
|
/// bump lifetimes are heap borrows.
|
||||||
pub struct SavedDiffWork<'bump> {
|
pub(crate) struct SavedDiffWork<'bump> {
|
||||||
pub mutations: Mutations<'bump>,
|
pub mutations: Mutations<'bump>,
|
||||||
pub stack: DiffStack<'bump>,
|
pub stack: DiffStack<'bump>,
|
||||||
pub seen_scopes: FxHashSet<ScopeId>,
|
pub seen_scopes: FxHashSet<ScopeId>,
|
||||||
@@ -174,36 +174,23 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
///
|
///
|
||||||
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
|
/// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
|
||||||
pub async fn work(&mut self) {
|
pub async fn work(&mut self) {
|
||||||
// defer to individual functions so the compiler produces better code
|
|
||||||
// large functions tend to be difficult for the compiler to work with
|
|
||||||
while let Some(instruction) = self.stack.pop() {
|
while let Some(instruction) = self.stack.pop() {
|
||||||
|
// defer to individual functions so the compiler produces better code
|
||||||
|
// large functions tend to be difficult for the compiler to work with
|
||||||
match instruction {
|
match instruction {
|
||||||
DiffInstruction::PopScope => {
|
DiffInstruction::PopScope => {
|
||||||
self.stack.pop_scope();
|
self.stack.pop_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
DiffInstruction::DiffNode { old, new, .. } => {
|
DiffInstruction::DiffNode { old, new, .. } => self.diff_node(old, new),
|
||||||
self.diff_node(old, new);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiffInstruction::DiffChildren { old, new } => {
|
DiffInstruction::DiffChildren { old, new } => self.diff_children(old, new),
|
||||||
self.diff_children(old, new);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiffInstruction::Create { node } => {
|
DiffInstruction::Create { node } => self.create_node(node),
|
||||||
self.create_node(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiffInstruction::Mount { and } => {
|
DiffInstruction::Mount { and } => self.mount(and),
|
||||||
self.mount(and);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiffInstruction::PrepareMoveNode { node } => {
|
DiffInstruction::PrepareMoveNode { node } => self.prepare_move_node(node),
|
||||||
for el in RealChildIterator::new(node, self.vdom) {
|
|
||||||
self.mutations.push_root(el.direct_id());
|
|
||||||
self.stack.add_child_count(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// todo: call this less frequently, there is a bit of overhead involved
|
// todo: call this less frequently, there is a bit of overhead involved
|
||||||
@@ -211,6 +198,13 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
|
||||||
|
for el in RealChildIterator::new(node, self.vdom) {
|
||||||
|
self.mutations.push_root(el.direct_id());
|
||||||
|
self.stack.add_child_count(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn mount(&mut self, and: MountType<'bump>) {
|
fn mount(&mut self, and: MountType<'bump>) {
|
||||||
let nodes_created = self.stack.pop_nodes_created();
|
let nodes_created = self.stack.pop_nodes_created();
|
||||||
match and {
|
match and {
|
||||||
@@ -363,22 +357,12 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
|
let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
|
||||||
|
|
||||||
// Run the scope for one iteration to initialize it
|
// Run the scope for one iteration to initialize it
|
||||||
match new_component.run_scope() {
|
if new_component.run_scope() {
|
||||||
Ok(_g) => {
|
// Take the node that was just generated from running the component
|
||||||
// all good, new nodes exist
|
let nextnode = new_component.frames.fin_head();
|
||||||
}
|
self.stack.create_component(new_idx, nextnode);
|
||||||
Err(err) => {
|
|
||||||
// failed to run. this is the first time the component ran, and it failed
|
|
||||||
// we manually set its head node to an empty fragment
|
|
||||||
panic!("failing components not yet implemented");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take the node that was just generated from running the component
|
|
||||||
let nextnode = new_component.frames.fin_head();
|
|
||||||
|
|
||||||
self.stack.create_component(new_idx, nextnode);
|
|
||||||
|
|
||||||
// Finally, insert this scope as a seen node.
|
// Finally, insert this scope as a seen node.
|
||||||
self.seen_scopes.insert(new_idx);
|
self.seen_scopes.insert(new_idx);
|
||||||
}
|
}
|
||||||
@@ -545,8 +529,9 @@ impl<'bump> DiffMachine<'bump> {
|
|||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
// the props are different...
|
// the props are different...
|
||||||
scope.run_scope().unwrap();
|
if scope.run_scope() {
|
||||||
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub enum MountType<'a> {
|
|||||||
InsertBefore { other_node: &'a VNode<'a> },
|
InsertBefore { other_node: &'a VNode<'a> },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DiffStack<'bump> {
|
pub(crate) struct DiffStack<'bump> {
|
||||||
instructions: Vec<DiffInstruction<'bump>>,
|
instructions: Vec<DiffInstruction<'bump>>,
|
||||||
nodes_created_stack: SmallVec<[usize; 10]>,
|
nodes_created_stack: SmallVec<[usize; 10]>,
|
||||||
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use crate::innerlude::ScopeId;
|
|
||||||
|
|
||||||
/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
|
|
||||||
/// network or through FFI boundaries.
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
#[cfg_attr(
|
|
||||||
feature = "serialize",
|
|
||||||
derive(serde::Serialize, serde::Deserialize),
|
|
||||||
serde(tag = "type")
|
|
||||||
)]
|
|
||||||
pub enum DomEdit<'bump> {
|
|
||||||
PushRoot {
|
|
||||||
id: u64,
|
|
||||||
},
|
|
||||||
PopRoot,
|
|
||||||
|
|
||||||
AppendChildren {
|
|
||||||
many: u32,
|
|
||||||
},
|
|
||||||
|
|
||||||
// "Root" refers to the item direclty
|
|
||||||
// it's a waste of an instruction to push the root directly
|
|
||||||
ReplaceWith {
|
|
||||||
root: u64,
|
|
||||||
m: u32,
|
|
||||||
},
|
|
||||||
InsertAfter {
|
|
||||||
root: u64,
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
InsertBefore {
|
|
||||||
root: u64,
|
|
||||||
n: u32,
|
|
||||||
},
|
|
||||||
Remove {
|
|
||||||
root: u64,
|
|
||||||
},
|
|
||||||
|
|
||||||
RemoveAllChildren,
|
|
||||||
CreateTextNode {
|
|
||||||
text: &'bump str,
|
|
||||||
id: u64,
|
|
||||||
},
|
|
||||||
CreateElement {
|
|
||||||
tag: &'bump str,
|
|
||||||
id: u64,
|
|
||||||
},
|
|
||||||
CreateElementNs {
|
|
||||||
tag: &'bump str,
|
|
||||||
id: u64,
|
|
||||||
ns: &'static str,
|
|
||||||
},
|
|
||||||
CreatePlaceholder {
|
|
||||||
id: u64,
|
|
||||||
},
|
|
||||||
NewEventListener {
|
|
||||||
event_name: &'static str,
|
|
||||||
scope: ScopeId,
|
|
||||||
mounted_node_id: u64,
|
|
||||||
},
|
|
||||||
RemoveEventListener {
|
|
||||||
event: &'static str,
|
|
||||||
},
|
|
||||||
SetText {
|
|
||||||
text: &'bump str,
|
|
||||||
},
|
|
||||||
SetAttribute {
|
|
||||||
field: &'static str,
|
|
||||||
value: &'bump str,
|
|
||||||
ns: Option<&'bump str>,
|
|
||||||
},
|
|
||||||
RemoveAttribute {
|
|
||||||
name: &'static str,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
impl DomEdit<'_> {
|
|
||||||
pub fn is(&self, id: &'static str) -> bool {
|
|
||||||
match self {
|
|
||||||
DomEdit::InsertAfter { .. } => id == "InsertAfter",
|
|
||||||
DomEdit::InsertBefore { .. } => id == "InsertBefore",
|
|
||||||
DomEdit::PushRoot { .. } => id == "PushRoot",
|
|
||||||
DomEdit::PopRoot => id == "PopRoot",
|
|
||||||
DomEdit::AppendChildren { .. } => id == "AppendChildren",
|
|
||||||
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
|
|
||||||
DomEdit::Remove { .. } => id == "Remove",
|
|
||||||
DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
|
|
||||||
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
|
|
||||||
DomEdit::CreateElement { .. } => id == "CreateElement",
|
|
||||||
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
|
|
||||||
DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
|
|
||||||
DomEdit::NewEventListener { .. } => id == "NewEventListener",
|
|
||||||
DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
|
|
||||||
DomEdit::SetText { .. } => id == "SetText",
|
|
||||||
DomEdit::SetAttribute { .. } => id == "SetAttribute",
|
|
||||||
DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
//! Internal error handling for Dioxus
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use thiserror::Error as ThisError;
|
|
||||||
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("Context is missing")]
|
|
||||||
MissingSharedContext,
|
|
||||||
|
|
||||||
#[error("No event to progress")]
|
|
||||||
NoEvent,
|
|
||||||
|
|
||||||
#[error("Wrong Properties Type")]
|
|
||||||
WrongProps,
|
|
||||||
|
|
||||||
#[error("The component failed to return VNodes")]
|
|
||||||
ComponentFailed,
|
|
||||||
|
|
||||||
#[error("Base scope has not been mounted yet")]
|
|
||||||
NotMounted,
|
|
||||||
|
|
||||||
#[error("I/O Error: {0}")]
|
|
||||||
IO(#[from] std::io::Error),
|
|
||||||
}
|
|
||||||
+13
-16
@@ -1,14 +1,5 @@
|
|||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
//! Dioxus Core
|
|
||||||
//! ----------
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
format_args_f, html, rsx, Context, DiffInstruction, DioxusElement, DomEdit, DomTree, ElementId,
|
format_args_f, html, rsx, Context, DiffInstruction, DioxusElement, DomEdit, DomTree, ElementId,
|
||||||
@@ -26,7 +17,6 @@ pub mod prelude {
|
|||||||
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
pub use dioxus_core_macro::{format_args_f, html, rsx, Props};
|
||||||
}
|
}
|
||||||
|
|
||||||
// types used internally that are important
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
pub use crate::bumpframe::*;
|
pub use crate::bumpframe::*;
|
||||||
pub use crate::childiter::*;
|
pub use crate::childiter::*;
|
||||||
@@ -34,8 +24,6 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::context::*;
|
pub use crate::context::*;
|
||||||
pub use crate::diff::*;
|
pub use crate::diff::*;
|
||||||
pub use crate::diff_stack::*;
|
pub use crate::diff_stack::*;
|
||||||
pub use crate::dom_edits::*;
|
|
||||||
pub use crate::error::*;
|
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::heuristics::*;
|
pub use crate::heuristics::*;
|
||||||
pub use crate::hooklist::*;
|
pub use crate::hooklist::*;
|
||||||
@@ -43,7 +31,6 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::scheduler::*;
|
pub use crate::scheduler::*;
|
||||||
// pub use crate::scheduler::*;
|
|
||||||
pub use crate::scope::*;
|
pub use crate::scope::*;
|
||||||
pub use crate::util::*;
|
pub use crate::util::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
@@ -57,19 +44,29 @@ pub(crate) mod innerlude {
|
|||||||
|
|
||||||
pub mod exports {
|
pub mod exports {
|
||||||
//! Important dependencies that are used by the rest of the library
|
//! Important dependencies that are used by the rest of the library
|
||||||
|
|
||||||
// the foundation of this library
|
// the foundation of this library
|
||||||
pub use bumpalo;
|
pub use bumpalo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Navigating this crate:
|
||||||
|
- virtual_dom: the primary entrypoint for the crate
|
||||||
|
- scheduler: the core interior logic called by virtual_dom
|
||||||
|
- nodes: the definition of VNodes, listeners, etc.
|
||||||
|
-
|
||||||
|
|
||||||
|
|
||||||
|
Some utilities
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
pub mod bumpframe;
|
pub mod bumpframe;
|
||||||
pub mod childiter;
|
pub mod childiter;
|
||||||
pub mod component;
|
pub mod component;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod diff;
|
pub mod diff;
|
||||||
pub mod diff_stack;
|
pub mod diff_stack;
|
||||||
pub mod dom_edits;
|
|
||||||
pub mod error;
|
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod heuristics;
|
pub mod heuristics;
|
||||||
pub mod hooklist;
|
pub mod hooklist;
|
||||||
|
|||||||
@@ -163,3 +163,100 @@ impl<'a> NodeRefMutation<'a> {
|
|||||||
.and_then(|f| f.downcast_mut::<T>())
|
.and_then(|f| f.downcast_mut::<T>())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A `DomEdit` represents a serialzied form of the VirtualDom's trait-based API. This allows streaming edits across the
|
||||||
|
/// network or through FFI boundaries.
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serialize",
|
||||||
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
|
serde(tag = "type")
|
||||||
|
)]
|
||||||
|
pub enum DomEdit<'bump> {
|
||||||
|
PushRoot {
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
|
PopRoot,
|
||||||
|
|
||||||
|
AppendChildren {
|
||||||
|
many: u32,
|
||||||
|
},
|
||||||
|
|
||||||
|
// "Root" refers to the item direclty
|
||||||
|
// it's a waste of an instruction to push the root directly
|
||||||
|
ReplaceWith {
|
||||||
|
root: u64,
|
||||||
|
m: u32,
|
||||||
|
},
|
||||||
|
InsertAfter {
|
||||||
|
root: u64,
|
||||||
|
n: u32,
|
||||||
|
},
|
||||||
|
InsertBefore {
|
||||||
|
root: u64,
|
||||||
|
n: u32,
|
||||||
|
},
|
||||||
|
Remove {
|
||||||
|
root: u64,
|
||||||
|
},
|
||||||
|
|
||||||
|
RemoveAllChildren,
|
||||||
|
CreateTextNode {
|
||||||
|
text: &'bump str,
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
|
CreateElement {
|
||||||
|
tag: &'bump str,
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
|
CreateElementNs {
|
||||||
|
tag: &'bump str,
|
||||||
|
id: u64,
|
||||||
|
ns: &'static str,
|
||||||
|
},
|
||||||
|
CreatePlaceholder {
|
||||||
|
id: u64,
|
||||||
|
},
|
||||||
|
NewEventListener {
|
||||||
|
event_name: &'static str,
|
||||||
|
scope: ScopeId,
|
||||||
|
mounted_node_id: u64,
|
||||||
|
},
|
||||||
|
RemoveEventListener {
|
||||||
|
event: &'static str,
|
||||||
|
},
|
||||||
|
SetText {
|
||||||
|
text: &'bump str,
|
||||||
|
},
|
||||||
|
SetAttribute {
|
||||||
|
field: &'static str,
|
||||||
|
value: &'bump str,
|
||||||
|
ns: Option<&'bump str>,
|
||||||
|
},
|
||||||
|
RemoveAttribute {
|
||||||
|
name: &'static str,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
impl DomEdit<'_> {
|
||||||
|
pub fn is(&self, id: &'static str) -> bool {
|
||||||
|
match self {
|
||||||
|
DomEdit::InsertAfter { .. } => id == "InsertAfter",
|
||||||
|
DomEdit::InsertBefore { .. } => id == "InsertBefore",
|
||||||
|
DomEdit::PushRoot { .. } => id == "PushRoot",
|
||||||
|
DomEdit::PopRoot => id == "PopRoot",
|
||||||
|
DomEdit::AppendChildren { .. } => id == "AppendChildren",
|
||||||
|
DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
|
||||||
|
DomEdit::Remove { .. } => id == "Remove",
|
||||||
|
DomEdit::RemoveAllChildren => id == "RemoveAllChildren",
|
||||||
|
DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
|
||||||
|
DomEdit::CreateElement { .. } => id == "CreateElement",
|
||||||
|
DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
|
||||||
|
DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
|
||||||
|
DomEdit::NewEventListener { .. } => id == "NewEventListener",
|
||||||
|
DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
|
||||||
|
DomEdit::SetText { .. } => id == "SetText",
|
||||||
|
DomEdit::SetAttribute { .. } => id == "SetAttribute",
|
||||||
|
DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -128,7 +128,12 @@ pub enum SchedulerMsg {
|
|||||||
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
|
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub struct Scheduler {
|
pub(crate) struct Scheduler {
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||||
|
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||||
pub pool: ResourcePool,
|
pub pool: ResourcePool,
|
||||||
|
|
||||||
pub heuristics: HeuristicsEngine,
|
pub heuristics: HeuristicsEngine,
|
||||||
@@ -523,7 +528,7 @@ impl Scheduler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PriorityLane {
|
pub(crate) struct PriorityLane {
|
||||||
pub dirty_scopes: IndexSet<ScopeId>,
|
pub dirty_scopes: IndexSet<ScopeId>,
|
||||||
pub saved_state: Option<SavedDiffWork<'static>>,
|
pub saved_state: Option<SavedDiffWork<'static>>,
|
||||||
pub in_progress: bool,
|
pub in_progress: bool,
|
||||||
@@ -682,7 +687,7 @@ impl ResourcePool {
|
|||||||
&'b self,
|
&'b self,
|
||||||
_id: ScopeId,
|
_id: ScopeId,
|
||||||
_f: impl FnOnce(&'b mut Scope) -> O,
|
_f: impl FnOnce(&'b mut Scope) -> O,
|
||||||
) -> Result<O> {
|
) -> Option<O> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,13 +697,13 @@ impl ResourcePool {
|
|||||||
&self,
|
&self,
|
||||||
_id: ScopeId,
|
_id: ScopeId,
|
||||||
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
_f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
||||||
) -> Result<&VNode<'b>> {
|
) -> Option<&VNode<'b>> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_remove(&self, id: ScopeId) -> Result<Scope> {
|
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
|
||||||
let inner = unsafe { &mut *self.components.get() };
|
let inner = unsafe { &mut *self.components.get() };
|
||||||
Ok(inner.remove(id.0))
|
Some(inner.remove(id.0))
|
||||||
// .try_remove(id.0)
|
// .try_remove(id.0)
|
||||||
// .ok_or_else(|| Error::FatalInternal("Scope not found"))
|
// .ok_or_else(|| Error::FatalInternal("Scope not found"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,9 @@ impl Scope {
|
|||||||
self.child_nodes = child_nodes;
|
self.child_nodes = child_nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_scope<'sel>(&'sel mut self) -> Result<()> {
|
/// Returns true if the scope completed successfully
|
||||||
|
///
|
||||||
|
pub(crate) fn run_scope<'sel>(&'sel mut self) -> bool {
|
||||||
// Cycle to the next frame and then reset it
|
// Cycle to the next frame and then reset it
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
@@ -133,17 +135,12 @@ impl Scope {
|
|||||||
let render: &WrappedCaller = self.caller.as_ref();
|
let render: &WrappedCaller = self.caller.as_ref();
|
||||||
|
|
||||||
match render(self) {
|
match render(self) {
|
||||||
None => {
|
None => false,
|
||||||
// the user's component failed. We avoid cycling to the next frame
|
|
||||||
log::error!("Running your component failed! It will no longer receive events.");
|
|
||||||
Err(Error::ComponentFailed)
|
|
||||||
}
|
|
||||||
Some(new_head) => {
|
Some(new_head) => {
|
||||||
// the user's component succeeded. We can safely cycle to the next frame
|
// the user's component succeeded. We can safely cycle to the next frame
|
||||||
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||||
self.frames.cycle_frame();
|
self.frames.cycle_frame();
|
||||||
log::debug!("Successfully rendered component");
|
true
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +199,7 @@ impl Scope {
|
|||||||
&mut self,
|
&mut self,
|
||||||
event: SyntheticEvent,
|
event: SyntheticEvent,
|
||||||
element: ElementId,
|
element: ElementId,
|
||||||
) -> Result<()> {
|
) -> Option<()> {
|
||||||
let listners = self.listeners.borrow_mut();
|
let listners = self.listeners.borrow_mut();
|
||||||
|
|
||||||
let raw_listener = listners.iter().find(|lis| {
|
let raw_listener = listners.iter().find(|lis| {
|
||||||
@@ -226,7 +223,7 @@ impl Scope {
|
|||||||
log::warn!("An event was triggered but there was no listener to handle it");
|
log::warn!("An event was triggered but there was no listener to handle it");
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn root(&self) -> &VNode {
|
pub fn root(&self) -> &VNode {
|
||||||
|
|||||||
@@ -11,22 +11,3 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
|
|||||||
pub fn type_name_of<T>(_: T) -> &'static str {
|
pub fn type_name_of<T>(_: T) -> &'static str {
|
||||||
std::any::type_name::<T>()
|
std::any::type_name::<T>()
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// A helper type that lets scopes be ordered by their height
|
|
||||||
// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
// pub struct HeightMarker {
|
|
||||||
// pub idx: ScopeId,
|
|
||||||
// pub height: u32,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl Ord for HeightMarker {
|
|
||||||
// fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
||||||
// self.height.cmp(&other.height)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl PartialOrd for HeightMarker {
|
|
||||||
// fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
||||||
// Some(self.cmp(other))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
//!
|
//!
|
||||||
//! This module includes just the barebones for a complete VirtualDOM API.
|
//! This module includes just the barebones for a complete VirtualDOM API.
|
||||||
//! Additional functionality is defined in the respective files.
|
//! Additional functionality is defined in the respective files.
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use futures_util::{pin_mut, Future, FutureExt};
|
use futures_util::{pin_mut, Future, FutureExt};
|
||||||
use std::{
|
use std::{
|
||||||
@@ -35,23 +36,13 @@ use std::{
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
scheduler: Scheduler,
|
||||||
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
|
||||||
///
|
|
||||||
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
|
||||||
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
|
||||||
pub scheduler: Scheduler,
|
|
||||||
|
|
||||||
/// The index of the root component
|
|
||||||
/// Should always be the first (gen=0, id=0)
|
|
||||||
base_scope: ScopeId,
|
base_scope: ScopeId,
|
||||||
|
|
||||||
// for managing the props that were used to create the dom
|
root_prop_type: std::any::TypeId,
|
||||||
#[doc(hidden)]
|
|
||||||
_root_prop_type: std::any::TypeId,
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
root_props: Pin<Box<dyn std::any::Any>>,
|
||||||
_root_props: std::pin::Pin<Box<dyn std::any::Any>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
@@ -67,8 +58,8 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// fn Example(cx: Context<SomeProps>) -> VNode {
|
/// fn Example(cx: Context<()>) -> DomTree {
|
||||||
/// cx.render(rsx!{ div{"hello world"} })
|
/// cx.render(rsx!( div { "hello world" } ))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let dom = VirtualDom::new(Example);
|
/// let dom = VirtualDom::new(Example);
|
||||||
@@ -91,25 +82,34 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```
|
/// ```
|
||||||
/// fn Example(cx: Context<SomeProps>) -> VNode {
|
/// #[derive(PartialEq, Props)]
|
||||||
/// cx.render(rsx!{ div{"hello world"} })
|
/// struct SomeProps {
|
||||||
|
/// name: &'static str
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn Example(cx: Context<SomeProps>) -> DomTree {
|
||||||
|
/// cx.render(rsx!{ div{ "hello {cx.name}" } })
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let dom = VirtualDom::new(Example);
|
/// let dom = VirtualDom::new(Example);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
|
/// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||||
|
/// let mutations = dom.rebuild();
|
||||||
|
/// ```
|
||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let scheduler = Scheduler::new();
|
let scheduler = Scheduler::new();
|
||||||
|
|
||||||
let _root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
let _root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
||||||
let _root_prop_type = TypeId::of::<P>();
|
let _root_prop_type = TypeId::of::<P>();
|
||||||
|
|
||||||
let props_ptr = _root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
let props_ptr = _root_props.downcast_ref::<P>().unwrap() as *const P;
|
||||||
|
|
||||||
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
|
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
|
||||||
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
||||||
let name = type_name_of(root);
|
|
||||||
Scope::new(
|
Scope::new(
|
||||||
caller,
|
caller,
|
||||||
myidx,
|
myidx,
|
||||||
@@ -122,9 +122,9 @@ impl VirtualDom {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
base_scope,
|
base_scope,
|
||||||
_root_props,
|
|
||||||
scheduler,
|
scheduler,
|
||||||
_root_prop_type,
|
root_props: _root_props,
|
||||||
|
root_prop_type: _root_prop_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ impl VirtualDom {
|
|||||||
.expect("The base scope should never be moved");
|
.expect("The base scope should never be moved");
|
||||||
|
|
||||||
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||||
if cur_component.run_scope().is_ok() {
|
if cur_component.run_scope() {
|
||||||
diff_machine
|
diff_machine
|
||||||
.stack
|
.stack
|
||||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||||
@@ -204,9 +204,9 @@ impl VirtualDom {
|
|||||||
.get_scope_mut(self.base_scope)
|
.get_scope_mut(self.base_scope)
|
||||||
.expect("The base scope should never be moved");
|
.expect("The base scope should never be moved");
|
||||||
|
|
||||||
cur_component.run_scope().unwrap();
|
if cur_component.run_scope() {
|
||||||
|
diff_machine.diff_scope(self.base_scope).await;
|
||||||
diff_machine.diff_scope(self.base_scope).await;
|
}
|
||||||
|
|
||||||
diff_machine.mutations
|
diff_machine.mutations
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user