1use crate::{AttributeValue, RenderTargetId, Runtime, arena::ElementId};
2use std::{collections::BTreeMap, rc::Rc};
3
4pub trait WriteMutations {
13 #[doc(hidden)]
21 fn can_cache_template_roots(&mut self) -> bool {
22 true
23 }
24
25 fn push_id(&mut self, id: ElementId);
27
28 fn child(&mut self, index: usize);
30
31 fn pop(&mut self);
33
34 fn create_element(&mut self, tag: &str, ns: Option<&str>);
36
37 fn create_text(&mut self, value: &str);
39
40 fn clone(&mut self);
42
43 fn append_children(&mut self, m: usize);
46
47 fn replace_with(&mut self, m: usize);
50
51 fn insert_after(&mut self, m: usize);
54
55 fn insert_before(&mut self, m: usize);
58
59 fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue);
65
66 fn set_text(&mut self, value: &str);
70
71 fn add_event_listener(&mut self, name: &str);
75
76 fn remove_event_listener(&mut self, name: &str);
80
81 fn remove(&mut self);
83
84 fn set_id(&mut self, id: ElementId);
89}
90
91macro_rules! write_mutation_methods {
92 ($macro:ident, $($prefix:tt)*) => {
93 $macro! {
94 $($prefix)*
95 push_id(id: ElementId);
96 child(index: usize);
97 pop();
98 create_element(tag: &str, ns: Option<&str>);
99 create_text(value: &str);
100 clone();
101 append_children(m: usize);
102 replace_with(m: usize);
103 insert_after(m: usize);
104 insert_before(m: usize);
105 set_attribute(name: &str, ns: Option<&str>, value: &AttributeValue);
106 set_text(value: &str);
107 add_event_listener(name: &str);
108 remove_event_listener(name: &str);
109 remove();
110 set_id(id: ElementId);
111 }
112 };
113}
114
115macro_rules! forward_mut_write_mutations {
116 ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
117 $(
118 fn $method(&mut self, $($arg: $arg_ty),*) {
119 WriteMutations::$method(&mut **self, $($arg),*);
120 }
121 )*
122 };
123}
124
125impl<W: WriteMutations + ?Sized> WriteMutations for &mut W {
128 fn can_cache_template_roots(&mut self) -> bool {
129 WriteMutations::can_cache_template_roots(&mut **self)
130 }
131
132 write_mutation_methods!(forward_mut_write_mutations,);
133}
134
135pub(crate) fn with_id<R>(
141 to: &mut dyn WriteMutations,
142 id: ElementId,
143 with_node: impl FnOnce(&mut dyn WriteMutations) -> R,
144) -> R {
145 to.push_id(id);
146 let result = with_node(to);
147 to.pop();
148 result
149}
150
151pub(crate) fn with_consumed_id<R>(
156 to: &mut dyn WriteMutations,
157 id: ElementId,
158 consume_node: impl FnOnce(&mut dyn WriteMutations) -> R,
159) -> R {
160 to.push_id(id);
161 consume_node(to)
162}
163
164pub(crate) fn remove_id(to: &mut dyn WriteMutations, id: ElementId) {
165 with_consumed_id(to, id, |to| to.remove());
166}
167
168pub(crate) struct TargetedLazyScope<'a, P: FnMut(&mut dyn WriteMutations)> {
169 to: &'a mut dyn WriteMutations,
170 push: P,
171 runtime: Rc<Runtime>,
172 target: RenderTargetId,
173 pushed: bool,
174}
175
176impl<'a, P: FnMut(&mut dyn WriteMutations)> TargetedLazyScope<'a, P> {
177 pub(crate) fn new(to: &'a mut dyn WriteMutations, runtime: Rc<Runtime>, push: P) -> Self {
178 let target = runtime.current_render_target_id();
179 Self {
180 to,
181 push,
182 runtime,
183 target,
184 pushed: false,
185 }
186 }
187
188 #[inline]
189 fn ensure_pushed(&mut self) {
190 if !self.pushed && self.runtime.current_render_target_id() == self.target {
191 (self.push)(self.to);
192 self.pushed = true;
193 }
194 }
195}
196
197impl<P: FnMut(&mut dyn WriteMutations)> Drop for TargetedLazyScope<'_, P> {
198 fn drop(&mut self) {
199 if self.pushed {
200 self.to.pop();
201 }
202 }
203}
204
205macro_rules! forward_lazy_scope_write_mutations {
206 ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
207 $(
208 fn $method(&mut self, $($arg: $arg_ty),*) {
209 self.ensure_pushed();
210 WriteMutations::$method(self.to, $($arg),*);
211 }
212 )*
213 };
214}
215
216impl<P: FnMut(&mut dyn WriteMutations)> WriteMutations for TargetedLazyScope<'_, P> {
217 fn can_cache_template_roots(&mut self) -> bool {
218 self.to.can_cache_template_roots()
219 }
220
221 write_mutation_methods!(forward_lazy_scope_write_mutations,);
222}
223
224pub(crate) fn append_children_to(
225 to: &mut dyn WriteMutations,
226 id: ElementId,
227 runtime: Rc<Runtime>,
228 create_children: impl FnOnce(&mut dyn WriteMutations) -> usize,
229) -> usize {
230 append_children_to_with_result(to, id, runtime, |to| {
231 let count = create_children(to);
232 (count, count)
233 })
234}
235
236pub(crate) fn append_children_to_with_result<R>(
237 to: &mut dyn WriteMutations,
238 id: ElementId,
239 runtime: Rc<Runtime>,
240 create_children: impl FnOnce(&mut dyn WriteMutations) -> (usize, R),
241) -> R {
242 let mut to = TargetedLazyScope::new(to, runtime, move |to| to.push_id(id));
243 let (count, result) = create_children(&mut to);
244 if count > 0 {
245 to.append_children(count);
246 }
247 result
248}
249
250pub trait MultiWriter {
259 type Writer: WriteMutations;
261
262 fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut Self::Writer>;
264}
265
266impl<W: WriteMutations> MultiWriter for W {
267 type Writer = W;
268
269 fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
270 if id == RenderTargetId::ROOT {
271 Some(self)
272 } else {
273 None
274 }
275 }
276}
277
278impl<W: WriteMutations> MultiWriter for BTreeMap<RenderTargetId, W> {
279 type Writer = W;
280
281 fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
282 self.get_mut(&id)
283 }
284}
285
286pub(crate) struct TargetRouter<'a, M: MultiWriter> {
287 to: &'a mut M,
288 runtime: Rc<Runtime>,
289}
290
291impl<'a, M: MultiWriter> TargetRouter<'a, M> {
292 pub(crate) fn new(to: &'a mut M, runtime: Rc<Runtime>) -> Self {
293 Self { to, runtime }
294 }
295
296 #[inline]
297 fn current(&mut self) -> Option<&mut M::Writer> {
298 self.to.writer_for(self.runtime.current_render_target_id())
299 }
300
301 #[inline]
302 fn with_current(&mut self, f: impl FnOnce(&mut M::Writer)) {
303 if let Some(w) = self.current() {
304 f(w);
305 }
306 }
307}
308
309macro_rules! route_write_mutations {
310 ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
311 $(
312 fn $method(&mut self, $($arg: $arg_ty),*) {
313 self.with_current(|w| WriteMutations::$method(w, $($arg),*));
314 }
315 )*
316 };
317}
318
319impl<M: MultiWriter> WriteMutations for TargetRouter<'_, M> {
320 fn can_cache_template_roots(&mut self) -> bool {
321 self.current()
322 .is_some_and(WriteMutations::can_cache_template_roots)
323 }
324
325 write_mutation_methods!(route_write_mutations,);
326}
327
328#[derive(Debug, PartialEq)]
333pub enum Mutation {
334 PushId {
336 id: ElementId,
338 },
339 SetId {
341 id: ElementId,
343 },
344 Child {
346 index: usize,
348 },
349 Pop,
351 CreateElement {
353 tag: String,
355 ns: Option<String>,
357 },
358 CreateText {
360 value: String,
362 },
363 Clone,
365 AppendChildren {
367 m: usize,
369 },
370 ReplaceWith {
372 m: usize,
374 },
375 InsertAfter {
377 m: usize,
379 },
380 InsertBefore {
382 m: usize,
384 },
385
386 SetAttribute {
388 name: String,
390
391 ns: Option<String>,
394
395 value: AttributeValue,
397 },
398
399 SetText {
401 value: String,
403 },
404
405 NewEventListener {
407 name: String,
409 },
410
411 RemoveEventListener {
413 name: String,
415 },
416 Remove,
418}
419
420#[derive(Debug, PartialEq, Default)]
422pub struct Mutations {
423 pub edits: Vec<Mutation>,
425}
426
427fn serializable_attribute_value(value: &AttributeValue) -> AttributeValue {
428 match value {
429 AttributeValue::Text(_)
430 | AttributeValue::Bool(_)
431 | AttributeValue::Float(_)
432 | AttributeValue::Int(_)
433 | AttributeValue::None => value.clone(),
434 _ => panic!("Cannot serialize attribute value"),
435 }
436}
437
438macro_rules! collect_field_mutations {
439 ($($method:ident($field:ident: $field_ty:ty) => $variant:ident;)*) => {
440 $(
441 fn $method(&mut self, $field: $field_ty) {
442 self.edits.push(Mutation::$variant { $field })
443 }
444 )*
445 };
446}
447
448macro_rules! collect_string_mutations {
449 ($($method:ident($field:ident) => $variant:ident;)*) => {
450 $(
451 fn $method(&mut self, $field: &str) {
452 self.edits.push(Mutation::$variant {
453 $field: $field.into(),
454 })
455 }
456 )*
457 };
458}
459
460macro_rules! collect_unit_mutations {
461 ($($method:ident => $variant:ident;)*) => {
462 $(
463 fn $method(&mut self) {
464 self.edits.push(Mutation::$variant)
465 }
466 )*
467 };
468}
469
470impl WriteMutations for Mutations {
471 collect_field_mutations! {
472 push_id(id: ElementId) => PushId;
473 set_id(id: ElementId) => SetId;
474 child(index: usize) => Child;
475 append_children(m: usize) => AppendChildren;
476 replace_with(m: usize) => ReplaceWith;
477 insert_after(m: usize) => InsertAfter;
478 insert_before(m: usize) => InsertBefore;
479 }
480
481 fn create_element(&mut self, tag: &str, ns: Option<&str>) {
482 self.edits.push(Mutation::CreateElement {
483 tag: tag.to_string(),
484 ns: ns.map(str::to_string),
485 })
486 }
487
488 collect_string_mutations! {
489 create_text(value) => CreateText;
490 set_text(value) => SetText;
491 add_event_listener(name) => NewEventListener;
492 remove_event_listener(name) => RemoveEventListener;
493 }
494
495 collect_unit_mutations! {
496 pop => Pop;
497 clone => Clone;
498 remove => Remove;
499 }
500
501 fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue) {
502 self.edits.push(Mutation::SetAttribute {
503 name: name.to_string(),
504 ns: ns.map(str::to_string),
505 value: serializable_attribute_value(value),
506 })
507 }
508}
509
510pub struct NoOpMutations;
512
513macro_rules! noop_write_mutations {
514 ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
515 $(
516 fn $method(&mut self, $($arg: $arg_ty),*) {
517 $(let _ = $arg;)*
518 }
519 )*
520 };
521}
522
523impl WriteMutations for NoOpMutations {
524 fn can_cache_template_roots(&mut self) -> bool {
525 false
526 }
527
528 write_mutation_methods!(noop_write_mutations,);
529}