wip: it compiles cleanly

This commit is contained in:
Jonathan Kelley 2021-11-07 00:06:00 -04:00
parent 059294ab55
commit fd0c384dac
3 changed files with 158 additions and 244 deletions

View file

@ -108,7 +108,7 @@ pub struct DiffState<'bump> {
pub mutations: Mutations<'bump>,
pub(crate) stack: DiffStack<'bump>,
pub seen_scopes: FxHashSet<ScopeId>,
pub(crate) cfg: DiffCfg,
pub force_diff: bool,
}
impl<'bump> DiffState<'bump> {
@ -117,68 +117,14 @@ impl<'bump> DiffState<'bump> {
mutations,
stack: DiffStack::new(),
seen_scopes: Default::default(),
cfg: Default::default(),
force_diff: false,
}
}
}
pub(crate) struct DiffCfg {
pub force_diff: bool,
}
impl Default for DiffCfg {
fn default() -> Self {
Self {
force_diff: Default::default(),
}
}
}
/// a "saved" form of a diff machine
/// in regular diff machine, the &'bump reference is a stack borrow, but the
/// bump lifetimes are heap borrows.
pub(crate) struct SavedDiffWork<'bump> {
pub mutations: Mutations<'bump>,
pub stack: DiffStack<'bump>,
pub seen_scopes: FxHashSet<ScopeId>,
}
impl<'a> SavedDiffWork<'a> {
pub unsafe fn extend(self: SavedDiffWork<'a>) -> SavedDiffWork<'static> {
std::mem::transmute(self)
}
pub unsafe fn promote<'b>(self) -> DiffState<'b> {
let extended: SavedDiffWork<'b> = std::mem::transmute(self);
DiffState {
cfg: DiffCfg::default(),
mutations: extended.mutations,
stack: extended.stack,
seen_scopes: extended.seen_scopes,
}
}
}
impl<'bump> VirtualDom {
// impl<'bump> DiffState<'bump> {
// pub(crate) fn new(mutations: Mutations<'bump>) -> Self {
// Self {
// mutations,
// cfg: DiffCfg::default(),
// stack: DiffStack::new(),
// seen_scopes: FxHashSet::default(),
// }
// }
// pub fn save(self) -> SavedDiffWork<'bump> {
// SavedDiffWork {
// mutations: state.mutations,
// stack: state.stack,
// seen_scopes: self.seen_scopes,
// }
// }
pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: ScopeId) {
if let Some(component) = self.scopes.get(&id) {
impl<'bump> ScopeArena {
pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: &ScopeId) {
if let Some(component) = self.get_scope(&id) {
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
state.stack.push(DiffInstruction::Diff { new, old });
self.work(state, || false);
@ -382,7 +328,7 @@ impl<'bump> VirtualDom {
state.stack.add_child_count(1);
if let Some(cur_scope_id) = state.stack.current_scope() {
let scope = self.scopes.get(&cur_scope_id).unwrap();
let scope = self.get_scope(&cur_scope_id).unwrap();
for listener in *listeners {
self.attach_listener_to_scope(state, listener, scope);
@ -427,7 +373,7 @@ impl<'bump> VirtualDom {
let parent_scope = self.get_scope(&parent_idx).unwrap();
let new_idx: ScopeId = todo!();
// self.scopes
// self
// .new_with_key(fc_ptr, vcomp, parent_scope, height, subtree, sender);
// .(|new_idx| {
@ -445,7 +391,7 @@ impl<'bump> VirtualDom {
vcomponent.associated_scope.set(Some(new_idx));
if !vcomponent.can_memoize {
let cur_scope = self.scopes.get(&parent_idx).unwrap();
let cur_scope = self.get_scope(&parent_idx).unwrap();
let extended = vcomponent as *const VComponent;
let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
@ -456,7 +402,7 @@ impl<'bump> VirtualDom {
// add noderefs to current noderef list Noderefs
// add effects to current effect list Effects
let new_component = self.scopes.get(&new_idx).unwrap();
let new_component = self.get_scope(&new_idx).unwrap();
log::debug!(
"initializing component {:?} with height {:?}",
@ -606,7 +552,7 @@ impl<'bump> VirtualDom {
//
// TODO: take a more efficient path than this
if let Some(cur_scope_id) = state.stack.current_scope() {
let scope = self.scopes.get(&cur_scope_id).unwrap();
let scope = self.get_scope(&cur_scope_id).unwrap();
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
@ -664,7 +610,7 @@ impl<'bump> VirtualDom {
new.associated_scope.set(Some(scope_addr));
// make sure the component's caller function is up to date
let scope = self.scopes.get(&scope_addr).unwrap();
let scope = self.get_scope(&scope_addr).unwrap();
scope.update_vcomp(new);
// React doesn't automatically memoize, but we do.
@ -1312,7 +1258,7 @@ impl<'bump> VirtualDom {
VNode::Component(c) => {
let scope_id = c.associated_scope.get().unwrap();
let scope = self.scopes.get(&scope_id).unwrap();
let scope = self.get_scope(&scope_id).unwrap();
let root = scope.root_node();
self.remove_nodes(state, Some(root), gen_muts);
@ -1366,7 +1312,7 @@ impl<'bump> VirtualDom {
if let Some(scope) = state
.stack
.current_scope()
.and_then(|id| self.scopes.get(&id))
.and_then(|id| self.get_scope(&id))
{
// safety: this lifetime is managed by the logic on scope
let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) };

View file

@ -21,18 +21,20 @@ pub(crate) struct ScopeArena {
bump: Bump,
scopes: Vec<*mut ScopeState>,
free_scopes: Vec<ScopeId>,
pub(crate) sender: UnboundedSender<SchedulerMsg>,
}
impl ScopeArena {
pub fn new() -> Self {
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
Self {
bump: Bump::new(),
scopes: Vec::new(),
free_scopes: Vec::new(),
sender,
}
}
pub fn get(&self, id: &ScopeId) -> Option<&ScopeState> {
pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeState> {
unsafe { Some(&*self.scopes[id.0]) }
}
@ -86,5 +88,18 @@ impl ScopeArena {
}
}
pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeState> {
todo!()
}
pub fn reserve_node(&self, node: &VNode) -> ElementId {
todo!()
// self.node_reservations.insert(id);
}
pub fn collect_garbage(&self, id: ElementId) {
todo!()
}
// scopes never get dropepd
}

View file

@ -88,8 +88,6 @@ pub struct VirtualDom {
pending_messages: VecDeque<SchedulerMsg>,
dirty_scopes: IndexSet<ScopeId>,
saved_state: Option<SavedDiffWork<'static>>,
in_progress: bool,
}
@ -164,7 +162,7 @@ impl VirtualDom {
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
let mut scopes = ScopeArena::new();
let mut scopes = ScopeArena::new(sender.clone());
let base_scope = scopes.new_with_key(
//
@ -191,7 +189,6 @@ impl VirtualDom {
pending_futures: Default::default(),
dirty_scopes: Default::default(),
saved_state: None,
in_progress: false,
}
}
@ -216,7 +213,7 @@ impl VirtualDom {
///
///
pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a ScopeState> {
self.scopes.get(&id)
self.scopes.get_scope(&id)
}
/// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
@ -246,10 +243,12 @@ impl VirtualDom {
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
// todo: poll the events once even if there is work to do to prevent starvation
if self.has_any_work() {
return;
}
// if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
if self.pending_futures.is_empty() {
self.pending_messages
.push_front(self.receiver.next().await.unwrap());
} else {
struct PollTasks<'a> {
pending_futures: &'a FxHashSet<ScopeId>,
scopes: &'a ScopeArena,
@ -258,12 +257,18 @@ impl VirtualDom {
impl<'a> Future for PollTasks<'a> {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Self::Output> {
let mut all_pending = true;
// Poll every scope manually
for fut in self.pending_futures.iter() {
let scope = self.scopes.get(fut).expect("Scope should never be moved");
let scope = self
.scopes
.get_scope(fut)
.expect("Scope should never be moved");
let mut items = scope.items.borrow_mut();
for task in items.tasks.iter_mut() {
@ -271,17 +276,17 @@ impl VirtualDom {
// todo: does this make sense?
// I don't usually write futures by hand
// I think the futures neeed to be pinned using bumpbox or something
// right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
let unpinned = unsafe { Pin::new_unchecked(task) };
match unpinned.poll(cx) {
Poll::Ready(_) => {
all_pending = false;
}
Poll::Pending => {}
if let Poll::Ready(_) = unpinned.poll(cx) {
all_pending = false
}
}
}
// Resolve the future if any task is ready
// Resolve the future if any singular task is ready
match all_pending {
true => Poll::Pending,
false => Poll::Ready(()),
@ -289,21 +294,24 @@ impl VirtualDom {
}
}
// Poll both the futures and the scheduler message queue simulataneously
use futures_util::future::{select, Either};
let scheduler_fut = self.receiver.next();
let tasks_fut = PollTasks {
pending_futures: &self.pending_futures,
scopes: &self.scopes,
};
use futures_util::future::{select, Either};
match select(tasks_fut, scheduler_fut).await {
// Tasks themselves don't generate work
// Futures don't generate work
Either::Left((_, _)) => {}
// Save these messages in FIFO to be processed later
Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
}
}
}
/// Run the virtualdom with a deadline.
///
@ -333,10 +341,15 @@ impl VirtualDom {
///
/// ```no_run
/// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
///
/// let mut dom = VirtualDom::new(App);
///
/// loop {
/// let deadline = TimeoutFuture::from_ms(16);
/// let mut timeout = TimeoutFuture::from_ms(16);
/// let deadline = move || timeout.now_or_never();
///
/// let mutations = dom.run_with_deadline(deadline).await;
///
/// apply_mutations(mutations);
/// }
/// ```
@ -352,124 +365,91 @@ impl VirtualDom {
&'a mut self,
mut deadline: impl FnMut() -> bool,
) -> Vec<Mutations<'a>> {
let mut committed_mutations = Vec::<Mutations<'static>>::new();
let mut committed_mutations = Vec::<Mutations>::new();
while self.has_any_work() {
while let Ok(Some(msg)) = self.receiver.try_next() {
self.pending_messages.push_front(msg);
}
for msg in self.pending_messages.drain(..) {
while let Some(msg) = self.pending_messages.pop_back() {
match msg {
SchedulerMsg::Immediate(id) => {
// it's dirty
self.dirty_scopes.insert(id);
}
SchedulerMsg::UiEvent(event) => {
// there's an event
let scope = self.scopes.get(&event.scope_id).unwrap();
if let Some(element) = event.mounted_dom_id {
log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
let scope = self.scopes.get_scope(&event.scope_id).unwrap();
// TODO: bubble properly here
scope.call_listener(event, element);
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
match dirty_scope {
SchedulerMsg::Immediate(im) => {
self.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
}
self.pending_messages.push_front(dirty_scope);
}
} else {
log::debug!("User event without a targetted ElementId. Unsure how to proceed. {:?}", event);
}
}
}
}
let work_complete = {
// Work through the current subtree, and commit the results when it finishes
// When the deadline expires, give back the work
let saved_state = unsafe { self.load_work() };
// We have to split away some parts of ourself - current lane is borrowed mutably
let mut machine = unsafe { saved_state.promote() };
let mut diff_state: DiffState = DiffState::new(Mutations::new());
let mut ran_scopes = FxHashSet::default();
if machine.stack.is_empty() {
todo!("order scopes");
// self.dirty_scopes.retain(|id| self.get_scope(id).is_some());
// self.dirty_scopes.sort_by(|a, b| {
// let h1 = self.get_scope(a).unwrap().height;
// let h2 = self.get_scope(b).unwrap().height;
// h1.cmp(&h2).reverse()
// });
// todo: the 2021 version of rust will let us not have to force the borrow
let scopes = &self.scopes;
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
self.dirty_scopes
.retain(|id| scopes.get_scope(id).is_some());
self.dirty_scopes.sort_by(|a, b| {
let h1 = scopes.get_scope(a).unwrap().height;
let h2 = scopes.get_scope(b).unwrap().height;
h1.cmp(&h2).reverse()
});
if let Some(scopeid) = self.dirty_scopes.pop() {
log::info!("handling dirty scope {:?}", scopeid);
if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
// if let Some(component) = self.get_scope_mut(&scopeid) {
if self.run_scope(&scopeid) {
todo!("diff the scope")
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
// // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
// machine.stack.scope_stack.push(scopeid);
// machine.stack.push(DiffInstruction::Diff { new, old });
}
// }
let scope = self.scopes.get_scope(&scopeid).unwrap();
let (old, new) = (scope.frames.wip_head(), scope.frames.fin_head());
diff_state.stack.scope_stack.push(scopeid);
diff_state.stack.push(DiffInstruction::Diff { new, old });
}
}
}
let work_completed: bool = todo!();
// let work_completed = machine.work(deadline_reached);
// log::debug!("raw edits {:?}", machine.mutations.edits);
let mut machine: DiffState<'static> = unsafe { std::mem::transmute(machine) };
// let mut saved = machine.save();
let work_completed = self.scopes.work(&mut diff_state, &mut deadline);
if work_completed {
for node in machine.seen_scopes.drain() {
// self.dirty_scopes.clear();
// self.ui_events.clear();
self.dirty_scopes.remove(&node);
// self.dirty_scopes.remove(&node);
let DiffState {
mutations,
seen_scopes,
stack,
..
} = diff_state;
for scope in seen_scopes {
self.dirty_scopes.remove(&scope);
}
let mut new_mutations = Mutations::new();
// I think the stack should be empty at the end of diffing?
debug_assert_eq!(stack.scope_stack.len(), 0);
for edit in machine.mutations.edits.drain(..) {
new_mutations.edits.push(edit);
}
// for edit in saved.edits.drain(..) {
// new_mutations.edits.push(edit);
// }
// std::mem::swap(&mut new_mutations, &mut saved.mutations);
mutations.push(new_mutations);
// log::debug!("saved edits {:?}", mutations);
todo!();
// let mut saved = machine.save();
// self.save_work(saved);
true
// self.save_work(saved);
// false
committed_mutations.push(mutations);
} else {
false
}
};
if !work_complete {
todo!("don't have a mechanism to pause work (yet)");
return committed_mutations;
}
}
@ -495,7 +475,13 @@ impl VirtualDom {
/// apply_edits(edits);
/// ```
pub fn rebuild(&mut self) -> Mutations {
self.hard_diff(&self.base_scope)
// todo: I think we need to append a node or something
// diff_machine
// .stack
// .create_node(cur_component.frames.fin_head(), MountType::Append);
let scope = self.base_scope;
self.hard_diff(&scope).unwrap()
}
/// Compute a manual diff of the VirtualDOM between states.
@ -532,59 +518,26 @@ impl VirtualDom {
///
/// let edits = dom.diff();
/// ```
pub fn hard_diff<'a>(&'a mut self, base_scope: &ScopeId) -> Mutations<'a> {
// // TODO: drain any in-flight work
// // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
// if self.run_scope(&base_scope) {
// let cur_component = self
// .get_scope_mut(&base_scope)
// .expect("The base scope should never be moved");
pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> {
log::debug!("hard diff {:?}", scope_id);
// log::debug!("rebuild {:?}", base_scope);
// let mut diff_machine = DiffState::new(Mutations::new());
// diff_machine
// .stack
// .create_node(cur_component.frames.fin_head(), MountType::Append);
// diff_machine.stack.scope_stack.push(base_scope);
// todo!()
// // self.work(&mut diff_machine, || false);
// // diff_machine.work(|| false);
// } else {
// // todo: should this be a hard error?
// log::warn!(
// "Component failed to run successfully during rebuild.
// This does not result in a failed rebuild, but indicates a logic failure within your app."
// );
// }
// todo!()
// // unsafe { std::mem::transmute(diff_machine.mutations) }
let cur_component = self
.scopes
.get(&base_scope)
.expect("The base scope should never be moved");
log::debug!("hard diff {:?}", base_scope);
if self.run_scope(&base_scope) {
if self.run_scope(&scope_id) {
let mut diff_machine = DiffState::new(Mutations::new());
diff_machine.cfg.force_diff = true;
self.diff_scope(&mut diff_machine, base_scope);
// diff_machine.diff_scope(base_scope);
diff_machine.mutations
diff_machine.force_diff = true;
self.scopes.diff_scope(&mut diff_machine, scope_id);
Some(diff_machine.mutations)
} else {
Mutations::new()
None
}
}
pub fn run_scope(&mut self, id: &ScopeId) -> bool {
pub fn run_scope(&self, id: &ScopeId) -> bool {
let scope = self
.scopes
.get(id)
.get_scope(id)
.expect("The base scope should never be moved");
// Cycle to the next frame and then reset it