use crate::Task;
use crate::{
any_props::AnyProps,
arena::ElementId,
innerlude::{
DirtyTasks, ElementRef, ErrorBoundary, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState,
VNodeMount, VProps, WriteMutations,
},
nodes::RenderReturn,
nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard},
scopes::ScopeId,
AttributeValue, ComponentFunction, Element, Event, Mutations, VNode,
};
use futures_util::StreamExt;
use rustc_hash::FxHashMap;
use slab::Slab;
use std::collections::BTreeSet;
use std::{any::Any, rc::Rc};
use tracing::instrument;
pub struct VirtualDom {
pub(crate) scopes: Slab<ScopeState>,
pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
pub(crate) dirty_tasks: BTreeSet<DirtyTasks>,
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template>>,
pub(crate) queued_templates: Vec<Template>,
pub(crate) elements: Slab<Option<ElementRef>>,
pub(crate) mounts: Slab<VNodeMount>,
pub(crate) runtime: Rc<Runtime>,
rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
}
impl VirtualDom {
pub fn new(app: fn() -> Element) -> Self {
Self::new_with_props(app, ())
}
pub fn new_with_props<P: Clone + 'static, M: 'static>(
root: impl ComponentFunction<P, M>,
root_props: P,
) -> Self {
Self::new_with_component(VProps::new(root, |_, _| true, root_props, "root"))
}
pub fn prebuilt(app: fn() -> Element) -> Self {
let mut dom = Self::new(app);
dom.rebuild_in_place();
dom
}
#[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
pub(crate) fn new_with_component(root: impl AnyProps + 'static) -> Self {
let (tx, rx) = futures_channel::mpsc::unbounded();
let mut dom = Self {
rx,
runtime: Runtime::new(tx),
scopes: Default::default(),
dirty_scopes: Default::default(),
dirty_tasks: Default::default(),
templates: Default::default(),
queued_templates: Default::default(),
elements: Default::default(),
mounts: Default::default(),
};
let root = dom.new_scope(Box::new(root), "app");
root.state()
.provide_context(Rc::new(ErrorBoundary::new_in_scope(ScopeId::ROOT)));
dom.elements.insert(None);
dom
}
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get(id.0)
}
pub fn base_scope(&self) -> &ScopeState {
self.get_scope(ScopeId::ROOT).unwrap()
}
#[instrument(skip(self, f), level = "trace", name = "VirtualDom::in_runtime")]
pub fn in_runtime<O>(&self, f: impl FnOnce() -> O) -> O {
let _runtime = RuntimeGuard::new(self.runtime.clone());
f()
}
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
self.base_scope().state().provide_context(context);
self
}
pub fn provide_root_context<T: Clone + 'static>(&self, context: T) {
self.base_scope().state().provide_context(context);
}
pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
self.base_scope().state().provide_any_context(context);
}
pub fn mark_dirty(&mut self, id: ScopeId) {
let Some(scope) = self.runtime.get_state(id) else {
return;
};
tracing::event!(tracing::Level::TRACE, "Marking scope {:?} as dirty", id);
let order = ScopeOrder::new(scope.height(), id);
drop(scope);
self.queue_scope(order);
}
fn mark_task_dirty(&mut self, task: Task) {
let Some(scope) = self.runtime.task_scope(task) else {
return;
};
let Some(scope) = self.runtime.get_state(scope) else {
return;
};
tracing::event!(
tracing::Level::TRACE,
"Marking task {:?} (spawned in {:?}) as dirty",
task,
scope.id
);
let order = ScopeOrder::new(scope.height(), scope.id);
drop(scope);
self.queue_task(task, order);
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::handle_event")]
pub fn handle_event(
&mut self,
name: &str,
data: Rc<dyn Any>,
element: ElementId,
bubbles: bool,
) {
let _runtime = RuntimeGuard::new(self.runtime.clone());
if let Some(Some(parent_path)) = self.elements.get(element.0).copied() {
if bubbles {
self.handle_bubbling_event(parent_path, name, Event::new(data, bubbles));
} else {
self.handle_non_bubbling_event(parent_path, name, Event::new(data, bubbles));
}
}
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")]
pub async fn wait_for_work(&mut self) {
loop {
self.process_events();
if self.has_dirty_scopes() {
return;
}
let _runtime = RuntimeGuard::new(self.runtime.clone());
self.wait_for_event().await;
}
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_event")]
async fn wait_for_event(&mut self) {
match self.rx.next().await.expect("channel should never close") {
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(id) => {
self.mark_task_dirty(id);
}
SchedulerMsg::EffectQueued => {}
};
}
fn queue_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.mark_task_dirty(task),
SchedulerMsg::EffectQueued => {}
}
}
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::process_events")]
pub fn process_events(&mut self) {
self.queue_events();
if self.has_dirty_scopes() {
return;
}
self.poll_tasks()
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::poll_tasks")]
fn poll_tasks(&mut self) {
let _runtime = RuntimeGuard::new(self.runtime.clone());
while !self.dirty_tasks.is_empty() || !self.runtime.pending_effects.borrow().is_empty() {
while let Some(task) = self.pop_task() {
let mut tasks = task.tasks_queued.into_inner();
while let Some(task) = tasks.pop_front() {
let _ = self.runtime.handle_task_wakeup(task);
self.queue_events();
if self.has_dirty_scopes() {
for task in tasks {
self.mark_task_dirty(task);
}
return;
}
}
}
while let Some(effect) = self.pop_effect() {
effect.run(&self.runtime);
self.queue_events();
if self.has_dirty_scopes() {
return;
}
}
}
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::replace_template")]
pub fn replace_template(&mut self, template: Template) {
self.register_template_first_byte_index(template);
let mut dirty = Vec::new();
for (id, scope) in self.scopes.iter() {
fn check_node_for_templates(node: &VNode, template: Template) -> bool {
let this_template_name = node.template.get().name.rsplit_once(':').unwrap().0;
if this_template_name == template.name.rsplit_once(':').unwrap().0 {
return true;
}
for dynamic in node.dynamic_nodes.iter() {
if let crate::DynamicNode::Fragment(nodes) = dynamic {
for node in nodes {
if check_node_for_templates(node, template) {
return true;
}
}
}
}
false
}
if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() {
if check_node_for_templates(sync, template) {
dirty.push(ScopeId(id));
}
}
}
for dirty in dirty {
self.mark_dirty(dirty);
}
}
pub fn rebuild_in_place(&mut self) {
self.rebuild(&mut NoOpMutations);
}
pub fn rebuild_to_vec(&mut self) -> Mutations {
let mut mutations = Mutations::default();
self.rebuild(&mut mutations);
mutations
}
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
let _runtime = RuntimeGuard::new(self.runtime.clone());
let new_nodes = self.run_scope(ScopeId::ROOT);
let m = self.create_scope(to, ScopeId::ROOT, new_nodes, None);
to.append_children(ElementId(0), m);
}
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
self.flush_templates(to);
self.process_events();
while let Some(work) = self.pop_work() {
{
let _runtime = RuntimeGuard::new(self.runtime.clone());
for task in work.tasks {
let _ = self.runtime.handle_task_wakeup(task);
}
self.queue_events();
if work.rerun_scope {
let new_nodes = self.run_scope(work.scope.id);
self.diff_scope(to, work.scope.id, new_nodes);
}
}
}
self.runtime.finish_render();
}
pub fn render_immediate_to_vec(&mut self) -> Mutations {
let mut mutations = Mutations::default();
self.render_immediate(&mut mutations);
mutations
}
#[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_suspense")]
pub async fn wait_for_suspense(&mut self) {
loop {
if self.runtime.suspended_tasks.get() == 0 {
break;
}
'wait_for_work: loop {
self.queue_events();
if self.has_dirty_scopes() {
break;
}
{
let _runtime = RuntimeGuard::new(self.runtime.clone());
while let Some(task) = self.pop_task() {
let mut tasks = task.tasks_queued.into_inner();
while let Some(task) = tasks.pop_front() {
if self.runtime.task_runs_during_suspense(task) {
let _ = self.runtime.handle_task_wakeup(task);
self.queue_events();
if self.has_dirty_scopes() {
for task in tasks {
self.mark_task_dirty(task);
}
break 'wait_for_work;
}
}
}
}
}
self.wait_for_event().await;
}
let _runtime = RuntimeGuard::new(self.runtime.clone());
while let Some(work) = self.pop_work() {
for task in work.tasks {
if self.runtime.task_runs_during_suspense(task) {
let _ = self.runtime.handle_task_wakeup(task);
}
}
self.queue_events();
if work.rerun_scope {
let new_nodes = self.run_scope(work.scope.id);
self.diff_scope(&mut NoOpMutations, work.scope.id, new_nodes);
}
}
}
}
pub fn runtime(&self) -> Rc<Runtime> {
self.runtime.clone()
}
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::flush_templates")]
fn flush_templates(&mut self, to: &mut impl WriteMutations) {
for template in self.queued_templates.drain(..) {
to.register_template(template);
}
}
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_bubbling_event"
)]
fn handle_bubbling_event(&mut self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
let mut parent = Some(parent);
while let Some(path) = parent {
let mut listeners = vec![];
let el_ref = &self.mounts[path.mount.0].node;
let node_template = el_ref.template.get();
let target_path = path.path;
for (idx, this_path) in node_template.breadth_first_attribute_paths() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
if attr.name.trim_start_matches("on") == name
&& target_path.is_decendant(this_path)
{
listeners.push(&attr.value);
if target_path == this_path {
break;
}
}
}
}
tracing::event!(
tracing::Level::TRACE,
"Calling {} listeners",
listeners.len()
);
tracing::info!("Listeners: {:?}", listeners);
for listener in listeners.into_iter().rev() {
if let AttributeValue::Listener(listener) = listener {
self.runtime.rendering.set(false);
listener.call(uievent.clone());
self.runtime.rendering.set(true);
if !uievent.propagates.get() {
return;
}
}
}
let mount = el_ref.mount.get().as_usize();
parent = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
}
}
#[instrument(
skip(self, uievent),
level = "trace",
name = "VirtualDom::handle_non_bubbling_event"
)]
fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
let el_ref = &self.mounts[node.mount.0].node;
let node_template = el_ref.template.get();
let target_path = node.path;
for (idx, this_path) in node_template.breadth_first_attribute_paths() {
let attrs = &*el_ref.dynamic_attrs[idx];
for attr in attrs.iter() {
if attr.name.trim_start_matches("on") == name && target_path == this_path {
if let AttributeValue::Listener(listener) = &attr.value {
self.runtime.rendering.set(false);
listener.call(uievent.clone());
self.runtime.rendering.set(true);
break;
}
}
}
}
}
}
impl Drop for VirtualDom {
fn drop(&mut self) {
let mut scopes = self.scopes.drain().collect::<Vec<_>>();
scopes.sort_by_key(|scope| scope.state().height);
for scope in scopes.into_iter().rev() {
drop(scope);
}
}
}