use crate::builder::*; use crate::graph::*; use rhai::{Array, Engine, EvalAltResult, Position}; pub(crate) fn err(message: String) -> Box { Box::new(EvalAltResult::ErrorRuntime(message.into(), Position::NONE)) } fn condition(parameter: &str, mode: CondMode, threshold: f32) -> Condition { Condition { parameter: parameter.to_string(), mode, threshold, } } fn grid(value: i64) -> Result> { i32::try_from(value).map_err(|_| err(format!("grid position {value} does not fit in a 32-bit integer"))) } fn add_state(aac: &Aac, layer: usize, machine: &[usize], name: &str, x: i64, y: i64) -> Result> { let position = GridPos::new(grid(x)?, grid(y)?); let state = aac.write(|graph| { let states = &mut machine_mut(graph, layer, machine).states; states.push(State { name: name.to_string(), position, ..Default::default() }); states.len() - 1 }); Ok(StateRef { aac: aac.clone(), layer, machine: machine.to_vec(), state, }) } fn add_sub_machine( aac: &Aac, layer: usize, machine: &[usize], name: &str, x: i64, y: i64, ) -> Result> { let position = GridPos::new(grid(x)?, grid(y)?); let index = aac.write(|graph| { let sub_machines = &mut machine_mut(graph, layer, machine).sub_machines; sub_machines.push(StateMachine { name: Some(name.to_string()), position, ..Default::default() }); sub_machines.len() - 1 }); let mut path = machine.to_vec(); path.push(index); Ok(MachineRef { aac: aac.clone(), layer, machine: path, }) } fn assert_new_parameter(aac: &Aac, name: &str) -> Result<(), Box> { if name.trim().is_empty() { return Err(err("parameter name is empty".to_string())); } let duplicate = aac.write(|graph| graph.parameters.iter().any(|p| p.name() == name)); if duplicate { return Err(err(format!("parameter `{name}` is already declared"))); } Ok(()) } pub fn engine(aac: Aac) -> Engine { let mut engine = Engine::new(); engine.register_type_with_name::("Aac"); engine.register_type_with_name::("FloatParam"); engine.register_type_with_name::("IntParam"); engine.register_type_with_name::("BoolParam"); engine.register_type_with_name::("Condition"); engine.register_type_with_name::("Controller"); engine.register_type_with_name::("Layer"); engine.register_type_with_name::("Machine"); engine.register_type_with_name::("State"); engine.register_type_with_name::("Transition"); engine.register_type_with_name::("Clip"); engine.register_type_with_name::("AnimationStore"); engine.register_type_with_name::("BlendTree"); register_conditions(&mut engine); register_setup(&mut engine, aac); register_navigation(&mut engine); register_transitions(&mut engine); crate::motion_api::register(&mut engine); engine } /// Leaf conditions come from overloaded comparison operators, because Rhai's `&&`/`||` cannot be /// overloaded. Combine several conditions with `when_all([...])`. fn register_conditions(engine: &mut Engine) { engine.register_fn(">", |p: FloatParam, v: f64| condition(&p.name, CondMode::Greater, v as f32)); engine.register_fn("<", |p: FloatParam, v: f64| condition(&p.name, CondMode::Less, v as f32)); engine.register_fn("==", |p: FloatParam, v: f64| condition(&p.name, CondMode::Equals, v as f32)); engine.register_fn("!=", |p: FloatParam, v: f64| condition(&p.name, CondMode::NotEqual, v as f32)); engine.register_fn(">", |p: IntParam, v: i64| condition(&p.name, CondMode::Greater, v as f32)); engine.register_fn("<", |p: IntParam, v: i64| condition(&p.name, CondMode::Less, v as f32)); engine.register_fn("==", |p: IntParam, v: i64| condition(&p.name, CondMode::Equals, v as f32)); engine.register_fn("!=", |p: IntParam, v: i64| condition(&p.name, CondMode::NotEqual, v as f32)); engine.register_fn("==", |p: BoolParam, v: bool| { condition(&p.name, if v { CondMode::If } else { CondMode::IfNot }, 0.0) }); engine.register_fn("!=", |p: BoolParam, v: bool| { condition(&p.name, if v { CondMode::IfNot } else { CondMode::If }, 0.0) }); } fn register_setup(engine: &mut Engine, aac: Aac) { engine.register_fn("AnimatorAsCode", move || aac.clone()); engine.register_fn("system_name", |a: &mut Aac, name: &str| { a.write(|graph| graph.system_name = name.to_string()); }); engine.register_fn("asset_key", |a: &mut Aac, key: &str| { a.write(|graph| graph.asset_key = key.to_string()); }); engine.register_fn("float_param", |a: &mut Aac, name: &str, default: f64| -> Result> { assert_new_parameter(a, name)?; a.write(|graph| { graph.parameters.push(Parameter::Float { name: name.to_string(), default: default as f32, }) }); Ok(FloatParam { name: name.to_string(), }) }); engine.register_fn("int_param", |a: &mut Aac, name: &str, default: i64| -> Result> { assert_new_parameter(a, name)?; a.write(|graph| { graph.parameters.push(Parameter::Int { name: name.to_string(), default: default as i32, }) }); Ok(IntParam { name: name.to_string(), }) }); engine.register_fn( "bool_param", |a: &mut Aac, name: &str, default: bool| -> Result> { assert_new_parameter(a, name)?; a.write(|graph| { graph.parameters.push(Parameter::Bool { name: name.to_string(), default, }) }); Ok(BoolParam { name: name.to_string(), }) }, ); engine.register_fn("new_controller", |a: &mut Aac| ControllerBuilder { aac: a.clone() }); } fn register_navigation(engine: &mut Engine) { // There is exactly one controller, so this is the only place layers are attached. engine.register_fn("layer", |c: ControllerBuilder, name: &str| -> LayerBuilder { let layer = c.aac.write(|graph| { graph.controller.layers.push(Layer { name: name.to_string(), ..Default::default() }); graph.controller.layers.len() - 1 }); LayerBuilder { aac: c.aac.clone(), layer, } }); engine.register_fn("state", |l: LayerBuilder, name: &str, x: i64, y: i64| { add_state(&l.aac, l.layer, &[], name, x, y) }); engine.register_fn("state", |m: MachineRef, name: &str, x: i64, y: i64| { add_state(&m.aac, m.layer, &m.machine, name, x, y) }); engine.register_fn("sub_machine", |l: LayerBuilder, name: &str, x: i64, y: i64| { add_sub_machine(&l.aac, l.layer, &[], name, x, y) }); engine.register_fn("sub_machine", |m: MachineRef, name: &str, x: i64, y: i64| { add_sub_machine(&m.aac, m.layer, &m.machine, name, x, y) }); engine.register_fn("any_state", |l: LayerBuilder| AnyStateRef { aac: l.aac.clone(), layer: l.layer, machine: Vec::new(), }); engine.register_fn("any_state", |m: MachineRef| AnyStateRef { aac: m.aac.clone(), layer: m.layer, machine: m.machine.clone(), }); } fn register_transitions(engine: &mut Engine) { engine.register_fn("transition_to", |from: StateRef, to: StateRef| from.transition_to(&to)); engine.register_fn("transition_to", |from: AnyStateRef, to: StateRef| from.transition_to(&to)); engine.register_fn("when", |t: TransitionRef, c: Condition| { t.add_conditions([c]); t }); engine.register_fn( "when_all", |t: TransitionRef, conditions: Array| -> Result> { let mut collected = Vec::with_capacity(conditions.len()); for value in conditions { collected.push( value .try_cast::() .ok_or_else(|| err("when_all expects a list of conditions".to_string()))?, ); } t.add_conditions(collected); Ok(t) }, ); engine.register_fn("duration", |t: TransitionRef, seconds: f64| { t.update(|transition| transition.duration = seconds as f32); t }); engine.register_fn("no_exit_time", |t: TransitionRef| { t.update(|transition| { transition.has_exit_time = false; transition.exit_time = 0.0; }); t }); engine.register_fn("exit_time", |t: TransitionRef, normalized: f64| { t.update(|transition| { transition.has_exit_time = true; transition.exit_time = normalized as f32; }); t }); engine.register_fn("ordered_interruption", |t: TransitionRef, value: bool| { t.update(|transition| transition.ordered_interruption = value); t }); engine.register_fn("source_interruption", |t: TransitionRef| { t.update(|transition| transition.source_interruption = true); t }); engine.register_fn("to_self", |t: TransitionRef| { t.update(|transition| transition.can_transition_to_self = true); t }); }