ecspresso 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -9
- package/dist/bundle.d.ts +5 -2
- package/dist/bundles/renderers/pixi.d.ts +248 -0
- package/dist/bundles/renderers/pixi.js +4 -0
- package/dist/bundles/renderers/pixi.js.map +12 -0
- package/dist/bundles/utils/timers.d.ts +113 -0
- package/dist/bundles/utils/timers.js +4 -0
- package/dist/bundles/utils/timers.js.map +12 -0
- package/dist/ecspresso.d.ts +122 -3
- package/dist/entity-manager.d.ts +19 -3
- package/dist/hierarchy-manager.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +12 -11
- package/dist/reactive-query-manager.d.ts +59 -0
- package/dist/resource-manager.d.ts +37 -5
- package/dist/system-builder.d.ts +8 -0
- package/dist/types.d.ts +22 -0
- package/package.json +23 -3
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/system-builder.ts", "../src/bundle.ts", "../src/bundles/renderers/pixi.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\tprivate _isRegistered = false; // Track if system has been auto-registered\n\tprivate _groups: string[] = [];\n\tprivate _inScreens?: string[];\n\tprivate _excludeScreens?: string[];\n\tprivate _requiredAssets?: string[];\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Auto-register this system with its ECSpresso instance if not already registered\n\t * @private\n\t */\n\tprivate _autoRegister(): void {\n\t\tif (this._isRegistered || !this._ecspresso) return;\n\t\t\n\t\tconst system = this._buildSystemObject();\n\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\tthis._isRegistered = true;\n\t}\n\n\t/**\n\t * Create the system object without registering it\n\t * @private\n\t */\n\tprivate _buildSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\treturn this._createSystemObject();\n\t}\n\n\t/**\n\t * Create a system object with all configured properties\n\t * @private\n\t */\n\tprivate _createSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._groups.length > 0) {\n\t\t\tsystem.groups = [...this._groups];\n\t\t}\n\n\t\tif (this._inScreens) {\n\t\t\tsystem.inScreens = this._inScreens;\n\t\t}\n\n\t\tif (this._excludeScreens) {\n\t\t\tsystem.excludeScreens = this._excludeScreens;\n\t\t}\n\n\t\tif (this._requiredAssets) {\n\t\t\tsystem.requiredAssets = this._requiredAssets;\n\t\t}\n\n\t\treturn system;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add this system to a group. Systems can belong to multiple groups.\n\t * When any group a system belongs to is disabled, the system will be skipped.\n\t * @param groupName The name of the group to add the system to\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinGroup(groupName: string): this {\n\t\tif (!this._groups.includes(groupName)) {\n\t\t\tthis._groups.push(groupName);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Restrict this system to only run in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is not in this list.\n\t * @param screens Array of screen names where this system should run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._inScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Exclude this system from running in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is in this list.\n\t * @param screens Array of screen names where this system should NOT run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\texcludeScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._excludeScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Require specific assets to be loaded for this system to run.\n\t * System will be skipped during update() if any required asset\n\t * is not loaded.\n\t * @param assets Array of asset keys that must be loaded\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\trequiresAssets(assets: ReadonlyArray<string>): this {\n\t\tthis._requiredAssets = [...assets];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register this system with its ECSpresso instance and return the ECSpresso for chaining\n\t * This enables seamless method chaining: .registerAndContinue().addSystem(...)\n\t * @returns ECSpresso instance if attached to one, otherwise throws an error\n\t */\n\tregisterAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Complete this system and return the parent container for seamless chaining\n\t * - For ECSpresso-attached builders: registers the system and returns ECSpresso\n\t * - For Bundle-attached builders: returns the Bundle\n\t * This method is typed via the specialized interfaces (SystemBuilderWithEcspresso, SystemBuilderWithBundle)\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | Bundle<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (this._ecspresso) {\n\t\t\tthis._autoRegister();\n\t\t\treturn this._ecspresso;\n\t\t}\n\n\t\tif (this._bundle) {\n\t\t\treturn this._bundle;\n\t\t}\n\n\t\tthrow new Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`);\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system = this._createSystemObject();\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// Use the new internal registration method instead of direct property access\n\tecspresso._registerSystem(system);\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n\t\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\n\t/**\n\t * Complete this system and return the Bundle for chaining\n\t * Enables fluent API: bundle.addSystem(...).and().addSystem(...)\n\t */\n\tand(): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
|
|
6
|
+
"import { createBundleSystemBuilder, SystemBuilderWithBundle } from './system-builder';\nimport type ECSpresso from './ecspresso';\nimport type { AssetDefinition } from './asset-types';\nimport type { ScreenDefinition } from './screen-types';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n\tAssetTypes extends Record<string, unknown> = {},\n\tScreenStates extends Record<string, ScreenDefinition<any, any>> = {},\n> {\n\tprivate _systems: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _assets: Map<string, AssetDefinition<unknown>> = new Map();\n\tprivate _assetGroups: Map<string, Map<string, () => Promise<unknown>>> = new Map();\n\tprivate _screens: Map<string, ScreenDefinition<any, any>> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle, by label (creating a new builder) or by reusing an existing one\n\t */\n\taddSystem<Q extends Record<string, any>>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>;\n\taddSystem(label: string): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, {}>;\n\taddSystem(builderOrLabel: string | SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>) {\n\t\tif (typeof builderOrLabel === 'string') {\n\t\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(builderOrLabel, this);\n\t\t\tthis._systems.push(system);\n\t\t\treturn system;\n\t\t} else {\n\t\t\tthis._systems.push(builderOrLabel);\n\t\t\treturn builderOrLabel;\n\t\t}\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value, a factory function, or a factory with dependencies\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource:\n\t\t\t| ResourceTypes[K]\n\t\t\t| ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t\t| { dependsOn: readonly string[]; factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]> }\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>) | { dependsOn, factory }\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add an asset to this bundle\n\t * @param key The asset key\n\t * @param loader Function that loads and returns the asset\n\t * @param options Optional asset configuration\n\t */\n\taddAsset<K extends string, T>(\n\t\tkey: K,\n\t\tloader: () => Promise<T>,\n\t\toptions?: { eager?: boolean; group?: string }\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates> {\n\t\tthis._assets.set(key, {\n\t\t\tloader,\n\t\t\teager: options?.eager ?? true,\n\t\t\tgroup: options?.group,\n\t\t});\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a group of assets to this bundle\n\t * @param groupName The group name\n\t * @param assets Object mapping asset keys to loader functions\n\t */\n\taddAssetGroup<G extends string, T extends Record<string, () => Promise<unknown>>>(\n\t\tgroupName: G,\n\t\tassets: T\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates> {\n\t\tconst groupAssets = new Map<string, () => Promise<unknown>>();\n\t\tfor (const [key, loader] of Object.entries(assets)) {\n\t\t\tgroupAssets.set(key, loader as () => Promise<unknown>);\n\t\t\tthis._assets.set(key, {\n\t\t\t\tloader: loader as () => Promise<unknown>,\n\t\t\t\teager: false,\n\t\t\t\tgroup: groupName,\n\t\t\t});\n\t\t}\n\t\tthis._assetGroups.set(groupName, groupAssets);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a screen to this bundle\n\t * @param name The screen name\n\t * @param definition The screen definition\n\t */\n\taddScreen<K extends string, Config extends Record<string, unknown>, State extends Record<string, unknown>>(\n\t\tname: K,\n\t\tdefinition: ScreenDefinition<Config, State>\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>> {\n\t\tthis._screens.set(name, definition);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>>;\n\t}\n\n\t/**\n\t * Get all asset definitions in this bundle\n\t */\n\tgetAssets(): Map<string, AssetDefinition<unknown>> {\n\t\treturn new Map(this._assets);\n\t}\n\n\t/**\n\t * Get all screen definitions in this bundle\n\t */\n\tgetScreens(): Map<string, ScreenDefinition<any, any>> {\n\t\treturn new Map(this._screens);\n\t}\n\n\t/**\n\t * Internal method to set a resource\n\t * @internal Used by mergeBundles\n\t */\n\t_setResource(key: string, value: unknown): void {\n\t\tthis._resources.set(key as keyof ResourceTypes, value as ResourceTypes[keyof ResourceTypes]);\n\t}\n\n\t/**\n\t * Internal method to set an asset definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setAsset(key: string, definition: AssetDefinition<unknown>): void {\n\t\tthis._assets.set(key, definition);\n\t}\n\n\t/**\n\t * Internal method to set a screen definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setScreen(name: string, definition: ScreenDefinition<any, any>): void {\n\t\tthis._screens.set(name, definition);\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n/**\n * Utility type to check if two types are exactly the same\n */\ntype Exactly<T, U> = T extends U ? U extends T ? true : false : false;\n\n/**\n * Simplified type constraint for bundle compatibility\n * Ensures that overlapping keys have exactly the same types\n */\ntype CompatibleBundles<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>,\n\tA1 extends Record<string, unknown> = {},\n\tA2 extends Record<string, unknown> = {},\n\tS1 extends Record<string, ScreenDefinition<any, any>> = {},\n\tS2 extends Record<string, ScreenDefinition<any, any>> = {},\n> = {\n\t[K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends true ? C1[K] : never;\n} & {\n\t[K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends true ? E1[K] : never;\n} & {\n\t[K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends true ? R1[K] : never;\n} & {\n\t[K in keyof A1 & keyof A2]: Exactly<A1[K], A2[K]> extends true ? A1[K] : never;\n} & {\n\t[K in keyof S1 & keyof S2]: Exactly<S1[K], S2[K]> extends true ? S1[K] : never;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tA1 extends Record<string, unknown>,\n\tS1 extends Record<string, ScreenDefinition<any, any>>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>,\n\tA2 extends Record<string, unknown>,\n\tS2 extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1, A1, S1>,\n\tbundle2: Bundle<C2, E2, R2, A2, S2> & CompatibleBundles<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tAssetTypes extends Record<string, unknown>,\n\tScreenStates extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle<any, any, any, any, any>>\n): Bundle<any, any, any, any, any> {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\t// reuse the full builder so we carry over queries, hooks, and handlers\n\t\t\tcombined.addSystem(system);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined._setResource(label as string, resource);\n\t\t}\n\n\t\t// Add assets from this bundle\n\t\tfor (const [key, definition] of bundle.getAssets().entries()) {\n\t\t\tcombined._setAsset(key, definition);\n\t\t}\n\n\t\t// Add screens from this bundle\n\t\tfor (const [name, definition] of bundle.getScreens().entries()) {\n\t\t\tcombined._setScreen(name, definition);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
|
|
7
|
+
"/**\n * PixiJS Renderer Bundle for ECSpresso\n *\n * An opt-in PixiJS rendering bundle that automates scene graph wiring.\n * Import from 'ecspresso/bundles/renderers/pixi'\n */\n\nimport type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';\nimport Bundle from '../../bundle';\nimport type ECSpresso from '../../ecspresso';\n\n// Dynamic import for Application to avoid requiring pixi.js at bundle creation time\n// when using managed mode (init options instead of pre-initialized app)\nasync function createPixiApplication(options: Partial<ApplicationOptions>): Promise<Application> {\n\tconst { Application } = await import('pixi.js');\n\tconst app = new Application();\n\tawait app.init(options);\n\treturn app;\n}\n\n// ==================== Component Types ====================\n\n/**\n * Local transform relative to parent (or world if no parent)\n */\nexport interface LocalTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * Computed world transform (accumulated from parent chain)\n */\nexport interface WorldTransform {\n\tx: number;\n\ty: number;\n\trotation: number;\n\tscaleX: number;\n\tscaleY: number;\n}\n\n/**\n * PixiJS Sprite component\n */\nexport interface PixiSprite {\n\tsprite: Sprite;\n\tanchor?: { x: number; y: number };\n}\n\n/**\n * PixiJS Graphics component\n */\nexport interface PixiGraphics {\n\tgraphics: Graphics;\n}\n\n/**\n * PixiJS Container component\n */\nexport interface PixiContainer {\n\tcontainer: Container;\n}\n\n/**\n * Visibility and alpha component\n */\nexport interface PixiVisible {\n\tvisible: boolean;\n\talpha?: number;\n}\n\n/**\n * Aggregate component types for PixiJS bundle.\n * Users should extend this interface with their own component types.\n *\n * @example\n * ```typescript\n * interface GameComponents extends PixiComponentTypes {\n * velocity: { x: number; y: number };\n * player: true;\n * }\n * ```\n */\nexport interface PixiComponentTypes {\n\tlocalTransform: LocalTransform;\n\tworldTransform: WorldTransform;\n\tpixiSprite: PixiSprite;\n\tpixiGraphics: PixiGraphics;\n\tpixiContainer: PixiContainer;\n\tpixiVisible: PixiVisible;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Events emitted by the PixiJS bundle\n */\nexport interface PixiEventTypes {\n\thierarchyChanged: {\n\t\tentityId: number;\n\t\toldParent: number | null;\n\t\tnewParent: number | null;\n\t};\n}\n\n// ==================== Resource Types ====================\n\n/**\n * Resources provided by the PixiJS bundle\n */\nexport interface PixiResourceTypes {\n\tpixiApp: Application;\n\tpixiRootContainer: Container;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Common options shared between both initialization modes\n */\ninterface PixiBundleCommonOptions {\n\t/** Optional custom root container (defaults to app.stage) */\n\trootContainer?: Container;\n\t/** System group name (default: 'pixi-renderer') */\n\tsystemGroup?: string;\n\t/** Priority for transform propagation system (default: 1000) */\n\ttransformPriority?: number;\n\t/** Priority for render sync system (default: 500) */\n\trenderSyncPriority?: number;\n}\n\n/**\n * Options when providing a pre-initialized PixiJS Application\n */\nexport interface PixiBundleAppOptions extends PixiBundleCommonOptions {\n\t/** The PixiJS Application instance (already initialized) */\n\tapp: Application;\n\tinit?: never;\n\tcontainer?: never;\n}\n\n/**\n * Options when letting the bundle create and manage the PixiJS Application\n */\nexport interface PixiBundleManagedOptions extends PixiBundleCommonOptions {\n\tapp?: never;\n\t/** PixiJS ApplicationOptions - bundle will create and initialize the Application */\n\tinit: Partial<ApplicationOptions>;\n\t/** Container element to append the canvas to, or CSS selector string */\n\tcontainer?: HTMLElement | string;\n}\n\n/**\n * Configuration options for the PixiJS bundle.\n *\n * Supports two modes:\n * 1. **Pre-initialized**: Pass an already-initialized Application via `app`\n * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`\n *\n * @example Pre-initialized mode (full control)\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n * const ecs = ECSpresso.create<...>()\n * .withBundle(createPixiBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode (convenience)\n * ```typescript\n * const ecs = ECSpresso.create<...>()\n * .withBundle(createPixiBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize(); // Application created here\n * ```\n */\nexport type PixiBundleOptions = PixiBundleAppOptions | PixiBundleManagedOptions;\n\n// ==================== Default Values ====================\n\n/**\n * Default local transform values\n */\nexport const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n} as const;\n\n/**\n * Default world transform values\n */\nexport const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform> = {\n\tx: 0,\n\ty: 0,\n\trotation: 0,\n\tscaleX: 1,\n\tscaleY: 1,\n} as const;\n\n// ==================== Helper Utilities ====================\n\ninterface PositionOption {\n\tx?: number;\n\ty?: number;\n}\n\ninterface TransformOptions {\n\trotation?: number;\n\tscale?: number | { x: number; y: number };\n\tvisible?: boolean;\n\talpha?: number;\n}\n\nfunction createLocalTransform(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): LocalTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createWorldTransform(\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): WorldTransform {\n\tconst scaleValue = options?.scale;\n\tconst scaleX = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.x ?? 1;\n\tconst scaleY = typeof scaleValue === 'number'\n\t\t? scaleValue\n\t\t: scaleValue?.y ?? 1;\n\n\treturn {\n\t\tx: position?.x ?? 0,\n\t\ty: position?.y ?? 0,\n\t\trotation: options?.rotation ?? 0,\n\t\tscaleX,\n\t\tscaleY,\n\t};\n}\n\nfunction createVisibleComponent(options?: TransformOptions): PixiVisible {\n\treturn {\n\t\tvisible: options?.visible ?? true,\n\t\talpha: options?.alpha,\n\t};\n}\n\n/**\n * Create components for a sprite entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const player = ecs.spawn({\n * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),\n * velocity: { x: 0, y: 0 },\n * });\n * ```\n */\nexport function createSpriteComponents(\n\tsprite: Sprite,\n\tposition?: PositionOption,\n\toptions?: TransformOptions & { anchor?: { x: number; y: number } }\n): Pick<PixiComponentTypes, 'pixiSprite' | 'localTransform' | 'worldTransform' | 'pixiVisible'> {\n\treturn {\n\t\tpixiSprite: {\n\t\t\tsprite,\n\t\t\tanchor: options?.anchor,\n\t\t},\n\t\tlocalTransform: createLocalTransform(position, options),\n\t\tworldTransform: createWorldTransform(position, options),\n\t\tpixiVisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a graphics entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const rect = ecs.spawn({\n * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),\n * });\n * ```\n */\nexport function createGraphicsComponents(\n\tgraphics: Graphics,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<PixiComponentTypes, 'pixiGraphics' | 'localTransform' | 'worldTransform' | 'pixiVisible'> {\n\treturn {\n\t\tpixiGraphics: { graphics },\n\t\tlocalTransform: createLocalTransform(position, options),\n\t\tworldTransform: createWorldTransform(position, options),\n\t\tpixiVisible: createVisibleComponent(options),\n\t};\n}\n\n/**\n * Create components for a container entity.\n * Returns an object suitable for spreading into spawn().\n *\n * @example\n * ```typescript\n * const group = ecs.spawn({\n * ...createContainerComponents(new Container(), { x: 0, y: 0 }),\n * });\n * ```\n */\nexport function createContainerComponents(\n\tcontainer: Container,\n\tposition?: PositionOption,\n\toptions?: TransformOptions\n): Pick<PixiComponentTypes, 'pixiContainer' | 'localTransform' | 'worldTransform' | 'pixiVisible'> {\n\treturn {\n\t\tpixiContainer: { container },\n\t\tlocalTransform: createLocalTransform(position, options),\n\t\tworldTransform: createWorldTransform(position, options),\n\t\tpixiVisible: createVisibleComponent(options),\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a PixiJS rendering bundle for ECSpresso.\n *\n * This bundle provides:\n * - Transform propagation system (computes world transforms from hierarchy)\n * - Render sync system (updates PixiJS objects from ECS components)\n * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)\n *\n * @example Pre-initialized mode\n * ```typescript\n * const app = new Application();\n * await app.init({ resizeTo: window });\n *\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createPixiBundle({ app }))\n * .build();\n * ```\n *\n * @example Managed mode\n * ```typescript\n * const ecs = ECSpresso.create<GameComponents, {}, {}>()\n * .withBundle(createPixiBundle({\n * init: { background: '#1099bb', resizeTo: window },\n * container: document.body,\n * }))\n * .build();\n * await ecs.initialize();\n * ```\n */\nexport function createPixiBundle(\n\toptions: PixiBundleOptions\n): Bundle<PixiComponentTypes, PixiEventTypes, PixiResourceTypes> {\n\tconst {\n\t\trootContainer: customRootContainer,\n\t\tsystemGroup = 'pixi-renderer',\n\t\ttransformPriority = 1000,\n\t\trenderSyncPriority = 500,\n\t} = options;\n\n\tconst bundle = new Bundle<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>('pixi-renderer');\n\n\t// Determine mode and set up resources accordingly\n\tconst isManaged = 'init' in options && options.init !== undefined;\n\n\tif (isManaged) {\n\t\t// Managed mode: create Application during initialization\n\t\tconst initOptions = options.init;\n\t\tconst containerOption = options.container;\n\n\t\t// Resource factory that creates the Application\n\t\tbundle.addResource('pixiApp', async () => {\n\t\t\tconst app = await createPixiApplication(initOptions);\n\n\t\t\t// Auto-append canvas if container specified\n\t\t\tif (containerOption) {\n\t\t\t\tconst containerEl = typeof containerOption === 'string'\n\t\t\t\t\t? document.querySelector(containerOption)\n\t\t\t\t\t: containerOption;\n\n\t\t\t\tif (containerEl) {\n\t\t\t\t\tcontainerEl.appendChild(app.canvas);\n\t\t\t\t} else if (typeof containerOption === 'string') {\n\t\t\t\t\tconsole.warn(`PixiJS bundle: container selector \"${containerOption}\" not found`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn app;\n\t\t});\n\n\t\t// pixiRootContainer depends on pixiApp - declarative dependency\n\t\tbundle.addResource('pixiRootContainer', {\n\t\t\tdependsOn: ['pixiApp'],\n\t\t\tfactory: (ecs) => customRootContainer ?? ecs.getResource('pixiApp').stage,\n\t\t});\n\t} else {\n\t\t// Pre-initialized mode: use provided Application\n\t\tconst app = options.app;\n\t\tbundle.addResource('pixiApp', app);\n\t\tbundle.addResource('pixiRootContainer', customRootContainer ?? app.stage);\n\t}\n\n\t// Entity ID -> PixiJS Container mapping for scene graph management\n\tconst entityToPixiObject = new Map<number, Container>();\n\n\t// Helper to get the PixiJS display object for an entity\n\tfunction getPixiObject(entityId: number, ecs: ECSpresso<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>): Container | null {\n\t\t// Check cache first\n\t\tconst cached = entityToPixiObject.get(entityId);\n\t\tif (cached) return cached;\n\n\t\t// Try to get from components\n\t\tconst spriteComp = ecs.entityManager.getComponent(entityId, 'pixiSprite');\n\t\tif (spriteComp) {\n\t\t\tentityToPixiObject.set(entityId, spriteComp.sprite);\n\t\t\treturn spriteComp.sprite;\n\t\t}\n\n\t\tconst graphicsComp = ecs.entityManager.getComponent(entityId, 'pixiGraphics');\n\t\tif (graphicsComp) {\n\t\t\tentityToPixiObject.set(entityId, graphicsComp.graphics);\n\t\t\treturn graphicsComp.graphics;\n\t\t}\n\n\t\tconst containerComp = ecs.entityManager.getComponent(entityId, 'pixiContainer');\n\t\tif (containerComp) {\n\t\t\tentityToPixiObject.set(entityId, containerComp.container);\n\t\t\treturn containerComp.container;\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t// Helper to add a PixiJS object to the scene graph\n\tfunction addToSceneGraph(\n\t\tentityId: number,\n\t\tpixiObject: Container,\n\t\tecs: ECSpresso<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>\n\t): void {\n\t\tconst rootContainer = ecs.getResource('pixiRootContainer');\n\t\tconst parentId = ecs.getParent(entityId);\n\t\tconst parentPixiObject = parentId !== null ? getPixiObject(parentId, ecs) : null;\n\t\tconst targetContainer = parentPixiObject ?? rootContainer;\n\n\t\t// Only add if not already a child\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// Helper to remove a PixiJS object from scene graph\n\tfunction removeFromSceneGraph(entityId: number): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (pixiObject) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\tentityToPixiObject.delete(entityId);\n\t\t}\n\t}\n\n\t// Helper to update parent in scene graph\n\tfunction updateSceneGraphParent(\n\t\tentityId: number,\n\t\tecs: ECSpresso<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>\n\t): void {\n\t\tconst pixiObject = entityToPixiObject.get(entityId);\n\t\tif (!pixiObject) return;\n\n\t\tconst rootContainer = ecs.getResource('pixiRootContainer');\n\t\tconst parentId = ecs.getParent(entityId);\n\t\tconst parentPixiObject = parentId !== null ? getPixiObject(parentId, ecs) : null;\n\t\tconst targetContainer = parentPixiObject ?? rootContainer;\n\n\t\tif (pixiObject.parent !== targetContainer) {\n\t\t\tpixiObject.removeFromParent();\n\t\t\ttargetContainer.addChild(pixiObject);\n\t\t}\n\t}\n\n\t// ==================== Transform Propagation System ====================\n\t// Computes world transforms from local transforms + parent hierarchy\n\tbundle\n\t\t.addSystem('pixi-transform-propagation')\n\t\t.setPriority(transformPriority)\n\t\t.inGroup(systemGroup)\n\t\t.setProcess((_queries, _deltaTime, ecs) => {\n\t\t\t// Use parent-first traversal to ensure parents are processed before children\n\t\t\tecs.forEachInHierarchy((entityId, parentId) => {\n\t\t\t\tconst localTransform = ecs.entityManager.getComponent(entityId, 'localTransform');\n\t\t\t\tconst worldTransform = ecs.entityManager.getComponent(entityId, 'worldTransform');\n\n\t\t\t\tif (!localTransform || !worldTransform) return;\n\n\t\t\t\tif (parentId === null) {\n\t\t\t\t\t// Root entity: world transform equals local transform\n\t\t\t\t\tworldTransform.x = localTransform.x;\n\t\t\t\t\tworldTransform.y = localTransform.y;\n\t\t\t\t\tworldTransform.rotation = localTransform.rotation;\n\t\t\t\t\tworldTransform.scaleX = localTransform.scaleX;\n\t\t\t\t\tworldTransform.scaleY = localTransform.scaleY;\n\t\t\t\t} else {\n\t\t\t\t\t// Child entity: combine with parent's world transform\n\t\t\t\t\tconst parentWorld = ecs.entityManager.getComponent(parentId, 'worldTransform');\n\t\t\t\t\tif (parentWorld) {\n\t\t\t\t\t\t// Apply parent's scale to local position\n\t\t\t\t\t\tconst scaledLocalX = localTransform.x * parentWorld.scaleX;\n\t\t\t\t\t\tconst scaledLocalY = localTransform.y * parentWorld.scaleY;\n\n\t\t\t\t\t\t// Rotate local position by parent's rotation\n\t\t\t\t\t\tconst cos = Math.cos(parentWorld.rotation);\n\t\t\t\t\t\tconst sin = Math.sin(parentWorld.rotation);\n\t\t\t\t\t\tconst rotatedX = scaledLocalX * cos - scaledLocalY * sin;\n\t\t\t\t\t\tconst rotatedY = scaledLocalX * sin + scaledLocalY * cos;\n\n\t\t\t\t\t\t// Add to parent's position\n\t\t\t\t\t\tworldTransform.x = parentWorld.x + rotatedX;\n\t\t\t\t\t\tworldTransform.y = parentWorld.y + rotatedY;\n\t\t\t\t\t\tworldTransform.rotation = parentWorld.rotation + localTransform.rotation;\n\t\t\t\t\t\tworldTransform.scaleX = parentWorld.scaleX * localTransform.scaleX;\n\t\t\t\t\t\tworldTransform.scaleY = parentWorld.scaleY * localTransform.scaleY;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Parent has no world transform, treat as root\n\t\t\t\t\t\tworldTransform.x = localTransform.x;\n\t\t\t\t\t\tworldTransform.y = localTransform.y;\n\t\t\t\t\t\tworldTransform.rotation = localTransform.rotation;\n\t\t\t\t\t\tworldTransform.scaleX = localTransform.scaleX;\n\t\t\t\t\t\tworldTransform.scaleY = localTransform.scaleY;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Also process entities not in hierarchy (orphans with transforms)\n\t\t\tconst orphanedEntities = ecs.getEntitiesWithQuery(['localTransform', 'worldTransform']);\n\t\t\tfor (const entity of orphanedEntities) {\n\t\t\t\tconst parentId = ecs.getParent(entity.id);\n\t\t\t\t// Only process if truly orphaned (no parent and not a root with children)\n\t\t\t\tif (parentId === null && ecs.getChildren(entity.id).length === 0) {\n\t\t\t\t\tconst { localTransform, worldTransform } = entity.components;\n\t\t\t\t\tworldTransform.x = localTransform.x;\n\t\t\t\t\tworldTransform.y = localTransform.y;\n\t\t\t\t\tworldTransform.rotation = localTransform.rotation;\n\t\t\t\t\tworldTransform.scaleX = localTransform.scaleX;\n\t\t\t\t\tworldTransform.scaleY = localTransform.scaleY;\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Render Sync System ====================\n\t// Updates PixiJS objects from world transforms and visibility\n\tbundle\n\t\t.addSystem('pixi-render-sync')\n\t\t.setPriority(renderSyncPriority)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('sprites', {\n\t\t\twith: ['pixiSprite', 'worldTransform'] as const,\n\t\t})\n\t\t.addQuery('graphics', {\n\t\t\twith: ['pixiGraphics', 'worldTransform'] as const,\n\t\t})\n\t\t.addQuery('containers', {\n\t\t\twith: ['pixiContainer', 'worldTransform'] as const,\n\t\t})\n\t\t.setProcess((queries, _deltaTime, ecs) => {\n\t\t\t// Process sprites\n\t\t\tfor (const entity of queries.sprites) {\n\t\t\t\tconst { pixiSprite, worldTransform } = entity.components;\n\t\t\t\tconst { sprite, anchor } = pixiSprite;\n\n\t\t\t\tsprite.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tsprite.rotation = worldTransform.rotation;\n\t\t\t\tsprite.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\tif (anchor) {\n\t\t\t\t\tsprite.anchor.set(anchor.x, anchor.y);\n\t\t\t\t}\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visible = ecs.entityManager.getComponent(entity.id, 'pixiVisible');\n\t\t\t\tif (visible) {\n\t\t\t\t\tsprite.visible = visible.visible;\n\t\t\t\t\tif (visible.alpha !== undefined) {\n\t\t\t\t\t\tsprite.alpha = visible.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process graphics\n\t\t\tfor (const entity of queries.graphics) {\n\t\t\t\tconst { pixiGraphics, worldTransform } = entity.components;\n\t\t\t\tconst { graphics } = pixiGraphics;\n\n\t\t\t\tgraphics.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tgraphics.rotation = worldTransform.rotation;\n\t\t\t\tgraphics.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visible = ecs.entityManager.getComponent(entity.id, 'pixiVisible');\n\t\t\t\tif (visible) {\n\t\t\t\t\tgraphics.visible = visible.visible;\n\t\t\t\t\tif (visible.alpha !== undefined) {\n\t\t\t\t\t\tgraphics.alpha = visible.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Process containers\n\t\t\tfor (const entity of queries.containers) {\n\t\t\t\tconst { pixiContainer, worldTransform } = entity.components;\n\t\t\t\tconst { container } = pixiContainer;\n\n\t\t\t\tcontainer.position.set(worldTransform.x, worldTransform.y);\n\t\t\t\tcontainer.rotation = worldTransform.rotation;\n\t\t\t\tcontainer.scale.set(worldTransform.scaleX, worldTransform.scaleY);\n\n\t\t\t\t// Apply visibility if component exists\n\t\t\t\tconst visible = ecs.entityManager.getComponent(entity.id, 'pixiVisible');\n\t\t\t\tif (visible) {\n\t\t\t\t\tcontainer.visible = visible.visible;\n\t\t\t\t\tif (visible.alpha !== undefined) {\n\t\t\t\t\t\tcontainer.alpha = visible.alpha;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\t// ==================== Scene Graph Manager System ====================\n\t// Sets up reactive queries to manage scene graph on entity create/destroy\n\t// High priority ensures this runs before user systems' onInitialize\n\tbundle\n\t\t.addSystem('pixi-scene-graph-manager')\n\t\t.setPriority(9999)\n\t\t.inGroup(systemGroup)\n\t\t.setOnInitialize((ecs) => {\n\t\t\t// Reactive query for sprites\n\t\t\tecs.addReactiveQuery('pixi-sprites', {\n\t\t\t\twith: ['pixiSprite'] as const,\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.pixiSprite.sprite;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for graphics\n\t\t\tecs.addReactiveQuery('pixi-graphics', {\n\t\t\t\twith: ['pixiGraphics'] as const,\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.pixiGraphics.graphics;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Reactive query for containers\n\t\t\tecs.addReactiveQuery('pixi-containers', {\n\t\t\t\twith: ['pixiContainer'] as const,\n\t\t\t\tonEnter: (entity) => {\n\t\t\t\t\tconst pixiObject = entity.components.pixiContainer.container;\n\t\t\t\t\tentityToPixiObject.set(entity.id, pixiObject);\n\t\t\t\t\taddToSceneGraph(entity.id, pixiObject, ecs);\n\t\t\t\t},\n\t\t\t\tonExit: (entityId) => {\n\t\t\t\t\tremoveFromSceneGraph(entityId);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\t// Subscribe to hierarchy changes to mirror reparenting in scene graph\n\t\t\tecs.on('hierarchyChanged', ({ entityId }) => {\n\t\t\t\tupdateSceneGraphParent(entityId, ecs);\n\t\t\t});\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "3807489A54FF20AF64756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timer Bundle for ECSpresso
|
|
3
|
+
*
|
|
4
|
+
* Provides ECS-native timers following the "data, not callbacks" philosophy.
|
|
5
|
+
* Timers are components processed each frame, automatically cleaned up when entities are removed.
|
|
6
|
+
*/
|
|
7
|
+
import Bundle from '../../bundle';
|
|
8
|
+
/**
|
|
9
|
+
* Timer component data structure.
|
|
10
|
+
* Use `justFinished` to detect timer completion in your systems.
|
|
11
|
+
*/
|
|
12
|
+
export interface Timer {
|
|
13
|
+
/** Time accumulated so far (seconds) */
|
|
14
|
+
elapsed: number;
|
|
15
|
+
/** Target duration (seconds) */
|
|
16
|
+
duration: number;
|
|
17
|
+
/** Whether timer repeats after completion */
|
|
18
|
+
repeat: boolean;
|
|
19
|
+
/** Whether timer is currently running */
|
|
20
|
+
active: boolean;
|
|
21
|
+
/** True for one frame after timer completes */
|
|
22
|
+
justFinished: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Component types provided by the timer bundle.
|
|
26
|
+
* Extend your component types with this interface.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* interface GameComponents extends TimerComponentTypes {
|
|
31
|
+
* velocity: { x: number; y: number };
|
|
32
|
+
* player: true;
|
|
33
|
+
* }
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export interface TimerComponentTypes {
|
|
37
|
+
timer: Timer;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Configuration options for the timer bundle.
|
|
41
|
+
*/
|
|
42
|
+
export interface TimerBundleOptions {
|
|
43
|
+
/** System group name (default: 'timers') */
|
|
44
|
+
systemGroup?: string;
|
|
45
|
+
/** Priority for timer update system (default: 0) */
|
|
46
|
+
priority?: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a one-shot timer that fires once after the specified duration.
|
|
50
|
+
*
|
|
51
|
+
* @param duration Duration in seconds until the timer completes
|
|
52
|
+
* @returns Component object suitable for spreading into spawn()
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```typescript
|
|
56
|
+
* // Timer that triggers after 2 seconds
|
|
57
|
+
* ecs.spawn({
|
|
58
|
+
* ...createTimer(2),
|
|
59
|
+
* explosion: true,
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createTimer(duration: number): Pick<TimerComponentTypes, 'timer'>;
|
|
64
|
+
/**
|
|
65
|
+
* Create a repeating timer that fires every `duration` seconds.
|
|
66
|
+
*
|
|
67
|
+
* @param duration Duration in seconds between each timer completion
|
|
68
|
+
* @returns Component object suitable for spreading into spawn()
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // Timer that triggers every 5 seconds
|
|
73
|
+
* ecs.spawn({
|
|
74
|
+
* ...createRepeatingTimer(5),
|
|
75
|
+
* spawner: true,
|
|
76
|
+
* });
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare function createRepeatingTimer(duration: number): Pick<TimerComponentTypes, 'timer'>;
|
|
80
|
+
/**
|
|
81
|
+
* Create a timer bundle for ECSpresso.
|
|
82
|
+
*
|
|
83
|
+
* This bundle provides:
|
|
84
|
+
* - Timer update system that processes all timer components each frame
|
|
85
|
+
* - `justFinished` flag pattern for one-frame completion detection
|
|
86
|
+
* - Automatic cleanup when entities are removed
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const ecs = ECSpresso
|
|
91
|
+
* .create<Components, Events, Resources>()
|
|
92
|
+
* .withBundle(createTimerBundle())
|
|
93
|
+
* .build();
|
|
94
|
+
*
|
|
95
|
+
* // Spawn entity with timer
|
|
96
|
+
* ecs.spawn({
|
|
97
|
+
* ...createRepeatingTimer(5),
|
|
98
|
+
* spawner: true,
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // React to timer completion in a system
|
|
102
|
+
* ecs.addSystem('spawn-on-timer')
|
|
103
|
+
* .addQuery('spawners', { with: ['timer', 'spawner'] })
|
|
104
|
+
* .setProcess((queries, _dt, ecs) => {
|
|
105
|
+
* for (const { components } of queries.spawners) {
|
|
106
|
+
* if (components.timer.justFinished) {
|
|
107
|
+
* ecs.spawn({ enemy: true });
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function createTimerBundle(options?: TimerBundleOptions): Bundle<TimerComponentTypes, {}, {}>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
var Z=Object.create;var{getPrototypeOf:_,defineProperty:V,getOwnPropertyNames:$}=Object;var C=Object.prototype.hasOwnProperty;var q=(j,x,F)=>{F=j!=null?Z(_(j)):{};let J=x||!j||!j.__esModule?V(F,"default",{value:j,enumerable:!0}):F;for(let H of $(j))if(!C.call(J,H))V(J,H,{get:()=>j[H],enumerable:!0});return J};var z=((j)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(j,{get:(x,F)=>(typeof require<"u"?require:x)[F]}):j)(function(j){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+j+'" is not supported')});class U{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_isRegistered=!1;_groups=[];_inScreens;_excludeScreens;_requiredAssets;constructor(j,x=null,F=null){this._label=j;this._ecspresso=x;this._bundle=F}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let j=this._buildSystemObject();R(j,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){return this._createSystemObject()}_createSystemObject(){let j={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)j.process=this.processFunction;if(this.detachFunction)j.onDetach=this.detachFunction;if(this.initializeFunction)j.onInitialize=this.initializeFunction;if(this.eventHandlers)j.eventHandlers=this.eventHandlers;if(this._groups.length>0)j.groups=[...this._groups];if(this._inScreens)j.inScreens=this._inScreens;if(this._excludeScreens)j.excludeScreens=this._excludeScreens;if(this._requiredAssets)j.requiredAssets=this._requiredAssets;return j}setPriority(j){return this._priority=j,this}inGroup(j){if(!this._groups.includes(j))this._groups.push(j);return this}inScreens(j){return this._inScreens=[...j],this}excludeScreens(j){return this._excludeScreens=[...j],this}requiresAssets(j){return this._requiredAssets=[...j],this}addQuery(j,x){let F=this;return F.queries={...this.queries,[j]:x},F}setProcess(j){return this.processFunction=j,this}registerAndContinue(){if(!this._ecspresso)throw Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);return this._autoRegister(),this._ecspresso}and(){if(this._ecspresso)return this._autoRegister(),this._ecspresso;if(this._bundle)return this._bundle;throw Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`)}setOnDetach(j){return this.detachFunction=j,this}setOnInitialize(j){return this.initializeFunction=j,this}setEventHandlers(j){return this.eventHandlers=j,this}build(j){let x=this._createSystemObject();if(this._ecspresso)R(x,this._ecspresso);if(j)R(x,j);return this}}function R(j,x){x._registerSystem(j)}function P(j,x){return new U(j,x)}function X(j,x){return new U(j,null,x)}function G(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class Q{_systems=[];_resources=new Map;_assets=new Map;_assetGroups=new Map;_screens=new Map;_id;constructor(j){this._id=j||G()}get id(){return this._id}set id(j){this._id=j}addSystem(j){if(typeof j==="string"){let x=X(j,this);return this._systems.push(x),x}else return this._systems.push(j),j}addResource(j,x){return this._resources.set(j,x),this}addAsset(j,x,F){return this._assets.set(j,{loader:x,eager:F?.eager??!0,group:F?.group}),this}addAssetGroup(j,x){let F=new Map;for(let[J,H]of Object.entries(x))F.set(J,H),this._assets.set(J,{loader:H,eager:!1,group:j});return this._assetGroups.set(j,F),this}addScreen(j,x){return this._screens.set(j,x),this}getAssets(){return new Map(this._assets)}getScreens(){return new Map(this._screens)}_setResource(j,x){this._resources.set(j,x)}_setAsset(j,x){this._assets.set(j,x)}_setScreen(j,x){this._screens.set(j,x)}getSystems(){return this._systems.map((j)=>j.build())}registerSystemsWithEcspresso(j){for(let x of this._systems)x.build(j)}getResources(){return new Map(this._resources)}getResource(j){return this._resources.get(j)}getSystemBuilders(){return[...this._systems]}hasResource(j){return this._resources.has(j)}}function E(j,...x){if(x.length===0)return new Q(j);let F=new Q(j);for(let J of x){for(let H of J.getSystemBuilders())F.addSystem(H);for(let[H,M]of J.getResources().entries())F._setResource(H,M);for(let[H,M]of J.getAssets().entries())F._setAsset(H,M);for(let[H,M]of J.getScreens().entries())F._setScreen(H,M)}return F}function A(j){return{timer:{elapsed:0,duration:j,repeat:!1,active:!0,justFinished:!1}}}function L(j){return{timer:{elapsed:0,duration:j,repeat:!0,active:!0,justFinished:!1}}}function N(j){let{systemGroup:x="timers",priority:F=0}=j??{},J=new Q("timers");return J.addSystem("timer-update").setPriority(F).inGroup(x).addQuery("timers",{with:["timer"]}).setProcess((H,M)=>{for(let Y of H.timers){let{timer:K}=Y.components;if(K.justFinished=!1,!K.active)continue;if(K.elapsed+=M,K.elapsed>=K.duration)if(K.justFinished=!0,K.repeat)K.elapsed-=K.duration;else K.active=!1}}).and(),J}export{N as createTimerBundle,A as createTimer,L as createRepeatingTimer};
|
|
2
|
+
|
|
3
|
+
//# debugId=13DBE68984ED14A764756E2164756E21
|
|
4
|
+
//# sourceMappingURL=timers.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/system-builder.ts", "../src/bundle.ts", "../src/bundles/utils/timers.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import Bundle from \"./bundle\";\nimport ECSpresso from \"./ecspresso\";\nimport type { FilteredEntity, System } from \"./types\";\n\n/**\n * Builder class for creating type-safe ECS Systems with proper query inference\n */\nexport class SystemBuilder<\n\tComponentTypes extends Record<string, any> = Record<string, any>,\n\tEventTypes extends Record<string, any> = Record<string, any>,\n\tResourceTypes extends Record<string, any> = Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {},\n> {\n\tprivate queries: Queries = {} as Queries;\n\tprivate processFunction?: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>;\n\tprivate detachFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate initializeFunction?: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>;\n\tprivate eventHandlers?: {\n\t\t[EventName in keyof EventTypes]?: {\n\t\t\thandler(\n\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\tecs: ECSpresso<\n\t\t\t\t\tComponentTypes,\n\t\t\t\t\tEventTypes,\n\t\t\t\t\tResourceTypes\n\t\t\t\t>,\n\t\t\t): void;\n\t\t};\n\t};\n\tprivate _priority = 0; // Default priority is 0\n\tprivate _isRegistered = false; // Track if system has been auto-registered\n\tprivate _groups: string[] = [];\n\tprivate _inScreens?: string[];\n\tprivate _excludeScreens?: string[];\n\tprivate _requiredAssets?: string[];\n\n\tconstructor(\n\t\tprivate _label: string,\n\t\tprivate _ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t\tprivate _bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes> | null = null,\n\t) {}\n\n\tget label() {\n\t\treturn this._label;\n\t}\n\n\t/**\n\t * Returns the associated bundle if one was provided in the constructor\n\t */\n\tget bundle() {\n\t\treturn this._bundle;\n\t}\n\n\t/**\n\t * Returns the associated ECSpresso instance if one was provided in the constructor\n\t */\n\tget ecspresso() {\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Auto-register this system with its ECSpresso instance if not already registered\n\t * @private\n\t */\n\tprivate _autoRegister(): void {\n\t\tif (this._isRegistered || !this._ecspresso) return;\n\t\t\n\t\tconst system = this._buildSystemObject();\n\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\tthis._isRegistered = true;\n\t}\n\n\t/**\n\t * Create the system object without registering it\n\t * @private\n\t */\n\tprivate _buildSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\treturn this._createSystemObject();\n\t}\n\n\t/**\n\t * Create a system object with all configured properties\n\t * @private\n\t */\n\tprivate _createSystemObject(): System<ComponentTypes, any, any, EventTypes, ResourceTypes> {\n\t\tconst system: System<ComponentTypes, any, any, EventTypes, ResourceTypes> = {\n\t\t\tlabel: this._label,\n\t\t\tentityQueries: this.queries,\n\t\t\tpriority: this._priority,\n\t\t};\n\n\t\tif (this.processFunction) {\n\t\t\tsystem.process = this.processFunction;\n\t\t}\n\n\t\tif (this.detachFunction) {\n\t\t\tsystem.onDetach = this.detachFunction;\n\t\t}\n\n\t\tif (this.initializeFunction) {\n\t\t\tsystem.onInitialize = this.initializeFunction;\n\t\t}\n\n\t\tif (this.eventHandlers) {\n\t\t\tsystem.eventHandlers = this.eventHandlers;\n\t\t}\n\n\t\tif (this._groups.length > 0) {\n\t\t\tsystem.groups = [...this._groups];\n\t\t}\n\n\t\tif (this._inScreens) {\n\t\t\tsystem.inScreens = this._inScreens;\n\t\t}\n\n\t\tif (this._excludeScreens) {\n\t\t\tsystem.excludeScreens = this._excludeScreens;\n\t\t}\n\n\t\tif (this._requiredAssets) {\n\t\t\tsystem.requiredAssets = this._requiredAssets;\n\t\t}\n\n\t\treturn system;\n\t}\n\n\t// TODO: Should this be a setter?\n\t/**\n\t * Set the priority of this system. Systems with higher priority values\n\t * execute before those with lower values. Systems with the same priority\n\t * execute in the order they were registered.\n\t * @param priority The priority value (default: 0)\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetPriority(priority: number): this {\n\t\tthis._priority = priority;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add this system to a group. Systems can belong to multiple groups.\n\t * When any group a system belongs to is disabled, the system will be skipped.\n\t * @param groupName The name of the group to add the system to\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinGroup(groupName: string): this {\n\t\tif (!this._groups.includes(groupName)) {\n\t\t\tthis._groups.push(groupName);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Restrict this system to only run in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is not in this list.\n\t * @param screens Array of screen names where this system should run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tinScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._inScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Exclude this system from running in specified screens.\n\t * System will be skipped during update() when the current screen\n\t * is in this list.\n\t * @param screens Array of screen names where this system should NOT run\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\texcludeScreens(screens: ReadonlyArray<string>): this {\n\t\tthis._excludeScreens = [...screens];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Require specific assets to be loaded for this system to run.\n\t * System will be skipped during update() if any required asset\n\t * is not loaded.\n\t * @param assets Array of asset keys that must be loaded\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\trequiresAssets(assets: ReadonlyArray<string>): this {\n\t\tthis._requiredAssets = [...assets];\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add a query definition to the system\n\t */\n\taddQuery<\n\t\tQueryName extends string,\n\t\tWithComponents extends keyof ComponentTypes,\n\t\tWithoutComponents extends keyof ComponentTypes = never,\n\t\tNewQueries extends Queries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>> =\n\t\t\tQueries & Record<QueryName, QueryDefinition<ComponentTypes, WithComponents, WithoutComponents>>\n\t>(\n\t\tname: QueryName,\n\t\tdefinition: {\n\t\t\twith: ReadonlyArray<WithComponents>;\n\t\t\twithout?: ReadonlyArray<WithoutComponents>;\n\t\t}\n\t): this extends SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t? SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t: this extends SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t\t\t? SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, NewQueries>\n\t\t\t: SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, NewQueries> {\n\t\t// Cast is needed because TypeScript can't preserve the type information\n\t\t// when modifying an object property\n\t\tconst newBuilder = this as any;\n\t\tnewBuilder.queries = {\n\t\t\t...this.queries,\n\t\t\t[name]: definition,\n\t\t};\n\t\treturn newBuilder;\n\t}\n\n\t/**\n\t * Set the system's process function that runs each update\n\t * @param process Function to process entities matching the system's queries each update\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetProcess(\n\t\tprocess: ProcessFunction<ComponentTypes, EventTypes, ResourceTypes, Queries>\n\t): this {\n\t\tthis.processFunction = process;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Register this system with its ECSpresso instance and return the ECSpresso for chaining\n\t * This enables seamless method chaining: .registerAndContinue().addSystem(...)\n\t * @returns ECSpresso instance if attached to one, otherwise throws an error\n\t */\n\tregisterAndContinue(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (!this._ecspresso) {\n\t\t\tthrow new Error(`Cannot register system '${this._label}': SystemBuilder is not attached to an ECSpresso instance. Use Bundle.addSystem() or ECSpresso.addSystem() instead.`);\n\t\t}\n\t\t\n\t\tthis._autoRegister();\n\t\treturn this._ecspresso;\n\t}\n\n\t/**\n\t * Complete this system and return the parent container for seamless chaining\n\t * - For ECSpresso-attached builders: registers the system and returns ECSpresso\n\t * - For Bundle-attached builders: returns the Bundle\n\t * This method is typed via the specialized interfaces (SystemBuilderWithEcspresso, SystemBuilderWithBundle)\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes> | Bundle<ComponentTypes, EventTypes, ResourceTypes> {\n\t\tif (this._ecspresso) {\n\t\t\tthis._autoRegister();\n\t\t\treturn this._ecspresso;\n\t\t}\n\n\t\tif (this._bundle) {\n\t\t\treturn this._bundle;\n\t\t}\n\n\t\tthrow new Error(`Cannot use and() on system '${this._label}': not attached to ECSpresso or Bundle.`);\n\t}\n\n\t/**\n\t * Set the onDetach lifecycle hook\n\t * Called when the system is removed from the ECS\n\t * @param onDetach Function to run when this system is detached from the ECS\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnDetach(\n\t\tonDetach: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.detachFunction = onDetach;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set the onInitialize lifecycle hook\n\t * Called when the system is initialized via ECSpresso.initialize() method\n\t * @param onInitialize Function to run when this system is initialized\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetOnInitialize(\n\t\tonInitialize: LifecycleFunction<ComponentTypes, EventTypes, ResourceTypes>\n\t): this {\n\t\tthis.initializeFunction = onInitialize;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Set event handlers for the system\n\t * These handlers will be automatically subscribed when the system is attached\n\t * @param handlers Object mapping event names to handler functions\n\t * @returns This SystemBuilder instance for method chaining\n\t */\n\tsetEventHandlers(\n\t\thandlers: {\n\t\t\t[EventName in keyof EventTypes]?: {\n\t\t\t\thandler(\n\t\t\t\t\tdata: EventTypes[EventName],\n\t\t\t\t\tecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n\t\t\t\t): void;\n\t\t\t};\n\t\t}\n\t): this {\n\t\tthis.eventHandlers = handlers;\n\t\treturn this;\n\t}\n\n\t/**\n\t * Build the final system object\n\t */\n\tbuild(ecspresso?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tconst system = this._createSystemObject();\n\n\t\tif (this._ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, this._ecspresso);\n\t\t}\n\n\t\tif(ecspresso) {\n\t\t\tregisterSystemWithEcspresso(system, ecspresso);\n\t\t}\n\n\t\treturn this;\n\t}\n}\n\n/**\n * Helper function to register a system with an ECSpresso instance\n * This handles attaching the system and setting up event handlers\n * @internal Used by SystemBuilder and Bundle\n */\nexport function registerSystemWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tsystem: System<ComponentTypes, any, any, EventTypes, ResourceTypes>,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n) {\n\t// Use the new internal registration method instead of direct property access\n\tecspresso._registerSystem(system);\n}\n\n// Helper type definitions\ntype QueryDefinition<\n\tComponentTypes,\n\tWithComponents extends keyof ComponentTypes = any,\n\tWithoutComponents extends keyof ComponentTypes = any,\n> = {\n\twith: ReadonlyArray<WithComponents>;\n\twithout?: ReadonlyArray<WithoutComponents>;\n};\n\ntype QueryResults<\n\tComponentTypes,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = {\n\t[QueryName in keyof Queries]: QueryName extends string\n\t\t? FilteredEntity<\n\t\t\tComponentTypes,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, infer W, any> ? W : never,\n\t\t\tQueries[QueryName] extends QueryDefinition<ComponentTypes, any, infer WO> ? WO : never\n\t\t>[]\n\t\t: never;\n};\n\n/**\n * Function signature for system process methods\n * @param queries Results of entity queries defined by the system\n * @param deltaTime Time elapsed since last update in seconds\n * @param ecs The ECSpresso instance providing access to all ECS functionality\n */\ntype ProcessFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>>,\n> = (\n\tqueries: QueryResults<ComponentTypes, Queries>,\n\tdeltaTime: number,\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>\n) => void;\n\n/**\n * Type for system initialization functions\n * These can be asynchronous\n */\ntype LifecycleFunction<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n> = (\n\tecs: ECSpresso<\n\t\tComponentTypes,\n\t\tEventTypes,\n\t\tResourceTypes\n\t>,\n) => void | Promise<void>;\n\n/**\n * Create a SystemBuilder attached to an ECSpresso instance\n * Helper function used by ECSpresso.addSystem\n */\nexport function createEcspressoSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tecspresso\n\t) as SystemBuilderWithEcspresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * Create a SystemBuilder attached to a Bundle\n * Helper function used by Bundle.addSystem\n */\nexport function createBundleSystemBuilder<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>\n>(\n\tlabel: string,\n\tbundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>\n): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes> {\n\treturn new SystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(\n\t\tlabel,\n\t\tnull,\n\t\tbundle\n\t) as SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n// Type interfaces for specialized SystemBuilders\n\n/**\n * SystemBuilder with a guaranteed non-null reference to an ECSpresso instance\n */\nexport interface SystemBuilderWithEcspresso<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n\t\n\t/**\n\t * Complete this system and return ECSpresso for seamless chaining\n\t * Automatically registers the system when called\n\t */\n\tand(): ECSpresso<ComponentTypes, EventTypes, ResourceTypes>;\n}\n\n/**\n * SystemBuilder with a guaranteed non-null reference to a Bundle\n */\nexport interface SystemBuilderWithBundle<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tQueries extends Record<string, QueryDefinition<ComponentTypes>> = {}\n> extends SystemBuilder<ComponentTypes, EventTypes, ResourceTypes, Queries> {\n\treadonly bundle: Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n\n\t/**\n\t * Complete this system and return the Bundle for chaining\n\t * Enables fluent API: bundle.addSystem(...).and().addSystem(...)\n\t */\n\tand(): Bundle<ComponentTypes, EventTypes, ResourceTypes>;\n}\n",
|
|
6
|
+
"import { createBundleSystemBuilder, SystemBuilderWithBundle } from './system-builder';\nimport type ECSpresso from './ecspresso';\nimport type { AssetDefinition } from './asset-types';\nimport type { ScreenDefinition } from './screen-types';\n\n/**\n * Generates a unique ID for a bundle\n */\nfunction generateBundleId(): string {\n\treturn `bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Bundle class that encapsulates a set of components, resources, events, and systems\n * that can be merged into a ECSpresso instance\n */\nexport default class Bundle<\n\tComponentTypes extends Record<string, any> = {},\n\tEventTypes extends Record<string, any> = {},\n\tResourceTypes extends Record<string, any> = {},\n\tAssetTypes extends Record<string, unknown> = {},\n\tScreenStates extends Record<string, ScreenDefinition<any, any>> = {},\n> {\n\tprivate _systems: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] = [];\n\tprivate _resources: Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> = new Map();\n\tprivate _assets: Map<string, AssetDefinition<unknown>> = new Map();\n\tprivate _assetGroups: Map<string, Map<string, () => Promise<unknown>>> = new Map();\n\tprivate _screens: Map<string, ScreenDefinition<any, any>> = new Map();\n\tprivate _id: string;\n\n\tconstructor(id?: string) {\n\t\tthis._id = id || generateBundleId();\n\t}\n\n\t/**\n\t * Get the unique ID of this bundle\n\t */\n\tget id(): string {\n\t\treturn this._id;\n\t}\n\n\t/**\n\t * Set the ID of this bundle\n\t * @internal Used by combineBundles\n\t */\n\tset id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\t/**\n\t * Add a system to this bundle, by label (creating a new builder) or by reusing an existing one\n\t */\n\taddSystem<Q extends Record<string, any>>(builder: SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, Q>;\n\taddSystem(label: string): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, {}>;\n\taddSystem(builderOrLabel: string | SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>) {\n\t\tif (typeof builderOrLabel === 'string') {\n\t\t\tconst system = createBundleSystemBuilder<ComponentTypes, EventTypes, ResourceTypes>(builderOrLabel, this);\n\t\t\tthis._systems.push(system);\n\t\t\treturn system;\n\t\t} else {\n\t\t\tthis._systems.push(builderOrLabel);\n\t\t\treturn builderOrLabel;\n\t\t}\n\t}\n\n\t/**\n\t * Add a resource to this bundle\n\t * @param label The resource key\n\t * @param resource The resource value, a factory function, or a factory with dependencies\n\t */\n\taddResource<K extends keyof ResourceTypes>(\n\t\tlabel: K,\n\t\tresource:\n\t\t\t| ResourceTypes[K]\n\t\t\t| ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)\n\t\t\t| { dependsOn: readonly string[]; factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]> }\n\t) {\n\t\t// We need this cast because TypeScript doesn't recognize that a value of type\n\t\t// ResourceTypes[K] | (() => ResourceTypes[K] | Promise<ResourceTypes[K]>) | { dependsOn, factory }\n\t\t// can be properly assigned to Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]>\n\t\tthis._resources.set(label, resource as unknown as ResourceTypes[K]);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add an asset to this bundle\n\t * @param key The asset key\n\t * @param loader Function that loads and returns the asset\n\t * @param options Optional asset configuration\n\t */\n\taddAsset<K extends string, T>(\n\t\tkey: K,\n\t\tloader: () => Promise<T>,\n\t\toptions?: { eager?: boolean; group?: string }\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates> {\n\t\tthis._assets.set(key, {\n\t\t\tloader,\n\t\t\teager: options?.eager ?? true,\n\t\t\tgroup: options?.group,\n\t\t});\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & Record<K, T>, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a group of assets to this bundle\n\t * @param groupName The group name\n\t * @param assets Object mapping asset keys to loader functions\n\t */\n\taddAssetGroup<G extends string, T extends Record<string, () => Promise<unknown>>>(\n\t\tgroupName: G,\n\t\tassets: T\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates> {\n\t\tconst groupAssets = new Map<string, () => Promise<unknown>>();\n\t\tfor (const [key, loader] of Object.entries(assets)) {\n\t\t\tgroupAssets.set(key, loader as () => Promise<unknown>);\n\t\t\tthis._assets.set(key, {\n\t\t\t\tloader: loader as () => Promise<unknown>,\n\t\t\t\teager: false,\n\t\t\t\tgroup: groupName,\n\t\t\t});\n\t\t}\n\t\tthis._assetGroups.set(groupName, groupAssets);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes & { [K in keyof T]: Awaited<ReturnType<T[K]>> }, ScreenStates>;\n\t}\n\n\t/**\n\t * Add a screen to this bundle\n\t * @param name The screen name\n\t * @param definition The screen definition\n\t */\n\taddScreen<K extends string, Config extends Record<string, unknown>, State extends Record<string, unknown>>(\n\t\tname: K,\n\t\tdefinition: ScreenDefinition<Config, State>\n\t): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>> {\n\t\tthis._screens.set(name, definition);\n\t\treturn this as unknown as Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates & Record<K, ScreenDefinition<Config, State>>>;\n\t}\n\n\t/**\n\t * Get all asset definitions in this bundle\n\t */\n\tgetAssets(): Map<string, AssetDefinition<unknown>> {\n\t\treturn new Map(this._assets);\n\t}\n\n\t/**\n\t * Get all screen definitions in this bundle\n\t */\n\tgetScreens(): Map<string, ScreenDefinition<any, any>> {\n\t\treturn new Map(this._screens);\n\t}\n\n\t/**\n\t * Internal method to set a resource\n\t * @internal Used by mergeBundles\n\t */\n\t_setResource(key: string, value: unknown): void {\n\t\tthis._resources.set(key as keyof ResourceTypes, value as ResourceTypes[keyof ResourceTypes]);\n\t}\n\n\t/**\n\t * Internal method to set an asset definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setAsset(key: string, definition: AssetDefinition<unknown>): void {\n\t\tthis._assets.set(key, definition);\n\t}\n\n\t/**\n\t * Internal method to set a screen definition\n\t * @internal Used by mergeBundles\n\t */\n\t_setScreen(name: string, definition: ScreenDefinition<any, any>): void {\n\t\tthis._screens.set(name, definition);\n\t}\n\n\t/**\n\t * Get all systems defined in this bundle\n\t * Returns built System objects instead of SystemBuilders\n\t */\n\tgetSystems() {\n\t\treturn this._systems.map(system => system.build());\n\t}\n\n\t/**\n\t * Register all systems in this bundle with an ECSpresso instance\n\t * @internal Used by ECSpresso when adding a bundle\n\t */\n\tregisterSystemsWithEcspresso(ecspresso: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) {\n\t\tfor (const systemBuilder of this._systems) {\n\t\t\tsystemBuilder.build(ecspresso);\n\t\t}\n\t}\n\n\t/**\n\t * Get all resources defined in this bundle\n\t */\n\tgetResources(): Map<keyof ResourceTypes, ResourceTypes[keyof ResourceTypes]> {\n\t\treturn new Map(this._resources);\n\t}\n\n\t/**\n\t * Get a specific resource by key\n\t * @param key The resource key\n\t * @returns The resource value or undefined if not found\n\t */\n\tgetResource<K extends keyof ResourceTypes>(key: K): ResourceTypes[K] {\n\t\treturn this._resources.get(key) as ResourceTypes[K];\n\t}\n\n\t/**\n\t * Get all system builders in this bundle\n\t */\n\tgetSystemBuilders(): SystemBuilderWithBundle<ComponentTypes, EventTypes, ResourceTypes, any>[] {\n\t\treturn [...this._systems];\n\t}\n\n\t/**\n\t * Check if this bundle has a specific resource\n\t * @param key The resource key to check\n\t * @returns True if the resource exists\n\t */\n\thasResource<K extends keyof ResourceTypes>(key: K): boolean {\n\t\treturn this._resources.has(key);\n\t}\n}\n\n/**\n * Utility type to check if two types are exactly the same\n */\ntype Exactly<T, U> = T extends U ? U extends T ? true : false : false;\n\n/**\n * Simplified type constraint for bundle compatibility\n * Ensures that overlapping keys have exactly the same types\n */\ntype CompatibleBundles<\n\tC1 extends Record<string, any>,\n\tC2 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tR2 extends Record<string, any>,\n\tA1 extends Record<string, unknown> = {},\n\tA2 extends Record<string, unknown> = {},\n\tS1 extends Record<string, ScreenDefinition<any, any>> = {},\n\tS2 extends Record<string, ScreenDefinition<any, any>> = {},\n> = {\n\t[K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends true ? C1[K] : never;\n} & {\n\t[K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends true ? E1[K] : never;\n} & {\n\t[K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends true ? R1[K] : never;\n} & {\n\t[K in keyof A1 & keyof A2]: Exactly<A1[K], A2[K]> extends true ? A1[K] : never;\n} & {\n\t[K in keyof S1 & keyof S2]: Exactly<S1[K], S2[K]> extends true ? S1[K] : never;\n};\n\n/**\n * Function that merges multiple bundles into a single bundle\n */\nexport function mergeBundles<\n\tC1 extends Record<string, any>,\n\tE1 extends Record<string, any>,\n\tR1 extends Record<string, any>,\n\tA1 extends Record<string, unknown>,\n\tS1 extends Record<string, ScreenDefinition<any, any>>,\n\tC2 extends Record<string, any>,\n\tE2 extends Record<string, any>,\n\tR2 extends Record<string, any>,\n\tA2 extends Record<string, unknown>,\n\tS2 extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\tbundle1: Bundle<C1, E1, R1, A1, S1>,\n\tbundle2: Bundle<C2, E2, R2, A2, S2> & CompatibleBundles<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2>\n): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2>;\n\nexport function mergeBundles<\n\tComponentTypes extends Record<string, any>,\n\tEventTypes extends Record<string, any>,\n\tResourceTypes extends Record<string, any>,\n\tAssetTypes extends Record<string, unknown>,\n\tScreenStates extends Record<string, ScreenDefinition<any, any>>,\n>(\n\tid: string,\n\t...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>>\n): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;\n\nexport function mergeBundles(\n\tid: string,\n\t...bundles: Array<Bundle<any, any, any, any, any>>\n): Bundle<any, any, any, any, any> {\n\tif (bundles.length === 0) {\n\t\treturn new Bundle(id);\n\t}\n\n\tconst combined = new Bundle(id);\n\n\tfor (const bundle of bundles) {\n\t\tfor (const system of bundle.getSystemBuilders()) {\n\t\t\t// reuse the full builder so we carry over queries, hooks, and handlers\n\t\t\tcombined.addSystem(system);\n\t\t}\n\n\t\t// Add resources from this bundle\n\t\tfor (const [label, resource] of bundle.getResources().entries()) {\n\t\t\tcombined._setResource(label as string, resource);\n\t\t}\n\n\t\t// Add assets from this bundle\n\t\tfor (const [key, definition] of bundle.getAssets().entries()) {\n\t\t\tcombined._setAsset(key, definition);\n\t\t}\n\n\t\t// Add screens from this bundle\n\t\tfor (const [name, definition] of bundle.getScreens().entries()) {\n\t\t\tcombined._setScreen(name, definition);\n\t\t}\n\t}\n\n\treturn combined;\n}\n",
|
|
7
|
+
"/**\n * Timer Bundle for ECSpresso\n *\n * Provides ECS-native timers following the \"data, not callbacks\" philosophy.\n * Timers are components processed each frame, automatically cleaned up when entities are removed.\n */\n\nimport Bundle from '../../bundle';\n\n// ==================== Component Types ====================\n\n/**\n * Timer component data structure.\n * Use `justFinished` to detect timer completion in your systems.\n */\nexport interface Timer {\n\t/** Time accumulated so far (seconds) */\n\telapsed: number;\n\t/** Target duration (seconds) */\n\tduration: number;\n\t/** Whether timer repeats after completion */\n\trepeat: boolean;\n\t/** Whether timer is currently running */\n\tactive: boolean;\n\t/** True for one frame after timer completes */\n\tjustFinished: boolean;\n}\n\n/**\n * Component types provided by the timer bundle.\n * Extend your component types with this interface.\n *\n * @example\n * ```typescript\n * interface GameComponents extends TimerComponentTypes {\n * velocity: { x: number; y: number };\n * player: true;\n * }\n * ```\n */\nexport interface TimerComponentTypes {\n\ttimer: Timer;\n}\n\n// ==================== Bundle Options ====================\n\n/**\n * Configuration options for the timer bundle.\n */\nexport interface TimerBundleOptions {\n\t/** System group name (default: 'timers') */\n\tsystemGroup?: string;\n\t/** Priority for timer update system (default: 0) */\n\tpriority?: number;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a one-shot timer that fires once after the specified duration.\n *\n * @param duration Duration in seconds until the timer completes\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * // Timer that triggers after 2 seconds\n * ecs.spawn({\n * ...createTimer(2),\n * explosion: true,\n * });\n * ```\n */\nexport function createTimer(duration: number): Pick<TimerComponentTypes, 'timer'> {\n\treturn {\n\t\ttimer: {\n\t\t\telapsed: 0,\n\t\t\tduration,\n\t\t\trepeat: false,\n\t\t\tactive: true,\n\t\t\tjustFinished: false,\n\t\t},\n\t};\n}\n\n/**\n * Create a repeating timer that fires every `duration` seconds.\n *\n * @param duration Duration in seconds between each timer completion\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * // Timer that triggers every 5 seconds\n * ecs.spawn({\n * ...createRepeatingTimer(5),\n * spawner: true,\n * });\n * ```\n */\nexport function createRepeatingTimer(duration: number): Pick<TimerComponentTypes, 'timer'> {\n\treturn {\n\t\ttimer: {\n\t\t\telapsed: 0,\n\t\t\tduration,\n\t\t\trepeat: true,\n\t\t\tactive: true,\n\t\t\tjustFinished: false,\n\t\t},\n\t};\n}\n\n// ==================== Bundle Factory ====================\n\n/**\n * Create a timer bundle for ECSpresso.\n *\n * This bundle provides:\n * - Timer update system that processes all timer components each frame\n * - `justFinished` flag pattern for one-frame completion detection\n * - Automatic cleanup when entities are removed\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso\n * .create<Components, Events, Resources>()\n * .withBundle(createTimerBundle())\n * .build();\n *\n * // Spawn entity with timer\n * ecs.spawn({\n * ...createRepeatingTimer(5),\n * spawner: true,\n * });\n *\n * // React to timer completion in a system\n * ecs.addSystem('spawn-on-timer')\n * .addQuery('spawners', { with: ['timer', 'spawner'] })\n * .setProcess((queries, _dt, ecs) => {\n * for (const { components } of queries.spawners) {\n * if (components.timer.justFinished) {\n * ecs.spawn({ enemy: true });\n * }\n * }\n * });\n * ```\n */\nexport function createTimerBundle(\n\toptions?: TimerBundleOptions\n): Bundle<TimerComponentTypes, {}, {}> {\n\tconst {\n\t\tsystemGroup = 'timers',\n\t\tpriority = 0,\n\t} = options ?? {};\n\n\tconst bundle = new Bundle<TimerComponentTypes, {}, {}>('timers');\n\n\tbundle\n\t\t.addSystem('timer-update')\n\t\t.setPriority(priority)\n\t\t.inGroup(systemGroup)\n\t\t.addQuery('timers', {\n\t\t\twith: ['timer'] as const,\n\t\t})\n\t\t.setProcess((queries, deltaTime) => {\n\t\t\tfor (const entity of queries.timers) {\n\t\t\t\tconst { timer } = entity.components;\n\n\t\t\t\t// Reset justFinished flag from previous frame\n\t\t\t\ttimer.justFinished = false;\n\n\t\t\t\t// Skip inactive timers\n\t\t\t\tif (!timer.active) continue;\n\n\t\t\t\t// Accumulate time\n\t\t\t\ttimer.elapsed += deltaTime;\n\n\t\t\t\t// Check for completion\n\t\t\t\tif (timer.elapsed >= timer.duration) {\n\t\t\t\t\ttimer.justFinished = true;\n\n\t\t\t\t\tif (timer.repeat) {\n\t\t\t\t\t\t// Preserve overflow for consistent timing\n\t\t\t\t\t\ttimer.elapsed -= timer.duration;\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttimer.active = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t\t.and();\n\n\treturn bundle;\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "13DBE68984ED14A764756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
package/dist/ecspresso.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import EntityManager from "./entity-manager";
|
|
|
2
2
|
import EventBus from "./event-bus";
|
|
3
3
|
import AssetManager from "./asset-manager";
|
|
4
4
|
import ScreenManager from "./screen-manager";
|
|
5
|
-
import
|
|
5
|
+
import { type ReactiveQueryDefinition } from "./reactive-query-manager";
|
|
6
|
+
import type { System, FilteredEntity, Entity, RemoveEntityOptions, HierarchyEntry, HierarchyIteratorOptions } from "./types";
|
|
6
7
|
import type Bundle from "./bundle";
|
|
7
8
|
import type { BundlesAreCompatible } from "./type-utils";
|
|
8
9
|
import type { AssetHandle, AssetConfigurator } from "./asset-types";
|
|
@@ -36,16 +37,25 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
36
37
|
private _sortedSystems;
|
|
37
38
|
/** Track installed bundles to prevent duplicates*/
|
|
38
39
|
private _installedBundles;
|
|
40
|
+
/** Disabled system groups */
|
|
41
|
+
private _disabledGroups;
|
|
39
42
|
/** Asset manager for loading and accessing assets */
|
|
40
43
|
private _assetManager;
|
|
41
44
|
/** Screen manager for state/screen transitions */
|
|
42
45
|
private _screenManager;
|
|
46
|
+
/** Reactive query manager for enter/exit callbacks */
|
|
47
|
+
private _reactiveQueryManager;
|
|
43
48
|
/** Post-update hooks to be called after all systems in update() */
|
|
44
49
|
private _postUpdateHooks;
|
|
45
50
|
/**
|
|
46
51
|
* Creates a new ECSpresso instance.
|
|
47
52
|
*/
|
|
48
53
|
constructor();
|
|
54
|
+
/**
|
|
55
|
+
* Sets up component lifecycle hooks for reactive query tracking
|
|
56
|
+
* @private
|
|
57
|
+
*/
|
|
58
|
+
private _setupReactiveQueryHooks;
|
|
49
59
|
/**
|
|
50
60
|
* Creates a new ECSpresso builder for type-safe bundle installation.
|
|
51
61
|
* This is the preferred way to create an ECSpresso instance with bundles.
|
|
@@ -107,6 +117,28 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
107
117
|
* @returns true if the system was found and updated, false otherwise
|
|
108
118
|
*/
|
|
109
119
|
updateSystemPriority(label: string, priority: number): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Disable a system group. Systems in this group will be skipped during update().
|
|
122
|
+
* @param groupName The name of the group to disable
|
|
123
|
+
*/
|
|
124
|
+
disableSystemGroup(groupName: string): void;
|
|
125
|
+
/**
|
|
126
|
+
* Enable a system group. Systems in this group will run during update().
|
|
127
|
+
* @param groupName The name of the group to enable
|
|
128
|
+
*/
|
|
129
|
+
enableSystemGroup(groupName: string): void;
|
|
130
|
+
/**
|
|
131
|
+
* Check if a system group is enabled.
|
|
132
|
+
* @param groupName The name of the group to check
|
|
133
|
+
* @returns true if the group is enabled (or doesn't exist), false if disabled
|
|
134
|
+
*/
|
|
135
|
+
isSystemGroupEnabled(groupName: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Get all system labels that belong to a specific group.
|
|
138
|
+
* @param groupName The name of the group
|
|
139
|
+
* @returns Array of system labels in the group
|
|
140
|
+
*/
|
|
141
|
+
getSystemsInGroup(groupName: string): string[];
|
|
110
142
|
/**
|
|
111
143
|
* Remove a system by its label
|
|
112
144
|
* Calls the system's onDetach method with this ECSpresso instance if defined
|
|
@@ -130,13 +162,29 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
130
162
|
/**
|
|
131
163
|
* Add a resource to the ECS instance
|
|
132
164
|
*/
|
|
133
|
-
addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)
|
|
165
|
+
addResource<K extends keyof ResourceTypes>(key: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | {
|
|
166
|
+
dependsOn?: readonly string[];
|
|
167
|
+
factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>;
|
|
168
|
+
onDispose?: (resource: ResourceTypes[K], ecs?: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => void | Promise<void>;
|
|
169
|
+
}): this;
|
|
134
170
|
/**
|
|
135
|
-
* Remove a resource from the ECS instance
|
|
171
|
+
* Remove a resource from the ECS instance (without calling onDispose)
|
|
136
172
|
* @param key The resource key to remove
|
|
137
173
|
* @returns True if the resource was removed, false if it didn't exist
|
|
138
174
|
*/
|
|
139
175
|
removeResource<K extends keyof ResourceTypes>(key: K): boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Dispose a single resource, calling its onDispose callback if defined
|
|
178
|
+
* @param key The resource key to dispose
|
|
179
|
+
* @returns True if the resource existed and was disposed, false if it didn't exist
|
|
180
|
+
*/
|
|
181
|
+
disposeResource<K extends keyof ResourceTypes>(key: K): Promise<boolean>;
|
|
182
|
+
/**
|
|
183
|
+
* Dispose all initialized resources in reverse dependency order.
|
|
184
|
+
* Resources that depend on others are disposed first.
|
|
185
|
+
* Calls each resource's onDispose callback if defined.
|
|
186
|
+
*/
|
|
187
|
+
disposeResources(): Promise<void>;
|
|
140
188
|
/**
|
|
141
189
|
* Update an existing resource using an updater function
|
|
142
190
|
* @param key The resource key to update
|
|
@@ -269,6 +317,20 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
269
317
|
* @returns Readonly array of root entity IDs
|
|
270
318
|
*/
|
|
271
319
|
getRootEntities(): readonly number[];
|
|
320
|
+
/**
|
|
321
|
+
* Traverse the hierarchy in parent-first (breadth-first) order.
|
|
322
|
+
* Parents are guaranteed to be visited before their children.
|
|
323
|
+
* @param callback Function called for each entity with (entityId, parentId, depth)
|
|
324
|
+
* @param options Optional traversal options (roots to filter to specific subtrees)
|
|
325
|
+
*/
|
|
326
|
+
forEachInHierarchy(callback: (entityId: number, parentId: number | null, depth: number) => void, options?: HierarchyIteratorOptions): void;
|
|
327
|
+
/**
|
|
328
|
+
* Generator-based hierarchy traversal in parent-first (breadth-first) order.
|
|
329
|
+
* Supports early termination via break.
|
|
330
|
+
* @param options Optional traversal options (roots to filter to specific subtrees)
|
|
331
|
+
* @yields HierarchyEntry for each entity in parent-first order
|
|
332
|
+
*/
|
|
333
|
+
hierarchyIterator(options?: HierarchyIteratorOptions): Generator<HierarchyEntry, void, unknown>;
|
|
272
334
|
/**
|
|
273
335
|
* Emit a hierarchy changed event
|
|
274
336
|
* @internal
|
|
@@ -280,6 +342,32 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
280
342
|
get installedBundles(): string[];
|
|
281
343
|
get entityManager(): EntityManager<ComponentTypes>;
|
|
282
344
|
get eventBus(): EventBus<EventTypes>;
|
|
345
|
+
/**
|
|
346
|
+
* Register a callback when a specific component is added to any entity
|
|
347
|
+
* @param componentName The component key
|
|
348
|
+
* @param handler Function receiving the new component value and the entity
|
|
349
|
+
* @returns Unsubscribe function to remove the callback
|
|
350
|
+
*/
|
|
351
|
+
onComponentAdded<K extends keyof ComponentTypes>(componentName: K, handler: (value: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
|
|
352
|
+
/**
|
|
353
|
+
* Register a callback when a specific component is removed from any entity
|
|
354
|
+
* @param componentName The component key
|
|
355
|
+
* @param handler Function receiving the old component value and the entity
|
|
356
|
+
* @returns Unsubscribe function to remove the callback
|
|
357
|
+
*/
|
|
358
|
+
onComponentRemoved<K extends keyof ComponentTypes>(componentName: K, handler: (oldValue: ComponentTypes[K], entity: Entity<ComponentTypes>) => void): () => void;
|
|
359
|
+
/**
|
|
360
|
+
* Add a reactive query that triggers callbacks when entities enter/exit the query match.
|
|
361
|
+
* @param name Unique name for the query
|
|
362
|
+
* @param definition Query definition with with/without arrays and onEnter/onExit callbacks
|
|
363
|
+
*/
|
|
364
|
+
addReactiveQuery<WithComponents extends keyof ComponentTypes, WithoutComponents extends keyof ComponentTypes = never>(name: string, definition: ReactiveQueryDefinition<ComponentTypes, WithComponents, WithoutComponents>): void;
|
|
365
|
+
/**
|
|
366
|
+
* Remove a reactive query by name.
|
|
367
|
+
* @param name Name of the query to remove
|
|
368
|
+
* @returns true if the query existed and was removed, false otherwise
|
|
369
|
+
*/
|
|
370
|
+
removeReactiveQuery(name: string): boolean;
|
|
283
371
|
/**
|
|
284
372
|
* Subscribe to an event (convenience wrapper for eventBus.subscribe)
|
|
285
373
|
* @param eventType The event type to subscribe to
|
|
@@ -397,6 +485,14 @@ export default class ECSpresso<ComponentTypes extends Record<string, any> = {},
|
|
|
397
485
|
*/
|
|
398
486
|
_installBundle<C extends Record<string, any>, E extends Record<string, any>, R extends Record<string, any>, A extends Record<string, unknown> = {}, S extends Record<string, ScreenDefinition<any, any>> = {}>(bundle: Bundle<C, E, R, A, S>): this;
|
|
399
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Resource factory with optional dependencies and disposal callback
|
|
490
|
+
*/
|
|
491
|
+
type ResourceFactoryWithDeps<T> = {
|
|
492
|
+
dependsOn?: readonly string[];
|
|
493
|
+
factory: (context?: any) => T | Promise<T>;
|
|
494
|
+
onDispose?: (resource: T, context?: any) => void | Promise<void>;
|
|
495
|
+
};
|
|
400
496
|
/**
|
|
401
497
|
* Builder class for ECSpresso that provides fluent type-safe bundle installation.
|
|
402
498
|
* Handles type checking during build process to ensure type safety.
|
|
@@ -408,6 +504,8 @@ export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E exte
|
|
|
408
504
|
private assetConfigurator;
|
|
409
505
|
/** Screen configurator for collecting screen definitions */
|
|
410
506
|
private screenConfigurator;
|
|
507
|
+
/** Pending resources to add during build */
|
|
508
|
+
private pendingResources;
|
|
411
509
|
constructor();
|
|
412
510
|
/**
|
|
413
511
|
* Add the first bundle when starting with empty types.
|
|
@@ -419,6 +517,26 @@ export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E exte
|
|
|
419
517
|
* This overload enforces bundle type compatibility.
|
|
420
518
|
*/
|
|
421
519
|
withBundle<BC extends Record<string, any>, BE extends Record<string, any>, BR extends Record<string, any>>(bundle: BundlesAreCompatible<C, BC, E, BE, R, BR> extends true ? Bundle<BC, BE, BR> : never): ECSpressoBuilder<C & BC, E & BE, R & BR, A, S>;
|
|
520
|
+
/**
|
|
521
|
+
* Add a resource during ECSpresso construction
|
|
522
|
+
* @param key The resource key
|
|
523
|
+
* @param resource The resource value, factory function, or factory with dependencies/disposal
|
|
524
|
+
* @returns This builder with updated resource types
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* ```typescript
|
|
528
|
+
* ECSpresso.create<Components, Events, Resources>()
|
|
529
|
+
* .withResource('config', { debug: true })
|
|
530
|
+
* .withResource('counter', () => 42)
|
|
531
|
+
* .withResource('derived', {
|
|
532
|
+
* dependsOn: ['base'],
|
|
533
|
+
* factory: (ecs) => ecs.getResource('base') * 2,
|
|
534
|
+
* onDispose: (value) => console.log('Disposed:', value)
|
|
535
|
+
* })
|
|
536
|
+
* .build();
|
|
537
|
+
* ```
|
|
538
|
+
*/
|
|
539
|
+
withResource<K extends string, V>(key: K, resource: V | ((context?: any) => V | Promise<V>) | ResourceFactoryWithDeps<V>): ECSpressoBuilder<C, E, R & Record<K, V>, A, S>;
|
|
422
540
|
/**
|
|
423
541
|
* Configure assets for this ECSpresso instance
|
|
424
542
|
* @param configurator Function that receives an AssetConfigurator and returns it after adding assets
|
|
@@ -464,3 +582,4 @@ export declare class ECSpressoBuilder<C extends Record<string, any> = {}, E exte
|
|
|
464
582
|
*/
|
|
465
583
|
build(): ECSpresso<C, E, R, A, S>;
|
|
466
584
|
}
|
|
585
|
+
export {};
|