wip: move things into a "shared" object
This commit is contained in:
1 parent
22f894e6b9
commit
f644d7c441
19 files changed
+542
-698
No files matched your search
@@ -109,20 +109,29 @@ impl Parse for Element<AS_HTML> {
|
|||||||
let name_str = name.to_string();
|
let name_str = name.to_string();
|
||||||
stream.parse::<Token![=]>()?;
|
stream.parse::<Token![=]>()?;
|
||||||
if name_str.starts_with("on") {
|
if name_str.starts_with("on") {
|
||||||
todo!()
|
let inner;
|
||||||
|
syn::braced!(inner in stream);
|
||||||
|
let toks = inner.parse::<Expr>()?;
|
||||||
|
let ty = AttrType::EventTokens(toks);
|
||||||
|
listeners.push(ElementAttr {
|
||||||
|
element_name: el_name.clone(),
|
||||||
|
name,
|
||||||
|
value: ty,
|
||||||
|
namespace: None,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
match name_str.as_str() {
|
match name_str.as_str() {
|
||||||
"style" => todo!(),
|
"style" => {}
|
||||||
"key" => todo!(),
|
"key" => {}
|
||||||
"classes" => todo!(),
|
"classes" | "namespace" | "ref" | _ => {
|
||||||
"namespace" => todo!(),
|
|
||||||
"ref" => todo!(),
|
|
||||||
_ => {
|
|
||||||
let ty = if stream.peek(LitStr) {
|
let ty = if stream.peek(LitStr) {
|
||||||
let rawtext = stream.parse::<LitStr>().unwrap();
|
let rawtext = stream.parse::<LitStr>().unwrap();
|
||||||
AttrType::BumpText(rawtext)
|
AttrType::BumpText(rawtext)
|
||||||
} else {
|
} else {
|
||||||
let toks = stream.parse::<Expr>()?;
|
// like JSX, we expect raw expressions
|
||||||
|
let inner;
|
||||||
|
syn::braced!(inner in stream);
|
||||||
|
let toks = inner.parse::<Expr>()?;
|
||||||
AttrType::FieldTokens(toks)
|
AttrType::FieldTokens(toks)
|
||||||
};
|
};
|
||||||
attributes.push(ElementAttr {
|
attributes.push(ElementAttr {
|
||||||
@@ -134,13 +143,6 @@ impl Parse for Element<AS_HTML> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// if stream.peek(LitStr) {
|
|
||||||
|
|
||||||
// } else {
|
|
||||||
// }
|
|
||||||
// if name_str.starts_with("on") {}
|
|
||||||
|
|
||||||
// attributes.push(stream.parse()?);
|
|
||||||
}
|
}
|
||||||
stream.parse::<Token![>]>()?;
|
stream.parse::<Token![>]>()?;
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
+111
-37
@@ -1,55 +1,99 @@
|
|||||||
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::{cell::UnsafeCell, rc::Rc};
|
use std::{cell::UnsafeCell, rc::Rc};
|
||||||
|
|
||||||
|
use crate::heuristics::*;
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
|
use futures_util::stream::FuturesUnordered;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
|
|
||||||
|
slotmap::new_key_type! {
|
||||||
|
// A dedicated key type for the all the scopes
|
||||||
|
pub struct ScopeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
slotmap::new_key_type! {
|
||||||
|
// A dedicated key type for every real element that the virtualdom creates.
|
||||||
|
//
|
||||||
|
// This is a slotmap key because we expect the "mirror" realdom to also maintain a slotmap mapping
|
||||||
|
// of virtual element to real element.
|
||||||
|
pub struct ElementId;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementId {
|
||||||
|
pub fn as_u64(self) -> u64 {
|
||||||
|
self.0.as_ffi()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Shared<T> = Rc<RefCell<T>>;
|
||||||
|
|
||||||
|
/// These are resources shared among all the components and the virtualdom itself
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SharedArena {
|
pub struct SharedResources {
|
||||||
pub components: Rc<UnsafeCell<ScopeMap>>,
|
pub components: Rc<UnsafeCell<SlotMap<ScopeId, Scope>>>,
|
||||||
}
|
|
||||||
pub type ScopeMap = SlotMap<ScopeId, Scope>;
|
|
||||||
|
|
||||||
enum MutStatus {
|
pub event_queue: Shared<Vec<HeightMarker>>,
|
||||||
Immut,
|
|
||||||
Mut,
|
pub events: Shared<Vec<EventTrigger>>,
|
||||||
|
|
||||||
|
pub(crate) heuristics: Shared<HeuristicsEngine>,
|
||||||
|
|
||||||
|
pub(crate) tasks: Shared<FuturesUnordered<FiberTask>>,
|
||||||
|
|
||||||
|
/// We use a SlotSet to keep track of the keys that are currently being used.
|
||||||
|
/// However, we don't store any specific data since the "mirror"
|
||||||
|
pub raw_elements: Shared<SlotMap<ElementId, ()>>,
|
||||||
|
|
||||||
|
pub task_setter: Rc<dyn Fn(ScopeId)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedArena {
|
impl SharedResources {
|
||||||
pub fn new(arena: ScopeMap) -> Self {
|
pub fn new() -> Self {
|
||||||
let components = Rc::new(UnsafeCell::new(arena));
|
// preallocate 1000 elements and 20 scopes to avoid dynamic allocation
|
||||||
SharedArena { components }
|
let components = Rc::new(UnsafeCell::new(
|
||||||
|
SlotMap::<ScopeId, Scope>::with_capacity_and_key(20),
|
||||||
|
));
|
||||||
|
let raw_elements = SlotMap::<ElementId, ()>::with_capacity_and_key(1000);
|
||||||
|
|
||||||
|
let event_queue = Rc::new(RefCell::new(Vec::new()));
|
||||||
|
let tasks = Vec::new();
|
||||||
|
let heuristics = HeuristicsEngine::new();
|
||||||
|
|
||||||
|
let queue = event_queue.clone();
|
||||||
|
let _components = components.clone();
|
||||||
|
let task_setter = Rc::new(move |idx| {
|
||||||
|
let comps = unsafe { &*_components.get() };
|
||||||
|
if let Some(scope) = comps.get(idx) {
|
||||||
|
queue.borrow_mut().push(HeightMarker {
|
||||||
|
height: scope.height,
|
||||||
|
idx,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
event_queue,
|
||||||
|
components,
|
||||||
|
tasks: Rc::new(RefCell::new(FuturesUnordered::new())),
|
||||||
|
events: Rc::new(RefCell::new(tasks)),
|
||||||
|
heuristics: Rc::new(RefCell::new(heuristics)),
|
||||||
|
raw_elements: Rc::new(RefCell::new(raw_elements)),
|
||||||
|
task_setter,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// THIS METHOD IS CURRENTLY UNSAFE
|
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||||
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
pub unsafe fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
|
||||||
pub fn get(&self, idx: ScopeId) -> Option<&Scope> {
|
let inner = &*self.components.get();
|
||||||
let inner = unsafe { &*self.components.get() };
|
|
||||||
inner.get(idx)
|
inner.get(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// THIS METHOD IS CURRENTLY UNSAFE
|
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||||
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
pub unsafe fn get_sope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
||||||
pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
let inner = &mut *self.components.get();
|
||||||
let inner = unsafe { &mut *self.components.get() };
|
|
||||||
inner.get_mut(idx)
|
inner.get_mut(idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner(&self) -> &ScopeMap {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn inner_mut(&mut self) -> &mut ScopeMap {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// THIS METHOD IS CURRENTLY UNSAFE
|
|
||||||
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
|
|
||||||
pub fn with<T>(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result<T> {
|
|
||||||
let inner = unsafe { &mut *self.components.get() };
|
|
||||||
Ok(f(inner))
|
|
||||||
// todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_scope<'b, O: 'static>(
|
pub fn with_scope<'b, O: 'static>(
|
||||||
&'b self,
|
&'b self,
|
||||||
_id: ScopeId,
|
_id: ScopeId,
|
||||||
@@ -75,7 +119,37 @@ impl SharedArena {
|
|||||||
.ok_or_else(|| Error::FatalInternal("Scope not found"))
|
.ok_or_else(|| Error::FatalInternal("Scope not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn inner_unchecked<'s>() -> &'s mut ScopeMap {
|
pub fn reserve_node(&self) -> ElementId {
|
||||||
todo!()
|
self.raw_elements.borrow_mut().insert(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// return the id, freeing the space of the original node
|
||||||
|
pub fn collect_garbage(&self, id: ElementId) {}
|
||||||
|
|
||||||
|
pub fn borrow_queue(&self) -> RefMut<Vec<HeightMarker>> {
|
||||||
|
self.event_queue.borrow_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
|
||||||
|
let g = unsafe { &mut *self.components.get() };
|
||||||
|
g.insert_with_key(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn schedule_update(&self) -> Rc<dyn Fn(ScopeId)> {
|
||||||
|
self.task_setter.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
||||||
|
self.tasks.borrow_mut().push(task);
|
||||||
|
TaskHandle {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TaskHandle {}
|
||||||
|
|
||||||
|
impl TaskHandle {
|
||||||
|
pub fn toggle(&self) {}
|
||||||
|
pub fn start(&self) {}
|
||||||
|
pub fn stop(&self) {}
|
||||||
|
pub fn restart(&self) {}
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
pub struct ActiveFrame {
|
pub struct ActiveFrame {
|
||||||
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||||
pub generation: RefCell<usize>,
|
pub generation: Cell<usize>,
|
||||||
|
|
||||||
// The double-buffering situation that we will use
|
// The double-buffering situation that we will use
|
||||||
pub frames: [BumpFrame; 2],
|
pub frames: [BumpFrame; 2],
|
||||||
@@ -13,78 +13,100 @@ pub struct ActiveFrame {
|
|||||||
pub struct BumpFrame {
|
pub struct BumpFrame {
|
||||||
pub bump: Bump,
|
pub bump: Bump,
|
||||||
pub head_node: VNode<'static>,
|
pub head_node: VNode<'static>,
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActiveFrame {
|
impl ActiveFrame {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::from_frames(
|
let frame_a = BumpFrame {
|
||||||
BumpFrame {
|
bump: Bump::new(),
|
||||||
bump: Bump::new(),
|
head_node: NodeFactory::unstable_place_holder(),
|
||||||
head_node: NodeFactory::static_text(""),
|
|
||||||
},
|
|
||||||
BumpFrame {
|
|
||||||
bump: Bump::new(),
|
|
||||||
head_node: NodeFactory::static_text(""),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
#[cfg(test)]
|
||||||
|
name: "old",
|
||||||
|
};
|
||||||
|
let frame_b = BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: NodeFactory::unstable_place_holder(),
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
name: "new",
|
||||||
|
};
|
||||||
Self {
|
Self {
|
||||||
generation: 0.into(),
|
generation: 0.into(),
|
||||||
frames: [a, b],
|
frames: [frame_a, frame_b],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cur_frame(&self) -> &BumpFrame {
|
pub fn prev_frame(&self) -> &BumpFrame {
|
||||||
match *self.generation.borrow() & 1 == 0 {
|
match self.generation.get() & 1 == 0 {
|
||||||
true => &self.frames[0],
|
true => &self.frames[0],
|
||||||
false => &self.frames[1],
|
false => &self.frames[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
|
||||||
match *self.generation.borrow() & 1 == 0 {
|
pub fn prev_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
|
match self.generation.get() & 1 == 0 {
|
||||||
true => &mut self.frames[0],
|
true => &mut self.frames[0],
|
||||||
false => &mut self.frames[1],
|
false => &mut self.frames[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cur_frame(&self) -> &BumpFrame {
|
||||||
|
match self.generation.get() & 1 == 1 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
false => &self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
|
match self.generation.get() & 1 == 1 {
|
||||||
|
true => &mut self.frames[0],
|
||||||
|
false => &mut self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Give out our self-referential item with our own borrowed lifetime
|
||||||
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
let raw_node = match *self.generation.borrow() & 1 == 0 {
|
let cur_head = &self.cur_frame().head_node;
|
||||||
true => &self.frames[0],
|
unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
|
||||||
false => &self.frames[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Give out our self-referential item with our own borrowed lifetime
|
|
||||||
unsafe {
|
|
||||||
let unsafe_head = &raw_node.head_node;
|
|
||||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
|
||||||
safe_node
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Give out our self-referential item with our own borrowed lifetime
|
||||||
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
let raw_node = match *self.generation.borrow() & 1 != 0 {
|
let cur_head = &self.prev_frame().head_node;
|
||||||
true => &self.frames[0],
|
unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
|
||||||
false => &self.frames[1],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Give out our self-referential item with our own borrowed lifetime
|
|
||||||
unsafe {
|
|
||||||
let unsafe_head = &raw_node.head_node;
|
|
||||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
|
||||||
safe_node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
|
|
||||||
match *self.generation.borrow() & 1 == 0 {
|
|
||||||
true => &mut self.frames[1],
|
|
||||||
false => &mut self.frames[0],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn cycle_frame(&mut self) {
|
pub fn cycle_frame(&mut self) {
|
||||||
*self.generation.borrow_mut() += 1;
|
self.generation.set(self.generation.get() + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
//! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bump_frame() {
|
||||||
|
let mut frames = ActiveFrame::new();
|
||||||
|
|
||||||
|
// just cycle a few times and make sure we get the right frames out
|
||||||
|
for _ in 0..5 {
|
||||||
|
let old = frames.prev_frame();
|
||||||
|
let new = frames.cur_frame();
|
||||||
|
assert_eq!(old.name, "old");
|
||||||
|
assert_eq!(new.name, "new");
|
||||||
|
frames.cycle_frame();
|
||||||
|
|
||||||
|
let old = frames.prev_frame();
|
||||||
|
let new = frames.cur_frame();
|
||||||
|
assert_eq!(old.name, "new");
|
||||||
|
assert_eq!(new.name, "old");
|
||||||
|
frames.cycle_frame();
|
||||||
|
}
|
||||||
|
assert_eq!(frames.generation.get(), 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
|
|
||||||
///
|
|
||||||
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
|
||||||
/// "InsertBefore".
|
|
||||||
struct RealChildIterator<'a> {
|
|
||||||
scopes: &'a SharedArena,
|
|
||||||
|
|
||||||
// Heuristcally we should never bleed into 5 completely nested fragments/components
|
|
||||||
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
|
||||||
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RealChildIterator<'a> {
|
|
||||||
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
|
|
||||||
Self {
|
|
||||||
scopes,
|
|
||||||
stack: smallvec::smallvec![(0, starter)],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for RealChildIterator<'a> {
|
|
||||||
type Item = &'a VNode<'a>;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<&'a VNode<'a>> {
|
|
||||||
let mut should_pop = false;
|
|
||||||
let mut returned_node = None;
|
|
||||||
let mut should_push = None;
|
|
||||||
|
|
||||||
while returned_node.is_none() {
|
|
||||||
if let Some((count, node)) = self.stack.last_mut() {
|
|
||||||
match node {
|
|
||||||
// We can only exit our looping when we get "real" nodes
|
|
||||||
// This includes fragments and components when they're empty (have a single root)
|
|
||||||
VNode::Element(_) | VNode::Text(_) => {
|
|
||||||
// We've recursed INTO an element/text
|
|
||||||
// We need to recurse *out* of it and move forward to the next
|
|
||||||
should_pop = true;
|
|
||||||
returned_node = Some(&**node);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get a fragment we push the next child
|
|
||||||
VNode::Fragment(frag) => {
|
|
||||||
let subcount = *count as usize;
|
|
||||||
|
|
||||||
if frag.children.len() == 0 {
|
|
||||||
should_pop = true;
|
|
||||||
returned_node = Some(&**node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if subcount >= frag.children.len() {
|
|
||||||
should_pop = true;
|
|
||||||
} else {
|
|
||||||
should_push = Some(&frag.children[subcount]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immediately abort suspended nodes - can't do anything with them yet
|
|
||||||
// VNode::Suspended => should_pop = true,
|
|
||||||
VNode::Suspended { real } => todo!(),
|
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
|
||||||
VNode::Component(sc) => {
|
|
||||||
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
|
|
||||||
|
|
||||||
// Simply swap the current node on the stack with the root of the component
|
|
||||||
*node = scope.root();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If there's no more items on the stack, we're done!
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
if should_pop {
|
|
||||||
self.stack.pop();
|
|
||||||
if let Some((id, _)) = self.stack.last_mut() {
|
|
||||||
*id += 1;
|
|
||||||
}
|
|
||||||
should_pop = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(push) = should_push {
|
|
||||||
self.stack.push((0, push));
|
|
||||||
should_push = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
returned_node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,8 +5,40 @@
|
|||||||
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||||
//! that ensures compile-time required and optional fields on cx.
|
//! that ensures compile-time required and optional fields on cx.
|
||||||
|
|
||||||
use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC};
|
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||||
|
|
||||||
|
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||||
|
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||||
|
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||||
|
///
|
||||||
|
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
|
||||||
|
/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
|
||||||
|
/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
|
||||||
|
///
|
||||||
|
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
|
||||||
|
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
|
||||||
|
///
|
||||||
|
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
|
||||||
|
/// we advise the use of smart pointers in cases where memoization is important.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// For props that are 'static:
|
||||||
|
/// ```rust ignore
|
||||||
|
/// #[derive(Props, PartialEq)]
|
||||||
|
/// struct MyProps {
|
||||||
|
/// data: String
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For props that borrow:
|
||||||
|
///
|
||||||
|
/// ```rust ignore
|
||||||
|
/// #[derive(Props)]
|
||||||
|
/// struct MyProps<'a >{
|
||||||
|
/// data: &'a str
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
pub trait Properties: Sized {
|
pub trait Properties: Sized {
|
||||||
type Builder;
|
type Builder;
|
||||||
const IS_STATIC: bool = false;
|
const IS_STATIC: bool = false;
|
||||||
@@ -50,6 +82,16 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
|||||||
///
|
///
|
||||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
||||||
|
///
|
||||||
|
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||||
|
///
|
||||||
|
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// rsx!{
|
||||||
|
/// Fragment { key: "abc" }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
#[allow(non_upper_case_globals, non_snake_case)]
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
|
||||||
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
|
||||||
|
|||||||
@@ -99,12 +99,21 @@ impl<'src, P> Context<'src, P> {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn children(&self) -> &'src [VNode<'src>] {
|
pub fn children(&self) -> &'src [VNode<'src>] {
|
||||||
self.scope.child_nodes
|
self.scope.child_nodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a subscription that schedules a future render for the reference component
|
/// Create a subscription that schedules a future render for the reference component
|
||||||
|
///
|
||||||
|
/// ## Notice: you should prefer using prepare_update and get_scope_id
|
||||||
|
///
|
||||||
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
||||||
self.scope.event_channel.clone()
|
let cb = self.scope.vdom.schedule_update();
|
||||||
|
let id = self.get_scope_id();
|
||||||
|
Rc::new(move || cb(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
|
||||||
|
self.scope.vdom.schedule_update()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
|
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
|
||||||
@@ -142,7 +151,7 @@ impl<'src, P> Context<'src, P> {
|
|||||||
let scope_ref = self.scope;
|
let scope_ref = self.scope;
|
||||||
let listener_id = &scope_ref.listener_idx;
|
let listener_id = &scope_ref.listener_idx;
|
||||||
Some(lazy_nodes.into_vnode(NodeFactory {
|
Some(lazy_nodes.into_vnode(NodeFactory {
|
||||||
scope_ref,
|
scope: scope_ref,
|
||||||
listener_id,
|
listener_id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@@ -297,7 +306,7 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
} else {
|
} else {
|
||||||
match inner.parent_idx {
|
match inner.parent_idx {
|
||||||
Some(parent_id) => {
|
Some(parent_id) => {
|
||||||
scope = inner.arena_link.get(parent_id);
|
scope = unsafe { inner.vdom.get_scope(parent_id) };
|
||||||
}
|
}
|
||||||
None => break,
|
None => break,
|
||||||
}
|
}
|
||||||
@@ -329,8 +338,7 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
|
||||||
(self.scope.task_submitter)(task);
|
self.scope.vdom.submit_task(task)
|
||||||
TaskHandle { _p: PhantomData {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||||
@@ -341,13 +349,14 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
pub fn use_task<Out, Fut, Init>(
|
pub fn use_task<Out, Fut, Init>(
|
||||||
self,
|
self,
|
||||||
task_initializer: Init,
|
task_initializer: Init,
|
||||||
) -> (TaskHandle<'src>, &'src Option<Out>)
|
) -> (&'src TaskHandle, &'src Option<Out>)
|
||||||
where
|
where
|
||||||
Out: 'static,
|
Out: 'static,
|
||||||
Fut: Future<Output = Out> + 'static,
|
Fut: Future<Output = Out> + 'static,
|
||||||
Init: FnOnce() -> Fut + 'src,
|
Init: FnOnce() -> Fut + 'src,
|
||||||
{
|
{
|
||||||
struct TaskHook<T> {
|
struct TaskHook<T> {
|
||||||
|
handle: TaskHandle,
|
||||||
task_dump: Rc<RefCell<Option<T>>>,
|
task_dump: Rc<RefCell<Option<T>>>,
|
||||||
value: Option<T>,
|
value: Option<T>,
|
||||||
}
|
}
|
||||||
@@ -360,12 +369,15 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
let task_dump = Rc::new(RefCell::new(None));
|
let task_dump = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
let slot = task_dump.clone();
|
let slot = task_dump.clone();
|
||||||
let update = self.schedule_update();
|
|
||||||
|
let updater = self.prepare_update();
|
||||||
|
let update_id = self.get_scope_id();
|
||||||
|
|
||||||
let originator = self.scope.our_arena_idx.clone();
|
let originator = self.scope.our_arena_idx.clone();
|
||||||
|
|
||||||
self.submit_task(Box::pin(task_fut.then(move |output| async move {
|
let handle = self.submit_task(Box::pin(task_fut.then(move |output| async move {
|
||||||
*slot.as_ref().borrow_mut() = Some(output);
|
*slot.as_ref().borrow_mut() = Some(output);
|
||||||
update();
|
updater(update_id);
|
||||||
EventTrigger {
|
EventTrigger {
|
||||||
event: VirtualEvent::AsyncEvent { hook_idx },
|
event: VirtualEvent::AsyncEvent { hook_idx },
|
||||||
originator,
|
originator,
|
||||||
@@ -377,13 +389,14 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
TaskHook {
|
TaskHook {
|
||||||
task_dump,
|
task_dump,
|
||||||
value: None,
|
value: None,
|
||||||
|
handle,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|hook| {
|
|hook| {
|
||||||
if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
||||||
hook.value = Some(val);
|
hook.value = Some(val);
|
||||||
}
|
}
|
||||||
(TaskHandle { _p: PhantomData }, &hook.value)
|
(&hook.handle, &hook.value)
|
||||||
},
|
},
|
||||||
|_| {},
|
|_| {},
|
||||||
)
|
)
|
||||||
@@ -393,7 +406,7 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
pub(crate) struct SuspenseHook {
|
pub(crate) struct SuspenseHook {
|
||||||
pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
|
pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
|
||||||
pub callback: SuspendedCallback,
|
pub callback: SuspendedCallback,
|
||||||
pub dom_node_id: Rc<Cell<RealDomNode>>,
|
pub dom_node_id: Rc<Cell<Option<ElementId>>>,
|
||||||
}
|
}
|
||||||
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
|
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
|
||||||
|
|
||||||
@@ -422,7 +435,7 @@ impl<'src, P> Context<'src, P> {
|
|||||||
move |hook_idx| {
|
move |hook_idx| {
|
||||||
let value = Rc::new(RefCell::new(None));
|
let value = Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
let dom_node_id = Rc::new(RealDomNode::empty_cell());
|
let dom_node_id = Rc::new(empty_cell());
|
||||||
let domnode = dom_node_id.clone();
|
let domnode = dom_node_id.clone();
|
||||||
|
|
||||||
let slot = value.clone();
|
let slot = value.clone();
|
||||||
@@ -438,7 +451,7 @@ impl<'src, P> Context<'src, P> {
|
|||||||
None => {
|
None => {
|
||||||
//
|
//
|
||||||
Some(VNode {
|
Some(VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key: None,
|
key: None,
|
||||||
kind: VNodeKind::Suspended {
|
kind: VNodeKind::Suspended {
|
||||||
node: domnode.clone(),
|
node: domnode.clone(),
|
||||||
@@ -495,20 +508,8 @@ impl<'src> SuspendedContext<'src> {
|
|||||||
let scope_ref = self.inner.scope;
|
let scope_ref = self.inner.scope;
|
||||||
let listener_id = &scope_ref.listener_idx;
|
let listener_id = &scope_ref.listener_idx;
|
||||||
Some(lazy_nodes.into_vnode(NodeFactory {
|
Some(lazy_nodes.into_vnode(NodeFactory {
|
||||||
scope_ref,
|
scope: scope_ref,
|
||||||
listener_id,
|
listener_id,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct TaskHandle<'src> {
|
|
||||||
_p: PhantomData<&'src ()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src> TaskHandle<'src> {
|
|
||||||
pub fn toggle(&self) {}
|
|
||||||
pub fn start(&self) {}
|
|
||||||
pub fn stop(&self) {}
|
|
||||||
pub fn restart(&self) {}
|
|
||||||
}
|
|
||||||
+91
-75
@@ -15,7 +15,7 @@
|
|||||||
//! whose keys can be converted to u64 on FFI boundaries.
|
//! whose keys can be converted to u64 on FFI boundaries.
|
||||||
//!
|
//!
|
||||||
//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
|
//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
|
||||||
//! always make sure to copy over the ID. If we don't do this properly, the realdomnode will be populated incorrectly and
|
//! always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly and
|
||||||
//! brick the user's page.
|
//! brick the user's page.
|
||||||
//!
|
//!
|
||||||
//! ## Subtree Memoization
|
//! ## Subtree Memoization
|
||||||
@@ -48,11 +48,11 @@
|
|||||||
//! More info on how to improve this diffing algorithm:
|
//! More info on how to improve this diffing algorithm:
|
||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
|
|
||||||
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
|
use crate::{arena::SharedResources, innerlude::*};
|
||||||
use fxhash::FxHashSet;
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use std::any::Any;
|
use std::{any::Any, borrow::Borrow};
|
||||||
|
|
||||||
/// The accompanying "real dom" exposes an imperative API for controlling the UI layout
|
/// The accompanying "real dom" exposes an imperative API for controlling the UI layout
|
||||||
///
|
///
|
||||||
@@ -66,21 +66,35 @@ use std::any::Any;
|
|||||||
/// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
|
/// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
|
||||||
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
|
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
|
||||||
pub trait RealDom<'a> {
|
pub trait RealDom<'a> {
|
||||||
fn request_available_node(&mut self) -> RealDomNode;
|
|
||||||
fn raw_node_as_any(&self) -> &mut dyn Any;
|
fn raw_node_as_any(&self) -> &mut dyn Any;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
|
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
|
||||||
pub dom: &'real mut Dom,
|
pub real_dom: &'real mut Dom,
|
||||||
|
pub vdom: &'bump SharedResources,
|
||||||
pub edits: DomEditor<'real, 'bump>,
|
pub edits: DomEditor<'real, 'bump>,
|
||||||
pub components: &'bump SharedArena,
|
|
||||||
pub task_queue: &'bump TaskQueue,
|
|
||||||
pub cur_idxs: SmallVec<[ScopeId; 5]>,
|
pub cur_idxs: SmallVec<[ScopeId; 5]>,
|
||||||
pub diffed: FxHashSet<ScopeId>,
|
pub diffed: FxHashSet<ScopeId>,
|
||||||
pub event_queue: EventQueue,
|
|
||||||
pub seen_nodes: FxHashSet<ScopeId>,
|
pub seen_nodes: FxHashSet<ScopeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'r, 'b, D: RealDom<'b>> DiffMachine<'r, 'b, D> {
|
||||||
|
pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> {
|
||||||
|
// ensure we haven't seen this scope before
|
||||||
|
// if we have, then we're trying to alias it, which is not allowed
|
||||||
|
debug_assert!(!self.seen_nodes.contains(id));
|
||||||
|
let compon = unsafe { &mut *self.vdom.components.get() };
|
||||||
|
compon.get_mut(*id)
|
||||||
|
}
|
||||||
|
pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> {
|
||||||
|
// ensure we haven't seen this scope before
|
||||||
|
// if we have, then we're trying to alias it, which is not allowed
|
||||||
|
let compon = unsafe { &*self.vdom.components.get() };
|
||||||
|
compon.get(*id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
|
impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
|
||||||
where
|
where
|
||||||
Dom: RealDom<'bump>,
|
Dom: RealDom<'bump>,
|
||||||
@@ -88,18 +102,14 @@ where
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
edits: &'real mut Vec<DomEdit<'bump>>,
|
edits: &'real mut Vec<DomEdit<'bump>>,
|
||||||
dom: &'real mut Dom,
|
dom: &'real mut Dom,
|
||||||
components: &'bump SharedArena,
|
|
||||||
cur_idx: ScopeId,
|
cur_idx: ScopeId,
|
||||||
event_queue: EventQueue,
|
shared: &'bump SharedResources,
|
||||||
task_queue: &'bump TaskQueue,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
real_dom: dom,
|
||||||
edits: DomEditor::new(edits),
|
edits: DomEditor::new(edits),
|
||||||
components,
|
|
||||||
dom,
|
|
||||||
cur_idxs: smallvec![cur_idx],
|
cur_idxs: smallvec![cur_idx],
|
||||||
event_queue,
|
vdom: shared,
|
||||||
task_queue,
|
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
seen_nodes: FxHashSet::default(),
|
seen_nodes: FxHashSet::default(),
|
||||||
}
|
}
|
||||||
@@ -112,20 +122,24 @@ where
|
|||||||
//
|
//
|
||||||
// each function call assumes the stack is fresh (empty).
|
// each function call assumes the stack is fresh (empty).
|
||||||
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
||||||
|
let root = old_node
|
||||||
|
.dom_id
|
||||||
|
.get()
|
||||||
|
.expect("Should not be diffing old nodes that were never assigned");
|
||||||
|
|
||||||
match (&old_node.kind, &new_node.kind) {
|
match (&old_node.kind, &new_node.kind) {
|
||||||
// Handle the "sane" cases first.
|
// Handle the "sane" cases first.
|
||||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||||
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
||||||
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
||||||
let root = old_node.dom_id.get();
|
|
||||||
if old.text != new.text {
|
if old.text != new.text {
|
||||||
self.edits.push(root);
|
self.edits.push_root(root);
|
||||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
log::debug!("Text has changed {}, {}", old.text, new.text);
|
||||||
self.edits.set_text(new.text);
|
self.edits.set_text(new.text);
|
||||||
self.edits.pop();
|
self.edits.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
new_node.dom_id.set(root);
|
new_node.dom_id.set(Some(root));
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
||||||
@@ -133,20 +147,19 @@ where
|
|||||||
// This is an optimization React makes due to how users structure their code
|
// This is an optimization React makes due to how users structure their code
|
||||||
//
|
//
|
||||||
// In Dioxus, this is less likely to occur unless through a fragment
|
// In Dioxus, this is less likely to occur unless through a fragment
|
||||||
let root = old_node.dom_id.get();
|
|
||||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||||
self.edits.push(root);
|
self.edits.push_root(root);
|
||||||
let meta = self.create(new_node);
|
let meta = self.create(new_node);
|
||||||
self.edits.replace_with(meta.added_to_stack);
|
self.edits.replace_with(meta.added_to_stack);
|
||||||
self.edits.pop();
|
self.edits.pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_node.dom_id.set(root);
|
new_node.dom_id.set(Some(root));
|
||||||
|
|
||||||
// push it just in case
|
// push it just in case
|
||||||
// TODO: remove this - it clogs up things and is inefficient
|
// TODO: remove this - it clogs up things and is inefficient
|
||||||
self.edits.push(root);
|
self.edits.push_root(root);
|
||||||
self.diff_listeners(old.listeners, new.listeners);
|
self.diff_listeners(old.listeners, new.listeners);
|
||||||
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
self.diff_attr(old.attributes, new.attributes, new.namespace);
|
||||||
self.diff_children(old.children, new.children);
|
self.diff_children(old.children, new.children);
|
||||||
@@ -160,11 +173,11 @@ where
|
|||||||
self.cur_idxs.push(old.ass_scope.get().unwrap());
|
self.cur_idxs.push(old.ass_scope.get().unwrap());
|
||||||
|
|
||||||
// Make sure the new component vnode is referencing the right scope id
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
let scope_id = old.ass_scope.get();
|
let scope_addr = old.ass_scope.get().unwrap();
|
||||||
new.ass_scope.set(scope_id);
|
new.ass_scope.set(Some(scope_addr));
|
||||||
|
|
||||||
// make sure the component's caller function is up to date
|
// make sure the component's caller function is up to date
|
||||||
let scope = self.components.get_mut(scope_id.unwrap()).unwrap();
|
let scope = self.get_scope_mut(&scope_addr).unwrap();
|
||||||
|
|
||||||
scope.caller = new.caller.clone();
|
scope.caller = new.caller.clone();
|
||||||
|
|
||||||
@@ -185,22 +198,22 @@ where
|
|||||||
}
|
}
|
||||||
self.cur_idxs.pop();
|
self.cur_idxs.pop();
|
||||||
|
|
||||||
self.seen_nodes.insert(scope_id.unwrap());
|
self.seen_nodes.insert(scope_addr);
|
||||||
} else {
|
} else {
|
||||||
// this seems to be a fairy common code path that we could
|
// this seems to be a fairy common code path that we could
|
||||||
let mut old_iter = RealChildIterator::new(old_node, &self.components);
|
let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
|
||||||
let first = old_iter
|
let first = old_iter
|
||||||
.next()
|
.next()
|
||||||
.expect("Components should generate a placeholder root");
|
.expect("Components should generate a placeholder root");
|
||||||
|
|
||||||
// remove any leftovers
|
// remove any leftovers
|
||||||
for to_remove in old_iter {
|
for to_remove in old_iter {
|
||||||
self.edits.push(to_remove);
|
self.edits.push_root(to_remove);
|
||||||
self.edits.remove();
|
self.edits.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
// seems like we could combine this into a single instruction....
|
// seems like we could combine this into a single instruction....
|
||||||
self.edits.push(first);
|
self.edits.push_root(first);
|
||||||
let meta = self.create(new_node);
|
let meta = self.create(new_node);
|
||||||
self.edits.replace_with(meta.added_to_stack);
|
self.edits.replace_with(meta.added_to_stack);
|
||||||
self.edits.pop();
|
self.edits.pop();
|
||||||
@@ -229,8 +242,12 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
|
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
|
||||||
|
//
|
||||||
// We also walk the "real node" list to make sure all latent roots are claened up
|
// We also walk the "real node" list to make sure all latent roots are claened up
|
||||||
// This covers the case any time a fragment or component shows up with pretty much anything else
|
// This covers the case any time a fragment or component shows up with pretty much anything else
|
||||||
|
//
|
||||||
|
// This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
|
||||||
|
// are pretty rare. IE replacing a list (component or fragment) with a single node.
|
||||||
(
|
(
|
||||||
VNodeKind::Component(_)
|
VNodeKind::Component(_)
|
||||||
| VNodeKind::Fragment(_)
|
| VNodeKind::Fragment(_)
|
||||||
@@ -242,12 +259,12 @@ where
|
|||||||
| VNodeKind::Element(_),
|
| VNodeKind::Element(_),
|
||||||
) => {
|
) => {
|
||||||
// Choose the node to use as the placeholder for replacewith
|
// Choose the node to use as the placeholder for replacewith
|
||||||
let back_node = match old_node.kind {
|
let back_node_id = match old_node.kind {
|
||||||
// We special case these two types to avoid allocating the small-vecs
|
// We special case these two types to avoid allocating the small-vecs
|
||||||
VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(),
|
VNodeKind::Element(_) | VNodeKind::Text(_) => root,
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
let mut old_iter = RealChildIterator::new(old_node, &self.components);
|
let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
|
||||||
|
|
||||||
let back_node = old_iter
|
let back_node = old_iter
|
||||||
.next()
|
.next()
|
||||||
@@ -255,7 +272,7 @@ where
|
|||||||
|
|
||||||
// remove any leftovers
|
// remove any leftovers
|
||||||
for to_remove in old_iter {
|
for to_remove in old_iter {
|
||||||
self.edits.push(to_remove);
|
self.edits.push_root(to_remove);
|
||||||
self.edits.remove();
|
self.edits.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +281,7 @@ where
|
|||||||
};
|
};
|
||||||
|
|
||||||
// replace the placeholder or first node with the nodes generated from the "new"
|
// replace the placeholder or first node with the nodes generated from the "new"
|
||||||
self.edits.push(back_node);
|
self.edits.push_root(back_node_id);
|
||||||
let meta = self.create(new_node);
|
let meta = self.create(new_node);
|
||||||
self.edits.replace_with(meta.added_to_stack);
|
self.edits.replace_with(meta.added_to_stack);
|
||||||
|
|
||||||
@@ -314,9 +331,10 @@ where
|
|||||||
log::warn!("Creating node! ... {:#?}", node);
|
log::warn!("Creating node! ... {:#?}", node);
|
||||||
match &node.kind {
|
match &node.kind {
|
||||||
VNodeKind::Text(text) => {
|
VNodeKind::Text(text) => {
|
||||||
let real_id = self.dom.request_available_node();
|
let real_id = self.vdom.reserve_node();
|
||||||
self.edits.create_text_node(text.text, real_id);
|
self.edits.create_text_node(text.text, real_id);
|
||||||
node.dom_id.set(real_id);
|
node.dom_id.set(Some(real_id));
|
||||||
|
|
||||||
CreateMeta::new(text.is_static, 1)
|
CreateMeta::new(text.is_static, 1)
|
||||||
}
|
}
|
||||||
VNodeKind::Element(el) => {
|
VNodeKind::Element(el) => {
|
||||||
@@ -338,18 +356,18 @@ where
|
|||||||
static_listeners: _,
|
static_listeners: _,
|
||||||
} = el;
|
} = el;
|
||||||
|
|
||||||
let real_id = self.dom.request_available_node();
|
let real_id = self.vdom.reserve_node();
|
||||||
if let Some(namespace) = namespace {
|
if let Some(namespace) = namespace {
|
||||||
self.edits
|
self.edits
|
||||||
.create_element(tag_name, Some(namespace), real_id)
|
.create_element(tag_name, Some(namespace), real_id)
|
||||||
} else {
|
} else {
|
||||||
self.edits.create_element(tag_name, None, real_id)
|
self.edits.create_element(tag_name, None, real_id)
|
||||||
};
|
};
|
||||||
node.dom_id.set(real_id);
|
node.dom_id.set(Some(real_id));
|
||||||
|
|
||||||
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
||||||
log::info!("setting listener id to {:#?}", real_id);
|
log::info!("setting listener id to {:#?}", real_id);
|
||||||
listener.mounted_node.set(real_id);
|
listener.mounted_node.set(Some(real_id));
|
||||||
self.edits
|
self.edits
|
||||||
.new_event_listener(listener.event, listener.scope, idx, real_id);
|
.new_event_listener(listener.event, listener.scope, idx, real_id);
|
||||||
|
|
||||||
@@ -405,25 +423,18 @@ where
|
|||||||
let parent_idx = self.cur_idxs.last().unwrap().clone();
|
let parent_idx = self.cur_idxs.last().unwrap().clone();
|
||||||
|
|
||||||
// Insert a new scope into our component list
|
// Insert a new scope into our component list
|
||||||
let new_idx = self
|
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||||
.components
|
let parent_scope = self.get_scope(&parent_idx).unwrap();
|
||||||
.with(|components| {
|
let height = parent_scope.height + 1;
|
||||||
components.insert_with_key(|new_idx| {
|
Scope::new(
|
||||||
let parent_scope = self.components.get(parent_idx).unwrap();
|
caller,
|
||||||
let height = parent_scope.height + 1;
|
new_idx,
|
||||||
Scope::new(
|
Some(parent_idx),
|
||||||
caller,
|
height,
|
||||||
new_idx,
|
vcomponent.children,
|
||||||
Some(parent_idx),
|
self.vdom.clone(),
|
||||||
height,
|
)
|
||||||
self.event_queue.new_channel(height, new_idx),
|
});
|
||||||
self.components.clone(),
|
|
||||||
vcomponent.children,
|
|
||||||
self.task_queue.new_submitter(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
|
// This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
|
||||||
// This is mostly used for cleanup - to remove old scopes when components are destroyed.
|
// This is mostly used for cleanup - to remove old scopes when components are destroyed.
|
||||||
@@ -437,7 +448,7 @@ where
|
|||||||
// .insert(idx);
|
// .insert(idx);
|
||||||
|
|
||||||
// TODO: abstract this unsafe into the arena abstraction
|
// TODO: abstract this unsafe into the arena abstraction
|
||||||
let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
|
let inner: &'bump mut _ = unsafe { &mut *self.vdom.components.get() };
|
||||||
let new_component = inner.get_mut(new_idx).unwrap();
|
let new_component = inner.get_mut(new_idx).unwrap();
|
||||||
|
|
||||||
// Actually initialize the caller's slot with the right address
|
// Actually initialize the caller's slot with the right address
|
||||||
@@ -477,10 +488,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
VNodeKind::Suspended { node: real_node } => {
|
VNodeKind::Suspended { node: real_node } => {
|
||||||
let id = self.dom.request_available_node();
|
let id = self.vdom.reserve_node();
|
||||||
self.edits.create_placeholder(id);
|
self.edits.create_placeholder(id);
|
||||||
node.dom_id.set(id);
|
node.dom_id.set(Some(id));
|
||||||
real_node.set(id);
|
real_node.set(Some(id));
|
||||||
CreateMeta::new(false, 1)
|
CreateMeta::new(false, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,7 +511,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
while let Some(scope_id) = scopes_to_explore.pop() {
|
while let Some(scope_id) = scopes_to_explore.pop() {
|
||||||
// If we're planning on deleting this node, then we don't need to both rendering it
|
// If we're planning on deleting this node, then we don't need to both rendering it
|
||||||
self.seen_nodes.insert(scope_id);
|
self.seen_nodes.insert(scope_id);
|
||||||
let scope = self.components.get(scope_id).unwrap();
|
let scope = self.get_scope(&scope_id).unwrap();
|
||||||
for child in scope.descendents.borrow().iter() {
|
for child in scope.descendents.borrow().iter() {
|
||||||
// Add this node to be explored
|
// Add this node to be explored
|
||||||
scopes_to_explore.push(child.clone());
|
scopes_to_explore.push(child.clone());
|
||||||
@@ -513,7 +524,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// Delete all scopes that we found as part of this subtree
|
// Delete all scopes that we found as part of this subtree
|
||||||
for node in nodes_to_delete {
|
for node in nodes_to_delete {
|
||||||
log::debug!("Removing scope {:#?}", node);
|
log::debug!("Removing scope {:#?}", node);
|
||||||
let _scope = self.components.try_remove(node).unwrap();
|
let _scope = self.vdom.try_remove(node).unwrap();
|
||||||
// do anything we need to do to delete the scope
|
// do anything we need to do to delete the scope
|
||||||
// I think we need to run the destructors on the hooks
|
// I think we need to run the destructors on the hooks
|
||||||
// TODO
|
// TODO
|
||||||
@@ -527,7 +538,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
// [... node]
|
// [... node]
|
||||||
//
|
//
|
||||||
// The change list stack is left unchanged.
|
// The change list stack is left unchanged.
|
||||||
fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
fn diff_listeners(&mut self, old: &[&mut Listener<'_>], new: &[&mut Listener<'_>]) {
|
||||||
if !old.is_empty() || !new.is_empty() {
|
if !old.is_empty() || !new.is_empty() {
|
||||||
// self.edits.commit_traversal();
|
// self.edits.commit_traversal();
|
||||||
}
|
}
|
||||||
@@ -547,7 +558,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
|
|||||||
new_l.mounted_node.set(old_l.mounted_node.get());
|
new_l.mounted_node.set(old_l.mounted_node.get());
|
||||||
// if new_l.id != old_l.id {
|
// if new_l.id != old_l.id {
|
||||||
// self.edits.remove_event_listener(event_type);
|
// self.edits.remove_event_listener(event_type);
|
||||||
// // TODO! we need to mess with events and assign them by RealDomNode
|
// // TODO! we need to mess with events and assign them by ElementId
|
||||||
// // self.edits
|
// // self.edits
|
||||||
// // .update_event_listener(event_type, new_l.scope, new_l.id)
|
// // .update_event_listener(event_type, new_l.scope, new_l.id)
|
||||||
// }
|
// }
|
||||||
@@ -1270,7 +1281,7 @@ enum KeyedPrefixResult {
|
|||||||
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
||||||
/// "InsertBefore".
|
/// "InsertBefore".
|
||||||
struct RealChildIterator<'a> {
|
struct RealChildIterator<'a> {
|
||||||
scopes: &'a SharedArena,
|
scopes: &'a SharedResources,
|
||||||
|
|
||||||
// Heuristcally we should never bleed into 4 completely nested fragments/components
|
// Heuristcally we should never bleed into 4 completely nested fragments/components
|
||||||
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
|
||||||
@@ -1279,7 +1290,7 @@ struct RealChildIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RealChildIterator<'a> {
|
impl<'a> RealChildIterator<'a> {
|
||||||
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
|
fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self {
|
||||||
Self {
|
Self {
|
||||||
scopes,
|
scopes,
|
||||||
stack: smallvec::smallvec![(0, starter)],
|
stack: smallvec::smallvec![(0, starter)],
|
||||||
@@ -1288,9 +1299,9 @@ impl<'a> RealChildIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Iterator for RealChildIterator<'a> {
|
impl<'a> Iterator for RealChildIterator<'a> {
|
||||||
type Item = RealDomNode;
|
type Item = ElementId;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<RealDomNode> {
|
fn next(&mut self) -> Option<ElementId> {
|
||||||
let mut should_pop = false;
|
let mut should_pop = false;
|
||||||
let mut returned_node = None;
|
let mut returned_node = None;
|
||||||
let mut should_push = None;
|
let mut should_push = None;
|
||||||
@@ -1304,7 +1315,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
// We've recursed INTO an element/text
|
// We've recursed INTO an element/text
|
||||||
// We need to recurse *out* of it and move forward to the next
|
// We need to recurse *out* of it and move forward to the next
|
||||||
should_pop = true;
|
should_pop = true;
|
||||||
returned_node = Some(node.dom_id.get());
|
returned_node = node.dom_id.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we get a fragment we push the next child
|
// If we get a fragment we push the next child
|
||||||
@@ -1313,7 +1324,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
|
|
||||||
if frag.children.len() == 0 {
|
if frag.children.len() == 0 {
|
||||||
should_pop = true;
|
should_pop = true;
|
||||||
returned_node = Some(node.dom_id.get());
|
returned_node = node.dom_id.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
if subcount >= frag.children.len() {
|
if subcount >= frag.children.len() {
|
||||||
@@ -1325,11 +1336,16 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
|||||||
|
|
||||||
// Immediately abort suspended nodes - can't do anything with them yet
|
// Immediately abort suspended nodes - can't do anything with them yet
|
||||||
// VNodeKind::Suspended => should_pop = true,
|
// VNodeKind::Suspended => should_pop = true,
|
||||||
VNodeKind::Suspended { .. } => todo!(),
|
VNodeKind::Suspended { node, .. } => {
|
||||||
|
todo!()
|
||||||
|
// *node = node.as_ref().borrow().get().expect("msg");
|
||||||
|
}
|
||||||
|
|
||||||
// For components, we load their root and push them onto the stack
|
// For components, we load their root and push them onto the stack
|
||||||
VNodeKind::Component(sc) => {
|
VNodeKind::Component(sc) => {
|
||||||
let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
|
let scope =
|
||||||
|
unsafe { self.scopes.get_scope(sc.ass_scope.get().unwrap()) }.unwrap();
|
||||||
|
// let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
|
||||||
|
|
||||||
// Simply swap the current node on the stack with the root of the component
|
// Simply swap the current node on the stack with the root of the component
|
||||||
*node = scope.root();
|
*node = scope.root();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{innerlude::ScopeId, RealDomNode};
|
use crate::{innerlude::ScopeId, ElementId};
|
||||||
|
|
||||||
/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
|
/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
|
||||||
///
|
///
|
||||||
@@ -24,7 +24,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
pub(crate) fn push(&mut self, root: RealDomNode) {
|
pub(crate) fn push_root(&mut self, root: ElementId) {
|
||||||
let id = root.as_u64();
|
let id = root.as_u64();
|
||||||
self.edits.push(PushRoot { id });
|
self.edits.push(PushRoot { id });
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
|||||||
|
|
||||||
// Create
|
// Create
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) {
|
pub(crate) fn create_text_node(&mut self, text: &'bump str, id: ElementId) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
self.edits.push(CreateTextNode { text, id });
|
self.edits.push(CreateTextNode { text, id });
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tag: &'static str,
|
tag: &'static str,
|
||||||
ns: Option<&'static str>,
|
ns: Option<&'static str>,
|
||||||
id: RealDomNode,
|
id: ElementId,
|
||||||
) {
|
) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
match ns {
|
match ns {
|
||||||
@@ -80,7 +80,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
|
||||||
pub(crate) fn create_placeholder(&mut self, id: RealDomNode) {
|
pub(crate) fn create_placeholder(&mut self, id: ElementId) {
|
||||||
let id = id.as_u64();
|
let id = id.as_u64();
|
||||||
self.edits.push(CreatePlaceholder { id });
|
self.edits.push(CreatePlaceholder { id });
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
|||||||
event: &'static str,
|
event: &'static str,
|
||||||
scope: ScopeId,
|
scope: ScopeId,
|
||||||
element_id: usize,
|
element_id: usize,
|
||||||
realnode: RealDomNode,
|
realnode: ElementId,
|
||||||
) {
|
) {
|
||||||
self.edits.push(NewEventListener {
|
self.edits.push(NewEventListener {
|
||||||
scope,
|
scope,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
use std::{cell::Cell, rc::Rc};
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use crate::innerlude::{RealDomNode, ScopeId};
|
use crate::innerlude::{ElementId, HeightMarker, ScopeId};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EventTrigger {
|
pub struct EventTrigger {
|
||||||
@@ -14,7 +14,7 @@ pub struct EventTrigger {
|
|||||||
pub originator: ScopeId,
|
pub originator: ScopeId,
|
||||||
|
|
||||||
/// The optional real node associated with the trigger
|
/// The optional real node associated with the trigger
|
||||||
pub real_node_id: Option<RealDomNode>,
|
pub real_node_id: Option<ElementId>,
|
||||||
|
|
||||||
/// The type of event
|
/// The type of event
|
||||||
pub event: VirtualEvent,
|
pub event: VirtualEvent,
|
||||||
@@ -72,7 +72,7 @@ impl EventTrigger {
|
|||||||
pub fn new(
|
pub fn new(
|
||||||
event: VirtualEvent,
|
event: VirtualEvent,
|
||||||
scope: ScopeId,
|
scope: ScopeId,
|
||||||
mounted_dom_id: Option<RealDomNode>,
|
mounted_dom_id: Option<ElementId>,
|
||||||
priority: EventPriority,
|
priority: EventPriority,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -105,6 +105,9 @@ pub enum VirtualEvent {
|
|||||||
|
|
||||||
// image event has conflicting method types
|
// image event has conflicting method types
|
||||||
// ImageEvent(event_data::ImageEvent),
|
// ImageEvent(event_data::ImageEvent),
|
||||||
|
SetStateEvent {
|
||||||
|
height: HeightMarker,
|
||||||
|
},
|
||||||
|
|
||||||
// Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
|
// Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
|
||||||
//
|
//
|
||||||
@@ -117,10 +120,8 @@ pub enum VirtualEvent {
|
|||||||
// These are more intrusive than the rest
|
// These are more intrusive than the rest
|
||||||
SuspenseEvent {
|
SuspenseEvent {
|
||||||
hook_idx: usize,
|
hook_idx: usize,
|
||||||
domnode: Rc<Cell<RealDomNode>>,
|
domnode: Rc<Cell<Option<ElementId>>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
OtherEvent,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod on {
|
pub mod on {
|
||||||
@@ -138,7 +139,7 @@ pub mod on {
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
innerlude::NodeFactory,
|
innerlude::NodeFactory,
|
||||||
innerlude::{Attribute, Listener, RealDomNode, VNode},
|
innerlude::{Attribute, ElementId, Listener, VNode},
|
||||||
};
|
};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
@@ -180,8 +181,8 @@ pub mod on {
|
|||||||
let bump = &c.bump();
|
let bump = &c.bump();
|
||||||
Listener {
|
Listener {
|
||||||
event: stringify!($name),
|
event: stringify!($name),
|
||||||
mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
|
mounted_node: Cell::new(None),
|
||||||
scope: c.scope_ref.our_arena_idx,
|
scope: c.scope.our_arena_idx,
|
||||||
callback: bump.alloc(move |evt: VirtualEvent| match evt {
|
callback: bump.alloc(move |evt: VirtualEvent| match evt {
|
||||||
VirtualEvent::$wrapper(event) => callback(event),
|
VirtualEvent::$wrapper(event) => callback(event),
|
||||||
_ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
|
_ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
use crate::FC;
|
use crate::FC;
|
||||||
|
|
||||||
pub(crate) struct HeuristicsEngine {
|
/// Provides heuristics to the "SharedResources" object for improving allocation performance.
|
||||||
heuristics: HashMap<FcSlot, Heuristic>,
|
///
|
||||||
|
/// This heueristic engine records the memory footprint of bump arenas and hook lists for each component. These records are
|
||||||
|
/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
|
||||||
|
/// that would slow down the diffing and initializion process.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub struct HeuristicsEngine {
|
||||||
|
heuristics: FxHashMap<FcSlot, Heuristic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type FcSlot = *const ();
|
pub type FcSlot = *const ();
|
||||||
|
|
||||||
pub(crate) struct Heuristic {
|
pub struct Heuristic {
|
||||||
hooks: u32,
|
hooks: u32,
|
||||||
bump_size: u64,
|
bump_size: u64,
|
||||||
}
|
}
|
||||||
@@ -16,7 +25,7 @@ pub(crate) struct Heuristic {
|
|||||||
impl HeuristicsEngine {
|
impl HeuristicsEngine {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
heuristics: HashMap::new(),
|
heuristics: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
//!
|
//!
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes,
|
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventTrigger,
|
||||||
NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
|
LazyNodes, NodeFactory, Properties, RealDom, ScopeId, VNode, VNodeKind, VirtualDom,
|
||||||
VirtualEvent, FC,
|
VirtualEvent, FC,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,7 +38,6 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::hooklist::*;
|
pub use crate::hooklist::*;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::scope::*;
|
pub use crate::scope::*;
|
||||||
pub use crate::tasks::*;
|
|
||||||
pub use crate::util::*;
|
pub use crate::util::*;
|
||||||
pub use crate::virtual_dom::*;
|
pub use crate::virtual_dom::*;
|
||||||
|
|
||||||
@@ -66,6 +65,5 @@ pub mod hooklist;
|
|||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
pub mod scope;
|
pub mod scope;
|
||||||
pub mod signals;
|
pub mod signals;
|
||||||
pub mod tasks;
|
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod virtual_dom;
|
pub mod virtual_dom;
|
||||||
+41
-26
@@ -5,7 +5,7 @@
|
|||||||
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
|
||||||
use crate::{
|
use crate::{
|
||||||
events::VirtualEvent,
|
events::VirtualEvent,
|
||||||
innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC},
|
innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::Cell,
|
cell::Cell,
|
||||||
@@ -16,7 +16,12 @@ use std::{
|
|||||||
|
|
||||||
pub struct VNode<'src> {
|
pub struct VNode<'src> {
|
||||||
pub kind: VNodeKind<'src>,
|
pub kind: VNodeKind<'src>,
|
||||||
pub(crate) dom_id: Cell<RealDomNode>,
|
|
||||||
|
///
|
||||||
|
/// ElementId supports NonZero32 and Cell is zero cost, so the size of this field is unaffected
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub(crate) dom_id: Cell<Option<ElementId>>,
|
||||||
pub(crate) key: Option<&'src str>,
|
pub(crate) key: Option<&'src str>,
|
||||||
}
|
}
|
||||||
impl VNode<'_> {
|
impl VNode<'_> {
|
||||||
@@ -39,7 +44,7 @@ pub enum VNodeKind<'src> {
|
|||||||
|
|
||||||
Component(&'src VComponent<'src>),
|
Component(&'src VComponent<'src>),
|
||||||
|
|
||||||
Suspended { node: Rc<Cell<RealDomNode>> },
|
Suspended { node: Rc<Cell<Option<ElementId>>> },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VText<'src> {
|
pub struct VText<'src> {
|
||||||
@@ -50,7 +55,6 @@ pub struct VText<'src> {
|
|||||||
pub struct VFragment<'src> {
|
pub struct VFragment<'src> {
|
||||||
pub children: &'src [VNode<'src>],
|
pub children: &'src [VNode<'src>],
|
||||||
pub is_static: bool,
|
pub is_static: bool,
|
||||||
pub is_error: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait DioxusElement {
|
pub trait DioxusElement {
|
||||||
@@ -72,7 +76,7 @@ pub struct VElement<'a> {
|
|||||||
pub namespace: Option<&'static str>,
|
pub namespace: Option<&'static str>,
|
||||||
|
|
||||||
pub static_listeners: bool,
|
pub static_listeners: bool,
|
||||||
pub listeners: &'a [Listener<'a>],
|
pub listeners: &'a [&'a mut Listener<'a>],
|
||||||
|
|
||||||
pub static_attrs: bool,
|
pub static_attrs: bool,
|
||||||
pub attributes: &'a [Attribute<'a>],
|
pub attributes: &'a [Attribute<'a>],
|
||||||
@@ -105,9 +109,9 @@ pub struct Listener<'bump> {
|
|||||||
|
|
||||||
pub scope: ScopeId,
|
pub scope: ScopeId,
|
||||||
|
|
||||||
pub mounted_node: &'bump mut Cell<RealDomNode>,
|
pub mounted_node: Cell<Option<ElementId>>,
|
||||||
|
|
||||||
pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
|
pub(crate) callback: &'bump mut dyn FnMut(VirtualEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
@@ -136,20 +140,31 @@ pub struct VComponent<'src> {
|
|||||||
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
/// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct NodeFactory<'a> {
|
pub struct NodeFactory<'a> {
|
||||||
pub scope_ref: &'a Scope,
|
pub scope: &'a Scope,
|
||||||
pub listener_id: &'a Cell<usize>,
|
pub listener_id: &'a Cell<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NodeFactory<'a> {
|
impl<'a> NodeFactory<'a> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bump(&self) -> &'a bumpalo::Bump {
|
pub fn bump(&self) -> &'a bumpalo::Bump {
|
||||||
&self.scope_ref.cur_frame().bump
|
&self.scope.cur_frame().bump
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unstable_place_holder() -> VNode<'static> {
|
||||||
|
VNode {
|
||||||
|
dom_id: empty_cell(),
|
||||||
|
key: None,
|
||||||
|
kind: VNodeKind::Text(VText {
|
||||||
|
text: "",
|
||||||
|
is_static: true,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used in a place or two to make it easier to build vnodes from dummy text
|
/// Used in a place or two to make it easier to build vnodes from dummy text
|
||||||
pub fn static_text(text: &'static str) -> VNode {
|
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
|
||||||
VNode {
|
VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key: None,
|
key: None,
|
||||||
kind: VNodeKind::Text(VText {
|
kind: VNodeKind::Text(VText {
|
||||||
text,
|
text,
|
||||||
@@ -178,7 +193,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||||
let (text, is_static) = self.raw_text(args);
|
let (text, is_static) = self.raw_text(args);
|
||||||
VNode {
|
VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key: None,
|
key: None,
|
||||||
kind: VNodeKind::Text(VText { text, is_static }),
|
kind: VNodeKind::Text(VText { text, is_static }),
|
||||||
}
|
}
|
||||||
@@ -187,7 +202,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
pub fn element(
|
pub fn element(
|
||||||
&self,
|
&self,
|
||||||
el: impl DioxusElement,
|
el: impl DioxusElement,
|
||||||
listeners: &'a mut [Listener<'a>],
|
listeners: &'a mut [&'a mut Listener<'a>],
|
||||||
attributes: &'a [Attribute<'a>],
|
attributes: &'a [Attribute<'a>],
|
||||||
children: &'a [VNode<'a>],
|
children: &'a [VNode<'a>],
|
||||||
key: Option<&'a str>,
|
key: Option<&'a str>,
|
||||||
@@ -206,7 +221,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
&self,
|
&self,
|
||||||
tag: &'static str,
|
tag: &'static str,
|
||||||
namespace: Option<&'static str>,
|
namespace: Option<&'static str>,
|
||||||
listeners: &'a mut [Listener],
|
listeners: &'a mut [&'a mut Listener<'a>],
|
||||||
attributes: &'a [Attribute],
|
attributes: &'a [Attribute],
|
||||||
children: &'a [VNode<'a>],
|
children: &'a [VNode<'a>],
|
||||||
key: Option<&'a str>,
|
key: Option<&'a str>,
|
||||||
@@ -215,15 +230,17 @@ impl<'a> NodeFactory<'a> {
|
|||||||
// TODO: this code shouldn't necessarily be here of all places
|
// TODO: this code shouldn't necessarily be here of all places
|
||||||
// It would make more sense to do this in diffing
|
// It would make more sense to do this in diffing
|
||||||
|
|
||||||
let mut queue = self.scope_ref.listeners.borrow_mut();
|
let mut queue = self.scope.listeners.borrow_mut();
|
||||||
for listener in listeners.iter_mut() {
|
for listener in listeners.iter_mut() {
|
||||||
let mounted = listener.mounted_node as *mut _;
|
let raw_listener = &mut **listener;
|
||||||
let callback = listener.callback as *const _ as *mut _;
|
|
||||||
queue.push((mounted, callback))
|
let long_listener: &mut Listener<'static> =
|
||||||
|
unsafe { std::mem::transmute(raw_listener) };
|
||||||
|
queue.push(long_listener as *mut _)
|
||||||
}
|
}
|
||||||
|
|
||||||
VNode {
|
VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key,
|
key,
|
||||||
kind: VNodeKind::Element(self.bump().alloc(VElement {
|
kind: VNodeKind::Element(self.bump().alloc(VElement {
|
||||||
tag_name: tag,
|
tag_name: tag,
|
||||||
@@ -242,10 +259,10 @@ impl<'a> NodeFactory<'a> {
|
|||||||
|
|
||||||
pub fn suspended() -> VNode<'static> {
|
pub fn suspended() -> VNode<'static> {
|
||||||
VNode {
|
VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key: None,
|
key: None,
|
||||||
kind: VNodeKind::Suspended {
|
kind: VNodeKind::Suspended {
|
||||||
node: Rc::new(RealDomNode::empty_cell()),
|
node: Rc::new(empty_cell()),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -322,7 +339,7 @@ impl<'a> NodeFactory<'a> {
|
|||||||
|
|
||||||
VNode {
|
VNode {
|
||||||
key,
|
key,
|
||||||
dom_id: Cell::new(RealDomNode::empty()),
|
dom_id: empty_cell(),
|
||||||
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
|
||||||
user_fc,
|
user_fc,
|
||||||
comparator,
|
comparator,
|
||||||
@@ -383,12 +400,11 @@ To help you identify where this error is coming from, we've generated a backtrac
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
VNode {
|
VNode {
|
||||||
dom_id: RealDomNode::empty_cell(),
|
dom_id: empty_cell(),
|
||||||
key: None,
|
key: None,
|
||||||
kind: VNodeKind::Fragment(VFragment {
|
kind: VNodeKind::Fragment(VFragment {
|
||||||
children: nodes.into_bump_slice(),
|
children: nodes.into_bump_slice(),
|
||||||
is_static: false,
|
is_static: false,
|
||||||
is_error: false,
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +460,6 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
|
|||||||
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
|
||||||
children: fragment.children,
|
children: fragment.children,
|
||||||
is_static: fragment.is_static,
|
is_static: fragment.is_static,
|
||||||
is_error: false,
|
|
||||||
}),
|
}),
|
||||||
VNodeKind::Component(component) => VNodeKind::Component(component),
|
VNodeKind::Component(component) => VNodeKind::Component(component),
|
||||||
|
|
||||||
@@ -535,7 +550,7 @@ impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
|
|||||||
|
|
||||||
impl IntoVNode<'_> for &'static str {
|
impl IntoVNode<'_> for &'static str {
|
||||||
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||||
NodeFactory::static_text(self)
|
cx.static_text(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl IntoVNode<'_> for Arguments<'_> {
|
impl IntoVNode<'_> for Arguments<'_> {
|
||||||
|
|||||||
+31
-54
@@ -18,40 +18,30 @@ use std::{
|
|||||||
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
|
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
|
||||||
/// usecase they might have.
|
/// usecase they might have.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
// Book-keeping about the arena
|
// Book-keeping about our spot in the arena
|
||||||
pub(crate) parent_idx: Option<ScopeId>,
|
pub(crate) parent_idx: Option<ScopeId>,
|
||||||
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
|
|
||||||
pub(crate) our_arena_idx: ScopeId,
|
pub(crate) our_arena_idx: ScopeId,
|
||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
|
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
// an internal, highly efficient storage of vnodes
|
// an internal, highly efficient storage of vnodes
|
||||||
pub(crate) frames: ActiveFrame,
|
pub(crate) frames: ActiveFrame,
|
||||||
pub(crate) child_nodes: &'static [VNode<'static>],
|
|
||||||
pub(crate) caller: Rc<WrappedCaller>,
|
pub(crate) caller: Rc<WrappedCaller>,
|
||||||
|
pub(crate) child_nodes: &'static [VNode<'static>],
|
||||||
|
|
||||||
// Listeners
|
// Listeners
|
||||||
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
|
pub(crate) listeners: RefCell<Vec<*mut Listener<'static>>>,
|
||||||
pub(crate) listener_idx: Cell<usize>,
|
pub(crate) listener_idx: Cell<usize>,
|
||||||
|
|
||||||
// State
|
// State
|
||||||
pub(crate) hooks: HookList,
|
pub(crate) hooks: HookList,
|
||||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
||||||
|
|
||||||
// Events
|
// A reference to the resources shared by all the comonents
|
||||||
pub(crate) event_channel: Rc<dyn Fn() + 'static>,
|
pub(crate) vdom: SharedResources,
|
||||||
|
|
||||||
// Tasks
|
|
||||||
pub(crate) task_submitter: TaskSubmitter,
|
|
||||||
|
|
||||||
// A reference to the list of components.
|
|
||||||
// This lets us traverse the component list whenever we need to access our parent or children.
|
|
||||||
pub(crate) arena_link: SharedArena,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type of the channel function
|
|
||||||
type EventChannel = Rc<dyn Fn()>;
|
|
||||||
|
|
||||||
// The type of closure that wraps calling components
|
// The type of closure that wraps calling components
|
||||||
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
|
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
|
||||||
|
|
||||||
@@ -68,13 +58,16 @@ impl Scope {
|
|||||||
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
||||||
pub fn new<'creator_node>(
|
pub fn new<'creator_node>(
|
||||||
caller: Rc<WrappedCaller>,
|
caller: Rc<WrappedCaller>,
|
||||||
|
|
||||||
arena_idx: ScopeId,
|
arena_idx: ScopeId,
|
||||||
|
|
||||||
parent: Option<ScopeId>,
|
parent: Option<ScopeId>,
|
||||||
|
|
||||||
height: u32,
|
height: u32,
|
||||||
event_channel: EventChannel,
|
|
||||||
arena_link: SharedArena,
|
|
||||||
child_nodes: &'creator_node [VNode<'creator_node>],
|
child_nodes: &'creator_node [VNode<'creator_node>],
|
||||||
task_submitter: TaskSubmitter,
|
|
||||||
|
vdom: SharedResources,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
||||||
Self {
|
Self {
|
||||||
@@ -83,9 +76,7 @@ impl Scope {
|
|||||||
parent_idx: parent,
|
parent_idx: parent,
|
||||||
our_arena_idx: arena_idx,
|
our_arena_idx: arena_idx,
|
||||||
height,
|
height,
|
||||||
event_channel,
|
vdom,
|
||||||
arena_link,
|
|
||||||
task_submitter,
|
|
||||||
listener_idx: Default::default(),
|
listener_idx: Default::default(),
|
||||||
frames: ActiveFrame::new(),
|
frames: ActiveFrame::new(),
|
||||||
hooks: Default::default(),
|
hooks: Default::default(),
|
||||||
@@ -113,7 +104,7 @@ impl Scope {
|
|||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
|
|
||||||
// This is a very dangerous operation
|
// This is a very dangerous operation
|
||||||
let next_frame = self.frames.old_frame_mut();
|
let next_frame = self.frames.prev_frame_mut();
|
||||||
next_frame.bump.reset();
|
next_frame.bump.reset();
|
||||||
|
|
||||||
self.listeners.borrow_mut().clear();
|
self.listeners.borrow_mut().clear();
|
||||||
@@ -132,7 +123,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
Some(new_head) => {
|
Some(new_head) => {
|
||||||
// the user's component succeeded. We can safely cycle to the next frame
|
// the user's component succeeded. We can safely cycle to the next frame
|
||||||
self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
self.frames.prev_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||||
self.frames.cycle_frame();
|
self.frames.cycle_frame();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -162,33 +153,27 @@ impl Scope {
|
|||||||
|
|
||||||
let listners = self.listeners.borrow_mut();
|
let listners = self.listeners.borrow_mut();
|
||||||
|
|
||||||
let raw_listener = listners.iter().find(|(domptr, _)| {
|
let raw_listener = listners.iter().find(|lis| {
|
||||||
let search = unsafe { &**domptr };
|
let search = unsafe { &mut ***lis };
|
||||||
let search_id = search.get();
|
let search_id = search.mounted_node.get();
|
||||||
log::info!("searching listener {:#?}", search_id);
|
log::info!("searching listener {:#?}", search_id);
|
||||||
match real_node_id {
|
|
||||||
Some(e) => search_id == e,
|
match (real_node_id, search_id) {
|
||||||
None => false,
|
(Some(e), Some(search_id)) => search_id == e,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
match raw_listener {
|
if let Some(raw_listener) = raw_listener {
|
||||||
Some((_node, listener)) => unsafe {
|
let listener = unsafe { &mut **raw_listener };
|
||||||
// TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
|
(listener.callback)(event);
|
||||||
let listener_fn = &mut **listener;
|
} else {
|
||||||
listener_fn(event);
|
log::warn!("An event was triggered but there was no listener to handle it");
|
||||||
},
|
|
||||||
None => todo!(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn submit_task(&self, task: FiberTask) {
|
|
||||||
log::debug!("Task submitted into scope");
|
|
||||||
(self.task_submitter)(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
self.frames.current_head_node()
|
self.frames.current_head_node()
|
||||||
@@ -207,18 +192,10 @@ impl Scope {
|
|||||||
/// Get the root VNode of this component
|
/// Get the root VNode of this component
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn root<'a>(&'a self) -> &'a VNode<'a> {
|
pub fn root<'a>(&'a self) -> &'a VNode<'a> {
|
||||||
&self.frames.cur_frame().head_node
|
&self.frames.current_head_node()
|
||||||
}
|
}
|
||||||
}
|
#[inline]
|
||||||
|
pub fn child_nodes<'a>(&'a self) -> &'a [VNode<'a>] {
|
||||||
pub fn errored_fragment() -> VNode<'static> {
|
unsafe { std::mem::transmute(self.child_nodes) }
|
||||||
VNode {
|
|
||||||
dom_id: RealDomNode::empty_cell(),
|
|
||||||
key: None,
|
|
||||||
kind: VNodeKind::Fragment(VFragment {
|
|
||||||
children: &[],
|
|
||||||
is_static: false,
|
|
||||||
is_error: true,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
//! The TaskQueue serves as a centralized async store for all tasks in Dioxus.
|
|
||||||
//! When a component renders, it may submit an async task to the queue.
|
|
||||||
//!
|
|
||||||
//! Then the task complete, it is emitted from the virtual dom in the event loop, which is then fed back into the virtualdom
|
|
||||||
//! as an event trigger.
|
|
||||||
//!
|
|
||||||
//! When a component is scheduled to re-render, the awaing task must be dumped from the queue.
|
|
||||||
//!
|
|
||||||
//! This is all pretty unsafe stuff.
|
|
||||||
//! The major invariant here is that tasks that enter the queue may be invalidated during transitions.
|
|
||||||
|
|
||||||
use std::{
|
|
||||||
cell::Cell,
|
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt};
|
|
||||||
use slotmap::{DefaultKey, SlotMap};
|
|
||||||
|
|
||||||
use crate::innerlude::{EventTrigger, FiberTask, ScopeId};
|
|
||||||
|
|
||||||
pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>;
|
|
||||||
|
|
||||||
pub struct TaskQueue {
|
|
||||||
slots: Arc<RwLock<FuturesUnordered<FiberTask>>>,
|
|
||||||
// slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
|
|
||||||
submitter: TaskSubmitter,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TaskQueue {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let slots = Arc::new(RwLock::new(FuturesUnordered::new()));
|
|
||||||
let slots2 = slots.clone();
|
|
||||||
|
|
||||||
let submitter = Arc::new(move |task| {
|
|
||||||
let mut slots = slots2.write().unwrap();
|
|
||||||
log::debug!("Task submitted into global task queue");
|
|
||||||
slots.push(task);
|
|
||||||
});
|
|
||||||
Self { slots, submitter }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_submitter(&self) -> TaskSubmitter {
|
|
||||||
self.submitter.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submit_task(&mut self, task: FiberTask) {
|
|
||||||
self.slots.write().unwrap().push(task);
|
|
||||||
// TaskHandle { key }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.slots.read().unwrap().is_empty()
|
|
||||||
}
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.slots.read().unwrap().len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn next(&mut self) -> Option<EventTrigger> {
|
|
||||||
let mut slots = self.slots.write().unwrap();
|
|
||||||
slots.next().await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl Stream for TaskQueue {
|
|
||||||
// type Item = EventTrigger;
|
|
||||||
|
|
||||||
// /// We can never be finished polling
|
|
||||||
// fn poll_next(
|
|
||||||
// self: Pin<&mut Self>,
|
|
||||||
// cx: &mut std::task::Context<'_>,
|
|
||||||
// ) -> std::task::Poll<Option<Self::Item>> {
|
|
||||||
// // let yield_every = self.len();
|
|
||||||
// // let mut polled = 0;
|
|
||||||
|
|
||||||
// let mut slots = self.slots.write().unwrap();
|
|
||||||
// for (_key, slot) in slots.iter_mut() {
|
|
||||||
// if slot.dead.get() {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// let r = slot.fut;
|
|
||||||
// // let fut = unsafe { &mut *r };
|
|
||||||
// // use futures::{future::Future, poll, FutureExt};
|
|
||||||
|
|
||||||
// let f2 = fut.as_mut();
|
|
||||||
// let w = cx.waker();
|
|
||||||
// let mut cx = Context::from_waker(&w);
|
|
||||||
|
|
||||||
// // Pin::new_unchecked(pointer)
|
|
||||||
// // use std::future::Future;
|
|
||||||
// match f2.poll(&mut cx) {
|
|
||||||
// Poll::Ready(_) => {
|
|
||||||
// let trigger = EventTrigger::new_from_task(slot.originator);
|
|
||||||
// slot.dead.set(true);
|
|
||||||
// return Poll::Ready(Some(trigger));
|
|
||||||
// }
|
|
||||||
// Poll::Pending => continue,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // we tried polling every active task.
|
|
||||||
// // give up and relinquish controlto the parent
|
|
||||||
|
|
||||||
// // We have polled a large number of futures in a row without yielding.
|
|
||||||
// // To ensure we do not starve other tasks waiting on the executor,
|
|
||||||
// // we yield here, but immediately wake ourselves up to continue.
|
|
||||||
// // cx.waker().wake_by_ref();
|
|
||||||
// return Poll::Pending;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub struct TaskHandle {
|
|
||||||
key: DefaultKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DTask {
|
|
||||||
fut: FiberTask,
|
|
||||||
originator: ScopeId,
|
|
||||||
dead: Cell<bool>,
|
|
||||||
}
|
|
||||||
impl DTask {
|
|
||||||
pub fn new(fut: FiberTask, originator: ScopeId) -> Self {
|
|
||||||
Self {
|
|
||||||
fut,
|
|
||||||
originator,
|
|
||||||
dead: Cell::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn debug_new(fut: FiberTask) -> Self {
|
|
||||||
let originator = ScopeId::default();
|
|
||||||
Self {
|
|
||||||
fut,
|
|
||||||
originator,
|
|
||||||
dead: Cell::new(false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +1,11 @@
|
|||||||
use std::{
|
use std::cell::Cell;
|
||||||
cell::{Cell, RefCell, RefMut},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use futures_util::StreamExt;
|
|
||||||
use slotmap::{DefaultKey, Key, KeyData};
|
|
||||||
|
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Default)]
|
// create a cell with a "none" value
|
||||||
pub struct EventQueue {
|
#[inline]
|
||||||
pub queue: Rc<RefCell<Vec<HeightMarker>>>,
|
pub fn empty_cell() -> Cell<Option<ElementId>> {
|
||||||
}
|
Cell::new(None as Option<ElementId>)
|
||||||
|
|
||||||
impl EventQueue {
|
|
||||||
pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc<dyn Fn()> {
|
|
||||||
let inner = self.clone();
|
|
||||||
let marker = HeightMarker { height, idx };
|
|
||||||
Rc::new(move || {
|
|
||||||
log::debug!("channel updated {:#?}", marker);
|
|
||||||
inner.queue.as_ref().borrow_mut().push(marker)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sort_unstable(&self) {
|
|
||||||
self.queue.borrow_mut().sort_unstable()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn borrow_mut(&self) -> RefMut<Vec<HeightMarker>> {
|
|
||||||
self.queue.borrow_mut()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper type that lets scopes be ordered by their height
|
/// A helper type that lets scopes be ordered by their height
|
||||||
@@ -51,41 +27,10 @@ impl PartialOrd for HeightMarker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
|
pub struct DebugDom {}
|
||||||
///
|
|
||||||
/// "u64" was chosen for two reasons
|
|
||||||
/// - 0 cost hashing
|
|
||||||
/// - use with slotmap and other versioned slot arenas
|
|
||||||
|
|
||||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub struct RealDomNode(pub u64);
|
|
||||||
impl RealDomNode {
|
|
||||||
#[inline]
|
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self(u64::MIN)
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn empty_cell() -> Cell<Self> {
|
|
||||||
Cell::new(Self::empty())
|
|
||||||
}
|
|
||||||
#[inline]
|
|
||||||
pub fn from_u64(id: u64) -> Self {
|
|
||||||
Self(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn as_u64(&self) -> u64 {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DebugDom {
|
|
||||||
counter: u64,
|
|
||||||
}
|
|
||||||
impl DebugDom {
|
impl DebugDom {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { counter: 0 }
|
Self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,9 +38,4 @@ impl<'a> RealDom<'a> for DebugDom {
|
|||||||
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn request_available_node(&mut self) -> RealDomNode {
|
|
||||||
self.counter += 1;
|
|
||||||
RealDomNode::from_u64(self.counter)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -19,8 +19,7 @@
|
|||||||
//! This module includes just the barebones for a complete VirtualDOM API.
|
//! This module includes just the barebones for a complete VirtualDOM API.
|
||||||
//! Additional functionality is defined in the respective files.
|
//! Additional functionality is defined in the respective files.
|
||||||
|
|
||||||
use crate::tasks::TaskQueue;
|
use crate::{arena::SharedResources, innerlude::*};
|
||||||
use crate::{arena::SharedArena, innerlude::*};
|
|
||||||
|
|
||||||
use slotmap::DefaultKey;
|
use slotmap::DefaultKey;
|
||||||
use slotmap::SlotMap;
|
use slotmap::SlotMap;
|
||||||
@@ -30,10 +29,6 @@ use std::any::TypeId;
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
slotmap::new_key_type! {
|
|
||||||
pub struct ScopeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||||
/// Differences are converted into patches which a renderer can use to draw the UI.
|
/// Differences are converted into patches which a renderer can use to draw the UI.
|
||||||
///
|
///
|
||||||
@@ -49,7 +44,7 @@ pub struct VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||||
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||||
pub components: SharedArena,
|
pub shared: SharedResources,
|
||||||
|
|
||||||
/// The index of the root component
|
/// The index of the root component
|
||||||
/// Should always be the first (gen=0, id=0)
|
/// Should always be the first (gen=0, id=0)
|
||||||
@@ -57,21 +52,12 @@ pub struct VirtualDom {
|
|||||||
|
|
||||||
pub triggers: RefCell<Vec<EventTrigger>>,
|
pub triggers: RefCell<Vec<EventTrigger>>,
|
||||||
|
|
||||||
/// All components dump their updates into a queue to be processed
|
// for managing the props that were used to create the dom
|
||||||
pub event_queue: EventQueue,
|
|
||||||
|
|
||||||
pub tasks: TaskQueue,
|
|
||||||
|
|
||||||
heuristics: HeuristicsEngine,
|
|
||||||
|
|
||||||
root_props: std::pin::Pin<Box<dyn std::any::Any>>,
|
|
||||||
|
|
||||||
/// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
|
|
||||||
///
|
|
||||||
/// Whenver props need to be updated, an Error will be thrown if the new props do not
|
|
||||||
/// match the props used to create the VirtualDom.
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_root_prop_type: std::any::TypeId,
|
_root_prop_type: std::any::TypeId,
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
_root_props: std::pin::Pin<Box<dyn std::any::Any>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================
|
// ======================================
|
||||||
@@ -142,37 +128,22 @@ impl VirtualDom {
|
|||||||
/// let dom = VirtualDom::new(Example);
|
/// let dom = VirtualDom::new(Example);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
|
let components = SharedResources::new();
|
||||||
|
|
||||||
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
|
||||||
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
|
||||||
|
|
||||||
// Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
|
|
||||||
let event_queue = EventQueue::default();
|
|
||||||
let _event_queue = event_queue.clone();
|
|
||||||
|
|
||||||
let link = components.clone();
|
let link = components.clone();
|
||||||
|
|
||||||
let tasks = TaskQueue::new();
|
let base_scope = components.insert_scope_with_key(move |myidx| {
|
||||||
let submitter = tasks.new_submitter();
|
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
||||||
|
Scope::new(caller, myidx, None, 0, &[], link)
|
||||||
let base_scope = components
|
});
|
||||||
.with(|arena| {
|
|
||||||
arena.insert_with_key(move |myidx| {
|
|
||||||
let event_channel = _event_queue.new_channel(0, myidx);
|
|
||||||
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
|
|
||||||
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
base_scope,
|
base_scope,
|
||||||
event_queue,
|
_root_props: root_props,
|
||||||
components,
|
shared: components,
|
||||||
root_props,
|
|
||||||
tasks,
|
|
||||||
heuristics: HeuristicsEngine::new(),
|
|
||||||
triggers: Default::default(),
|
triggers: Default::default(),
|
||||||
_root_prop_type: TypeId::of::<P>(),
|
_root_prop_type: TypeId::of::<P>(),
|
||||||
}
|
}
|
||||||
@@ -209,29 +180,28 @@ impl VirtualDom {
|
|||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
||||||
///
|
///
|
||||||
/// Currently this doesn't do what we want it to do
|
|
||||||
///
|
|
||||||
/// The diff machine expects the RealDom's stack to be the root of the application
|
/// The diff machine expects the RealDom's stack to be the root of the application
|
||||||
pub fn rebuild<'s, Dom: RealDom<'s>>(
|
pub fn rebuild<'s, Dom: RealDom<'s>>(
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
realdom: &mut Dom,
|
realdom: &mut Dom,
|
||||||
edits: &mut Vec<DomEdit<'s>>,
|
edits: &mut Vec<DomEdit<'s>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut diff_machine = DiffMachine::new(
|
let mut diff_machine = DiffMachine::new(edits, realdom, self.base_scope, &self.shared);
|
||||||
edits,
|
|
||||||
realdom,
|
|
||||||
&self.components,
|
|
||||||
self.base_scope,
|
|
||||||
self.event_queue.clone(),
|
|
||||||
&self.tasks,
|
|
||||||
);
|
|
||||||
|
|
||||||
let cur_component = self.components.get_mut(self.base_scope).unwrap();
|
let cur_component = diff_machine
|
||||||
|
.get_scope_mut(&self.base_scope)
|
||||||
|
.expect("The base scope should never be moved");
|
||||||
|
|
||||||
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||||
if cur_component.run_scope().is_ok() {
|
if cur_component.run_scope().is_ok() {
|
||||||
let meta = diff_machine.create(cur_component.next_frame());
|
let meta = diff_machine.create(cur_component.next_frame());
|
||||||
diff_machine.edits.append_children(meta.added_to_stack);
|
diff_machine.edits.append_children(meta.added_to_stack);
|
||||||
|
} else {
|
||||||
|
// todo: should this be a hard error?
|
||||||
|
log::warn!(
|
||||||
|
"Component failed to run succesfully during rebuild.
|
||||||
|
This does not result in a failed rebuild, but indicates a logic failure within your app."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -242,10 +212,9 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
pub fn queue_event(&self, trigger: EventTrigger) -> Result<()> {
|
pub fn queue_event(&self, trigger: EventTrigger) {
|
||||||
let mut triggers = self.triggers.borrow_mut();
|
let mut triggers = self.triggers.borrow_mut();
|
||||||
triggers.push(trigger);
|
triggers.push(trigger);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||||
@@ -298,26 +267,18 @@ impl VirtualDom {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let trigger = self.triggers.borrow_mut().pop().expect("failed");
|
let trigger = self.triggers.borrow_mut().pop().expect("failed");
|
||||||
|
|
||||||
let mut diff_machine = DiffMachine::new(
|
let mut diff_machine = DiffMachine::new(edits, realdom, trigger.originator, &self.shared);
|
||||||
edits,
|
|
||||||
realdom,
|
|
||||||
&self.components,
|
|
||||||
trigger.originator,
|
|
||||||
self.event_queue.clone(),
|
|
||||||
&self.tasks,
|
|
||||||
);
|
|
||||||
|
|
||||||
match &trigger.event {
|
match &trigger.event {
|
||||||
VirtualEvent::OtherEvent => todo!(),
|
|
||||||
|
|
||||||
// Nothing yet
|
// Nothing yet
|
||||||
VirtualEvent::AsyncEvent { .. } => {}
|
VirtualEvent::AsyncEvent { .. } => {}
|
||||||
|
|
||||||
// Suspense Events! A component's suspended node is updated
|
// Suspense Events! A component's suspended node is updated
|
||||||
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
|
||||||
let scope = self.components.get_mut(trigger.originator).unwrap();
|
// Safety: this handler is the only thing that can mutate shared items at this moment in tim
|
||||||
|
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
|
||||||
|
|
||||||
// safety: we are sure that there are no other references to the inner content of this hook
|
// safety: we are sure that there are no other references to the inner content of suspense hooks
|
||||||
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
|
||||||
|
|
||||||
let cx = Context { scope, props: &() };
|
let cx = Context { scope, props: &() };
|
||||||
@@ -325,17 +286,24 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// generate the new node!
|
// generate the new node!
|
||||||
let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
|
let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
|
||||||
let nodes = nodes.unwrap_or_else(|| errored_fragment());
|
match nodes {
|
||||||
let nodes = scope.cur_frame().bump.alloc(nodes);
|
None => {
|
||||||
|
log::warn!("Suspense event came through, but there was no mounted node to update >:(");
|
||||||
|
}
|
||||||
|
Some(nodes) => {
|
||||||
|
let nodes = scope.cur_frame().bump.alloc(nodes);
|
||||||
|
|
||||||
// push the old node's root onto the stack
|
// push the old node's root onto the stack
|
||||||
diff_machine.edits.push(domnode.get());
|
let real_id = domnode.get().ok_or(Error::NotMounted)?;
|
||||||
|
diff_machine.edits.push_root(real_id);
|
||||||
|
|
||||||
// push these new nodes onto the diff machines stack
|
// push these new nodes onto the diff machines stack
|
||||||
let meta = diff_machine.create(&*nodes);
|
let meta = diff_machine.create(&*nodes);
|
||||||
|
|
||||||
// replace the placeholder with the new nodes we just pushed on the stack
|
// replace the placeholder with the new nodes we just pushed on the stack
|
||||||
diff_machine.edits.replace_with(meta.added_to_stack);
|
diff_machine.edits.replace_with(meta.added_to_stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the "meat" of our cooperative scheduler
|
// This is the "meat" of our cooperative scheduler
|
||||||
@@ -343,12 +311,12 @@ impl VirtualDom {
|
|||||||
//
|
//
|
||||||
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
|
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
|
||||||
_ => {
|
_ => {
|
||||||
self.components
|
diff_machine
|
||||||
.get_mut(trigger.originator)
|
.get_scope_mut(&trigger.originator)
|
||||||
.map(|f| f.call_listener(trigger));
|
.map(|f| f.call_listener(trigger));
|
||||||
|
|
||||||
// Now, there are events in the queue
|
// Now, there are events in the queue
|
||||||
let mut updates = self.event_queue.queue.as_ref().borrow_mut();
|
let mut updates = self.shared.borrow_queue();
|
||||||
|
|
||||||
// Order the nodes by their height, we want the nodes with the smallest depth on top
|
// Order the nodes by their height, we want the nodes with the smallest depth on top
|
||||||
// This prevents us from running the same component multiple times
|
// This prevents us from running the same component multiple times
|
||||||
@@ -370,8 +338,10 @@ impl VirtualDom {
|
|||||||
diff_machine.seen_nodes.insert(update.idx.clone());
|
diff_machine.seen_nodes.insert(update.idx.clone());
|
||||||
|
|
||||||
// Start a new mutable borrow to components
|
// Start a new mutable borrow to components
|
||||||
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified in the diff machine
|
||||||
let cur_component = self.components.get_mut(update.idx).unwrap();
|
let cur_component = diff_machine
|
||||||
|
.get_scope_mut(&update.idx)
|
||||||
|
.expect("Failed to find scope or borrow would be aliasing");
|
||||||
|
|
||||||
if cur_component.run_scope().is_ok() {
|
if cur_component.run_scope().is_ok() {
|
||||||
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
||||||
@@ -385,7 +355,7 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_scope(&self) -> &Scope {
|
pub fn base_scope(&self) -> &Scope {
|
||||||
self.components.get(self.base_scope).unwrap()
|
unsafe { self.shared.get_scope(self.base_scope).unwrap() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
|||||||
"{:}",
|
"{:}",
|
||||||
TextRenderer {
|
TextRenderer {
|
||||||
cfg: SsrConfig::default(),
|
cfg: SsrConfig::default(),
|
||||||
root: vdom.components.get(scope).unwrap().root(),
|
root: vdom.shared.get_scope(scope).unwrap().root(),
|
||||||
vdom: Some(vdom)
|
vdom: Some(vdom)
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -163,7 +163,7 @@ impl<'a> TextRenderer<'a> {
|
|||||||
VNodeKind::Component(vcomp) => {
|
VNodeKind::Component(vcomp) => {
|
||||||
let idx = vcomp.ass_scope.get().unwrap();
|
let idx = vcomp.ass_scope.get().unwrap();
|
||||||
if let Some(vdom) = self.vdom {
|
if let Some(vdom) = self.vdom {
|
||||||
let new_node = vdom.components.get(idx).unwrap().root();
|
let new_node = vdom.shared.get_scope(idx).unwrap().root();
|
||||||
self.html_render(new_node, f, il + 1)?;
|
self.html_render(new_node, f, il + 1)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-11
@@ -13,7 +13,7 @@ use web_sys::{
|
|||||||
|
|
||||||
pub struct WebsysDom {
|
pub struct WebsysDom {
|
||||||
pub stack: Stack,
|
pub stack: Stack,
|
||||||
nodes: slotmap::SlotMap<DefaultKey, Option<Node>>,
|
nodes: slotmap::SlotMap<DefaultKey, Node>,
|
||||||
document: Document,
|
document: Document,
|
||||||
root: Element,
|
root: Element,
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ impl WebsysDom {
|
|||||||
|
|
||||||
let mut nodes = slotmap::SlotMap::with_capacity(1000);
|
let mut nodes = slotmap::SlotMap::with_capacity(1000);
|
||||||
|
|
||||||
let root_id = nodes.insert(Some(root.clone().dyn_into::<Node>().unwrap()));
|
let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stack: Stack::with_capacity(10),
|
stack: Stack::with_capacity(10),
|
||||||
@@ -101,11 +101,16 @@ impl WebsysDom {
|
|||||||
let key = DefaultKey::from(KeyData::from_ffi(root));
|
let key = DefaultKey::from(KeyData::from_ffi(root));
|
||||||
let domnode = self.nodes.get_mut(key);
|
let domnode = self.nodes.get_mut(key);
|
||||||
|
|
||||||
let domnode = domnode.unwrap().as_mut().unwrap();
|
let real_node: Node = match domnode {
|
||||||
|
Some(n) => n.clone(),
|
||||||
|
None => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// let domnode = domnode.unwrap().as_mut().unwrap();
|
||||||
// .expect(&format!("Failed to pop know root: {:#?}", key))
|
// .expect(&format!("Failed to pop know root: {:#?}", key))
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
|
|
||||||
self.stack.push(domnode.clone());
|
self.stack.push(real_node);
|
||||||
}
|
}
|
||||||
// drop the node off the stack
|
// drop the node off the stack
|
||||||
fn pop(&mut self) {
|
fn pop(&mut self) {
|
||||||
@@ -202,7 +207,7 @@ impl WebsysDom {
|
|||||||
*self
|
*self
|
||||||
.nodes
|
.nodes
|
||||||
.get_mut(DefaultKey::from(KeyData::from_ffi(id)))
|
.get_mut(DefaultKey::from(KeyData::from_ffi(id)))
|
||||||
.unwrap() = Some(textnode);
|
.unwrap() = textnode;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
||||||
@@ -224,7 +229,7 @@ impl WebsysDom {
|
|||||||
let id = DefaultKey::from(KeyData::from_ffi(id));
|
let id = DefaultKey::from(KeyData::from_ffi(id));
|
||||||
|
|
||||||
self.stack.push(el.clone());
|
self.stack.push(el.clone());
|
||||||
*self.nodes.get_mut(id).unwrap() = Some(el);
|
*self.nodes.get_mut(id).unwrap() = el;
|
||||||
// let nid = self.node_counter.?next();
|
// let nid = self.node_counter.?next();
|
||||||
// let nid = self.nodes.insert(el).data().as_ffi();
|
// let nid = self.nodes.insert(el).data().as_ffi();
|
||||||
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
||||||
@@ -344,11 +349,11 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
|
||||||
fn request_available_node(&mut self) -> RealDomNode {
|
// fn request_available_node(&mut self) -> RealDomNode {
|
||||||
let key = self.nodes.insert(None);
|
// let key = self.nodes.insert(None);
|
||||||
log::debug!("making new key: {:#?}", key);
|
// log::debug!("making new key: {:#?}", key);
|
||||||
RealDomNode(key.data().as_ffi())
|
// RealDomNode(key.data().as_ffi())
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
|
||||||
todo!()
|
todo!()
|
||||||
|
|||||||
Reference in new issue
Block a user