ranim_core/animation/eval.rs
1//! Evaluation protocols and the standard author-facing adapters.
2//!
3//! [`Eval`] is the single visual leaf protocol;
4//! [`EvalExt`] adds build-time conveniences;
5//! [`pure::Pure`] adapts a closure
6//! and [`iterative::Iterative`]
7//! adapts a stepping function into that protocol. `EvalDyn` is the
8//! runtime-erased leaf dispatch held by `LeafContent::Anim`.
9
10use dyn_clone::DynClone;
11
12use crate::frame::EvaluatedFrame;
13
14use super::build::{Paramed, PlaybackExt};
15
16/// Iterative (stateful, stepped) evaluation.
17pub mod iterative;
18/// Pure (closed-form) evaluation adapters.
19pub mod pure;
20
21/// The general animation segment protocol: what the runtime can do with a segment.
22///
23/// An animation's content is immutable once defined: it is a pure function of
24/// its own normalized progress `alpha ∈ [0, 1]`. The protocol exposes a single
25/// entry — `eval_alpha` — and it is a **pure query** on `&self` (evaluating
26/// the same `alpha` always yields the same `Output`, regardless of call order
27/// or repetition). No evaluator sees seconds or the scene clock; the owning
28/// cell remaps time to progress before calling in.
29///
30/// Stateful (iterative) segments memoize their integration behind a snapshot so
31/// repeated queries are cheap; pure segments are a closed form. The standard
32/// author-facing adapters live here too:
33/// [`Iterative`](crate::animation::eval::iterative::Iterative) turns an
34/// [`IterativeEval`](crate::animation::eval::iterative::IterativeEval) step
35/// function into an `Eval`, and
36/// [`Pure`](crate::animation::eval::pure::Pure) wraps a raw closure
37/// `Fn(f64) -> T`. Implementing `Eval` directly remains the path for
38/// exotic segments.
39pub trait Eval {
40 /// Value produced by this evaluator.
41 ///
42 /// Playback requires `Clone + Send + Sync + 'static`. Each sample retains
43 /// the complete value, including any containers or placement wrappers.
44 type Output;
45
46 /// Evaluate the segment's content at normalized progress `alpha`.
47 fn eval_alpha(&self, alpha: f64) -> Self::Output;
48
49 /// The content resolution declared by iterative segments: `1/N` progress
50 /// per integration step (`N` declared via
51 /// [`Iterative::with_steps`](crate::animation::eval::iterative::Iterative::with_steps)).
52 ///
53 /// `None` for non-iterative segments. This is an introspection query for
54 /// tooling (e.g. `ranim inspect tree`); it does not affect evaluation.
55 fn sim_step(&self) -> Option<f64> {
56 None
57 }
58}
59
60/// Build-time conveniences over [`Eval`], split out so `Eval` stays a single
61/// primitive (`eval_alpha`).
62///
63/// The only consumers today are the built-in pure-animation families, which use
64/// `apply_to` to write an item's end state (or `apply_alpha_to` to write an
65/// arbitrary progress state) while constructing the animation.
66pub trait EvalExt: Eval + Sized {
67 /// Write the state at progress `alpha` into `item` and return this
68 /// evaluator (defined through `eval_alpha`).
69 fn apply_alpha_to(self, item: &mut Self::Output, alpha: f64) -> Self {
70 *item = self.eval_alpha(alpha);
71 self
72 }
73
74 /// Write the end state (`alpha == 1.0`) into `item` and return this
75 /// evaluator (defined through [`EvalExt::apply_alpha_to`]).
76 fn apply_to(self, item: &mut Self::Output) -> Self {
77 self.apply_alpha_to(item, 1.0)
78 }
79}
80
81impl<E: Eval + Sized> EvalExt for E {}
82
83/// Frame insertion state shared by the leaves of one top-level animation.
84pub(crate) struct OutputContext<'a> {
85 frame: &'a mut EvaluatedFrame,
86 source_id: usize,
87 start_order: usize,
88}
89
90impl<'a> OutputContext<'a> {
91 pub(crate) fn new(frame: &'a mut EvaluatedFrame, source_id: usize) -> Self {
92 let start_order = frame.len();
93 Self {
94 frame,
95 source_id,
96 start_order,
97 }
98 }
99
100 fn push<T: Send + Sync + 'static>(&mut self, value: T) {
101 let part = self.frame.len() - self.start_order;
102 self.frame.push((self.source_id, part), value);
103 }
104}
105
106/// A cloneable complete output captured by a hold.
107#[derive(Clone)]
108pub(crate) struct DynOutput(Box<dyn CapturedOutput>);
109
110trait CapturedOutput: DynClone + Send + Sync {
111 fn write_box(self: Box<Self>, output: &mut OutputContext<'_>);
112}
113
114impl<T: Clone + Send + Sync + 'static> CapturedOutput for T {
115 fn write_box(self: Box<Self>, output: &mut OutputContext<'_>) {
116 output.push(*self);
117 }
118}
119
120dyn_clone::clone_trait_object!(CapturedOutput);
121
122impl DynOutput {
123 pub(super) fn write(self, output: &mut OutputContext<'_>) {
124 self.0.write_box(output);
125 }
126}
127
128/// The erased visual-leaf protocol held by the runtime tree.
129///
130/// Ordinary sampling inserts each complete typed output directly. Holds capture
131/// the same raw values and replay them without introducing a container payload.
132pub(super) trait EvalDyn {
133 fn eval_frame(&self, alpha: f64, output: &mut OutputContext<'_>);
134 fn eval_capture(&self, alpha: f64, output: &mut Vec<DynOutput>);
135
136 /// The iterative content step, if this leaf is an iterative segment.
137 fn sim_step(&self) -> Option<f64> {
138 None
139 }
140}
141
142impl<E> EvalDyn for E
143where
144 E: Eval,
145 E::Output: Clone + Send + Sync + 'static,
146{
147 fn eval_frame(&self, alpha: f64, output: &mut OutputContext<'_>) {
148 output.push(self.eval_alpha(alpha));
149 }
150
151 fn eval_capture(&self, alpha: f64, output: &mut Vec<DynOutput>) {
152 output.push(DynOutput(Box::new(self.eval_alpha(alpha))));
153 }
154
155 fn sim_step(&self) -> Option<f64> {
156 Eval::sim_step(self)
157 }
158}
159
160/// A held batch replays each captured value with its original concrete type.
161pub(super) struct HeldOutput(Vec<DynOutput>);
162
163impl HeldOutput {
164 pub(super) fn new(values: Vec<DynOutput>) -> Self {
165 Self(values)
166 }
167}
168
169impl EvalDyn for HeldOutput {
170 fn eval_frame(&self, _alpha: f64, output: &mut OutputContext<'_>) {
171 for value in &self.0 {
172 value.clone().write(output);
173 }
174 }
175
176 fn eval_capture(&self, _alpha: f64, output: &mut Vec<DynOutput>) {
177 output.extend(self.0.iter().cloned());
178 }
179}
180
181/// A constant evaluator.
182#[derive(Clone)]
183pub struct Static<T: Clone>(pub T);
184
185impl<T: Clone> Eval for Static<T> {
186 type Output = T;
187
188 fn eval_alpha(&self, _alpha: f64) -> Self::Output {
189 self.0.clone()
190 }
191}
192
193/// Requirement for [`StaticAnim`].
194pub trait StaticAnimRequirement: Clone + Send + Sync + 'static {}
195
196impl<T: Clone + Send + Sync + 'static> StaticAnimRequirement for T {}
197
198/// Convenience methods for zero-duration static animations.
199pub trait StaticAnim: StaticAnimRequirement + Sized {
200 /// Show this value.
201 fn show(&self) -> Paramed<Static<Self>>;
202 /// Hide this value.
203 fn hide(&self) -> Paramed<Static<Self>>;
204}
205
206impl<T: StaticAnimRequirement + 'static> StaticAnim for T {
207 fn show(&self) -> Paramed<Static<Self>> {
208 Static(self.clone()).with_duration(0.0)
209 }
210
211 fn hide(&self) -> Paramed<Static<Self>> {
212 Static(self.clone()).with_enabled(false).with_duration(0.0)
213 }
214}