wip: merge in some code from the other branch

This commit is contained in:
Jonathan Kelley 2021-06-05 23:47:54 -04:00
parent 795a54a2e4
commit 7790750349
12 changed files with 782 additions and 124 deletions

View file

@ -2,8 +2,9 @@ fn main() {}
pub mod dioxus {
pub mod prelude {
pub trait Properties {
pub unsafe trait Properties {
type Builder;
const CAN_BE_MEMOIZED: bool;
fn builder() -> Self::Builder;
}
}

View file

@ -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;
const CAN_BE_MEMOIZED: bool = true;
fn builder() -> Self::Builder {
#name::builder()
}

View file

@ -9,9 +9,9 @@ use generational_arena::Arena;
use crate::innerlude::*;
#[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>>,
locks: HashMap<ScopeIdx, MutStatus>,
}
@ -61,6 +61,13 @@ impl ScopeArena {
// 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> {
todo!()
}

View file

@ -9,8 +9,9 @@ use crate::innerlude::FC;
pub type ScopeIdx = generational_arena::Index;
pub trait Properties: PartialEq {
pub unsafe trait Properties: PartialEq {
type Builder;
const CAN_BE_MEMOIZED: bool;
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;
fn builder() -> Self::Builder {

View file

@ -120,7 +120,7 @@ impl<'a> DiffMachine<'a> {
When re-entering, we reuse the EditList in DiffState
*/
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 {
self.change_list.commit_traversal();
self.change_list.set_text(new_text);
@ -157,30 +157,32 @@ impl<'a> DiffMachine<'a> {
// self.change_list.commit_traversal();
if cold.user_fc == cnew.user_fc {
// todo: create a stable addr
let caller = Rc::downgrade(&cnew.caller);
let id = cold.stable_addr.borrow().unwrap();
*cnew.stable_addr.borrow_mut() = Some(id);
*cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
// let caller = Rc::downgrade(&cnew.caller);
// let id = cold.stable_addr.borrow().unwrap();
// *cnew.stable_addr.borrow_mut() = Some(id);
// *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
let scope = Rc::downgrade(&cold.ass_scope);
self.lifecycle_events
.push_back(LifeCycleEvent::PropsChanged {
caller,
root_id: id,
stable_scope_addr: scope,
});
// let scope = Rc::downgrade(&cold.ass_scope);
todo!()
// self.lifecycle_events
// .push_back(LifeCycleEvent::PropsChanged {
// caller,
// root_id: id,
// stable_scope_addr: scope,
// });
} else {
let caller = Rc::downgrade(&cnew.caller);
let id = cold.stable_addr.borrow().unwrap();
let old_scope = Rc::downgrade(&cold.ass_scope);
let new_scope = Rc::downgrade(&cnew.ass_scope);
// let caller = Rc::downgrade(&cnew.caller);
// let id = cold.stable_addr.borrow().unwrap();
// let old_scope = Rc::downgrade(&cold.ass_scope);
// let new_scope = Rc::downgrade(&cnew.ass_scope);
self.lifecycle_events.push_back(LifeCycleEvent::Replace {
caller,
root_id: id,
old_scope,
new_scope,
});
todo!()
// self.lifecycle_events.push_back(LifeCycleEvent::Replace {
// caller,
// root_id: id,
// old_scope,
// new_scope,
// });
}
}
@ -217,7 +219,7 @@ impl<'a> DiffMachine<'a> {
fn create(&mut self, node: &VNode<'a>) {
debug_assert!(self.change_list.traversal_is_committed());
match node {
VNode::Text(VText { text }) => {
VNode::Text(text) => {
self.change_list.create_text_node(text);
}
VNode::Element(&VElement {
@ -252,7 +254,7 @@ impl<'a> DiffMachine<'a> {
// text content, and finally (3) append the text node to this
// parent.
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);
return;
}
@ -268,18 +270,20 @@ impl<'a> DiffMachine<'a> {
todo: integrate re-entrace
*/
VNode::Component(component) => {
self.change_list
.create_text_node("placeholder for vcomponent");
todo!()
// self.change_list
// .create_text_node("placeholder for vcomponent");
let id = get_id();
*component.stable_addr.as_ref().borrow_mut() = Some(id);
self.change_list.save_known_root(id);
let scope = Rc::downgrade(&component.ass_scope);
self.lifecycle_events.push_back(LifeCycleEvent::Mount {
caller: Rc::downgrade(&component.caller),
root_id: id,
stable_scope_addr: scope,
});
// let id = get_id();
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
// self.change_list.save_known_root(id);
// let scope = Rc::downgrade(&component.ass_scope);
// todo!()
// self.lifecycle_events.push_back(LifeCycleEvent::Mount {
// caller: Rc::downgrade(&component.caller),
// root_id: id,
// stable_scope_addr: scope,
// });
}
VNode::Suspended => {
todo!("Creation of VNode::Suspended not yet supported")
@ -422,14 +426,11 @@ impl<'a> DiffMachine<'a> {
if new.len() == 1 {
match (old.first(), &new[0]) {
(
Some(&VNode::Text(VText { text: old_text })),
&VNode::Text(VText { text: new_text }),
) if old_text == new_text => {
(Some(&VNode::Text(old_text)), &VNode::Text(new_text)) if old_text == new_text => {
// Don't take this fast path...
}
(_, &VNode::Text(VText { text })) => {
(_, &VNode::Text(text)) => {
self.change_list.commit_traversal();
self.change_list.set_text(text);
// for o in old {
@ -979,11 +980,12 @@ impl<'a> DiffMachine<'a> {
// self.change_list
// .create_text_node("placeholder for vcomponent");
let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
self.lifecycle_events.push_back(LifeCycleEvent::Remove {
root_id,
stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
})
todo!()
// let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
// self.lifecycle_events.push_back(LifeCycleEvent::Remove {
// root_id,
// stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
// })
// let id = get_id();
// *component.stable_addr.as_ref().borrow_mut() = Some(id);
// self.change_list.save_known_root(id);

View file

@ -86,6 +86,8 @@ pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and su
pub mod builder {
pub use super::nodebuilder::*;
}
pub mod scope;
pub mod support;
// types used internally that are important
pub(crate) mod innerlude {

View file

@ -27,7 +27,7 @@ pub enum VNode<'src> {
Element(&'src VElement<'src>),
/// A text node (node type `TEXT_NODE`).
Text(VText<'src>),
Text(&'src str),
/// A fragment is a "virtual position" in the DOM
/// Fragments may have children and keys
@ -46,11 +46,11 @@ pub enum VNode<'src> {
impl<'a> Clone for VNode<'a> {
fn clone(&self) -> Self {
match self {
VNode::Element(el) => VNode::Element(el),
VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
VNode::Fragment(frag) => VNode::Fragment(frag),
VNode::Element(element) => VNode::Element(element),
VNode::Text(text) => VNode::Text(text),
VNode::Fragment(fragment) => VNode::Fragment(fragment),
VNode::Component(component) => VNode::Component(component),
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.
#[inline]
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> {
@ -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
// ==============================
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub type StableScopeAddres = RefCell<Option<u32>>;
pub type VCompAssociatedScope = RefCell<Option<ScopeIdx>>;
pub type StableScopeAddres = Option<u32>;
pub type VCompAssociatedScope = Option<ScopeIdx>;
pub struct VComponent<'src> {
pub key: NodeKey<'src>,
pub stable_addr: Rc<StableScopeAddres>,
pub ass_scope: Rc<VCompAssociatedScope>,
pub stable_addr: RefCell<StableScopeAddres>,
pub ass_scope: RefCell<VCompAssociatedScope>,
// pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
pub caller: Rc<dyn Fn(&Scope) -> 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)
// raw_props: Box<dyn Any>,
// raw_props: *const (),
raw_props: *const (),
// a pointer to the raw fn typ
pub user_fc: *const (),
@ -264,7 +248,8 @@ impl<'a> VComponent<'a> {
// TODO: lift the requirement that props need to be static
// 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>,
// props: bumpalo::boxed::Box<'a, 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 {
// let bad_props = unsafe { transmogrify(props) };
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| {
// // Safety:
// // We are guaranteed that the props will be of the same type because
// // there is no way to create a VComponent other than this `new` method.
// //
// // Therefore, if the render functions are identical (by address), then so will be
// // props type paramter (because it is the same render function). Therefore, we can be
// // sure
// if caller_ref == other.user_fc {
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
// // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
// &props == g
// } else {
// false
// }
// };
let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
if P::CAN_BE_MEMOIZED {
Some(bump.alloc(move |other: &VComponent| {
// Safety:
// We are guaranteed that the props will be of the same type because
// there is no way to create a VComponent other than this `new` method.
//
// Therefore, if the render functions are identical (by address), then so will be
// props type paramter (because it is the same render function). Therefore, we can be
// sure
if caller_ref == other.user_fc {
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
&props == &real_other
} else {
false
}
}))
} else {
None
}
};
// let prref: &'a P = props.as_ref();
@ -313,16 +305,18 @@ impl<'a> VComponent<'a> {
None => NodeKey(None),
};
// raw_props: Box::new(props),
// comparator: Rc::new(props_comparator),
Self {
key,
ass_scope: Rc::new(RefCell::new(None)),
ass_scope: RefCell::new(None),
user_fc: caller_ref,
// raw_props: Box::new(props),
comparator,
raw_props,
_p: PhantomData,
children: &[],
caller,
// comparator: Rc::new(props_comparator),
stable_addr: Rc::new(RefCell::new(None)),
stable_addr: RefCell::new(None),
}
}
}

View file

@ -146,6 +146,7 @@ pub struct EditMachine<'lock> {
pub traversal: Traversal,
next_temporary: u32,
forcing_new_listeners: bool,
pub cur_height: u32,
// // if the current node is a "known" node
// // any actions that modify this node should update the mapping
@ -158,6 +159,7 @@ impl<'lock> EditMachine<'lock> {
Self {
// current_known: None,
traversal: Traversal::new(),
cur_height: 0,
next_temporary: 0,
forcing_new_listeners: false,
emitter: EditList::<'lock>::default(),

488
packages/core/src/scope.rs Normal file
View file

@ -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)
},
|_| {},
)
}
}

View file

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

View file

@ -191,10 +191,6 @@ impl VirtualDom {
// Ok(diff_machine.consume())
}
pub fn base_scope(&self) -> &Scope {
todo!()
}
}
// ======================================

View file

@ -32,7 +32,7 @@ mod traits {
// Atoms, selectors, and their family variants are readable
pub trait Readable<T: AtomValue>: Sized + Copy {
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
@ -95,9 +95,9 @@ mod atoms {
const EXAMPLE_ATOM: Atom<i32> = |_| 10;
// ensure that atoms are both read and write
let _ = use_read(ctx, &EXAMPLE_ATOM);
let _ = use_read_write(ctx, &EXAMPLE_ATOM);
let _ = use_write(ctx, &EXAMPLE_ATOM);
let _ = use_read(&ctx, &EXAMPLE_ATOM);
let _ = use_read_write(&ctx, &EXAMPLE_ATOM);
let _ = use_write(&ctx, &EXAMPLE_ATOM);
}
}
}
@ -161,7 +161,7 @@ mod atomfamily {
fn test(ctx: Context<()>) {
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 {
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(())) {
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
/// 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>()
}
pub fn use_write<'a, T: AtomValue, P>(
ctx: Context<'a, P>,
pub fn use_write<'a, T: AtomValue>(
ctx: &impl Scoped<'a>,
// todo: this shouldn't need to be static
writable: impl Writable<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
/// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
/// work with as
pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
ctx: Context<'a, P>,
pub fn use_read_raw<'a, T: AtomValue>(
ctx: &impl Scoped<'a>,
readable: impl Readable<T>,
) -> &Rc<T> {
) -> &'a Rc<T> {
struct ReadHook<T> {
value: Rc<T>,
consumer_id: u32,
@ -449,10 +449,7 @@ mod hooks {
}
///
pub fn use_read<'a, T: AtomValue, P: 'static>(
ctx: Context<'a, P>,
readable: impl Readable<T>,
) -> &'a T {
pub fn use_read<'a, T: AtomValue>(ctx: &impl Scoped<'a>, readable: impl Readable<T>) -> &'a T {
use_read_raw(ctx, readable).as_ref()
}
@ -471,8 +468,8 @@ mod hooks {
/// // equivalent to:
/// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
/// ```
pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
ctx: Context<'a, P>,
pub fn use_read_write<'a, T: AtomValue + 'static>(
ctx: &impl Scoped<'a>,
writable: impl Writable<T>,
) -> (&'a T, &'a Rc<dyn Fn(T)>) {
(use_read(ctx, writable), use_write(ctx, writable))