1use self::value_cache::IntoJsValue;
2use crate::{
3 data::{AttributeName, AttributeValue, EventListenerWrapper, KorvinClosure, TagName},
4 mutation::{
5 element::builder_mutation::{
6 marker::create::ElementCreateMutation,
7 marker::finish::ElementFinishMutation,
8 modify::set_text::ElementSetTextMutation,
9 modify::{
10 add_event_listener::{by_event_kind, ElementAddEventListenerMutation},
11 ElementBuilderModifyMutation,
12 },
13 modify::{
14 set_attribute::ElementSetAttributeMutation,
15 set_input_value::ElementSetInputValueMutation,
16 },
17 },
18 traits::Perform,
19 },
20};
21use std::{collections::BTreeMap, hash::Hasher, iter::empty, sync::Arc};
22use wasm_bindgen::{
23 convert::{FromWasmAbi, RefFromWasmAbi},
24 prelude::Closure,
25};
26
27pub mod value_cache {
28 use super::calculate_hash;
29 use crate::data::Value;
30 use std::{cell::RefCell, collections::BTreeMap};
31
32 pub trait IntoJsValue: std::hash::Hash + Copy {
33 fn into_value(self) -> Value;
34 }
35
36 impl<T> IntoJsValue for T
37 where
38 T: Into<Value> + std::hash::Hash + Copy,
39 {
40 fn into_value(self) -> Value {
41 self.into()
42 }
43 }
44
45 #[derive(Default)]
46 pub(crate) struct ValueCache {
47 previous: BTreeMap<u64, Value>,
48 current: BTreeMap<u64, Value>,
49 }
50
51 impl ValueCache {
52 pub(super) fn cached(&mut self, value: impl IntoJsValue) -> Value {
53 let hash = calculate_hash(&value);
54 self.current
55 .entry(hash)
56 .or_insert_with(|| {
57 self.previous
58 .remove(&hash)
59 .unwrap_or_else(|| value.into_value())
60 })
61 .clone()
62 }
63 pub(crate) fn next_rebuild(&mut self) {
64 std::mem::swap(&mut self.current, &mut self.previous);
65 self.current.clear();
66 }
67 }
68
69 thread_local! {
70 pub(crate) static VALUE_CACHE: RefCell<ValueCache> = Default::default();
71 }
72}
73
74pub trait AsElementBuilder {
75 fn into_builder(self) -> ElementBuilder;
76 fn builder(kind: impl IntoJsValue) -> ElementBuilder {
77 ElementBuilder::builder(kind)
78 }
79 fn text(self, text: impl IntoJsValue) -> ElementBuilder;
80 fn input_value(self, value: impl IntoJsValue) -> ElementBuilder;
81 fn event<Key: std::hash::Hash, EventKind>(
82 self,
83 key: Key,
84 name: impl IntoJsValue,
85 callback: impl Fn(EventKind) + 'static,
86 ) -> ElementBuilder
87 where
88 EventKind: std::fmt::Debug + Sized + RefFromWasmAbi + FromWasmAbi + 'static,
89 by_event_kind::ElementAddEventListenerMutation<EventKind>:
90 Into<ElementAddEventListenerMutation> + Perform;
91 fn child(self, child: impl Into<ElementBuilder>) -> ElementBuilder;
92 fn key(self, key: impl std::hash::Hash) -> ElementBuilder;
93 fn children(
94 self,
95 children: impl IntoIterator<Item = impl Into<ElementBuilder>>,
96 ) -> ElementBuilder;
97 fn attribute(self, attribute: impl IntoJsValue, value: impl IntoJsValue) -> ElementBuilder;
98 fn build(self) -> ElementWithChildrenRecipe;
99}
100
101pub struct ElementBuilder {
102 key: Option<u64>,
103 kind: TagName,
104 attributes: BTreeMap<AttributeName, AttributeValue>,
105 text: Option<AttributeValue>,
106 input_value: Option<AttributeValue>,
107 children: Vec<ElementBuilder>,
108 event_listeners: Vec<ElementAddEventListenerMutation>,
109}
110
111pub fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
112 let mut s = std::collections::hash_map::DefaultHasher::new();
113 t.hash(&mut s);
114 s.finish()
115}
116
117#[derive(Debug)]
118pub struct ElementRecipe {
119 pub key: Option<u64>,
120 pub create: ElementCreateMutation,
121 pub modify: Vec<ElementBuilderModifyMutation>,
122 pub finish: ElementFinishMutation,
123}
124
125#[derive(Debug)]
126pub struct ElementWithChildrenRecipe {
127 pub element: ElementRecipe,
128 pub children: Vec<Self>,
129}
130
131macro_rules! cached {
132 ($value:expr) => {
133 value_cache::VALUE_CACHE.with(|value_cache| {
134 value_cache
135 .try_borrow_mut()
136 .ok()
137 .map(|mut value_cache| value_cache.cached($value))
138 .unwrap_or_else(|| $value.into_value())
139 })
140 };
141}
142
143impl<'a> From<&'a str> for ElementBuilder {
144 fn from(val: &'a str) -> Self {
145 ElementBuilder::builder(val)
146 }
147}
148
149impl<'a> AsElementBuilder for &'a str {
150 fn into_builder(self) -> ElementBuilder {
151 ElementBuilder::from(self).into_builder()
152 }
153
154 fn text(self, text: impl IntoJsValue) -> ElementBuilder {
155 ElementBuilder::from(self).text(text)
156 }
157
158 fn input_value(self, value: impl IntoJsValue) -> ElementBuilder {
159 ElementBuilder::from(self).input_value(value)
160 }
161
162 fn event<Key: std::hash::Hash, EventKind>(
163 self,
164 key: Key,
165 name: impl IntoJsValue,
166 callback: impl Fn(EventKind) + 'static,
167 ) -> ElementBuilder
168 where
169 EventKind: std::fmt::Debug + Sized + RefFromWasmAbi + FromWasmAbi + 'static,
170 by_event_kind::ElementAddEventListenerMutation<EventKind>:
171 Into<ElementAddEventListenerMutation> + Perform,
172 {
173 ElementBuilder::from(self).event(key, name, callback)
174 }
175
176 fn child(self, child: impl Into<ElementBuilder>) -> ElementBuilder {
177 ElementBuilder::from(self).child(child.into())
178 }
179
180 fn key(self, key: impl std::hash::Hash) -> ElementBuilder {
181 ElementBuilder::from(self).key(key)
182 }
183
184 fn children(
185 self,
186 children: impl IntoIterator<Item = impl Into<ElementBuilder>>,
187 ) -> ElementBuilder {
188 ElementBuilder::from(self).children(children)
189 }
190
191 fn attribute(self, attribute: impl IntoJsValue, value: impl IntoJsValue) -> ElementBuilder {
192 ElementBuilder::from(self).attribute(attribute, value)
193 }
194
195 fn build(self) -> ElementWithChildrenRecipe {
196 ElementBuilder::from(self).build()
197 }
198}
199
200impl AsElementBuilder for ElementBuilder {
201 fn into_builder(self) -> ElementBuilder {
202 self
203 }
204 fn builder(kind: impl IntoJsValue) -> Self {
205 let kind = cached!(kind).into();
206 Self {
207 key: None,
208 kind,
209 input_value: None,
210 text: None,
211 event_listeners: Default::default(),
212 attributes: Default::default(),
213 children: Default::default(),
214 }
215 }
216
217 fn text(mut self, text: impl IntoJsValue) -> Self {
218 self.text = Some(cached!(text).into());
219 self
220 }
221
222 fn input_value(mut self, value: impl IntoJsValue) -> Self {
223 self.input_value = Some(cached!(value).into());
224 self
225 }
226 fn event<Key: std::hash::Hash, EventKind>(
227 mut self,
228 key: Key,
229 name: impl IntoJsValue,
230 callback: impl Fn(EventKind) + 'static,
231 ) -> Self
232 where
233 EventKind: std::fmt::Debug + Sized + RefFromWasmAbi + FromWasmAbi + 'static,
234 by_event_kind::ElementAddEventListenerMutation<EventKind>:
235 Into<ElementAddEventListenerMutation> + Perform,
236 {
237 let hash = calculate_hash(&key);
238 let listener = EventListenerWrapper::<EventKind> {
239 name: cached!(name).into(),
240 closure: KorvinClosure {
241 hash,
242 closure: Arc::new(Closure::new(callback)),
243 },
244 };
245 self.event_listeners
246 .push(by_event_kind::ElementAddEventListenerMutation { listener }.into());
247 self
248 }
249 fn child(mut self, child: impl Into<ElementBuilder>) -> Self {
250 self.children.push(child.into());
251 self
252 }
253 fn key(mut self, key: impl std::hash::Hash) -> Self {
254 self.key = Some(calculate_hash(&key));
255 self
256 }
257
258 fn children(self, children: impl IntoIterator<Item = impl Into<ElementBuilder>>) -> Self {
259 children
260 .into_iter()
261 .fold(self, |parent, child| parent.child(child))
262 }
263
264 fn attribute(mut self, attribute: impl IntoJsValue, value: impl IntoJsValue) -> Self {
265 self.attributes
266 .insert(cached!(attribute).into(), cached!(value).into());
267 self
268 }
269
270 fn build(self) -> ElementWithChildrenRecipe {
271 let Self {
272 key,
273 kind,
274 attributes,
275 children,
276 text,
277 event_listeners,
278 input_value,
279 } = self;
280
281 let element = ElementRecipe {
282 key,
283 create: ElementCreateMutation { kind },
284 modify: empty()
285 .chain(
286 attributes
287 .into_iter()
288 .map(|(attribute, value)| ElementSetAttributeMutation {
289 attribute,
290 value: Some(value),
291 })
292 .map(ElementBuilderModifyMutation::from),
293 )
294 .chain(
295 text.into_iter()
296 .map(|value| ElementSetTextMutation { value: Some(value) })
297 .map(ElementBuilderModifyMutation::from),
298 )
299 .chain(
300 input_value
301 .into_iter()
302 .map(|value| ElementSetInputValueMutation { value })
303 .map(ElementBuilderModifyMutation::from),
304 )
305 .chain(
306 event_listeners
307 .into_iter()
308 .map(ElementBuilderModifyMutation::from),
309 )
310 .collect(),
311 finish: ElementFinishMutation {},
312 };
313 ElementWithChildrenRecipe {
314 element,
315 children: children.into_iter().map(Self::build).collect(),
316 }
317 }
318}