chore: make warnings go away

This commit is contained in:
Jonathan Kelley committed 2022-11-29 16:31:04 -05:00
1 parent 0027cdd938
commit 7c3d308ab5
34 files changed
+901 -810

No files matched your search

+1 -3
View File
@@ -18,7 +18,6 @@ pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
pub render_fn: fn(Scope<'a, P>) -> F,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: P,
// pub props: PropsAllocation<P>,
_marker: PhantomData<A>,
}
@@ -35,7 +34,6 @@ where
render_fn,
memo,
props,
// props: PropsAllocation::Borrowed(props),
_marker: PhantomData,
}
}
@@ -60,7 +58,7 @@ where
}
fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
let scope = cx.bump().alloc(Scoped {
let scope: &mut Scoped<P> = cx.bump().alloc(Scoped {
props: &self.props,
scope: cx,
});
+62 -44
View File
@@ -1,10 +1,14 @@
use crate::{nodes::VNode, virtual_dom::VirtualDom, Mutations, ScopeId};
use crate::{
factory::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
ScopeId, VFragment,
};
use bumpalo::boxed::Box as BumpBox;
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);
pub struct ElementRef {
pub(crate) struct ElementRef {
// the pathway of the real element inside the template
pub path: ElementPath,
@@ -19,7 +23,7 @@ pub enum ElementPath {
}
impl ElementRef {
pub fn null() -> Self {
pub(crate) fn null() -> Self {
Self {
template: std::ptr::null_mut(),
path: ElementPath::Root(0),
@@ -28,74 +32,88 @@ impl ElementRef {
}
impl<'b> VirtualDom {
pub fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
entry.insert(ElementRef {
template: template as *const _ as *mut _,
path: ElementPath::Deep(path),
});
println!("Claiming {}", id);
ElementId(id)
}
pub fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
entry.insert(ElementRef {
template: template as *const _ as *mut _,
path: ElementPath::Root(path),
});
println!("Claiming {}", id);
ElementId(id)
}
pub fn cleanup_element(&mut self, id: ElementId) {
self.elements.remove(id.0);
pub(crate) fn reclaim(&mut self, el: ElementId) {
self.try_reclaim(el)
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
}
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, false);
todo!()
}
pub fn reclaim(&mut self, el: ElementId) {
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
assert_ne!(el, ElementId(0));
self.elements.remove(el.0);
self.elements.try_remove(el.0)
}
pub fn drop_template(
&mut self,
mutations: &mut Mutations,
template: &'b VNode<'b>,
gen_roots: bool,
) {
// for node in template.dynamic_nodes.iter() {
// match node {
// DynamicNode::Text { id, .. } => {}
// Drop a scope and all its children
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
let scope = self.scopes.get(id.0).unwrap();
// DynamicNode::Component { .. } => {
// todo!()
// }
if let Some(root) = scope.as_ref().try_root_node() {
let root = unsafe { root.extend_lifetime_ref() };
match root {
RenderReturn::Sync(Ok(node)) => self.drop_scope_inner(node),
_ => {}
}
}
// DynamicNode::Fragment { inner, nodes } => {}
// DynamicNode::Placeholder(_) => todo!(),
// _ => todo!(),
// }
// }
let scope = self.scopes.get(id.0).unwrap();
if let Some(root) = unsafe { scope.as_ref().previous_frame().try_load_node() } {
let root = unsafe { root.extend_lifetime_ref() };
match root {
RenderReturn::Sync(Ok(node)) => self.drop_scope_inner(node),
_ => {}
}
}
let scope = self.scopes.get(id.0).unwrap().as_ref();
// Drop all the hooks once the children are dropped
// this means we'll drop hooks bottom-up
for hook in scope.hook_list.borrow_mut().drain(..) {
drop(unsafe { BumpBox::from_raw(hook) });
}
}
fn drop_scope_inner(&mut self, node: &VNode) {
for attr in node.dynamic_attrs {
if let AttributeValue::Listener(l) = &attr.value {
l.borrow_mut().take();
}
}
for (idx, _) in node.template.roots.iter().enumerate() {
match node.dynamic_root(idx) {
Some(DynamicNode::Component(c)) => self.drop_scope(c.scope.get().unwrap()),
Some(DynamicNode::Fragment(VFragment::NonEmpty(nodes))) => {
for node in *nodes {
self.drop_scope_inner(node);
}
}
_ => {}
}
}
}
}
impl ElementPath {
pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool {
match *self {
+11 -10
View File
@@ -2,9 +2,9 @@ use crate::factory::RenderReturn;
use bumpalo::Bump;
use std::cell::Cell;
pub struct BumpFrame {
pub(crate) struct BumpFrame {
pub bump: Bump,
pub node: Cell<*mut RenderReturn<'static>>,
pub node: Cell<*const RenderReturn<'static>>,
}
impl BumpFrame {
@@ -12,17 +12,18 @@ impl BumpFrame {
let bump = Bump::with_capacity(capacity);
Self {
bump,
node: Cell::new(std::ptr::null_mut()),
node: Cell::new(std::ptr::null()),
}
}
pub fn reset(&mut self) {
self.bump.reset();
self.node.set(std::ptr::null_mut());
}
/// Creates a new lifetime out of thin air
pub unsafe fn load_node<'b>(&self) -> &'b RenderReturn<'b> {
unsafe { std::mem::transmute(&*self.node.get()) }
pub unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
let node = self.node.get();
if node.is_null() {
return None;
}
unsafe { std::mem::transmute(&*node) }
}
}
+38 -21
View File
@@ -5,9 +5,9 @@ use crate::mutations::Mutation::*;
use crate::nodes::VNode;
use crate::nodes::{DynamicNode, TemplateNode};
use crate::virtual_dom::VirtualDom;
use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext, TemplateAttribute};
use crate::{AttributeValue, ScopeId, SuspenseContext, TemplateAttribute};
impl<'b: 'static> VirtualDom {
impl<'b> VirtualDom {
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
///
/// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
@@ -44,7 +44,14 @@ impl<'b: 'static> VirtualDom {
DynamicNode::Text(VText { id: slot, value }) => {
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
self.mutations.push(CreateTextNode { value, id });
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_text = unsafe { std::mem::transmute(*value) };
self.mutations.push(CreateTextNode {
value: unbounded_text,
id,
});
1
}
@@ -96,16 +103,24 @@ impl<'b: 'static> VirtualDom {
let attribute = template.dynamic_attrs.get(attr_id).unwrap();
attribute.mounted_element.set(id);
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
match &attribute.value {
AttributeValue::Text(value) => self.mutations.push(SetAttribute {
name: attribute.name,
value: *value,
ns: attribute.namespace,
id,
}),
AttributeValue::Text(value) => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let unbounded_value = unsafe { std::mem::transmute(*value) };
self.mutations.push(SetAttribute {
name: unbounded_name,
value: unbounded_value,
ns: attribute.namespace,
id,
})
}
AttributeValue::Bool(value) => {
self.mutations.push(SetBoolAttribute {
name: attribute.name,
name: unbounded_name,
value: *value,
id,
})
@@ -113,7 +128,7 @@ impl<'b: 'static> VirtualDom {
AttributeValue::Listener(_) => {
self.mutations.push(NewEventListener {
// all listeners start with "on"
event_name: &attribute.name[2..],
event_name: &unbounded_name[2..],
scope: cur_scope,
id,
})
@@ -315,11 +330,14 @@ impl<'b: 'static> VirtualDom {
// Make sure the text node is assigned to the correct element
text.id.set(new_id);
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let value = unsafe { std::mem::transmute(text.value) };
// Add the mutation to the list
self.mutations.push(HydrateText {
id: new_id,
path: &template.template.node_paths[idx][1..],
value: text.value,
value,
});
// Since we're hydrating an existing node, we don't create any new nodes
@@ -356,18 +374,20 @@ impl<'b: 'static> VirtualDom {
}
}
fn create_component_node(
pub(super) fn create_component_node(
&mut self,
template: &'b VNode<'b>,
component: &'b VComponent<'b>,
idx: usize,
) -> usize {
let props = component.props.replace(None).unwrap();
let props = component
.props
.replace(None)
.expect("Props to always exist when a component is being created");
let prop_ptr = unsafe { std::mem::transmute(props.as_ref()) };
let scope = self.new_scope(prop_ptr).id;
let unbounded_props = unsafe { std::mem::transmute(props) };
component.props.replace(Some(props));
let scope = self.new_scope(unbounded_props).id;
component.scope.set(Some(scope));
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
@@ -375,10 +395,7 @@ impl<'b: 'static> VirtualDom {
use RenderReturn::*;
match return_nodes {
Sync(Ok(t)) => {
self.mount_component(scope, template, t, idx)
// self.create(t)
}
Sync(Ok(t)) => self.mount_component(scope, template, t, idx),
Sync(Err(_e)) => todo!("Propogate error upwards"),
Async(_) => self.mount_component_placeholder(template, idx, scope),
}
+101 -65
View File
@@ -13,39 +13,29 @@ use fxhash::{FxHashMap, FxHashSet};
use std::cell::Cell;
use DynamicNode::*;
impl<'b: 'static> VirtualDom {
impl<'b> VirtualDom {
pub fn diff_scope(&mut self, scope: ScopeId) {
let scope_state = &mut self.scopes[scope.0];
// Load the old and new bump arenas
let cur_arena = scope_state.current_frame();
let prev_arena = scope_state.previous_frame();
// Make sure the nodes arent null (they've been set properly)
// This is a rough check to make sure we're not entering any UB
assert_ne!(
cur_arena.node.get(),
std::ptr::null_mut(),
"Call rebuild before diffing"
);
assert_ne!(
prev_arena.node.get(),
std::ptr::null_mut(),
"Call rebuild before diffing"
);
self.scope_stack.push(scope);
unsafe {
let cur_arena = cur_arena.load_node();
let prev_arena = prev_arena.load_node();
self.diff_maybe_node(prev_arena, cur_arena);
// Load the old and new bump arenas
let old = scope_state
.previous_frame()
.try_load_node()
.expect("Call rebuild before diffing");
let new = scope_state
.current_frame()
.try_load_node()
.expect("Call rebuild before diffing");
self.diff_maybe_node(old, new);
}
self.scope_stack.pop();
}
fn diff_maybe_node(&mut self, left: &'b RenderReturn<'b>, right: &'b RenderReturn<'b>) {
fn diff_maybe_node(&mut self, old: &'b RenderReturn<'b>, new: &'b RenderReturn<'b>) {
use RenderReturn::{Async, Sync};
match (left, right) {
match (old, new) {
(Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
// Err cases
@@ -62,10 +52,10 @@ impl<'b: 'static> VirtualDom {
}
}
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, e: &anyhow::Error) {
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {
todo!("Not yet handling error rollover")
}
fn diff_err_to_ok(&mut self, e: &anyhow::Error, l: &'b VNode<'b>) {
fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {
todo!("Not yet handling error rollover")
}
@@ -88,11 +78,13 @@ impl<'b: 'static> VirtualDom {
// todo: add more types of attribute values
match right_attr.value {
AttributeValue::Text(text) => {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value = unsafe { std::mem::transmute(text) };
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
name: left_attr.name,
value: text,
ns: right_attr.namespace,
name,
value,
});
}
// todo: more types of attribute values
@@ -101,15 +93,18 @@ impl<'b: 'static> VirtualDom {
}
}
for (left_node, right_node) in left_template
for (idx, (left_node, right_node)) in left_template
.dynamic_nodes
.iter()
.zip(right_template.dynamic_nodes.iter())
.enumerate()
{
match (left_node, right_node) {
(Text(left), Text(right)) => self.diff_vtext(left, right),
(Fragment(left), Fragment(right)) => self.diff_vfragment(left, right),
(Component(left), Component(right)) => self.diff_vcomponent(left, right),
(Component(left), Component(right)) => {
self.diff_vcomponent(left, right, right_template, idx)
}
_ => self.replace(left_template, right_template),
};
}
@@ -124,24 +119,44 @@ impl<'b: 'static> VirtualDom {
}
}
fn diff_vcomponent(&mut self, left: &'b VComponent<'b>, right: &'b VComponent<'b>) {
// Due to how templates work, we should never get two different components. The only way we could enter
// this codepath is through "light_diff", but we check there that the pointers are the same
assert_eq!(left.render_fn, right.render_fn);
fn diff_vcomponent(
&mut self,
left: &'b VComponent<'b>,
right: &'b VComponent<'b>,
right_template: &'b VNode<'b>,
idx: usize,
) {
// Replace components that have different render fns
if left.render_fn != right.render_fn {
dbg!(&left, &right);
let created = self.create_component_node(right_template, right, idx);
let head = unsafe {
self.scopes[left.scope.get().unwrap().0]
.root_node()
.extend_lifetime_ref()
};
let id = match head {
RenderReturn::Sync(Ok(node)) => self.replace_inner(node),
_ => todo!(),
};
self.mutations
.push(Mutation::ReplaceWith { id, m: created });
return;
}
// Make sure the new vcomponent has the right scopeid associated to it
let scope_id = left.scope.get().unwrap();
right.scope.set(Some(scope_id));
// copy out the box for both
let old = left.props.replace(None).unwrap();
let old = self.scopes[scope_id.0].props.as_ref();
let new = right.props.replace(None).unwrap();
// If the props are static, then we try to memoize by setting the new with the old
// The target scopestate still has the reference to the old props, so there's no need to update anything
// This also implicitly drops the new props since they're not used
if left.static_props && unsafe { old.memoize(new.as_ref()) } {
return right.props.set(Some(old));
return;
}
// If the props are dynamic *or* the memoization failed, then we need to diff the props
@@ -198,7 +213,8 @@ impl<'b: 'static> VirtualDom {
None => self.replace(left, right),
Some(components) => components
.into_iter()
.for_each(|(l, r)| self.diff_vcomponent(l, r)),
.enumerate()
.for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)),
}
}
@@ -207,12 +223,12 @@ impl<'b: 'static> VirtualDom {
/// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
/// different.
fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
right.id.set(left.id.get());
let id = left.id.get();
right.id.set(id);
if left.value != right.value {
self.mutations.push(Mutation::SetText {
id: left.id.get(),
value: right.value,
});
let value = unsafe { std::mem::transmute(right.value) };
self.mutations.push(Mutation::SetText { id, value });
}
}
@@ -234,22 +250,25 @@ impl<'b: 'static> VirtualDom {
}
fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
let placeholder = self.next_element(&l[0], &[]);
r.set(placeholder);
self.mutations
.push(Mutation::CreatePlaceholder { id: placeholder });
// Remove the old nodes, except for onea
let first = self.replace_inner(&l[0]);
self.remove_nodes(&l[1..]);
let placeholder = self.next_element(&l[0], &[]);
r.set(placeholder);
self.mutations
.push(Mutation::CreatePlaceholder { id: placeholder });
self.mutations
.push(Mutation::ReplaceWith { id: first, m: 1 });
self.reclaim(first);
self.try_reclaim(first);
}
// Remove all the top-level nodes, returning the firstmost root ElementId
/// Remove all the top-level nodes, returning the firstmost root ElementId
///
/// All IDs will be garbage collected
fn replace_inner(&mut self, node: &'b VNode<'b>) -> ElementId {
let id = match node.dynamic_root(0) {
None => node.root_ids[0].get(),
@@ -284,8 +303,13 @@ impl<'b: 'static> VirtualDom {
///
/// Simply walks through the dynamic nodes
fn clean_up_node(&mut self, node: &'b VNode<'b>) {
for node in node.dynamic_nodes {
match node {
for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
// Roots are cleaned up automatically?
if node.template.node_paths[idx].len() == 1 {
continue;
}
match dyn_node {
Component(comp) => {
let scope = comp.scope.get().unwrap();
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
@@ -596,10 +620,9 @@ impl<'b: 'static> VirtualDom {
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
// create the new children afresh.
if shared_keys.is_empty() {
if let Some(first_old) = old.get(0) {
if let Some(_) = old.get(0) {
self.remove_nodes(&old[1..]);
let nodes_created = self.create_children(new);
self.replace_node_with_on_stack(first_old, nodes_created);
self.replace_many(&old[0], new);
} else {
// I think this is wrong - why are we appending?
// only valid of the if there are no trailing elements
@@ -718,10 +741,6 @@ impl<'b: 'static> VirtualDom {
}
}
fn replace_node_with_on_stack(&mut self, old: &'b VNode<'b>, m: usize) {
todo!()
}
/// Remove these nodes from the dom
/// Wont generate mutations for the inner nodes
fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
@@ -766,7 +785,7 @@ impl<'b: 'static> VirtualDom {
}
/// Push all the real nodes on the stack
fn push_all_real_nodes(&mut self, node: &VNode) -> usize {
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
let mut onstack = 0;
for (idx, _) in node.template.roots.iter().enumerate() {
@@ -785,7 +804,12 @@ impl<'b: 'static> VirtualDom {
}
}
Some(Component(comp)) => {
todo!()
let scope = comp.scope.get().unwrap();
onstack +=
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Sync(Ok(node)) => self.push_all_real_nodes(node),
_ => todo!(),
}
}
None => {
self.mutations.push(Mutation::PushRoot {
@@ -815,7 +839,7 @@ impl<'b: 'static> VirtualDom {
self.mutations.push(Mutation::InsertAfter { id, m })
}
fn find_first_element(&self, node: &VNode) -> ElementId {
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(0) {
None => node.root_ids[0].get(),
Some(Text(t)) => t.id.get(),
@@ -823,7 +847,7 @@ impl<'b: 'static> VirtualDom {
Some(Fragment(VFragment::Empty(t))) => t.get(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match self.scopes[scope.0].root_node() {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Sync(Ok(t)) => self.find_first_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
@@ -831,7 +855,7 @@ impl<'b: 'static> VirtualDom {
}
}
fn find_last_element(&self, node: &VNode) -> ElementId {
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
match node.dynamic_root(node.template.roots.len() - 1) {
None => node.root_ids.last().unwrap().get(),
Some(Text(t)) => t.id.get(),
@@ -839,7 +863,7 @@ impl<'b: 'static> VirtualDom {
Some(Fragment(VFragment::Empty(t))) => t.get(),
Some(Component(comp)) => {
let scope = comp.scope.get().unwrap();
match self.scopes[scope.0].root_node() {
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
RenderReturn::Sync(Ok(t)) => self.find_last_element(t),
_ => todo!("cannot handle nonstandard nodes"),
}
@@ -855,7 +879,18 @@ impl<'b: 'static> VirtualDom {
id: first,
m: created,
});
self.reclaim(id);
self.try_reclaim(id);
}
fn replace_many(&mut self, left: &'b VNode<'b>, right: &'b [VNode<'b>]) {
let first = self.find_first_element(left);
let id = self.replace_inner(left);
let created = self.create_children(right);
self.mutations.push(Mutation::ReplaceWith {
id: first,
m: created,
});
self.try_reclaim(id);
}
}
@@ -883,7 +918,7 @@ fn matching_components<'a>(
_ => return None,
};
(l.render_fn == r.render_fn).then(|| (l, r))
Some((l, r))
})
.collect()
}
@@ -894,6 +929,7 @@ fn matching_components<'a>(
/// - for text - we can use SetTextContent
/// - for clearning children we can use RemoveChildren
/// - for appending children we can use AppendChildren
#[allow(dead_code)]
fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
let path = node.template.node_paths[idx];
+12 -22
View File
@@ -1,29 +1,19 @@
use std::{cell::RefCell, rc::Rc};
use std::cell::RefCell;
use crate::{ScopeId, ScopeState};
use crate::ScopeId;
pub struct ErrorContext {
/// A boundary that will capture any errors from child components
#[allow(dead_code)]
pub struct ErrorBoundary {
error: RefCell<Option<(anyhow::Error, ScopeId)>>,
id: ScopeId,
}
/// Catch all errors from the children and bubble them up to this component
///
/// Returns the error and scope that caused the error
pub fn use_catch_error(cx: &ScopeState) -> Option<&(anyhow::Error, ScopeId)> {
let err_ctx = use_error_context(cx);
let out = cx.use_hook(|| None);
if let Some(error) = err_ctx.error.take() {
*out = Some(error);
impl ErrorBoundary {
pub fn new(id: ScopeId) -> Self {
Self {
error: RefCell::new(None),
id,
}
}
out.as_ref()
}
/// Create a new error context at this component.
///
/// This component will start to catch any errors that occur in its children.
pub fn use_error_context(cx: &ScopeState) -> &ErrorContext {
cx.use_hook(|| cx.provide_context(Rc::new(ErrorContext { error: None.into() })))
}
-52
View File
@@ -50,58 +50,6 @@ impl<T: Debug> std::fmt::Debug for UiEvent<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,
}
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
+3 -3
View File
@@ -82,9 +82,9 @@ impl ScopeState {
// let as_dyn: &dyn AnyProps = self.bump().alloc(vcomp);
// todo: clean up borrowed props
// if !P::IS_STATIC {
// let vcomp = &*vcomp;
// let vcomp = unsafe { std::mem::transmute(vcomp) };
// self.scope.items.borrow_mut().borrowed_props.push(vcomp);
// let vcomp = ex;
// let vcomp = unsafe { std::mem::transmute(vcomp) };
// self.items.borrow_mut().borrowed_props.push(vcomp);
// }
DynamicNode::Component(VComponent {
+12 -12
View File
@@ -26,17 +26,17 @@ use crate::innerlude::*;
///
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
#[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
let children = cx.props.0.as_ref().unwrap();
Ok(VNode {
node_id: children.node_id.clone(),
key: children.key.clone(),
parent: children.parent.clone(),
template: children.template,
root_ids: children.root_ids,
dynamic_nodes: children.dynamic_nodes,
dynamic_attrs: children.dynamic_attrs,
})
pub fn Fragment<'a>(_cx: Scope<'a, FragmentProps<'a>>) -> Element {
// let children = cx.props.0.as_ref().unwrap();
todo!()
// Ok(VNode {
// key: children.key.clone(),
// parent: children.parent.clone(),
// template: children.template,
// root_ids: children.root_ids,
// dynamic_nodes: children.dynamic_nodes,
// dynamic_attrs: children.dynamic_attrs,
// })
}
pub struct FragmentProps<'a>(Element<'a>);
@@ -96,7 +96,7 @@ impl<'a> Properties for FragmentProps<'a> {
type Builder = FragmentBuilder<'a, false>;
const IS_STATIC: bool = false;
fn builder() -> Self::Builder {
FragmentBuilder(VNode::empty())
todo!()
}
unsafe fn memoize(&self, _other: &Self) -> bool {
false
-63
View File
@@ -1,63 +0,0 @@
use futures_channel::mpsc::UnboundedSender;
use slab::Slab;
use std::future::Future;
use std::{cell::RefCell, rc::Rc, sync::Arc};
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: RefCell<Slab<Arc<dyn Future<Output = ()>>>>,
}
impl FutureQueue {
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
Self {
sender,
queue: Default::default(),
}
}
pub fn spawn(&self, scope: ScopeId, fut: impl Future<Output = ()> + 'static) -> TaskId {
let id = self.queue.borrow_mut().insert(Arc::new(fut));
TaskId { id, scope }
}
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,
}
+4 -5
View File
@@ -20,6 +20,7 @@ mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::arena::*;
pub use crate::dirty_scope::*;
pub use crate::error_boundary::*;
pub use crate::events::*;
pub use crate::fragment::*;
pub use crate::lazynodes::*;
@@ -78,8 +79,6 @@ pub use crate::innerlude::{
DynamicNode,
Element,
ElementId,
ElementRef,
EventPriority,
Fragment,
LazyNodes,
Mutation,
@@ -109,9 +108,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Element, EventHandler, EventPriority, Fragment, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
TemplateNode, UiEvent, VNode, VirtualDom,
fc_to_builder, Element, EventHandler, Fragment, LazyNodes, NodeFactory, Properties, Scope,
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent,
VNode, VirtualDom,
};
}
+1
View File
@@ -1,6 +1,7 @@
use crate::{arena::ElementId, ScopeId};
#[derive(Debug)]
#[must_use = "not handling edits can lead to visual inconsistencies in UI"]
pub struct Mutations<'a> {
pub subtree: usize,
pub template_mutations: Vec<Mutation<'a>>,
+12 -13
View File
@@ -1,4 +1,5 @@
use crate::{any_props::AnyProps, arena::ElementId, Element, ScopeId, ScopeState, UiEvent};
use bumpalo::boxed::Box as BumpBox;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
@@ -12,9 +13,6 @@ pub type TemplateId = &'static str;
/// static parts of the template.
#[derive(Debug, Clone)]
pub struct VNode<'a> {
// The ID assigned for the root of this template
pub node_id: Cell<ElementId>,
/// The key given to the root of this template.
///
/// In fragments, this is the key of the first child. In other cases, it is the key of the root.
@@ -40,7 +38,6 @@ pub struct VNode<'a> {
impl<'a> VNode<'a> {
pub fn empty() -> Element<'a> {
Ok(VNode {
node_id: Cell::new(ElementId(0)),
key: None,
parent: None,
root_ids: &[],
@@ -156,26 +153,28 @@ pub enum AttributeValue<'a> {
Float(f32),
Int(i32),
Bool(bool),
Listener(RefCell<&'a mut dyn FnMut(UiEvent<dyn Any>)>),
Any(&'a dyn AnyValue),
Listener(RefCell<Option<BumpBox<'a, dyn FnMut(UiEvent<dyn Any>) + 'a>>>),
Any(BumpBox<'a, dyn AnyValue>),
None,
}
impl<'a> AttributeValue<'a> {
pub fn new_listener<T: 'static>(
cx: &'a ScopeState,
mut f: impl FnMut(UiEvent<T>) + 'a,
mut callback: impl FnMut(UiEvent<T>) + 'a,
) -> AttributeValue<'a> {
AttributeValue::Listener(RefCell::new(cx.bump().alloc(
move |event: UiEvent<dyn Any>| {
let boxed: BumpBox<'a, dyn FnMut(_) + 'a> = unsafe {
BumpBox::from_raw(cx.bump().alloc(move |event: UiEvent<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() {
f(UiEvent {
callback(UiEvent {
bubbles: event.bubbles,
data,
})
}
},
)))
}))
};
AttributeValue::Listener(RefCell::new(Some(boxed)))
}
}
@@ -201,7 +200,7 @@ impl<'a> PartialEq for AttributeValue<'a> {
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Listener(_), Self::Listener(_)) => true,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(*r0),
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
+52 -6
View File
@@ -7,6 +7,7 @@ use crate::{
scheduler::RcWake,
scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom,
AttributeValue, DynamicNode, VFragment, VNode,
};
use futures_util::FutureExt;
use std::{
@@ -17,13 +18,13 @@ use std::{
};
impl VirtualDom {
pub(super) fn new_scope(&mut self, props: *const dyn AnyProps<'static>) -> &mut ScopeState {
pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &mut ScopeState {
let parent = self.acquire_current_scope_raw();
let entry = self.scopes.vacant_entry();
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
let id = ScopeId(entry.key());
entry.insert(ScopeState {
entry.insert(Box::new(ScopeState {
parent,
id,
height,
@@ -38,19 +39,64 @@ impl VirtualDom {
hook_idx: Default::default(),
shared_contexts: Default::default(),
tasks: self.scheduler.clone(),
})
}))
}
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))
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
}
pub fn ensure_drop_safety(&self, scope: ScopeId) {
let scope = &self.scopes[scope.0];
let node = unsafe { scope.previous_frame().try_load_node() };
// And now we want to make sure the previous frame has dropped anything that borrows self
if let Some(RenderReturn::Sync(Ok(node))) = node {
self.ensure_drop_safety_inner(node);
}
}
fn ensure_drop_safety_inner(&self, node: &VNode) {
for attr in node.dynamic_attrs {
if let AttributeValue::Listener(l) = &attr.value {
l.borrow_mut().take();
}
}
for child in node.dynamic_nodes {
match child {
DynamicNode::Component(c) => {
// Only descend if the props are borrowed
if !c.static_props {
self.ensure_drop_safety(c.scope.get().unwrap());
c.props.set(None);
}
}
DynamicNode::Fragment(VFragment::NonEmpty(f)) => {
for node in *f {
self.ensure_drop_safety_inner(node);
}
}
_ => {}
}
}
}
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
self.ensure_drop_safety(scope_id);
let mut new_nodes = unsafe {
let scope = &mut self.scopes[scope_id.0];
let scope = self.scopes[scope_id.0].as_mut();
scope.previous_frame_mut().bump.reset();
// Make sure to reset the hook counter so we give out hooks in the right order
scope.hook_idx.set(0);
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
@@ -113,7 +159,7 @@ impl VirtualDom {
let frame = scope.previous_frame();
// set the new head of the bump frame
let alloced = frame.bump.alloc(new_nodes);
let alloced = &*frame.bump.alloc(new_nodes);
frame.node.set(alloced);
// And move the render generation forward by one
+13 -4
View File
@@ -82,12 +82,12 @@ pub struct ScopeState {
pub(crate) tasks: Rc<Scheduler>,
pub(crate) spawned_tasks: HashSet<TaskId>,
pub(crate) props: *const dyn AnyProps<'static>,
pub(crate) props: Box<dyn AnyProps<'static>>,
pub(crate) placeholder: Cell<Option<ElementId>>,
}
impl ScopeState {
pub fn current_frame(&self) -> &BumpFrame {
pub(crate) fn current_frame(&self) -> &BumpFrame {
match self.render_cnt.get() % 2 {
0 => &self.node_arena_1,
1 => &self.node_arena_2,
@@ -95,7 +95,7 @@ impl ScopeState {
}
}
pub fn previous_frame(&self) -> &BumpFrame {
pub(crate) fn previous_frame(&self) -> &BumpFrame {
match self.render_cnt.get() % 2 {
1 => &self.node_arena_1,
0 => &self.node_arena_2,
@@ -103,6 +103,14 @@ impl ScopeState {
}
}
pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
match self.render_cnt.get() % 2 {
1 => &mut self.node_arena_1,
0 => &mut self.node_arena_2,
_ => unreachable!(),
}
}
/// Get the current render since the inception of this component
///
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
@@ -117,7 +125,8 @@ impl ScopeState {
///
/// If you need to allocate items that need to be dropped, use bumpalo's box.
pub fn bump(&self) -> &Bump {
&self.current_frame().bump
// note that this is actually the previous frame since we use that as scratch space while the component is rendering
&self.previous_frame().bump
}
/// Get a handle to the currently active head node arena for this Scope
+17 -17
View File
@@ -6,12 +6,12 @@ use crate::{
any_props::VProps,
arena::{ElementId, ElementRef},
factory::RenderReturn,
innerlude::{DirtyScope, ElementPath, Mutations, Scheduler, SchedulerMsg},
innerlude::{DirtyScope, Mutations, Scheduler, SchedulerMsg, ErrorBoundary},
mutations::Mutation,
nodes::{Template, TemplateId},
scheduler::{SuspenseBoundary, SuspenseId},
scopes::{ScopeId, ScopeState},
AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent,
AttributeValue, Element, Scope, SuspenseContext, UiEvent,
};
use futures_util::{pin_mut, StreamExt};
use slab::Slab;
@@ -149,7 +149,7 @@ use std::{
/// ```
pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) scopes: Slab<ScopeState>,
pub(crate) scopes: Slab<Box<ScopeState>>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,
@@ -224,10 +224,7 @@ impl VirtualDom {
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<P>(root: fn(Scope<P>) -> Element, root_props: P) -> Self
where
P: 'static,
{
pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
let mut dom = Self {
rx,
@@ -242,11 +239,11 @@ impl VirtualDom {
mutations: Mutations::new(),
};
let root = dom.new_scope(Box::leak(Box::new(VProps::new(
let root = dom.new_scope(Box::new(VProps::new(
root,
|_, _| unreachable!(),
root_props,
))));
)));
// The root component is always a suspense boundary for any async children
// This could be unexpected, so we might rethink this behavior later
@@ -254,6 +251,9 @@ impl VirtualDom {
// We *could* just panic if the suspense boundary is not found
root.provide_context(Rc::new(SuspenseBoundary::new(ScopeId(0))));
// Unlike react, we provide a default error boundary that just renders the error as a string
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
// the root element is always given element ID 0 since it's the container for the entire tree
dom.elements.insert(ElementRef::null());
@@ -264,7 +264,7 @@ impl VirtualDom {
///
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0)
self.scopes.get(id.0).map(|f| f.as_ref())
}
/// Get the single scope at the top of the VirtualDom tree that will always be around
@@ -285,7 +285,7 @@ impl VirtualDom {
/// Manually mark a scope as requiring a re-render
///
/// Whenever the VirtualDom "works", it will re-render this scope
pub fn mark_dirty_scope(&mut self, id: ScopeId) {
pub fn mark_dirty(&mut self, id: ScopeId) {
let height = self.scopes[id.0].height;
self.dirty_scopes.insert(DirtyScope { height, id });
}
@@ -329,7 +329,6 @@ impl VirtualDom {
data: Rc<dyn Any>,
element: ElementId,
bubbles: bool,
_priority: EventPriority,
) {
/*
------------------------
@@ -393,7 +392,10 @@ impl VirtualDom {
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
listener.borrow_mut()(uievent.clone());
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
if !uievent.bubbles.get() {
return;
}
@@ -427,7 +429,7 @@ impl VirtualDom {
match some_msg.take() {
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
Some(msg) => match msg {
SchedulerMsg::Immediate(id) => self.mark_dirty_scope(id),
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
},
@@ -479,7 +481,6 @@ impl VirtualDom {
///
/// apply_edits(edits);
/// ```
#[must_use]
pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
// Rebuilding implies we append the created elements to the root
@@ -497,7 +498,6 @@ impl VirtualDom {
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
/// suspended subtrees.
#[must_use]
pub fn render_immediate(&mut self) -> Mutations {
// Build a waker that won't wake up since our deadline is already expired when it's polled
let waker = futures_util::task::noop_waker();
@@ -612,6 +612,6 @@ impl VirtualDom {
impl Drop for VirtualDom {
fn drop(&mut self) {
// self.drop_scope(ScopeId(0));
self.drop_scope(ScopeId(0));
}
}