pax_runtime_api/properties/
mod.rs

1use serde::{Deserialize, Serialize};
2use std::{marker::PhantomData, rc::Rc};
3
4mod graph_operations;
5mod properties_table;
6#[cfg(test)]
7mod tests;
8mod untyped_property;
9
10use crate::{EasingCurve, Interpolatable, TransitionQueueEntry};
11
12use self::properties_table::{PropertyType, PROPERTY_TIME};
13use properties_table::PROPERTY_TABLE;
14pub use untyped_property::UntypedProperty;
15
16/// Sealed PropertyId needed for slotmap (strictly internal)
17mod private {
18    slotmap::new_key_type!(
19        pub struct PropertyId;
20    );
21}
22
23/// PropertyValue represents a restriction on valid generic types that a property
24/// can contain. All T need to be Clone (to enable .get()) + 'static (no
25/// references/ lifetimes)
26pub trait PropertyValue: Default + Clone + Interpolatable + 'static {}
27impl<T: Default + Clone + Interpolatable + 'static> PropertyValue for T {}
28
29impl<T: PropertyValue> Interpolatable for Property<T> {
30    fn interpolate(&self, other: &Self, t: f64) -> Self {
31        let cp_self = self.clone();
32        let cp_other = other.clone();
33        Property::computed(
34            move || cp_self.get().interpolate(&cp_other.get(), t),
35            &[self.untyped(), other.untyped()],
36        )
37    }
38}
39/// A typed wrapper over a UntypedProperty that casts to/from an untyped
40/// property on get/set
41#[derive(Clone)]
42pub struct Property<T> {
43    untyped: UntypedProperty,
44    _phantom: PhantomData<T>,
45}
46
47impl<T: PropertyValue + std::fmt::Debug> std::fmt::Debug for Property<T> {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(f, "Property ({:?})", self.get())
50    }
51}
52
53impl<T: PropertyValue> Property<T> {
54    pub fn new(val: T) -> Self {
55        Self::new_optional_name(val, None)
56    }
57
58    pub fn new_from_untyped(untyped: UntypedProperty) -> Self {
59        Self {
60            untyped,
61            _phantom: PhantomData {},
62        }
63    }
64
65    pub fn computed(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty]) -> Self {
66        Self::computed_with_config(evaluator, dependents, None)
67    }
68
69    pub fn new_with_name(val: T, name: &str) -> Self {
70        Self::new_optional_name(val, Some(name))
71    }
72
73    pub fn computed_with_name(
74        evaluator: impl Fn() -> T + 'static,
75        dependents: &[UntypedProperty],
76        name: &str,
77    ) -> Self {
78        Self::computed_with_config(evaluator, dependents, Some(name))
79    }
80
81    fn new_optional_name(val: T, name: Option<&str>) -> Self {
82        Self {
83            untyped: UntypedProperty::new(val, Vec::with_capacity(0), PropertyType::Literal, name),
84            _phantom: PhantomData {},
85        }
86    }
87
88    fn computed_with_config(
89        evaluator: impl Fn() -> T + 'static,
90        dependents: &[UntypedProperty],
91        name: Option<&str>,
92    ) -> Self {
93        let inbound: Vec<_> = dependents.iter().map(|v| v.get_id()).collect();
94        let start_val = T::default();
95        let evaluator = Rc::new(evaluator);
96        Self {
97            untyped: UntypedProperty::new(
98                start_val,
99                inbound,
100                PropertyType::Computed { evaluator },
101                name,
102            ),
103            _phantom: PhantomData {},
104        }
105    }
106
107    pub fn ease_to(&self, end_val: T, time: u64, curve: EasingCurve) {
108        self.ease_to_value(end_val, time, curve, true);
109    }
110
111    pub fn ease_to_later(&self, end_val: T, time: u64, curve: EasingCurve) {
112        self.ease_to_value(end_val, time, curve, false);
113    }
114
115    fn ease_to_value(&self, end_val: T, time: u64, curve: EasingCurve, overwrite: bool) {
116        PROPERTY_TABLE.with(|t| {
117            t.transition(
118                self.untyped.id,
119                TransitionQueueEntry {
120                    duration_frames: time,
121                    curve,
122                    ending_value: end_val,
123                },
124                overwrite,
125            )
126        })
127    }
128
129    /// Gets the currently stored value. Might be computationally
130    /// expensive in a large reactivity network since this triggers
131    /// re-evaluation of dirty property chains
132    pub fn get(&self) -> T {
133        PROPERTY_TABLE.with(|t| t.get_value(self.untyped.id))
134    }
135
136    /// Sets this properties value and sets the dirty bit recursively of all of
137    /// its dependencies if not already set
138    pub fn set(&self, val: T) {
139        PROPERTY_TABLE.with(|t| t.set_value(self.untyped.id, val));
140    }
141
142    // Get access to a mutable reference to the inner value T.
143    // Always updates dependents of this property, no matter
144    // if the value changed or not
145    pub fn update(&self, f: impl FnOnce(&mut T)) {
146        // This is a temporary impl of the update method.
147        // (very bad perf comparatively, but very safe).
148        let mut val = self.get();
149        f(&mut val);
150        self.set(val);
151    }
152
153    // Get access to a reference to the inner value T.
154    // WARNING: this method can panic if this property was already borrowed,
155    // which can happen if read is called inside a read of the same property
156    pub fn read<V>(&self, f: impl FnOnce(&T) -> V) -> V {
157        PROPERTY_TABLE.with(|t| t.read_value(self.untyped.id, f))
158    }
159
160    /// replaces a properties evaluation/inbounds/value to be the same as
161    /// target, while keeping its dependents.
162    /// WARNING: this method can introduce circular dependencies if one is not careful.
163    /// Using it wrongly can introduce memory leaks and inconsistent property behavior.
164    /// This method can be used to replace an inner value from for example a literal to
165    /// a computed computed, while keeping the link to its dependents
166    pub fn replace_with(&self, target: Property<T>) {
167        PROPERTY_TABLE.with(|t| {
168            // we know self contains T, and that target contains T, so this should never panic
169            t.replace_property_keep_outbound_connections::<T>(self.untyped.id, target.untyped.id)
170        })
171    }
172
173    /// Casts this property to its untyped version
174    pub fn untyped(&self) -> UntypedProperty {
175        self.untyped.clone()
176    }
177}
178
179impl<T: PropertyValue> Default for Property<T> {
180    fn default() -> Self {
181        Property::new(T::default())
182    }
183}
184
185/// Serialization and deserialization fully disconnects properties,
186/// and only loads them back in as literals.
187impl<'de, T: PropertyValue + Deserialize<'de>> Deserialize<'de> for Property<T> {
188    fn deserialize<D>(deserializer: D) -> Result<Property<T>, D::Error>
189    where
190        D: serde::Deserializer<'de>,
191    {
192        let value = T::deserialize(deserializer)?;
193        Ok(Property::new(value))
194    }
195}
196
197impl<T: PropertyValue + Serialize> Serialize for Property<T> {
198    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
199    where
200        S: serde::Serializer,
201    {
202        // TODO check if literal or computed, error on computed?
203        self.get().serialize(serializer)
204    }
205}
206
207/// Utility method to inspect total entry count in property table
208pub fn property_table_total_properties_count() -> usize {
209    PROPERTY_TABLE.with(|t| t.total_properties_count())
210}
211
212pub fn register_time(prop: &Property<u64>) {
213    PROPERTY_TIME.with_borrow_mut(|time| *time = prop.clone());
214}