use crate::{AttributeValue, RenderTargetId, Runtime, arena::ElementId};
use std::{collections::BTreeMap, rc::Rc};
pub trait WriteMutations {
#[doc(hidden)]
fn can_cache_template_roots(&mut self) -> bool {
true
}
fn push_id(&mut self, id: ElementId);
fn child(&mut self, index: usize);
fn pop(&mut self);
fn create_element(&mut self, tag: &str, ns: Option<&str>);
fn create_text(&mut self, value: &str);
fn clone(&mut self);
fn append_children(&mut self, m: usize);
fn replace_with(&mut self, m: usize);
fn insert_after(&mut self, m: usize);
fn insert_before(&mut self, m: usize);
fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue);
fn set_text(&mut self, value: &str);
fn add_event_listener(&mut self, name: &str);
fn remove_event_listener(&mut self, name: &str);
fn remove(&mut self);
fn set_id(&mut self, id: ElementId);
}
macro_rules! write_mutation_methods {
($macro:ident, $($prefix:tt)*) => {
$macro! {
$($prefix)*
push_id(id: ElementId);
child(index: usize);
pop();
create_element(tag: &str, ns: Option<&str>);
create_text(value: &str);
clone();
append_children(m: usize);
replace_with(m: usize);
insert_after(m: usize);
insert_before(m: usize);
set_attribute(name: &str, ns: Option<&str>, value: &AttributeValue);
set_text(value: &str);
add_event_listener(name: &str);
remove_event_listener(name: &str);
remove();
set_id(id: ElementId);
}
};
}
macro_rules! forward_mut_write_mutations {
($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
$(
fn $method(&mut self, $($arg: $arg_ty),*) {
WriteMutations::$method(&mut **self, $($arg),*);
}
)*
};
}
impl<W: WriteMutations + ?Sized> WriteMutations for &mut W {
fn can_cache_template_roots(&mut self) -> bool {
WriteMutations::can_cache_template_roots(&mut **self)
}
write_mutation_methods!(forward_mut_write_mutations,);
}
pub(crate) fn with_id<R>(
to: &mut dyn WriteMutations,
id: ElementId,
with_node: impl FnOnce(&mut dyn WriteMutations) -> R,
) -> R {
to.push_id(id);
let result = with_node(to);
to.pop();
result
}
pub(crate) fn with_consumed_id<R>(
to: &mut dyn WriteMutations,
id: ElementId,
consume_node: impl FnOnce(&mut dyn WriteMutations) -> R,
) -> R {
to.push_id(id);
consume_node(to)
}
pub(crate) fn remove_id(to: &mut dyn WriteMutations, id: ElementId) {
with_consumed_id(to, id, |to| to.remove());
}
pub(crate) struct TargetedLazyScope<'a, P: FnMut(&mut dyn WriteMutations)> {
to: &'a mut dyn WriteMutations,
push: P,
runtime: Rc<Runtime>,
target: RenderTargetId,
pushed: bool,
}
impl<'a, P: FnMut(&mut dyn WriteMutations)> TargetedLazyScope<'a, P> {
pub(crate) fn new(to: &'a mut dyn WriteMutations, runtime: Rc<Runtime>, push: P) -> Self {
let target = runtime.current_render_target_id();
Self {
to,
push,
runtime,
target,
pushed: false,
}
}
#[inline]
fn ensure_pushed(&mut self) {
if !self.pushed && self.runtime.current_render_target_id() == self.target {
(self.push)(self.to);
self.pushed = true;
}
}
}
impl<P: FnMut(&mut dyn WriteMutations)> Drop for TargetedLazyScope<'_, P> {
fn drop(&mut self) {
if self.pushed {
self.to.pop();
}
}
}
macro_rules! forward_lazy_scope_write_mutations {
($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
$(
fn $method(&mut self, $($arg: $arg_ty),*) {
self.ensure_pushed();
WriteMutations::$method(self.to, $($arg),*);
}
)*
};
}
impl<P: FnMut(&mut dyn WriteMutations)> WriteMutations for TargetedLazyScope<'_, P> {
fn can_cache_template_roots(&mut self) -> bool {
self.to.can_cache_template_roots()
}
write_mutation_methods!(forward_lazy_scope_write_mutations,);
}
pub(crate) fn append_children_to(
to: &mut dyn WriteMutations,
id: ElementId,
runtime: Rc<Runtime>,
create_children: impl FnOnce(&mut dyn WriteMutations) -> usize,
) -> usize {
append_children_to_with_result(to, id, runtime, |to| {
let count = create_children(to);
(count, count)
})
}
pub(crate) fn append_children_to_with_result<R>(
to: &mut dyn WriteMutations,
id: ElementId,
runtime: Rc<Runtime>,
create_children: impl FnOnce(&mut dyn WriteMutations) -> (usize, R),
) -> R {
let mut to = TargetedLazyScope::new(to, runtime, move |to| to.push_id(id));
let (count, result) = create_children(&mut to);
if count > 0 {
to.append_children(count);
}
result
}
pub trait MultiWriter {
type Writer: WriteMutations;
fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut Self::Writer>;
}
impl<W: WriteMutations> MultiWriter for W {
type Writer = W;
fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
if id == RenderTargetId::ROOT {
Some(self)
} else {
None
}
}
}
impl<W: WriteMutations> MultiWriter for BTreeMap<RenderTargetId, W> {
type Writer = W;
fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
self.get_mut(&id)
}
}
pub(crate) struct TargetRouter<'a, M: MultiWriter> {
to: &'a mut M,
runtime: Rc<Runtime>,
}
impl<'a, M: MultiWriter> TargetRouter<'a, M> {
pub(crate) fn new(to: &'a mut M, runtime: Rc<Runtime>) -> Self {
Self { to, runtime }
}
#[inline]
fn current(&mut self) -> Option<&mut M::Writer> {
self.to.writer_for(self.runtime.current_render_target_id())
}
#[inline]
fn with_current(&mut self, f: impl FnOnce(&mut M::Writer)) {
if let Some(w) = self.current() {
f(w);
}
}
}
macro_rules! route_write_mutations {
($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
$(
fn $method(&mut self, $($arg: $arg_ty),*) {
self.with_current(|w| WriteMutations::$method(w, $($arg),*));
}
)*
};
}
impl<M: MultiWriter> WriteMutations for TargetRouter<'_, M> {
fn can_cache_template_roots(&mut self) -> bool {
self.current()
.is_some_and(WriteMutations::can_cache_template_roots)
}
write_mutation_methods!(route_write_mutations,);
}
#[derive(Debug, PartialEq)]
pub enum Mutation {
PushId {
id: ElementId,
},
SetId {
id: ElementId,
},
Child {
index: usize,
},
Pop,
CreateElement {
tag: String,
ns: Option<String>,
},
CreateText {
value: String,
},
Clone,
AppendChildren {
m: usize,
},
ReplaceWith {
m: usize,
},
InsertAfter {
m: usize,
},
InsertBefore {
m: usize,
},
SetAttribute {
name: String,
ns: Option<String>,
value: AttributeValue,
},
SetText {
value: String,
},
NewEventListener {
name: String,
},
RemoveEventListener {
name: String,
},
Remove,
}
#[derive(Debug, PartialEq, Default)]
pub struct Mutations {
pub edits: Vec<Mutation>,
}
fn serializable_attribute_value(value: &AttributeValue) -> AttributeValue {
match value {
AttributeValue::Text(_)
| AttributeValue::Bool(_)
| AttributeValue::Float(_)
| AttributeValue::Int(_)
| AttributeValue::None => value.clone(),
_ => panic!("Cannot serialize attribute value"),
}
}
macro_rules! collect_field_mutations {
($($method:ident($field:ident: $field_ty:ty) => $variant:ident;)*) => {
$(
fn $method(&mut self, $field: $field_ty) {
self.edits.push(Mutation::$variant { $field })
}
)*
};
}
macro_rules! collect_string_mutations {
($($method:ident($field:ident) => $variant:ident;)*) => {
$(
fn $method(&mut self, $field: &str) {
self.edits.push(Mutation::$variant {
$field: $field.into(),
})
}
)*
};
}
macro_rules! collect_unit_mutations {
($($method:ident => $variant:ident;)*) => {
$(
fn $method(&mut self) {
self.edits.push(Mutation::$variant)
}
)*
};
}
impl WriteMutations for Mutations {
collect_field_mutations! {
push_id(id: ElementId) => PushId;
set_id(id: ElementId) => SetId;
child(index: usize) => Child;
append_children(m: usize) => AppendChildren;
replace_with(m: usize) => ReplaceWith;
insert_after(m: usize) => InsertAfter;
insert_before(m: usize) => InsertBefore;
}
fn create_element(&mut self, tag: &str, ns: Option<&str>) {
self.edits.push(Mutation::CreateElement {
tag: tag.to_string(),
ns: ns.map(str::to_string),
})
}
collect_string_mutations! {
create_text(value) => CreateText;
set_text(value) => SetText;
add_event_listener(name) => NewEventListener;
remove_event_listener(name) => RemoveEventListener;
}
collect_unit_mutations! {
pop => Pop;
clone => Clone;
remove => Remove;
}
fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue) {
self.edits.push(Mutation::SetAttribute {
name: name.to_string(),
ns: ns.map(str::to_string),
value: serializable_attribute_value(value),
})
}
}
pub struct NoOpMutations;
macro_rules! noop_write_mutations {
($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
$(
fn $method(&mut self, $($arg: $arg_ty),*) {
$(let _ = $arg;)*
}
)*
};
}
impl WriteMutations for NoOpMutations {
fn can_cache_template_roots(&mut self) -> bool {
false
}
write_mutation_methods!(noop_write_mutations,);
}