1use std::cell::{Cell, RefCell};
48use std::collections::{HashMap, HashSet};
49use std::rc::Rc;
50
51use teksilo_core::signal::{ObserverHandle, Signal};
52
53use crate::check_state::CheckState;
54use crate::dnd_types::ItemKey;
55use crate::tree_checked_model::AggregateMode;
56use crate::tree_data_source::TreeDataSource;
57
58type ChildrenFn<K> = Rc<dyn Fn(&K) -> Vec<K>>;
60type ParentFn<K> = Rc<dyn Fn(&K) -> Option<K>>;
61
62struct Inner<K: ItemKey> {
63 state: HashMap<K, Signal<CheckState>>,
64 observers: HashMap<K, ObserverHandle>,
65 bool_signals: HashMap<K, Signal<bool>>,
66 bridge_guards: HashMap<K, Rc<Cell<bool>>>,
67 bridge_observers: HashMap<K, (ObserverHandle, ObserverHandle)>,
68 suppressed: HashSet<K>,
75}
76
77pub struct KeyedTreeCheckedModel<K: ItemKey> {
80 children: ChildrenFn<K>,
81 parent: ParentFn<K>,
82 inner: Rc<RefCell<Inner<K>>>,
83 mode: Rc<Cell<AggregateMode>>,
84}
85
86impl<K: ItemKey> KeyedTreeCheckedModel<K> {
87 pub fn new(
91 children: impl Fn(&K) -> Vec<K> + 'static,
92 parent: impl Fn(&K) -> Option<K> + 'static,
93 ) -> Self {
94 Self {
95 children: Rc::new(children),
96 parent: Rc::new(parent),
97 inner: Rc::new(RefCell::new(Inner {
98 state: HashMap::new(),
99 observers: HashMap::new(),
100 bool_signals: HashMap::new(),
101 bridge_guards: HashMap::new(),
102 bridge_observers: HashMap::new(),
103 suppressed: HashSet::new(),
104 })),
105 mode: Rc::new(Cell::new(AggregateMode::default())),
106 }
107 }
108
109 pub fn from_source<S>(source: S) -> Self
115 where
116 S: TreeDataSource<Key = K> + Clone + 'static,
117 {
118 let for_children = source.clone();
119 let for_parent = source;
120 Self::new(
121 move |k| for_children.child_keys(k),
122 move |k| for_parent.parent(k),
123 )
124 }
125
126 pub fn with_mode(self, mode: AggregateMode) -> Self {
128 self.mode.set(mode);
129 self
130 }
131
132 pub fn aggregate_mode(&self) -> AggregateMode {
134 self.mode.get()
135 }
136
137 pub fn set_aggregate_mode(&self, mode: AggregateMode) {
139 self.mode.set(mode);
140 }
141
142 pub fn signal_for(&self, key: K) -> Signal<CheckState> {
148 let sig = self
150 .inner
151 .borrow_mut()
152 .state
153 .entry(key.clone())
154 .or_insert_with(|| Signal::new(CheckState::Unchecked))
155 .clone();
156 if !self.inner.borrow().observers.contains_key(&key) {
158 let handle = self.make_cascade_observer(&sig, key.clone());
159 self.inner.borrow_mut().observers.insert(key, handle);
160 }
161 sig
162 }
163
164 fn make_cascade_observer(&self, sig: &Signal<CheckState>, node: K) -> ObserverHandle {
168 let inner_w = Rc::downgrade(&self.inner);
169 let mode_w = Rc::downgrade(&self.mode);
170 let children = self.children.clone();
171 let parent = self.parent.clone();
172 sig.observe(move |new_state| {
173 let Some(inner_rc) = inner_w.upgrade() else {
174 return;
175 };
176 let Some(mode_rc) = mode_w.upgrade() else {
177 return;
178 };
179 if inner_rc.borrow().suppressed.contains(&node) {
185 return;
186 }
187 if mode_rc.get() != AggregateMode::DescendantsDriveAncestors {
188 return;
189 }
190 if *new_state != CheckState::Indeterminate {
192 cascade_descendants(&children, &inner_rc, &node, *new_state);
193 }
194 let mut cur = parent(&node);
196 while let Some(p) = cur {
197 recompute_from_children(&children, &inner_rc, &p);
198 cur = parent(&p);
199 }
200 })
201 }
202
203 pub fn bool_signal_for(&self, key: K) -> Signal<bool> {
207 if let Some(b) = self.inner.borrow().bool_signals.get(&key) {
208 return b.clone();
209 }
210 let tristate = self.signal_for(key.clone());
211 let bool_sig = Signal::new(tristate.get() == CheckState::Checked);
212 let guard = Rc::new(Cell::new(false));
213
214 let bool_for_tri = bool_sig.clone();
216 let guard_for_tri = guard.clone();
217 let tri_to_bool = tristate.observe(move |state| {
218 if guard_for_tri.get() {
219 return;
220 }
221 let want = matches!(state, CheckState::Checked);
222 if bool_for_tri.get() != want {
223 guard_for_tri.set(true);
224 bool_for_tri.set(want);
225 guard_for_tri.set(false);
226 }
227 });
228
229 let tri_for_bool = tristate.clone();
231 let guard_for_bool = guard.clone();
232 let bool_to_tri = bool_sig.observe(move |checked| {
233 if guard_for_bool.get() {
234 return;
235 }
236 let want = if *checked {
237 CheckState::Checked
238 } else {
239 CheckState::Unchecked
240 };
241 if tri_for_bool.get() != want {
242 guard_for_bool.set(true);
243 tri_for_bool.set(want);
244 guard_for_bool.set(false);
245 }
246 });
247
248 let mut inner = self.inner.borrow_mut();
249 inner.bool_signals.insert(key.clone(), bool_sig.clone());
250 inner.bridge_guards.insert(key.clone(), guard);
251 inner
252 .bridge_observers
253 .insert(key, (tri_to_bool, bool_to_tri));
254 bool_sig
255 }
256
257 pub fn check_state(&self, key: &K) -> CheckState {
259 self.inner
260 .borrow()
261 .state
262 .get(key)
263 .map(|s| s.get())
264 .unwrap_or(CheckState::Unchecked)
265 }
266
267 pub fn check(&self, key: K) {
269 self.signal_for(key).set(CheckState::Checked);
270 }
271
272 pub fn uncheck(&self, key: K) {
274 self.signal_for(key).set(CheckState::Unchecked);
275 }
276
277 pub fn toggle(&self, key: K) {
280 let current = self.check_state(&key);
281 let next = match (self.mode.get(), self.is_leaf(&key), current) {
282 (AggregateMode::DescendantsDriveAncestors, true, CheckState::Unchecked) => {
283 CheckState::Checked
284 }
285 (AggregateMode::DescendantsDriveAncestors, true, _) => CheckState::Unchecked,
286 (_, _, _) => current.next_tristate(),
287 };
288 self.signal_for(key).set(next);
289 }
290
291 pub fn checked_keys(&self) -> Vec<K> {
295 self.inner
296 .borrow()
297 .state
298 .iter()
299 .filter(|(_, sig)| sig.get() == CheckState::Checked)
300 .map(|(k, _)| k.clone())
301 .collect()
302 }
303
304 pub fn clear(&self) {
315 let keys: Vec<K> = self.inner.borrow().state.keys().cloned().collect();
316 for k in keys {
317 write_state(&self.inner, &k, CheckState::Unchecked);
318 }
319 }
320
321 pub fn prune_missing(&self, exists: impl Fn(&K) -> bool) {
328 let all: Vec<K> = self.inner.borrow().state.keys().cloned().collect();
331 let stale: Vec<K> = all.into_iter().filter(|k| !exists(k)).collect();
332 if !stale.is_empty() {
333 let mut inner = self.inner.borrow_mut();
334 for k in &stale {
335 inner.state.remove(k);
336 inner.observers.remove(k);
337 inner.bool_signals.remove(k);
338 inner.bridge_guards.remove(k);
339 inner.bridge_observers.remove(k);
340 }
341 }
342 self.reaggregate();
353 }
354
355 pub fn reaggregate(&self) {
361 if self.mode.get() != AggregateMode::DescendantsDriveAncestors {
362 return;
363 }
364 let tracked: Vec<K> = self.inner.borrow().state.keys().cloned().collect();
365 let mut keys: HashSet<K> = tracked.iter().cloned().collect();
375 for k in &tracked {
376 let mut cur = (self.parent)(k);
377 let mut guard = 0usize;
378 while let Some(p) = cur {
379 if !keys.insert(p.clone()) {
380 break;
383 }
384 guard += 1;
385 if guard > 1_000_000 {
386 break; }
388 cur = (self.parent)(&p);
389 }
390 }
391 let mut keys: Vec<K> = keys.into_iter().collect();
392 keys.sort_by_key(|k| std::cmp::Reverse(self.depth_of(k)));
394 for k in keys {
397 recompute_from_children(&self.children, &self.inner, &k);
398 }
399 }
400
401 fn depth_of(&self, key: &K) -> usize {
403 let mut depth = 0usize;
404 let mut cur = (self.parent)(key);
405 while let Some(p) = cur {
407 depth += 1;
408 if depth > 1_000_000 {
409 break;
410 }
411 cur = (self.parent)(&p);
412 }
413 depth
414 }
415
416 fn is_leaf(&self, key: &K) -> bool {
417 (self.children)(key).is_empty()
418 }
419}
420
421struct SuppressGuard<K: ItemKey> {
426 inner: Rc<RefCell<Inner<K>>>,
427 key: K,
428}
429
430impl<K: ItemKey> SuppressGuard<K> {
431 fn new(inner: &Rc<RefCell<Inner<K>>>, key: K) -> Self {
432 inner.borrow_mut().suppressed.insert(key.clone());
433 Self {
434 inner: inner.clone(),
435 key,
436 }
437 }
438}
439
440impl<K: ItemKey> Drop for SuppressGuard<K> {
441 fn drop(&mut self) {
442 if let Ok(mut inner) = self.inner.try_borrow_mut() {
444 inner.suppressed.remove(&self.key);
445 }
446 }
447}
448
449fn cascade_descendants<K: ItemKey>(
452 children: &ChildrenFn<K>,
453 inner: &Rc<RefCell<Inner<K>>>,
454 root: &K,
455 target: CheckState,
456) {
457 for child in children(root) {
458 write_state(inner, &child, target);
459 cascade_descendants(children, inner, &child, target);
460 }
461}
462
463fn recompute_from_children<K: ItemKey>(
464 children: &ChildrenFn<K>,
465 inner: &Rc<RefCell<Inner<K>>>,
466 node: &K,
467) {
468 let kids = children(node);
469 if kids.is_empty() {
470 return;
471 }
472 let mut all_checked = true;
473 let mut all_unchecked = true;
474 for child in &kids {
475 match read_state(inner, child) {
476 CheckState::Checked => all_unchecked = false,
477 CheckState::Unchecked => all_checked = false,
478 CheckState::Indeterminate => {
479 all_checked = false;
480 all_unchecked = false;
481 }
482 }
483 }
484 let new_state = if all_checked {
485 CheckState::Checked
486 } else if all_unchecked {
487 CheckState::Unchecked
488 } else {
489 CheckState::Indeterminate
490 };
491 write_state(inner, node, new_state);
492}
493
494fn read_state<K: ItemKey>(inner: &Rc<RefCell<Inner<K>>>, node: &K) -> CheckState {
495 inner
496 .borrow()
497 .state
498 .get(node)
499 .map(|s| s.get())
500 .unwrap_or(CheckState::Unchecked)
501}
502
503fn write_state<K: ItemKey>(inner: &Rc<RefCell<Inner<K>>>, node: &K, state: CheckState) {
504 let sig = {
505 let mut map = inner.borrow_mut();
506 map.state
507 .entry(node.clone())
508 .or_insert_with(|| Signal::new(CheckState::Unchecked))
509 .clone()
510 };
511 if sig.get() != state {
512 let _guard = SuppressGuard::new(inner, node.clone());
515 sig.set(state);
516 }
517}
518
519impl<K: ItemKey> Clone for KeyedTreeCheckedModel<K> {
520 fn clone(&self) -> Self {
521 Self {
522 children: self.children.clone(),
523 parent: self.parent.clone(),
524 inner: self.inner.clone(),
525 mode: self.mode.clone(),
526 }
527 }
528}
529
530impl<K: ItemKey> std::fmt::Debug for KeyedTreeCheckedModel<K> {
531 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
532 f.debug_struct("KeyedTreeCheckedModel")
533 .field("mode", &self.mode.get())
534 .field("tracked_nodes", &self.inner.borrow().state.len())
535 .finish()
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542 use crate::{TreeDataSlice, TreeRow};
543
544 fn slice() -> TreeDataSlice<u64, &'static str> {
546 TreeDataSlice::from_rows(vec![
547 TreeRow::new(1, "Binder", 0),
548 TreeRow::new(2, "Chapter", 1),
549 TreeRow::new(3, "Scene A", 2),
550 TreeRow::new(5, "Scene C", 2),
551 TreeRow::new(4, "Scene B", 1),
552 ])
553 }
554
555 fn model() -> KeyedTreeCheckedModel<u64> {
556 let m = KeyedTreeCheckedModel::from_source(slice());
557 let _ = (
559 m.signal_for(1),
560 m.signal_for(2),
561 m.signal_for(3),
562 m.signal_for(4),
563 m.signal_for(5),
564 );
565 m
566 }
567
568 #[test]
569 fn descendants_drive_ancestors() {
570 let m = model();
571 m.check(3);
572 assert_eq!(m.check_state(&3), CheckState::Checked);
573 assert_eq!(m.check_state(&2), CheckState::Indeterminate);
575 assert_eq!(m.check_state(&1), CheckState::Indeterminate);
577
578 m.check(5);
579 assert_eq!(m.check_state(&2), CheckState::Checked);
581 assert_eq!(m.check_state(&1), CheckState::Indeterminate); m.check(4);
583 assert_eq!(m.check_state(&1), CheckState::Checked);
584 }
585
586 #[test]
587 fn parent_cascades_to_descendants() {
588 let m = model();
589 m.check(2); assert_eq!(m.check_state(&3), CheckState::Checked);
591 assert_eq!(m.check_state(&5), CheckState::Checked);
592 m.uncheck(2);
593 assert_eq!(m.check_state(&3), CheckState::Unchecked);
594 assert_eq!(m.check_state(&5), CheckState::Unchecked);
595 }
596
597 #[test]
598 fn check_root_cascades_whole_tree() {
599 let m = model();
600 m.check(1);
601 for k in [2u64, 3, 4, 5] {
602 assert_eq!(m.check_state(&k), CheckState::Checked);
603 }
604 }
605
606 #[test]
607 fn aggregate_mode_none_is_independent() {
608 let m = KeyedTreeCheckedModel::from_source(slice()).with_mode(AggregateMode::None);
609 let _ = (m.signal_for(1), m.signal_for(3));
610 m.check(3);
611 assert_eq!(m.check_state(&3), CheckState::Checked);
612 assert_eq!(m.check_state(&2), CheckState::Unchecked);
613 assert_eq!(m.check_state(&1), CheckState::Unchecked);
614 }
615
616 #[test]
617 fn external_signal_write_cascades() {
618 let m = model();
619 m.signal_for(2).set(CheckState::Checked); assert_eq!(m.check_state(&3), CheckState::Checked);
621 assert_eq!(m.check_state(&5), CheckState::Checked);
622 }
623
624 #[test]
625 fn bool_signal_bridge() {
626 let m = model();
627 let b = m.bool_signal_for(3);
628 assert!(!b.get());
629 b.set(true);
630 assert_eq!(m.check_state(&3), CheckState::Checked);
631 m.uncheck(3);
632 assert!(!b.get());
633 }
634
635 #[test]
636 fn bool_signal_indeterminate_reads_false() {
637 let m = model();
638 let binder_bool = m.bool_signal_for(1);
639 m.check(3); assert_eq!(m.check_state(&1), CheckState::Indeterminate);
641 assert!(!binder_bool.get());
642 }
643
644 #[test]
645 fn checked_keys_excludes_indeterminate() {
646 let m = model();
647 m.check(3);
648 let keys = m.checked_keys();
649 assert!(keys.contains(&3));
650 assert!(!keys.contains(&2)); assert!(!keys.contains(&1));
652 }
653
654 #[test]
655 fn signal_is_stable_across_calls() {
656 let m = model();
657 let s1 = m.signal_for(3);
658 let s2 = m.signal_for(3);
659 m.check(3);
660 assert_eq!(s1.get(), CheckState::Checked);
661 assert_eq!(s2.get(), CheckState::Checked);
662 }
663
664 #[test]
665 fn prune_missing_drops_stale_state() {
666 let m = model();
667 m.check(3);
668 assert!(m.checked_keys().contains(&3));
669 m.prune_missing(|k| *k != 3);
671 assert!(!m.checked_keys().contains(&3));
672 assert_eq!(m.check_state(&3), CheckState::Unchecked); }
674
675 #[test]
676 fn clear_resets_all() {
677 let m = model();
678 m.check(1);
679 m.clear();
680 assert_eq!(m.checked_keys(), Vec::<u64>::new());
681 }
682
683 #[test]
684 fn clear_resets_every_key_and_still_notifies() {
685 let m = model();
693 m.check(2);
694 m.check(4);
695 assert_eq!(m.check_state(&1), CheckState::Checked);
696 assert_eq!(m.check_state(&2), CheckState::Checked);
697 assert_eq!(m.check_state(&3), CheckState::Checked);
698 assert_eq!(m.check_state(&5), CheckState::Checked);
699 let bool_3 = m.bool_signal_for(3);
700 assert!(bool_3.get());
701
702 let notified: Rc<RefCell<HashSet<u64>>> = Rc::new(RefCell::new(HashSet::new()));
703 let mut handles = Vec::new();
704 for key in [1u64, 2, 3, 4, 5] {
705 let log = notified.clone();
706 handles.push(m.signal_for(key).observe(move |state| {
707 if *state == CheckState::Unchecked {
708 log.borrow_mut().insert(key);
709 }
710 }));
711 }
712
713 m.clear();
714
715 assert_eq!(m.checked_keys(), Vec::<u64>::new());
716 for key in [1u64, 2, 3, 4, 5] {
717 assert_eq!(m.check_state(&key), CheckState::Unchecked);
718 }
719 assert!(!bool_3.get());
720 assert_eq!(
721 notified.borrow().len(),
722 5,
723 "every tracked key must still notify its own observers on clear: {:?}",
724 notified.borrow()
725 );
726 drop(handles);
727 }
728
729 #[test]
730 fn lazy_signal_still_cascades() {
731 let m = KeyedTreeCheckedModel::from_source(slice());
735 let _ = m.signal_for(1); m.check(1); assert_eq!(m.check_state(&3), CheckState::Checked);
738
739 let scene3 = m.signal_for(3); scene3.set(CheckState::Unchecked); assert_eq!(m.check_state(&2), CheckState::Indeterminate);
743 assert_eq!(m.check_state(&1), CheckState::Indeterminate);
744 }
745
746 #[test]
747 fn prune_missing_reaggregates_ancestors() {
748 let s = slice(); let m = KeyedTreeCheckedModel::from_source(s.clone());
752 let _ = (
753 m.signal_for(1),
754 m.signal_for(2),
755 m.signal_for(3),
756 m.signal_for(5),
757 );
758 m.check(3);
759 assert_eq!(m.check_state(&2), CheckState::Indeterminate);
760 assert_eq!(m.check_state(&1), CheckState::Indeterminate);
761
762 s.set_rows(vec![
764 TreeRow::new(1, "Binder", 0),
765 TreeRow::new(2, "Chapter", 1),
766 TreeRow::new(5, "Scene C", 2),
767 TreeRow::new(4, "Scene B", 1),
768 ]);
769 assert_eq!(s.child_keys_of(&2), vec![5]);
770
771 m.prune_missing(|k| s.contains_key(k));
772 assert!(!m.checked_keys().contains(&3));
773 assert_eq!(m.check_state(&2), CheckState::Unchecked);
774 assert_eq!(m.check_state(&1), CheckState::Unchecked);
775 }
776
777 #[test]
778 fn reaggregate_after_added_child() {
779 let s = slice();
780 let m = KeyedTreeCheckedModel::from_source(s.clone());
781 let _ = (m.signal_for(2), m.signal_for(3), m.signal_for(5));
782 m.check(2); assert_eq!(m.check_state(&2), CheckState::Checked);
784
785 s.set_rows(vec![
787 TreeRow::new(1, "Binder", 0),
788 TreeRow::new(2, "Chapter", 1),
789 TreeRow::new(3, "Scene A", 2),
790 TreeRow::new(5, "Scene C", 2),
791 TreeRow::new(6, "Scene D", 2),
792 TreeRow::new(4, "Scene B", 1),
793 ]);
794 m.reaggregate();
795 assert_eq!(m.check_state(&2), CheckState::Indeterminate);
797 }
798
799 #[test]
800 fn new_with_explicit_closures() {
801 let parents: HashMap<&str, &str> = [("B", "A"), ("C", "A")].into_iter().collect();
803 let m = KeyedTreeCheckedModel::<&str>::new(
804 |k| match *k {
805 "A" => vec!["B", "C"],
806 _ => vec![],
807 },
808 move |k| parents.get(k).copied(),
809 );
810 let _ = (m.signal_for("A"), m.signal_for("B"), m.signal_for("C"));
811 m.check("B");
812 assert_eq!(m.check_state(&"A"), CheckState::Indeterminate);
813 m.check("C");
814 assert_eq!(m.check_state(&"A"), CheckState::Checked);
815 }
816
817 #[test]
818 fn reentrant_write_to_unrelated_key_still_cascades() {
819 let parents: HashMap<&str, &str> = [("a", "root1"), ("b", "root1"), ("c", "root2")]
826 .into_iter()
827 .collect();
828 let m = KeyedTreeCheckedModel::<&str>::new(
829 |k| match *k {
830 "root1" => vec!["a", "b"],
831 "root2" => vec!["c"],
832 _ => vec![],
833 },
834 move |k| parents.get(k).copied(),
835 );
836 let _ = (
837 m.signal_for("root1"),
838 m.signal_for("a"),
839 m.signal_for("b"),
840 m.signal_for("root2"),
841 m.signal_for("c"),
842 );
843
844 let m_for_observer = m.clone();
845 let _obs = m.signal_for("a").observe(move |state| {
846 if *state == CheckState::Checked {
847 m_for_observer.check("c");
848 }
849 });
850
851 m.check("root1"); assert_eq!(m.check_state(&"a"), CheckState::Checked);
854 assert_eq!(m.check_state(&"c"), CheckState::Checked);
855 assert_eq!(m.check_state(&"root2"), CheckState::Checked);
858 }
859}