Venus Lua API › Core

Behavior

ScriptBehavior component — a live Lua script bound to a GameObject.

authored lua/venus_lua/src/lua_behavior.rs

Behavior

A Behavior is the handle to a ScriptBehavior component: the running instance of a
Script asset attached to a GameObject. Behavior.Attach creates one
from a loaded Script, giving the instance its own duplicated copy of the script’s function
references; the component then receives the script’s lifecycle callbacks. For the file the script
must return, the self it receives, and the full catalog of callbacks the component calls, see the
Scripting Model.

Static calls use the type table (Behavior.Attach(...)); instance methods use colon syntax on a
handle (behavior:Activate()).

Types

Behavior — an opaque 8-byte handle (userdata) to a ScriptBehavior component. A handle may go
stale (its component destroyed). The two failure modes differ:

tostring(behavior) always yields the constant string Behavior — it carries no object name and
does not change when the handle goes stale.

Creation & Destruction

Behavior.Attach( GameObject object, Script script ) : Behavior or nil
Creates a new ScriptBehavior on object using the functions from the script asset (each
instance gets its own duplicated function refs). Raises a Lua type error if object is not a
GameObject or script is not a Script. Returns nil and logs otherwise: NotFound if the script
cache is unavailable or the script index is not loaded, InvalidArgument if the component cannot
be added (e.g. object is stale).

Behavior.Get( GameObject object ) : Behavior or nil
Returns the ScriptBehavior already attached to object. Raises a Lua type error if object is not
a GameObject; returns nil if the object has no ScriptBehavior (no warning).

Behavior.Exists( Behavior behavior ) : boolean
The safe test. Uses luaL_testudata, so any non-Behavior value (including nil) returns false
without raising or warning. Returns true only if the handle resolves to live component data.

behavior:Destroy( ) : void
Destroys the component if the handle is live; silently no-ops on a stale handle (the result is
discarded — no warning, unlike GameObject:Destroy). The component stops existing immediately
(the GameObject survives — only this component is removed). Called from inside a callback — including
a component destroying itself from Update — its OnDestroy and slot reclaim are deferred to
the end of the update cycle, so self stays valid until the callback returns.

Functions

behavior:GameObject( ) : GameObject or nil
The GameObject this behavior is attached to (pushed with its cached slot resolved, so the returned
handle’s transform getters take the fast path). Returns nil and logs a warning (NotFound) if
the owner cannot be resolved (stale handle).

behavior:Active( ) : boolean or nil
The component’s own enabled flag — what Activate()/Deactivate() write. Mirrors
GameObject:Active(). Returns nil and logs a warning (NotFound) on a stale handle, so a
destroyed behavior is distinguishable from a merely disabled one.

Note this is intent, not effect: a behavior whose own flag is enabled but whose owning
GameObject is deactivated still reports true here — and does not run. Use
ActiveInHierarchy() for the effective answer.

behavior:ActiveInHierarchy( ) : boolean or nil
true only when this behavior’s own flag is enabled and its owning GameObject is active in
hierarchy — i.e. the behavior is actually receiving OnUpdate. This is the predicate the engine
itself uses to gate dispatch. Returns nil on a stale handle.

Fixed 2026-07 (was: behavior:Active() returned handle liveness, not enabled state — it
called get_component_data, which applies no active-flag filter, so it stayed true after
behavior:Deactivate() and duplicated Behavior.Exists). Active() now reads the component’s
enabled flag and ActiveInHierarchy() was added, matching the C reference’s
IcoComponent_ActiveSelf / IcoComponent_ActiveInHierarchy split. The three predicates are now
distinct: Behavior.Exists = the handle resolves; Active = own flag; ActiveInHierarchy =
own flag AND owner active.

Methods

behavior:Activate( ) : void
Sets the component’s own enabled flag. Its enable callback runs only if the owning GameObject is
also active. Silently no-ops on a stale handle — the result is discarded, so no warning is logged.

behavior:Deactivate( ) : void
Clears the component’s own enabled flag; the disable callback runs only if the behavior was
actually active. Silently no-ops on a stale handle.

Example

-- Load a script once, then attach it as a behavior on a new object.
local script = Script.Create("spinner", "scripts/spinner.lua")

local go = GameObject.Create()
local behavior = Behavior.Attach(go, script)

-- Attach can fail (returns nil) — guard before using the handle.
if Behavior.Exists(behavior) then
    behavior:Activate()
end

-- The three predicates answer three different questions.
behavior:Deactivate()
print(Behavior.Exists(behavior))     --> true   (the handle still resolves)
print(behavior:Active())             --> false  (its own flag is off)
print(behavior:ActiveInHierarchy())  --> false  (so it is not running)

behavior:Activate()
go:Deactivate()
print(behavior:Active())             --> true   (own flag untouched by the owner)
print(behavior:ActiveInHierarchy())  --> false  (but the owner is off, so it still isn't running)