Skip to main content

adam_rs/
cell.rs

1//! Value cells in the property model bipartite graph.
2//!
3//! Cells are accessed exclusively through [`crate::sheet::Sheet`].
4
5use std::any::{Any, TypeId};
6
7use slotmap::new_key_type;
8
9use crate::filter::FilterData;
10use crate::relationship::RelationshipId;
11use crate::requirement::RequirementId;
12
13new_key_type! {
14    /// A stable handle to a cell in a [`crate::sheet::Sheet`].
15    pub struct CellId;
16}
17
18/// A cell's fixed role in the planner's per-round source/derived assignment.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum CellKind {
21    /// May be a source or derived, chosen per round by the planner. Default kind.
22    Cell,
23    /// Always a source: never claimable as any method's output.
24    Source,
25    /// Always derived by exactly one fixed writer method; never `write()`-able.
26    Out,
27}
28
29/// Internal storage for a single value cell.
30pub(crate) struct CellData {
31    /// The value from the most recent `write()`/`add_cell`. Also written directly by
32    /// `Sheet::propagate` for outputs that aren't shadowed (the common case: an ordinary
33    /// derived cell behaves exactly as it did before `derived` existed). Left untouched by
34    /// `propagate()` only for outputs that *are* shadowed — self-referencing methods and
35    /// conditionally forced relationships write those into `derived` instead, which is what
36    /// keeps this field holding the original value for exactly those cells.
37    pub(crate) source: Box<dyn Any>,
38    /// The value most recently produced by a method this round, if this cell was
39    /// shadowed (a self-referencing output, or a pure output of a conditionally
40    /// registered relationship). Reset to `None` for every cell at the start of
41    /// every `Sheet::propagate` call, before planning begins.
42    pub(crate) derived: Option<Box<dyn Any>>,
43    /// The `TypeId` of the value, fixed at cell creation.
44    pub(crate) type_id: TypeId,
45    /// Write-recency strength. High-order bit (bit 63) is set for cells that have been
46    /// written or created via `add_cell`. Derived cells (outputs of selected methods)
47    /// receive strengths with bit 63 clear, assigned during the post-processing pass --
48    /// except a cell claimed *self-referencingly*, whose own written value is still the
49    /// authority behind the result and so keeps its explicit strength (see
50    /// `Sheet::post_process_strengths`).
51    pub(crate) strength: u64,
52    /// Set during `Sheet::propagate`; cleared by `Sheet::clear_changed`.
53    pub(crate) changed: bool,
54    /// Relationships that include this cell.
55    pub(crate) adj: Vec<RelationshipId>,
56    /// Type-erased equality: returns `true` iff both arguments hold equal values of the
57    /// cell's registered type. Captured at `add_cell` time from the concrete `T: PartialEq`.
58    pub(crate) eq_fn: fn(&dyn Any, &dyn Any) -> bool,
59    /// This cell's filter, if one is attached via `Sheet::add_filter`. At most one per
60    /// cell.
61    pub(crate) filter: Option<FilterData>,
62    /// This cell's fixed role in the planner's per-round source/derived assignment.
63    pub(crate) kind: CellKind,
64    /// This cell's requirements, in attachment order. Empty for most cells.
65    pub(crate) requirements: Vec<RequirementId>,
66}
67
68impl CellData {
69    /// Returns the effective current value: `derived` if present, else `source`.
70    pub(crate) fn effective(&self) -> &dyn Any {
71        self.derived.as_deref().unwrap_or(self.source.as_ref())
72    }
73
74    /// Returns whether `strength` reflects a live explicit `write()`/`add_cell` (bit 63
75    /// set) rather than a strength assigned by the post-round derived-strength pass.
76    pub(crate) fn has_explicit_strength(&self) -> bool {
77        self.strength & (1u64 << 63) != 0
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn cell_data_initial_state() {
87        let data = CellData {
88            source: Box::new(42_i32),
89            derived: None,
90            type_id: TypeId::of::<i32>(),
91            strength: 0,
92            changed: false,
93            adj: vec![],
94            eq_fn: |a, b| a.downcast_ref::<i32>() == b.downcast_ref::<i32>(),
95            filter: None,
96            kind: CellKind::Cell,
97            requirements: Vec::new(),
98        };
99        assert_eq!(data.type_id, TypeId::of::<i32>());
100        assert_eq!(data.strength, 0);
101        assert!(!data.changed);
102        assert!(data.adj.is_empty());
103        assert!(data.derived.is_none());
104        assert_eq!(*data.source.downcast_ref::<i32>().unwrap(), 42);
105        assert_eq!(*data.effective().downcast_ref::<i32>().unwrap(), 42);
106        assert_eq!(data.kind, CellKind::Cell);
107        assert!(data.requirements.is_empty());
108        let x: i32 = 42;
109        let y: i32 = 99;
110        assert!((data.eq_fn)(&x, &x));
111        assert!(!(data.eq_fn)(&x, &y));
112    }
113
114    #[test]
115    fn cell_id_is_copy() {
116        fn takes_copy<T: Copy>(_: T) {}
117        takes_copy(CellId::default());
118    }
119}