use crate::{
any_props::VProps,
arena::{ElementId, ElementRef},
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg, ScopeSlab},
mutations::Mutation,
nodes::RenderReturn,
nodes::{Template, TemplateId},
scheduler::SuspenseId,
scopes::{ScopeId, ScopeState},
AttributeValue, Element, Event, Scope, SuspenseContext,
};
use futures_util::{pin_mut, StreamExt};
use rustc_hash::FxHashMap;
use slab::Slab;
use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
pub struct VirtualDom {
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
pub(crate) scopes: ScopeSlab,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,
pub(crate) elements: Slab<ElementRef>,
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) collected_leaves: Vec<SuspenseId>,
pub(crate) finished_fibers: Vec<ScopeId>,
pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
pub(crate) mutations: Mutations<'static>,
}
impl VirtualDom {
pub fn new(app: fn(Scope) -> Element) -> Self {
Self::new_with_props(app, ())
}
pub fn new_with_props<P: 'static>(root: fn(Scope<P>) -> Element, root_props: P) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
let mut dom = Self {
rx,
scheduler: Scheduler::new(tx),
templates: Default::default(),
scopes: Default::default(),
elements: Default::default(),
scope_stack: Vec::new(),
dirty_scopes: BTreeSet::new(),
collected_leaves: Vec::new(),
finished_fibers: Vec::new(),
mutations: Mutations::default(),
};
let root = dom.new_scope(
Box::new(VProps::new(root, |_, _| unreachable!(), root_props)),
"app",
);
root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
dom.elements.insert(ElementRef::none());
dom
}
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id)
}
pub fn base_scope(&self) -> &ScopeState {
self.scopes.get(ScopeId(0)).unwrap()
}
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
self.base_scope().provide_context(context);
self
}
pub fn mark_dirty(&mut self, id: ScopeId) {
if let Some(scope) = self.scopes.get(id) {
let height = scope.height;
self.dirty_scopes.insert(DirtyScope { height, id });
}
}
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
!self.scopes[id]
.consume_context::<Rc<SuspenseContext>>()
.unwrap()
.waiting_on
.borrow()
.is_empty()
}
pub fn has_suspended_work(&self) -> bool {
!self.scheduler.leaves.borrow().is_empty()
}
pub fn handle_event(
&mut self,
name: &str,
data: Rc<dyn Any>,
element: ElementId,
bubbles: bool,
) {
let mut parent_path = self.elements.get(element.0);
let mut listeners = vec![];
let uievent = Event {
propagates: Rc::new(Cell::new(bubbles)),
data,
};
while let Some(el_ref) = parent_path {
let template = unsafe { el_ref.template.unwrap().as_ref() };
let node_template = template.template.get();
let target_path = el_ref.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
if attr.name.trim_start_matches("on") == name
&& target_path.is_ascendant(&this_path)
{
listeners.push(&attr.value);
if !bubbles {
break;
}
if target_path == this_path {
break;
}
}
}
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
if !uievent.propagates.get() {
return;
}
}
}
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
}
}
pub async fn wait_for_work(&mut self) {
let mut some_msg = None;
loop {
match some_msg.take() {
Some(msg) => match msg {
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
},
None => {
match self.rx.try_next() {
Ok(Some(val)) => some_msg = Some(val),
Ok(None) => return,
Err(_) => {
if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
return;
}
some_msg = self.rx.next().await
}
}
}
}
}
}
pub fn process_events(&mut self) {
while let Ok(Some(msg)) = self.rx.try_next() {
match msg {
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
}
}
}
pub fn replace_template(&mut self, template: Template<'static>) {
self.register_template_first_byte_index(template);
for scope in self.scopes.iter() {
if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() {
if sync.template.get().name.rsplit_once(':').unwrap().0
== template.name.rsplit_once(':').unwrap().0
{
let height = scope.height;
self.dirty_scopes.insert(DirtyScope {
height,
id: scope.id,
});
}
}
}
}
pub fn rebuild(&mut self) -> Mutations {
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
RenderReturn::Ready(node) => {
let m = self.create_scope(ScopeId(0), node);
self.mutations.edits.push(Mutation::AppendChildren {
id: ElementId(0),
m,
});
}
RenderReturn::Aborted(_placeholder) => panic!("Cannot catch errors during rebuild"),
RenderReturn::Pending(_) => unreachable!("Root scope cannot be an async component"),
}
self.finalize()
}
pub fn render_immediate(&mut self) -> Mutations {
let waker = futures_util::task::noop_waker();
let mut cx = std::task::Context::from_waker(&waker);
let fut = self.render_with_deadline(std::future::ready(()));
pin_mut!(fut);
match fut.poll(&mut cx) {
std::task::Poll::Ready(mutations) => mutations,
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
}
}
pub async fn render_with_deadline(&mut self, deadline: impl Future<Output = ()>) -> Mutations {
pin_mut!(deadline);
self.process_events();
loop {
for finished_fiber in self.finished_fibers.drain(..) {
let scope = &self.scopes[finished_fiber];
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
self.mutations
.templates
.append(&mut context.mutations.borrow_mut().templates);
self.mutations
.edits
.append(&mut context.mutations.borrow_mut().edits);
self.mutations.push(Mutation::ReplaceWith {
id: context.placeholder.get().unwrap(),
m: 1,
})
}
if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
self.dirty_scopes.remove(&dirty);
if !self.scopes.contains(dirty.id) {
continue;
}
if self.is_scope_suspended(dirty.id) {
continue;
}
let mutations_to_this_point = self.mutations.edits.len();
self.run_scope(dirty.id);
self.diff_scope(dirty.id);
if !self.collected_leaves.is_empty() {
let mut boundary = self.scopes[dirty.id]
.consume_context::<Rc<SuspenseContext>>()
.unwrap();
let boundary_mut = boundary.borrow_mut();
boundary_mut
.mutations
.borrow_mut()
.edits
.extend(self.mutations.edits.split_off(mutations_to_this_point));
boundary
.waiting_on
.borrow_mut()
.extend(self.collected_leaves.drain(..));
}
}
if !self.dirty_scopes.is_empty() {
continue;
}
if self.scheduler.leaves.borrow().is_empty() {
return self.finalize();
}
let mut work = self.wait_for_work();
let pinned = unsafe { std::pin::Pin::new_unchecked(&mut work) };
use futures_util::future::{select, Either};
if let Either::Left((_, _)) = select(&mut deadline, pinned).await {
drop(work);
return self.finalize();
}
}
}
fn finalize(&mut self) -> Mutations {
std::mem::take(&mut self.mutations)
}
}
impl Drop for VirtualDom {
fn drop(&mut self) {
self.drop_scope(ScopeId(0), true);
}
}