wip: successfully building
This commit is contained in:
1 parent
7790750349
commit
e3d9db0847
11 files changed
+355
-827
No files matched your search
@@ -52,8 +52,8 @@ pub struct VirtualDom {
|
||||
|
||||
/// a strong allocation to the "caller" for the original component and its props
|
||||
#[doc(hidden)]
|
||||
_root_caller: Rc<OpaqueComponent<'static>>,
|
||||
|
||||
_root_caller: Rc<OpaqueComponent>,
|
||||
// _root_caller: Rc<OpaqueComponent<'static>>,
|
||||
/// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic.
|
||||
///
|
||||
/// Whenver props need to be updated, an Error will be thrown if the new props do not
|
||||
@@ -137,7 +137,8 @@ impl VirtualDom {
|
||||
|
||||
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
|
||||
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
|
||||
let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
|
||||
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |scope| {
|
||||
// let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
|
||||
// the lifetime of this closure is just as long as the lifetime on the scope reference
|
||||
// this closure moves root props (which is static) into this closure
|
||||
let props = unsafe { &*(&root_props as *const _) };
|
||||
@@ -154,10 +155,11 @@ impl VirtualDom {
|
||||
// Make the first scope
|
||||
// We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
|
||||
let link = components.clone();
|
||||
let event_channel = Rc::new(move || {});
|
||||
|
||||
let base_scope = components
|
||||
.with(|arena| {
|
||||
arena.insert_with(move |myidx| {
|
||||
let event_channel = _event_queue.new_channel(0, myidx);
|
||||
Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[])
|
||||
})
|
||||
})
|
||||
@@ -175,21 +177,24 @@ impl VirtualDom {
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
|
||||
/// Currently this doesn't do what we want it to do
|
||||
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
||||
todo!()
|
||||
// let mut diff_machine = DiffMachine::new();
|
||||
let mut diff_machine = DiffMachine::new(
|
||||
self.components.clone(),
|
||||
self.base_scope,
|
||||
self.event_queue.clone(),
|
||||
);
|
||||
|
||||
// // Schedule an update and then immediately call it on the root component
|
||||
// // This is akin to a hook being called from a listener and requring a re-render
|
||||
// // Instead, this is done on top-level component
|
||||
// Schedule an update and then immediately call it on the root component
|
||||
// This is akin to a hook being called from a listener and requring a re-render
|
||||
// Instead, this is done on top-level component
|
||||
|
||||
// let base = self.components.try_get(self.base_scope)?;
|
||||
let base = self.components.try_get(self.base_scope)?;
|
||||
|
||||
// let update = &base.event_channel;
|
||||
// update();
|
||||
let update = &base.event_channel;
|
||||
update();
|
||||
|
||||
// self.progress_completely(&mut diff_machine)?;
|
||||
self.progress_completely(&mut diff_machine)?;
|
||||
|
||||
// Ok(diff_machine.consume())
|
||||
Ok(diff_machine.consume())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,7 +250,9 @@ impl VirtualDom {
|
||||
|
||||
self.components.try_get_mut(id)?.call_listener(event)?;
|
||||
|
||||
let mut diff_machine = DiffMachine::new(self.components.clone());
|
||||
let mut diff_machine =
|
||||
DiffMachine::new(self.components.clone(), id, self.event_queue.clone());
|
||||
|
||||
self.progress_completely(&mut diff_machine)?;
|
||||
|
||||
Ok(diff_machine.consume())
|
||||
@@ -260,212 +267,48 @@ impl VirtualDom {
|
||||
diff_machine: &'_ mut DiffMachine<'s>,
|
||||
) -> Result<()> {
|
||||
// Add this component to the list of components that need to be difed
|
||||
#[allow(unused_assignments)]
|
||||
let mut cur_height: u32 = 0;
|
||||
// #[allow(unused_assignments)]
|
||||
// let mut cur_height: u32 = 0;
|
||||
|
||||
// Now, there are events in the queue
|
||||
let mut seen_nodes = HashSet::<ScopeIdx>::new();
|
||||
let mut updates = self.event_queue.0.as_ref().borrow_mut();
|
||||
|
||||
// Order the nodes by their height, we want the biggest nodes on the top
|
||||
// This prevents us from running the same component multiple times
|
||||
updates.sort_unstable();
|
||||
|
||||
log::debug!("There are: {:#?} updates to be processed", updates.len());
|
||||
|
||||
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
||||
for update in updates.drain(..) {
|
||||
log::debug!("Running updates for: {:#?}", update);
|
||||
// Make sure this isn't a node we've already seen, we don't want to double-render anything
|
||||
// If we double-renderer something, this would cause memory safety issues
|
||||
if seen_nodes.contains(&update.idx) {
|
||||
if diff_machine.seen_nodes.contains(&update.idx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now, all the "seen nodes" are nodes that got notified by running this listener
|
||||
seen_nodes.insert(update.idx.clone());
|
||||
diff_machine.seen_nodes.insert(update.idx.clone());
|
||||
|
||||
// Start a new mutable borrow to components
|
||||
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
|
||||
|
||||
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
||||
// let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
|
||||
// let cur_component = inner.get_mut(update.idx).unwrap();
|
||||
|
||||
cur_component.run_scope()?;
|
||||
|
||||
diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame());
|
||||
|
||||
cur_height = cur_component.height;
|
||||
// cur_height = cur_component.height;
|
||||
|
||||
log::debug!(
|
||||
"Processing update: {:#?} with height {}",
|
||||
&update.idx,
|
||||
cur_height
|
||||
);
|
||||
|
||||
// Now, the entire subtree has been invalidated. We need to descend depth-first and process
|
||||
// any updates that the diff machine has proprogated into the component lifecycle queue
|
||||
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
|
||||
match event {
|
||||
// A new component has been computed from the diffing algorithm
|
||||
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
|
||||
// this will flood the lifecycle queue with new updates to build up the subtree
|
||||
LifeCycleEvent::Mount {
|
||||
caller,
|
||||
root_id: id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
log::debug!("Mounting a new component");
|
||||
|
||||
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
|
||||
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
|
||||
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
|
||||
|
||||
// Insert a new scope into our component list
|
||||
let idx = self.components.with(|components| {
|
||||
components.insert_with(|new_idx| {
|
||||
let height = cur_height + 1;
|
||||
Scope::new(
|
||||
caller,
|
||||
new_idx,
|
||||
Some(cur_component.arena_idx),
|
||||
height,
|
||||
self.event_queue.new_channel(height, new_idx),
|
||||
self.components.clone(),
|
||||
&[],
|
||||
)
|
||||
})
|
||||
})?;
|
||||
|
||||
{
|
||||
let cur_component = self.components.try_get_mut(update.idx).unwrap();
|
||||
let mut ch = cur_component.descendents.borrow_mut();
|
||||
ch.insert(idx);
|
||||
std::mem::drop(ch);
|
||||
}
|
||||
|
||||
// Grab out that component
|
||||
let new_component = self.components.try_get_mut(idx).unwrap();
|
||||
|
||||
// Actually initialize the caller's slot with the right address
|
||||
*stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
|
||||
|
||||
// Run the scope for one iteration to initialize it
|
||||
new_component.run_scope()?;
|
||||
|
||||
// Navigate the diff machine to the right point in the output dom
|
||||
diff_machine.change_list.load_known_root(id);
|
||||
|
||||
// And then run the diff algorithm
|
||||
diff_machine
|
||||
.diff_node(new_component.old_frame(), new_component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
seen_nodes.insert(idx);
|
||||
}
|
||||
|
||||
// A component has remained in the same location but its properties have changed
|
||||
// We need to process this component and then dump the output lifecycle events into the queue
|
||||
LifeCycleEvent::PropsChanged {
|
||||
caller,
|
||||
root_id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
log::debug!("Updating a component after its props have changed");
|
||||
|
||||
// Get the stable index to the target component
|
||||
// This *should* exist due to guarantees in the diff algorithm
|
||||
let idx = stable_scope_addr
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.unwrap();
|
||||
|
||||
// Grab out that component
|
||||
let component = self.components.try_get_mut(idx).unwrap();
|
||||
|
||||
// We have to move the caller over or running the scope will fail
|
||||
component.update_caller(caller);
|
||||
|
||||
// Run the scope
|
||||
component.run_scope()?;
|
||||
|
||||
// Navigate the diff machine to the right point in the output dom
|
||||
diff_machine.change_list.load_known_root(root_id);
|
||||
|
||||
// And then run the diff algorithm
|
||||
diff_machine.diff_node(component.old_frame(), component.next_frame());
|
||||
|
||||
// Finally, insert this node as a seen node.
|
||||
seen_nodes.insert(idx);
|
||||
}
|
||||
|
||||
// A component's parent has updated, but its properties did not change.
|
||||
// This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
|
||||
LifeCycleEvent::SameProps {
|
||||
caller,
|
||||
stable_scope_addr,
|
||||
..
|
||||
} => {
|
||||
// In this case, the parent made a new VNode that resulted in the same props for us
|
||||
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
|
||||
log::debug!("Received the same props");
|
||||
|
||||
// Get the stable index to the target component
|
||||
// This *should* exist due to guarantees in the diff algorithm
|
||||
let idx = stable_scope_addr
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.unwrap();
|
||||
|
||||
// Grab out that component
|
||||
let component = self.components.try_get_mut(idx).unwrap();
|
||||
|
||||
// We have to move the caller over or running the scope will fail
|
||||
component.update_caller(caller);
|
||||
|
||||
// This time, we will not add it to our seen nodes since we did not actually run it
|
||||
}
|
||||
|
||||
LifeCycleEvent::Remove {
|
||||
root_id,
|
||||
stable_scope_addr,
|
||||
} => {
|
||||
let id = stable_scope_addr
|
||||
.upgrade()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.borrow()
|
||||
.unwrap();
|
||||
|
||||
log::warn!("Removing node {:#?}", id);
|
||||
|
||||
// This would normally be recursive but makes sense to do linear to
|
||||
let mut children_to_remove = VecDeque::new();
|
||||
children_to_remove.push_back(id);
|
||||
|
||||
// Accumulate all the child components that need to be removed
|
||||
while let Some(child_id) = children_to_remove.pop_back() {
|
||||
let comp = self.components.try_get(child_id).unwrap();
|
||||
let children = comp.descendents.borrow();
|
||||
for child in children.iter() {
|
||||
children_to_remove.push_front(*child);
|
||||
}
|
||||
log::debug!("Removing component: {:#?}", child_id);
|
||||
self.components
|
||||
.with(|components| components.remove(child_id).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
LifeCycleEvent::Replace {
|
||||
caller,
|
||||
root_id: id,
|
||||
..
|
||||
} => {
|
||||
unimplemented!("This feature (Replace) is unimplemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
// log::debug!(
|
||||
// "Processing update: {:#?} with height {}",
|
||||
// &update.idx,
|
||||
// cur_height
|
||||
// );
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -489,7 +332,7 @@ pub struct Scope {
|
||||
|
||||
// IDs of children that this scope has created
|
||||
// This enables us to drop the children and their children when this scope is destroyed
|
||||
descendents: RefCell<HashSet<ScopeIdx>>,
|
||||
pub(crate) descendents: RefCell<HashSet<ScopeIdx>>,
|
||||
|
||||
child_nodes: &'static [VNode<'static>],
|
||||
|
||||
@@ -507,8 +350,8 @@ pub struct Scope {
|
||||
pub event_channel: Rc<dyn Fn() + 'static>,
|
||||
|
||||
// pub event_queue: EventQueue,
|
||||
pub caller: Weak<OpaqueComponent<'static>>,
|
||||
|
||||
pub caller: Weak<OpaqueComponent>,
|
||||
// pub caller: Weak<OpaqueComponent<'static>>,
|
||||
pub hookidx: RefCell<usize>,
|
||||
|
||||
// ==========================
|
||||
@@ -542,8 +385,9 @@ impl Scope {
|
||||
//
|
||||
// Scopes cannot be made anywhere else except for this file
|
||||
// Therefore, their lifetimes are connected exclusively to the virtual dom
|
||||
fn new<'creator_node>(
|
||||
caller: Weak<OpaqueComponent<'creator_node>>,
|
||||
pub fn new<'creator_node>(
|
||||
caller: Weak<OpaqueComponent>,
|
||||
// caller: Weak<OpaqueComponent<'creator_node>>,
|
||||
arena_idx: ScopeIdx,
|
||||
parent: Option<ScopeIdx>,
|
||||
height: u32,
|
||||
@@ -569,8 +413,10 @@ impl Scope {
|
||||
// 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>>,
|
||||
Weak<OpaqueComponent>,
|
||||
Weak<OpaqueComponent>,
|
||||
// Weak<OpaqueComponent<'creator_node>>,
|
||||
// Weak<OpaqueComponent<'static>>,
|
||||
>(caller)
|
||||
};
|
||||
|
||||
@@ -591,11 +437,14 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent>) {
|
||||
// pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
|
||||
let broken_caller = unsafe {
|
||||
std::mem::transmute::<
|
||||
Weak<OpaqueComponent<'creator_node>>,
|
||||
Weak<OpaqueComponent<'static>>,
|
||||
Weak<OpaqueComponent>,
|
||||
Weak<OpaqueComponent>,
|
||||
// Weak<OpaqueComponent<'creator_node>>,
|
||||
// Weak<OpaqueComponent<'static>>,
|
||||
>(caller)
|
||||
};
|
||||
|
||||
@@ -627,8 +476,10 @@ impl Scope {
|
||||
.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<'sel> = unsafe { std::mem::transmute(c2) };
|
||||
let c2: &OpaqueComponent = caller.as_ref();
|
||||
let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) };
|
||||
// let c2: &OpaqueComponent<'static> = caller.as_ref();
|
||||
// let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) };
|
||||
|
||||
let unsafe_head = unsafe { self.own_vnodes(c3) };
|
||||
|
||||
@@ -638,7 +489,7 @@ impl Scope {
|
||||
}
|
||||
|
||||
// this is its own function so we can preciesly control how lifetimes flow
|
||||
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> {
|
||||
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> {
|
||||
let new_head: VNode<'a> = f(self);
|
||||
let out: VNode<'static> = std::mem::transmute(new_head);
|
||||
out
|
||||
@@ -956,22 +807,25 @@ Any function prefixed with "use" should not be called conditionally.
|
||||
|
||||
// We actually allocate the properties for components in their parent's properties
|
||||
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
||||
pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
|
||||
pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Default)]
|
||||
pub(crate) struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
|
||||
pub 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))
|
||||
Rc::new(move || {
|
||||
log::debug!("channel updated {:#?}", marker);
|
||||
inner.0.as_ref().borrow_mut().push(marker)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper type that lets scopes be ordered by their height
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct HeightMarker {
|
||||
pub struct HeightMarker {
|
||||
pub idx: ScopeIdx,
|
||||
pub height: u32,
|
||||
}
|
||||
@@ -1047,20 +901,20 @@ impl ActiveFrame {
|
||||
)
|
||||
}
|
||||
|
||||
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||
pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
||||
Self {
|
||||
generation: 0.into(),
|
||||
frames: [a, b],
|
||||
}
|
||||
}
|
||||
|
||||
fn cur_frame(&self) -> &BumpFrame {
|
||||
pub fn cur_frame(&self) -> &BumpFrame {
|
||||
match *self.generation.borrow() & 1 == 0 {
|
||||
true => &self.frames[0],
|
||||
false => &self.frames[1],
|
||||
}
|
||||
}
|
||||
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match *self.generation.borrow() & 1 == 0 {
|
||||
true => &mut self.frames[0],
|
||||
false => &mut self.frames[1],
|
||||
@@ -1095,7 +949,7 @@ impl ActiveFrame {
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self) -> &mut BumpFrame {
|
||||
pub fn next(&mut self) -> &mut BumpFrame {
|
||||
*self.generation.borrow_mut() += 1;
|
||||
|
||||
if *self.generation.borrow() % 2 == 0 {
|
||||
|
||||
Reference in new issue
Block a user