mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 14:44:12 +00:00
feat: simple tests passing
This commit is contained in:
parent
23603aaaf5
commit
4a31b29703
27 changed files with 1568 additions and 375 deletions
|
@ -19,7 +19,7 @@ members = [
|
|||
"packages/rsx",
|
||||
"packages/native-core",
|
||||
"packages/native-core-macro",
|
||||
"packages/edit-stream",
|
||||
# "packages/edit-stream",
|
||||
"docs/guide",
|
||||
]
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ use dioxus_rsx as rsx;
|
|||
#[proc_macro]
|
||||
pub fn format_args_f(input: TokenStream) -> TokenStream {
|
||||
use rsx::*;
|
||||
let item = parse_macro_input!(input as IfmtInput);
|
||||
format_args_f_impl(item)
|
||||
format_args_f_impl(parse_macro_input!(input as IfmtInput))
|
||||
.unwrap_or_else(|err| err.to_compile_error())
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -424,7 +424,6 @@ impl<P> Clone for Scope<'_, P> {
|
|||
}
|
||||
|
||||
impl<'a, P> std::ops::Deref for Scope<'a, P> {
|
||||
// rust will auto deref again to the original 'a lifetime at the call site
|
||||
type Target = &'a ScopeState;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.scope
|
||||
|
@ -621,9 +620,6 @@ impl ScopeState {
|
|||
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
||||
pub fn needs_update(&self) {
|
||||
self.needs_update_any(self.scope_id());
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::cell::Cell;
|
||||
|
||||
use futures_util::Future;
|
||||
|
||||
use crate::{
|
||||
component::Component,
|
||||
component::{Component, ComponentFn, Dummy, IntoComponent},
|
||||
element::Element,
|
||||
scopes::{Scope, ScopeState},
|
||||
};
|
||||
|
@ -12,21 +14,18 @@ pub trait AnyProps {
|
|||
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) struct VComponentProps<P> {
|
||||
pub render_fn: Component<P>,
|
||||
pub(crate) struct VComponentProps<P, F: Future<Output = ()> = Dummy> {
|
||||
pub render_fn: ComponentFn<P, F>,
|
||||
pub memo: unsafe fn(&P, &P) -> bool,
|
||||
pub props: Scope<P>,
|
||||
pub props: *const P,
|
||||
}
|
||||
|
||||
impl VComponentProps<()> {
|
||||
pub fn new_empty(render_fn: Component<()>) -> Self {
|
||||
Self {
|
||||
render_fn,
|
||||
render_fn: render_fn.into_component(),
|
||||
memo: <() as PartialEq>::eq,
|
||||
props: Scope {
|
||||
props: (),
|
||||
state: Cell::new(std::ptr::null_mut()),
|
||||
},
|
||||
props: std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,10 +34,10 @@ impl<P> VComponentProps<P> {
|
|||
pub(crate) fn new(
|
||||
render_fn: Component<P>,
|
||||
memo: unsafe fn(&P, &P) -> bool,
|
||||
props: Scope<P>,
|
||||
props: *const P,
|
||||
) -> Self {
|
||||
Self {
|
||||
render_fn,
|
||||
render_fn: render_fn.into_component(),
|
||||
memo,
|
||||
props,
|
||||
}
|
||||
|
@ -62,10 +61,18 @@ impl<P> AnyProps for VComponentProps<P> {
|
|||
|
||||
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
|
||||
// Make sure the scope ptr is not null
|
||||
self.props.state.set(scope);
|
||||
// self.props.state.set(scope);
|
||||
|
||||
let scope = Scope {
|
||||
props: unsafe { &*self.props },
|
||||
scope,
|
||||
};
|
||||
|
||||
// Call the render function directly
|
||||
// todo: implement async
|
||||
(self.render_fn)(unsafe { std::mem::transmute(&self.props) })
|
||||
match self.render_fn {
|
||||
ComponentFn::Sync(f) => f(scope),
|
||||
ComponentFn::Async(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +1,32 @@
|
|||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::{
|
||||
nodes::{Template, VNode},
|
||||
virtualdom::VirtualDom,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct ElementId(pub usize);
|
||||
|
||||
// pub struct ElementId(pub NonZeroUsize);
|
||||
|
||||
// impl Default for ElementId {
|
||||
// fn default() -> Self {
|
||||
// Self(NonZeroUsize::new(1).unwrap())
|
||||
// }
|
||||
// }
|
||||
|
||||
pub struct ElementArena {
|
||||
counter: usize,
|
||||
pub struct ElementPath {
|
||||
pub template: *mut VNode<'static>,
|
||||
pub element: usize,
|
||||
}
|
||||
|
||||
impl Default for ElementArena {
|
||||
fn default() -> Self {
|
||||
Self { counter: 1 }
|
||||
}
|
||||
}
|
||||
impl VirtualDom {
|
||||
pub fn next_element(&mut self, template: &VNode) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
|
||||
entry.insert(ElementPath {
|
||||
template: template as *const _ as *mut _,
|
||||
element: id,
|
||||
});
|
||||
|
||||
impl ElementArena {
|
||||
pub fn next(&mut self) -> ElementId {
|
||||
let id = self.counter;
|
||||
self.counter += 1;
|
||||
ElementId(id)
|
||||
}
|
||||
|
||||
pub fn cleanup_element(&mut self, id: ElementId) {
|
||||
self.elements.remove(id.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@ use std::cell::Cell;
|
|||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::nodes::VTemplate;
|
||||
use crate::nodes::VNode;
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub node: Cell<*const VTemplate<'static>>,
|
||||
pub node: Cell<*const VNode<'static>>,
|
||||
}
|
||||
impl BumpFrame {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
|
|
|
@ -2,6 +2,44 @@
|
|||
// fn into_component_type(self) -> ComponentType;
|
||||
// }
|
||||
|
||||
use futures_util::Future;
|
||||
|
||||
use crate::{element::Element, scopes::Scope};
|
||||
|
||||
pub type Component<T = ()> = fn(&Scope<T>) -> Element;
|
||||
pub type Component<T = ()> = fn(Scope<T>) -> Element;
|
||||
|
||||
pub enum ComponentFn<T, F: Future<Output = ()> = Dummy> {
|
||||
Sync(fn(Scope<T>) -> Element),
|
||||
Async(fn(Scope<T>) -> F),
|
||||
}
|
||||
|
||||
pub trait IntoComponent<T, A = ()> {
|
||||
fn into_component(self) -> ComponentFn<T>;
|
||||
}
|
||||
|
||||
impl<T> IntoComponent<T> for fn(Scope<T>) -> Element {
|
||||
fn into_component(self) -> ComponentFn<T> {
|
||||
ComponentFn::Sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AsyncMarker;
|
||||
impl<'a, T, F: Future<Output = Element<'a>>> IntoComponent<T, AsyncMarker>
|
||||
for fn(&'a Scope<T>) -> F
|
||||
{
|
||||
fn into_component(self) -> ComponentFn<T> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dummy;
|
||||
impl Future for Dummy {
|
||||
type Output = ();
|
||||
|
||||
fn poll(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Self::Output> {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,30 @@
|
|||
use crate::VirtualDom;
|
||||
|
||||
use crate::any_props::VComponentProps;
|
||||
use crate::arena::ElementArena;
|
||||
use crate::component::Component;
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{
|
||||
AttributeLocation, DynamicNode, DynamicNodeKind, Template, TemplateId, TemplateNode,
|
||||
};
|
||||
use crate::scopes::Scope;
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
nodes::VTemplate,
|
||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
||||
};
|
||||
use slab::Slab;
|
||||
use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
||||
use crate::virtualdom::VirtualDom;
|
||||
|
||||
impl VirtualDom {
|
||||
/// Create this template and write its mutations
|
||||
pub fn create<'a>(
|
||||
&mut self,
|
||||
mutations: &mut Vec<Mutation<'a>>,
|
||||
template: &'a VTemplate<'a>,
|
||||
template: &'a VNode<'a>,
|
||||
) -> usize {
|
||||
// The best renderers will have tempaltes prehydrated
|
||||
// The best renderers will have templates prehydrated
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
if !self.templates.contains_key(&template.template.id) {
|
||||
self.create_static_template(mutations, template.template);
|
||||
for node in template.template.roots {
|
||||
self.create_static_node(mutations, template, node);
|
||||
}
|
||||
|
||||
mutations.push(SaveTemplate {
|
||||
name: template.template.id,
|
||||
m: template.template.roots.len(),
|
||||
});
|
||||
|
||||
self.templates
|
||||
.insert(template.template.id, template.template.clone());
|
||||
}
|
||||
|
||||
// Walk the roots backwards, creating nodes and assigning IDs
|
||||
|
@ -38,28 +35,37 @@ impl VirtualDom {
|
|||
let mut on_stack = 0;
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
on_stack += match root {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text(_) => {
|
||||
mutations.push(LoadTemplate {
|
||||
name: template.template.id,
|
||||
index: root_idx,
|
||||
});
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
TemplateNode::Dynamic(id) => {
|
||||
self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
|
||||
}
|
||||
TemplateNode::DynamicText { .. }
|
||||
| TemplateNode::Element { .. }
|
||||
| TemplateNode::Text(_) => 1,
|
||||
|
||||
TemplateNode::DynamicText { .. } => 1,
|
||||
};
|
||||
|
||||
// we're on top of a node that has a dynamic attribute for a descendant
|
||||
// Set that attribute now before the stack gets in a weird state
|
||||
while let Some(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
|
||||
// Attach all the elementIDs to the nodes with dynamic content
|
||||
let id = self.elements.next();
|
||||
mutations.push(Mutation::AssignId {
|
||||
let id = self.next_element(template);
|
||||
|
||||
mutations.push(AssignId {
|
||||
path: &loc.path[1..],
|
||||
id,
|
||||
});
|
||||
|
||||
loc.mounted_element.set(id);
|
||||
|
||||
for attr in loc.attrs {
|
||||
mutations.push(Mutation::SetAttribute {
|
||||
for attr in loc.attrs.iter() {
|
||||
mutations.push(SetAttribute {
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
id,
|
||||
|
@ -67,31 +73,71 @@ impl VirtualDom {
|
|||
}
|
||||
}
|
||||
|
||||
// We're on top of a node that has a dynamic child for a descndent
|
||||
// Might as well set it now while we can
|
||||
// We're on top of a node that has a dynamic child for a descendant
|
||||
while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
|
||||
self.create_dynamic_node(mutations, template, node);
|
||||
let m = self.create_dynamic_node(mutations, template, node);
|
||||
mutations.push(ReplacePlaceholder {
|
||||
m,
|
||||
path: &node.path[1..],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
on_stack
|
||||
}
|
||||
|
||||
fn create_static_template(&mut self, mutations: &mut Vec<Mutation>, template: &Template) {
|
||||
todo!("load template")
|
||||
fn create_static_node<'a>(
|
||||
&mut self,
|
||||
mutations: &mut Vec<Mutation<'a>>,
|
||||
template: &'a VNode<'a>,
|
||||
node: &'a TemplateNode<'static>,
|
||||
) {
|
||||
match *node {
|
||||
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
|
||||
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||
value: "placeholder",
|
||||
}),
|
||||
TemplateNode::Element {
|
||||
attrs,
|
||||
children,
|
||||
namespace,
|
||||
tag,
|
||||
} => {
|
||||
let id = self.next_element(template);
|
||||
|
||||
mutations.push(CreateElement {
|
||||
name: tag,
|
||||
namespace,
|
||||
id,
|
||||
});
|
||||
|
||||
mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
id,
|
||||
}));
|
||||
|
||||
children
|
||||
.into_iter()
|
||||
.for_each(|child| self.create_static_node(mutations, template, child));
|
||||
|
||||
mutations.push(AppendChildren { m: children.len() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_dynamic_node<'a>(
|
||||
&mut self,
|
||||
mutations: &mut Vec<Mutation<'a>>,
|
||||
template: &VTemplate<'a>,
|
||||
template: &'a VNode<'a>,
|
||||
node: &'a DynamicNode<'a>,
|
||||
) -> usize {
|
||||
match &node.kind {
|
||||
DynamicNodeKind::Text { id, value } => {
|
||||
let new_id = self.elements.next();
|
||||
let new_id = self.next_element(template);
|
||||
id.set(new_id);
|
||||
mutations.push(Mutation::HydrateText {
|
||||
mutations.push(HydrateText {
|
||||
id: new_id,
|
||||
path: &node.path[1..],
|
||||
value,
|
||||
|
@ -99,16 +145,28 @@ impl VirtualDom {
|
|||
|
||||
1
|
||||
}
|
||||
DynamicNodeKind::Component { props, fn_ptr, .. } => {
|
||||
let id = self.new_scope(*fn_ptr, None, ElementId(0), *props);
|
||||
|
||||
DynamicNodeKind::Component { props, .. } => {
|
||||
let id = self.new_scope(*props);
|
||||
|
||||
let template = self.run_scope(id);
|
||||
|
||||
todo!("create component has bad data");
|
||||
// shut up about lifetimes please, I know what I'm doing
|
||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||
|
||||
self.scope_stack.push(id);
|
||||
let created = self.create(mutations, template);
|
||||
self.scope_stack.pop();
|
||||
|
||||
created
|
||||
}
|
||||
DynamicNodeKind::Fragment { children } => children
|
||||
|
||||
DynamicNodeKind::Fragment { children } => {
|
||||
//
|
||||
children
|
||||
.iter()
|
||||
.fold(0, |acc, child| acc + self.create(mutations, child)),
|
||||
.fold(0, |acc, child| acc + self.create(mutations, child))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::VirtualDom;
|
||||
use crate::virtualdom::VirtualDom;
|
||||
|
||||
use crate::any_props::VComponentProps;
|
||||
use crate::arena::ElementArena;
|
||||
|
||||
use crate::component::Component;
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{DynamicNode, Template, TemplateId};
|
||||
|
@ -10,9 +10,18 @@ use crate::{
|
|||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
nodes::VTemplate,
|
||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
||||
nodes::VNode,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
};
|
||||
use slab::Slab;
|
||||
|
||||
impl VirtualDom {}
|
||||
pub struct DirtyScope {
|
||||
height: usize,
|
||||
id: ScopeId,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
fn diff_scope<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>, scope: ScopeId) {
|
||||
let scope_state = &mut self.scopes[scope.0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
use crate::nodes::{Template, VTemplate};
|
||||
use crate::nodes::{Template, VNode};
|
||||
|
||||
pub type Element<'a> = Option<VTemplate<'a>>;
|
||||
pub type Element<'a> = Option<VNode<'a>>;
|
||||
|
|
159
packages/core/src/events.rs
Normal file
159
packages/core/src/events.rs
Normal file
|
@ -0,0 +1,159 @@
|
|||
use std::{any::Any, cell::Cell};
|
||||
|
||||
use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
|
||||
|
||||
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
|
||||
///
|
||||
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
|
||||
/// where each listener is checked and fired if the event name matches.
|
||||
///
|
||||
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
|
||||
/// attempting to downcast the event data.
|
||||
///
|
||||
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
|
||||
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// render!(div {
|
||||
/// onclick: move |_| println!("Clicked!")
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// let mut dom = VirtualDom::new(App);
|
||||
/// let mut scheduler = dom.get_scheduler_channel();
|
||||
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
|
||||
/// UserEvent {
|
||||
/// scope_id: None,
|
||||
/// priority: EventPriority::Medium,
|
||||
/// name: "click",
|
||||
/// element: Some(ElementId(0)),
|
||||
/// data: Arc::new(ClickEvent { .. })
|
||||
/// }
|
||||
/// )).unwrap();
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct UiEvent<T> {
|
||||
/// The priority of the event to be scheduled around ongoing work
|
||||
pub priority: EventPriority,
|
||||
|
||||
/// The optional real node associated with the trigger
|
||||
pub element: ElementId,
|
||||
|
||||
/// The event type IE "onclick" or "onmouseover"
|
||||
pub name: &'static str,
|
||||
pub bubble: Cell<bool>,
|
||||
pub event: T,
|
||||
}
|
||||
|
||||
/// Priority of Event Triggers.
|
||||
///
|
||||
/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
|
||||
/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
|
||||
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
|
||||
///
|
||||
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
|
||||
///
|
||||
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
|
||||
/// we keep it simple, and just use a 3-tier priority system.
|
||||
///
|
||||
/// - `NoPriority` = 0
|
||||
/// - `LowPriority` = 1
|
||||
/// - `NormalPriority` = 2
|
||||
/// - `UserBlocking` = 3
|
||||
/// - `HighPriority` = 4
|
||||
/// - `ImmediatePriority` = 5
|
||||
///
|
||||
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
|
||||
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
|
||||
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
||||
pub enum EventPriority {
|
||||
/// Work that must be completed during the EventHandler phase.
|
||||
///
|
||||
/// Currently this is reserved for controlled inputs.
|
||||
Immediate = 3,
|
||||
|
||||
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
|
||||
///
|
||||
/// This is typically reserved for things like user interaction.
|
||||
///
|
||||
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
|
||||
High = 2,
|
||||
|
||||
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
|
||||
/// than "High Priority" events and will take precedence over low priority events.
|
||||
///
|
||||
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
|
||||
///
|
||||
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
|
||||
Medium = 1,
|
||||
|
||||
/// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
|
||||
/// advanced to the front of the work queue until completed.
|
||||
///
|
||||
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
|
||||
///
|
||||
/// This is considered "idle" work or "background" work.
|
||||
Low = 0,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
/// Returns None if no element could be found
|
||||
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
|
||||
let path = self.elements.get(event.element.0)?;
|
||||
|
||||
let location = unsafe { &mut *path.template }
|
||||
.dynamic_attrs
|
||||
.iter_mut()
|
||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||
|
||||
let mut index = Some((path.template, location));
|
||||
|
||||
let mut listeners = Vec::<&mut Listener>::new();
|
||||
|
||||
while let Some((raw_parent, dyn_index)) = index {
|
||||
let parent = unsafe { &mut *raw_parent };
|
||||
let path = parent.dynamic_nodes[dyn_index].path;
|
||||
|
||||
listeners.extend(
|
||||
parent
|
||||
.dynamic_attrs
|
||||
.iter_mut()
|
||||
.filter(|attr| is_path_ascendant(attr.path, path))
|
||||
.map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
|
||||
.flatten(),
|
||||
);
|
||||
|
||||
index = parent.parent;
|
||||
}
|
||||
|
||||
for listener in listeners {
|
||||
(listener.callback)(&event.event);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
// ensures a strict descendant relationship
|
||||
// returns false if the paths are equal
|
||||
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||
if small.len() >= big.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
small == &big[..small.len()]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_slice() {
|
||||
let left = &[1, 2, 3];
|
||||
let right = &[1, 2, 3, 4, 5];
|
||||
assert!(is_path_ascendant(left, right));
|
||||
assert!(!is_path_ascendant(right, left));
|
||||
assert!(!is_path_ascendant(left, left));
|
||||
|
||||
assert!(is_path_ascendant(&[1, 2], &[1, 2, 3, 4, 5]));
|
||||
}
|
46
packages/core/src/factory.rs
Normal file
46
packages/core/src/factory.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use std::fmt::Arguments;
|
||||
|
||||
use crate::{innerlude::DynamicNode, LazyNodes, ScopeState, VNode};
|
||||
|
||||
impl ScopeState {
|
||||
/// Create some text that's allocated along with the other vnodes
|
||||
///
|
||||
pub fn text(&self, args: Arguments) -> DynamicNode {
|
||||
// let (text, _is_static) = self.raw_text(args);
|
||||
|
||||
// VNode::Text(self.bump.alloc(VText {
|
||||
// text,
|
||||
// id: Default::default(),
|
||||
// }))
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
|
||||
&'a self,
|
||||
it: impl IntoIterator<Item = F>,
|
||||
) -> DynamicNode {
|
||||
let mut bump_vec = bumpalo::vec![in self.bump();];
|
||||
|
||||
for item in it {
|
||||
bump_vec.push(item.into_dynamic_node(self));
|
||||
}
|
||||
|
||||
DynamicNode {
|
||||
path: &[0, 0],
|
||||
kind: crate::innerlude::DynamicNodeKind::Fragment {
|
||||
children: bump_vec.into_bump_slice(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoVnode<'a, A = ()> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
}
|
||||
}
|
65
packages/core/src/future_container.rs
Normal file
65
packages/core/src/future_container.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use futures_channel::mpsc::UnboundedSender;
|
||||
use futures_util::Future;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use crate::innerlude::ScopeId;
|
||||
/// The type of message that can be sent to the scheduler.
|
||||
///
|
||||
/// These messages control how the scheduler will process updates to the UI.
|
||||
#[derive(Debug)]
|
||||
pub enum SchedulerMsg {
|
||||
/// Events from athe Renderer
|
||||
Event,
|
||||
|
||||
/// Immediate updates from Components that mark them as dirty
|
||||
Immediate(ScopeId),
|
||||
|
||||
/// Mark all components as dirty and update them
|
||||
DirtyAll,
|
||||
|
||||
/// New tasks from components that should be polled when the next poll is ready
|
||||
NewTask(ScopeId),
|
||||
}
|
||||
|
||||
// todo extract this so spawning doesn't require refcell and rc doesnt need to be tracked
|
||||
#[derive(Clone)]
|
||||
pub struct FutureQueue {
|
||||
pub sender: UnboundedSender<SchedulerMsg>,
|
||||
pub queue: Rc<RefCell<Vec<Box<dyn Future<Output = ()>>>>>,
|
||||
}
|
||||
|
||||
impl FutureQueue {
|
||||
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
|
||||
Self {
|
||||
sender,
|
||||
queue: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn(&self, id: ScopeId, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::NewTask(id))
|
||||
.unwrap();
|
||||
self.queue.borrow_mut().push(Box::new(fut));
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: TaskId) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
/// A task's unique identifier.
|
||||
///
|
||||
/// `TaskId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`TaskID`]s will never be reused
|
||||
/// once a Task has been completed.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TaskId {
|
||||
/// The global ID of the task
|
||||
pub id: usize,
|
||||
|
||||
/// The original scope that this task was scheduled in
|
||||
pub scope: ScopeId,
|
||||
}
|
30
packages/core/src/garbage.rs
Normal file
30
packages/core/src/garbage.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use crate::{
|
||||
nodes::{DynamicNodeKind, VNode},
|
||||
scopes::ScopeId,
|
||||
virtualdom::VirtualDom,
|
||||
};
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn drop_scope(&mut self, id: ScopeId) {
|
||||
let scope = self.scopes.get(id.0).unwrap();
|
||||
|
||||
let root = scope.root_node();
|
||||
let root = unsafe { std::mem::transmute(root) };
|
||||
|
||||
self.drop_template(root);
|
||||
}
|
||||
|
||||
pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
|
||||
for node in template.dynamic_nodes.iter() {
|
||||
match &node.kind {
|
||||
DynamicNodeKind::Text { id, .. } => {}
|
||||
|
||||
DynamicNodeKind::Component { .. } => {
|
||||
todo!()
|
||||
}
|
||||
|
||||
DynamicNodeKind::Fragment { children } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
324
packages/core/src/lazynodes.rs
Normal file
324
packages/core/src/lazynodes.rs
Normal file
|
@ -0,0 +1,324 @@
|
|||
//! Support for storing lazy-nodes on the stack
|
||||
//!
|
||||
//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
|
||||
//! to `rsx!` more efficient.
|
||||
//!
|
||||
//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
|
||||
//!
|
||||
//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
|
||||
//! we build a tiny alloactor in the stack and allocate the closure into that.
|
||||
//!
|
||||
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
|
||||
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
|
||||
|
||||
use crate::{innerlude::VNode, ScopeState};
|
||||
use std::mem;
|
||||
|
||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||
///
|
||||
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
||||
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
|
||||
/// ```
|
||||
pub struct LazyNodes<'a, 'b> {
|
||||
inner: StackNodeStorage<'a, 'b>,
|
||||
}
|
||||
|
||||
pub type NodeFactory<'a> = &'a ScopeState;
|
||||
|
||||
type StackHeapSize = [usize; 16];
|
||||
|
||||
enum StackNodeStorage<'a, 'b> {
|
||||
Stack(LazyStack),
|
||||
Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
|
||||
}
|
||||
|
||||
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||
/// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack.
|
||||
///
|
||||
/// If the closure cannot fit into the stack allocation (16 bytes), then it
|
||||
/// is placed on the heap. Most closures will fit into the stack, and is
|
||||
/// the most optimal way to use the creation function.
|
||||
pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
|
||||
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||
let mut slot = Some(val);
|
||||
|
||||
let val = move |fac: Option<NodeFactory<'a>>| {
|
||||
fac.map(
|
||||
slot.take()
|
||||
.expect("LazyNodes closure to be called only once"),
|
||||
)
|
||||
};
|
||||
|
||||
// miri does not know how to work with mucking directly into bytes
|
||||
// just use a heap allocated type when miri is running
|
||||
if cfg!(miri) {
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||
}
|
||||
} else {
|
||||
unsafe { LazyNodes::new_inner(val) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new [`LazyNodes`] closure, but force it onto the heap.
|
||||
pub fn new_boxed<F>(inner: F) -> Self
|
||||
where
|
||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
||||
{
|
||||
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
|
||||
let mut slot = Some(inner);
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
|
||||
fac.map(
|
||||
slot.take()
|
||||
.expect("LazyNodes closure to be called only once"),
|
||||
)
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn new_inner<F>(val: F) -> Self
|
||||
where
|
||||
F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
|
||||
{
|
||||
let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
|
||||
|
||||
assert_eq!(
|
||||
ptr as *const u8, &val as *const _ as *const u8,
|
||||
"MISUSE: Closure returned different pointer"
|
||||
);
|
||||
assert_eq!(
|
||||
std::mem::size_of_val(&*ptr),
|
||||
std::mem::size_of::<F>(),
|
||||
"MISUSE: Closure returned a subset pointer"
|
||||
);
|
||||
|
||||
let words = ptr_as_slice(&mut ptr);
|
||||
assert!(
|
||||
words[0] == &val as *const _ as usize,
|
||||
"BUG: Pointer layout is not (data_ptr, info...)"
|
||||
);
|
||||
|
||||
// - Ensure that Self is aligned same as data requires
|
||||
assert!(
|
||||
std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
|
||||
"TODO: Enforce alignment >{} (requires {})",
|
||||
std::mem::align_of::<Self>(),
|
||||
std::mem::align_of::<F>()
|
||||
);
|
||||
|
||||
let info = &words[1..];
|
||||
let data = words[0] as *mut ();
|
||||
let size = mem::size_of::<F>();
|
||||
|
||||
let stored_size = info.len() * mem::size_of::<usize>() + size;
|
||||
let max_size = mem::size_of::<StackHeapSize>();
|
||||
|
||||
if stored_size > max_size {
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||
}
|
||||
} else {
|
||||
let mut buf: StackHeapSize = StackHeapSize::default();
|
||||
|
||||
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
|
||||
|
||||
// Place pointer information at the end of the region
|
||||
// - Allows the data to be at the start for alignment purposes
|
||||
{
|
||||
let info_ofs = buf.as_ref().len() - info.len();
|
||||
let info_dst = &mut buf.as_mut()[info_ofs..];
|
||||
for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
|
||||
*d = *v;
|
||||
}
|
||||
}
|
||||
|
||||
let src_ptr = data as *const u8;
|
||||
let dataptr = buf.as_mut_ptr().cast::<u8>();
|
||||
|
||||
for i in 0..size {
|
||||
*dataptr.add(i) = *src_ptr.add(i);
|
||||
}
|
||||
|
||||
std::mem::forget(val);
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Stack(LazyStack {
|
||||
_align: [],
|
||||
buf,
|
||||
dropped: false,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the closure with the given factory to produce real [`VNode`].
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
|
||||
///
|
||||
/// let fac = NodeFactory::new(&cx);
|
||||
///
|
||||
/// let node = f.call(cac);
|
||||
/// ```
|
||||
#[must_use]
|
||||
pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
|
||||
match self.inner {
|
||||
StackNodeStorage::Heap(mut lazy) => {
|
||||
lazy(Some(f)).expect("Closure should not be called twice")
|
||||
}
|
||||
StackNodeStorage::Stack(mut stack) => stack.call(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LazyStack {
|
||||
_align: [u64; 0],
|
||||
buf: StackHeapSize,
|
||||
dropped: bool,
|
||||
}
|
||||
|
||||
impl LazyStack {
|
||||
fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
|
||||
let LazyStack { buf, .. } = self;
|
||||
let data = buf.as_ref();
|
||||
|
||||
let info_size =
|
||||
mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
|
||||
/ mem::size_of::<usize>()
|
||||
- 1;
|
||||
|
||||
let info_ofs = data.len() - info_size;
|
||||
|
||||
let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
|
||||
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
let clos = unsafe { &mut *g };
|
||||
clos(Some(f)).unwrap()
|
||||
}
|
||||
}
|
||||
impl Drop for LazyStack {
|
||||
fn drop(&mut self) {
|
||||
if !self.dropped {
|
||||
let LazyStack { buf, .. } = self;
|
||||
let data = buf.as_ref();
|
||||
|
||||
let info_size = mem::size_of::<
|
||||
*mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
|
||||
>() / mem::size_of::<usize>()
|
||||
- 1;
|
||||
|
||||
let info_ofs = data.len() - info_size;
|
||||
|
||||
let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
|
||||
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
|
||||
|
||||
self.dropped = true;
|
||||
|
||||
let clos = unsafe { &mut *g };
|
||||
clos(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain mutable access to a pointer's words
|
||||
fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
|
||||
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
|
||||
let words = mem::size_of::<T>() / mem::size_of::<usize>();
|
||||
// SAFE: Points to valid memory (a raw pointer)
|
||||
unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
|
||||
}
|
||||
|
||||
/// Re-construct a fat pointer
|
||||
unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
|
||||
let mut rv = mem::MaybeUninit::<*mut T>::uninit();
|
||||
{
|
||||
let s = ptr_as_slice(&mut rv);
|
||||
s[0] = data_ptr;
|
||||
s[1..].copy_from_slice(meta_vals);
|
||||
}
|
||||
let rv = rv.assume_init();
|
||||
assert_eq!(rv as *const (), data_ptr as *const ());
|
||||
rv
|
||||
}
|
||||
|
||||
fn round_to_words(len: usize) -> usize {
|
||||
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
// use crate::innerlude::{Element, Scope, VirtualDom};
|
||||
|
||||
// #[test]
|
||||
// fn it_works() {
|
||||
// fn app(cx: Scope<()>) -> Element {
|
||||
// cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
|
||||
// }
|
||||
|
||||
// let mut dom = VirtualDom::new(app);
|
||||
// dom.rebuild();
|
||||
|
||||
// let g = dom.base_scope().root_node();
|
||||
// dbg!(g);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn it_drops() {
|
||||
// use std::rc::Rc;
|
||||
|
||||
// struct AppProps {
|
||||
// inner: Rc<i32>,
|
||||
// }
|
||||
|
||||
// fn app(cx: Scope<AppProps>) -> Element {
|
||||
// struct DropInner {
|
||||
// id: i32,
|
||||
// }
|
||||
// impl Drop for DropInner {
|
||||
// fn drop(&mut self) {
|
||||
// eprintln!("dropping inner");
|
||||
// }
|
||||
// }
|
||||
|
||||
// let caller = {
|
||||
// let it = (0..10).map(|i| {
|
||||
// let val = cx.props.inner.clone();
|
||||
// LazyNodes::new(move |f| {
|
||||
// eprintln!("hell closure");
|
||||
// let inner = DropInner { id: i };
|
||||
// f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
|
||||
// })
|
||||
// });
|
||||
|
||||
// LazyNodes::new(|f| {
|
||||
// eprintln!("main closure");
|
||||
// f.fragment_from_iter(it)
|
||||
// })
|
||||
// };
|
||||
|
||||
// cx.render(caller)
|
||||
// }
|
||||
|
||||
// let inner = Rc::new(0);
|
||||
// let mut dom = VirtualDom::new_with_props(
|
||||
// app,
|
||||
// AppProps {
|
||||
// inner: inner.clone(),
|
||||
// },
|
||||
// );
|
||||
// dom.rebuild();
|
||||
|
||||
// drop(dom);
|
||||
|
||||
// assert_eq!(Rc::strong_count(&inner), 1);
|
||||
// }
|
||||
// }
|
|
@ -1,20 +1,3 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
nodes::VTemplate,
|
||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
||||
};
|
||||
use any_props::VComponentProps;
|
||||
use arena::ElementArena;
|
||||
use component::Component;
|
||||
use mutations::Mutation;
|
||||
use nodes::{DynamicNode, Template, TemplateId};
|
||||
use scopes::Scope;
|
||||
use slab::Slab;
|
||||
|
||||
mod any_props;
|
||||
mod arena;
|
||||
mod bump_frame;
|
||||
|
@ -22,54 +5,142 @@ mod component;
|
|||
mod create;
|
||||
mod diff;
|
||||
mod element;
|
||||
mod events;
|
||||
mod factory;
|
||||
mod future_container;
|
||||
mod garbage;
|
||||
mod lazynodes;
|
||||
mod mutations;
|
||||
mod nodes;
|
||||
mod scope_arena;
|
||||
mod scopes;
|
||||
mod virtualdom;
|
||||
|
||||
pub struct VirtualDom {
|
||||
templates: HashMap<TemplateId, Template>,
|
||||
elements: ElementArena,
|
||||
scopes: Slab<ScopeState>,
|
||||
scope_stack: Vec<ScopeId>,
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::element::Element;
|
||||
pub use crate::events::*;
|
||||
pub use crate::future_container::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
// pub use crate::properties::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::scopes::*;
|
||||
pub use crate::virtualdom::*;
|
||||
|
||||
// /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
||||
// ///
|
||||
// /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
|
||||
// pub type Element<'a> = Option<VNodea<'a>>;
|
||||
|
||||
/// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
|
||||
///
|
||||
/// Components can be used in other components with two syntax options:
|
||||
/// - lowercase as a function call with named arguments (rust style)
|
||||
/// - uppercase as an element (react style)
|
||||
///
|
||||
/// ## Rust-Style
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn example(cx: Scope<Props>) -> Element {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// rsx!(
|
||||
/// example()
|
||||
/// )
|
||||
/// ```
|
||||
/// ## React-Style
|
||||
/// ```rust, ignore
|
||||
/// fn Example(cx: Scope<Props>) -> Element {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// rsx!(
|
||||
/// Example {}
|
||||
/// )
|
||||
/// ```
|
||||
pub type Component<P = ()> = fn(Scope<P>) -> Element;
|
||||
|
||||
/// A list of attributes
|
||||
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn new(app: Component) -> Self {
|
||||
let mut res = Self {
|
||||
templates: Default::default(),
|
||||
scopes: Slab::default(),
|
||||
elements: ElementArena::default(),
|
||||
scope_stack: Vec::new(),
|
||||
pub use crate::innerlude::{
|
||||
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||
Attribute,
|
||||
AttributeValue,
|
||||
Element,
|
||||
EventPriority,
|
||||
LazyNodes,
|
||||
Listener,
|
||||
NodeFactory,
|
||||
Scope,
|
||||
ScopeId,
|
||||
ScopeState,
|
||||
TaskId,
|
||||
Template,
|
||||
TemplateAttribute,
|
||||
TemplateNode,
|
||||
UiEvent,
|
||||
VNode,
|
||||
VirtualDom,
|
||||
};
|
||||
// EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
|
||||
// SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
// UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
|
||||
|
||||
res.new_scope(
|
||||
app as _,
|
||||
None,
|
||||
ElementId(0),
|
||||
Box::new(VComponentProps::new_empty(app)),
|
||||
);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn root_scope(&self) -> &ScopeState {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Render the virtualdom, waiting for all suspended nodes to complete before moving on
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
///
|
||||
/// Forces a full render of the virtualdom from scratch.
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
|
||||
ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
pub mod exports {
|
||||
//! Important dependencies that are used by the rest of the library
|
||||
//! Feel free to just add the dependencies in your own Crates.toml
|
||||
pub use bumpalo;
|
||||
pub use futures_channel;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
/// A helper macro for using hooks in async environements.
|
||||
///
|
||||
/// Use other methods to update the virtualdom incrementally.
|
||||
pub fn render_all<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||
let root = self.root_scope();
|
||||
|
||||
let root_template = root.current_arena();
|
||||
|
||||
let root_node: &'a VTemplate = unsafe { &*root_template.node.get() };
|
||||
let root_node: &'a VTemplate<'a> = unsafe { std::mem::transmute(root_node) };
|
||||
|
||||
self.create(mutations, root_node);
|
||||
/// # Usage
|
||||
///
|
||||
///
|
||||
/// ```ignore
|
||||
/// let (data) = use_ref(&cx, || {});
|
||||
///
|
||||
/// let handle_thing = move |_| {
|
||||
/// to_owned![data]
|
||||
/// cx.spawn(async move {
|
||||
/// // do stuff
|
||||
/// });
|
||||
/// }
|
||||
/// ```
|
||||
macro_rules! to_owned {
|
||||
($($es:ident),+) => {$(
|
||||
#[allow(unused_mut)]
|
||||
let mut $es = $es.to_owned();
|
||||
)*}
|
||||
}
|
||||
|
||||
/// get the code location of the code that called this function
|
||||
#[macro_export]
|
||||
macro_rules! get_line_num {
|
||||
() => {
|
||||
concat!(
|
||||
file!(),
|
||||
":",
|
||||
line!(),
|
||||
":",
|
||||
column!(),
|
||||
":",
|
||||
env!("CARGO_MANIFEST_DIR")
|
||||
)
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,6 +17,11 @@ pub enum Mutation<'a> {
|
|||
index: usize,
|
||||
},
|
||||
|
||||
SaveTemplate {
|
||||
name: &'static str,
|
||||
m: usize,
|
||||
},
|
||||
|
||||
HydrateText {
|
||||
path: &'static [u8],
|
||||
value: &'a str,
|
||||
|
@ -30,7 +35,7 @@ pub enum Mutation<'a> {
|
|||
|
||||
ReplacePlaceholder {
|
||||
path: &'static [u8],
|
||||
id: ElementId,
|
||||
m: usize,
|
||||
},
|
||||
|
||||
AssignId {
|
||||
|
@ -42,4 +47,20 @@ pub enum Mutation<'a> {
|
|||
Replace {
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
CreateElement {
|
||||
name: &'a str,
|
||||
namespace: Option<&'a str>,
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
CreateText {
|
||||
value: &'a str,
|
||||
},
|
||||
|
||||
CreatePlaceholder,
|
||||
|
||||
AppendChildren {
|
||||
m: usize,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{any_props::AnyProps, arena::ElementId, scopes::ComponentPtr};
|
||||
use crate::{any_props::AnyProps, arena::ElementId};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::Cell,
|
||||
|
@ -8,11 +8,14 @@ use std::{
|
|||
pub type TemplateId = &'static str;
|
||||
|
||||
/// A reference to a template along with any context needed to hydrate it
|
||||
pub struct VTemplate<'a> {
|
||||
pub struct VNode<'a> {
|
||||
// The ID assigned for the root of this template
|
||||
pub node_id: Cell<ElementId>,
|
||||
|
||||
pub template: &'static Template,
|
||||
// When rendered, this template will be linked to its parent
|
||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||
|
||||
pub template: Template,
|
||||
|
||||
pub root_ids: &'a [Cell<ElementId>],
|
||||
|
||||
|
@ -25,7 +28,6 @@ pub struct VTemplate<'a> {
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Template {
|
||||
pub id: &'static str,
|
||||
|
||||
pub roots: &'static [TemplateNode<'static>],
|
||||
}
|
||||
|
||||
|
@ -54,8 +56,7 @@ pub enum DynamicNodeKind<'a> {
|
|||
// IE in caps or with underscores
|
||||
Component {
|
||||
name: &'static str,
|
||||
fn_ptr: ComponentPtr,
|
||||
props: Box<dyn AnyProps>,
|
||||
props: *mut dyn AnyProps,
|
||||
},
|
||||
|
||||
// Comes in with string interpolation or from format_args, include_str, etc
|
||||
|
@ -66,7 +67,7 @@ pub enum DynamicNodeKind<'a> {
|
|||
|
||||
// Anything that's coming in as an iterator
|
||||
Fragment {
|
||||
children: &'a [VTemplate<'a>],
|
||||
children: &'a [VNode<'a>],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -80,8 +81,8 @@ pub struct TemplateAttribute<'a> {
|
|||
|
||||
pub struct AttributeLocation<'a> {
|
||||
pub mounted_element: Cell<ElementId>,
|
||||
pub attrs: &'a [Attribute<'a>],
|
||||
pub listeners: &'a [Listener<'a>],
|
||||
pub attrs: &'a mut [Attribute<'a>],
|
||||
pub listeners: &'a mut [Listener<'a>],
|
||||
pub path: &'static [u8],
|
||||
}
|
||||
|
||||
|
@ -118,12 +119,12 @@ where
|
|||
|
||||
pub struct Listener<'a> {
|
||||
pub name: &'static str,
|
||||
pub callback: &'a dyn Fn(),
|
||||
pub callback: &'a mut dyn FnMut(&dyn Any),
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_are_the_sizes() {
|
||||
dbg!(std::mem::size_of::<VTemplate>());
|
||||
dbg!(std::mem::size_of::<VNode>());
|
||||
dbg!(std::mem::size_of::<Template>());
|
||||
dbg!(std::mem::size_of::<TemplateNode>());
|
||||
}
|
||||
|
|
|
@ -1,50 +1,61 @@
|
|||
use slab::Slab;
|
||||
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
nodes::VTemplate,
|
||||
scopes::{ComponentPtr, ScopeId, ScopeState},
|
||||
VirtualDom,
|
||||
nodes::VNode,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
virtualdom::VirtualDom,
|
||||
};
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn new_scope(
|
||||
&mut self,
|
||||
fn_ptr: ComponentPtr,
|
||||
parent: Option<*mut ScopeState>,
|
||||
container: ElementId,
|
||||
props: Box<dyn AnyProps>,
|
||||
) -> ScopeId {
|
||||
pub fn new_scope(&mut self, props: *mut dyn AnyProps) -> ScopeId {
|
||||
let parent = self.acquire_current_scope_raw();
|
||||
let container = self.acquire_current_container();
|
||||
let entry = self.scopes.vacant_entry();
|
||||
let our_arena_idx = entry.key();
|
||||
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
|
||||
let id = ScopeId(entry.key());
|
||||
|
||||
entry.insert(ScopeState {
|
||||
parent,
|
||||
container,
|
||||
our_arena_idx,
|
||||
id,
|
||||
height,
|
||||
fn_ptr,
|
||||
props,
|
||||
tasks: self.pending_futures.clone(),
|
||||
node_arena_1: BumpFrame::new(50),
|
||||
node_arena_2: BumpFrame::new(50),
|
||||
render_cnt: Default::default(),
|
||||
hook_arena: Default::default(),
|
||||
hook_vals: Default::default(),
|
||||
hook_idx: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
});
|
||||
|
||||
our_arena_idx
|
||||
id
|
||||
}
|
||||
|
||||
pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VTemplate<'a> {
|
||||
let scope = &mut self.scopes[id];
|
||||
pub fn acquire_current_container(&self) -> ElementId {
|
||||
self.element_stack
|
||||
.last()
|
||||
.copied()
|
||||
.expect("Always have a container")
|
||||
}
|
||||
|
||||
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
|
||||
self.scope_stack
|
||||
.last()
|
||||
.copied()
|
||||
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f as *mut ScopeState))
|
||||
}
|
||||
|
||||
pub fn run_scope<'a>(&'a mut self, id: ScopeId) -> &'a VNode<'a> {
|
||||
let scope = &mut self.scopes[id.0];
|
||||
scope.hook_idx.set(0);
|
||||
|
||||
let res = scope.props.render(scope).unwrap();
|
||||
let res: VTemplate<'static> = unsafe { std::mem::transmute(res) };
|
||||
let props = unsafe { &mut *scope.props };
|
||||
|
||||
let res = props.render(scope).unwrap();
|
||||
let res: VNode<'static> = unsafe { std::mem::transmute(res) };
|
||||
|
||||
let frame = match scope.render_cnt % 2 {
|
||||
0 => &mut scope.node_arena_1,
|
||||
|
|
|
@ -1,13 +1,50 @@
|
|||
use std::{
|
||||
any::Any,
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use futures_util::Future;
|
||||
|
||||
use crate::{any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, nodes::VTemplate};
|
||||
use crate::{
|
||||
any_props::AnyProps, arena::ElementId, bump_frame::BumpFrame, future_container::FutureQueue,
|
||||
innerlude::SchedulerMsg, lazynodes::LazyNodes, nodes::VNode, TaskId,
|
||||
};
|
||||
|
||||
pub type ScopeId = usize;
|
||||
pub struct Scope<'a, T = ()> {
|
||||
pub scope: &'a ScopeState,
|
||||
pub props: &'a T,
|
||||
}
|
||||
|
||||
impl<T> Copy for Scope<'_, T> {}
|
||||
|
||||
impl<T> Clone for Scope<'_, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
props: self.props,
|
||||
scope: self.scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> std::ops::Deref for Scope<'a, T> {
|
||||
type Target = &'a ScopeState;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.scope
|
||||
}
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
|
||||
/// once a component has been unmounted.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
pub struct ScopeState {
|
||||
pub render_cnt: usize,
|
||||
|
@ -17,16 +54,19 @@ pub struct ScopeState {
|
|||
|
||||
pub parent: Option<*mut ScopeState>,
|
||||
pub container: ElementId,
|
||||
pub our_arena_idx: ScopeId,
|
||||
pub id: ScopeId,
|
||||
|
||||
pub height: u32,
|
||||
pub fn_ptr: ComponentPtr,
|
||||
|
||||
pub hook_arena: Bump,
|
||||
pub hook_vals: RefCell<Vec<*mut dyn Any>>,
|
||||
pub hook_idx: Cell<usize>,
|
||||
|
||||
pub props: Box<dyn AnyProps>,
|
||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||
|
||||
pub tasks: FutureQueue,
|
||||
|
||||
pub props: *mut dyn AnyProps,
|
||||
}
|
||||
|
||||
impl ScopeState {
|
||||
|
@ -37,19 +77,316 @@ impl ScopeState {
|
|||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bump(&self) -> &Bump {
|
||||
&self.current_arena().bump
|
||||
}
|
||||
|
||||
pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
|
||||
|
||||
pub struct Scope<T = ()> {
|
||||
pub props: T,
|
||||
pub state: Cell<*const ScopeState>,
|
||||
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
||||
let r = unsafe { &*self.current_arena().node.get() };
|
||||
unsafe { std::mem::transmute(r) }
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for Scope<T> {
|
||||
type Target = ScopeState;
|
||||
/// Get the height of this Scope - IE the number of scopes above it.
|
||||
///
|
||||
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||
/// dom.rebuild();
|
||||
///
|
||||
/// let base = dom.base_scope();
|
||||
///
|
||||
/// assert_eq!(base.height(), 0);
|
||||
/// ```
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.state.get() }
|
||||
/// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
|
||||
///
|
||||
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
///
|
||||
/// The base component will not have a parent, and will return `None`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||
/// dom.rebuild();
|
||||
///
|
||||
/// let base = dom.base_scope();
|
||||
///
|
||||
/// assert_eq!(base.parent(), None);
|
||||
/// ```
|
||||
pub fn parent(&self) -> Option<ScopeId> {
|
||||
// safety: the pointer to our parent is *always* valid thanks to the bump arena
|
||||
self.parent.map(|p| unsafe { &*p }.id)
|
||||
}
|
||||
|
||||
/// Get the ID of this Scope within this Dioxus [`VirtualDom`].
|
||||
///
|
||||
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mut dom = VirtualDom::new(|cx| cx.render(rsx!{ div {} }));
|
||||
/// dom.rebuild();
|
||||
/// let base = dom.base_scope();
|
||||
///
|
||||
/// assert_eq!(base.scope_id(), 0);
|
||||
/// ```
|
||||
pub fn scope_id(&self) -> ScopeId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Get a handle to the raw update scheduler channel
|
||||
pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
|
||||
self.tasks.sender.clone()
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
///
|
||||
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
||||
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||
}
|
||||
|
||||
/// Schedule an update for any component given its [`ScopeId`].
|
||||
///
|
||||
/// A component's [`ScopeId`] can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
|
||||
///
|
||||
/// This method should be used when you want to schedule an update for a component
|
||||
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
||||
let chan = self.tasks.sender.clone();
|
||||
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||
}
|
||||
|
||||
pub fn needs_update(&self) {
|
||||
self.needs_update_any(self.scope_id());
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
self.tasks
|
||||
.sender
|
||||
.unbounded_send(SchedulerMsg::Immediate(id))
|
||||
.expect("Scheduler to exist if scope exists");
|
||||
}
|
||||
|
||||
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
||||
///
|
||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||
///
|
||||
/// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
|
||||
///
|
||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||
/// the context via Rc/Weak.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static App: Component = |cx| {
|
||||
/// cx.use_hook(|| cx.provide_context(SharedState("world")));
|
||||
/// render!(Child {})
|
||||
/// }
|
||||
///
|
||||
/// static Child: Component = |cx| {
|
||||
/// let state = cx.consume_state::<SharedState>();
|
||||
/// render!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
self.shared_contexts
|
||||
.borrow_mut()
|
||||
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
||||
.and_then(|f| f.downcast::<T>().ok());
|
||||
value
|
||||
}
|
||||
|
||||
/// Provide a context for the root component from anywhere in your app.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static App: Component = |cx| {
|
||||
/// cx.use_hook(|| cx.provide_root_context(SharedState("world")));
|
||||
/// render!(Child {})
|
||||
/// }
|
||||
///
|
||||
/// static Child: Component = |cx| {
|
||||
/// let state = cx.consume_state::<SharedState>();
|
||||
/// render!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
// if we *are* the root component, then we can just provide the context directly
|
||||
if self.scope_id() == ScopeId(0) {
|
||||
self.shared_contexts
|
||||
.borrow_mut()
|
||||
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
||||
.and_then(|f| f.downcast::<T>().ok());
|
||||
return value;
|
||||
}
|
||||
|
||||
let mut search_parent = self.parent;
|
||||
|
||||
while let Some(parent) = search_parent.take() {
|
||||
let parent = unsafe { &*parent };
|
||||
|
||||
if parent.scope_id() == ScopeId(0) {
|
||||
let exists = parent
|
||||
.shared_contexts
|
||||
.borrow_mut()
|
||||
.insert(TypeId::of::<T>(), Box::new(value.clone()));
|
||||
|
||||
if exists.is_some() {
|
||||
log::warn!("Context already provided to parent scope - replacing it");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
search_parent = parent.parent;
|
||||
}
|
||||
|
||||
unreachable!("all apps have a root scope")
|
||||
}
|
||||
|
||||
/// Try to retrieve a shared state with type T from the any parent Scope.
|
||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||
Some(
|
||||
(*shared
|
||||
.downcast_ref::<T>()
|
||||
.expect("Context of type T should exist"))
|
||||
.clone(),
|
||||
)
|
||||
} else {
|
||||
let mut search_parent = self.parent;
|
||||
|
||||
while let Some(parent_ptr) = search_parent {
|
||||
// safety: all parent pointers are valid thanks to the bump arena
|
||||
let parent = unsafe { &*parent_ptr };
|
||||
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||
return Some(
|
||||
shared
|
||||
.downcast_ref::<T>()
|
||||
.expect("Context of type T should exist")
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
search_parent = parent.parent;
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||
// wake up the scheduler if it is sleeping
|
||||
self.tasks
|
||||
.sender
|
||||
.unbounded_send(SchedulerMsg::NewTask(self.id))
|
||||
.expect("Scheduler should exist");
|
||||
|
||||
self.tasks.spawn(self.id, fut)
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`TaskId`]
|
||||
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) {
|
||||
self.push_future(fut);
|
||||
}
|
||||
|
||||
/// Spawn a future that Dioxus will never clean up
|
||||
///
|
||||
/// This is good for tasks that need to be run after the component has been dropped.
|
||||
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||
// wake up the scheduler if it is sleeping
|
||||
self.tasks
|
||||
.sender
|
||||
.unbounded_send(SchedulerMsg::NewTask(self.id))
|
||||
.expect("Scheduler should exist");
|
||||
|
||||
// The root scope will never be unmounted so we can just add the task at the top of the app
|
||||
self.tasks.spawn(ScopeId(0), fut)
|
||||
}
|
||||
|
||||
/// Informs the scheduler that this task is no longer needed and should be removed
|
||||
/// on next poll.
|
||||
pub fn remove_future(&self, id: TaskId) {
|
||||
self.tasks.remove(id);
|
||||
}
|
||||
|
||||
/// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// fn Component(cx: Scope<Props>) -> Element {
|
||||
/// // Lazy assemble the VNode tree
|
||||
/// let lazy_nodes = rsx!("hello world");
|
||||
///
|
||||
/// // Actually build the tree and allocate it
|
||||
/// cx.render(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
|
||||
Some(rsx.call(self))
|
||||
}
|
||||
|
||||
/// Store a value between renders. The foundational hook for all other hooks.
|
||||
///
|
||||
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||
///
|
||||
/// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use dioxus_core::ScopeState;
|
||||
///
|
||||
/// // prints a greeting on the initial render
|
||||
/// pub fn use_hello_world(cx: &ScopeState) {
|
||||
/// cx.use_hook(|| println!("Hello, world!"));
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||
let mut vals = self.hook_vals.borrow_mut();
|
||||
|
||||
let hook_len = vals.len();
|
||||
let cur_idx = self.hook_idx.get();
|
||||
|
||||
if cur_idx >= hook_len {
|
||||
vals.push(self.hook_arena.alloc(initializer()));
|
||||
}
|
||||
|
||||
vals
|
||||
.get(cur_idx)
|
||||
.and_then(|inn| {
|
||||
self.hook_idx.set(cur_idx + 1);
|
||||
let raw_box = unsafe { &mut **inn };
|
||||
raw_box.downcast_mut::<State>()
|
||||
})
|
||||
.expect(
|
||||
r###"
|
||||
Unable to retrieve the hook that was initialized at this index.
|
||||
Consult the `rules of hooks` to understand how to use hooks properly.
|
||||
|
||||
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
||||
Functions prefixed with "use" should never be called conditionally.
|
||||
"###,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
91
packages/core/src/virtualdom.rs
Normal file
91
packages/core/src/virtualdom.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use crate::any_props::VComponentProps;
|
||||
use crate::arena::ElementPath;
|
||||
use crate::component::{Component, IntoComponent};
|
||||
use crate::diff::DirtyScope;
|
||||
use crate::future_container::FutureQueue;
|
||||
use crate::innerlude::SchedulerMsg;
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{Template, TemplateId};
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
};
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use slab::Slab;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: HashMap<TemplateId, Template>,
|
||||
pub(crate) elements: Slab<ElementPath>,
|
||||
pub(crate) scopes: Slab<ScopeState>,
|
||||
pub(crate) scope_stack: Vec<ScopeId>,
|
||||
pub(crate) element_stack: Vec<ElementId>,
|
||||
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||
pub(crate) pending_futures: FutureQueue,
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn new(app: Component<()>) -> Self {
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded();
|
||||
|
||||
let mut res = Self {
|
||||
templates: Default::default(),
|
||||
scopes: Slab::default(),
|
||||
elements: Default::default(),
|
||||
scope_stack: Vec::new(),
|
||||
element_stack: vec![ElementId(0)],
|
||||
dirty_scopes: BTreeSet::new(),
|
||||
pending_futures: FutureQueue::new(sender.clone()),
|
||||
receiver,
|
||||
sender,
|
||||
};
|
||||
|
||||
let props = Box::into_raw(Box::new(VComponentProps::new_empty(app)));
|
||||
|
||||
let root = res.new_scope(props);
|
||||
|
||||
assert_eq!(root, ScopeId(0));
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Render the virtualdom, without processing any suspense.
|
||||
pub fn rebuild<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||
// let root = self.scopes.get(0).unwrap();
|
||||
|
||||
let root_node = unsafe { std::mem::transmute(self.run_scope(ScopeId(0))) };
|
||||
|
||||
// let root_node = unsafe { std::mem::transmute(root.root_node()) };
|
||||
|
||||
self.scope_stack.push(ScopeId(0));
|
||||
self.create(mutations, root_node);
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
/// Render what you can given the timeline and then move on
|
||||
pub async fn render_with_deadline<'a>(
|
||||
&'a mut self,
|
||||
future: impl std::future::Future<Output = ()>,
|
||||
mutations: &mut Vec<Mutation<'a>>,
|
||||
) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Whenever the future is canceled, the VirtualDom will be
|
||||
pub async fn render<'a>(&'a mut self, mutations: &mut Vec<Mutation<'a>>) {
|
||||
//
|
||||
}
|
||||
|
||||
/// Wait for futures internal to the virtualdom
|
||||
///
|
||||
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
||||
pub async fn wait_for_work(&mut self) {}
|
||||
}
|
||||
|
||||
impl Drop for VirtualDom {
|
||||
fn drop(&mut self) {
|
||||
// self.drop_scope(ScopeId(0));
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
use dioxus_core::{prelude::*, TemplateNode, VTemplate, VText};
|
||||
|
||||
// #[test]
|
||||
// fn simple_static() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// static MyTemplate: TemplateDef = TemplateDef {
|
||||
// id: "my-template",
|
||||
// static_nodes: &[TemplateNode::Element {
|
||||
// attributes: &[],
|
||||
// nodes: &[TemplateNode::StaticText("Hello, world!")],
|
||||
// tag: "div",
|
||||
// }],
|
||||
// dynamic_nodes: &[],
|
||||
// };
|
||||
|
||||
// Some(VNode::Template(NodeFactory::new(&cx).bump().alloc(
|
||||
// VTemplate {
|
||||
// def: &MyTemplate,
|
||||
// dynamic_nodes: &[],
|
||||
// rendered_nodes: &[],
|
||||
// },
|
||||
// )))
|
||||
// }
|
||||
|
||||
// let mut dom = VirtualDom::new(app);
|
||||
// let edits = dom.rebuild();
|
||||
// dbg!(edits);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn mixed_dynamic() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// static MyTemplate: TemplateDef = TemplateDef {
|
||||
// id: "my-template",
|
||||
// static_nodes: &[TemplateNode::Element {
|
||||
// tag: "div",
|
||||
// attributes: &[],
|
||||
// nodes: &[
|
||||
// TemplateNode::StaticText("Hello, world!"),
|
||||
// TemplateNode::DynamicText,
|
||||
// ],
|
||||
// }],
|
||||
// dynamic_nodes: &[],
|
||||
// };
|
||||
|
||||
// let val = cx.use_hook(|| 0);
|
||||
// *val += 1;
|
||||
|
||||
// let fact = NodeFactory::new(&cx);
|
||||
|
||||
// Some(VNode::Template(fact.bump().alloc(VTemplateRef {
|
||||
// def: &MyTemplate,
|
||||
// dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
|
||||
// })))
|
||||
// }
|
||||
|
||||
// let mut dom = VirtualDom::new(app);
|
||||
// let edits = dom.rebuild();
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn mixes() {
|
||||
// fn app(cx: Scope) -> Element {
|
||||
// static MyTemplate: TemplateDef = TemplateDef {
|
||||
// id: "my-template",
|
||||
// static_nodes: &[TemplateNode::Element {
|
||||
// tag: "div",
|
||||
// attributes: &[],
|
||||
// nodes: &[
|
||||
// TemplateNode::StaticText("Hello, world!"),
|
||||
// TemplateNode::DynamicText,
|
||||
// ],
|
||||
// }],
|
||||
// dynamic_nodes: &[],
|
||||
// };
|
||||
|
||||
// let val = cx.use_hook(|| 1);
|
||||
// *val += 1;
|
||||
|
||||
// let fact = NodeFactory::new(&cx);
|
||||
|
||||
// if *val % 2 == 0 {
|
||||
// Some(VNode::Template(fact.bump().alloc(VTemplateRef {
|
||||
// def: &MyTemplate,
|
||||
// dynamic_nodes: fact.bump().alloc([fact.text(format_args!("{val}"))]),
|
||||
// })))
|
||||
// } else {
|
||||
// Some(fact.text(format_args!("Hello, world! {val}")))
|
||||
// }
|
||||
// }
|
||||
|
||||
// let mut dom = VirtualDom::new(app);
|
||||
// let edits = dom.rebuild();
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
|
||||
// let edits = dom.hard_diff(ScopeId(0));
|
||||
// dbg!(edits);
|
||||
// }
|
26
packages/core/tests/simple_syntax.rs
Normal file
26
packages/core/tests/simple_syntax.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use dioxus_core::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
todo!();
|
||||
// render! {
|
||||
// Suspend {
|
||||
// delay: Duration::from_millis(100),
|
||||
// fallback: rsx! { "Loading..." },
|
||||
// ChildAsync {}
|
||||
// ChildAsync {}
|
||||
// ChildAsync {}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
async fn ChildAsync(cx: Scope<'_>) -> Element {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mut mutations = vec![];
|
||||
dom.rebuild(&mut mutations);
|
||||
}
|
|
@ -32,7 +32,7 @@ rand = { version = "0.8.4", features = ["small_rng"] }
|
|||
criterion = "0.3.5"
|
||||
thiserror = "1.0.30"
|
||||
env_logger = "0.9.0"
|
||||
dioxus-edit-stream = { path = "../edit-stream" }
|
||||
# dioxus-edit-stream = { path = "../edit-stream" }
|
||||
|
||||
[[bench]]
|
||||
name = "create"
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
use dioxus::prelude::*;
|
||||
use dioxus_core::{Attribute, TemplateAttribute};
|
||||
use dioxus_edit_stream::*;
|
||||
|
||||
fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||
let asd = 123;
|
||||
let var = 123;
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "asd", class: "{asd}",
|
||||
div {
|
||||
class: "asd",
|
||||
class: "{asd}",
|
||||
onclick: move |_| {},
|
||||
|
||||
div { "{var}" }
|
||||
div {
|
||||
h1 { "var" }
|
||||
p { "you're great!" }
|
||||
div {
|
||||
background_color: "red",
|
||||
div { background_color: "red",
|
||||
h1 { "var" }
|
||||
div {
|
||||
b { "asd" }
|
||||
|
@ -29,16 +29,24 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||
|
||||
fn basic_template(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {"hi!"}
|
||||
div {
|
||||
(0..2).map(|i| rsx! {
|
||||
div { "asd" }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_prints() {
|
||||
let dom = VirtualDom::new(basic_template);
|
||||
let mut dom = VirtualDom::new(basic_template);
|
||||
|
||||
let renderer = dioxus_edit_stream::Mutations::default();
|
||||
dom.rebuild(&mut renderer);
|
||||
let mut edits = Vec::new();
|
||||
|
||||
dbg!(renderer.edits);
|
||||
dom.rebuild(&mut edits);
|
||||
|
||||
dbg!(edits);
|
||||
// let renderer = dioxus_edit_stream::Mutations::default();
|
||||
//
|
||||
// dbg!(renderer.edits);
|
||||
}
|
||||
|
|
|
@ -40,28 +40,29 @@ pub mod on {
|
|||
// mut callback: impl FnMut(UiEvent<$data>) + 'a,
|
||||
) -> Listener<'a>
|
||||
{
|
||||
let bump = &factory.bump();
|
||||
// let bump = &factory.bump();
|
||||
|
||||
|
||||
use dioxus_core::{AnyEvent};
|
||||
// we can't allocate unsized in bumpalo's box, so we need to craft the box manually
|
||||
// safety: this is essentially the same as calling Box::new() but manually
|
||||
// The box is attached to the lifetime of the bumpalo allocator
|
||||
let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
|
||||
let event = evt.downcast::<$data>().unwrap();
|
||||
callback(event)
|
||||
});
|
||||
// // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
|
||||
// // safety: this is essentially the same as calling Box::new() but manually
|
||||
// // The box is attached to the lifetime of the bumpalo allocator
|
||||
// let cb: &mut dyn FnMut(AnyEvent) = bump.alloc(move |evt: AnyEvent| {
|
||||
// let event = evt.downcast::<$data>().unwrap();
|
||||
// callback(event)
|
||||
// });
|
||||
|
||||
let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
||||
// let callback: BumpBox<dyn FnMut(AnyEvent) + 'a> = unsafe { BumpBox::from_raw(cb) };
|
||||
|
||||
// ie oncopy
|
||||
let event_name = stringify!($name);
|
||||
// // ie oncopy
|
||||
// let event_name = stringify!($name);
|
||||
|
||||
// ie copy
|
||||
let shortname: &'static str = &event_name[2..];
|
||||
// // ie copy
|
||||
// let shortname: &'static str = &event_name[2..];
|
||||
|
||||
let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
|
||||
factory.listener(shortname, handler)
|
||||
// let handler = bump.alloc(std::cell::RefCell::new(Some(callback)));
|
||||
// factory.listener(shortname, handler)
|
||||
|
||||
todo!()
|
||||
}
|
||||
)*
|
||||
)*
|
||||
|
@ -1423,7 +1424,7 @@ impl KeyCode {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
|
||||
pub(crate) fn _event_meta<T>(event: &UiEvent<T>) -> (bool, EventPriority) {
|
||||
use EventPriority::*;
|
||||
|
||||
match event.name {
|
||||
|
|
|
@ -17,10 +17,6 @@ mod errors;
|
|||
// mod attributes;
|
||||
mod component;
|
||||
mod element;
|
||||
// #[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
// mod elements;
|
||||
// #[cfg(any(feature = "hot-reload", debug_assertions))]
|
||||
// mod error;
|
||||
mod ifmt;
|
||||
mod node;
|
||||
mod template;
|
||||
|
@ -173,23 +169,40 @@ impl ToTokens for CallBody {
|
|||
let listener_printer = context.dynamic_listeners.iter();
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
__cx.template_ref(
|
||||
::dioxus::core::Template {
|
||||
// LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
// __cx.template_ref(
|
||||
// ::dioxus::core::Template {
|
||||
// id: ::dioxus::core::get_line_num!(),
|
||||
// roots: &[ #roots ]
|
||||
// },
|
||||
// __cx.bump().alloc([
|
||||
// #( #node_printer ),*
|
||||
// ]),
|
||||
// __cx.bump().alloc([
|
||||
// #( #attr_printer ),*
|
||||
// ]),
|
||||
// __cx.bump().alloc([
|
||||
// #( #listener_printer ),*
|
||||
// ]),
|
||||
// None
|
||||
// )
|
||||
// })
|
||||
|
||||
|
||||
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||
id: ::dioxus::core::get_line_num!(),
|
||||
roots: &[ #roots ]
|
||||
},
|
||||
__cx.bump().alloc([
|
||||
#( #node_printer ),*
|
||||
]),
|
||||
__cx.bump().alloc([
|
||||
#( #attr_printer ),*
|
||||
]),
|
||||
__cx.bump().alloc([
|
||||
#( #listener_printer ),*
|
||||
]),
|
||||
None
|
||||
)
|
||||
};
|
||||
|
||||
::dioxus::core::VNode {
|
||||
node_id: Default::default(),
|
||||
parent: None,
|
||||
template: TEMPLATE,
|
||||
root_ids: __cx.bump().alloc([]),
|
||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||
dynamic_attrs: __cx.bump().alloc([]),
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -221,22 +234,16 @@ impl CallBody {
|
|||
roots: &[]
|
||||
};
|
||||
|
||||
LazyNodes::new(TEMPLATE, move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
todo!()
|
||||
LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
::dioxus::core::VNode {
|
||||
node_id: Default::default(),
|
||||
parent: None,
|
||||
template: &TEMPLATE,
|
||||
root_ids: __cx.bump().alloc([]),
|
||||
dynamic_nodes: __cx.bump().alloc([]),
|
||||
dynamic_attrs: __cx.bump().alloc([]),
|
||||
}
|
||||
})
|
||||
})
|
||||
// let template = TemplateBuilder::from_roots(self.roots.clone());
|
||||
// let inner = if let Some(template) = template {
|
||||
// quote! { #template }
|
||||
// } else {
|
||||
// let children = &self.roots;
|
||||
// if children.len() == 1 {
|
||||
// let inner = &self.roots[0];
|
||||
// quote! { #inner }
|
||||
// } else {
|
||||
// quote! { __cx.fragment_root([ #(#children),* ]) }
|
||||
// }
|
||||
// };
|
||||
// out_tokens.append_all(inner);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue