1use std::fmt;
9
10use super::cond::Use;
11use super::{
12 Clip, Cond, Graph, Name, ParamId, ParamKind, Params, Rate, Source, State, StateId, Transition,
13};
14
15#[derive(Clone, Debug, PartialEq)]
17pub struct StateSpec {
18 pub name: Name,
20 pub clip: Clip,
22 pub rate: Rate<Name>,
24 pub lock: f32,
26 pub next: Option<Name>,
28}
29
30impl StateSpec {
31 pub fn new(name: impl Into<Name>, clip: Clip) -> Self {
33 Self {
34 name: name.into(),
35 clip,
36 rate: Rate::Fixed(1.0),
37 lock: 0.0,
38 next: None,
39 }
40 }
41
42 pub fn rate_by(mut self, param: impl Into<Name>, per: f32, min: f32, max: f32) -> Self {
44 self.rate = Rate::Param {
45 param: param.into(),
46 per,
47 min,
48 max,
49 };
50 self
51 }
52
53 pub fn lock(mut self, secs: f32) -> Self {
55 self.lock = secs;
56 self
57 }
58
59 pub fn then(mut self, next: impl Into<Name>) -> Self {
61 self.next = Some(next.into());
62 self
63 }
64}
65
66#[derive(Clone, Debug, PartialEq)]
68pub struct TransitionSpec {
69 pub from: Vec<Name>,
71 pub except: Vec<Name>,
73 pub to: Name,
75 pub when: Cond<Name>,
77 pub priority: i32,
79 pub breaks_lock: bool,
81 pub restart: bool,
83}
84
85impl TransitionSpec {
86 pub fn new<S: Into<Name>>(
88 from: impl IntoIterator<Item = S>,
89 to: impl Into<Name>,
90 when: Cond<Name>,
91 ) -> Self {
92 Self {
93 from: from.into_iter().map(Into::into).collect(),
94 except: Vec::new(),
95 to: to.into(),
96 when,
97 priority: 0,
98 breaks_lock: false,
99 restart: false,
100 }
101 }
102
103 pub fn any(to: impl Into<Name>, when: Cond<Name>) -> Self {
105 Self::new(std::iter::empty::<Name>(), to, when)
106 }
107
108 pub fn except<S: Into<Name>>(mut self, states: impl IntoIterator<Item = S>) -> Self {
110 self.except = states.into_iter().map(Into::into).collect();
111 self
112 }
113
114 pub fn priority(mut self, p: i32) -> Self {
116 self.priority = p;
117 self
118 }
119
120 pub fn breaks_lock(mut self) -> Self {
122 self.breaks_lock = true;
123 self
124 }
125
126 pub fn restart(mut self) -> Self {
128 self.restart = true;
129 self
130 }
131}
132
133#[derive(Clone, Debug, Default, PartialEq)]
135pub struct GraphSpec {
136 pub params: Vec<(Name, ParamKind)>,
138 pub states: Vec<StateSpec>,
140 pub transitions: Vec<TransitionSpec>,
142 pub entry: Option<Name>,
144}
145
146#[derive(Clone, Debug, PartialEq)]
148pub enum SpecError {
149 NoStates,
151 DuplicateState(Name),
153 DuplicateParam(Name),
155 UnknownState(Name),
157 UnknownParam(Name),
159 WrongKind {
161 param: Name,
163 declared: ParamKind,
165 },
166}
167
168impl fmt::Display for SpecError {
169 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170 match self {
171 SpecError::NoStates => write!(f, "the graph has no states"),
172 SpecError::DuplicateState(n) => write!(f, "state {n:?} is declared twice"),
173 SpecError::DuplicateParam(n) => write!(f, "parameter {n:?} is declared twice"),
174 SpecError::UnknownState(n) => write!(f, "no state called {n:?}"),
175 SpecError::UnknownParam(n) => write!(f, "no parameter called {n:?}"),
176 SpecError::WrongKind { param, declared } => {
177 write!(
178 f,
179 "parameter {param:?} is a {declared:?} and can't be used that way"
180 )
181 }
182 }
183 }
184}
185
186impl std::error::Error for SpecError {}
187
188impl GraphSpec {
189 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn bool(mut self, name: impl Into<Name>) -> Self {
196 self.params.push((name.into(), ParamKind::Bool));
197 self
198 }
199
200 pub fn float(mut self, name: impl Into<Name>) -> Self {
202 self.params.push((name.into(), ParamKind::Float));
203 self
204 }
205
206 pub fn trigger(mut self, name: impl Into<Name>, ttl: f32) -> Self {
208 self.params.push((name.into(), ParamKind::Trigger { ttl }));
209 self
210 }
211
212 pub fn state(mut self, s: StateSpec) -> Self {
214 self.states.push(s);
215 self
216 }
217
218 pub fn transition(mut self, t: TransitionSpec) -> Self {
220 self.transitions.push(t);
221 self
222 }
223
224 pub fn entry(mut self, name: impl Into<Name>) -> Self {
226 self.entry = Some(name.into());
227 self
228 }
229
230 pub fn compile(&self) -> Result<Graph, SpecError> {
232 let mut params = Params::default();
233 for (name, kind) in &self.params {
234 if params.id(name).is_some() {
235 return Err(SpecError::DuplicateParam(name.clone()));
236 }
237 params.declare(name.clone(), *kind);
238 }
239 if self.states.is_empty() {
240 return Err(SpecError::NoStates);
241 }
242 let names: Vec<&Name> = self.states.iter().map(|s| &s.name).collect();
243 for (i, n) in names.iter().enumerate() {
244 if names[..i].contains(n) {
245 return Err(SpecError::DuplicateState((*n).clone()));
246 }
247 }
248 let state = |n: &Name| {
249 names
250 .iter()
251 .position(|s| *s == n)
252 .map(|i| StateId(i as u16))
253 .ok_or_else(|| SpecError::UnknownState(n.clone()))
254 };
255 let mut param = |n: &Name, u: Use| -> Result<ParamId, SpecError> {
256 let id = params
257 .id(n)
258 .ok_or_else(|| SpecError::UnknownParam(n.clone()))?;
259 let declared = params.kind(id);
260 let ok = matches!(
261 (u, declared),
262 (Use::Bool, ParamKind::Bool)
263 | (Use::Float, ParamKind::Float)
264 | (Use::Trigger, ParamKind::Trigger { .. })
265 );
266 if ok {
267 Ok(id)
268 } else {
269 Err(SpecError::WrongKind {
270 param: n.clone(),
271 declared,
272 })
273 }
274 };
275
276 let mut states = Vec::with_capacity(self.states.len());
277 for s in &self.states {
278 let rate = match &s.rate {
279 Rate::Fixed(r) => Rate::Fixed(*r),
280 Rate::Param {
281 param: p,
282 per,
283 min,
284 max,
285 } => Rate::Param {
286 param: param(p, Use::Float)?,
287 per: *per,
288 min: *min,
289 max: *max,
290 },
291 };
292 states.push(State {
293 name: s.name.clone(),
294 clip: s.clip.clone(),
295 rate,
296 lock: s.lock,
297 next: s.next.as_ref().map(state).transpose()?,
298 });
299 }
300
301 let mut transitions = Vec::with_capacity(self.transitions.len());
302 for t in &self.transitions {
303 let ids = |v: &[Name]| v.iter().map(state).collect::<Result<Vec<_>, _>>();
304 let from = if t.from.is_empty() {
305 Source::Any {
306 except: ids(&t.except)?,
307 }
308 } else {
309 Source::States(ids(&t.from)?)
310 };
311 transitions.push(Transition {
312 from,
313 to: state(&t.to)?,
314 when: t.when.resolve(&mut param)?,
315 priority: t.priority,
316 breaks_lock: t.breaks_lock,
317 restart: t.restart,
318 });
319 }
320 transitions.sort_by_key(|t| std::cmp::Reverse(t.priority));
321
322 let entry = match &self.entry {
323 Some(n) => state(n)?,
324 None => StateId(0),
325 };
326 Ok(Graph {
327 params,
328 states,
329 transitions,
330 entry,
331 })
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::anim::Playback;
339
340 fn clip() -> Clip {
341 Clip::uniform(Playback::Loop, &[0], 0.1)
342 }
343
344 #[test]
345 fn unknown_names_and_wrong_kinds_are_errors() {
346 let base = GraphSpec::new()
347 .float("speed")
348 .state(StateSpec::new("idle", clip()))
349 .state(StateSpec::new("run", clip()));
350 let bad_state =
351 base.clone()
352 .transition(TransitionSpec::new(["idle"], "walk", Cond::Always));
353 assert_eq!(
354 bad_state.compile(),
355 Err(SpecError::UnknownState("walk".into()))
356 );
357 let bad_kind = base.clone().transition(TransitionSpec::new(
358 ["idle"],
359 "run",
360 Cond::Trigger("speed".into()),
361 ));
362 assert!(matches!(
363 bad_kind.compile(),
364 Err(SpecError::WrongKind { .. })
365 ));
366 let ok = base.transition(TransitionSpec::new(
367 ["idle"],
368 "run",
369 Cond::Above("speed".into(), 1.0),
370 ));
371 assert!(ok.compile().is_ok());
372 }
373
374 #[test]
375 fn transitions_are_ordered_by_priority_stably() {
376 let g = GraphSpec::new()
377 .state(StateSpec::new("a", clip()))
378 .state(StateSpec::new("b", clip()))
379 .state(StateSpec::new("c", clip()))
380 .transition(TransitionSpec::any("b", Cond::Always))
381 .transition(TransitionSpec::any("c", Cond::Always).priority(5))
382 .transition(TransitionSpec::any("a", Cond::Always))
383 .compile()
384 .unwrap();
385 let order: Vec<_> = g
386 .transitions
387 .iter()
388 .map(|t| g.state(t.to).name.as_ref())
389 .collect();
390 assert_eq!(order, ["c", "b", "a"]);
391 }
392}