Feat: wire up rebuild
This commit is contained in:
1 parent
4d01455729
commit
06ae4fc178
11 files changed
+210
-170
No files matched your search
@@ -1,10 +1,11 @@
|
||||
use crate::nodes::VNode;
|
||||
use crate::prelude::*;
|
||||
use crate::{nodes::VNode};
|
||||
use bumpalo::Bump;
|
||||
use hooks::Hook;
|
||||
use log::info;
|
||||
use std::{
|
||||
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
|
||||
sync::atomic::AtomicUsize,
|
||||
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData, ops::Deref,
|
||||
rc::Rc, sync::atomic::AtomicUsize,
|
||||
};
|
||||
|
||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||
@@ -35,6 +36,8 @@ pub struct Context<'src> {
|
||||
pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||
pub(crate) bump: &'src Bump,
|
||||
|
||||
pub(crate) final_nodes: Rc<RefCell<Option<VNode<'static>>>>,
|
||||
|
||||
// holder for the src lifetime
|
||||
// todo @jon remove this
|
||||
pub _p: std::marker::PhantomData<&'src ()>,
|
||||
@@ -73,10 +76,10 @@ impl<'a> Context<'a> {
|
||||
/// }
|
||||
///```
|
||||
pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree {
|
||||
// pub fn view(self, lazy_nodes: impl for<'b> FnOnce(&'b Bump) -> VNode<'b> + 'a + 'p) -> DomTree {
|
||||
// pub fn view<'p>(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a + 'p) -> DomTree {
|
||||
// pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
|
||||
let _g = lazy_nodes(self.bump);
|
||||
let safe_nodes = lazy_nodes(self.bump);
|
||||
let unsafe_nodes = unsafe { std::mem::transmute::<VNode<'a>, VNode<'static>>(safe_nodes) };
|
||||
self.final_nodes.deref().borrow_mut().replace(unsafe_nodes);
|
||||
info!("lazy nodes have been generated");
|
||||
DomTree {}
|
||||
}
|
||||
|
||||
@@ -186,9 +189,8 @@ mod context_api {
|
||||
//! a failure of implementation.
|
||||
//!
|
||||
//!
|
||||
|
||||
|
||||
use std::{ops::Deref};
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct RemoteState<T> {
|
||||
inner: *const T,
|
||||
|
||||
+36
-25
@@ -16,34 +16,45 @@ impl EventTrigger {
|
||||
pub fn new() -> Self {
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Create a new "start" event that boots up the virtual dom if it is paused
|
||||
pub fn start_event() -> Self {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum VirtualEvent {
|
||||
// the event to drain the current lifecycle queue
|
||||
// Used to initate the dom
|
||||
StartEvent,
|
||||
|
||||
// Real events
|
||||
ClipboardEvent,
|
||||
CompositionEvent,
|
||||
KeyboardEvent,
|
||||
FocusEvent,
|
||||
FormEvent,
|
||||
GenericEvent,
|
||||
MouseEvent,
|
||||
PointerEvent,
|
||||
SelectionEvent,
|
||||
TouchEvent,
|
||||
UIEvent,
|
||||
WheelEvent,
|
||||
MediaEvent,
|
||||
ImageEvent,
|
||||
AnimationEvent,
|
||||
TransitionEvent,
|
||||
ClipboardEvent(ClipboardEvent),
|
||||
CompositionEvent(CompositionEvent),
|
||||
KeyboardEvent(KeyboardEvent),
|
||||
FocusEvent(FocusEvent),
|
||||
FormEvent(FormEvent),
|
||||
GenericEvent(GenericEvent),
|
||||
MouseEvent(MouseEvent),
|
||||
PointerEvent(PointerEvent),
|
||||
SelectionEvent(SelectionEvent),
|
||||
TouchEvent(TouchEvent),
|
||||
UIEvent(UIEvent),
|
||||
WheelEvent(WheelEvent),
|
||||
MediaEvent(MediaEvent),
|
||||
ImageEvent(ImageEvent),
|
||||
AnimationEvent(AnimationEvent),
|
||||
TransitionEvent(TransitionEvent),
|
||||
|
||||
OtherEvent,
|
||||
}
|
||||
|
||||
// these should reference the underlying event
|
||||
|
||||
pub struct ClipboardEvent {}
|
||||
pub struct CompositionEvent {}
|
||||
pub struct KeyboardEvent {}
|
||||
pub struct FocusEvent {}
|
||||
pub struct FormEvent {}
|
||||
pub struct GenericEvent {}
|
||||
pub struct MouseEvent {}
|
||||
pub struct PointerEvent {}
|
||||
pub struct SelectionEvent {}
|
||||
pub struct TouchEvent {}
|
||||
pub struct UIEvent {}
|
||||
pub struct WheelEvent {}
|
||||
pub struct MediaEvent {}
|
||||
pub struct ImageEvent {}
|
||||
pub struct AnimationEvent {}
|
||||
pub struct TransitionEvent {}
|
||||
@@ -88,7 +88,7 @@ pub mod builder {
|
||||
// types used internally that are important
|
||||
pub(crate) mod innerlude {
|
||||
// pub(crate) use crate::component::Properties;
|
||||
|
||||
|
||||
pub(crate) use crate::context::Context;
|
||||
pub(crate) use crate::error::{Error, Result};
|
||||
use crate::nodes;
|
||||
@@ -101,9 +101,7 @@ pub(crate) mod innerlude {
|
||||
|
||||
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
|
||||
mod fc2 {
|
||||
|
||||
}
|
||||
mod fc2 {}
|
||||
// pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
|
||||
// pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
|
||||
|
||||
+65
-67
@@ -12,6 +12,7 @@ use std::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::atomic::AtomicUsize,
|
||||
sync::atomic::Ordering,
|
||||
todo,
|
||||
};
|
||||
|
||||
@@ -40,34 +41,23 @@ pub struct Scope {
|
||||
|
||||
// lying, cheating reference >:(
|
||||
pub props: Box<dyn std::any::Any>,
|
||||
// pub props: Box<dyn Properties>,
|
||||
|
||||
//
|
||||
// pub props_type: TypeId,
|
||||
pub caller: *const (),
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
// create a new scope from a function
|
||||
pub fn new<'a, P1, P2: 'static>(
|
||||
// pub fn new<'a, P: Properties, PFree: P + 'a, PLocked: P + 'static>(
|
||||
f: FC<P1>,
|
||||
props: P1,
|
||||
parent: Option<Index>,
|
||||
) -> Self
|
||||
// where
|
||||
// PFree: 'a,
|
||||
// PLocked: 'static,
|
||||
{
|
||||
// Capture the props type
|
||||
// let props_type = TypeId::of::<P>();
|
||||
pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<Index>) -> Self {
|
||||
let hook_arena = typed_arena::Arena::new();
|
||||
let hooks = RefCell::new(Vec::new());
|
||||
|
||||
// Capture the caller
|
||||
let caller = f as *const ();
|
||||
|
||||
let listeners = Vec::new();
|
||||
let listeners: Vec<Box<dyn Fn()>> = vec![Box::new(|| {
|
||||
log::info!("Base listener called");
|
||||
})];
|
||||
|
||||
let old_frame = BumpFrame {
|
||||
bump: Bump::new(),
|
||||
@@ -84,16 +74,11 @@ impl Scope {
|
||||
// box the props
|
||||
let props = Box::new(props);
|
||||
|
||||
// erase the lifetime
|
||||
// we'll manage this with dom lifecycle
|
||||
|
||||
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
|
||||
|
||||
// todo!()
|
||||
Self {
|
||||
hook_arena,
|
||||
hooks,
|
||||
// props_type,
|
||||
caller,
|
||||
frames,
|
||||
listeners,
|
||||
@@ -102,13 +87,6 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update this component's props with a new set of props, remotely
|
||||
///
|
||||
///
|
||||
pub(crate) fn update_props<'a, P>(&self, _new_props: P) -> crate::error::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
@@ -120,67 +98,67 @@ impl Scope {
|
||||
frame
|
||||
};
|
||||
|
||||
let node_slot = std::rc::Rc::new(RefCell::new(None));
|
||||
let ctx: Context<'bump> = Context {
|
||||
arena: &self.hook_arena,
|
||||
hooks: &self.hooks,
|
||||
bump: &frame.bump,
|
||||
idx: 0.into(),
|
||||
_p: PhantomData {},
|
||||
final_nodes: node_slot.clone(),
|
||||
};
|
||||
|
||||
unsafe {
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
||||
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
||||
we transmute the function back using the props as reference.
|
||||
|
||||
we could do a better check to make sure that the TypeID is correct before casting
|
||||
--
|
||||
This is safe because we check that the generic type matches before casting.
|
||||
*/
|
||||
// we use plocked to be able to remove the borrowed lifetime
|
||||
// these lifetimes could be very broken, so we need to dynamically manage them
|
||||
let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
|
||||
let props = self.props.downcast_ref::<PLocked>().unwrap();
|
||||
|
||||
// Note that the actual modification of the vnode head element occurs during this call
|
||||
let _nodes: DomTree = caller(ctx, props);
|
||||
todo!("absorb domtree into self")
|
||||
// let nodes: VNode<'bump> = caller(ctx, props);
|
||||
|
||||
// let unsafe_node = std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes);
|
||||
// frame.head_node = unsafe_node;
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||
|
||||
Some things to note:
|
||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||
- The VNode has a private API and can only be used from accessors.
|
||||
- Public API cannot drop or destructure VNode
|
||||
*/
|
||||
// the nodes we care about have been unsafely extended to a static lifetime in context
|
||||
frame.head_node = node_slot
|
||||
.deref()
|
||||
.borrow_mut()
|
||||
.take()
|
||||
.expect("Viewing did not happen");
|
||||
}
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
||||
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
||||
we transmute the function back using the props as reference.
|
||||
|
||||
we could do a better check to make sure that the TypeID is correct before casting
|
||||
--
|
||||
This is safe because we check that the generic type matches before casting.
|
||||
*/
|
||||
|
||||
/*
|
||||
SAFETY ALERT
|
||||
|
||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||
|
||||
Some things to note:
|
||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||
- The VNode has a private API and can only be used from accessors.
|
||||
- Public API cannot drop or destructure VNode
|
||||
*/
|
||||
}
|
||||
|
||||
/// Accessor to get the root node and its children (safely)\
|
||||
/// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
|
||||
pub fn current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.current_head_node()
|
||||
}
|
||||
|
||||
pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
todo!()
|
||||
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.prev_head_node()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub head_node: VNode<'static>,
|
||||
}
|
||||
|
||||
// todo, do better with the active frame stuff
|
||||
// somehow build this vnode with a lifetime tied to self
|
||||
// This root node has "static" lifetime, but it's really not static.
|
||||
@@ -192,6 +170,11 @@ pub struct ActiveFrame {
|
||||
pub frames: [BumpFrame; 2],
|
||||
}
|
||||
|
||||
pub struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub head_node: VNode<'static>,
|
||||
}
|
||||
|
||||
impl ActiveFrame {
|
||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||
Self {
|
||||
@@ -201,8 +184,23 @@ impl ActiveFrame {
|
||||
}
|
||||
|
||||
fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed) % 1;
|
||||
let raw_node = &self.frames[cur_idx];
|
||||
let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 != 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
safe_node
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
|
||||
let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let unsafe_head = &raw_node.head_node;
|
||||
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
|
||||
@@ -211,8 +209,8 @@ impl ActiveFrame {
|
||||
}
|
||||
|
||||
fn next(&mut self) -> &mut BumpFrame {
|
||||
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
|
||||
self.idx.fetch_add(1, Ordering::Relaxed);
|
||||
let cur = self.idx.borrow().load(Ordering::Relaxed);
|
||||
match cur % 1 {
|
||||
1 => &mut self.frames[1],
|
||||
0 => &mut self.frames[0],
|
||||
@@ -276,7 +274,7 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
fn child_example(ctx: Context, props: &ExampleProps) -> DomTree {
|
||||
fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
|
||||
ctx.view(move |b| {
|
||||
div(b)
|
||||
.child(text(props.name))
|
||||
|
||||
@@ -78,6 +78,34 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
//// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom
|
||||
pub fn rebuild(&mut self) -> Result<EditList<'_>> {
|
||||
// Reset and then build a new diff machine
|
||||
// The previous edit list cannot be around while &mut is held
|
||||
// Make sure variance doesnt break this
|
||||
self.diff_bump.reset();
|
||||
let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
||||
|
||||
// this is still a WIP
|
||||
// we'll need to re-fecth all the scopes that were changed and build the diff machine
|
||||
// fetch the component again
|
||||
let component = self
|
||||
.components
|
||||
.get_mut(self.base_scope)
|
||||
.expect("Root should always exist");
|
||||
|
||||
component.run::<()>();
|
||||
|
||||
let old = component.old_frame();
|
||||
let new = component.new_frame();
|
||||
dbg!(old);
|
||||
dbg!(new);
|
||||
|
||||
diff_machine.diff_node(old, new);
|
||||
|
||||
Ok(diff_machine.consume())
|
||||
}
|
||||
|
||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||
///
|
||||
/// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
|
||||
@@ -102,17 +130,16 @@ impl VirtualDom {
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
|
||||
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
|
||||
let EventTrigger {
|
||||
component_id,
|
||||
listener_id,
|
||||
event: _,
|
||||
} = evt;
|
||||
} = event;
|
||||
|
||||
let component = self
|
||||
.components
|
||||
.get(component_id)
|
||||
// todo: update this with a dedicated error type so implementors know what went wrong
|
||||
.expect("Component should exist if an event was triggered");
|
||||
|
||||
let listener = component
|
||||
@@ -121,47 +148,37 @@ impl VirtualDom {
|
||||
.expect("Listener should exist if it was triggered")
|
||||
.as_ref();
|
||||
|
||||
// Run the callback
|
||||
// This should cause internal state to progress, dumping events into the event queue
|
||||
// todo: integrate this with a tracing mechanism exposed to a dev tool
|
||||
// Run the callback with the user event
|
||||
listener();
|
||||
|
||||
// Run through our events, tagging which Indexes are receiving updates
|
||||
// Prop updates take prescedence over subscription updates
|
||||
// Run all prop updates *first* as they will cascade into children.
|
||||
// *then* run the non-prop updates that were not already covered by props
|
||||
|
||||
// Mark dirty components. Descend from the highest node until all dirty nodes are updated.
|
||||
let mut affected_components = Vec::new();
|
||||
|
||||
// It's essentially draining the vec, but with some dancing to release the RefMut
|
||||
// We also want to be able to push events into the queue from processing the event
|
||||
while let Some(event) = {
|
||||
let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
|
||||
new_evt
|
||||
} {
|
||||
while let Some(event) = self.pop_event() {
|
||||
if let Some(component_idx) = event.index() {
|
||||
affected_components.push(component_idx);
|
||||
}
|
||||
self.process_lifecycle(event)?;
|
||||
}
|
||||
|
||||
// Reset and then build a new diff machine
|
||||
// The previous edit list cannot be around while &mut is held
|
||||
// Make sure variance doesnt break this
|
||||
self.diff_bump.reset();
|
||||
let diff_machine = DiffMachine::new(&self.diff_bump);
|
||||
|
||||
Ok(diff_machine.consume())
|
||||
todo!()
|
||||
}
|
||||
|
||||
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
|
||||
fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
|
||||
match event_type {
|
||||
// Component needs to be mounted to the virtual dom
|
||||
LifecycleType::Mount { to: _, under: _, props: _ } => {}
|
||||
LifecycleType::Mount {
|
||||
to: _,
|
||||
under: _,
|
||||
props: _,
|
||||
} => {}
|
||||
|
||||
// The parent for this component generated new props and the component needs update
|
||||
LifecycleType::PropsChanged { props: _, component: _ } => {}
|
||||
LifecycleType::PropsChanged {
|
||||
props: _,
|
||||
component: _,
|
||||
} => {}
|
||||
|
||||
// Component was messaged via the internal subscription service
|
||||
LifecycleType::Callback { component: _ } => {}
|
||||
@@ -170,6 +187,11 @@ impl VirtualDom {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pop the top event of the internal lifecycle event queu
|
||||
pub fn pop_event(&self) -> Option<LifecycleEvent> {
|
||||
self.event_queue.as_ref().borrow_mut().pop_front()
|
||||
}
|
||||
|
||||
/// With access to the virtual dom, schedule an update to the Root component's props.
|
||||
/// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
|
||||
/// back into the event system to get an edit list.
|
||||
@@ -215,10 +237,30 @@ pub enum LifecycleType {
|
||||
impl LifecycleEvent {
|
||||
fn index(&self) -> Option<Index> {
|
||||
match &self.event_type {
|
||||
LifecycleType::Mount { to: _, under: _, props: _ } => None,
|
||||
LifecycleType::Mount {
|
||||
to: _,
|
||||
under: _,
|
||||
props: _,
|
||||
} => None,
|
||||
|
||||
LifecycleType::PropsChanged { component, .. }
|
||||
| LifecycleType::Callback { component } => Some(component.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_dom() {
|
||||
let mut dom = VirtualDom::new(|ctx, props| {
|
||||
ctx.view(|bump| {
|
||||
use crate::builder::*;
|
||||
div(bump).child(text("hello, world")).finish()
|
||||
})
|
||||
});
|
||||
let edits = dom.rebuild().unwrap();
|
||||
println!("{:#?}", edits);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user