Behavior path
methods! defines reusable behavior on shared script type. Use &self + ScriptContext.
Perro separates behavior and data so script methods stay shareable while each node instance keeps isolated runtime state. This avoids mutable aliasing on script objects and enables cross-script calls without object ownership graphs.
methods! defines reusable behavior on shared script type. Use &self + ScriptContext.
#[State] stores per-instance data. Access via with_state!/with_state_mut! closures to scope borrows.
script_vars in scene files map into runtime script vars and can be read/write through get_var!/set_var!.
call_method! uses method ids + Variant params for deterministic runtime dispatch across scripts.
Keep each runtime data borrow in its own closure. End one state borrow before nested runtime calls. This pattern prevents borrow collisions while preserving clear intent.
Net effect: scripts can mutate other script instances from different systems and call sites safely, as long as each mutation lives in its own scoped runtime closure.
Real hazard is nested runtime window usage inside an already-active runtime closure; avoid that and borrow behavior stays predictable.
with_state!(ctx.run, StateType, node_id, |state| -> V { ... }) -> Vwith_state_mut!(ctx.run, StateType, node_id, |state| -> V { ... }) -> Option<V>get_var!(ctx.run, node_id, var!("name")) -> Variantset_var!(ctx.run, node_id, var!("name"), variant!(value)) -> ()call_method!(ctx.run, node_id, func!("method"), params![...]) -> Variantmethods!({ fn apply_damage( &self, ctx: &mut ScriptContext<'_, RT, RS, IP>, amount: i32, ) { with_state_mut!(ctx.run, EnemyState, ctx.id, |state| { state.health -= amount; }); let hp = with_state!(ctx.run, EnemyState, ctx.id, |state| state.health) ; signal_emit!(ctx.run, signal!("enemy_hp_changed"), params![ctx.id, hp]); }});