mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
wip: clean up, use old approach to components
This commit is contained in:
parent
289d2f2518
commit
2559740463
18 changed files with 180 additions and 247 deletions
|
@ -24,7 +24,7 @@ criterion_group!(mbenches, create_rows);
|
|||
criterion_main!(mbenches);
|
||||
|
||||
fn create_rows(c: &mut Criterion) {
|
||||
static App: FC<()> = |(cx, _)| {
|
||||
static App: FC<()> = |cx, _| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
|
@ -58,7 +58,7 @@ struct RowProps {
|
|||
row_id: usize,
|
||||
label: Label,
|
||||
}
|
||||
fn Row((cx, props): Scope<RowProps>) -> Element {
|
||||
fn Row(cx: Context, props: &RowProps) -> Element {
|
||||
let [adj, col, noun] = props.label.0;
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
|
|
9
packages/core/examples/props_expand.rs
Normal file
9
packages/core/examples/props_expand.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use dioxus_core as dioxus;
|
||||
use dioxus_core_macro::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ChildProps<'a> {
|
||||
name: &'a str,
|
||||
}
|
|
@ -7,7 +7,7 @@ fn main() {
|
|||
let _ = VirtualDom::new(Parent);
|
||||
}
|
||||
|
||||
fn Parent((cx, _): Scope<()>) -> Element {
|
||||
fn Parent(cx: Context, props: &()) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -22,7 +22,7 @@ struct ChildProps<'a> {
|
|||
name: &'a str,
|
||||
}
|
||||
|
||||
fn Child((cx, props): Scope<ChildProps>) -> Element {
|
||||
fn Child(cx: Context, props: &ChildProps) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "it's nested" }
|
||||
|
@ -36,7 +36,7 @@ struct Grandchild<'a> {
|
|||
name: &'a str,
|
||||
}
|
||||
|
||||
fn Child2((cx, props): Scope<Grandchild>) -> Element {
|
||||
fn Child2(cx: Context, props: &Grandchild) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "Hello {props.name}!" }
|
||||
})
|
||||
|
|
|
@ -6,39 +6,6 @@
|
|||
//! that ensures compile-time required and optional fields on cx.
|
||||
|
||||
use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren};
|
||||
/// A component is a wrapper around a Context and some Props that share a lifetime
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// With memoized state:
|
||||
/// ```rust
|
||||
/// struct State {}
|
||||
///
|
||||
/// fn Example((cx, props): Scope<State>) -> DomTree {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// With borrowed state:
|
||||
/// ```rust
|
||||
/// struct State<'a> {
|
||||
/// name: &'a str
|
||||
/// }
|
||||
///
|
||||
/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// With owned state as a closure:
|
||||
/// ```rust
|
||||
/// static Example: FC<()> = |(cx, props)| {
|
||||
/// // ...
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
pub type Scope<'a, T> = (Context<'a>, &'a T);
|
||||
|
||||
pub struct FragmentProps<'a> {
|
||||
children: ScopeChildren<'a>,
|
||||
|
@ -99,7 +66,7 @@ impl<'a> Properties for FragmentProps<'a> {
|
|||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
///
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
pub fn Fragment<'a>(cx: Context<'a>, props: &'a FragmentProps<'a>) -> Element {
|
||||
cx.render(Some(LazyNodes::new(|f| {
|
||||
f.fragment_from_iter(&props.children)
|
||||
})))
|
||||
|
@ -170,6 +137,7 @@ impl EmptyBuilder {
|
|||
|
||||
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||
/// to initialize a component's props.
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &T) -> Element) -> T::Builder {
|
||||
// pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
|
||||
use crate::innerlude::*;
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use slab::Slab;
|
||||
use DomEdit::*;
|
||||
|
||||
/// Our DiffMachine is an iterative tree differ.
|
||||
|
@ -609,7 +610,7 @@ impl<'bump> ScopeArena {
|
|||
|
||||
// make sure the component's caller function is up to date
|
||||
let scope = self.get_scope(&scope_addr).unwrap();
|
||||
scope.update_vcomp(new);
|
||||
let mut items = scope.items.borrow_mut();
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
let props_are_the_same = todo!("reworking component memoization");
|
||||
|
@ -1280,7 +1281,7 @@ impl<'bump> ScopeArena {
|
|||
}
|
||||
|
||||
/// Adds a listener closure to a scope during diff.
|
||||
fn attach_listener_to_scope(&'bump self, listener: &'bump Listener<'bump>, scope: &ScopeState) {
|
||||
fn attach_listener_to_scope(&'bump self, listener: &'bump Listener<'bump>, scope: &Scope) {
|
||||
let long_listener = unsafe { std::mem::transmute(listener) };
|
||||
scope.items.borrow_mut().listeners.push(long_listener)
|
||||
}
|
||||
|
|
|
@ -49,10 +49,6 @@ impl<'bump> DiffStack<'bump> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.instructions.is_empty()
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
|
||||
self.instructions.pop()
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ pub(crate) mod innerlude {
|
|||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type Element = Option<NodeLink>;
|
||||
pub type FC<P> = for<'a> fn(Scope<'a, P>) -> Element;
|
||||
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
|
@ -50,9 +50,11 @@ pub use crate::innerlude::{
|
|||
};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
||||
pub use crate::innerlude::Context;
|
||||
pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
|
||||
pub use crate::innerlude::{
|
||||
DioxusElement, Element, LazyNodes, NodeFactory, Scope, ScopeChildren, FC,
|
||||
};
|
||||
pub use crate::nodes::VNode;
|
||||
pub use crate::VirtualDom;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
//! cheap and *very* fast to construct - building a full tree should be quick.
|
||||
|
||||
use crate::{
|
||||
innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeState},
|
||||
innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId},
|
||||
lazynodes::LazyNodes,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
|
@ -344,31 +344,29 @@ pub struct Listener<'bump> {
|
|||
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
|
||||
}
|
||||
|
||||
pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
|
||||
/// Virtual Components for custom user-defined components
|
||||
/// Only supports the functional syntax
|
||||
pub struct VComponent<'src> {
|
||||
pub key: Option<&'src str>,
|
||||
|
||||
pub associated_scope: Cell<Option<ScopeId>>,
|
||||
// pub associated_scope: Cell<Option<*mut ScopeInner>>,
|
||||
|
||||
// Function pointer to the FC that was used to generate this component
|
||||
pub user_fc: *const (),
|
||||
|
||||
pub(crate) can_memoize: bool,
|
||||
|
||||
pub(crate) hard_allocation: Cell<Option<*const ()>>,
|
||||
|
||||
// Raw pointer into the bump arena for the props of the component
|
||||
pub(crate) raw_props: *const (),
|
||||
pub(crate) bump_props: *const (),
|
||||
|
||||
// during the "teardown" process we'll take the caller out so it can be dropped properly
|
||||
pub(crate) caller: Option<VCompCaller<'src>>,
|
||||
pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
|
||||
}
|
||||
|
||||
pub enum VCompCaller<'src> {
|
||||
Borrowed(BumpBox<'src, dyn for<'b> Fn(&'b ScopeState) -> Element + 'src>),
|
||||
Owned(Box<dyn for<'b> Fn(&'b ScopeState) -> Element>),
|
||||
}
|
||||
|
||||
pub struct VSuspended<'a> {
|
||||
pub task_id: u64,
|
||||
pub dom_id: Cell<Option<ElementId>>,
|
||||
|
@ -511,7 +509,7 @@ impl<'a> NodeFactory<'a> {
|
|||
|
||||
pub fn component<P>(
|
||||
&self,
|
||||
component: fn(Scope<'a, P>) -> Element,
|
||||
component: fn(Context<'a>, &'a P) -> Element,
|
||||
props: P,
|
||||
key: Option<Arguments>,
|
||||
) -> VNode<'a>
|
||||
|
@ -530,18 +528,8 @@ impl<'a> NodeFactory<'a> {
|
|||
|
||||
let bump = self.bump();
|
||||
|
||||
// let p = BumpBox::new_in(x, a)
|
||||
|
||||
// the best place to allocate the props are the other component's arena
|
||||
// the second best place is the global allocator
|
||||
|
||||
// // if the props are static
|
||||
// let boxed = if P::IS_STATIC {
|
||||
// todo!()
|
||||
// } else {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// later, we'll do a hard allocation
|
||||
let raw_ptr = bump.alloc(props);
|
||||
// let caller = Box::new(|f: &ScopeInner| -> Element {
|
||||
// //
|
||||
// component((f, &props))
|
||||
|
@ -574,26 +562,26 @@ impl<'a> NodeFactory<'a> {
|
|||
|
||||
let key = key.map(|f| self.raw_text(f).0);
|
||||
|
||||
let caller = match P::IS_STATIC {
|
||||
true => {
|
||||
// it just makes sense to box the props
|
||||
let boxed_props: Box<P> = Box::new(props);
|
||||
let props_we_know_are_static = todo!();
|
||||
VCompCaller::Owned(Box::new(|f| {
|
||||
//
|
||||
// let caller = match P::IS_STATIC {
|
||||
// true => {
|
||||
// // it just makes sense to box the props
|
||||
// let boxed_props: Box<P> = Box::new(props);
|
||||
// let props_we_know_are_static = todo!();
|
||||
// VCompCaller::Owned(Box::new(|f| {
|
||||
// //
|
||||
|
||||
let p = todo!();
|
||||
// let p = todo!();
|
||||
|
||||
todo!()
|
||||
}))
|
||||
}
|
||||
false => VCompCaller::Borrowed({
|
||||
//
|
||||
// todo!()
|
||||
// }))
|
||||
// }
|
||||
// false => VCompCaller::Borrowed({
|
||||
// //
|
||||
|
||||
todo!()
|
||||
// let caller = bump.alloc()
|
||||
}),
|
||||
};
|
||||
// todo!()
|
||||
// // let caller = bump.alloc()
|
||||
// }),
|
||||
// };
|
||||
|
||||
todo!()
|
||||
// let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
|
||||
|
|
|
@ -30,7 +30,7 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
|
|||
/// cx.render(rsx!{ div {"Hello, {props.name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
pub type Context<'a> = &'a ScopeState;
|
||||
pub type Context<'a> = &'a Scope;
|
||||
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
///
|
||||
|
@ -41,7 +41,7 @@ pub type Context<'a> = &'a ScopeState;
|
|||
///
|
||||
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
|
||||
/// use case they might have.
|
||||
pub struct ScopeState {
|
||||
pub struct Scope {
|
||||
// Book-keeping about our spot in the arena
|
||||
|
||||
// safety:
|
||||
|
@ -49,7 +49,7 @@ pub struct ScopeState {
|
|||
// pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
|
||||
// this is just a bit of a hack to not need an Rc to the ScopeArena.
|
||||
// todo: replace this will ScopeId and provide a connection to scope arena directly
|
||||
pub(crate) parent_scope: Option<*mut ScopeState>,
|
||||
pub(crate) parent_scope: Option<*mut Scope>,
|
||||
|
||||
pub(crate) our_arena_idx: ScopeId,
|
||||
|
||||
|
@ -62,8 +62,6 @@ pub struct ScopeState {
|
|||
// The double-buffering situation that we will use
|
||||
pub(crate) frames: [Bump; 2],
|
||||
|
||||
pub(crate) vcomp: *const VComponent<'static>,
|
||||
|
||||
pub(crate) old_root: RefCell<Option<NodeLink>>,
|
||||
pub(crate) new_root: RefCell<Option<NodeLink>>,
|
||||
|
||||
|
@ -89,6 +87,8 @@ pub struct SelfReferentialItems<'a> {
|
|||
pub(crate) cached_nodes_old: Vec<VNode<'a>>,
|
||||
pub(crate) cached_nodes_new: Vec<VNode<'a>>,
|
||||
|
||||
pub(crate) caller: &'a dyn Fn(&Scope) -> Element,
|
||||
|
||||
pub(crate) generation: Cell<u32>,
|
||||
|
||||
pub(crate) listeners: Vec<&'a Listener<'a>>,
|
||||
|
@ -99,7 +99,7 @@ pub struct SelfReferentialItems<'a> {
|
|||
}
|
||||
|
||||
// Public methods exposed to libraries and components
|
||||
impl ScopeState {
|
||||
impl Scope {
|
||||
/// Get the root VNode for this Scope.
|
||||
///
|
||||
/// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
|
||||
|
@ -440,7 +440,7 @@ Functions prefixed with "use" should never be called conditionally.
|
|||
"###;
|
||||
|
||||
// Important internal methods
|
||||
impl ScopeState {
|
||||
impl Scope {
|
||||
/// Give out our self-referential item with our own borrowed lifetime
|
||||
pub(crate) fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
|
||||
todo!()
|
||||
|
@ -527,14 +527,4 @@ impl ScopeState {
|
|||
// Some(cur)
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) fn update_vcomp(&self, vcomp: &VComponent) {
|
||||
let f: *const _ = vcomp;
|
||||
todo!()
|
||||
// self.vcomp = unsafe { std::mem::transmute(f) };
|
||||
}
|
||||
|
||||
pub(crate) fn load_vcomp<'a>(&'a mut self) -> &'a VComponent<'a> {
|
||||
unsafe { std::mem::transmute(&*self.vcomp) }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use slab::Slab;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
|
@ -19,7 +20,7 @@ pub struct Heuristic {
|
|||
// has an internal heuristics engine to pre-allocate arenas to the right size
|
||||
pub(crate) struct ScopeArena {
|
||||
bump: Bump,
|
||||
scopes: Vec<*mut ScopeState>,
|
||||
scopes: Vec<*mut Scope>,
|
||||
free_scopes: Vec<ScopeId>,
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
}
|
||||
|
@ -30,19 +31,20 @@ impl ScopeArena {
|
|||
bump: Bump::new(),
|
||||
scopes: Vec::new(),
|
||||
free_scopes: Vec::new(),
|
||||
|
||||
sender,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeState> {
|
||||
pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
|
||||
unsafe { Some(&*self.scopes[id.0]) }
|
||||
}
|
||||
|
||||
pub fn new_with_key(
|
||||
&mut self,
|
||||
fc_ptr: *const (),
|
||||
vcomp: &VComponent,
|
||||
parent_scope: Option<*mut ScopeState>,
|
||||
caller: *mut dyn Fn(&Scope) -> Element,
|
||||
parent_scope: Option<*mut Scope>,
|
||||
height: u32,
|
||||
subtree: u32,
|
||||
) -> ScopeId {
|
||||
|
@ -55,9 +57,10 @@ impl ScopeArena {
|
|||
} else {
|
||||
let id = ScopeId(self.scopes.len());
|
||||
|
||||
let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) };
|
||||
// cast off the lifetime
|
||||
let caller = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
let new_scope = ScopeState {
|
||||
let new_scope = Scope {
|
||||
sender: self.sender.clone(),
|
||||
parent_scope,
|
||||
our_arena_idx: id,
|
||||
|
@ -65,7 +68,6 @@ impl ScopeArena {
|
|||
subtree: Cell::new(subtree),
|
||||
is_subtree_root: Cell::new(false),
|
||||
frames: [Bump::default(), Bump::default()],
|
||||
vcomp,
|
||||
|
||||
hooks: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
|
@ -78,7 +80,8 @@ impl ScopeArena {
|
|||
pending_effects: Default::default(),
|
||||
cached_nodes_old: Default::default(),
|
||||
generation: Default::default(),
|
||||
cached_nodes_new: todo!(),
|
||||
cached_nodes_new: Default::default(),
|
||||
caller,
|
||||
}),
|
||||
old_root: todo!(),
|
||||
new_root: todo!(),
|
||||
|
@ -90,7 +93,7 @@ impl ScopeArena {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeState> {
|
||||
pub fn try_remove(&self, id: &ScopeId) -> Option<Scope> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
|
@ -154,4 +157,58 @@ impl ScopeArena {
|
|||
.map(|li| unsafe { &*li })
|
||||
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
|
||||
let scope = self
|
||||
.get_scope(id)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
self.ensure_drop_safety(id);
|
||||
|
||||
// Safety:
|
||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||
unsafe { scope.hooks.reset() };
|
||||
|
||||
// Safety:
|
||||
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
||||
unsafe { scope.reset_wip_frame() };
|
||||
|
||||
let mut items = scope.items.borrow_mut();
|
||||
|
||||
// just forget about our suspended nodes while we're at it
|
||||
items.suspended_nodes.clear();
|
||||
|
||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||
debug_assert!(items.listeners.is_empty());
|
||||
debug_assert!(items.suspended_nodes.is_empty());
|
||||
debug_assert!(items.borrowed_props.is_empty());
|
||||
|
||||
log::debug!("Borrowed stuff is successfully cleared");
|
||||
|
||||
// temporarily cast the vcomponent to the right lifetime
|
||||
// let vcomp = scope.load_vcomp();
|
||||
|
||||
let render: &dyn Fn(&Scope) -> Element = todo!();
|
||||
|
||||
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
||||
if let Some(key) = render(scope) {
|
||||
// todo!("attach the niode");
|
||||
// let new_head = builder.into_vnode(NodeFactory {
|
||||
// bump: &scope.frames.wip_frame().bump,
|
||||
// });
|
||||
// log::debug!("Render is successful");
|
||||
|
||||
// the user's component succeeded. We can safely cycle to the next frame
|
||||
// scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||
// scope.frames.cycle_frame();
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,6 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
|
|||
Cell::new(None)
|
||||
}
|
||||
|
||||
pub fn type_name_of<T>(_: T) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
|
|
|
@ -20,21 +20,13 @@
|
|||
//! Additional functionality is defined in the respective files.
|
||||
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use futures_util::{Future, StreamExt};
|
||||
use fxhash::FxHashSet;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, UnsafeCell},
|
||||
collections::{HashSet, VecDeque},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{any::Any, collections::VecDeque};
|
||||
|
||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||
///
|
||||
|
@ -69,10 +61,7 @@ use std::{
|
|||
pub struct VirtualDom {
|
||||
base_scope: ScopeId,
|
||||
|
||||
_root_props: Rc<dyn Any>,
|
||||
|
||||
// we need to keep the allocation around, but we don't necessarily use it
|
||||
_root_caller: Box<dyn Any>,
|
||||
_root_caller: *mut dyn Fn(&Scope) -> Element,
|
||||
|
||||
pub(crate) scopes: ScopeArena,
|
||||
|
||||
|
@ -156,30 +145,22 @@ impl VirtualDom {
|
|||
sender: UnboundedSender<SchedulerMsg>,
|
||||
receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
) -> Self {
|
||||
let mut scopes = ScopeArena::new(sender);
|
||||
let mut scopes = ScopeArena::new(sender.clone());
|
||||
|
||||
let base_scope = scopes.new_with_key(
|
||||
//
|
||||
root as _,
|
||||
todo!(),
|
||||
// boxed_comp.as_ref(),
|
||||
None,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
let caller = Box::new(move |f: &Scope| -> Element { root(f, &root_props) });
|
||||
let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
|
||||
let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
|
||||
|
||||
Self {
|
||||
scopes,
|
||||
base_scope,
|
||||
receiver,
|
||||
sender,
|
||||
|
||||
_root_props: todo!(),
|
||||
_root_caller: todo!(),
|
||||
|
||||
// todo: clean this up manually?
|
||||
_root_caller: caller_ref,
|
||||
pending_messages: VecDeque::new(),
|
||||
pending_futures: Default::default(),
|
||||
dirty_scopes: Default::default(),
|
||||
sender,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,7 +170,7 @@ impl VirtualDom {
|
|||
/// directly.
|
||||
///
|
||||
/// # Example
|
||||
pub fn base_scope(&self) -> &ScopeState {
|
||||
pub fn base_scope(&self) -> &Scope {
|
||||
self.get_scope(&self.base_scope).unwrap()
|
||||
}
|
||||
|
||||
|
@ -199,7 +180,7 @@ impl VirtualDom {
|
|||
///
|
||||
///
|
||||
///
|
||||
pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a ScopeState> {
|
||||
pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> {
|
||||
self.scopes.get_scope(id)
|
||||
}
|
||||
|
||||
|
@ -402,7 +383,7 @@ impl VirtualDom {
|
|||
|
||||
log::debug!("about to run scope {:?}", scopeid);
|
||||
|
||||
if self.run_scope(&scopeid) {
|
||||
if self.scopes.run_scope(&scopeid) {
|
||||
let scope = self.scopes.get_scope(&scopeid).unwrap();
|
||||
let (old, new) = (scope.wip_head(), scope.fin_head());
|
||||
diff_state.stack.scope_stack.push(scopeid);
|
||||
|
@ -503,7 +484,7 @@ impl VirtualDom {
|
|||
pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> {
|
||||
log::debug!("hard diff {:?}", scope_id);
|
||||
|
||||
if self.run_scope(scope_id) {
|
||||
if self.scopes.run_scope(scope_id) {
|
||||
let mut diff_machine = DiffState::new(Mutations::new());
|
||||
|
||||
diff_machine.force_diff = true;
|
||||
|
@ -515,61 +496,6 @@ impl VirtualDom {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn run_scope(&self, id: &ScopeId) -> bool {
|
||||
let scope = self
|
||||
.scopes
|
||||
.get_scope(id)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
self.scopes.ensure_drop_safety(id);
|
||||
|
||||
// Safety:
|
||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||
unsafe { scope.hooks.reset() };
|
||||
|
||||
// Safety:
|
||||
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
|
||||
unsafe { scope.reset_wip_frame() };
|
||||
|
||||
let mut items = scope.items.borrow_mut();
|
||||
|
||||
// just forget about our suspended nodes while we're at it
|
||||
items.suspended_nodes.clear();
|
||||
|
||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||
debug_assert!(items.listeners.is_empty());
|
||||
debug_assert!(items.suspended_nodes.is_empty());
|
||||
debug_assert!(items.borrowed_props.is_empty());
|
||||
|
||||
log::debug!("Borrowed stuff is successfully cleared");
|
||||
|
||||
// temporarily cast the vcomponent to the right lifetime
|
||||
// let vcomp = scope.load_vcomp();
|
||||
|
||||
let render: &dyn Fn(&ScopeState) -> Element = todo!();
|
||||
|
||||
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
||||
if let Some(key) = render(scope) {
|
||||
// todo!("attach the niode");
|
||||
// let new_head = builder.into_vnode(NodeFactory {
|
||||
// bump: &scope.frames.wip_frame().bump,
|
||||
// });
|
||||
// log::debug!("Render is successful");
|
||||
|
||||
// the user's component succeeded. We can safely cycle to the next frame
|
||||
// scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||
// scope.frames.cycle_frame();
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SchedulerMsg {
|
||||
|
|
|
@ -10,7 +10,7 @@ fn test_borrowed_state() {
|
|||
let _ = VirtualDom::new(Parent);
|
||||
}
|
||||
|
||||
fn Parent((cx, _): Scope<()>) -> Element {
|
||||
fn Parent(cx: Context, props: &()) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
@ -28,7 +28,7 @@ struct ChildProps<'a> {
|
|||
name: &'a str,
|
||||
}
|
||||
|
||||
fn Child((cx, props): Scope<ChildProps>) -> Element {
|
||||
fn Child(cx: Context, props: &ChildProps) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "it's nested" }
|
||||
|
@ -42,7 +42,7 @@ struct Grandchild<'a> {
|
|||
name: &'a str,
|
||||
}
|
||||
|
||||
fn Child2((cx, props): Scope<Grandchild>) -> Element {
|
||||
fn Child2(cx: Context, props: &Grandchild) -> Element {
|
||||
cx.render(rsx! {
|
||||
div { "Hello {props.name}!" }
|
||||
})
|
||||
|
|
|
@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
|
|||
|
||||
#[test]
|
||||
fn test_original_diff() {
|
||||
static APP: FC<()> = |(cx, props)| {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
|
@ -57,7 +57,7 @@ fn test_original_diff() {
|
|||
|
||||
#[test]
|
||||
fn create() {
|
||||
static APP: FC<()> = |(cx, props)| {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
|
@ -120,7 +120,7 @@ fn create() {
|
|||
|
||||
#[test]
|
||||
fn create_list() {
|
||||
static APP: FC<()> = |(cx, props)| {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
{(0..3).map(|f| rsx!{ div {
|
||||
"hello"
|
||||
|
@ -169,7 +169,7 @@ fn create_list() {
|
|||
|
||||
#[test]
|
||||
fn create_simple() {
|
||||
static APP: FC<()> = |(cx, props)| {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
div {}
|
||||
|
@ -207,7 +207,7 @@ fn create_simple() {
|
|||
}
|
||||
#[test]
|
||||
fn create_components() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
Child { "abc1" }
|
||||
Child { "abc2" }
|
||||
|
@ -220,7 +220,7 @@ fn create_components() {
|
|||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element {
|
||||
fn Child<'a>(cx: Context<'a>, props: &ChildProps<'a>) -> Element {
|
||||
cx.render(rsx! {
|
||||
h1 {}
|
||||
div { {&props.children} }
|
||||
|
@ -273,7 +273,7 @@ fn create_components() {
|
|||
}
|
||||
#[test]
|
||||
fn anchors() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
{true.then(|| rsx!{ div { "hello" } })}
|
||||
{false.then(|| rsx!{ div { "goodbye" } })}
|
||||
|
@ -302,17 +302,18 @@ fn anchors() {
|
|||
|
||||
#[test]
|
||||
fn suspended() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
let val = use_suspense(cx, || async {}, |p| todo!());
|
||||
todo!()
|
||||
// static App: FC<()> = |cx, props| {
|
||||
// let val = use_suspense(cx, || async {}, |p| todo!());
|
||||
|
||||
cx.render(rsx! { {val} })
|
||||
};
|
||||
// cx.render(rsx! { {val} })
|
||||
// };
|
||||
|
||||
let mut dom = new_dom(App, ());
|
||||
let mutations = dom.rebuild();
|
||||
// let mut dom = new_dom(App, ());
|
||||
// let mutations = dom.rebuild();
|
||||
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
|
||||
);
|
||||
// assert_eq!(
|
||||
// mutations.edits,
|
||||
// [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
|
||||
// );
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ mod test_logging;
|
|||
|
||||
#[test]
|
||||
fn please_work() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
hidden: "true"
|
||||
|
@ -27,7 +27,7 @@ fn please_work() {
|
|||
})
|
||||
};
|
||||
|
||||
static Child: FC<()> = |(cx, props)| {
|
||||
static Child: FC<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div { "child" }
|
||||
})
|
||||
|
@ -35,6 +35,4 @@ fn please_work() {
|
|||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
dom.rebuild();
|
||||
|
||||
println!("{}", dom);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ fn manual_diffing() {
|
|||
value: Shared<&'static str>,
|
||||
}
|
||||
|
||||
static App: FC<AppProps> = |(cx, props)| {
|
||||
static App: FC<AppProps> = |cx, props| {
|
||||
let val = props.value.lock().unwrap();
|
||||
cx.render(rsx! { div { "{val}" } })
|
||||
};
|
||||
|
@ -37,7 +37,7 @@ fn manual_diffing() {
|
|||
|
||||
*value.lock().unwrap() = "goodbye";
|
||||
|
||||
let edits = dom.diff();
|
||||
let edits = dom.rebuild();
|
||||
|
||||
log::debug!("edits: {:?}", edits);
|
||||
}
|
||||
|
|
|
@ -13,12 +13,12 @@ mod test_logging;
|
|||
fn shared_state_test() {
|
||||
struct MySharedState(&'static str);
|
||||
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.provide_state(MySharedState("world!"));
|
||||
cx.render(rsx!(Child {}))
|
||||
};
|
||||
|
||||
static Child: FC<()> = |(cx, props)| {
|
||||
static Child: FC<()> = |cx, props| {
|
||||
let shared = cx.consume_state::<MySharedState>()?;
|
||||
cx.render(rsx!("Hello, {shared.0}"))
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ use dioxus_html as dioxus_elements;
|
|||
|
||||
#[test]
|
||||
fn app_runs() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
//
|
||||
cx.render(rsx!( div{"hello"} ))
|
||||
};
|
||||
|
@ -28,7 +28,7 @@ fn app_runs() {
|
|||
|
||||
#[test]
|
||||
fn fragments_work() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
div{"hello"}
|
||||
div{"goodbye"}
|
||||
|
@ -42,7 +42,7 @@ fn fragments_work() {
|
|||
|
||||
#[test]
|
||||
fn lists_work() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
{(0..6).map(|f| rsx!(span{ "{f}" }))}
|
||||
|
@ -55,7 +55,7 @@ fn lists_work() {
|
|||
|
||||
#[test]
|
||||
fn conditional_rendering() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
{true.then(|| rsx!(span{ "a" }))}
|
||||
|
@ -72,13 +72,13 @@ fn conditional_rendering() {
|
|||
|
||||
#[test]
|
||||
fn child_components() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
static App: FC<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
{true.then(|| rsx!(Child { }))}
|
||||
{false.then(|| rsx!(Child { }))}
|
||||
))
|
||||
};
|
||||
static Child: FC<()> = |(cx, props)| {
|
||||
static Child: FC<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
h1 {"goodbye"}
|
||||
|
@ -91,13 +91,14 @@ fn child_components() {
|
|||
|
||||
#[test]
|
||||
fn suspended_works() {
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
|
||||
// let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
|
||||
cx.render(rsx!("hello" { title }))
|
||||
};
|
||||
todo!()
|
||||
// static App: FC<()> = |cx, props| {
|
||||
// let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
|
||||
// // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
|
||||
// cx.render(rsx!("hello" { title }))
|
||||
// };
|
||||
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
let edits = vdom.rebuild();
|
||||
dbg!(edits);
|
||||
// let mut vdom = VirtualDom::new(App);
|
||||
// let edits = vdom.rebuild();
|
||||
// dbg!(edits);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue