ecspresso 0.6.0 → 0.7.1

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
@@ -18,6 +18,8 @@ A type-safe, modular, and extensible Entity Component System (ECS) framework for
18
18
  - **Reactive Queries**: Enter/exit callbacks when entities match or unmatch queries
19
19
  - **System Groups**: Enable/disable groups of systems at runtime
20
20
  - **Component Lifecycle**: Callbacks for component add/remove with unsubscribe support
21
+ - **Command Buffer**: Deferred structural changes for safe entity/component operations during systems
22
+ - **Timer Bundle**: ECS-native timers with event-based completion notifications
21
23
 
22
24
  ## Installation
23
25
 
@@ -967,6 +969,191 @@ const removed = world.removeReactiveQuery('enemies'); // returns true if existed
967
969
 
968
970
  **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
971
 
972
+ ## Command Buffer
973
+
974
+ The command buffer allows you to queue structural changes (entity creation, removal, component changes) that execute at the end of the update cycle. This prevents issues when modifying entities during system iteration.
975
+
976
+ ```typescript
977
+ // Queue commands during system execution
978
+ world.addSystem('combat')
979
+ .addQuery('enemies', { with: ['enemy', 'health'] })
980
+ .setProcess((queries, dt, ecs) => {
981
+ for (const entity of queries.enemies) {
982
+ if (entity.components.health.value <= 0) {
983
+ // Queue removal - doesn't execute immediately
984
+ ecs.commands.removeEntity(entity.id);
985
+
986
+ // Queue spawning an explosion
987
+ ecs.commands.spawn({
988
+ position: entity.components.position,
989
+ explosion: true,
990
+ });
991
+ }
992
+ }
993
+ // Commands execute automatically at end of update()
994
+ })
995
+ .build();
996
+ ```
997
+
998
+ ### Available Commands
999
+
1000
+ ```typescript
1001
+ // Entity operations
1002
+ ecs.commands.spawn({ position: { x: 0, y: 0 } });
1003
+ ecs.commands.spawnChild(parentId, { position: { x: 10, y: 0 } });
1004
+ ecs.commands.removeEntity(entityId);
1005
+ ecs.commands.removeEntity(entityId, { cascade: false }); // Orphan children
1006
+
1007
+ // Component operations
1008
+ ecs.commands.addComponent(entityId, 'velocity', { x: 5, y: 0 });
1009
+ ecs.commands.addComponents(entityId, { velocity: { x: 5, y: 0 }, health: { value: 100 } });
1010
+ ecs.commands.removeComponent(entityId, 'velocity');
1011
+
1012
+ // Hierarchy operations
1013
+ ecs.commands.setParent(childId, parentId);
1014
+ ecs.commands.removeParent(childId);
1015
+
1016
+ // Utility
1017
+ ecs.commands.length; // Number of queued commands
1018
+ ecs.commands.clear(); // Discard all queued commands
1019
+ ```
1020
+
1021
+ Commands execute in FIFO order. If a command fails (e.g., entity doesn't exist), it logs a warning and continues with remaining commands.
1022
+
1023
+ ## Built-in Bundles
1024
+
1025
+ ECSpresso provides optional utility bundles for common game development needs:
1026
+
1027
+ | Bundle | Import | Description |
1028
+ |--------|--------|-------------|
1029
+ | **Transform** | `ecspresso/bundles/utils/transform` | Hierarchical transform propagation (local/world transforms) |
1030
+ | **Movement** | `ecspresso/bundles/utils/movement` | Velocity-based movement integration |
1031
+ | **Bounds** | `ecspresso/bundles/utils/bounds` | Screen bounds enforcement (destroy, clamp, wrap) |
1032
+ | **Collision** | `ecspresso/bundles/utils/collision` | Layer-based AABB/circle collision detection with events |
1033
+ | **Timers** | `ecspresso/bundles/utils/timers` | ECS-native timers with event-based completion |
1034
+ | **PixiJS Renderer** | `ecspresso/bundles/renderers/pixi` | Automated PixiJS scene graph wiring |
1035
+
1036
+ ## Timer Bundle
1037
+
1038
+ The timer bundle provides ECS-native timers that follow the "data, not callbacks" philosophy. Timers are components processed each frame, with optional event-based completion notifications.
1039
+
1040
+ ```typescript
1041
+ import {
1042
+ createTimerBundle,
1043
+ createTimer,
1044
+ createRepeatingTimer,
1045
+ type TimerComponentTypes,
1046
+ type TimerEventData
1047
+ } from 'ecspresso/bundles/utils/timers';
1048
+
1049
+ // Define events that use TimerEventData payload
1050
+ interface Events {
1051
+ hideMessage: TimerEventData;
1052
+ spawnWave: TimerEventData;
1053
+ }
1054
+
1055
+ // Extend components with timer support
1056
+ interface Components extends TimerComponentTypes<Events> {
1057
+ position: { x: number; y: number };
1058
+ messageDisplay: true;
1059
+ spawner: true;
1060
+ }
1061
+
1062
+ // Create world with timer bundle
1063
+ const world = ECSpresso
1064
+ .create<Components, Events, Resources>()
1065
+ .withBundle(createTimerBundle<Events>())
1066
+ .build();
1067
+
1068
+ // One-shot timer without event (poll justFinished)
1069
+ world.spawn({
1070
+ ...createTimer<Events>(2.0),
1071
+ messageDisplay: true,
1072
+ });
1073
+
1074
+ // One-shot timer with completion event
1075
+ world.spawn({
1076
+ ...createTimer<Events>(1.5, { onComplete: 'hideMessage' }),
1077
+ messageDisplay: true,
1078
+ });
1079
+
1080
+ // Repeating timer with event
1081
+ world.spawn({
1082
+ ...createRepeatingTimer<Events>(5.0, { onComplete: 'spawnWave' }),
1083
+ spawner: true,
1084
+ });
1085
+
1086
+ // Subscribe to timer events
1087
+ world.on('hideMessage', (data) => {
1088
+ console.log(`Timer on entity ${data.entityId} completed after ${data.elapsed}s`);
1089
+ });
1090
+ ```
1091
+
1092
+ ### Timer Event Data
1093
+
1094
+ Events used with timer `onComplete` must have `TimerEventData` as their payload type:
1095
+
1096
+ ```typescript
1097
+ interface TimerEventData {
1098
+ entityId: number; // Entity the timer belongs to
1099
+ duration: number; // Timer's configured duration
1100
+ elapsed: number; // Actual elapsed time (may exceed duration slightly)
1101
+ }
1102
+ ```
1103
+
1104
+ ### Polling vs Events
1105
+
1106
+ You can use timers in two ways:
1107
+
1108
+ ```typescript
1109
+ // 1. Polling with justFinished flag
1110
+ world.addSystem('timerPolling')
1111
+ .addQuery('timers', { with: ['timer', 'messageDisplay'] })
1112
+ .setProcess((queries) => {
1113
+ for (const entity of queries.timers) {
1114
+ if (entity.components.timer.justFinished) {
1115
+ // Timer just completed this frame
1116
+ hideMessage(entity.id);
1117
+ }
1118
+ }
1119
+ })
1120
+ .build();
1121
+
1122
+ // 2. Event-based (decoupled)
1123
+ world.addSystem('timerEvents')
1124
+ .setEventHandlers({
1125
+ hideMessage: {
1126
+ handler: (data, ecs) => {
1127
+ // React to timer completion
1128
+ ecs.commands.removeEntity(data.entityId);
1129
+ }
1130
+ }
1131
+ })
1132
+ .build();
1133
+ ```
1134
+
1135
+ ### Timer Properties
1136
+
1137
+ ```typescript
1138
+ interface Timer {
1139
+ elapsed: number; // Time accumulated (seconds)
1140
+ duration: number; // Target duration (seconds)
1141
+ repeat: boolean; // Whether timer repeats
1142
+ active: boolean; // Whether timer is running
1143
+ justFinished: boolean; // True for one frame after completion
1144
+ onComplete?: string; // Event name to publish (optional)
1145
+ }
1146
+
1147
+ // Control timer at runtime
1148
+ const entity = world.spawn({ ...createTimer<Events>(5.0), myTimer: true });
1149
+ const timer = entity.components.timer;
1150
+
1151
+ timer.active = false; // Pause
1152
+ timer.active = true; // Resume
1153
+ timer.elapsed = 0; // Reset
1154
+ timer.duration = 10; // Change duration
1155
+ ```
1156
+
970
1157
  ## Error Handling
971
1158
 
972
1159
  ECSpresso provides clear, contextual error messages for common issues:
package/dist/bundle.d.ts CHANGED
@@ -2,6 +2,7 @@ import { SystemBuilderWithBundle } from './system-builder';
2
2
  import type ECSpresso from './ecspresso';
3
3
  import type { AssetDefinition } from './asset-types';
4
4
  import type { ScreenDefinition } from './screen-types';
5
+ import type { BundlesAreCompatible } from './type-utils';
5
6
  /**
6
7
  * Bundle class that encapsulates a set of components, resources, events, and systems
7
8
  * that can be merged into a ECSpresso instance
@@ -115,28 +116,8 @@ export default class Bundle<ComponentTypes extends Record<string, any> = {}, Eve
115
116
  */
116
117
  hasResource<K extends keyof ResourceTypes>(key: K): boolean;
117
118
  }
118
- /**
119
- * Utility type to check if two types are exactly the same
120
- */
121
- type Exactly<T, U> = T extends U ? U extends T ? true : false : false;
122
- /**
123
- * Simplified type constraint for bundle compatibility
124
- * Ensures that overlapping keys have exactly the same types
125
- */
126
- type CompatibleBundles<C1 extends Record<string, any>, C2 extends Record<string, any>, E1 extends Record<string, any>, E2 extends Record<string, any>, R1 extends Record<string, any>, R2 extends Record<string, any>, A1 extends Record<string, unknown> = {}, A2 extends Record<string, unknown> = {}, S1 extends Record<string, ScreenDefinition<any, any>> = {}, S2 extends Record<string, ScreenDefinition<any, any>> = {}> = {
127
- [K in keyof C1 & keyof C2]: Exactly<C1[K], C2[K]> extends true ? C1[K] : never;
128
- } & {
129
- [K in keyof E1 & keyof E2]: Exactly<E1[K], E2[K]> extends true ? E1[K] : never;
130
- } & {
131
- [K in keyof R1 & keyof R2]: Exactly<R1[K], R2[K]> extends true ? R1[K] : never;
132
- } & {
133
- [K in keyof A1 & keyof A2]: Exactly<A1[K], A2[K]> extends true ? A1[K] : never;
134
- } & {
135
- [K in keyof S1 & keyof S2]: Exactly<S1[K], S2[K]> extends true ? S1[K] : never;
136
- };
137
119
  /**
138
120
  * Function that merges multiple bundles into a single bundle
139
121
  */
140
- export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, A1 extends Record<string, unknown>, S1 extends Record<string, ScreenDefinition<any, any>>, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>, A2 extends Record<string, unknown>, S2 extends Record<string, ScreenDefinition<any, any>>>(id: string, bundle1: Bundle<C1, E1, R1, A1, S1>, bundle2: Bundle<C2, E2, R2, A2, S2> & CompatibleBundles<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2>): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2>;
122
+ export declare function mergeBundles<C1 extends Record<string, any>, E1 extends Record<string, any>, R1 extends Record<string, any>, A1 extends Record<string, unknown>, S1 extends Record<string, ScreenDefinition<any, any>>, C2 extends Record<string, any>, E2 extends Record<string, any>, R2 extends Record<string, any>, A2 extends Record<string, unknown>, S2 extends Record<string, ScreenDefinition<any, any>>>(id: string, bundle1: Bundle<C1, E1, R1, A1, S1>, bundle2: BundlesAreCompatible<C1, C2, E1, E2, R1, R2, A1, A2, S1, S2> extends true ? Bundle<C2, E2, R2, A2, S2> : never): Bundle<C1 & C2, E1 & E2, R1 & R2, A1 & A2, S1 & S2>;
141
123
  export declare function mergeBundles<ComponentTypes extends Record<string, any>, EventTypes extends Record<string, any>, ResourceTypes extends Record<string, any>, AssetTypes extends Record<string, unknown>, ScreenStates extends Record<string, ScreenDefinition<any, any>>>(id: string, ...bundles: Array<Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>>): Bundle<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>;
142
- export {};
@@ -3,29 +3,14 @@
3
3
  *
4
4
  * An opt-in PixiJS rendering bundle that automates scene graph wiring.
5
5
  * Import from 'ecspresso/bundles/renderers/pixi'
6
+ *
7
+ * This bundle includes transform propagation automatically.
6
8
  */
7
9
  import type { Application, ApplicationOptions, Container, Sprite, Graphics } from 'pixi.js';
8
10
  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
- }
11
+ import { type LocalTransform, type WorldTransform, type TransformComponentTypes, type TransformBundleOptions } from '../utils/transform';
12
+ export type { LocalTransform, WorldTransform, TransformComponentTypes };
13
+ export { createTransform, createLocalTransform, createWorldTransform } from '../utils/transform';
29
14
  /**
30
15
  * PixiJS Sprite component
31
16
  */
@@ -67,9 +52,7 @@ export interface PixiVisible {
67
52
  * }
68
53
  * ```
69
54
  */
70
- export interface PixiComponentTypes {
71
- localTransform: LocalTransform;
72
- worldTransform: WorldTransform;
55
+ export interface PixiComponentTypes extends TransformComponentTypes {
73
56
  pixiSprite: PixiSprite;
74
57
  pixiGraphics: PixiGraphics;
75
58
  pixiContainer: PixiContainer;
@@ -100,10 +83,10 @@ interface PixiBundleCommonOptions {
100
83
  rootContainer?: Container;
101
84
  /** System group name (default: 'pixi-renderer') */
102
85
  systemGroup?: string;
103
- /** Priority for transform propagation system (default: 1000) */
104
- transformPriority?: number;
105
86
  /** Priority for render sync system (default: 500) */
106
87
  renderSyncPriority?: number;
88
+ /** Options for the included transform bundle */
89
+ transform?: TransformBundleOptions;
107
90
  }
108
91
  /**
109
92
  * Options when providing a pre-initialized PixiJS Application
@@ -131,18 +114,20 @@ export interface PixiBundleManagedOptions extends PixiBundleCommonOptions {
131
114
  * 1. **Pre-initialized**: Pass an already-initialized Application via `app`
132
115
  * 2. **Managed**: Pass `init` options and the bundle creates the Application during `ecs.initialize()`
133
116
  *
117
+ * This bundle includes transform propagation automatically - no need to add createTransformBundle() separately.
118
+ *
134
119
  * @example Pre-initialized mode (full control)
135
120
  * ```typescript
136
121
  * const app = new Application();
137
122
  * await app.init({ resizeTo: window });
138
- * const ecs = ECSpresso.create<...>()
123
+ * const ecs = ECSpresso.create<GameComponents, {}, {}>()
139
124
  * .withBundle(createPixiBundle({ app }))
140
125
  * .build();
141
126
  * ```
142
127
  *
143
128
  * @example Managed mode (convenience)
144
129
  * ```typescript
145
- * const ecs = ECSpresso.create<...>()
130
+ * const ecs = ECSpresso.create<GameComponents, {}, {}>()
146
131
  * .withBundle(createPixiBundle({
147
132
  * init: { background: '#1099bb', resizeTo: window },
148
133
  * container: document.body,
@@ -219,7 +204,7 @@ export declare function createContainerComponents(container: Container, position
219
204
  * Create a PixiJS rendering bundle for ECSpresso.
220
205
  *
221
206
  * This bundle provides:
222
- * - Transform propagation system (computes world transforms from hierarchy)
207
+ * - Transform propagation (localTransform worldTransform)
223
208
  * - Render sync system (updates PixiJS objects from ECS components)
224
209
  * - Scene graph management (mirrors ECS hierarchy in PixiJS scene graph)
225
210
  *
@@ -245,4 +230,3 @@ export declare function createContainerComponents(container: Container, position
245
230
  * ```
246
231
  */
247
232
  export declare function createPixiBundle(options: PixiBundleOptions): Bundle<PixiComponentTypes, PixiEventTypes, PixiResourceTypes>;
248
- export {};
@@ -1,4 +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};
1
+ var O=Object.create;var{getPrototypeOf:x,defineProperty:C,getOwnPropertyNames:T}=Object;var I=Object.prototype.hasOwnProperty;var f=(H,J,K)=>{K=H!=null?O(x(H)):{};let _=J||!H||!H.__esModule?C(K,"default",{value:H,enumerable:!0}):K;for(let U of T(H))if(!I.call(_,U))C(_,U,{get:()=>H[U],enumerable:!0});return _};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 L{_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();k(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)k(J,this._ecspresso);if(H)k(J,H);return this}}function k(H,J){J._registerSystem(H)}function r(H,J){return new L(H,J)}function B(H,J){return new L(H,null,J)}function u(){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||u()}get id(){return this._id}set id(H){this._id=H}addSystem(H){if(typeof H==="string"){let J=B(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[_,U]of Object.entries(J))K.set(_,U),this._assets.set(_,{loader:U,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 j(H,...J){if(J.length===0)return new E(H);let K=new E(H);for(let _ of J){for(let U of _.getSystemBuilders())K.addSystem(U);for(let[U,M]of _.getResources().entries())K._setResource(U,M);for(let[U,M]of _.getAssets().entries())K._setAsset(U,M);for(let[U,M]of _.getScreens().entries())K._setScreen(U,M)}return K}function m(H,J){return{localTransform:{x:H,y:J,rotation:0,scaleX:1,scaleY:1}}}function y(H,J){return{worldTransform:{x:H,y:J,rotation:0,scaleX:1,scaleY:1}}}function d(H,J,K){let _=K?.scale??K?.scaleX??1,U=K?.scale??K?.scaleY??1,M=K?.rotation??0,Y={x:H,y:J,rotation:M,scaleX:_,scaleY:U};return{localTransform:{...Y},worldTransform:{...Y}}}function w(H){let{systemGroup:J="transform",priority:K=500}=H??{},_=new E("transform");return _.addSystem("transform-propagation").setPriority(K).inGroup(J).setProcess((U,M,Y)=>{l(Y)}).and(),_}function l(H){H.forEachInHierarchy((K,_)=>{let U=H.entityManager.getComponent(K,"localTransform"),M=H.entityManager.getComponent(K,"worldTransform");if(!U||!M)return;if(_===null)V(U,M);else{let Y=H.entityManager.getComponent(_,"worldTransform");if(Y)c(Y,U,M);else V(U,M)}});let J=H.getEntitiesWithQuery(["localTransform","worldTransform"]);for(let K of J)if(H.getParent(K.id)===null&&H.getChildren(K.id).length===0){let{localTransform:U,worldTransform:M}=K.components;V(U,M)}}function V(H,J){J.x=H.x,J.y=H.y,J.rotation=H.rotation,J.scaleX=H.scaleX,J.scaleY=H.scaleY}function c(H,J,K){let _=J.x*H.scaleX,U=J.y*H.scaleY,M=Math.cos(H.rotation),Y=Math.sin(H.rotation),X=_*M-U*Y,N=_*Y+U*M;K.x=H.x+X,K.y=H.y+N,K.rotation=H.rotation+J.rotation,K.scaleX=H.scaleX*J.scaleX,K.scaleY=H.scaleY*J.scaleY}async function p(H){let{Application:J}=await import("pixi.js"),K=new J;return await K.init(H),K}var KH={x:0,y:0,rotation:0,scaleX:1,scaleY:1},QH={x:0,y:0,rotation:0,scaleX:1,scaleY:1};function P(H,J){let K=J?.scale,_=typeof K==="number"?K:K?.x??1,U=typeof K==="number"?K:K?.y??1;return{x:H?.x??0,y:H?.y??0,rotation:J?.rotation??0,scaleX:_,scaleY:U}}function v(H,J){let K=J?.scale,_=typeof K==="number"?K:K?.x??1,U=typeof K==="number"?K:K?.y??1;return{x:H?.x??0,y:H?.y??0,rotation:J?.rotation??0,scaleX:_,scaleY:U}}function S(H){return{visible:H?.visible??!0,alpha:H?.alpha}}function UH(H,J,K){return{pixiSprite:{sprite:H,anchor:K?.anchor},localTransform:P(J,K),worldTransform:v(J,K),pixiVisible:S(K)}}function ZH(H,J,K){return{pixiGraphics:{graphics:H},localTransform:P(J,K),worldTransform:v(J,K),pixiVisible:S(K)}}function _H(H,J,K){return{pixiContainer:{container:H},localTransform:P(J,K),worldTransform:v(J,K),pixiVisible:S(K)}}function $H(H){let{rootContainer:J,systemGroup:K="pixi-renderer",renderSyncPriority:_=500,transform:U}=H,M=new E("pixi-renderer-internal");if("init"in H&&H.init!==void 0){let{init:Z,container:Q}=H;M.addResource("pixiApp",async()=>{let $=await p(Z);if(Q){let F=typeof Q==="string"?document.querySelector(Q):Q;if(F)F.appendChild($.canvas);else if(typeof Q==="string")console.warn(`PixiJS bundle: container selector "${Q}" not found`)}return $}),M.addResource("pixiRootContainer",{dependsOn:["pixiApp"],factory:($)=>J??$.getResource("pixiApp").stage})}else{let Z=H.app;M.addResource("pixiApp",Z),M.addResource("pixiRootContainer",J??Z.stage)}let X=new Map;function N(Z,Q){let $=X.get(Z);if($)return $;let F=Q.entityManager.getComponent(Z,"pixiSprite");if(F)return X.set(Z,F.sprite),F.sprite;let R=Q.entityManager.getComponent(Z,"pixiGraphics");if(R)return X.set(Z,R.graphics),R.graphics;let D=Q.entityManager.getComponent(Z,"pixiContainer");if(D)return X.set(Z,D.container),D.container;return null}function A(Z,Q,$){let F=$.getResource("pixiRootContainer"),R=$.getParent(Z),z=(R!==null?N(R,$):null)??F;if(Q.parent!==z)z.addChild(Q)}function G(Z){let Q=X.get(Z);if(Q)Q.removeFromParent(),X.delete(Z)}function g(Z,Q){let $=X.get(Z);if(!$)return;let F=Q.getResource("pixiRootContainer"),R=Q.getParent(Z),z=(R!==null?N(R,Q):null)??F;if($.parent!==z)$.removeFromParent(),z.addChild($)}M.addSystem("pixi-render-sync").setPriority(_).inGroup(K).addQuery("sprites",{with:["pixiSprite","worldTransform"]}).addQuery("graphics",{with:["pixiGraphics","worldTransform"]}).addQuery("containers",{with:["pixiContainer","worldTransform"]}).setProcess((Z,Q,$)=>{for(let F of Z.sprites){let{pixiSprite:R,worldTransform:D}=F.components,{sprite:z,anchor:q}=R;if(z.position.set(D.x,D.y),z.rotation=D.rotation,z.scale.set(D.scaleX,D.scaleY),q)z.anchor.set(q.x,q.y);let W=$.entityManager.getComponent(F.id,"pixiVisible");if(W){if(z.visible=W.visible,W.alpha!==void 0)z.alpha=W.alpha}}for(let F of Z.graphics){let{pixiGraphics:R,worldTransform:D}=F.components,{graphics:z}=R;z.position.set(D.x,D.y),z.rotation=D.rotation,z.scale.set(D.scaleX,D.scaleY);let q=$.entityManager.getComponent(F.id,"pixiVisible");if(q){if(z.visible=q.visible,q.alpha!==void 0)z.alpha=q.alpha}}for(let F of Z.containers){let{pixiContainer:R,worldTransform:D}=F.components,{container:z}=R;z.position.set(D.x,D.y),z.rotation=D.rotation,z.scale.set(D.scaleX,D.scaleY);let q=$.entityManager.getComponent(F.id,"pixiVisible");if(q){if(z.visible=q.visible,q.alpha!==void 0)z.alpha=q.alpha}}}).and(),M.addSystem("pixi-scene-graph-manager").setPriority(9999).inGroup(K).setOnInitialize((Z)=>{Z.addReactiveQuery("pixi-sprites",{with:["pixiSprite"],onEnter:(Q)=>{let $=Q.components.pixiSprite.sprite;X.set(Q.id,$),A(Q.id,$,Z)},onExit:(Q)=>{G(Q)}}),Z.addReactiveQuery("pixi-graphics",{with:["pixiGraphics"],onEnter:(Q)=>{let $=Q.components.pixiGraphics.graphics;X.set(Q.id,$),A(Q.id,$,Z)},onExit:(Q)=>{G(Q)}}),Z.addReactiveQuery("pixi-containers",{with:["pixiContainer"],onEnter:(Q)=>{let $=Q.components.pixiContainer.container;X.set(Q.id,$),A(Q.id,$,Z)},onExit:(Q)=>{G(Q)}}),Z.on("hierarchyChanged",({entityId:Q})=>{g(Q,Z)})}).and();let h=w(U);return j("pixi-renderer",h,M)}export{y as createWorldTransform,d as createTransform,UH as createSpriteComponents,$H as createPixiBundle,m as createLocalTransform,ZH as createGraphicsComponents,_H as createContainerComponents,QH as DEFAULT_WORLD_TRANSFORM,KH as DEFAULT_LOCAL_TRANSFORM};
2
2
 
3
- //# debugId=3807489A54FF20AF64756E2164756E21
3
+ //# debugId=FD4EB6F5A72B658164756E2164756E21
4
4
  //# sourceMappingURL=pixi.js.map