import type ECSpresso from "./ecspresso"; import type { SystemDefaults } from "./system-registrar"; import type { FilteredEntity, QueryDefinition, System, SystemPhase } from "./types"; import type { WorldConfig, EmptyConfig } from "./type-utils"; import type { CleanupControl } from "./cleanup-control"; export declare const PROCESS_EACH_QUERY: "__each"; type ProcessEachKey = typeof PROCESS_EACH_QUERY; /** * Builder class for creating type-safe ECS Systems with proper query inference. * Systems are automatically registered with their ECSpresso instance when * finalized (at the start of initialize() or update()). */ export declare class SystemBuilder> = {}, Label extends string = string, SysGroups extends string = never, ResourceKeys extends keyof Cfg['resources'] = never, Singletons extends Record> = {}> { private _label; private queries; private singletons; private processFunction?; private detachFunction?; private initializeFunction?; private eventHandlers?; private _priority; private _phase; private _groups; private _inScreens?; private _excludeScreens?; private _requiredAssets?; private _runWhenEmpty; private _entityEnterHandlers; private _resourceKeys?; constructor(_label: string, defaults?: SystemDefaults); get label(): string; /** * Create a system object with all configured properties. * @internal Used by ECSpresso to finalize and register the system */ _createSystemObject(): System; /** * Set the priority of this system. Systems with higher priority values * execute before those with lower values. Systems with the same priority * execute in the order they were registered. * @param priority The priority value (default: 0) * @returns This SystemBuilder instance for method chaining */ setPriority(priority: number): this; /** * Set the execution phase for this system. * Systems are grouped by phase and executed in order: * preUpdate -> fixedUpdate -> update -> postUpdate -> render * @param phase The phase to assign this system to (default: 'update') * @returns This SystemBuilder instance for method chaining */ inPhase(phase: SystemPhase): this; /** * Add this system to a group. Systems can belong to multiple groups. * When any group a system belongs to is disabled, the system will be skipped. * @param groupName The name of the group to add the system to * @returns This SystemBuilder instance for method chaining */ inGroup(groupName: G): SystemBuilder; /** * Restrict this system to only run in specified screens. * System will be skipped during update() when the current screen * is not in this list. * @param screens Array of screen names where this system should run * @returns This SystemBuilder instance for method chaining */ inScreens(screens: ReadonlyArray): this; /** * Exclude this system from running in specified screens. * System will be skipped during update() when the current screen * is in this list. * @param screens Array of screen names where this system should NOT run * @returns This SystemBuilder instance for method chaining */ excludeScreens(screens: ReadonlyArray): this; /** * Require specific assets to be loaded for this system to run. * System will be skipped during update() if any required asset * is not loaded. * @param assets Array of asset keys that must be loaded * @returns This SystemBuilder instance for method chaining */ requiresAssets(assets: ReadonlyArray): this; /** * Allow this system to run even when all queries return zero entities. * By default, systems with queries are skipped when no entities match. */ runWhenEmpty(): this; /** * Declare resource dependencies for this system. Resource values are resolved * before each process call, while the containing object is reused every frame. * The resolved resources are available as ctx.resources in setProcess. * @param keys Array of resource keys to resolve * @returns This SystemBuilder instance for method chaining */ withResources(keys: readonly RK[]): SystemBuilder; /** * Add a query definition to the system. * * When `mutates` is declared, every iterated entity is automatically * `markChanged`'d for each listed component after the system's * `process()` returns. Components in `with` but absent from `mutates` * are narrowed to `Readonly` in the iteration entity type. */ addQuery> = Queries & Record>>(name: QueryName, definition: { with: ReadonlyArray; without?: ReadonlyArray; changed?: ReadonlyArray; optional?: ReadonlyArray; parentHas?: ReadonlyArray; mutates?: ReadonlyArray; }): SystemBuilder; /** * Add a singleton query — a named query that yields a single * `FilteredEntity | undefined` instead of an array. Surfaces on the * process context's `queries` object alongside regular queries. * * When multiple entities match, the first is returned (no error). Use * the instance-level `getSingleton` / `tryGetSingleton` helpers on * `ECSpresso` if you need strictness guarantees. * * When `mutates` is declared, the resolved entity is automatically * `markChanged`'d for each listed component after the system's * `process()` returns. Components in `with` but absent from `mutates` * are narrowed to `Readonly` in the iteration entity type. */ addSingleton> = Singletons & Record>>(name: SingletonName, definition: { with: ReadonlyArray; without?: ReadonlyArray; changed?: ReadonlyArray; optional?: ReadonlyArray; parentHas?: ReadonlyArray; mutates?: ReadonlyArray; }): SystemBuilder; /** * Set the system's process function that runs each update. * The callback receives a single context object { queries, dt, ecs, resources? }. * The context is pre-allocated per system and reused every frame. * @param process Function to process entities matching the system's queries each update * @returns This SystemBuilder instance for method chaining */ setProcess(process: SystemProcessFn): this; private _wrapWithResources; /** * Inline-query terminator: define a single query and a per-entity callback * in one call. Collapses the common `addQuery` + `setProcess` + for-loop * pattern into a single chain step. * * Only valid on a builder with no prior queries or process function — * TypeScript narrows `this` to `never` otherwise, and a runtime guard * throws for untyped callers. For multi-query systems use * `addQuery` + `setProcess`. * * When `mutates` is declared, the callback may `return false` to skip the * auto-mark for that specific entity. Returning `true`, `undefined`, or * any other value stamps all components listed in `mutates`. Components * in `with` but absent from `mutates` are narrowed to `Readonly` on * the per-entity iteration type. * * @param definition Inline query definition (with / without / optional / changed / parentHas / mutates) * @param process Callback invoked once per matching entity each frame */ setProcessEach(this: [keyof Queries] extends [never] ? [keyof Singletons] extends [never] ? SystemBuilder : never : never, definition: { with: ReadonlyArray; without?: ReadonlyArray; optional?: ReadonlyArray; changed?: ReadonlyArray; parentHas?: ReadonlyArray; mutates?: ReadonlyArray; }, process: (ctx: { entity: FilteredEntity; dt: number; ecs: ECSpresso; } & ([ResourceKeys] extends [never] ? {} : { resources: { readonly [K in ResourceKeys]: Cfg['resources'][K]; }; })) => boolean | void): SystemBuilder>, Label, SysGroups, ResourceKeys, Singletons>; /** * Register a callback that fires once per entity the first time it appears * in a query's results. Fires before process. Automatic cleanup when entity * leaves the query so re-entry fires the callback again. * @param queryName Name of a query previously added via addQuery * @param callback Function called with the entity and ecs instance * @returns This SystemBuilder instance for method chaining */ setOnEntityEnter(queryName: QN, callback: (ctx: { entity: FilteredEntity ? W : never, Queries[QN] extends QueryDefinition ? WO : never, Queries[QN] extends QueryDefinition ? O : never>; ecs: ECSpresso; }) => void): this; /** * Set the onDetach lifecycle hook * Called when the system is removed from the ECS * @param onDetach Function receiving the world and a CleanupControl when this * system is detached from the ECS * @returns This SystemBuilder instance for method chaining */ setOnDetach(onDetach: SystemDetachFn): this; /** * Set the onInitialize lifecycle hook. * * Fires exactly once per system. For systems added before `initialize()`, * the hook is awaited inside `initialize()` itself. For systems added * after `initialize()` has returned, the hook fires on registration (at * the next `update()`'s finalize step) — async hooks run independently of * the first `process` call, and world disposal waits for them to settle. * * @param onInitialize Function to run when this system is initialized * @returns This SystemBuilder instance for method chaining */ setOnInitialize(onInitialize: SystemLifecycleFn): this; /** * Set event handlers for the system * These handlers will be automatically subscribed when the system is attached * @param handlers Object mapping event names to handler functions * @returns This SystemBuilder instance for method chaining */ setEventHandlers(handlers: { [EventName in keyof Cfg['events']]?: (ctx: { data: Cfg['events'][EventName]; ecs: ECSpresso; }) => void; }): this; } type QueryResults, Queries extends Record>, Singletons extends Record> = {}> = { [QueryName in keyof Queries]: QueryName extends string ? FilteredEntity ? W : never, Queries[QueryName] extends QueryDefinition ? WO : never, Queries[QueryName] extends QueryDefinition ? O : never, Queries[QueryName] extends QueryDefinition ? (unknown extends M ? W2 : M) : never>[] : never; } & { [SingletonName in keyof Singletons]: SingletonName extends string ? FilteredEntity ? W : never, Singletons[SingletonName] extends QueryDefinition ? WO : never, Singletons[SingletonName] extends QueryDefinition ? O : never, Singletons[SingletonName] extends QueryDefinition ? (unknown extends M ? W2 : M) : never> | undefined : never; }; /** * Context object passed to system process functions. * Pre-allocated per system and reused every frame (zero per-frame allocation). * When resources are declared via withResources(), the context includes a * `resources` field whose values are refreshed before each process call. */ export type ProcessContext>, ResourceKeys extends keyof Cfg['resources'] = never, Singletons extends Record> = {}> = { queries: QueryResults; dt: number; ecs: ECSpresso; } & ([ResourceKeys] extends [never] ? {} : { resources: { readonly [K in ResourceKeys]: Cfg['resources'][K]; }; }); /** * Function signature for system process methods. * Receives a single context object with queries, dt, ecs, and optionally resources. */ export type SystemProcessFn>, ResourceKeys extends keyof Cfg['resources'] = never, Singletons extends Record> = {}> = (ctx: ProcessContext) => void; /** * Type for system lifecycle functions * These can be asynchronous */ export type SystemLifecycleFn = (ecs: ECSpresso) => void | Promise; /** * Type for system detach callbacks. Cleanup may request world disposal through * the explicit control without waiting on its own completion barrier. */ export type SystemDetachFn = (ecs: ECSpresso, cleanup: CleanupControl) => void | Promise; export {};