Lifecycle and method callbacks receive one context object:ScriptContext. Use ctx.run to mutate nodes,ctx.res to load resources, and ctx.ipt to query input state.

Practical borrow model: Perro removes most day-to-day borrow friction by forcing mutation through short closure scopes. Main real footgun is nesting one runtime operation inside another active runtime closure.

RuntimeWindow (ctx.run)

Mutable. Create/remove nodes, reparent, query, run node queries, call methods, emit signals, access time helpers.

ResourceWindow (ctx.res)

Read-only. Load meshes, textures, materials, and audio via helpers like texture_load!(res, path).

InputWindow (ctx.ipt)

Read-only. Query keyboard, mouse, gamepad, player, and Joy-Con state through input helpers.

RuntimeWindow Borrow Shape

Mutable window

RuntimeWindow is a mutable borrow into runtime. Any helper that uses ctx.run participates in that borrow.

No nested RuntimeWindow

Do not call runtime helpers from inside another runtime closure. Read values like delta time first, then use saved value inside closure.

Split blocks

Put runtime work in separate blocks or closures so one borrow ends before the next starts. Copy or clone small values when needed.

Normal Rust

After runtime scoping, most borrow errors come from your own modules, functions, structs, or traits. Fix them like regular Rust.

Context signatures

// lifecycle / methods context argsctx: &mut ScriptContext<'_, RT, RS, IP>ctx.run: &mut RuntimeWindow<'_, RT>ctx.res: &ResourceWindow<'_, RS>ctx.ipt: &InputWindow<'_, IP>ctx.id: NodeID // common runtime-context userswith_node!(ctx.run, Type, id, |node| -> V { ... }) -> Vwith_node_mut!(ctx.run, Type, id, |node| -> V { ... }) -> Option<V>with_state!(ctx.run, StateType, id, |state| -> V { ... }) -> Vwith_state_mut!(ctx.run, StateType, id, |state| -> V { ... }) -> Option<V>query!(ctx.run, expr) -> Vec<NodeID>call_method!(ctx.run, id, func!("name"), params![...]) -> Variant

Example

lifecycle!({    fn on_update(&self, ctx: &mut ScriptContext<'_, RT, RS, IP>) {        // Store runtime-derived values before entering a runtime closure.        let dt = delta_time!(ctx.run);         with_node_mut!(ctx.run, Node2D, ctx.id, |node| {            node.position.x += 1.0 * dt;        });         let tex = texture_load!(ctx.res, "res://player.png");        if key_down!(ctx.ipt, KeyCode::KeyD) {            // handle input        }    }});