feat: simple tests passing

This commit is contained in:
Jonathan Kelley 2022-11-01 18:42:29 -07:00
parent 23603aaaf5
commit 4a31b29703
27 changed files with 1568 additions and 375 deletions

View file

@ -19,7 +19,7 @@ members = [
"packages/rsx",
"packages/native-core",
"packages/native-core-macro",
"packages/edit-stream",
# "packages/edit-stream",
"docs/guide",
]

View file

@ -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()
}

View file

@ -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());
}

View file

@ -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!(),
}
}
}

View file

@ -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);
}
}

View file

@ -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 {

View file

@ -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!()
}
}

View file

@ -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))
}
}
}
}

View file

@ -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];
}
}

View file

@ -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
View 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]));
}

View 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)
}
}

View 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,
}

View 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 } => {}
}
}
}
}

View 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);
// }
// }

View file

@ -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")
)
};
}

View file

@ -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,
},
}

View file

@ -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>());
}

View file

@ -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,

View file

@ -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.
"###,
)
}
}

View 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));
}
}

View file

@ -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);
// }

View 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);
}

View file

@ -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"

View file

@ -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);
}

View file

@ -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 {

View file

@ -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);
}
}