/** * Unity-style behaviour lifecycle over a headless id-keyed node tree, ported from * vladkrutenyuk/three-start (MIT) and adapted to run without three.js: nodes are plain * string ids (use entity instance ids to pair with `entityStore`), and the shell binds * render-side hooks separately. * * Lifecycle per behaviour: `onAwake` (once, first activation) → `onEnable` (every * inactive→active transition) → `onStart` (once, after the first `onEnable`) → * `onUpdate` (per `world.update` while active) → `onDisable` (every active→inactive * transition) → `onDestroy` (once, always — even if never activated). * * `world.start()` bootstraps in passes: modules awake → modules start → modules join * update dispatch → ALL behaviours on effectively-active nodes awake (even disabled * ones) → enable/start/subscribe. Behaviours attached during the module pass are * deferred to the behaviour passes, so module `onUpdate` always dispatches before any * behaviour `onUpdate`. */ /** * Declaration-merging registry games and adapters augment for typed access to * world modules: * * ```ts * declare module "@jgengine/core/behaviour/behaviour" { * interface JGEngineRegister { * modules: { physics: PhysicsModule; director: DirectorModule }; * } * } * ``` */ export interface JGEngineRegister { } export type RegisterField = TKey extends keyof JGEngineRegister ? JGEngineRegister[TKey] : TFallback; /** Resolves to the augmented module map when `JGEngineRegister` declares one. */ export type BehaviourModules = RegisterField<"modules", Record>; interface UpdateSubscriber { onUpdate(dt: number): void; } interface BehaviourWorldInternal extends BehaviourWorld { _subscribeUpdate(subscriber: UpdateSubscriber): void; _unsubscribeUpdate(subscriber: UpdateSubscriber): void; _isEffectivelyActive(nodeId: string): boolean; _detachBehaviour(behaviour: Behaviour): void; } /** * Subclass and override the lifecycle hooks. A behaviour only joins the per-frame * update dispatch if it actually overrides `onUpdate` (prototype-identity check at * each activation), so hook-only behaviours cost nothing per frame. */ export declare class Behaviour { /** @internal */ _world: BehaviourWorldInternal | null; /** @internal */ _nodeId: string | null; /** @internal */ _awoken: boolean; /** @internal */ _startedOnce: boolean; /** @internal */ _isActive: boolean; private _enabled; private _destroyed; /** The node this behaviour is attached to; available from `onAwake` onward, never in the constructor. */ get nodeId(): string; get world(): BehaviourWorld; get modules(): BehaviourModules; /** Own enabled flag; effective activity also requires the node's hierarchy to be active. */ get enabled(): boolean; /** Whether the lifecycle is currently in the active span (`onEnable` fired, `onDisable` not yet). */ get isActive(): boolean; onAwake(): void; onEnable(): void; onStart(): void; onUpdate(_dt: number): void; onDisable(): void; onDestroy(): void; enable(): void; disable(): void; /** Flips the flag only when the node is hierarchy-inactive or the world hasn't started; lifecycle catches up on the next activation. */ setEnabled(enabled: boolean): void; /** Detaches from the node and fires `onDisable` (if active) then `onDestroy`. */ destroy(): void; /** @internal */ _activate(): void; /** @internal */ _deactivate(): void; /** @internal */ _destroy(): void; } /** * A world-lifetime service with typed sibling access via `this.modules`. Modules awake * and start before any behaviour during `world.start()`, subscribe to update dispatch * first (their `onUpdate` fires before every behaviour's), and have no disable/destroy — * they live as long as the world. */ export declare class BehaviourModule { /** @internal */ _world: BehaviourWorldInternal | null; get world(): BehaviourWorld; get modules(): BehaviourModules; onAwake(): void; onStart(): void; onUpdate(_dt: number): void; /** @internal */ _subscribe(): void; } export interface BehaviourWorld { /** Creates the node if missing (parented under `parentId` when given, else a root); reparents an existing node when `parentId` differs. */ add(id: string, parentId?: string): void; has(id: string): boolean; parentOf(id: string): string | null; childrenOf(id: string): readonly string[]; /** Moves `id` (and subtree) under `parentId` (`null` = root), firing enable/disable cascades when effective activity changes. Throws on cycles. */ setParent(id: string, parentId: string | null): void; /** Attaches and immediately activates when the world has started, the node is effectively active, and the behaviour is enabled (deferred during bootstrap). */ attach(nodeId: string, behaviour: T): T; behavioursOf(nodeId: string): readonly Behaviour[]; /** * Sets the node's own flag and cascades activation/deactivation through the subtree, * pruning at descendants whose own flag is false — so reactivating a parent restores * exactly the per-child stored flags. A flip under an inactive ancestor changes only * the flag. */ setActive(id: string, active: boolean): void; /** The node's own flag; unknown ids count active. */ isActiveSelf(id: string): boolean; /** Own flag AND every ancestor's; unknown ids count active. */ isActive(id: string): boolean; /** Registers modules by key. Throws on duplicate keys or after `start()`. */ addModules(modules: Partial): void; readonly modules: BehaviourModules; /** Bootstraps: modules awake/start/subscribe, then all behaviours on effectively-active nodes awake, then enable+start+subscribe. Idempotent. */ start(): void; started(): boolean; /** Dispatches `onUpdate(dt)` to modules then behaviours, over a snapshot — subscribers added mid-dispatch first fire next update. */ update(dt: number): void; /** Destroys every behaviour in the subtree (parent-first) and removes the nodes. */ destroyNode(id: string): boolean; /** Destroys all nodes and behaviours and clears update dispatch. */ destroy(): void; } export declare function createBehaviourWorld(): BehaviourWorld; export {};