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 CHANGED
@@ -15,6 +15,9 @@ A type-safe, modular, and extensible Entity Component System (ECS) framework for
15
15
  - **Screen Management**: Game state/screen transitions with overlay support
16
16
  - **Entity Hierarchy**: Parent-child relationships with traversal and cascade deletion
17
17
  - **Query System**: Powerful entity filtering with helper type utilities
18
+ - **Reactive Queries**: Enter/exit callbacks when entities match or unmatch queries
19
+ - **System Groups**: Enable/disable groups of systems at runtime
20
+ - **Component Lifecycle**: Callbacks for component add/remove with unsubscribe support
18
21
 
19
22
  ## Installation
20
23
 
@@ -212,6 +215,35 @@ world.addSystem('physics')
212
215
  .build();
213
216
  ```
214
217
 
218
+ ### System Groups
219
+
220
+ Organize systems into groups that can be enabled/disabled at runtime:
221
+
222
+ ```typescript
223
+ // Assign systems to groups
224
+ world.addSystem('renderSprites')
225
+ .inGroup('rendering')
226
+ .addQuery('sprites', { with: ['position', 'sprite'] })
227
+ .setProcess((queries) => { /* ... */ })
228
+ .and()
229
+ .addSystem('renderParticles')
230
+ .inGroup('rendering')
231
+ .inGroup('effects') // Systems can belong to multiple groups
232
+ .setProcess(() => { /* ... */ })
233
+ .build();
234
+
235
+ // Disable/enable groups at runtime
236
+ world.disableSystemGroup('rendering'); // All rendering systems skip
237
+ world.enableSystemGroup('rendering'); // Resume rendering
238
+
239
+ // Query group state
240
+ world.isSystemGroupEnabled('rendering'); // true/false
241
+ world.getSystemsInGroup('rendering'); // ['renderSprites', 'renderParticles']
242
+
243
+ // If a system belongs to multiple groups, disabling ANY group skips the system
244
+ world.disableSystemGroup('effects'); // renderParticles won't run
245
+ ```
246
+
215
247
  ## Advanced Features
216
248
 
217
249
  ### Bundles
@@ -316,7 +348,8 @@ Create resources lazily with factory functions:
316
348
  ```typescript
317
349
  interface Resources {
318
350
  config: { difficulty: string; soundEnabled: boolean };
319
- assets: { textures: any[] };
351
+ database: Database;
352
+ cache: { db: Database };
320
353
  }
321
354
 
322
355
  const world = new ECSpresso<Components, {}, Resources>();
@@ -328,15 +361,87 @@ world.addResource('config', () => ({
328
361
  }));
329
362
 
330
363
  // Async factory
331
- world.addResource('assets', async () => {
332
- const textures = await loadTextures();
333
- return { textures };
364
+ world.addResource('database', async () => {
365
+ return await connectToDatabase();
334
366
  });
335
367
 
336
- // Initialize all resources
368
+ // Factory with dependencies - initialized after dependencies are ready
369
+ world.addResource('cache', {
370
+ dependsOn: ['database'],
371
+ factory: (ecs) => ({
372
+ db: ecs.getResource('database')
373
+ })
374
+ });
375
+
376
+ // Initialize all resources (respects dependency order)
337
377
  await world.initializeResources();
338
378
  ```
339
379
 
380
+ **Dependency Features:**
381
+ - Resources are initialized in topological order (dependencies first)
382
+ - Circular dependencies throw a descriptive error at initialization time
383
+ - Existing patterns (direct values, simple factories) work unchanged
384
+
385
+ ### Resource Builder
386
+
387
+ Add resources fluently during ECSpresso construction using `withResource()`:
388
+
389
+ ```typescript
390
+ const world = ECSpresso
391
+ .create<Components, Events, Resources>()
392
+ .withBundle(physicsBundle)
393
+ .withResource('config', { debug: true, maxEntities: 1000 })
394
+ .withResource('score', () => ({ value: 0 }))
395
+ .withResource('cache', {
396
+ dependsOn: ['database'],
397
+ factory: (ecs) => createCache(ecs.getResource('database'))
398
+ })
399
+ .build();
400
+ ```
401
+
402
+ This chains naturally with `withBundle()`, `withAssets()`, and `withScreens()`.
403
+
404
+ ### Resource Disposal
405
+
406
+ Resources can define cleanup logic with `onDispose` callbacks, useful for removing event listeners, closing connections, or releasing resources:
407
+
408
+ ```typescript
409
+ // Factory with disposal callback
410
+ world.addResource('keyboard', {
411
+ factory: () => {
412
+ const handler = (e: KeyboardEvent) => { /* ... */ };
413
+ window.addEventListener('keydown', handler);
414
+ return { handler };
415
+ },
416
+ onDispose: (resource) => {
417
+ window.removeEventListener('keydown', resource.handler);
418
+ }
419
+ });
420
+
421
+ // Or with the builder pattern
422
+ const world = ECSpresso
423
+ .create<Components, Events, Resources>()
424
+ .withResource('database', {
425
+ factory: async () => await connectToDatabase(),
426
+ onDispose: async (db) => await db.close()
427
+ })
428
+ .build();
429
+
430
+ // Dispose a single resource
431
+ await world.disposeResource('keyboard');
432
+
433
+ // Dispose all resources in reverse dependency order
434
+ // (dependents are disposed before their dependencies)
435
+ await world.disposeResources();
436
+ ```
437
+
438
+ **Disposal Features:**
439
+ - `onDispose` receives the resource value and the ECSpresso instance as context
440
+ - `disposeResources()` disposes in reverse topological order (dependents first)
441
+ - Only initialized resources have their `onDispose` called
442
+ - Supports both sync and async disposal callbacks
443
+ - `removeResource()` still exists for removal without disposal
444
+
340
445
  ### System Lifecycle
341
446
 
342
447
  Systems can have initialization and cleanup hooks:
@@ -446,6 +551,32 @@ world.getChildAt(root.id, 0); // child.id
446
551
  world.getChildIndex(root.id, child2.id); // 1
447
552
  ```
448
553
 
554
+ #### Parent-First Traversal
555
+
556
+ Iterate the hierarchy with guaranteed parent-first order (useful for transform propagation):
557
+
558
+ ```typescript
559
+ // Callback-based traversal
560
+ world.forEachInHierarchy((entityId, parentId, depth) => {
561
+ // Parents are always visited before their children
562
+ console.log(`Entity ${entityId} at depth ${depth}, parent: ${parentId}`);
563
+ });
564
+
565
+ // Filter to specific subtrees
566
+ world.forEachInHierarchy(
567
+ (entityId, parentId, depth) => {
568
+ // Only visits entities under root.id
569
+ },
570
+ { roots: [root.id] }
571
+ );
572
+
573
+ // Generator-based traversal (supports early termination)
574
+ for (const { entityId, parentId, depth } of world.hierarchyIterator()) {
575
+ if (depth > 2) break; // Stop at depth 2
576
+ console.log(entityId);
577
+ }
578
+ ```
579
+
449
580
  #### Cascade Deletion
450
581
 
451
582
  When removing entities, descendants are automatically removed by default:
@@ -771,19 +902,71 @@ world.withBundle(conflictingBundle); // TypeScript prevents this
771
902
 
772
903
  ## Component Callbacks
773
904
 
774
- React to component changes with callbacks:
905
+ React to component changes with callbacks. Both methods return an unsubscribe function:
775
906
 
776
907
  ```typescript
777
- // Listen for component additions/removals
778
- world.entityManager.onComponentAdded('health', (value, entity) => {
908
+ // Listen for component additions - returns unsubscribe function
909
+ const unsubAdd = world.onComponentAdded('health', (value, entity) => {
779
910
  console.log(`Health added to entity ${entity.id}:`, value);
780
911
  });
781
912
 
782
- world.entityManager.onComponentRemoved('health', (oldValue, entity) => {
913
+ // Listen for component removals
914
+ const unsubRemove = world.onComponentRemoved('health', (oldValue, entity) => {
783
915
  console.log(`Health removed from entity ${entity.id}:`, oldValue);
784
916
  });
917
+
918
+ // Unsubscribe when done
919
+ unsubAdd();
920
+ unsubRemove();
921
+
922
+ // Also available on entityManager directly
923
+ world.entityManager.onComponentAdded('position', (value, entity) => {
924
+ // ...
925
+ });
785
926
  ```
786
927
 
928
+ ## Reactive Queries
929
+
930
+ Get callbacks when entities enter or exit a query match. Unlike regular queries that you poll during `update()`, reactive queries push notifications when the entity's components change:
931
+
932
+ ```typescript
933
+ // Add a reactive query with enter/exit callbacks
934
+ world.addReactiveQuery('enemies', {
935
+ with: ['position', 'enemy'],
936
+ without: ['dead'],
937
+ onEnter: (entity) => {
938
+ // Called when entity starts matching the query
939
+ console.log(`Enemy ${entity.id} appeared at`, entity.components.position);
940
+ spawnHealthBar(entity.id);
941
+ },
942
+ onExit: (entityId) => {
943
+ // Called when entity stops matching (receives ID since entity may be removed)
944
+ console.log(`Enemy ${entityId} gone`);
945
+ removeHealthBar(entityId);
946
+ },
947
+ });
948
+
949
+ // Triggers: spawning matching entity, adding required component,
950
+ // removing excluded component
951
+ const enemy = world.spawn({ position: { x: 0, y: 0 }, enemy: true }); // onEnter fires
952
+
953
+ // Triggers: removing required component, adding excluded component,
954
+ // removing entity
955
+ world.entityManager.addComponent(enemy.id, 'dead', true); // onExit fires
956
+
957
+ // Existing matching entities trigger onEnter when query is added
958
+ world.spawn({ position: { x: 10, y: 10 }, enemy: true });
959
+ world.addReactiveQuery('positioned', {
960
+ with: ['position'],
961
+ onEnter: (entity) => { /* Called for all existing entities with position */ },
962
+ });
963
+
964
+ // Remove reactive query when no longer needed
965
+ const removed = world.removeReactiveQuery('enemies'); // returns true if existed
966
+ ```
967
+
968
+ **Note:** Component replacement (calling `addComponent` with a component that already exists) does NOT trigger enter/exit callbacks since the entity's query match status doesn't change.
969
+
787
970
  ## Error Handling
788
971
 
789
972
  ECSpresso provides clear, contextual error messages for common issues:
package/dist/bundle.d.ts CHANGED
@@ -31,9 +31,12 @@ export default class Bundle<ComponentTypes extends Record<string, any> = {}, Eve
31
31
  /**
32
32
  * Add a resource to this bundle
33
33
  * @param label The resource key
34
- * @param resource The resource value or a factory function that returns the resource
34
+ * @param resource The resource value, a factory function, or a factory with dependencies
35
35
  */
36
- addResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>)): this;
36
+ addResource<K extends keyof ResourceTypes>(label: K, resource: ResourceTypes[K] | ((ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>) | {
37
+ dependsOn: readonly string[];
38
+ factory: (ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes>) => ResourceTypes[K] | Promise<ResourceTypes[K]>;
39
+ }): this;
37
40
  /**
38
41
  * Add an asset to this bundle
39
42
  * @param key The asset key
@@ -0,0 +1,248 @@
1
+ /**
2
+ * PixiJS Renderer Bundle for ECSpresso
3
+ *
4
+ * An opt-in PixiJS rendering bundle that automates scene graph wiring.
5
+ * Import from 'ecspresso/bundles/renderers/pixi'
6
+ */
7
+ import type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';
8
+ import Bundle from '../../bundle';
9
+ /**
10
+ * Local transform relative to parent (or world if no parent)
11
+ */
12
+ export interface LocalTransform {
13
+ x: number;
14
+ y: number;
15
+ rotation: number;
16
+ scaleX: number;
17
+ scaleY: number;
18
+ }
19
+ /**
20
+ * Computed world transform (accumulated from parent chain)
21
+ */
22
+ export interface WorldTransform {
23
+ x: number;
24
+ y: number;
25
+ rotation: number;
26
+ scaleX: number;
27
+ scaleY: number;
28
+ }
29
+ /**
30
+ * PixiJS Sprite component
31
+ */
32
+ export interface PixiSprite {
33
+ sprite: Sprite;
34
+ anchor?: {
35
+ x: number;
36
+ y: number;
37
+ };
38
+ }
39
+ /**
40
+ * PixiJS Graphics component
41
+ */
42
+ export interface PixiGraphics {
43
+ graphics: Graphics;
44
+ }
45
+ /**
46
+ * PixiJS Container component
47
+ */
48
+ export interface PixiContainer {
49
+ container: Container;
50
+ }
51
+ /**
52
+ * Visibility and alpha component
53
+ */
54
+ export interface PixiVisible {
55
+ visible: boolean;
56
+ alpha?: number;
57
+ }
58
+ /**
59
+ * Aggregate component types for PixiJS bundle.
60
+ * Users should extend this interface with their own component types.
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * interface GameComponents extends PixiComponentTypes {
65
+ * velocity: { x: number; y: number };
66
+ * player: true;
67
+ * }
68
+ * ```
69
+ */
70
+ export interface PixiComponentTypes {
71
+ localTransform: LocalTransform;
72
+ worldTransform: WorldTransform;
73
+ pixiSprite: PixiSprite;
74
+ pixiGraphics: PixiGraphics;
75
+ pixiContainer: PixiContainer;
76
+ pixiVisible: PixiVisible;
77
+ }
78
+ /**
79
+ * Events emitted by the PixiJS bundle
80
+ */
81
+ export interface PixiEventTypes {
82
+ hierarchyChanged: {
83
+ entityId: number;
84
+ oldParent: number | null;
85
+ newParent: number | null;
86
+ };
87
+ }
88
+ /**
89
+ * Resources provided by the PixiJS bundle
90
+ */
91
+ export interface PixiResourceTypes {
92
+ pixiApp: Application;
93
+ pixiRootContainer: Container;
94
+ }
95
+ /**
96
+ * Common options shared between both initialization modes
97
+ */
98
+ interface PixiBundleCommonOptions {
99
+ /** Optional custom root container (defaults to app.stage) */
100
+ rootContainer?: Container;
101
+ /** System group name (default: 'pixi-renderer') */
102
+ systemGroup?: string;
103
+ /** Priority for transform propagation system (default: 1000) */
104
+ transformPriority?: number;
105
+ /** Priority for render sync system (default: 500) */
106
+ renderSyncPriority?: number;
107
+ }
108
+ /**
109
+ * Options when providing a pre-initialized PixiJS Application
110
+ */
111
+ export interface PixiBundleAppOptions extends PixiBundleCommonOptions {
112
+ /** The PixiJS Application instance (already initialized) */
113
+ app: Application;
114
+ init?: never;
115
+ container?: never;
116
+ }
117
+ /**
118
+ * Options when letting the bundle create and manage the PixiJS Application
119
+ */
120
+ export interface PixiBundleManagedOptions extends PixiBundleCommonOptions {
121
+ app?: never;
122
+ /** PixiJS ApplicationOptions - bundle will create and initialize the Application */
123
+ init: Partial<ApplicationOptions>;
124
+ /** Container element to append the canvas to, or CSS selector string */
125
+ container?: HTMLElement | string;
126
+ }
127
+ /**
128
+ * Configuration options for the PixiJS bundle.
129
+ *
130
+ * Supports two modes:
131
+ * 1. **Pre-initialized**: Pass an already-initialized Application via `app`
132
+ * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`
133
+ *
134
+ * @example Pre-initialized mode (full control)
135
+ * ```typescript
136
+ * const app = new Application();
137
+ * await app.init({ resizeTo: window });
138
+ * const ecs = ECSpresso.create<...>()
139
+ * .withBundle(createPixiBundle({ app }))
140
+ * .build();
141
+ * ```
142
+ *
143
+ * @example Managed mode (convenience)
144
+ * ```typescript
145
+ * const ecs = ECSpresso.create<...>()
146
+ * .withBundle(createPixiBundle({
147
+ * init: { background: '#1099bb', resizeTo: window },
148
+ * container: document.body,
149
+ * }))
150
+ * .build();
151
+ * await ecs.initialize(); // Application created here
152
+ * ```
153
+ */
154
+ export type PixiBundleOptions = PixiBundleAppOptions | PixiBundleManagedOptions;
155
+ /**
156
+ * Default local transform values
157
+ */
158
+ export declare const DEFAULT_LOCAL_TRANSFORM: Readonly<LocalTransform>;
159
+ /**
160
+ * Default world transform values
161
+ */
162
+ export declare const DEFAULT_WORLD_TRANSFORM: Readonly<WorldTransform>;
163
+ interface PositionOption {
164
+ x?: number;
165
+ y?: number;
166
+ }
167
+ interface TransformOptions {
168
+ rotation?: number;
169
+ scale?: number | {
170
+ x: number;
171
+ y: number;
172
+ };
173
+ visible?: boolean;
174
+ alpha?: number;
175
+ }
176
+ /**
177
+ * Create components for a sprite entity.
178
+ * Returns an object suitable for spreading into spawn().
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * const player = ecs.spawn({
183
+ * ...createSpriteComponents(new Sprite(texture), { x: 100, y: 100 }),
184
+ * velocity: { x: 0, y: 0 },
185
+ * });
186
+ * ```
187
+ */
188
+ export declare function createSpriteComponents(sprite: Sprite, position?: PositionOption, options?: TransformOptions & {
189
+ anchor?: {
190
+ x: number;
191
+ y: number;
192
+ };
193
+ }): Pick<PixiComponentTypes, 'pixiSprite' | 'localTransform' | 'worldTransform' | 'pixiVisible'>;
194
+ /**
195
+ * Create components for a graphics entity.
196
+ * Returns an object suitable for spreading into spawn().
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * const rect = ecs.spawn({
201
+ * ...createGraphicsComponents(graphics, { x: 50, y: 50 }),
202
+ * });
203
+ * ```
204
+ */
205
+ export declare function createGraphicsComponents(graphics: Graphics, position?: PositionOption, options?: TransformOptions): Pick<PixiComponentTypes, 'pixiGraphics' | 'localTransform' | 'worldTransform' | 'pixiVisible'>;
206
+ /**
207
+ * Create components for a container entity.
208
+ * Returns an object suitable for spreading into spawn().
209
+ *
210
+ * @example
211
+ * ```typescript
212
+ * const group = ecs.spawn({
213
+ * ...createContainerComponents(new Container(), { x: 0, y: 0 }),
214
+ * });
215
+ * ```
216
+ */
217
+ export declare function createContainerComponents(container: Container, position?: PositionOption, options?: TransformOptions): Pick<PixiComponentTypes, 'pixiContainer' | 'localTransform' | 'worldTransform' | 'pixiVisible'>;
218
+ /**
219
+ * Create a PixiJS rendering bundle for ECSpresso.
220
+ *
221
+ * This bundle provides:
222
+ * - Transform propagation system (computes world transforms from hierarchy)
223
+ * - Render sync system (updates PixiJS objects from ECS components)
224
+ * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)
225
+ *
226
+ * @example Pre-initialized mode
227
+ * ```typescript
228
+ * const app = new Application();
229
+ * await app.init({ resizeTo: window });
230
+ *
231
+ * const ecs = ECSpresso.create<GameComponents, {}, {}>()
232
+ * .withBundle(createPixiBundle({ app }))
233
+ * .build();
234
+ * ```
235
+ *
236
+ * @example Managed mode
237
+ * ```typescript
238
+ * const ecs = ECSpresso.create<GameComponents, {}, {}>()
239
+ * .withBundle(createPixiBundle({
240
+ * init: { background: '#1099bb', resizeTo: window },
241
+ * container: document.body,
242
+ * }))
243
+ * .build();
244
+ * await ecs.initialize();
245
+ * ```
246
+ */
247
+ export declare function createPixiBundle(options: PixiBundleOptions): Bundle<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>;
248
+ export {};
@@ -0,0 +1,4 @@
1
+ var O=Object.create;var{getPrototypeOf:I,defineProperty:j,getOwnPropertyNames:T}=Object;var f=Object.prototype.hasOwnProperty;var u=(H,J,K)=>{K=H!=null?O(I(H)):{};let F=J||!H||!H.__esModule?j(K,"default",{value:H,enumerable:!0}):K;for(let R of T(H))if(!f.call(F,R))j(F,R,{get:()=>H[R],enumerable:!0});return F};var b=((H)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(H,{get:(J,K)=>(typeof require<"u"?require:J)[K]}):H)(function(H){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+H+'" is not supported')});class k{_label;_ecspresso;_bundle;queries={};processFunction;detachFunction;initializeFunction;eventHandlers;_priority=0;_isRegistered=!1;_groups=[];_inScreens;_excludeScreens;_requiredAssets;constructor(H,J=null,K=null){this._label=H;this._ecspresso=J;this._bundle=K}get label(){return this._label}get bundle(){return this._bundle}get ecspresso(){return this._ecspresso}_autoRegister(){if(this._isRegistered||!this._ecspresso)return;let H=this._buildSystemObject();G(H,this._ecspresso),this._isRegistered=!0}_buildSystemObject(){return this._createSystemObject()}_createSystemObject(){let H={label:this._label,entityQueries:this.queries,priority:this._priority};if(this.processFunction)H.process=this.processFunction;if(this.detachFunction)H.onDetach=this.detachFunction;if(this.initializeFunction)H.onInitialize=this.initializeFunction;if(this.eventHandlers)H.eventHandlers=this.eventHandlers;if(this._groups.length>0)H.groups=[...this._groups];if(this._inScreens)H.inScreens=this._inScreens;if(this._excludeScreens)H.excludeScreens=this._excludeScreens;if(this._requiredAssets)H.requiredAssets=this._requiredAssets;return H}setPriority(H){return this._priority=H,this}inGroup(H){if(!this._groups.includes(H))this._groups.push(H);return this}inScreens(H){return this._inScreens=[...H],this}excludeScreens(H){return this._excludeScreens=[...H],this}requiresAssets(H){return this._requiredAssets=[...H],this}addQuery(H,J){let K=this;return K.queries={...this.queries,[H]:J},K}setProcess(H){return this.processFunction=H,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(H){return this.detachFunction=H,this}setOnInitialize(H){return this.initializeFunction=H,this}setEventHandlers(H){return this.eventHandlers=H,this}build(H){let J=this._createSystemObject();if(this._ecspresso)G(J,this._ecspresso);if(H)G(J,H);return this}}function G(H,J){J._registerSystem(H)}function c(H,J){return new k(H,J)}function x(H,J){return new k(H,null,J)}function y(){return`bundle_${Date.now().toString(36)}_${Math.random().toString(36).substring(2,9)}`}class E{_systems=[];_resources=new Map;_assets=new Map;_assetGroups=new Map;_screens=new Map;_id;constructor(H){this._id=H||y()}get id(){return this._id}set id(H){this._id=H}addSystem(H){if(typeof H==="string"){let J=x(H,this);return this._systems.push(J),J}else return this._systems.push(H),H}addResource(H,J){return this._resources.set(H,J),this}addAsset(H,J,K){return this._assets.set(H,{loader:J,eager:K?.eager??!0,group:K?.group}),this}addAssetGroup(H,J){let K=new Map;for(let[F,R]of Object.entries(J))K.set(F,R),this._assets.set(F,{loader:R,eager:!1,group:H});return this._assetGroups.set(H,K),this}addScreen(H,J){return this._screens.set(H,J),this}getAssets(){return new Map(this._assets)}getScreens(){return new Map(this._screens)}_setResource(H,J){this._resources.set(H,J)}_setAsset(H,J){this._assets.set(H,J)}_setScreen(H,J){this._screens.set(H,J)}getSystems(){return this._systems.map((H)=>H.build())}registerSystemsWithEcspresso(H){for(let J of this._systems)J.build(H)}getResources(){return new Map(this._resources)}getResource(H){return this._resources.get(H)}getSystemBuilders(){return[...this._systems]}hasResource(H){return this._resources.has(H)}}function n(H,...J){if(J.length===0)return new E(H);let K=new E(H);for(let F of J){for(let R of F.getSystemBuilders())K.addSystem(R);for(let[R,Y]of F.getResources().entries())K._setResource(R,Y);for(let[R,Y]of F.getAssets().entries())K._setAsset(R,Y);for(let[R,Y]of F.getScreens().entries())K._setScreen(R,Y)}return K}async function m(H){let{Application:J}=await import("pixi.js"),K=new J;return await K.init(H),K}var t={x:0,y:0,rotation:0,scaleX:1,scaleY:1},o={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function L(H,J){let K=J?.scale,F=typeof K==="number"?K:K?.x??1,R=typeof K==="number"?K:K?.y??1;return{x:H?.x??0,y:H?.y??0,rotation:J?.rotation??0,scaleX:F,scaleY:R}}function V(H,J){let K=J?.scale,F=typeof K==="number"?K:K?.x??1,R=typeof K==="number"?K:K?.y??1;return{x:H?.x??0,y:H?.y??0,rotation:J?.rotation??0,scaleX:F,scaleY:R}}function B(H){return{visible:H?.visible??!0,alpha:H?.alpha}}function e(H,J,K){return{pixiSprite:{sprite:H,anchor:K?.anchor},localTransform:L(J,K),worldTransform:V(J,K),pixiVisible:B(K)}}function HH(H,J,K){return{pixiGraphics:{graphics:H},localTransform:L(J,K),worldTransform:V(J,K),pixiVisible:B(K)}}function JH(H,J,K){return{pixiContainer:{container:H},localTransform:L(J,K),worldTransform:V(J,K),pixiVisible:B(K)}}function KH(H){let{rootContainer:J,systemGroup:K="pixi-renderer",transformPriority:F=1000,renderSyncPriority:R=500}=H,Y=new E("pixi-renderer");if("init"in H&&H.init!==void 0){let{init:_,container:Q}=H;Y.addResource("pixiApp",async()=>{let U=await m(_);if(Q){let D=typeof Q==="string"?document.querySelector(Q):Q;if(D)D.appendChild(U.canvas);else if(typeof Q==="string")console.warn(`PixiJS bundle: container selector "${Q}" not found`)}return U}),Y.addResource("pixiRootContainer",{dependsOn:["pixiApp"],factory:(U)=>J??U.getResource("pixiApp").stage})}else{let _=H.app;Y.addResource("pixiApp",_),Y.addResource("pixiRootContainer",J??_.stage)}let q=new Map;function W(_,Q){let U=q.get(_);if(U)return U;let D=Q.entityManager.getComponent(_,"pixiSprite");if(D)return q.set(_,D.sprite),D.sprite;let z=Q.entityManager.getComponent(_,"pixiGraphics");if(z)return q.set(_,z.graphics),z.graphics;let $=Q.entityManager.getComponent(_,"pixiContainer");if($)return q.set(_,$.container),$.container;return null}function A(_,Q,U){let D=U.getResource("pixiRootContainer"),z=U.getParent(_),M=(z!==null?W(z,U):null)??D;if(Q.parent!==M)M.addChild(Q)}function N(_){let Q=q.get(_);if(Q)Q.removeFromParent(),q.delete(_)}function g(_,Q){let U=q.get(_);if(!U)return;let D=Q.getResource("pixiRootContainer"),z=Q.getParent(_),M=(z!==null?W(z,Q):null)??D;if(U.parent!==M)U.removeFromParent(),M.addChild(U)}return Y.addSystem("pixi-transform-propagation").setPriority(F).inGroup(K).setProcess((_,Q,U)=>{U.forEachInHierarchy((z,$)=>{let M=U.entityManager.getComponent(z,"localTransform"),Z=U.entityManager.getComponent(z,"worldTransform");if(!M||!Z)return;if($===null)Z.x=M.x,Z.y=M.y,Z.rotation=M.rotation,Z.scaleX=M.scaleX,Z.scaleY=M.scaleY;else{let X=U.entityManager.getComponent($,"worldTransform");if(X){let P=M.x*X.scaleX,v=M.y*X.scaleY,S=Math.cos(X.rotation),C=Math.sin(X.rotation),w=P*S-v*C,h=P*C+v*S;Z.x=X.x+w,Z.y=X.y+h,Z.rotation=X.rotation+M.rotation,Z.scaleX=X.scaleX*M.scaleX,Z.scaleY=X.scaleY*M.scaleY}else Z.x=M.x,Z.y=M.y,Z.rotation=M.rotation,Z.scaleX=M.scaleX,Z.scaleY=M.scaleY}});let D=U.getEntitiesWithQuery(["localTransform","worldTransform"]);for(let z of D)if(U.getParent(z.id)===null&&U.getChildren(z.id).length===0){let{localTransform:M,worldTransform:Z}=z.components;Z.x=M.x,Z.y=M.y,Z.rotation=M.rotation,Z.scaleX=M.scaleX,Z.scaleY=M.scaleY}}).and(),Y.addSystem("pixi-render-sync").setPriority(R).inGroup(K).addQuery("sprites",{with:["pixiSprite","worldTransform"]}).addQuery("graphics",{with:["pixiGraphics","worldTransform"]}).addQuery("containers",{with:["pixiContainer","worldTransform"]}).setProcess((_,Q,U)=>{for(let D of _.sprites){let{pixiSprite:z,worldTransform:$}=D.components,{sprite:M,anchor:Z}=z;if(M.position.set($.x,$.y),M.rotation=$.rotation,M.scale.set($.scaleX,$.scaleY),Z)M.anchor.set(Z.x,Z.y);let X=U.entityManager.getComponent(D.id,"pixiVisible");if(X){if(M.visible=X.visible,X.alpha!==void 0)M.alpha=X.alpha}}for(let D of _.graphics){let{pixiGraphics:z,worldTransform:$}=D.components,{graphics:M}=z;M.position.set($.x,$.y),M.rotation=$.rotation,M.scale.set($.scaleX,$.scaleY);let Z=U.entityManager.getComponent(D.id,"pixiVisible");if(Z){if(M.visible=Z.visible,Z.alpha!==void 0)M.alpha=Z.alpha}}for(let D of _.containers){let{pixiContainer:z,worldTransform:$}=D.components,{container:M}=z;M.position.set($.x,$.y),M.rotation=$.rotation,M.scale.set($.scaleX,$.scaleY);let Z=U.entityManager.getComponent(D.id,"pixiVisible");if(Z){if(M.visible=Z.visible,Z.alpha!==void 0)M.alpha=Z.alpha}}}).and(),Y.addSystem("pixi-scene-graph-manager").setPriority(9999).inGroup(K).setOnInitialize((_)=>{_.addReactiveQuery("pixi-sprites",{with:["pixiSprite"],onEnter:(Q)=>{let U=Q.components.pixiSprite.sprite;q.set(Q.id,U),A(Q.id,U,_)},onExit:(Q)=>{N(Q)}}),_.addReactiveQuery("pixi-graphics",{with:["pixiGraphics"],onEnter:(Q)=>{let U=Q.components.pixiGraphics.graphics;q.set(Q.id,U),A(Q.id,U,_)},onExit:(Q)=>{N(Q)}}),_.addReactiveQuery("pixi-containers",{with:["pixiContainer"],onEnter:(Q)=>{let U=Q.components.pixiContainer.container;q.set(Q.id,U),A(Q.id,U,_)},onExit:(Q)=>{N(Q)}}),_.on("hierarchyChanged",({entityId:Q})=>{g(Q,_)})}).and(),Y}export{e as createSpriteComponents,KH as createPixiBundle,HH as createGraphicsComponents,JH as createContainerComponents,o as DEFAULT_WORLD_TRANSFORM,t as DEFAULT_LOCAL_TRANSFORM};
2
+
3
+ //# debugId=3807489A54FF20AF64756E2164756E21
4
+ //# sourceMappingURL=pixi.js.map