Skip to main content

cranpose_ui/modifier/
local.rs

1use std::{
2    any::{Any, TypeId},
3    fmt,
4    hash::{Hash, Hasher},
5    rc::Rc,
6};
7
8use cranpose_core::collections::map::{HashMap, HashSet};
9use cranpose_foundation::{
10    DelegatableNode, InvalidationKind, ModifierInvalidation, ModifierInvalidations, ModifierNode,
11    ModifierNodeChain, ModifierNodeElement, NodeCapabilities, NodeState,
12};
13
14#[derive(Clone)]
15struct ModifierLocalId(Rc<()>);
16
17impl ModifierLocalId {
18    fn new() -> Self {
19        Self(Rc::new(()))
20    }
21}
22
23impl fmt::Debug for ModifierLocalId {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        f.debug_tuple("ModifierLocalId")
26            .field(&(Rc::as_ptr(&self.0) as usize))
27            .finish()
28    }
29}
30
31impl PartialEq for ModifierLocalId {
32    fn eq(&self, other: &Self) -> bool {
33        Rc::ptr_eq(&self.0, &other.0)
34    }
35}
36
37impl Eq for ModifierLocalId {}
38
39impl Hash for ModifierLocalId {
40    fn hash<H: Hasher>(&self, state: &mut H) {
41        Rc::as_ptr(&self.0).hash(state);
42    }
43}
44
45#[derive(Clone, Debug, PartialEq, Eq, Hash)]
46pub(crate) struct ModifierLocalToken {
47    id: ModifierLocalId,
48    type_id: TypeId,
49}
50
51impl ModifierLocalToken {
52    fn new(type_id: TypeId) -> Self {
53        let id = ModifierLocalId::new();
54        Self { id, type_id }
55    }
56
57    fn id(&self) -> ModifierLocalId {
58        self.id.clone()
59    }
60}
61
62/// Type-safe key referencing a modifier local value.
63#[derive(Clone)]
64pub struct ModifierLocalKey<T: 'static> {
65    token: ModifierLocalToken,
66    default: Rc<dyn Fn() -> T>,
67}
68
69impl<T: 'static> ModifierLocalKey<T> {
70    /// Creates a key. `factory` makes the value that consumers read when no provider of this key is
71    /// above them.
72    pub fn new(factory: impl Fn() -> T + 'static) -> Self {
73        Self {
74            token: ModifierLocalToken::new(TypeId::of::<T>()),
75            default: Rc::new(factory),
76        }
77    }
78
79    pub(crate) fn token(&self) -> ModifierLocalToken {
80        self.token.clone()
81    }
82
83    fn into_token(self) -> ModifierLocalToken {
84        self.token
85    }
86
87    pub(crate) fn default_value(&self) -> T {
88        (self.default)()
89    }
90}
91
92impl<T: 'static> PartialEq for ModifierLocalKey<T> {
93    fn eq(&self, other: &Self) -> bool {
94        self.token == other.token
95    }
96}
97
98impl<T: 'static> Eq for ModifierLocalKey<T> {}
99
100impl<T: 'static> Hash for ModifierLocalKey<T> {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        self.token.hash(state);
103    }
104}
105
106pub(super) struct ModifierLocalProviderNode {
107    token: ModifierLocalToken,
108    value_factory: Rc<dyn Fn() -> Box<dyn Any>>,
109    value: Rc<dyn Any>,
110    version: u64,
111    state: NodeState,
112}
113
114impl ModifierLocalProviderNode {
115    fn new(token: ModifierLocalToken, factory: Rc<dyn Fn() -> Box<dyn Any>>) -> Self {
116        Self {
117            token,
118            value: Self::create_value(&factory),
119            value_factory: factory,
120            version: 0,
121            state: NodeState::new(),
122        }
123    }
124
125    fn update_value(&mut self) {
126        self.value = Self::create_value(&self.value_factory);
127        self.version = self.version.wrapping_add(1);
128    }
129
130    fn set_factory(&mut self, factory: Rc<dyn Fn() -> Box<dyn Any>>) {
131        self.value_factory = factory;
132        self.update_value();
133    }
134
135    fn token(&self) -> ModifierLocalToken {
136        self.token.clone()
137    }
138
139    fn value(&self) -> Rc<dyn Any> {
140        Rc::clone(&self.value)
141    }
142
143    fn version(&self) -> u64 {
144        self.version
145    }
146
147    fn create_value(factory: &Rc<dyn Fn() -> Box<dyn Any>>) -> Rc<dyn Any> {
148        Rc::from(factory())
149    }
150}
151
152impl DelegatableNode for ModifierLocalProviderNode {
153    fn node_state(&self) -> &NodeState {
154        &self.state
155    }
156}
157
158impl ModifierNode for ModifierLocalProviderNode {}
159
160pub(super) struct ModifierLocalConsumerNode {
161    callback: Rc<dyn for<'a> Fn(&mut ModifierLocalReadScope<'a>)>,
162    state: NodeState,
163}
164
165impl ModifierLocalConsumerNode {
166    fn new(callback: Rc<dyn for<'a> Fn(&mut ModifierLocalReadScope<'a>)>) -> Self {
167        Self {
168            callback,
169            state: NodeState::new(),
170        }
171    }
172
173    fn notify(&self, scope: &mut ModifierLocalReadScope<'_>) {
174        (self.callback)(scope);
175    }
176
177    fn id(&self) -> usize {
178        self as *const Self as usize
179    }
180}
181
182#[derive(Clone)]
183pub(crate) struct ResolvedModifierLocal {
184    value: Rc<dyn Any>,
185    version: u64,
186    source: ModifierLocalSource,
187}
188
189impl ResolvedModifierLocal {
190    fn new(value: Rc<dyn Any>, version: u64, source: ModifierLocalSource) -> Self {
191        Self {
192            value,
193            version,
194            source,
195        }
196    }
197
198    pub(crate) fn value(&self) -> Rc<dyn Any> {
199        Rc::clone(&self.value)
200    }
201
202    pub(crate) fn version(&self) -> u64 {
203        self.version
204    }
205
206    pub(crate) fn with_source(mut self, source: ModifierLocalSource) -> Self {
207        self.source = source;
208        self
209    }
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
213pub(crate) enum ModifierLocalSource {
214    Chain,
215    Ancestor,
216}
217
218pub(crate) type ModifierLocalAncestorResolver<'a> =
219    dyn FnMut(&ModifierLocalToken) -> Option<ResolvedModifierLocal> + 'a;
220
221#[derive(Clone)]
222struct ProviderRecord {
223    value: Rc<dyn Any>,
224    version: u64,
225}
226
227impl ProviderRecord {
228    fn new(value: Rc<dyn Any>, version: u64) -> Self {
229        Self { value, version }
230    }
231
232    fn version(&self) -> u64 {
233        self.version
234    }
235
236    fn value(&self) -> &Rc<dyn Any> {
237        &self.value
238    }
239}
240
241#[derive(Clone)]
242struct DependencyRecord {
243    token: ModifierLocalToken,
244    source: DependencySource,
245    version: u64,
246}
247
248impl DependencyRecord {
249    fn from_chain(token: ModifierLocalToken, version: u64) -> Self {
250        Self {
251            token,
252            source: DependencySource::Chain,
253            version,
254        }
255    }
256
257    fn from_ancestor(token: ModifierLocalToken, version: u64) -> Self {
258        Self {
259            token,
260            source: DependencySource::Ancestor,
261            version,
262        }
263    }
264
265    fn from_default(token: ModifierLocalToken) -> Self {
266        Self {
267            token,
268            source: DependencySource::Default,
269            version: 0,
270        }
271    }
272
273    fn is_dirty(
274        &self,
275        providers: &HashMap<ModifierLocalId, ProviderRecord>,
276        ancestor_lookup: &mut ModifierLocalAncestorResolver<'_>,
277    ) -> bool {
278        match self.source {
279            DependencySource::Chain => match providers.get(&self.token.id()) {
280                Some(record) => record.version() != self.version,
281                None => true,
282            },
283            DependencySource::Ancestor => {
284                if providers.contains_key(&self.token.id()) {
285                    return true;
286                }
287                ancestor_lookup(&self.token)
288                    .is_none_or(|resolved| resolved.version() != self.version)
289            }
290            DependencySource::Default => {
291                providers.contains_key(&self.token.id()) || ancestor_lookup(&self.token).is_some()
292            }
293        }
294    }
295}
296
297#[derive(Clone, Copy)]
298enum DependencySource {
299    Chain,
300    Ancestor,
301    Default,
302}
303
304struct ConsumerState {
305    dependencies: Vec<DependencyRecord>,
306}
307
308impl ConsumerState {
309    fn new(dependencies: Vec<DependencyRecord>) -> Self {
310        Self { dependencies }
311    }
312
313    fn needs_update(
314        &self,
315        providers: &HashMap<ModifierLocalId, ProviderRecord>,
316        ancestor_lookup: &mut ModifierLocalAncestorResolver<'_>,
317    ) -> bool {
318        if self.dependencies.is_empty() {
319            return true;
320        }
321        self.dependencies
322            .iter()
323            .any(|dependency| dependency.is_dirty(providers, ancestor_lookup))
324    }
325}
326
327impl DelegatableNode for ModifierLocalConsumerNode {
328    fn node_state(&self) -> &NodeState {
329        &self.state
330    }
331}
332
333impl ModifierNode for ModifierLocalConsumerNode {}
334
335#[derive(Clone)]
336pub(super) struct ModifierLocalProviderElement {
337    token: ModifierLocalToken,
338    factory: Rc<dyn Fn() -> Box<dyn Any>>,
339}
340
341impl ModifierLocalProviderElement {
342    pub(super) fn new<T, F>(key: ModifierLocalKey<T>, factory: F) -> Self
343    where
344        T: 'static,
345        F: Fn() -> T + 'static,
346    {
347        let erased = Rc::new(move || -> Box<dyn Any> { Box::new(factory()) });
348        Self {
349            token: key.into_token(),
350            factory: erased,
351        }
352    }
353}
354
355impl fmt::Debug for ModifierLocalProviderElement {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        f.debug_struct("ModifierLocalProviderElement")
358            .field("id", &self.token.id())
359            .finish()
360    }
361}
362
363impl PartialEq for ModifierLocalProviderElement {
364    fn eq(&self, other: &Self) -> bool {
365        self.token == other.token
366    }
367}
368
369impl Eq for ModifierLocalProviderElement {}
370
371impl Hash for ModifierLocalProviderElement {
372    fn hash<H: Hasher>(&self, state: &mut H) {
373        "modifier_local_provider".hash(state);
374        self.token.hash(state);
375    }
376}
377
378impl ModifierNodeElement for ModifierLocalProviderElement {
379    type Node = ModifierLocalProviderNode;
380
381    fn create(&self) -> Self::Node {
382        ModifierLocalProviderNode::new(self.token.clone(), Rc::clone(&self.factory))
383    }
384
385    fn update(&self, node: &mut Self::Node) {
386        node.set_factory(Rc::clone(&self.factory));
387    }
388
389    fn capabilities(&self) -> NodeCapabilities {
390        NodeCapabilities::MODIFIER_LOCALS
391    }
392
393    fn always_update(&self) -> bool {
394        true
395    }
396}
397
398#[derive(Clone)]
399pub(super) struct ModifierLocalConsumerElement {
400    callback: Rc<dyn for<'a> Fn(&mut ModifierLocalReadScope<'a>)>,
401}
402
403impl ModifierLocalConsumerElement {
404    pub(super) fn new<F>(callback: F) -> Self
405    where
406        F: for<'a> Fn(&mut ModifierLocalReadScope<'a>) + 'static,
407    {
408        Self {
409            callback: Rc::new(callback),
410        }
411    }
412}
413
414impl fmt::Debug for ModifierLocalConsumerElement {
415    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416        f.write_str("ModifierLocalConsumerElement")
417    }
418}
419
420impl PartialEq for ModifierLocalConsumerElement {
421    fn eq(&self, _other: &Self) -> bool {
422        true
423    }
424}
425
426impl Eq for ModifierLocalConsumerElement {}
427
428impl Hash for ModifierLocalConsumerElement {
429    fn hash<H: Hasher>(&self, state: &mut H) {
430        "modifier_local_consumer".hash(state);
431    }
432}
433
434impl ModifierNodeElement for ModifierLocalConsumerElement {
435    type Node = ModifierLocalConsumerNode;
436
437    fn create(&self) -> Self::Node {
438        ModifierLocalConsumerNode::new(Rc::clone(&self.callback))
439    }
440
441    fn update(&self, node: &mut Self::Node) {
442        node.callback = Rc::clone(&self.callback);
443    }
444
445    fn capabilities(&self) -> NodeCapabilities {
446        NodeCapabilities::MODIFIER_LOCALS
447    }
448
449    fn always_update(&self) -> bool {
450        true
451    }
452}
453
454/// Lightweight read scope surfaced to modifier local consumers.
455pub struct ModifierLocalReadScope<'a> {
456    providers: &'a HashMap<ModifierLocalId, ProviderRecord>,
457    ancestor_lookup: &'a mut ModifierLocalAncestorResolver<'a>,
458    dependencies: &'a mut Vec<DependencyRecord>,
459    fallbacks: HashMap<ModifierLocalId, Rc<dyn Any>>,
460}
461
462impl<'a> ModifierLocalReadScope<'a> {
463    fn new(
464        providers: &'a HashMap<ModifierLocalId, ProviderRecord>,
465        ancestor_lookup: &'a mut ModifierLocalAncestorResolver<'a>,
466        dependencies: &'a mut Vec<DependencyRecord>,
467    ) -> Self {
468        Self {
469            providers,
470            ancestor_lookup,
471            dependencies,
472            fallbacks: HashMap::default(),
473        }
474    }
475
476    /// Returns the value of the nearest provider of `key`, or the key's default value when there is
477    /// none. The consumer runs again when a value it read changes.
478    pub fn get<T: 'static>(&mut self, key: &ModifierLocalKey<T>) -> &T {
479        let token = key.token();
480        if let Some(record) = self.providers.get(&token.id()) {
481            self.dependencies
482                .push(DependencyRecord::from_chain(token, record.version()));
483            if let Some(value) = record.value().downcast_ref::<T>() {
484                return value;
485            }
486            return self.default_value_for(key);
487        }
488
489        if let Some(resolved) = (self.ancestor_lookup)(&token) {
490            self.dependencies.push(DependencyRecord::from_ancestor(
491                token.clone(),
492                resolved.version(),
493            ));
494            let resolved_value = resolved.value();
495            if resolved_value.downcast_ref::<T>().is_some() {
496                self.fallbacks.insert(token.id(), resolved_value);
497                return self.fallback_value_for(key);
498            }
499            return self.default_value_for(key);
500        }
501
502        self.dependencies
503            .push(DependencyRecord::from_default(token));
504        self.default_value_for(key)
505    }
506
507    fn fallback_value_for<T: 'static>(&mut self, key: &ModifierLocalKey<T>) -> &T {
508        let id = key.token().id();
509        if self
510            .fallbacks
511            .get(&id)
512            .and_then(|value| value.downcast_ref::<T>())
513            .is_none()
514        {
515            self.fallbacks
516                .insert(id.clone(), Rc::new(key.default_value()) as Rc<dyn Any>);
517        }
518
519        match self
520            .fallbacks
521            .get(&id)
522            .and_then(|value| value.downcast_ref::<T>())
523        {
524            Some(value) => value,
525            None => Box::leak(Box::new(key.default_value())),
526        }
527    }
528
529    fn default_value_for<T: 'static>(&mut self, key: &ModifierLocalKey<T>) -> &T {
530        self.fallback_value_for(key)
531    }
532}
533
534#[cfg(test)]
535#[path = "tests/local_tests.rs"]
536mod tests;
537
538#[derive(Default)]
539pub struct ModifierLocalManager {
540    providers: HashMap<ModifierLocalId, ProviderRecord>,
541    consumers: HashMap<usize, ConsumerState>,
542}
543
544impl ModifierLocalManager {
545    pub fn new() -> Self {
546        Self::default()
547    }
548
549    #[expect(
550        private_interfaces,
551        reason = "the sync callback types stay crate-private"
552    )]
553    pub fn sync(
554        &mut self,
555        chain: &ModifierNodeChain,
556        ancestor_lookup: &mut ModifierLocalAncestorResolver<'_>,
557    ) -> ModifierInvalidations {
558        if !chain.has_capability(NodeCapabilities::MODIFIER_LOCALS) {
559            self.providers.clear();
560            self.consumers.clear();
561            return ModifierInvalidations::new();
562        }
563
564        let mut providers: HashMap<ModifierLocalId, ProviderRecord> = HashMap::default();
565        let mut seen_consumers: HashSet<usize> = HashSet::default();
566        let mut invalidations = ModifierInvalidations::new();
567
568        chain.for_each_node_with_capability(NodeCapabilities::MODIFIER_LOCALS, |_ref, node| {
569            if let Some(provider) = node.as_any().downcast_ref::<ModifierLocalProviderNode>() {
570                providers.insert(
571                    provider.token().id(),
572                    ProviderRecord::new(provider.value(), provider.version()),
573                );
574                return;
575            }
576
577            if let Some(consumer) = node.as_any().downcast_ref::<ModifierLocalConsumerNode>() {
578                let id = consumer.id();
579                seen_consumers.insert(id);
580                let needs_update = self
581                    .consumers
582                    .get(&id)
583                    .is_none_or(|state| state.needs_update(&providers, ancestor_lookup));
584                if !needs_update {
585                    return;
586                }
587
588                let mut dependencies = Vec::new();
589                {
590                    let mut scope =
591                        ModifierLocalReadScope::new(&providers, ancestor_lookup, &mut dependencies);
592                    consumer.notify(&mut scope);
593                }
594                self.consumers.insert(id, ConsumerState::new(dependencies));
595                if !invalidations
596                    .iter()
597                    .any(|entry: &ModifierInvalidation| entry.kind() == InvalidationKind::Layout)
598                {
599                    invalidations.push(ModifierInvalidation::new(
600                        InvalidationKind::Layout,
601                        NodeCapabilities::LAYOUT | NodeCapabilities::MODIFIER_LOCALS,
602                    ));
603                }
604            }
605        });
606
607        self.providers = providers;
608        self.consumers.retain(|id, _| seen_consumers.contains(id));
609
610        invalidations
611    }
612
613    pub(crate) fn resolve(&self, token: &ModifierLocalToken) -> Option<ResolvedModifierLocal> {
614        self.providers.get(&token.id()).map(|record| {
615            ResolvedModifierLocal::new(
616                Rc::clone(record.value()),
617                record.version(),
618                ModifierLocalSource::Chain,
619            )
620        })
621    }
622}