wip: todomvc
This commit is contained in:
1 parent
0bcff1f88e
commit
ce33031519
22 files changed
+753
-203
No files matched your search
@@ -2,7 +2,7 @@ use crate::{nodebuilder::IntoDomTree, prelude::*};
|
||||
use crate::{nodebuilder::LazyNodes, nodes::VNode};
|
||||
use bumpalo::Bump;
|
||||
use hooks::Hook;
|
||||
use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::AtomicUsize};
|
||||
use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
|
||||
|
||||
/// 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.
|
||||
@@ -25,13 +25,14 @@ use std::{cell::RefCell, future::Future, ops::Deref, rc::Rc, sync::atomic::Atomi
|
||||
// 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> {
|
||||
pub idx: AtomicUsize,
|
||||
pub idx: RefCell<usize>,
|
||||
|
||||
pub scope: ScopeIdx,
|
||||
|
||||
// Borrowed from scope
|
||||
pub(crate) arena: &'src typed_arena::Arena<Hook>,
|
||||
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
// pub(crate) arena: &'src typed_arena::Arena<Hook>,
|
||||
pub(crate) hooks: &'src RefCell<Vec<Hook>>,
|
||||
// pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
pub(crate) bump: &'src Bump,
|
||||
|
||||
pub listeners: &'src RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
|
||||
@@ -114,14 +115,16 @@ impl<'a> Context<'a> {
|
||||
|
||||
/// This module provides internal state management functionality for Dioxus components
|
||||
pub mod hooks {
|
||||
use std::any::Any;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Hook(pub Box<dyn std::any::Any>);
|
||||
pub struct Hook(pub Pin<Box<dyn std::any::Any>>);
|
||||
|
||||
impl Hook {
|
||||
pub fn new(state: Box<dyn std::any::Any>) -> Self {
|
||||
Self(state)
|
||||
pub fn new<T: 'static>(state: T) -> Self {
|
||||
Self(Box::pin(state))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,28 +142,27 @@ pub mod hooks {
|
||||
// TODO: add this to the "clean up" group for when the component is dropped
|
||||
_cleanup: impl FnOnce(InternalHookState),
|
||||
) -> Output {
|
||||
let raw_hook = {
|
||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let idx = *self.idx.borrow();
|
||||
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
// Mutate hook list if necessary
|
||||
let mut hooks = self.hooks.borrow_mut();
|
||||
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
let boxed_state: Box<dyn std::any::Any> = Box::new(new_state);
|
||||
let hook = self.arena.alloc(Hook::new(boxed_state));
|
||||
// Initialize the hook by allocating it in the typed arena.
|
||||
// We get a reference from the arena which is owned by the component scope
|
||||
// This is valid because "Context" is only valid while the scope is borrowed
|
||||
if idx >= hooks.len() {
|
||||
let new_state = initializer();
|
||||
hooks.push(Hook::new(new_state));
|
||||
}
|
||||
*self.idx.borrow_mut() = 1;
|
||||
|
||||
// Push the raw pointer instead of the &mut
|
||||
// A "poor man's OwningRef"
|
||||
hooks.push(hook);
|
||||
}
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
|
||||
let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
|
||||
let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
|
||||
|
||||
*hooks.get(idx).unwrap()
|
||||
};
|
||||
// we extend the lifetime from borrowed in this scope to borrowed from self.
|
||||
// This is okay because the hook is pinned
|
||||
runner(unsafe { &mut *(internal_state as *mut _) })
|
||||
|
||||
/*
|
||||
** UNSAFETY ALERT **
|
||||
@@ -178,11 +180,17 @@ pub mod hooks {
|
||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||
*/
|
||||
let borrowed_hook: &'a mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||
// let raw_hook: &'scope mut _ = unsafe { &mut *raw_hook };
|
||||
// let p = raw_hook.0.downcast_mut::<InternalHookState>();
|
||||
// let r = p.unwrap();
|
||||
// let v = unsafe { Pin::get_unchecked_mut(raw_hook) };
|
||||
// // let carreied_ref: &'scope mut dyn Any = unsafe { &mut *v };
|
||||
// let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
|
||||
|
||||
let internal_state = borrowed_hook.0.downcast_mut::<InternalHookState>().unwrap();
|
||||
// let real_internal = unsafe { internal_state as *mut _ };
|
||||
|
||||
runner(internal_state)
|
||||
// runner(unsafe { &mut *real_internal })
|
||||
// runner(internal_state)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,6 +255,8 @@ Context should *never* be dangling!. If a Context is torn down, so should anythi
|
||||
pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
|
||||
}
|
||||
|
||||
/// # SAFETY ALERT
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
//! - [ ] use_reducer
|
||||
//! - [ ] use_effect
|
||||
|
||||
pub use new_use_state_def::use_state_new;
|
||||
pub use use_reducer_def::use_reducer;
|
||||
pub use use_ref_def::use_ref;
|
||||
pub use use_state_def::use_state;
|
||||
@@ -13,15 +14,94 @@ mod use_state_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::Display,
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
struct UseState<T: 'static> {
|
||||
new_val: Rc<RefCell<Option<T>>>,
|
||||
current_val: T,
|
||||
caller: Box<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
/// Store state between component renders!
|
||||
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||
/// When the setter is called, the component is re-ran with the new value.
|
||||
///
|
||||
/// This is behaves almost exactly the same way as React's "use_state".
|
||||
///
|
||||
/// Usage:
|
||||
/// ```ignore
|
||||
/// static Example: FC<()> = |ctx| {
|
||||
/// let (counter, set_counter) = use_state(ctx, || 0);
|
||||
/// let increment = |_| set_couter(counter + 1);
|
||||
/// let decrement = |_| set_couter(counter + 1);
|
||||
///
|
||||
/// html! {
|
||||
/// <div>
|
||||
/// <h1>"Counter: {counter}" </h1>
|
||||
/// <button onclick={increment}> "Increment" </button>
|
||||
/// <button onclick={decrement}> "Decrement" </button>
|
||||
/// </div>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> (&'a T, &'a impl Fn(T)) {
|
||||
ctx.use_hook(
|
||||
move || UseState {
|
||||
new_val: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
caller: Box::new(|_| println!("setter called!")),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("Use_state set called");
|
||||
let inner = hook.new_val.clone();
|
||||
let scheduled_update = ctx.schedule_update();
|
||||
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() {
|
||||
hook.current_val = new_val;
|
||||
}
|
||||
|
||||
// todo: swap out the caller with a subscription call and an internal update
|
||||
hook.caller = Box::new(move |new_val| {
|
||||
// update the setter with the new value
|
||||
let mut new_inner = inner.as_ref().borrow_mut();
|
||||
*new_inner = Some(new_val);
|
||||
|
||||
// Ensure the component gets updated
|
||||
scheduled_update();
|
||||
});
|
||||
|
||||
// box gets derefed into a ref which is then taken as ref with the hook
|
||||
(&hook.current_val, &hook.caller)
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
mod new_use_state_def {
|
||||
use crate::innerlude::*;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
fmt::{Debug, Display},
|
||||
ops::{Deref, DerefMut},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub struct UseState<T: 'static> {
|
||||
modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
|
||||
current_val: T,
|
||||
update: Box<dyn Fn() + 'static>,
|
||||
setter: Box<dyn Fn(T) + 'static>,
|
||||
// setter: Box<dyn Fn(T) + 'static>,
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy)]
|
||||
@@ -33,8 +113,11 @@ mod use_state_def {
|
||||
|
||||
impl<'a, T: 'static> UseState<T> {
|
||||
pub fn setter(&self) -> &dyn Fn(T) {
|
||||
todo!()
|
||||
&self.setter
|
||||
// let r = self.setter.as_mut();
|
||||
// unsafe { Pin::get_unchecked_mut(r) }
|
||||
}
|
||||
|
||||
pub fn set(&self, new_val: T) {
|
||||
self.modify(|f| *f = new_val);
|
||||
}
|
||||
@@ -84,7 +167,7 @@ mod use_state_def {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
pub fn use_state_new<'a, 'c, T: 'static, F: FnOnce() -> T>(
|
||||
ctx: &'c Context<'a>,
|
||||
initial_state_fn: F,
|
||||
) -> &'a UseState<T> {
|
||||
@@ -93,19 +176,32 @@ mod use_state_def {
|
||||
modifier: Rc::new(RefCell::new(None)),
|
||||
current_val: initial_state_fn(),
|
||||
update: Box::new(|| {}),
|
||||
setter: Box::new(|_| {}),
|
||||
},
|
||||
move |hook| {
|
||||
log::debug!("addr of hook: {:#?}", hook as *const _);
|
||||
let scheduled_update = ctx.schedule_update();
|
||||
|
||||
// log::debug!("Checking if new value {:#?}", &hook.current_val);
|
||||
// get ownership of the new val and replace the current with the new
|
||||
// -> as_ref -> borrow_mut -> deref_mut -> take
|
||||
// -> rc -> &RefCell -> RefMut -> &Option<T> -> T
|
||||
if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
|
||||
// log::debug!("setting prev {:#?}", &hook.current_val);
|
||||
(new_val)(&mut hook.current_val);
|
||||
// log::debug!("setting new value {:#?}", &hook.current_val);
|
||||
}
|
||||
|
||||
hook.update = Box::new(move || scheduled_update());
|
||||
|
||||
let modifier = hook.modifier.clone();
|
||||
hook.setter = Box::new(move |new_val: T| {
|
||||
let mut slot = modifier.as_ref().borrow_mut();
|
||||
|
||||
let slot2 = slot.deref_mut();
|
||||
*slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
|
||||
});
|
||||
|
||||
&*hook
|
||||
},
|
||||
|_| {},
|
||||
|
||||
@@ -566,6 +566,11 @@ where
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDomTree<'a> for () {
|
||||
fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
|
||||
VNode::Suspended
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterator_of_nodes<'b>() {
|
||||
@@ -646,9 +651,6 @@ pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
|
||||
let f: &'a str = contents.into_bump_str();
|
||||
VNode::text(f)
|
||||
}
|
||||
// pub fn text<'a>(contents: &'a str) -> VNode<'a> {
|
||||
// VNode::text(contents)
|
||||
// }
|
||||
|
||||
/// Construct an attribute for an element.
|
||||
///
|
||||
@@ -666,35 +668,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
|
||||
Attribute { name, value }
|
||||
}
|
||||
|
||||
// /// Create an event listener.
|
||||
// ///
|
||||
// /// `event` is the type of event to listen for, e.g. `"click"`. The `callback`
|
||||
// /// is the function that will be invoked when the event occurs.
|
||||
// ///
|
||||
// /// # Example
|
||||
// ///
|
||||
// /// ```no_run
|
||||
// /// use dioxus::{builder::*, bumpalo::Bump};
|
||||
// ///
|
||||
// /// let b = Bump::new();
|
||||
// ///
|
||||
// /// let listener = on(&b, "click", |root, vdom, event| {
|
||||
// /// // do something when a click happens...
|
||||
// /// });
|
||||
// /// ```
|
||||
// pub fn on<'a, 'b>(
|
||||
// // pub fn on<'a, F: 'static>(
|
||||
// bump: &'a Bump,
|
||||
// event: &'static str,
|
||||
// callback: impl Fn(VirtualEvent) + 'a,
|
||||
// ) -> Listener<'a> {
|
||||
// Listener {
|
||||
// event,
|
||||
// callback: bump.alloc(callback),
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC<T>, p: T) -> VNode<'a> {
|
||||
// currently concerned about if props have a custom drop implementation
|
||||
// might override it with the props macro
|
||||
let propsd: &'a mut _ = ctx.bump.alloc(p);
|
||||
VNode::Component(crate::nodes::VComponent::new(f, propsd))
|
||||
}
|
||||
@@ -36,9 +36,9 @@ pub struct Scope {
|
||||
// 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
|
||||
pub hooks: RefCell<Vec<*mut Hook>>,
|
||||
pub hooks: RefCell<Vec<Hook>>,
|
||||
|
||||
pub hook_arena: typed_arena::Arena<Hook>,
|
||||
pub hook_arena: Vec<Hook>,
|
||||
|
||||
// Unsafety:
|
||||
// - is self-refenrential and therefore needs to point into the bump
|
||||
@@ -63,7 +63,7 @@ impl Scope {
|
||||
|
||||
Self {
|
||||
caller: broken_caller,
|
||||
hook_arena: typed_arena::Arena::new(),
|
||||
hook_arena: Vec::new(),
|
||||
hooks: RefCell::new(Vec::new()),
|
||||
frames: ActiveFrame::new(),
|
||||
children: HashSet::new(),
|
||||
@@ -92,7 +92,7 @@ impl Scope {
|
||||
};
|
||||
|
||||
let ctx: Context<'b> = Context {
|
||||
arena: &self.hook_arena,
|
||||
// arena: &self.hook_arena,
|
||||
hooks: &self.hooks,
|
||||
bump: &frame.bump,
|
||||
idx: 0.into(),
|
||||
@@ -145,7 +145,7 @@ impl Scope {
|
||||
// Run the callback with the user event
|
||||
log::debug!("Running listener");
|
||||
listener(source);
|
||||
log::debug!("Running listener");
|
||||
log::debug!("Listener finished");
|
||||
|
||||
// drain all the event listeners
|
||||
// if we don't, then they'll stick around and become invalid
|
||||
@@ -203,7 +203,7 @@ impl ActiveFrame {
|
||||
}
|
||||
}
|
||||
|
||||
fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
@@ -217,7 +217,7 @@ impl ActiveFrame {
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match *self.idx.borrow() & 1 != 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
|
||||
@@ -6,6 +6,8 @@ use generational_arena::Arena;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
rc::{Rc, Weak},
|
||||
};
|
||||
use thiserror::private::AsDynError;
|
||||
@@ -19,11 +21,13 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
|
||||
pub struct VirtualDom {
|
||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
components: Arena<Scope>,
|
||||
pub components: Arena<Scope>,
|
||||
|
||||
/// The index of the root component.
|
||||
/// Will not be ready if the dom is fresh
|
||||
base_scope: ScopeIdx,
|
||||
pub base_scope: ScopeIdx,
|
||||
|
||||
pub(crate) update_schedule: Rc<RefCell<Vec<HierarchyMarker>>>,
|
||||
|
||||
// a strong allocation to the "caller" for the original props
|
||||
#[doc(hidden)]
|
||||
@@ -70,6 +74,7 @@ impl VirtualDom {
|
||||
components,
|
||||
_root_caller: root_caller,
|
||||
base_scope,
|
||||
update_schedule: Rc::new(RefCell::new(Vec::new())),
|
||||
_root_prop_type: TypeId::of::<P>(),
|
||||
}
|
||||
}
|
||||
@@ -85,13 +90,14 @@ impl VirtualDom {
|
||||
let mut diff_machine = DiffMachine::new();
|
||||
|
||||
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
|
||||
let mut component = self
|
||||
let component = self
|
||||
.components
|
||||
.get_mut(self.base_scope)
|
||||
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
|
||||
component.run_scope()?;
|
||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||
|
||||
// log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
|
||||
// chew down the the lifecycle events until all dirty nodes are computed
|
||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
||||
match event {
|
||||
@@ -119,6 +125,7 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
LifeCycleEvent::PropsChanged { caller, id, scope } => {
|
||||
log::debug!("PROPS CHANGED");
|
||||
let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
|
||||
unsafe {
|
||||
let p = &mut *(very_unsafe_components);
|
||||
@@ -131,6 +138,7 @@ impl VirtualDom {
|
||||
// break 'render;
|
||||
}
|
||||
LifeCycleEvent::SameProps { caller, id, scope } => {
|
||||
log::debug!("SAME PROPS RECEIVED");
|
||||
//
|
||||
// break 'render;
|
||||
}
|
||||
@@ -181,7 +189,15 @@ impl VirtualDom {
|
||||
.expect("Borrowing should not fail");
|
||||
|
||||
component.call_listener(event);
|
||||
component.run_scope()?;
|
||||
|
||||
/*
|
||||
-> call listener
|
||||
-> sort through accumulated queue by the top
|
||||
-> run each component, running its children
|
||||
-> add new updates to the tree of updates (these are dirty)
|
||||
->
|
||||
*/
|
||||
// component.run_scope()?;
|
||||
|
||||
// let mut diff_machine = DiffMachine::new();
|
||||
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
||||
@@ -192,3 +208,8 @@ impl VirtualDom {
|
||||
self.rebuild()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct HierarchyMarker {
|
||||
height: u32,
|
||||
}
|
||||
Reference in new issue
Block a user