wip: merge in some code from the other branch
This commit is contained in:
1 parent
795a54a2e4
commit
7790750349
12 files changed
+782
-124
No files matched your search
@@ -2,8 +2,9 @@ fn main() {}
|
|||||||
|
|
||||||
pub mod dioxus {
|
pub mod dioxus {
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub trait Properties {
|
pub unsafe trait Properties {
|
||||||
type Builder;
|
type Builder;
|
||||||
|
const CAN_BE_MEMOIZED: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -679,8 +679,9 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
|
unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
|
||||||
type Builder = #builder_name #generics_with_empty;
|
type Builder = #builder_name #generics_with_empty;
|
||||||
|
const CAN_BE_MEMOIZED: bool = true;
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
#name::builder()
|
#name::builder()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use generational_arena::Arena;
|
|||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ScopeArena(Rc<RefCell<ScopeArenaInner>>);
|
pub struct ScopeArena(pub Rc<RefCell<ScopeArenaInner>>);
|
||||||
|
|
||||||
struct ScopeArenaInner {
|
pub struct ScopeArenaInner {
|
||||||
pub(crate) arena: UnsafeCell<Arena<Scope>>,
|
pub(crate) arena: UnsafeCell<Arena<Scope>>,
|
||||||
locks: HashMap<ScopeIdx, MutStatus>,
|
locks: HashMap<ScopeIdx, MutStatus>,
|
||||||
}
|
}
|
||||||
@@ -61,6 +61,13 @@ impl ScopeArena {
|
|||||||
// todo!()
|
// todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn try_remove(&mut self, id: ScopeIdx) -> Result<Scope> {
|
||||||
|
let inner = unsafe { &mut *self.0.borrow().arena.get() };
|
||||||
|
inner
|
||||||
|
.remove(id)
|
||||||
|
.ok_or_else(|| Error::FatalInternal("Scope not found"))
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn inner_unchecked<'s>() -> &'s mut Arena<Scope> {
|
unsafe fn inner_unchecked<'s>() -> &'s mut Arena<Scope> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,9 @@ use crate::innerlude::FC;
|
|||||||
|
|
||||||
pub type ScopeIdx = generational_arena::Index;
|
pub type ScopeIdx = generational_arena::Index;
|
||||||
|
|
||||||
pub trait Properties: PartialEq {
|
pub unsafe trait Properties: PartialEq {
|
||||||
type Builder;
|
type Builder;
|
||||||
|
const CAN_BE_MEMOIZED: bool;
|
||||||
fn builder() -> Self::Builder;
|
fn builder() -> Self::Builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +22,8 @@ impl EmptyBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Properties for () {
|
unsafe impl Properties for () {
|
||||||
|
const CAN_BE_MEMOIZED: bool = true;
|
||||||
type Builder = EmptyBuilder;
|
type Builder = EmptyBuilder;
|
||||||
|
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
|
|||||||
+47
-45
@@ -120,7 +120,7 @@ impl<'a> DiffMachine<'a> {
|
|||||||
When re-entering, we reuse the EditList in DiffState
|
When re-entering, we reuse the EditList in DiffState
|
||||||
*/
|
*/
|
||||||
match (old, new) {
|
match (old, new) {
|
||||||
(VNode::Text(VText { text: old_text }), VNode::Text(VText { 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();
|
||||||
self.change_list.set_text(new_text);
|
self.change_list.set_text(new_text);
|
||||||
@@ -157,30 +157,32 @@ impl<'a> DiffMachine<'a> {
|
|||||||
// self.change_list.commit_traversal();
|
// self.change_list.commit_traversal();
|
||||||
if cold.user_fc == cnew.user_fc {
|
if cold.user_fc == cnew.user_fc {
|
||||||
// todo: create a stable addr
|
// todo: create a stable addr
|
||||||
let caller = Rc::downgrade(&cnew.caller);
|
// let caller = Rc::downgrade(&cnew.caller);
|
||||||
let id = cold.stable_addr.borrow().unwrap();
|
// let id = cold.stable_addr.borrow().unwrap();
|
||||||
*cnew.stable_addr.borrow_mut() = Some(id);
|
// *cnew.stable_addr.borrow_mut() = Some(id);
|
||||||
*cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
|
// *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
|
||||||
|
|
||||||
let scope = Rc::downgrade(&cold.ass_scope);
|
// let scope = Rc::downgrade(&cold.ass_scope);
|
||||||
self.lifecycle_events
|
todo!()
|
||||||
.push_back(LifeCycleEvent::PropsChanged {
|
// self.lifecycle_events
|
||||||
caller,
|
// .push_back(LifeCycleEvent::PropsChanged {
|
||||||
root_id: id,
|
// caller,
|
||||||
stable_scope_addr: scope,
|
// root_id: id,
|
||||||
});
|
// stable_scope_addr: scope,
|
||||||
|
// });
|
||||||
} else {
|
} else {
|
||||||
let caller = Rc::downgrade(&cnew.caller);
|
// let caller = Rc::downgrade(&cnew.caller);
|
||||||
let id = cold.stable_addr.borrow().unwrap();
|
// let id = cold.stable_addr.borrow().unwrap();
|
||||||
let old_scope = Rc::downgrade(&cold.ass_scope);
|
// let old_scope = Rc::downgrade(&cold.ass_scope);
|
||||||
let new_scope = Rc::downgrade(&cnew.ass_scope);
|
// let new_scope = Rc::downgrade(&cnew.ass_scope);
|
||||||
|
|
||||||
self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
todo!()
|
||||||
caller,
|
// self.lifecycle_events.push_back(LifeCycleEvent::Replace {
|
||||||
root_id: id,
|
// caller,
|
||||||
old_scope,
|
// root_id: id,
|
||||||
new_scope,
|
// old_scope,
|
||||||
});
|
// new_scope,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +219,7 @@ impl<'a> DiffMachine<'a> {
|
|||||||
fn create(&mut self, node: &VNode<'a>) {
|
fn create(&mut self, node: &VNode<'a>) {
|
||||||
debug_assert!(self.change_list.traversal_is_committed());
|
debug_assert!(self.change_list.traversal_is_committed());
|
||||||
match node {
|
match node {
|
||||||
VNode::Text(VText { text }) => {
|
VNode::Text(text) => {
|
||||||
self.change_list.create_text_node(text);
|
self.change_list.create_text_node(text);
|
||||||
}
|
}
|
||||||
VNode::Element(&VElement {
|
VNode::Element(&VElement {
|
||||||
@@ -252,7 +254,7 @@ impl<'a> DiffMachine<'a> {
|
|||||||
// text content, and finally (3) append the text node to this
|
// text content, and finally (3) append the text node to this
|
||||||
// parent.
|
// parent.
|
||||||
if children.len() == 1 {
|
if children.len() == 1 {
|
||||||
if let VNode::Text(VText { text }) = children[0] {
|
if let VNode::Text(text) = children[0] {
|
||||||
self.change_list.set_text(text);
|
self.change_list.set_text(text);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -268,18 +270,20 @@ impl<'a> DiffMachine<'a> {
|
|||||||
todo: integrate re-entrace
|
todo: integrate re-entrace
|
||||||
*/
|
*/
|
||||||
VNode::Component(component) => {
|
VNode::Component(component) => {
|
||||||
self.change_list
|
todo!()
|
||||||
.create_text_node("placeholder for vcomponent");
|
// self.change_list
|
||||||
|
// .create_text_node("placeholder for vcomponent");
|
||||||
|
|
||||||
let id = get_id();
|
// let id = get_id();
|
||||||
*component.stable_addr.as_ref().borrow_mut() = Some(id);
|
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
|
||||||
self.change_list.save_known_root(id);
|
// self.change_list.save_known_root(id);
|
||||||
let scope = Rc::downgrade(&component.ass_scope);
|
// let scope = Rc::downgrade(&component.ass_scope);
|
||||||
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
// todo!()
|
||||||
caller: Rc::downgrade(&component.caller),
|
// self.lifecycle_events.push_back(LifeCycleEvent::Mount {
|
||||||
root_id: id,
|
// caller: Rc::downgrade(&component.caller),
|
||||||
stable_scope_addr: scope,
|
// root_id: id,
|
||||||
});
|
// stable_scope_addr: scope,
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
VNode::Suspended => {
|
VNode::Suspended => {
|
||||||
todo!("Creation of VNode::Suspended not yet supported")
|
todo!("Creation of VNode::Suspended not yet supported")
|
||||||
@@ -422,14 +426,11 @@ impl<'a> DiffMachine<'a> {
|
|||||||
|
|
||||||
if new.len() == 1 {
|
if new.len() == 1 {
|
||||||
match (old.first(), &new[0]) {
|
match (old.first(), &new[0]) {
|
||||||
(
|
(Some(&VNode::Text(old_text)), &VNode::Text(new_text)) if old_text == new_text => {
|
||||||
Some(&VNode::Text(VText { text: old_text })),
|
|
||||||
&VNode::Text(VText { text: new_text }),
|
|
||||||
) if old_text == new_text => {
|
|
||||||
// Don't take this fast path...
|
// Don't take this fast path...
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, &VNode::Text(VText { 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 {
|
// for o in old {
|
||||||
@@ -979,11 +980,12 @@ impl<'a> DiffMachine<'a> {
|
|||||||
// self.change_list
|
// self.change_list
|
||||||
// .create_text_node("placeholder for vcomponent");
|
// .create_text_node("placeholder for vcomponent");
|
||||||
|
|
||||||
let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
|
todo!()
|
||||||
self.lifecycle_events.push_back(LifeCycleEvent::Remove {
|
// let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
|
||||||
root_id,
|
// self.lifecycle_events.push_back(LifeCycleEvent::Remove {
|
||||||
stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
|
// root_id,
|
||||||
})
|
// stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
|
||||||
|
// })
|
||||||
// let id = get_id();
|
// let id = get_id();
|
||||||
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
|
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
|
||||||
// self.change_list.save_known_root(id);
|
// self.change_list.save_known_root(id);
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ 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;
|
||||||
|
|
||||||
// types used internally that are important
|
// types used internally that are important
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
|
|||||||
+45
-51
@@ -27,7 +27,7 @@ pub enum VNode<'src> {
|
|||||||
Element(&'src VElement<'src>),
|
Element(&'src VElement<'src>),
|
||||||
|
|
||||||
/// A text node (node type `TEXT_NODE`).
|
/// A text node (node type `TEXT_NODE`).
|
||||||
Text(VText<'src>),
|
Text(&'src str),
|
||||||
|
|
||||||
/// A fragment is a "virtual position" in the DOM
|
/// A fragment is a "virtual position" in the DOM
|
||||||
/// Fragments may have children and keys
|
/// Fragments may have children and keys
|
||||||
@@ -46,11 +46,11 @@ pub enum VNode<'src> {
|
|||||||
impl<'a> Clone for VNode<'a> {
|
impl<'a> Clone for VNode<'a> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
VNode::Element(el) => VNode::Element(el),
|
VNode::Element(element) => VNode::Element(element),
|
||||||
VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
|
VNode::Text(text) => VNode::Text(text),
|
||||||
VNode::Fragment(frag) => VNode::Fragment(frag),
|
VNode::Fragment(fragment) => VNode::Fragment(fragment),
|
||||||
|
VNode::Component(component) => VNode::Component(component),
|
||||||
VNode::Suspended => VNode::Suspended,
|
VNode::Suspended => VNode::Suspended,
|
||||||
VNode::Component(c) => VNode::Component(c),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl<'a> VNode<'a> {
|
|||||||
/// Construct a new text node with the given text.
|
/// Construct a new text node with the given text.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn text(text: &'a str) -> VNode<'a> {
|
pub fn text(text: &'a str) -> VNode<'a> {
|
||||||
VNode::Text(VText { text })
|
VNode::Text(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> {
|
pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> {
|
||||||
@@ -210,47 +210,31 @@ impl<'a> NodeKey<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub struct VText<'bump> {
|
|
||||||
pub text: &'bump str,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> Clone for VText<'b> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self { text: self.text }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> VText<'a> {
|
|
||||||
// / Create an new `VText` instance with the specified text.
|
|
||||||
pub fn new(text: &'a str) -> Self {
|
|
||||||
VText { text: text.into() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==============================
|
// ==============================
|
||||||
// Custom components
|
// Custom components
|
||||||
// ==============================
|
// ==============================
|
||||||
|
|
||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
pub type StableScopeAddres = RefCell<Option<u32>>;
|
pub type StableScopeAddres = Option<u32>;
|
||||||
pub type VCompAssociatedScope = RefCell<Option<ScopeIdx>>;
|
pub type VCompAssociatedScope = Option<ScopeIdx>;
|
||||||
|
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
pub key: NodeKey<'src>,
|
pub key: NodeKey<'src>,
|
||||||
|
|
||||||
pub stable_addr: Rc<StableScopeAddres>,
|
pub stable_addr: RefCell<StableScopeAddres>,
|
||||||
pub ass_scope: Rc<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 + 'src>,
|
||||||
|
|
||||||
pub children: &'src [VNode<'src>],
|
pub children: &'src [VNode<'src>],
|
||||||
|
|
||||||
|
pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
||||||
|
|
||||||
// a pointer into the bump arena (given by the 'src lifetime)
|
// a pointer into the bump arena (given by the 'src lifetime)
|
||||||
// raw_props: Box<dyn Any>,
|
// raw_props: Box<dyn Any>,
|
||||||
// raw_props: *const (),
|
raw_props: *const (),
|
||||||
|
|
||||||
// a pointer to the raw fn typ
|
// a pointer to the raw fn typ
|
||||||
pub user_fc: *const (),
|
pub user_fc: *const (),
|
||||||
@@ -264,7 +248,8 @@ impl<'a> VComponent<'a> {
|
|||||||
// TODO: lift the requirement that props need to be static
|
// TODO: lift the requirement that props need to be static
|
||||||
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
|
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
|
||||||
|
|
||||||
pub fn new<P: Properties>(
|
pub fn new<P: Properties + 'a>(
|
||||||
|
bump: &'a Bump,
|
||||||
component: FC<P>,
|
component: FC<P>,
|
||||||
// props: bumpalo::boxed::Box<'a, P>,
|
// props: bumpalo::boxed::Box<'a, P>,
|
||||||
props: P,
|
props: P,
|
||||||
@@ -273,25 +258,32 @@ impl<'a> VComponent<'a> {
|
|||||||
// pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
// pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
||||||
// let bad_props = unsafe { transmogrify(props) };
|
// let bad_props = unsafe { transmogrify(props) };
|
||||||
let caller_ref = component as *const ();
|
let caller_ref = component as *const ();
|
||||||
|
let props = bump.alloc(props);
|
||||||
|
|
||||||
// let raw_props = props as *const P as *const ();
|
let raw_props = props as *const P as *const ();
|
||||||
|
|
||||||
// let props_comparator = move |other: &VComponent| {
|
let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
|
||||||
// // Safety:
|
if P::CAN_BE_MEMOIZED {
|
||||||
// // We are guaranteed that the props will be of the same type because
|
Some(bump.alloc(move |other: &VComponent| {
|
||||||
// // there is no way to create a VComponent other than this `new` method.
|
// Safety:
|
||||||
// //
|
// We are guaranteed that the props will be of the same type because
|
||||||
// // Therefore, if the render functions are identical (by address), then so will be
|
// there is no way to create a VComponent other than this `new` method.
|
||||||
// // props type paramter (because it is the same render function). Therefore, we can be
|
//
|
||||||
// // sure
|
// Therefore, if the render functions are identical (by address), then so will be
|
||||||
// if caller_ref == other.user_fc {
|
// props type paramter (because it is the same render function). Therefore, we can be
|
||||||
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
// sure
|
||||||
// // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
if caller_ref == other.user_fc {
|
||||||
// &props == g
|
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
||||||
// } else {
|
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||||
// false
|
&props == &real_other
|
||||||
// }
|
} else {
|
||||||
// };
|
false
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// let prref: &'a P = props.as_ref();
|
// let prref: &'a P = props.as_ref();
|
||||||
|
|
||||||
@@ -313,16 +305,18 @@ impl<'a> VComponent<'a> {
|
|||||||
None => NodeKey(None),
|
None => NodeKey(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// raw_props: Box::new(props),
|
||||||
|
// comparator: Rc::new(props_comparator),
|
||||||
Self {
|
Self {
|
||||||
key,
|
key,
|
||||||
ass_scope: Rc::new(RefCell::new(None)),
|
ass_scope: RefCell::new(None),
|
||||||
user_fc: caller_ref,
|
user_fc: caller_ref,
|
||||||
// raw_props: Box::new(props),
|
comparator,
|
||||||
|
raw_props,
|
||||||
_p: PhantomData,
|
_p: PhantomData,
|
||||||
children: &[],
|
children: &[],
|
||||||
caller,
|
caller,
|
||||||
// comparator: Rc::new(props_comparator),
|
stable_addr: RefCell::new(None),
|
||||||
stable_addr: Rc::new(RefCell::new(None)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ pub struct EditMachine<'lock> {
|
|||||||
pub traversal: Traversal,
|
pub traversal: Traversal,
|
||||||
next_temporary: u32,
|
next_temporary: u32,
|
||||||
forcing_new_listeners: bool,
|
forcing_new_listeners: bool,
|
||||||
|
pub cur_height: u32,
|
||||||
|
|
||||||
// // if the current node is a "known" node
|
// // if the current node is a "known" node
|
||||||
// // any actions that modify this node should update the mapping
|
// // any actions that modify this node should update the mapping
|
||||||
@@ -158,6 +159,7 @@ impl<'lock> EditMachine<'lock> {
|
|||||||
Self {
|
Self {
|
||||||
// current_known: None,
|
// current_known: None,
|
||||||
traversal: Traversal::new(),
|
traversal: Traversal::new(),
|
||||||
|
cur_height: 0,
|
||||||
next_temporary: 0,
|
next_temporary: 0,
|
||||||
forcing_new_listeners: false,
|
forcing_new_listeners: false,
|
||||||
emitter: EditList::<'lock>::default(),
|
emitter: EditList::<'lock>::default(),
|
||||||
|
|||||||
@@ -0,0 +1,488 @@
|
|||||||
|
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)
|
||||||
|
},
|
||||||
|
|_| {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
pub use crate::scope::*;
|
||||||
|
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},
|
||||||
|
};
|
||||||
|
// 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"
|
||||||
|
pub(crate) type OpaqueComponent<'e> = dyn Fn(&'e Scope) -> VNode<'e> + 'e;
|
||||||
|
// pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone, Default)]
|
||||||
|
pub(crate) struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
|
||||||
|
|
||||||
|
impl EventQueue {
|
||||||
|
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
|
||||||
|
let inner = self.clone();
|
||||||
|
let marker = HeightMarker { height, idx };
|
||||||
|
Rc::new(move || inner.0.as_ref().borrow_mut().push(marker))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A helper type that lets scopes be ordered by their height
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) struct HeightMarker {
|
||||||
|
pub idx: ScopeIdx,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for HeightMarker {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.height.cmp(&other.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for HeightMarker {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NodeCtx is used to build VNodes in the component's memory space.
|
||||||
|
// This struct adds metadata to the final VNode about listeners, attributes, and children
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NodeCtx<'a> {
|
||||||
|
pub scope_ref: &'a Scope,
|
||||||
|
pub listener_id: RefCell<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> NodeCtx<'a> {
|
||||||
|
pub fn bump(&self) -> &'a Bump {
|
||||||
|
&self.scope_ref.cur_frame().bump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for NodeCtx<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Hash)]
|
||||||
|
pub struct ContextId {
|
||||||
|
// Which component is the scope in
|
||||||
|
original: ScopeIdx,
|
||||||
|
|
||||||
|
// What's the height of the scope
|
||||||
|
height: u32,
|
||||||
|
|
||||||
|
// Which scope is it (in order)
|
||||||
|
id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ActiveFrame {
|
||||||
|
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
|
||||||
|
pub generation: RefCell<usize>,
|
||||||
|
|
||||||
|
// The double-buffering situation that we will use
|
||||||
|
pub frames: [BumpFrame; 2],
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BumpFrame {
|
||||||
|
pub bump: Bump,
|
||||||
|
pub head_node: VNode<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveFrame {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::from_frames(
|
||||||
|
BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
},
|
||||||
|
BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||||
|
Self {
|
||||||
|
generation: 0.into(),
|
||||||
|
frames: [a, b],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cur_frame(&self) -> &BumpFrame {
|
||||||
|
match *self.generation.borrow() & 1 == 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
false => &self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||||
|
match *self.generation.borrow() & 1 == 0 {
|
||||||
|
true => &mut self.frames[0],
|
||||||
|
false => &mut self.frames[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
|
let raw_node = match *self.generation.borrow() & 1 == 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
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 prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||||
|
let raw_node = match *self.generation.borrow() & 1 != 0 {
|
||||||
|
true => &self.frames[0],
|
||||||
|
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(crate) fn next(&mut self) -> &mut BumpFrame {
|
||||||
|
*self.generation.borrow_mut() += 1;
|
||||||
|
|
||||||
|
if *self.generation.borrow() % 2 == 0 {
|
||||||
|
&mut self.frames[0]
|
||||||
|
} else {
|
||||||
|
&mut self.frames[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -191,10 +191,6 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// Ok(diff_machine.consume())
|
// Ok(diff_machine.consume())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base_scope(&self) -> &Scope {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ======================================
|
// ======================================
|
||||||
|
|||||||
+15
-18
@@ -32,7 +32,7 @@ mod traits {
|
|||||||
// Atoms, selectors, and their family variants are readable
|
// Atoms, selectors, and their family variants are readable
|
||||||
pub trait Readable<T: AtomValue>: Sized + Copy {
|
pub trait Readable<T: AtomValue>: Sized + Copy {
|
||||||
fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
|
fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
|
||||||
hooks::use_read(ctx, self)
|
hooks::use_read(&ctx, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This returns a future of the value
|
// This returns a future of the value
|
||||||
@@ -95,9 +95,9 @@ mod atoms {
|
|||||||
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
|
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
|
||||||
|
|
||||||
// ensure that atoms are both read and write
|
// ensure that atoms are both read and write
|
||||||
let _ = use_read(ctx, &EXAMPLE_ATOM);
|
let _ = use_read(&ctx, &EXAMPLE_ATOM);
|
||||||
let _ = use_read_write(ctx, &EXAMPLE_ATOM);
|
let _ = use_read_write(&ctx, &EXAMPLE_ATOM);
|
||||||
let _ = use_write(ctx, &EXAMPLE_ATOM);
|
let _ = use_write(&ctx, &EXAMPLE_ATOM);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ mod atomfamily {
|
|||||||
|
|
||||||
fn test(ctx: Context<()>) {
|
fn test(ctx: Context<()>) {
|
||||||
let title = Titles.select(&10).use_read(ctx);
|
let title = Titles.select(&10).use_read(ctx);
|
||||||
let t2 = use_read(ctx, &Titles.select(&10));
|
let t2 = use_read(&ctx, &Titles.select(&10));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +368,7 @@ mod root {
|
|||||||
|
|
||||||
mod hooks {
|
mod hooks {
|
||||||
use super::*;
|
use super::*;
|
||||||
use dioxus_core::{hooks::use_ref, prelude::Context};
|
use dioxus_core::{hooks::use_ref, prelude::Context, scope::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()))
|
||||||
@@ -381,12 +381,12 @@ mod hooks {
|
|||||||
///
|
///
|
||||||
/// You can use this method to create controllers that perform much more complex actions than set/get
|
/// You can use this method to create controllers that perform much more complex actions than set/get
|
||||||
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
|
/// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
|
||||||
pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc<RecoilContext> {
|
pub fn use_recoil_api<'a>(ctx: &impl Scoped<'a>) -> &'a Rc<RecoilContext> {
|
||||||
ctx.use_context::<RecoilContext>()
|
ctx.use_context::<RecoilContext>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_write<'a, T: AtomValue, P>(
|
pub fn use_write<'a, T: AtomValue>(
|
||||||
ctx: Context<'a, P>,
|
ctx: &impl Scoped<'a>,
|
||||||
// todo: this shouldn't need to be static
|
// todo: this shouldn't need to be static
|
||||||
writable: impl Writable<T>,
|
writable: impl Writable<T>,
|
||||||
) -> &'a Rc<dyn Fn(T)> {
|
) -> &'a Rc<dyn Fn(T)> {
|
||||||
@@ -412,10 +412,10 @@ mod hooks {
|
|||||||
/// Read the atom and get the Rc directly to the Atom's slot
|
/// Read the atom and get the Rc directly to the Atom's slot
|
||||||
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
|
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
|
||||||
/// work with as
|
/// work with as
|
||||||
pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
|
pub fn use_read_raw<'a, T: AtomValue>(
|
||||||
ctx: Context<'a, P>,
|
ctx: &impl Scoped<'a>,
|
||||||
readable: impl Readable<T>,
|
readable: impl Readable<T>,
|
||||||
) -> &Rc<T> {
|
) -> &'a Rc<T> {
|
||||||
struct ReadHook<T> {
|
struct ReadHook<T> {
|
||||||
value: Rc<T>,
|
value: Rc<T>,
|
||||||
consumer_id: u32,
|
consumer_id: u32,
|
||||||
@@ -449,10 +449,7 @@ mod hooks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
pub fn use_read<'a, T: AtomValue, P: 'static>(
|
pub fn use_read<'a, T: AtomValue>(ctx: &impl Scoped<'a>, readable: impl Readable<T>) -> &'a T {
|
||||||
ctx: Context<'a, P>,
|
|
||||||
readable: impl Readable<T>,
|
|
||||||
) -> &'a T {
|
|
||||||
use_read_raw(ctx, readable).as_ref()
|
use_read_raw(ctx, readable).as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -471,8 +468,8 @@ mod hooks {
|
|||||||
/// // equivalent to:
|
/// // equivalent to:
|
||||||
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
|
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
|
pub fn use_read_write<'a, T: AtomValue + 'static>(
|
||||||
ctx: Context<'a, P>,
|
ctx: &impl Scoped<'a>,
|
||||||
writable: impl Writable<T>,
|
writable: impl Writable<T>,
|
||||||
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
|
||||||
(use_read(ctx, writable), use_write(ctx, writable))
|
(use_read(ctx, writable), use_write(ctx, writable))
|
||||||
|
|||||||
Reference in new issue
Block a user