wip: successfully building
This commit is contained in:
1 parent
7790750349
commit
e3d9db0847
11 files changed
+355
-827
No files matched your search
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,24 @@ impl ScopeArena {
|
|||||||
// todo!()
|
// todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_scope<'b, O: 'static>(
|
||||||
|
&'b self,
|
||||||
|
id: ScopeIdx,
|
||||||
|
f: impl FnOnce(&'b mut Scope) -> O,
|
||||||
|
) -> Result<O> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return a bumpframe with a lifetime attached to the arena borrow
|
||||||
|
// this is useful for merging lifetimes
|
||||||
|
pub fn with_scope_vnode<'b>(
|
||||||
|
&self,
|
||||||
|
id: ScopeIdx,
|
||||||
|
f: impl FnOnce(&mut Scope) -> &VNode<'b>,
|
||||||
|
) -> Result<&VNode<'b>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn try_remove(&mut self, id: ScopeIdx) -> Result<Scope> {
|
pub fn try_remove(&mut self, id: ScopeIdx) -> Result<Scope> {
|
||||||
let inner = unsafe { &mut *self.0.borrow().arena.get() };
|
let inner = unsafe { &mut *self.0.borrow().arena.get() };
|
||||||
inner
|
inner
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::innerlude::FC;
|
|||||||
|
|
||||||
pub type ScopeIdx = generational_arena::Index;
|
pub type ScopeIdx = generational_arena::Index;
|
||||||
|
|
||||||
pub unsafe trait Properties: PartialEq {
|
pub unsafe trait Properties: PartialEq + Sized {
|
||||||
type Builder;
|
type Builder;
|
||||||
const CAN_BE_MEMOIZED: bool;
|
const CAN_BE_MEMOIZED: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
@@ -36,7 +36,9 @@ pub fn fc_to_builder<T: Properties>(_f: FC<T>) -> T::Builder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod testing {
|
mod testing {
|
||||||
use std::any::Any;
|
use std::{any::Any, ops::Deref};
|
||||||
|
|
||||||
|
use crate::innerlude::VNode;
|
||||||
|
|
||||||
// trait PossibleProps {
|
// trait PossibleProps {
|
||||||
// type POut: PartialEq;
|
// type POut: PartialEq;
|
||||||
@@ -137,6 +139,43 @@ mod testing {
|
|||||||
// blah(10, "asd");
|
// blah(10, "asd");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Inner<'a> {
|
||||||
|
a: String,
|
||||||
|
b: i32,
|
||||||
|
c: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Custom<'a, P: 'a> {
|
||||||
|
inner: &'a P,
|
||||||
|
// inner: *const (),
|
||||||
|
_p: std::marker::PhantomData<&'a P>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P> Custom<'a, P> {
|
||||||
|
fn props(&self) -> &P {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<P> Deref for Custom<P> {
|
||||||
|
// type Target = Inner;
|
||||||
|
|
||||||
|
// fn deref(&self) -> &Self::Target {
|
||||||
|
// unsafe { &*self.inner }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn test2<'a>(a: Custom<'a, Inner<'a>>) -> VNode {
|
||||||
|
let r = a.inner;
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
// let g = a.props();
|
||||||
|
// todo!()
|
||||||
|
// let g = &a.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_comp() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod style {}
|
mod style {}
|
||||||
+173
-99
@@ -59,52 +59,29 @@ use std::{
|
|||||||
/// subscriptions and props changes.
|
/// subscriptions and props changes.
|
||||||
pub struct DiffMachine<'a> {
|
pub struct DiffMachine<'a> {
|
||||||
pub create_diffs: bool,
|
pub create_diffs: bool,
|
||||||
|
pub cur_idx: ScopeIdx,
|
||||||
pub change_list: EditMachine<'a>,
|
pub change_list: EditMachine<'a>,
|
||||||
pub diffed: FxHashSet<ScopeIdx>,
|
pub diffed: FxHashSet<ScopeIdx>,
|
||||||
pub lifecycle_events: VecDeque<LifeCycleEvent<'a>>,
|
pub components: ScopeArena,
|
||||||
pub vdom: ScopeArena,
|
pub event_queue: EventQueue,
|
||||||
}
|
pub seen_nodes: FxHashSet<ScopeIdx>,
|
||||||
pub enum LifeCycleEvent<'a> {
|
|
||||||
Mount {
|
|
||||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
|
||||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
|
||||||
root_id: u32,
|
|
||||||
},
|
|
||||||
PropsChanged {
|
|
||||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
|
||||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
|
||||||
root_id: u32,
|
|
||||||
},
|
|
||||||
SameProps {
|
|
||||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
|
||||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
|
||||||
root_id: u32,
|
|
||||||
},
|
|
||||||
Replace {
|
|
||||||
caller: Weak<dyn Fn(&Scope) -> VNode + 'a>,
|
|
||||||
old_scope: Weak<VCompAssociatedScope>,
|
|
||||||
new_scope: Weak<VCompAssociatedScope>,
|
|
||||||
root_id: u32,
|
|
||||||
},
|
|
||||||
Remove {
|
|
||||||
stable_scope_addr: Weak<VCompAssociatedScope>,
|
|
||||||
root_id: u32,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
static COUNTER: AtomicU32 = AtomicU32::new(1);
|
||||||
fn get_id() -> u32 {
|
fn next_id() -> u32 {
|
||||||
COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DiffMachine<'a> {
|
impl<'a> DiffMachine<'a> {
|
||||||
pub fn new(vdom: ScopeArena) -> Self {
|
pub fn new(components: ScopeArena, cur_idx: ScopeIdx, event_queue: EventQueue) -> Self {
|
||||||
Self {
|
Self {
|
||||||
vdom,
|
components,
|
||||||
|
cur_idx,
|
||||||
|
event_queue,
|
||||||
create_diffs: true,
|
create_diffs: true,
|
||||||
lifecycle_events: VecDeque::new(),
|
|
||||||
change_list: EditMachine::new(),
|
change_list: EditMachine::new(),
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
|
seen_nodes: FxHashSet::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,14 +89,14 @@ impl<'a> DiffMachine<'a> {
|
|||||||
self.change_list.emitter
|
self.change_list.emitter
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
pub fn diff_node(&mut self, old_node: &VNode<'a>, new_node: &VNode<'a>) {
|
||||||
// pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
// pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
||||||
/*
|
/*
|
||||||
For each valid case, we "commit traversal", meaning we save this current position in the tree.
|
For each valid case, we "commit traversal", meaning we save this current position in the tree.
|
||||||
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
||||||
When re-entering, we reuse the EditList in DiffState
|
When re-entering, we reuse the EditList in DiffState
|
||||||
*/
|
*/
|
||||||
match (old, new) {
|
match (old_node, new_node) {
|
||||||
(VNode::Text(old_text), VNode::Text(new_text)) => {
|
(VNode::Text(old_text), VNode::Text(new_text)) => {
|
||||||
if old_text != new_text {
|
if old_text != new_text {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
@@ -129,13 +106,13 @@ impl<'a> DiffMachine<'a> {
|
|||||||
|
|
||||||
(VNode::Text(_), VNode::Element(_)) => {
|
(VNode::Text(_), VNode::Element(_)) => {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
self.create(new);
|
self.create(new_node);
|
||||||
self.change_list.replace_with();
|
self.change_list.replace_with();
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Element(_), VNode::Text(_)) => {
|
(VNode::Element(_), VNode::Text(_)) => {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
self.create(new);
|
self.create(new_node);
|
||||||
self.change_list.replace_with();
|
self.change_list.replace_with();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,57 +130,84 @@ impl<'a> DiffMachine<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Component(cold), VNode::Component(cnew)) => {
|
(VNode::Component(cold), VNode::Component(cnew)) => {
|
||||||
// todo!("should not happen")
|
// Make sure we're dealing with the same component (by function pointer)
|
||||||
// self.change_list.commit_traversal();
|
|
||||||
if cold.user_fc == cnew.user_fc {
|
if cold.user_fc == cnew.user_fc {
|
||||||
// todo: create a stable addr
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
// let caller = Rc::downgrade(&cnew.caller);
|
let scope_id = cold.ass_scope.borrow().clone();
|
||||||
// let id = cold.stable_addr.borrow().unwrap();
|
*cnew.ass_scope.borrow_mut() = scope_id;
|
||||||
// *cnew.stable_addr.borrow_mut() = Some(id);
|
|
||||||
// *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
|
|
||||||
|
|
||||||
// let scope = Rc::downgrade(&cold.ass_scope);
|
// make sure the component's caller function is up to date
|
||||||
todo!()
|
self.components
|
||||||
// self.lifecycle_events
|
.with_scope(scope_id.unwrap(), |scope| {
|
||||||
// .push_back(LifeCycleEvent::PropsChanged {
|
scope.caller = Rc::downgrade(&cnew.caller)
|
||||||
// caller,
|
})
|
||||||
// root_id: id,
|
.unwrap();
|
||||||
// stable_scope_addr: scope,
|
|
||||||
// });
|
// React doesn't automatically memoize, but we do.
|
||||||
|
// The cost is low enough to make it worth checking
|
||||||
|
let should_render = match cold.comparator {
|
||||||
|
Some(comparator) => comparator(cnew),
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_render {
|
||||||
|
self.change_list.commit_traversal();
|
||||||
|
self.components
|
||||||
|
.with_scope(scope_id.unwrap(), |f| {
|
||||||
|
f.run_scope().unwrap();
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
// diff_machine.change_list.load_known_root(root_id);
|
||||||
|
// run the scope
|
||||||
|
//
|
||||||
|
} else {
|
||||||
|
// Component has memoized itself and doesn't need to be re-rendered.
|
||||||
|
// We still need to make sure the child's props are up-to-date.
|
||||||
|
// Don't commit traversal
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// let caller = Rc::downgrade(&cnew.caller);
|
// A new component has shown up! We need to destroy the old node
|
||||||
// let id = cold.stable_addr.borrow().unwrap();
|
|
||||||
// let old_scope = Rc::downgrade(&cold.ass_scope);
|
|
||||||
// let new_scope = Rc::downgrade(&cnew.ass_scope);
|
|
||||||
|
|
||||||
todo!()
|
// Wipe the old one and plant the new one
|
||||||
// self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
self.change_list.commit_traversal();
|
||||||
// caller,
|
self.create(new_node);
|
||||||
// root_id: id,
|
self.change_list.replace_with();
|
||||||
// old_scope,
|
|
||||||
// new_scope,
|
// Now we need to remove the old scope and all of its descendents
|
||||||
// });
|
let old_scope = cold.ass_scope.borrow().as_ref().unwrap().clone();
|
||||||
|
self.destroy_scopes(old_scope);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: knock out any listeners
|
// todo: knock out any listeners
|
||||||
(_, VNode::Component(_new)) => {
|
(_, VNode::Component(_)) => {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
|
self.create(new_node);
|
||||||
|
self.change_list.replace_with();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A component is being torn down in favor of a non-component node
|
||||||
(VNode::Component(_old), _) => {
|
(VNode::Component(_old), _) => {
|
||||||
todo!("Usage of component VNode not currently supported");
|
self.change_list.commit_traversal();
|
||||||
|
self.create(new_node);
|
||||||
|
self.change_list.replace_with();
|
||||||
|
|
||||||
|
// Destroy the original scope and any of its children
|
||||||
|
self.destroy_scopes(_old.ass_scope.borrow().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anything suspended is not enabled ATM
|
||||||
(VNode::Suspended, _) | (_, VNode::Suspended) => {
|
(VNode::Suspended, _) | (_, VNode::Suspended) => {
|
||||||
todo!("Suspended components not currently available")
|
todo!("Suspended components not currently available")
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNode::Fragment(_), VNode::Fragment(_)) => {
|
// (VNode::Fragment(_), VNode::Fragment(_)) => {
|
||||||
|
// todo!("Fragments not currently supported in diffing")
|
||||||
|
// }
|
||||||
|
// Fragments are special
|
||||||
|
(VNode::Fragment(_), _) | (_, VNode::Fragment(_)) => {
|
||||||
todo!("Fragments not currently supported in diffing")
|
todo!("Fragments not currently supported in diffing")
|
||||||
}
|
}
|
||||||
(_, VNode::Fragment(_)) => todo!("Fragments not currently supported in diffing"),
|
|
||||||
(VNode::Fragment(_), _) => todo!("Fragments not currently supported in diffing"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,28 +270,74 @@ impl<'a> DiffMachine<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
todo: integrate re-entrace
|
|
||||||
*/
|
|
||||||
VNode::Component(component) => {
|
VNode::Component(component) => {
|
||||||
todo!()
|
self.change_list
|
||||||
// self.change_list
|
.create_text_node("placeholder for vcomponent");
|
||||||
// .create_text_node("placeholder for vcomponent");
|
|
||||||
|
|
||||||
// let id = get_id();
|
let root_id = next_id();
|
||||||
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
|
self.change_list.save_known_root(root_id);
|
||||||
// self.change_list.save_known_root(id);
|
|
||||||
// let scope = Rc::downgrade(&component.ass_scope);
|
log::debug!("Mounting a new component");
|
||||||
// todo!()
|
let caller: Weak<OpaqueComponent> = Rc::downgrade(&component.caller);
|
||||||
// self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
|
||||||
// caller: Rc::downgrade(&component.caller),
|
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
||||||
// root_id: id,
|
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
||||||
// stable_scope_addr: scope,
|
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
||||||
// });
|
|
||||||
|
// Insert a new scope into our component list
|
||||||
|
let idx = self
|
||||||
|
.components
|
||||||
|
.with(|components| {
|
||||||
|
components.insert_with(|new_idx| {
|
||||||
|
let cur_idx = self.cur_idx;
|
||||||
|
let cur_scope = self.components.try_get(cur_idx).unwrap();
|
||||||
|
let height = cur_scope.height + 1;
|
||||||
|
Scope::new(
|
||||||
|
caller,
|
||||||
|
new_idx,
|
||||||
|
Some(cur_idx),
|
||||||
|
height,
|
||||||
|
self.event_queue.new_channel(height, new_idx),
|
||||||
|
self.components.clone(),
|
||||||
|
&[],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let cur_component = self.components.try_get_mut(idx).unwrap();
|
||||||
|
let mut ch = cur_component.descendents.borrow_mut();
|
||||||
|
ch.insert(idx);
|
||||||
|
std::mem::drop(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// yaaaaay lifetimes out of thin air
|
||||||
|
// really tho, we're merging the frame lifetimes together
|
||||||
|
let inner: &'a mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
|
||||||
|
let new_component = inner.get_mut(idx).unwrap();
|
||||||
|
|
||||||
|
// Actually initialize the caller's slot with the right address
|
||||||
|
*component.ass_scope.borrow_mut() = Some(idx);
|
||||||
|
|
||||||
|
// Run the scope for one iteration to initialize it
|
||||||
|
new_component.run_scope().unwrap();
|
||||||
|
|
||||||
|
// // Navigate the diff machine to the right point in the output dom
|
||||||
|
// self.change_list.load_known_root(id);
|
||||||
|
// let root_id = component.stable_addr
|
||||||
|
|
||||||
|
// And then run the diff algorithm
|
||||||
|
self.diff_node(new_component.old_frame(), new_component.next_frame());
|
||||||
|
|
||||||
|
// Finally, insert this node as a seen node.
|
||||||
|
self.seen_nodes.insert(idx);
|
||||||
}
|
}
|
||||||
VNode::Suspended => {
|
VNode::Suspended => {
|
||||||
todo!("Creation of VNode::Suspended not yet supported")
|
todo!("Creation of VNode::Suspended not yet supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we go the the "known root" but only operate on a sibling basis
|
||||||
VNode::Fragment(frag) => {
|
VNode::Fragment(frag) => {
|
||||||
//
|
//
|
||||||
todo!("Cannot current create fragments")
|
todo!("Cannot current create fragments")
|
||||||
@@ -295,6 +345,38 @@ impl<'a> DiffMachine<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Destroy a scope and all of its descendents.
|
||||||
|
///
|
||||||
|
/// Calling this will run the destuctors on all hooks in the tree.
|
||||||
|
/// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered.
|
||||||
|
fn destroy_scopes(&mut self, old_scope: ScopeIdx) {
|
||||||
|
let mut nodes_to_delete = vec![old_scope];
|
||||||
|
let mut scopes_to_explore = vec![old_scope];
|
||||||
|
|
||||||
|
// explore the scope tree breadth first
|
||||||
|
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
|
||||||
|
self.seen_nodes.insert(scope_id);
|
||||||
|
let scope = self.components.try_get(scope_id).unwrap();
|
||||||
|
for child in scope.descendents.borrow().iter() {
|
||||||
|
// Add this node to be explored
|
||||||
|
scopes_to_explore.push(child.clone());
|
||||||
|
|
||||||
|
// Also add it for deletion
|
||||||
|
nodes_to_delete.push(child.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all scopes that we found as part of this subtree
|
||||||
|
for node in nodes_to_delete {
|
||||||
|
log::debug!("Removing scope {:#?}", node);
|
||||||
|
let _scope = self.components.try_remove(node).unwrap();
|
||||||
|
// do anything we need to do to delete the scope
|
||||||
|
// I think we need to run the destructors on the hooks
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Diff event listeners between `old` and `new`.
|
// Diff event listeners between `old` and `new`.
|
||||||
//
|
//
|
||||||
// The listeners' node must be on top of the change list stack:
|
// The listeners' node must be on top of the change list stack:
|
||||||
@@ -303,7 +385,6 @@ impl<'a> DiffMachine<'a> {
|
|||||||
//
|
//
|
||||||
// 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: &[Listener<'_>], new: &[Listener<'_>]) {
|
||||||
// fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
|
|
||||||
if !old.is_empty() || !new.is_empty() {
|
if !old.is_empty() || !new.is_empty() {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
}
|
}
|
||||||
@@ -325,26 +406,15 @@ impl<'a> DiffMachine<'a> {
|
|||||||
.update_event_listener(event_type, new_l.scope, new_l.id)
|
.update_event_listener(event_type, new_l.scope, new_l.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let Some(scope) = self.current_idx {
|
|
||||||
// let cb = CbIdx::from_gi_index(scope, l_idx);
|
|
||||||
// self.change_list
|
|
||||||
// .update_event_listener(event_type, new_l.scope, new_l.id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
continue 'outer1;
|
continue 'outer1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let Some(scope) = self.current_idx {
|
|
||||||
// let cb = CbIdx::from_gi_index(scope, l_idx);
|
|
||||||
self.change_list
|
self.change_list
|
||||||
.new_event_listener(event_type, new_l.scope, new_l.id);
|
.new_event_listener(event_type, new_l.scope, new_l.id);
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
'outer2: for old_l in old {
|
'outer2: for old_l in old {
|
||||||
// registry.remove(old_l);
|
|
||||||
|
|
||||||
for new_l in new {
|
for new_l in new {
|
||||||
if new_l.event == old_l.event {
|
if new_l.event == old_l.event {
|
||||||
continue 'outer2;
|
continue 'outer2;
|
||||||
@@ -369,6 +439,9 @@ impl<'a> DiffMachine<'a> {
|
|||||||
) {
|
) {
|
||||||
// Do O(n^2) passes to add/update and remove attributes, since
|
// Do O(n^2) passes to add/update and remove attributes, since
|
||||||
// there are almost always very few attributes.
|
// there are almost always very few attributes.
|
||||||
|
//
|
||||||
|
// The "fast" path is when the list of attributes name is identical and in the same order
|
||||||
|
// With the Rsx and Html macros, this will almost always be the case
|
||||||
'outer: for new_attr in new {
|
'outer: for new_attr in new {
|
||||||
if new_attr.is_volatile() {
|
if new_attr.is_volatile() {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
@@ -386,6 +459,8 @@ impl<'a> DiffMachine<'a> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
|
} else {
|
||||||
|
// names are different, a varying order of attributes has arrived
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,12 +508,10 @@ impl<'a> DiffMachine<'a> {
|
|||||||
(_, &VNode::Text(text)) => {
|
(_, &VNode::Text(text)) => {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
self.change_list.set_text(text);
|
self.change_list.set_text(text);
|
||||||
// for o in old {
|
|
||||||
// registry.remove_subtree(o);
|
|
||||||
// }
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: any more optimizations
|
||||||
(_, _) => {}
|
(_, _) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,6 +538,7 @@ impl<'a> DiffMachine<'a> {
|
|||||||
|
|
||||||
if new_is_keyed && old_is_keyed {
|
if new_is_keyed && old_is_keyed {
|
||||||
let t = self.change_list.next_temporary();
|
let t = self.change_list.next_temporary();
|
||||||
|
self.diff_keyed_children(old, new);
|
||||||
self.change_list.set_next_temporary(t);
|
self.change_list.set_next_temporary(t);
|
||||||
} else {
|
} else {
|
||||||
self.diff_non_keyed_children(old, new);
|
self.diff_non_keyed_children(old, new);
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and su
|
|||||||
pub mod builder {
|
pub mod builder {
|
||||||
pub use super::nodebuilder::*;
|
pub use super::nodebuilder::*;
|
||||||
}
|
}
|
||||||
pub mod scope;
|
|
||||||
pub mod support;
|
pub mod support;
|
||||||
|
|
||||||
// types used internally that are important
|
// types used internally that are important
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
|
|||||||
Attribute { name, value }
|
Attribute { name, value }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn virtual_child<'a, T: Properties>(
|
pub fn virtual_child<'a, T: Properties + 'a>(
|
||||||
ctx: &NodeCtx<'a>,
|
ctx: &NodeCtx<'a>,
|
||||||
f: FC<T>,
|
f: FC<T>,
|
||||||
props: T,
|
props: T,
|
||||||
@@ -658,9 +658,11 @@ pub fn virtual_child<'a, T: Properties>(
|
|||||||
) -> VNode<'a> {
|
) -> VNode<'a> {
|
||||||
// currently concerned about if props have a custom drop implementation
|
// currently concerned about if props have a custom drop implementation
|
||||||
// might override it with the props macro
|
// might override it with the props macro
|
||||||
todo!()
|
// todo!()
|
||||||
// VNode::Component(
|
VNode::Component(
|
||||||
// ctx.bump()
|
ctx.bump()
|
||||||
// .alloc(crate::nodes::VComponent::new(f, props, key)),
|
.alloc(crate::nodes::VComponent::new(ctx.bump(), f, props, key)),
|
||||||
// )
|
// ctx.bump()
|
||||||
|
// .alloc(crate::nodes::VComponent::new(f, props, key)),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
+43
-13
@@ -226,7 +226,7 @@ pub struct VComponent<'src> {
|
|||||||
pub ass_scope: RefCell<VCompAssociatedScope>,
|
pub ass_scope: RefCell<VCompAssociatedScope>,
|
||||||
|
|
||||||
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
|
||||||
pub caller: Rc<dyn Fn(&Scope) -> VNode + 'src>,
|
pub caller: Rc<dyn Fn(&Scope) -> VNode>,
|
||||||
|
|
||||||
pub children: &'src [VNode<'src>],
|
pub children: &'src [VNode<'src>],
|
||||||
|
|
||||||
@@ -287,18 +287,24 @@ impl<'a> VComponent<'a> {
|
|||||||
|
|
||||||
// let prref: &'a P = props.as_ref();
|
// let prref: &'a P = props.as_ref();
|
||||||
|
|
||||||
let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(move |scope| {
|
// let r = create_closure(component, raw_props);
|
||||||
//
|
// let caller: Rc<dyn for<'g> Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| {
|
||||||
// let props2 = bad_props;
|
// // r(scope);
|
||||||
// props.as_ref()
|
// //
|
||||||
// let ctx = Context {
|
// // let props2 = bad_props;
|
||||||
// props: prref,
|
// // props.as_ref();
|
||||||
// scope,
|
// // let ctx = Context {
|
||||||
// };
|
// // props: prref,
|
||||||
todo!()
|
// // scope,
|
||||||
// component(ctx)
|
// // };
|
||||||
});
|
// // let ctx: Context<'g, P> = todo!();
|
||||||
// let caller = Rc::new(create_closure(component, raw_props));
|
// // todo!()
|
||||||
|
// // let r = component(ctx);
|
||||||
|
// todo!()
|
||||||
|
// });
|
||||||
|
let caller = create_closure(component, raw_props);
|
||||||
|
|
||||||
|
// let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(create_closure(component, raw_props));
|
||||||
|
|
||||||
let key = match key {
|
let key = match key {
|
||||||
Some(key) => NodeKey::new(key),
|
Some(key) => NodeKey::new(key),
|
||||||
@@ -321,6 +327,30 @@ impl<'a> VComponent<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||||
|
|
||||||
|
fn create_closure<'a, P: Properties + 'a>(
|
||||||
|
component: FC<P>,
|
||||||
|
raw_props: *const (),
|
||||||
|
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||||
|
// ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> {
|
||||||
|
let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
|
||||||
|
// cast back into the right lifetime
|
||||||
|
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||||
|
// let ctx: Context<P2> = todo!();
|
||||||
|
let ctx: Context<P> = Context {
|
||||||
|
props: safe_props,
|
||||||
|
scope: scp,
|
||||||
|
};
|
||||||
|
|
||||||
|
let g = component(ctx);
|
||||||
|
let g2 = unsafe { std::mem::transmute(g) };
|
||||||
|
g2
|
||||||
|
});
|
||||||
|
let r: Captured<'static> = unsafe { std::mem::transmute(g) };
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
pub struct VFragment<'src> {
|
pub struct VFragment<'src> {
|
||||||
pub key: NodeKey<'src>,
|
pub key: NodeKey<'src>,
|
||||||
pub children: &'src [VNode<'src>],
|
pub children: &'src [VNode<'src>],
|
||||||
|
|||||||
@@ -1,488 +0,0 @@
|
|||||||
use crate::{arena::ScopeArena, innerlude::*};
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use generational_arena::Arena;
|
|
||||||
use std::{
|
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::RefCell,
|
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
|
||||||
fmt::Debug,
|
|
||||||
future::Future,
|
|
||||||
ops::Deref,
|
|
||||||
pin::Pin,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Every component in Dioxus is represented by a `Scope`.
|
|
||||||
///
|
|
||||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
|
||||||
///
|
|
||||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
|
||||||
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
|
||||||
pub struct Scope {
|
|
||||||
// The parent's scope ID
|
|
||||||
pub parent: Option<ScopeIdx>,
|
|
||||||
|
|
||||||
// IDs of children that this scope has created
|
|
||||||
// This enables us to drop the children and their children when this scope is destroyed
|
|
||||||
pub(crate) descendents: RefCell<HashSet<ScopeIdx>>,
|
|
||||||
|
|
||||||
pub(crate) child_nodes: &'static [VNode<'static>],
|
|
||||||
|
|
||||||
// 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: ScopeArena,
|
|
||||||
|
|
||||||
pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
|
||||||
|
|
||||||
// Our own ID accessible from the component map
|
|
||||||
pub arena_idx: ScopeIdx,
|
|
||||||
|
|
||||||
pub height: u32,
|
|
||||||
|
|
||||||
pub event_channel: Rc<dyn Fn() + 'static>,
|
|
||||||
|
|
||||||
// pub event_queue: EventQueue,
|
|
||||||
pub caller: Weak<OpaqueComponent<'static>>,
|
|
||||||
|
|
||||||
pub hookidx: RefCell<usize>,
|
|
||||||
|
|
||||||
// ==========================
|
|
||||||
// slightly unsafe stuff
|
|
||||||
// ==========================
|
|
||||||
// an internal, highly efficient storage of vnodes
|
|
||||||
pub frames: ActiveFrame,
|
|
||||||
|
|
||||||
// These hooks are actually references into the hook arena
|
|
||||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
|
||||||
// or we could dedicate a tiny bump arena just for them
|
|
||||||
// could also use ourborous
|
|
||||||
hooks: RefCell<Vec<Hook>>,
|
|
||||||
|
|
||||||
// Unsafety:
|
|
||||||
// - is self-refenrential and therefore needs to point into the bump
|
|
||||||
// Stores references into the listeners attached to the vnodes
|
|
||||||
// NEEDS TO BE PRIVATE
|
|
||||||
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to pin the hook so it doesn't move as we initialize the list of hooks
|
|
||||||
type Hook = Pin<Box<dyn std::any::Any>>;
|
|
||||||
type EventChannel = Rc<dyn Fn()>;
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
|
|
||||||
// we are going to break this lifetime by force in order to save it on ourselves.
|
|
||||||
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
|
|
||||||
// This should never happen, but is a good check to keep around
|
|
||||||
//
|
|
||||||
// Scopes cannot be made anywhere else except for this file
|
|
||||||
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
|
||||||
pub(crate) fn new<'creator_node>(
|
|
||||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
|
||||||
arena_idx: ScopeIdx,
|
|
||||||
parent: Option<ScopeIdx>,
|
|
||||||
height: u32,
|
|
||||||
event_channel: EventChannel,
|
|
||||||
arena_link: ScopeArena,
|
|
||||||
child_nodes: &'creator_node [VNode<'creator_node>],
|
|
||||||
) -> Self {
|
|
||||||
log::debug!(
|
|
||||||
"New scope created, height is {}, idx is {:?}",
|
|
||||||
height,
|
|
||||||
arena_idx
|
|
||||||
);
|
|
||||||
|
|
||||||
// The function to run this scope is actually located in the parent's bump arena.
|
|
||||||
// Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame.
|
|
||||||
// If children try to run this invalid caller, it *will* result in UB.
|
|
||||||
//
|
|
||||||
// During the lifecycle progression process, this caller will need to be updated. Right now,
|
|
||||||
// until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller
|
|
||||||
// lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully,
|
|
||||||
// this is a bit of a hack, but will remain this way until we've figured out a cleaner solution.
|
|
||||||
//
|
|
||||||
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
|
|
||||||
let caller = unsafe {
|
|
||||||
std::mem::transmute::<
|
|
||||||
Weak<OpaqueComponent<'creator_node>>,
|
|
||||||
Weak<OpaqueComponent<'static>>,
|
|
||||||
>(caller)
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
child_nodes: &[],
|
|
||||||
caller,
|
|
||||||
parent,
|
|
||||||
arena_idx,
|
|
||||||
height,
|
|
||||||
event_channel,
|
|
||||||
arena_link,
|
|
||||||
frames: ActiveFrame::new(),
|
|
||||||
hooks: Default::default(),
|
|
||||||
shared_contexts: Default::default(),
|
|
||||||
listeners: Default::default(),
|
|
||||||
hookidx: Default::default(),
|
|
||||||
descendents: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
|
||||||
let broken_caller = unsafe {
|
|
||||||
std::mem::transmute::<
|
|
||||||
Weak<OpaqueComponent<'creator_node>>,
|
|
||||||
Weak<OpaqueComponent<'static>>,
|
|
||||||
>(caller)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.caller = broken_caller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new context and run the component with references from the Virtual Dom
|
|
||||||
/// This function downcasts the function pointer based on the stored props_type
|
|
||||||
///
|
|
||||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
|
||||||
pub fn run_scope<'sel>(&'sel mut self) -> Result<()> {
|
|
||||||
// Cycle to the next frame and then reset it
|
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
|
||||||
self.frames.next().bump.reset();
|
|
||||||
|
|
||||||
// Remove all the outdated listeners
|
|
||||||
//
|
|
||||||
self.listeners
|
|
||||||
.try_borrow_mut()
|
|
||||||
.ok()
|
|
||||||
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
|
|
||||||
.drain(..);
|
|
||||||
|
|
||||||
*self.hookidx.borrow_mut() = 0;
|
|
||||||
|
|
||||||
let caller = self
|
|
||||||
.caller
|
|
||||||
.upgrade()
|
|
||||||
.ok_or(Error::FatalInternal("Failed to get caller"))?;
|
|
||||||
|
|
||||||
// Cast the caller ptr from static to one with our own reference
|
|
||||||
let c2: &OpaqueComponent<'static> = caller.as_ref();
|
|
||||||
let c3: &OpaqueComponent<'_> = unsafe { std::mem::transmute(c2) };
|
|
||||||
|
|
||||||
let unsafe_head = unsafe { self.own_vnodes(c3) };
|
|
||||||
|
|
||||||
self.frames.cur_frame_mut().head_node = unsafe_head;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// this is its own function so we can preciesly control how lifetimes flow
|
|
||||||
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> {
|
|
||||||
let new_head: VNode<'a> = f(self);
|
|
||||||
let out: VNode<'static> = std::mem::transmute(new_head);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
// A safe wrapper around calling listeners
|
|
||||||
// calling listeners will invalidate the list of listeners
|
|
||||||
// The listener list will be completely drained because the next frame will write over previous listeners
|
|
||||||
pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
|
|
||||||
let EventTrigger {
|
|
||||||
listener_id, event, ..
|
|
||||||
} = trigger;
|
|
||||||
//
|
|
||||||
unsafe {
|
|
||||||
// Convert the raw ptr into an actual object
|
|
||||||
// This operation is assumed to be safe
|
|
||||||
let listener_fn = self
|
|
||||||
.listeners
|
|
||||||
.try_borrow()
|
|
||||||
.ok()
|
|
||||||
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
|
|
||||||
.get(listener_id as usize)
|
|
||||||
.ok_or(Error::FatalInternal("Event should exist if triggered"))?
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(Error::FatalInternal("Raw event ptr is invalid"))?;
|
|
||||||
|
|
||||||
// Run the callback with the user event
|
|
||||||
listener_fn(event);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
self.frames.current_head_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
self.frames.prev_head_node()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cur_frame(&self) -> &BumpFrame {
|
|
||||||
self.frames.cur_frame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
|
||||||
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
|
|
||||||
///
|
|
||||||
/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// #[derive(Properties)]
|
|
||||||
/// struct Props {
|
|
||||||
/// name: String
|
|
||||||
///
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn example(ctx: Context, props: &Props -> VNode {
|
|
||||||
/// html! {
|
|
||||||
/// <div> "Hello, {ctx.ctx.name}" </div>
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
// todo: force lifetime of source into T as a valid lifetime too
|
|
||||||
// it's definitely possible, just needs some more messing around
|
|
||||||
|
|
||||||
pub struct Context<'src, T> {
|
|
||||||
pub props: &'src T,
|
|
||||||
pub scope: &'src Scope,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, T> Copy for Context<'src, T> {}
|
|
||||||
impl<'src, T> Clone for Context<'src, T> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
props: self.props,
|
|
||||||
scope: self.scope,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, T> Deref for Context<'a, T> {
|
|
||||||
type Target = &'a T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.props
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'src, T> Scoped<'src> for Context<'src, T> {
|
|
||||||
fn get_scope(&self) -> &'src Scope {
|
|
||||||
self.scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Scoped<'src>: Sized {
|
|
||||||
fn get_scope(&self) -> &'src Scope;
|
|
||||||
|
|
||||||
/// Access the children elements passed into the component
|
|
||||||
fn children(&self) -> &'src [VNode<'src>] {
|
|
||||||
// We're re-casting the nodes back out
|
|
||||||
// They don't really have a static lifetime
|
|
||||||
unsafe {
|
|
||||||
let scope = self.get_scope();
|
|
||||||
let nodes: &'src [VNode<'static>] = scope.child_nodes;
|
|
||||||
|
|
||||||
// cast the lifetime back correctly
|
|
||||||
std::mem::transmute(nodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a subscription that schedules a future render for the reference component
|
|
||||||
fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
|
||||||
self.get_scope().event_channel.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
|
||||||
///
|
|
||||||
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
|
|
||||||
///
|
|
||||||
/// ## Example
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// fn Component(ctx: Context<()>) -> VNode {
|
|
||||||
/// // Lazy assemble the VNode tree
|
|
||||||
/// let lazy_tree = html! {<div> "Hello World" </div>};
|
|
||||||
///
|
|
||||||
/// // Actually build the tree and allocate it
|
|
||||||
/// ctx.render(lazy_tree)
|
|
||||||
/// }
|
|
||||||
///```
|
|
||||||
fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
|
|
||||||
self,
|
|
||||||
lazy_nodes: LazyNodes<'src, F>,
|
|
||||||
) -> VNode<'src> {
|
|
||||||
lazy_nodes.into_vnode(&NodeCtx {
|
|
||||||
scope_ref: self.get_scope(),
|
|
||||||
listener_id: 0.into(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// impl<'scope> Context<'scope> {
|
|
||||||
/// Store a value between renders
|
|
||||||
///
|
|
||||||
/// - Initializer: closure used to create the initial hook state
|
|
||||||
/// - Runner: closure used to output a value every time the hook is used
|
|
||||||
/// - Cleanup: closure used to teardown the hook once the dom is cleaned up
|
|
||||||
///
|
|
||||||
/// ```ignore
|
|
||||||
/// // use_ref is the simplest way of storing a value between renders
|
|
||||||
/// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
|
|
||||||
/// use_hook(
|
|
||||||
/// || Rc::new(RefCell::new(initial_value())),
|
|
||||||
/// |state| state.clone(),
|
|
||||||
/// |_| {},
|
|
||||||
/// )
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
fn use_hook<InternalHookState: 'static, Output: 'src>(
|
|
||||||
&self,
|
|
||||||
|
|
||||||
// The closure that builds the hook state
|
|
||||||
initializer: impl FnOnce() -> InternalHookState,
|
|
||||||
|
|
||||||
// The closure that takes the hookstate and returns some value
|
|
||||||
runner: impl FnOnce(&'src mut InternalHookState) -> Output,
|
|
||||||
|
|
||||||
// The closure that cleans up whatever mess is left when the component gets torn down
|
|
||||||
// TODO: add this to the "clean up" group for when the component is dropped
|
|
||||||
_cleanup: impl FnOnce(InternalHookState),
|
|
||||||
) -> Output {
|
|
||||||
let scope = self.get_scope();
|
|
||||||
|
|
||||||
let idx = *scope.hookidx.borrow();
|
|
||||||
|
|
||||||
// Grab out the hook list
|
|
||||||
let mut hooks = scope.hooks.borrow_mut();
|
|
||||||
|
|
||||||
// If the idx is the same as the hook length, then we need to add the current hook
|
|
||||||
if idx >= hooks.len() {
|
|
||||||
let new_state = initializer();
|
|
||||||
hooks.push(Box::pin(new_state));
|
|
||||||
}
|
|
||||||
|
|
||||||
*scope.hookidx.borrow_mut() += 1;
|
|
||||||
|
|
||||||
let stable_ref = hooks
|
|
||||||
.get_mut(idx)
|
|
||||||
.expect("Should not fail, idx is validated")
|
|
||||||
.as_mut();
|
|
||||||
|
|
||||||
let pinned_state = unsafe { Pin::get_unchecked_mut(stable_ref) };
|
|
||||||
|
|
||||||
let internal_state = pinned_state.downcast_mut::<InternalHookState>().expect(
|
|
||||||
r###"
|
|
||||||
Unable to retrive the hook that was initialized in this index.
|
|
||||||
Consult the `rules of hooks` to understand how to use hooks properly.
|
|
||||||
|
|
||||||
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
|
|
||||||
Any function prefixed with "use" should not be called conditionally.
|
|
||||||
"###,
|
|
||||||
);
|
|
||||||
|
|
||||||
// We extend the lifetime of the internal state
|
|
||||||
runner(unsafe { &mut *(internal_state as *mut _) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
|
||||||
///
|
|
||||||
/// This is a hook, so it may not be called conditionally!
|
|
||||||
///
|
|
||||||
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
|
|
||||||
/// so don't put it in a conditional.
|
|
||||||
///
|
|
||||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
|
||||||
/// the context via Rc/Weak.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
|
|
||||||
let scope = self.get_scope();
|
|
||||||
let mut ctxs = scope.shared_contexts.borrow_mut();
|
|
||||||
let ty = TypeId::of::<T>();
|
|
||||||
|
|
||||||
let is_initialized = self.use_hook(
|
|
||||||
|| false,
|
|
||||||
|s| {
|
|
||||||
let i = s.clone();
|
|
||||||
*s = true;
|
|
||||||
i
|
|
||||||
},
|
|
||||||
|_| {},
|
|
||||||
);
|
|
||||||
|
|
||||||
match (is_initialized, ctxs.contains_key(&ty)) {
|
|
||||||
// Do nothing, already initialized and already exists
|
|
||||||
(true, true) => {}
|
|
||||||
|
|
||||||
// Needs to be initialized
|
|
||||||
(false, false) => {
|
|
||||||
log::debug!("Initializing context...");
|
|
||||||
ctxs.insert(ty, Rc::new(init()));
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// There are hooks going on here!
|
|
||||||
fn use_context<T: 'static>(&self) -> &'src Rc<T> {
|
|
||||||
self.try_use_context().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uses a context, storing the cached value around
|
|
||||||
fn try_use_context<T: 'static>(&self) -> Result<&'src Rc<T>> {
|
|
||||||
struct UseContextHook<C> {
|
|
||||||
par: Option<Rc<C>>,
|
|
||||||
we: Option<Weak<C>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
self.use_hook(
|
|
||||||
move || UseContextHook {
|
|
||||||
par: None as Option<Rc<T>>,
|
|
||||||
we: None as Option<Weak<T>>,
|
|
||||||
},
|
|
||||||
move |hook| {
|
|
||||||
let scope = self.get_scope();
|
|
||||||
let mut scope = Some(scope);
|
|
||||||
|
|
||||||
if let Some(we) = &hook.we {
|
|
||||||
if let Some(re) = we.upgrade() {
|
|
||||||
hook.par = Some(re);
|
|
||||||
return Ok(hook.par.as_ref().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let ty = TypeId::of::<T>();
|
|
||||||
while let Some(inner) = scope {
|
|
||||||
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
|
|
||||||
let shared_contexts = inner.shared_contexts.borrow();
|
|
||||||
|
|
||||||
if let Some(shared_ctx) = shared_contexts.get(&ty) {
|
|
||||||
log::debug!("found matching ctx");
|
|
||||||
let rc = shared_ctx
|
|
||||||
.clone()
|
|
||||||
.downcast::<T>()
|
|
||||||
.expect("Should not fail, already validated the type from the hashmap");
|
|
||||||
|
|
||||||
hook.we = Some(Rc::downgrade(&rc));
|
|
||||||
hook.par = Some(rc);
|
|
||||||
return Ok(hook.par.as_ref().unwrap());
|
|
||||||
} else {
|
|
||||||
match inner.parent {
|
|
||||||
Some(parent_id) => {
|
|
||||||
let parent = inner
|
|
||||||
.arena_link
|
|
||||||
.try_get(parent_id)
|
|
||||||
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
|
|
||||||
|
|
||||||
scope = Some(parent);
|
|
||||||
}
|
|
||||||
None => return Err(Error::MissingSharedContext),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(Error::MissingSharedContext)
|
|
||||||
},
|
|
||||||
|_| {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
pub use crate::scope::*;
|
|
||||||
use crate::{arena::ScopeArena, innerlude::*};
|
use crate::{arena::ScopeArena, innerlude::*};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ pub struct VirtualDom {
|
|||||||
|
|
||||||
/// a strong allocation to the "caller" for the original component and its props
|
/// a strong allocation to the "caller" for the original component and its props
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
_root_caller: Rc<OpaqueComponent>,
|
||||||
|
// _root_caller: Rc<OpaqueComponent<'static>>,
|
||||||
/// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic.
|
/// Type of the original ctx. 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
|
/// Whenver props need to be updated, an Error will be thrown if the new props do not
|
||||||
@@ -137,7 +137,8 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
|
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
|
||||||
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
|
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
|
||||||
let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
|
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |scope| {
|
||||||
|
// let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
|
||||||
// the lifetime of this closure is just as long as the lifetime on the scope reference
|
// the lifetime of this closure is just as long as the lifetime on the scope reference
|
||||||
// this closure moves root props (which is static) into this closure
|
// this closure moves root props (which is static) into this closure
|
||||||
let props = unsafe { &*(&root_props as *const _) };
|
let props = unsafe { &*(&root_props as *const _) };
|
||||||
@@ -154,10 +155,11 @@ impl VirtualDom {
|
|||||||
// Make the first scope
|
// Make the first scope
|
||||||
// We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
|
// We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
|
||||||
let link = components.clone();
|
let link = components.clone();
|
||||||
let event_channel = Rc::new(move || {});
|
|
||||||
let base_scope = components
|
let base_scope = components
|
||||||
.with(|arena| {
|
.with(|arena| {
|
||||||
arena.insert_with(move |myidx| {
|
arena.insert_with(move |myidx| {
|
||||||
|
let event_channel = _event_queue.new_channel(0, myidx);
|
||||||
Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[])
|
Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -175,21 +177,24 @@ 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
|
/// Currently this doesn't do what we want it to do
|
||||||
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
||||||
todo!()
|
let mut diff_machine = DiffMachine::new(
|
||||||
// let mut diff_machine = DiffMachine::new();
|
self.components.clone(),
|
||||||
|
self.base_scope,
|
||||||
|
self.event_queue.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
// // Schedule an update and then immediately call it on the root component
|
// Schedule an update and then immediately call it on the root component
|
||||||
// // This is akin to a hook being called from a listener and requring a re-render
|
// This is akin to a hook being called from a listener and requring a re-render
|
||||||
// // Instead, this is done on top-level component
|
// Instead, this is done on top-level component
|
||||||
|
|
||||||
// let base = self.components.try_get(self.base_scope)?;
|
let base = self.components.try_get(self.base_scope)?;
|
||||||
|
|
||||||
// let update = &base.event_channel;
|
let update = &base.event_channel;
|
||||||
// update();
|
update();
|
||||||
|
|
||||||
// self.progress_completely(&mut diff_machine)?;
|
self.progress_completely(&mut diff_machine)?;
|
||||||
|
|
||||||
// Ok(diff_machine.consume())
|
Ok(diff_machine.consume())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +250,9 @@ impl VirtualDom {
|
|||||||
|
|
||||||
self.components.try_get_mut(id)?.call_listener(event)?;
|
self.components.try_get_mut(id)?.call_listener(event)?;
|
||||||
|
|
||||||
let mut diff_machine = DiffMachine::new(self.components.clone());
|
let mut diff_machine =
|
||||||
|
DiffMachine::new(self.components.clone(), id, self.event_queue.clone());
|
||||||
|
|
||||||
self.progress_completely(&mut diff_machine)?;
|
self.progress_completely(&mut diff_machine)?;
|
||||||
|
|
||||||
Ok(diff_machine.consume())
|
Ok(diff_machine.consume())
|
||||||
@@ -260,212 +267,48 @@ impl VirtualDom {
|
|||||||
diff_machine: &'_ mut DiffMachine<'s>,
|
diff_machine: &'_ mut DiffMachine<'s>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Add this component to the list of components that need to be difed
|
// Add this component to the list of components that need to be difed
|
||||||
#[allow(unused_assignments)]
|
// #[allow(unused_assignments)]
|
||||||
let mut cur_height: u32 = 0;
|
// let mut cur_height: u32 = 0;
|
||||||
|
|
||||||
// Now, there are events in the queue
|
// Now, there are events in the queue
|
||||||
let mut seen_nodes = HashSet::<ScopeIdx>::new();
|
|
||||||
let mut updates = self.event_queue.0.as_ref().borrow_mut();
|
let mut updates = self.event_queue.0.as_ref().borrow_mut();
|
||||||
|
|
||||||
// Order the nodes by their height, we want the biggest nodes on the top
|
// Order the nodes by their height, we want the biggest nodes on the top
|
||||||
// This prevents us from running the same component multiple times
|
// This prevents us from running the same component multiple times
|
||||||
updates.sort_unstable();
|
updates.sort_unstable();
|
||||||
|
|
||||||
|
log::debug!("There are: {:#?} updates to be processed", updates.len());
|
||||||
|
|
||||||
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
||||||
for update in updates.drain(..) {
|
for update in updates.drain(..) {
|
||||||
|
log::debug!("Running updates for: {:#?}", update);
|
||||||
// Make sure this isn't a node we've already seen, we don't want to double-render anything
|
// Make sure this isn't a node we've already seen, we don't want to double-render anything
|
||||||
// If we double-renderer something, this would cause memory safety issues
|
// If we double-renderer something, this would cause memory safety issues
|
||||||
if seen_nodes.contains(&update.idx) {
|
if diff_machine.seen_nodes.contains(&update.idx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, all the "seen nodes" are nodes that got notified by running this listener
|
// Now, all the "seen nodes" are nodes that got notified by running this listener
|
||||||
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
|
||||||
|
|
||||||
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
||||||
|
// let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
|
||||||
|
// let cur_component = inner.get_mut(update.idx).unwrap();
|
||||||
|
|
||||||
cur_component.run_scope()?;
|
cur_component.run_scope()?;
|
||||||
|
|
||||||
diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame());
|
diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame());
|
||||||
|
|
||||||
cur_height = cur_component.height;
|
// cur_height = cur_component.height;
|
||||||
|
|
||||||
log::debug!(
|
// log::debug!(
|
||||||
"Processing update: {:#?} with height {}",
|
// "Processing update: {:#?} with height {}",
|
||||||
&update.idx,
|
// &update.idx,
|
||||||
cur_height
|
// cur_height
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Now, the entire subtree has been invalidated. We need to descend depth-first and process
|
|
||||||
// any updates that the diff machine has proprogated into the component lifecycle queue
|
|
||||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
|
||||||
match event {
|
|
||||||
// A new component has been computed from the diffing algorithm
|
|
||||||
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
|
||||||
// this will flood the lifecycle queue with new updates to build up the subtree
|
|
||||||
LifeCycleEvent::Mount {
|
|
||||||
caller,
|
|
||||||
root_id: id,
|
|
||||||
stable_scope_addr,
|
|
||||||
} => {
|
|
||||||
log::debug!("Mounting a new component");
|
|
||||||
|
|
||||||
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
|
||||||
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
|
||||||
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
|
||||||
|
|
||||||
// Insert a new scope into our component list
|
|
||||||
let idx = self.components.with(|components| {
|
|
||||||
components.insert_with(|new_idx| {
|
|
||||||
let height = cur_height + 1;
|
|
||||||
Scope::new(
|
|
||||||
caller,
|
|
||||||
new_idx,
|
|
||||||
Some(cur_component.arena_idx),
|
|
||||||
height,
|
|
||||||
self.event_queue.new_channel(height, new_idx),
|
|
||||||
self.components.clone(),
|
|
||||||
&[],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
{
|
|
||||||
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
|
||||||
let mut ch = cur_component.descendents.borrow_mut();
|
|
||||||
ch.insert(idx);
|
|
||||||
std::mem::drop(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab out that component
|
|
||||||
let new_component = self.components.try_get_mut(idx).unwrap();
|
|
||||||
|
|
||||||
// Actually initialize the caller's slot with the right address
|
|
||||||
*stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
|
|
||||||
|
|
||||||
// Run the scope for one iteration to initialize it
|
|
||||||
new_component.run_scope()?;
|
|
||||||
|
|
||||||
// Navigate the diff machine to the right point in the output dom
|
|
||||||
diff_machine.change_list.load_known_root(id);
|
|
||||||
|
|
||||||
// And then run the diff algorithm
|
|
||||||
diff_machine
|
|
||||||
.diff_node(new_component.old_frame(), new_component.next_frame());
|
|
||||||
|
|
||||||
// Finally, insert this node as a seen node.
|
|
||||||
seen_nodes.insert(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A component has remained in the same location but its properties have changed
|
|
||||||
// We need to process this component and then dump the output lifecycle events into the queue
|
|
||||||
LifeCycleEvent::PropsChanged {
|
|
||||||
caller,
|
|
||||||
root_id,
|
|
||||||
stable_scope_addr,
|
|
||||||
} => {
|
|
||||||
log::debug!("Updating a component after its props have changed");
|
|
||||||
|
|
||||||
// Get the stable index to the target component
|
|
||||||
// This *should* exist due to guarantees in the diff algorithm
|
|
||||||
let idx = stable_scope_addr
|
|
||||||
.upgrade()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
.borrow()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Grab out that component
|
|
||||||
let component = self.components.try_get_mut(idx).unwrap();
|
|
||||||
|
|
||||||
// We have to move the caller over or running the scope will fail
|
|
||||||
component.update_caller(caller);
|
|
||||||
|
|
||||||
// Run the scope
|
|
||||||
component.run_scope()?;
|
|
||||||
|
|
||||||
// Navigate the diff machine to the right point in the output dom
|
|
||||||
diff_machine.change_list.load_known_root(root_id);
|
|
||||||
|
|
||||||
// And then run the diff algorithm
|
|
||||||
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
|
||||||
|
|
||||||
// Finally, insert this node as a seen node.
|
|
||||||
seen_nodes.insert(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A component's parent has updated, but its properties did not change.
|
|
||||||
// This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
|
|
||||||
LifeCycleEvent::SameProps {
|
|
||||||
caller,
|
|
||||||
stable_scope_addr,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
// In this case, the parent made a new VNode that resulted in the same props for us
|
|
||||||
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
|
||||||
log::debug!("Received the same props");
|
|
||||||
|
|
||||||
// Get the stable index to the target component
|
|
||||||
// This *should* exist due to guarantees in the diff algorithm
|
|
||||||
let idx = stable_scope_addr
|
|
||||||
.upgrade()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
.borrow()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Grab out that component
|
|
||||||
let component = self.components.try_get_mut(idx).unwrap();
|
|
||||||
|
|
||||||
// We have to move the caller over or running the scope will fail
|
|
||||||
component.update_caller(caller);
|
|
||||||
|
|
||||||
// This time, we will not add it to our seen nodes since we did not actually run it
|
|
||||||
}
|
|
||||||
|
|
||||||
LifeCycleEvent::Remove {
|
|
||||||
root_id,
|
|
||||||
stable_scope_addr,
|
|
||||||
} => {
|
|
||||||
let id = stable_scope_addr
|
|
||||||
.upgrade()
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
.borrow()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
log::warn!("Removing node {:#?}", id);
|
|
||||||
|
|
||||||
// This would normally be recursive but makes sense to do linear to
|
|
||||||
let mut children_to_remove = VecDeque::new();
|
|
||||||
children_to_remove.push_back(id);
|
|
||||||
|
|
||||||
// Accumulate all the child components that need to be removed
|
|
||||||
while let Some(child_id) = children_to_remove.pop_back() {
|
|
||||||
let comp = self.components.try_get(child_id).unwrap();
|
|
||||||
let children = comp.descendents.borrow();
|
|
||||||
for child in children.iter() {
|
|
||||||
children_to_remove.push_front(*child);
|
|
||||||
}
|
|
||||||
log::debug!("Removing component: {:#?}", child_id);
|
|
||||||
self.components
|
|
||||||
.with(|components| components.remove(child_id).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LifeCycleEvent::Replace {
|
|
||||||
caller,
|
|
||||||
root_id: id,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
unimplemented!("This feature (Replace) is unimplemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -489,7 +332,7 @@ pub struct Scope {
|
|||||||
|
|
||||||
// IDs of children that this scope has created
|
// IDs of children that this scope has created
|
||||||
// This enables us to drop the children and their children when this scope is destroyed
|
// This enables us to drop the children and their children when this scope is destroyed
|
||||||
descendents: RefCell<HashSet<ScopeIdx>>,
|
pub(crate) descendents: RefCell<HashSet<ScopeIdx>>,
|
||||||
|
|
||||||
child_nodes: &'static [VNode<'static>],
|
child_nodes: &'static [VNode<'static>],
|
||||||
|
|
||||||
@@ -507,8 +350,8 @@ pub struct Scope {
|
|||||||
pub event_channel: Rc<dyn Fn() + 'static>,
|
pub event_channel: Rc<dyn Fn() + 'static>,
|
||||||
|
|
||||||
// pub event_queue: EventQueue,
|
// pub event_queue: EventQueue,
|
||||||
pub caller: Weak<OpaqueComponent<'static>>,
|
pub caller: Weak<OpaqueComponent>,
|
||||||
|
// pub caller: Weak<OpaqueComponent<'static>>,
|
||||||
pub hookidx: RefCell<usize>,
|
pub hookidx: RefCell<usize>,
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
@@ -542,8 +385,9 @@ impl Scope {
|
|||||||
//
|
//
|
||||||
// Scopes cannot be made anywhere else except for this file
|
// Scopes cannot be made anywhere else except for this file
|
||||||
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
||||||
fn new<'creator_node>(
|
pub fn new<'creator_node>(
|
||||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
caller: Weak<OpaqueComponent>,
|
||||||
|
// caller: Weak<OpaqueComponent<'creator_node>>,
|
||||||
arena_idx: ScopeIdx,
|
arena_idx: ScopeIdx,
|
||||||
parent: Option<ScopeIdx>,
|
parent: Option<ScopeIdx>,
|
||||||
height: u32,
|
height: u32,
|
||||||
@@ -569,8 +413,10 @@ impl Scope {
|
|||||||
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
|
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
|
||||||
let caller = unsafe {
|
let caller = unsafe {
|
||||||
std::mem::transmute::<
|
std::mem::transmute::<
|
||||||
Weak<OpaqueComponent<'creator_node>>,
|
Weak<OpaqueComponent>,
|
||||||
Weak<OpaqueComponent<'static>>,
|
Weak<OpaqueComponent>,
|
||||||
|
// Weak<OpaqueComponent<'creator_node>>,
|
||||||
|
// Weak<OpaqueComponent<'static>>,
|
||||||
>(caller)
|
>(caller)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -591,11 +437,14 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent>) {
|
||||||
|
// pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||||
let broken_caller = unsafe {
|
let broken_caller = unsafe {
|
||||||
std::mem::transmute::<
|
std::mem::transmute::<
|
||||||
Weak<OpaqueComponent<'creator_node>>,
|
Weak<OpaqueComponent>,
|
||||||
Weak<OpaqueComponent<'static>>,
|
Weak<OpaqueComponent>,
|
||||||
|
// Weak<OpaqueComponent<'creator_node>>,
|
||||||
|
// Weak<OpaqueComponent<'static>>,
|
||||||
>(caller)
|
>(caller)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -627,8 +476,10 @@ impl Scope {
|
|||||||
.ok_or(Error::FatalInternal("Failed to get caller"))?;
|
.ok_or(Error::FatalInternal("Failed to get caller"))?;
|
||||||
|
|
||||||
// Cast the caller ptr from static to one with our own reference
|
// Cast the caller ptr from static to one with our own reference
|
||||||
let c2: &OpaqueComponent<'static> = caller.as_ref();
|
let c2: &OpaqueComponent = caller.as_ref();
|
||||||
let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) };
|
let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) };
|
||||||
|
// let c2: &OpaqueComponent<'static> = caller.as_ref();
|
||||||
|
// let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) };
|
||||||
|
|
||||||
let unsafe_head = unsafe { self.own_vnodes(c3) };
|
let unsafe_head = unsafe { self.own_vnodes(c3) };
|
||||||
|
|
||||||
@@ -638,7 +489,7 @@ impl Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is its own function so we can preciesly control how lifetimes flow
|
// this is its own function so we can preciesly control how lifetimes flow
|
||||||
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> {
|
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> {
|
||||||
let new_head: VNode<'a> = f(self);
|
let new_head: VNode<'a> = f(self);
|
||||||
let out: VNode<'static> = std::mem::transmute(new_head);
|
let out: VNode<'static> = std::mem::transmute(new_head);
|
||||||
out
|
out
|
||||||
@@ -956,22 +807,25 @@ Any function prefixed with "use" should not be called conditionally.
|
|||||||
|
|
||||||
// We actually allocate the properties for components in their parent's properties
|
// We actually allocate the properties for components in their parent's properties
|
||||||
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
||||||
pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
|
pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Default)]
|
#[derive(PartialEq, Debug, Clone, Default)]
|
||||||
pub(crate) struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
|
pub struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
|
||||||
|
|
||||||
impl EventQueue {
|
impl EventQueue {
|
||||||
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
|
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
|
||||||
let inner = self.clone();
|
let inner = self.clone();
|
||||||
let marker = HeightMarker { height, idx };
|
let marker = HeightMarker { height, idx };
|
||||||
Rc::new(move || inner.0.as_ref().borrow_mut().push(marker))
|
Rc::new(move || {
|
||||||
|
log::debug!("channel updated {:#?}", marker);
|
||||||
|
inner.0.as_ref().borrow_mut().push(marker)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A helper type that lets scopes be ordered by their height
|
/// A helper type that lets scopes be ordered by their height
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) struct HeightMarker {
|
pub struct HeightMarker {
|
||||||
pub idx: ScopeIdx,
|
pub idx: ScopeIdx,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
@@ -1047,20 +901,20 @@ impl ActiveFrame {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||||
Self {
|
Self {
|
||||||
generation: 0.into(),
|
generation: 0.into(),
|
||||||
frames: [a, b],
|
frames: [a, b],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cur_frame(&self) -> &BumpFrame {
|
pub fn cur_frame(&self) -> &BumpFrame {
|
||||||
match *self.generation.borrow() & 1 == 0 {
|
match *self.generation.borrow() & 1 == 0 {
|
||||||
true => &self.frames[0],
|
true => &self.frames[0],
|
||||||
false => &self.frames[1],
|
false => &self.frames[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
match *self.generation.borrow() & 1 == 0 {
|
match *self.generation.borrow() & 1 == 0 {
|
||||||
true => &mut self.frames[0],
|
true => &mut self.frames[0],
|
||||||
false => &mut self.frames[1],
|
false => &mut self.frames[1],
|
||||||
@@ -1095,7 +949,7 @@ impl ActiveFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next(&mut self) -> &mut BumpFrame {
|
pub fn next(&mut self) -> &mut BumpFrame {
|
||||||
*self.generation.borrow_mut() += 1;
|
*self.generation.borrow_mut() += 1;
|
||||||
|
|
||||||
if *self.generation.borrow() % 2 == 0 {
|
if *self.generation.borrow() % 2 == 0 {
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ mod root {
|
|||||||
|
|
||||||
mod hooks {
|
mod hooks {
|
||||||
use super::*;
|
use super::*;
|
||||||
use dioxus_core::{hooks::use_ref, prelude::Context, scope::Scoped};
|
use dioxus_core::{hooks::use_ref, prelude::Context, virtual_dom::Scoped};
|
||||||
|
|
||||||
pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
|
pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
|
||||||
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
|
ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
|
||||||
|
|||||||
Reference in new issue
Block a user