wip: successfully building

This commit is contained in:
Jonathan Kelley committed 2021-06-07 14:14:49 -04:00
1 parent 7790750349
commit e3d9db0847
11 files changed
+355 -827

No files matched your search

+69 -215
View File
@@ -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 {