ranim_core/animation/eval.rs
1//! Evaluation protocols and the standard author-facing adapters.
2//!
3//! [`Eval`](crate::animation::eval::Eval) is the single visual leaf protocol;
4//! [`EvalExt`](crate::animation::eval::EvalExt) adds build-time conveniences;
5//! [`pure::Pure`](crate::animation::eval::pure::Pure) adapts a closure
6//! and [`iterative::Iterative`](crate::animation::eval::iterative::Iterative)
7//! adapts a stepping function into that protocol. `EvalDyn` is the
8//! runtime-erased leaf dispatch held by `NodeContent::Leaf`.
9
10use crate::core_item::{AnyExtractCoreItem, DynItem};
11
12use super::build::{Paramed, PlaybackExt};
13
14/// Iterative (stateful, stepped) evaluation.
15pub mod iterative;
16/// Pure (closed-form) evaluation adapters.
17pub mod pure;
18
19/// The general animation segment protocol: what the runtime can do with a segment.
20///
21/// An animation's content is immutable once defined: it is a pure function of
22/// its own normalized progress `alpha ∈ [0, 1]`. The protocol exposes a single
23/// entry — `eval_alpha` — and it is a **pure query** on `&self` (evaluating
24/// the same `alpha` always yields the same `Output`, regardless of call order
25/// or repetition). No evaluator sees seconds or the scene clock; the owning
26/// cell remaps time to progress before calling in.
27///
28/// Stateful (iterative) segments memoize their integration behind a snapshot so
29/// repeated queries are cheap; pure segments are a closed form. The standard
30/// author-facing adapters live here too:
31/// [`Iterative`](crate::animation::eval::iterative::Iterative) turns an
32/// [`IterativeEval`](crate::animation::eval::iterative::IterativeEval) step
33/// function into an `Eval`, and
34/// [`Pure`](crate::animation::eval::pure::Pure) wraps a raw closure
35/// `Fn(f64) -> T`. Implementing `Eval` directly remains the path for
36/// exotic segments.
37pub trait Eval {
38 /// Value produced by this evaluator.
39 type Output;
40
41 /// Evaluate the segment's content at normalized progress `alpha`.
42 fn eval_alpha(&self, alpha: f64) -> Self::Output;
43
44 /// The content resolution declared by iterative segments: `1/N` progress
45 /// per integration step (`N` declared via
46 /// [`Iterative::with_steps`](crate::animation::eval::iterative::Iterative::with_steps)).
47 ///
48 /// `None` for non-iterative segments. This is an introspection query for
49 /// tooling (e.g. `ranim inspect tree`); it does not affect evaluation.
50 fn sim_step(&self) -> Option<f64> {
51 None
52 }
53}
54
55/// Build-time conveniences over [`Eval`], split out so `Eval` stays a single
56/// primitive (`eval_alpha`).
57///
58/// The only consumers today are the built-in pure-animation families, which use
59/// `apply_to` to write an item's end state (or `apply_alpha_to` to write an
60/// arbitrary progress state) while constructing the animation.
61pub trait EvalExt: Eval + Sized {
62 /// Write the state at progress `alpha` into `item` and return this
63 /// evaluator (defined through `eval_alpha`).
64 fn apply_alpha_to(self, item: &mut Self::Output, alpha: f64) -> Self {
65 *item = self.eval_alpha(alpha);
66 self
67 }
68
69 /// Write the end state (`alpha == 1.0`) into `item` and return this
70 /// evaluator (defined through [`EvalExt::apply_alpha_to`]).
71 fn apply_to(self, item: &mut Self::Output) -> Self {
72 self.apply_alpha_to(item, 1.0)
73 }
74}
75
76impl<E: Eval + Sized> EvalExt for E {}
77
78/// The erased visual-leaf protocol: [`Eval`] without its type.
79///
80/// This is the only type-erased box in the runtime tree
81/// (`NodeContent::Leaf`) — the open world of user evaluators. Containers are
82/// closed runtime variants and never implement this.
83pub(super) trait EvalDyn {
84 /// Evaluate this leaf's content at its normalized progress `alpha`,
85 /// pushing the resulting erased items into `output`.
86 fn eval_into(&self, alpha: f64, output: &mut Vec<DynItem>);
87
88 /// The iterative content step, if this leaf is an iterative segment.
89 fn sim_step(&self) -> Option<f64> {
90 None
91 }
92}
93
94impl<E> EvalDyn for E
95where
96 E: Eval,
97 E::Output: AnyExtractCoreItem,
98{
99 fn eval_into(&self, alpha: f64, output: &mut Vec<DynItem>) {
100 output.push(DynItem(Box::new(self.eval_alpha(alpha))));
101 }
102
103 fn sim_step(&self) -> Option<f64> {
104 Eval::sim_step(self)
105 }
106}
107
108/// A constant evaluator.
109pub struct Static<T: Clone>(pub T);
110
111impl<T: Clone> Eval for Static<T> {
112 type Output = T;
113
114 fn eval_alpha(&self, _alpha: f64) -> Self::Output {
115 self.0.clone()
116 }
117}
118
119/// Requirement for [`StaticAnim`].
120pub trait StaticAnimRequirement: Clone + AnyExtractCoreItem {}
121
122impl<T: Clone + AnyExtractCoreItem> StaticAnimRequirement for T {}
123
124/// Convenience methods for zero-duration static animations.
125pub trait StaticAnim: StaticAnimRequirement + Sized {
126 /// Show this value.
127 fn show(&self) -> Paramed<Static<Self>>;
128 /// Hide this value.
129 fn hide(&self) -> Paramed<Static<Self>>;
130}
131
132impl<T: StaticAnimRequirement + 'static> StaticAnim for T {
133 fn show(&self) -> Paramed<Static<Self>> {
134 Static(self.clone()).with_duration(0.0)
135 }
136
137 fn hide(&self) -> Paramed<Static<Self>> {
138 Static(self.clone()).with_enabled(false).with_duration(0.0)
139 }
140}