State
Back to ScriptsWhy #[State] is useful
#[State] keeps behavior and data separate. Behavior methods stay shareable on script type, while runtime stores per-node instance data. Because state access is scoped with closures, runtime borrows end predictably and cross-system calls stay safe.
Behavior: methods on &self reused across many node instances.
Data: runtime-owned #[State] instance per node/script binding.
Safety: with_state!/with_state_mut! scope borrows and prevent long-lived mutable access.
API links
State struct example
use perro_api::prelude::*; #[derive(Clone, Copy, Variant)]struct AimPoint { target: Vector2,} #[State]struct PlayerState { #[default = 100] health: i32, #[default = 220.0] speed: f32, #[default = AimPoint { target: Vector2::ZERO }] aim: AimPoint,}Runtime signatures
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, member) -> Variantset_var!(ctx.run, node_id, member, value) -> ()call_method!(ctx.run, node_id, method, params) -> Variantwith_state_mut! does mutation and can return closure data through Option<V>.
let hp_after = with_state_mut!(ctx.run, PlayerState, ctx.id, |state| { state.health -= 5; state.health}); match hp_after { Some(hp) => { /* use hp */ } None => { /* state missing / type mismatch */ }}Scene script_vars mapping
script_vars inject per-instance script data from scene authoring. Shared behavior reads/writes those values at runtime without hardcoding per-node constants.
[player][Node2D]script = "res://scripts/player.rs"script_vars = { health = 140, speed = 280.0, aim = { target = (32.0, -16.0) }}[/Node2D][/player]