pax_runtime_api/properties/
mod.rs1use 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::{Duration, EasingCurve, Interpolatable, TransitionQueueEntry};
11
12use self::properties_table::{PropertyType, PROPERTY_MILLIS, PROPERTY_TIME};
13pub use properties_table::EffectDrainReport;
14use properties_table::PROPERTY_TABLE;
15pub use untyped_property::UntypedProperty;
16
17mod private {
19 slotmap::new_key_type!(
20 pub struct PropertyId;
21 );
22}
23
24pub trait PropertyValue: Default + Clone + Interpolatable + 'static {}
29impl<T: Default + Clone + Interpolatable + 'static> PropertyValue for T {}
30
31impl<T: PropertyValue> Interpolatable for Property<T> {
32 fn interpolate(&self, other: &Self, t: f64) -> Self {
33 let cp_self = self.clone();
34 let cp_other = other.clone();
35 Property::computed(
36 move || cp_self.get().interpolate(&cp_other.get(), t),
37 &[self.untyped(), other.untyped()],
38 )
39 }
40}
41#[derive(Clone)]
46pub struct Property<T> {
47 untyped: UntypedProperty,
48 _phantom: PhantomData<T>,
49}
50
51impl<T: PropertyValue + std::fmt::Debug> std::fmt::Debug for Property<T> {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 write!(f, "Property ({:?})", self.get())
54 }
55}
56
57impl<T: PropertyValue> Property<T> {
58 pub fn new(val: T) -> Self {
60 Self::new_optional_name(val, None)
61 }
62
63 pub fn new_from_untyped(untyped: UntypedProperty) -> Self {
65 Self {
66 untyped,
67 _phantom: PhantomData {},
68 }
69 }
70
71 pub fn computed(evaluator: impl Fn() -> T + 'static, dependents: &[UntypedProperty]) -> Self {
73 Self::computed_with_config(evaluator, dependents, None, None)
74 }
75
76 pub fn computed_with_cutoff(
83 evaluator: impl Fn() -> T + 'static,
84 dependents: &[UntypedProperty],
85 cutoff: impl Fn(&T, &T) -> bool + 'static,
86 ) -> Self {
87 Self::computed_with_config(evaluator, dependents, None, Some(Rc::new(cutoff)))
88 }
89
90 pub fn new_with_name(val: T, name: &str) -> Self {
92 Self::new_optional_name(val, Some(name))
93 }
94
95 pub fn computed_with_name(
97 evaluator: impl Fn() -> T + 'static,
98 dependents: &[UntypedProperty],
99 name: &str,
100 ) -> Self {
101 Self::computed_with_config(evaluator, dependents, Some(name), None)
102 }
103
104 pub fn computed_with_cutoff_and_name(
106 evaluator: impl Fn() -> T + 'static,
107 dependents: &[UntypedProperty],
108 cutoff: impl Fn(&T, &T) -> bool + 'static,
109 name: &str,
110 ) -> Self {
111 Self::computed_with_config(evaluator, dependents, Some(name), Some(Rc::new(cutoff)))
112 }
113
114 fn new_optional_name(val: T, name: Option<&str>) -> Self {
115 Self {
116 untyped: UntypedProperty::new(val, Vec::with_capacity(0), PropertyType::Literal, name),
117 _phantom: PhantomData {},
118 }
119 }
120
121 fn computed_with_config(
122 evaluator: impl Fn() -> T + 'static,
123 dependents: &[UntypedProperty],
124 name: Option<&str>,
125 cutoff: Option<Rc<dyn Fn(&T, &T) -> bool>>,
126 ) -> Self {
127 let inbound: Vec<_> = dependents.iter().map(|v| v.get_id()).collect();
128 let start_val = T::default();
129 let evaluator = Rc::new(evaluator);
130 Self {
131 untyped: UntypedProperty::new(
132 start_val,
133 inbound,
134 PropertyType::Computed {
135 evaluator,
136 cutoff: cutoff.map(|predicate| properties_table::Cutoff {
137 predicate,
138 initialized: false,
139 }),
140 },
141 name,
142 ),
143 _phantom: PhantomData {},
144 }
145 }
146
147 pub fn ease_to<D: Into<Duration>>(&self, end_val: T, duration: D, curve: EasingCurve) {
153 self.ease_to_value(end_val, duration.into(), curve, true);
154 }
155
156 pub fn ease_to_later<D: Into<Duration>>(&self, end_val: T, duration: D, curve: EasingCurve) {
159 self.ease_to_value(end_val, duration.into(), curve, false);
160 }
161
162 fn ease_to_value(&self, end_val: T, duration: Duration, curve: EasingCurve, overwrite: bool) {
164 PROPERTY_TABLE.with(|t| {
165 t.transition(
166 self.untyped.id,
167 TransitionQueueEntry {
168 duration,
169 curve,
170 ending_value: end_val,
171 },
172 overwrite,
173 )
174 })
175 }
176
177 pub fn cancel_transitions(&self) {
183 PROPERTY_TABLE.with(|t| t.cancel_transitions::<T>(self.untyped.id));
184 }
185
186 pub fn get(&self) -> T {
190 PROPERTY_TABLE.with(|t| t.get_value(self.untyped.id))
191 }
192
193 pub fn set(&self, val: T) {
196 PROPERTY_TABLE.with(|t| t.set_value(self.untyped.id, val));
197 }
198
199 #[doc(hidden)]
204 pub fn invalidate(&self) {
205 PROPERTY_TABLE.with(|t| t.invalidate(self.untyped.id));
206 }
207
208 pub fn set_if_neq(&self, val: T) -> bool
212 where
213 T: PartialEq,
214 {
215 let should_set =
216 PROPERTY_TABLE.with(|t| t.read_value(self.untyped.id, |current: &T| current != &val));
217 if should_set {
218 self.set(val);
219 }
220 should_set
221 }
222
223 pub fn update(&self, f: impl FnOnce(&mut T)) {
227 let mut val = self.get();
230 f(&mut val);
231 self.set(val);
232 }
233
234 pub fn read<V>(&self, f: impl FnOnce(&T) -> V) -> V {
239 PROPERTY_TABLE.with(|t| t.read_value(self.untyped.id, f))
240 }
241
242 pub fn replace_with(&self, target: Property<T>) {
248 PROPERTY_TABLE.with(|t| {
249 t.replace_property_keep_outbound_connections::<T>(self.untyped.id, target.untyped.id)
251 })
252 }
253
254 pub fn untyped(&self) -> UntypedProperty {
256 self.untyped.clone()
257 }
258}
259
260impl<T: PropertyValue> Default for Property<T> {
261 fn default() -> Self {
262 Property::new(T::default())
263 }
264}
265
266impl<'de, T: PropertyValue + Deserialize<'de>> Deserialize<'de> for Property<T> {
269 fn deserialize<D>(deserializer: D) -> Result<Property<T>, D::Error>
270 where
271 D: serde::Deserializer<'de>,
272 {
273 let value = T::deserialize(deserializer)?;
274 Ok(Property::new(value))
275 }
276}
277
278impl<T: PropertyValue + Serialize> Serialize for Property<T> {
279 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
280 where
281 S: serde::Serializer,
282 {
283 self.get().serialize(serializer)
285 }
286}
287
288pub fn property_table_total_properties_count() -> usize {
290 PROPERTY_TABLE.with(|t| t.total_properties_count())
291}
292
293#[doc(hidden)]
294pub fn register_effect_property(prop: &Property<()>) {
295 PROPERTY_TABLE.with(|t| t.register_effect(prop.untyped.id));
296}
297
298#[doc(hidden)]
299pub fn register_effect_property_with_name(prop: &Property<()>, debug_name: &str) {
300 PROPERTY_TABLE.with(|t| t.register_effect_with_name(prop.untyped.id, Some(debug_name)));
301}
302
303#[doc(hidden)]
304pub fn drain_effects(max_iterations: usize) -> usize {
305 PROPERTY_TABLE.with(|t| t.drain_effects(max_iterations))
306}
307
308#[doc(hidden)]
309pub fn drain_effects_with_report(max_iterations: usize) -> EffectDrainReport {
310 PROPERTY_TABLE.with(|t| t.drain_effects_with_report(max_iterations))
311}
312
313#[doc(hidden)]
314pub fn property_outbound_debug_names(prop: &UntypedProperty) -> Vec<String> {
315 PROPERTY_TABLE.with(|t| t.outbound_debug_names(prop.id))
316}
317
318#[doc(hidden)]
319pub fn property_has_direct_outbound(source: &UntypedProperty, outbound: &UntypedProperty) -> bool {
320 PROPERTY_TABLE.with(|t| t.has_direct_outbound(source.id, outbound.id))
321}
322
323pub fn register_time(prop: &Property<u64>) {
325 PROPERTY_TIME.with_borrow_mut(|time| *time = prop.clone());
326}
327
328pub fn register_millis(prop: &Property<u64>) {
330 PROPERTY_MILLIS.with_borrow_mut(|time| *time = prop.clone());
331}