ecspresso 0.11.0 → 0.12.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.
Files changed (96) hide show
  1. package/README.md +200 -148
  2. package/dist/asset-manager.d.ts +1 -1
  3. package/dist/asset-types.d.ts +2 -2
  4. package/dist/command-buffer.d.ts +34 -24
  5. package/dist/ecspresso-builder.d.ts +100 -72
  6. package/dist/ecspresso.d.ts +257 -122
  7. package/dist/entity-manager.d.ts +57 -47
  8. package/dist/index.d.ts +5 -4
  9. package/dist/plugin.d.ts +61 -0
  10. package/dist/{bundles → plugins}/audio.d.ts +27 -47
  11. package/dist/{bundles → plugins}/bounds.d.ts +17 -25
  12. package/dist/{bundles → plugins}/camera.d.ts +8 -9
  13. package/dist/{bundles → plugins}/collision.d.ts +22 -26
  14. package/dist/plugins/coroutine.d.ts +126 -0
  15. package/dist/{bundles → plugins}/diagnostics.d.ts +5 -4
  16. package/dist/{bundles → plugins}/input.d.ts +9 -15
  17. package/dist/plugins/particles.d.ts +225 -0
  18. package/dist/{bundles → plugins}/physics2D.d.ts +27 -23
  19. package/dist/{bundles → plugins}/renderers/renderer2D.d.ts +40 -39
  20. package/dist/{bundles → plugins}/spatial-index.d.ts +11 -10
  21. package/dist/plugins/sprite-animation.d.ts +150 -0
  22. package/dist/{bundles → plugins}/state-machine.d.ts +50 -104
  23. package/dist/plugins/timers.d.ts +151 -0
  24. package/dist/{bundles → plugins}/transform.d.ts +18 -19
  25. package/dist/{bundles → plugins}/tween.d.ts +36 -71
  26. package/dist/resource-manager.d.ts +32 -7
  27. package/dist/screen-manager.d.ts +17 -11
  28. package/dist/screen-types.d.ts +5 -2
  29. package/dist/src/index.js +2 -2
  30. package/dist/src/index.js.map +17 -17
  31. package/dist/src/plugins/audio.js +4 -0
  32. package/dist/src/plugins/audio.js.map +10 -0
  33. package/dist/src/plugins/bounds.js +4 -0
  34. package/dist/src/plugins/bounds.js.map +10 -0
  35. package/dist/src/plugins/camera.js +4 -0
  36. package/dist/src/plugins/camera.js.map +10 -0
  37. package/dist/src/plugins/collision.js +4 -0
  38. package/dist/src/plugins/collision.js.map +11 -0
  39. package/dist/src/plugins/coroutine.js +4 -0
  40. package/dist/src/plugins/coroutine.js.map +10 -0
  41. package/dist/src/plugins/diagnostics.js +5 -0
  42. package/dist/src/plugins/diagnostics.js.map +10 -0
  43. package/dist/src/plugins/input.js +4 -0
  44. package/dist/src/plugins/input.js.map +10 -0
  45. package/dist/src/plugins/particles.js +4 -0
  46. package/dist/src/plugins/particles.js.map +10 -0
  47. package/dist/src/plugins/physics2D.js +4 -0
  48. package/dist/src/plugins/physics2D.js.map +11 -0
  49. package/dist/src/plugins/renderers/renderer2D.js +4 -0
  50. package/dist/src/plugins/renderers/renderer2D.js.map +10 -0
  51. package/dist/src/plugins/spatial-index.js +4 -0
  52. package/dist/src/plugins/spatial-index.js.map +11 -0
  53. package/dist/src/plugins/sprite-animation.js +4 -0
  54. package/dist/src/plugins/sprite-animation.js.map +10 -0
  55. package/dist/src/plugins/state-machine.js +4 -0
  56. package/dist/src/plugins/state-machine.js.map +10 -0
  57. package/dist/src/plugins/timers.js +4 -0
  58. package/dist/src/plugins/timers.js.map +10 -0
  59. package/dist/src/plugins/transform.js +4 -0
  60. package/dist/src/plugins/transform.js.map +10 -0
  61. package/dist/src/plugins/tween.js +4 -0
  62. package/dist/src/plugins/tween.js.map +11 -0
  63. package/dist/system-builder.d.ts +66 -97
  64. package/dist/type-utils.d.ts +218 -27
  65. package/dist/types.d.ts +52 -24
  66. package/dist/utils/check-required-cycle.d.ts +1 -1
  67. package/dist/utils/narrowphase.d.ts +7 -7
  68. package/package.json +53 -45
  69. package/dist/bundle.d.ts +0 -173
  70. package/dist/bundles/timers.d.ts +0 -173
  71. package/dist/src/bundles/audio.js +0 -4
  72. package/dist/src/bundles/audio.js.map +0 -10
  73. package/dist/src/bundles/bounds.js +0 -4
  74. package/dist/src/bundles/bounds.js.map +0 -10
  75. package/dist/src/bundles/camera.js +0 -4
  76. package/dist/src/bundles/camera.js.map +0 -10
  77. package/dist/src/bundles/collision.js +0 -4
  78. package/dist/src/bundles/collision.js.map +0 -11
  79. package/dist/src/bundles/diagnostics.js +0 -5
  80. package/dist/src/bundles/diagnostics.js.map +0 -10
  81. package/dist/src/bundles/input.js +0 -4
  82. package/dist/src/bundles/input.js.map +0 -10
  83. package/dist/src/bundles/physics2D.js +0 -4
  84. package/dist/src/bundles/physics2D.js.map +0 -11
  85. package/dist/src/bundles/renderers/renderer2D.js +0 -4
  86. package/dist/src/bundles/renderers/renderer2D.js.map +0 -10
  87. package/dist/src/bundles/spatial-index.js +0 -4
  88. package/dist/src/bundles/spatial-index.js.map +0 -11
  89. package/dist/src/bundles/state-machine.js +0 -4
  90. package/dist/src/bundles/state-machine.js.map +0 -10
  91. package/dist/src/bundles/timers.js +0 -4
  92. package/dist/src/bundles/timers.js.map +0 -10
  93. package/dist/src/bundles/transform.js +0 -4
  94. package/dist/src/bundles/transform.js.map +0 -10
  95. package/dist/src/bundles/tween.js +0 -4
  96. package/dist/src/bundles/tween.js.map +0 -11
@@ -2,6 +2,7 @@
2
2
  * Utility types for ECSpresso ECS framework
3
3
  * This file contains reusable type helpers used across the codebase
4
4
  */
5
+ import type { ScreenDefinition } from './screen-types';
5
6
  /**
6
7
  * Check if two types are exactly the same for overlapping keys
7
8
  */
@@ -16,11 +17,89 @@ export type TypesAreCompatible<T extends Record<string, any>, U extends Record<s
16
17
  [K in keyof T & keyof U]: ExactlyCompatible<T[K], U[K]>;
17
18
  }[keyof T & keyof U] extends false ? false : true;
18
19
  /**
19
- * Bundle compatibility checker.
20
- * Returns true if bundles can be merged without type conflicts.
21
- * All overlapping keys across all type categories must have identical types.
20
+ * Single config object that bundles all 5 world type dimensions.
21
+ * Replaces the 5 positional type params (ComponentTypes, EventTypes,
22
+ * ResourceTypes, AssetTypes, ScreenStates) throughout the codebase.
22
23
  */
23
- export type BundlesAreCompatible<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, any> = {}, S2 extends Record<string, any> = {}> = TypesAreCompatible<C1, C2> extends true ? TypesAreCompatible<E1, E2> extends true ? TypesAreCompatible<R1, R2> extends true ? TypesAreCompatible<A1, A2> extends true ? TypesAreCompatible<S1, S2> : false : false : false : false;
24
+ export interface WorldConfig {
25
+ readonly components: Record<string, any>;
26
+ readonly events: Record<string, any>;
27
+ readonly resources: Record<string, any>;
28
+ readonly assets: Record<string, unknown>;
29
+ readonly screens: Record<string, ScreenDefinition<any, any>>;
30
+ }
31
+ /**
32
+ * Construct a WorldConfig from individual type dimensions.
33
+ * All parameters default to empty records.
34
+ */
35
+ export type WorldConfigFrom<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>> = {}> = {
36
+ readonly components: C;
37
+ readonly events: E;
38
+ readonly resources: R;
39
+ readonly assets: A;
40
+ readonly screens: S;
41
+ };
42
+ /**
43
+ * Empty WorldConfig with all slots defaulting to {}.
44
+ */
45
+ export type EmptyConfig = WorldConfigFrom;
46
+ /**
47
+ * Merge two WorldConfig types by intersecting each slot.
48
+ */
49
+ export type MergeConfigs<A extends WorldConfig, B extends WorldConfig> = {
50
+ readonly components: A['components'] & B['components'];
51
+ readonly events: A['events'] & B['events'];
52
+ readonly resources: A['resources'] & B['resources'];
53
+ readonly assets: A['assets'] & B['assets'];
54
+ readonly screens: A['screens'] & B['screens'];
55
+ };
56
+ export type WithComponents<Cfg extends WorldConfig, T> = {
57
+ readonly components: Cfg['components'] & T;
58
+ readonly events: Cfg['events'];
59
+ readonly resources: Cfg['resources'];
60
+ readonly assets: Cfg['assets'];
61
+ readonly screens: Cfg['screens'];
62
+ };
63
+ export type WithEvents<Cfg extends WorldConfig, T> = {
64
+ readonly components: Cfg['components'];
65
+ readonly events: Cfg['events'] & T;
66
+ readonly resources: Cfg['resources'];
67
+ readonly assets: Cfg['assets'];
68
+ readonly screens: Cfg['screens'];
69
+ };
70
+ export type WithResources<Cfg extends WorldConfig, T> = {
71
+ readonly components: Cfg['components'];
72
+ readonly events: Cfg['events'];
73
+ readonly resources: Cfg['resources'] & T;
74
+ readonly assets: Cfg['assets'];
75
+ readonly screens: Cfg['screens'];
76
+ };
77
+ export type WithAssets<Cfg extends WorldConfig, T> = {
78
+ readonly components: Cfg['components'];
79
+ readonly events: Cfg['events'];
80
+ readonly resources: Cfg['resources'];
81
+ readonly assets: Cfg['assets'] & T;
82
+ readonly screens: Cfg['screens'];
83
+ };
84
+ export type WithScreens<Cfg extends WorldConfig, T> = {
85
+ readonly components: Cfg['components'];
86
+ readonly events: Cfg['events'];
87
+ readonly resources: Cfg['resources'];
88
+ readonly assets: Cfg['assets'];
89
+ readonly screens: Cfg['screens'] & T;
90
+ };
91
+ /**
92
+ * Check if two WorldConfig types are compatible (no conflicting keys
93
+ * across any slot).
94
+ */
95
+ export type ConfigsAreCompatible<A extends WorldConfig, B extends WorldConfig> = TypesAreCompatible<A['components'], B['components']> extends true ? TypesAreCompatible<A['events'], B['events']> extends true ? TypesAreCompatible<A['resources'], B['resources']> extends true ? TypesAreCompatible<A['assets'], B['assets']> extends true ? TypesAreCompatible<A['screens'], B['screens']> : false : false : false : false;
96
+ /**
97
+ * Check if a Requires config is satisfied by an Accumulated config.
98
+ * Checks all five WorldConfig slots (components, events, resources, assets, screens).
99
+ * When Required is EmptyConfig, all slots have `keyof {} = never`,
100
+ * and `never extends X = true`, so empty requirements are always satisfied.
101
+ */
102
+ export type RequirementsSatisfied<Accumulated extends WorldConfig, Required extends WorldConfig> = keyof Required['components'] extends keyof Accumulated['components'] ? keyof Required['events'] extends keyof Accumulated['events'] ? keyof Required['resources'] extends keyof Accumulated['resources'] ? keyof Required['assets'] extends keyof Accumulated['assets'] ? keyof Required['screens'] extends keyof Accumulated['screens'] ? true : false : false : false : false : false;
24
103
  /**
25
104
  * Utility type for merging two types
26
105
  */
@@ -30,55 +109,167 @@ export type Merge<T1, T2> = T1 & T2;
30
109
  */
31
110
  export type MergeAll<T extends any[]> = T extends [infer First, ...infer Rest] ? Rest extends [] ? First : Merge<First, MergeAll<Rest>> : {};
32
111
  /**
33
- * Extract the ComponentTypes from a Bundle instance
112
+ * Wildcard ECSpresso type that any concrete instance is assignable to.
113
+ * Use as a generic constraint for functions that accept any ECSpresso world.
114
+ * Matches the phantom _cfg property declared on the ECSpresso class.
34
115
  */
35
- export type ComponentsOf<B> = B extends import('./bundle').default<infer C extends Record<string, any>, any, any, any, any, any, any, any, any> ? C : never;
116
+ export type AnyECSpresso = {
117
+ readonly _cfg: WorldConfig;
118
+ };
36
119
  /**
37
- * Extract the EventTypes from a Bundle instance
120
+ * Wildcard Plugin type that any concrete plugin is assignable to.
121
+ * Matches the phantom _cfg and _requires properties declared on the Plugin interface.
38
122
  */
39
- export type EventsOf<B> = B extends import('./bundle').default<any, infer E extends Record<string, any>, any, any, any, any, any, any, any> ? E : never;
123
+ export type AnyPlugin = {
124
+ readonly _cfg?: WorldConfig;
125
+ readonly _requires?: WorldConfig;
126
+ };
40
127
  /**
41
- * Extract the ResourceTypes from a Bundle instance
128
+ * Extract the full WorldConfig from a Plugin or ECSpresso instance
42
129
  */
43
- export type ResourcesOf<B> = B extends import('./bundle').default<any, any, infer R extends Record<string, any>, any, any, any, any, any, any> ? R : never;
130
+ export type ConfigOf<B> = B extends {
131
+ readonly _cfg: infer Cfg extends WorldConfig;
132
+ } ? Cfg : never;
44
133
  /**
45
- * Extract the system Labels from a Bundle instance
134
+ * Extract the ComponentTypes from a Plugin or ECSpresso instance
46
135
  */
47
- export type LabelsOf<B> = B extends import('./bundle').default<any, any, any, any, any, infer L extends string, any, any, any> ? L : never;
136
+ export type ComponentsOf<B> = B extends {
137
+ readonly _cfg: {
138
+ components: infer C extends Record<string, any>;
139
+ };
140
+ } ? C : B extends {
141
+ readonly _cfg?: {
142
+ components: infer C extends Record<string, any>;
143
+ };
144
+ } ? C : never;
48
145
  /**
49
- * Extract the system Groups from a Bundle instance
146
+ * Extract the EventTypes from a Plugin or ECSpresso instance
50
147
  */
51
- export type GroupsOf<B> = B extends import('./bundle').default<any, any, any, any, any, any, infer G extends string, any, any> ? G : never;
148
+ export type EventsOf<B> = B extends {
149
+ readonly _cfg: {
150
+ events: infer E extends Record<string, any>;
151
+ };
152
+ } ? E : B extends {
153
+ readonly _cfg?: {
154
+ events: infer E extends Record<string, any>;
155
+ };
156
+ } ? E : never;
52
157
  /**
53
- * Extract the AssetGroupNames from a Bundle instance
158
+ * Extract the ResourceTypes from a Plugin or ECSpresso instance
54
159
  */
55
- export type AssetGroupNamesOf<B> = B extends import('./bundle').default<any, any, any, any, any, any, any, infer AG extends string, any> ? AG : never;
160
+ export type ResourcesOf<B> = B extends {
161
+ readonly _cfg: {
162
+ resources: infer R extends Record<string, any>;
163
+ };
164
+ } ? R : B extends {
165
+ readonly _cfg?: {
166
+ resources: infer R extends Record<string, any>;
167
+ };
168
+ } ? R : never;
56
169
  /**
57
- * Extract the ReactiveQueryNames from a Bundle instance
170
+ * Extract AssetTypes from a Plugin or ECSpresso instance
58
171
  */
59
- export type ReactiveQueryNamesOf<B> = B extends import('./bundle').default<any, any, any, any, any, any, any, any, infer RQ extends string> ? RQ : never;
172
+ export type AssetTypesOf<B> = B extends {
173
+ readonly _cfg: {
174
+ assets: infer A extends Record<string, unknown>;
175
+ };
176
+ } ? A : B extends {
177
+ readonly _cfg?: {
178
+ assets: infer A extends Record<string, unknown>;
179
+ };
180
+ } ? A : never;
60
181
  /**
61
- * Extract AssetTypes from a Bundle instance
182
+ * Extract ScreenStates from a Plugin or ECSpresso instance
62
183
  */
63
- export type AssetTypesOf<B> = B extends import('./bundle').default<any, any, any, infer A extends Record<string, unknown>, any, any, any, any, any> ? A : never;
184
+ export type ScreenStatesOf<B> = B extends {
185
+ readonly _cfg: {
186
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
187
+ };
188
+ } ? S : B extends {
189
+ readonly _cfg?: {
190
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
191
+ };
192
+ } ? S : never;
64
193
  /**
65
- * Extract ScreenStates from a Bundle instance
194
+ * Extract the system Labels from a Plugin instance
66
195
  */
67
- export type ScreenStatesOf<B> = B extends import('./bundle').default<any, any, any, any, infer S extends Record<string, import('./screen-types').ScreenDefinition<any, any>>, any, any, any, any> ? S : never;
196
+ export type LabelsOf<B> = B extends {
197
+ readonly _labels?: infer L;
198
+ } ? L extends string ? L : never : never;
68
199
  /**
69
- * Extract ScreenStates from an ECSpresso world instance type
200
+ * Extract the system Groups from a Plugin instance
70
201
  */
71
- export type ScreenStatesOfWorld<W> = W extends import('./ecspresso').default<any, any, any, any, infer S extends Record<string, import('./screen-types').ScreenDefinition<any, any>>, any, any, any, any> ? S : never;
202
+ export type GroupsOf<B> = B extends {
203
+ readonly _groups?: infer G;
204
+ } ? G extends string ? G : never : never;
205
+ /**
206
+ * Extract the AssetGroupNames from a Plugin instance
207
+ */
208
+ export type AssetGroupNamesOf<B> = B extends {
209
+ readonly _assetGroupNames?: infer AG;
210
+ } ? AG extends string ? AG : never : never;
211
+ /**
212
+ * Extract the ReactiveQueryNames from a Plugin instance
213
+ */
214
+ export type ReactiveQueryNamesOf<B> = B extends {
215
+ readonly _reactiveQueryNames?: infer RQ;
216
+ } ? RQ extends string ? RQ : never : never;
72
217
  /**
73
218
  * Extract ComponentTypes from an ECSpresso world instance type.
74
219
  */
75
- export type ComponentsOfWorld<W> = W extends import('./ecspresso').default<infer C extends Record<string, any>, any, any, any, any, any, any, any, any> ? C : never;
220
+ export type ComponentsOfWorld<W> = W extends {
221
+ readonly _cfg: {
222
+ components: infer C extends Record<string, any>;
223
+ };
224
+ } ? C : never;
76
225
  /**
77
226
  * Extract EventTypes from an ECSpresso world instance type.
78
227
  */
79
- export type EventsOfWorld<W> = W extends import('./ecspresso').default<any, infer E extends Record<string, any>, any, any, any, any, any, any, any> ? E : never;
228
+ export type EventsOfWorld<W> = W extends {
229
+ readonly _cfg: {
230
+ events: infer E extends Record<string, any>;
231
+ };
232
+ } ? E : never;
80
233
  /**
81
234
  * Extract AssetTypes from an ECSpresso world instance type.
82
235
  */
83
- export type AssetsOfWorld<W> = W extends import('./ecspresso').default<any, any, any, infer A extends Record<string, unknown>, any, any, any, any, any> ? A : never;
236
+ export type AssetsOfWorld<W> = W extends {
237
+ readonly _cfg: {
238
+ assets: infer A extends Record<string, unknown>;
239
+ };
240
+ } ? A : never;
241
+ /**
242
+ * Extract ScreenStates from an ECSpresso world instance type
243
+ */
244
+ export type ScreenStatesOfWorld<W> = W extends {
245
+ readonly _cfg: {
246
+ screens: infer S extends Record<string, ScreenDefinition<any, any>>;
247
+ };
248
+ } ? S : never;
249
+ /**
250
+ * Extract event names from an EventTypes record whose payload extends the given shape.
251
+ * Eliminates the need for each plugin to define its own mapped filter type.
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * interface MyEventData { entityId: number }
256
+ * type MyEventName<ET> = EventNameMatching<ET, MyEventData>;
257
+ * ```
258
+ */
259
+ export type EventNameMatching<ET extends Record<string, any>, Payload> = {
260
+ [K in keyof ET & string]: ET[K] extends Payload ? K : never;
261
+ }[keyof ET & string];
262
+ /**
263
+ * Extract the channel type from a world's AudioSource component.
264
+ * Falls back to `string` if the world has no audioSource component.
265
+ */
266
+ export type ChannelOfWorld<W> = W extends {
267
+ readonly _cfg: {
268
+ components: {
269
+ audioSource: {
270
+ channel: infer Ch extends string;
271
+ };
272
+ };
273
+ };
274
+ } ? Ch : string;
84
275
  export {};
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import ECSpresso from "./ecspresso";
2
+ import type { WorldConfig, EmptyConfig, WorldConfigFrom } from "./type-utils";
2
3
  /**
3
4
  * Execution phase for systems. Systems are grouped by phase and executed
4
5
  * in this fixed order: preUpdate -> fixedUpdate -> update -> postUpdate -> render.
@@ -109,7 +110,7 @@ export type QueryDefinition<ComponentTypes extends Record<string, any>, WithComp
109
110
  *
110
111
  * world.addSystem('movement')
111
112
  * .addQuery('entities', movingEntitiesQuery)
112
- * .setProcess((queries) => {
113
+ * .setProcess(({ queries }) => {
113
114
  * for (const entity of queries.entities) {
114
115
  * updatePosition(entity);
115
116
  * }
@@ -123,7 +124,7 @@ export declare function createQueryDefinition<ComponentTypes extends Record<stri
123
124
  optional?: ReadonlyArray<keyof ComponentTypes>;
124
125
  parentHas?: ReadonlyArray<keyof ComponentTypes>;
125
126
  }>(queryDef: QueryDef): QueryDef;
126
- export interface System<ComponentTypes extends Record<string, any> = {}, WithComponents extends keyof ComponentTypes = never, WithoutComponents extends keyof ComponentTypes = never, EventTypes extends Record<string, any> = {}, ResourceTypes extends Record<string, any> = {}, AssetTypes extends Record<string, unknown> = {}, ScreenStates extends Record<string, any> = {}> {
127
+ export interface System<Cfg extends WorldConfig = EmptyConfig, WithComponents extends keyof Cfg['components'] = never, WithoutComponents extends keyof Cfg['components'] = never> {
127
128
  label: string;
128
129
  /**
129
130
  * System priority - higher values execute first (default: 0)
@@ -144,17 +145,17 @@ export interface System<ComponentTypes extends Record<string, any> = {}, WithCom
144
145
  * Screens where this system should run. If specified, system only runs
145
146
  * when current screen is in this list.
146
147
  */
147
- inScreens?: ReadonlyArray<keyof ScreenStates & string>;
148
+ inScreens?: ReadonlyArray<keyof Cfg['screens'] & string>;
148
149
  /**
149
150
  * Screens where this system should NOT run. If specified, system skips
150
151
  * when current screen is in this list.
151
152
  */
152
- excludeScreens?: ReadonlyArray<keyof ScreenStates & string>;
153
+ excludeScreens?: ReadonlyArray<keyof Cfg['screens'] & string>;
153
154
  /**
154
155
  * Assets that must be loaded for this system to run.
155
156
  * System will be skipped if any required asset is not loaded.
156
157
  */
157
- requiredAssets?: ReadonlyArray<keyof AssetTypes & string>;
158
+ requiredAssets?: ReadonlyArray<keyof Cfg['assets'] & string>;
158
159
  /**
159
160
  * When true, the system's process function runs even when all queries
160
161
  * return zero entities. Default is false (system is skipped when all
@@ -162,41 +163,68 @@ export interface System<ComponentTypes extends Record<string, any> = {}, WithCom
162
163
  */
163
164
  runWhenEmpty?: boolean;
164
165
  entityQueries?: {
165
- [queryName: string]: QueryConfig<ComponentTypes, WithComponents, WithoutComponents>;
166
+ [queryName: string]: QueryConfig<Cfg['components'], WithComponents, WithoutComponents>;
166
167
  };
167
168
  /**
168
- * Process method that runs during each update cycle
169
- * @param queries The entity queries results based on system's entityQueries definition
170
- * @param deltaTime Time elapsed since the last update in seconds
171
- * @param ecs The ECSpresso instance providing access to all ECS functionality
169
+ * Process method that runs during each update cycle.
170
+ * Receives a single context object with queries, dt, and ecs.
172
171
  */
173
- process?(queries: {
174
- [queryName: string]: Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>;
175
- } | Array<FilteredEntity<ComponentTypes, WithComponents, WithoutComponents>>, deltaTime: number, ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
172
+ process?(ctx: {
173
+ queries: {
174
+ [queryName: string]: Array<FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>>;
175
+ };
176
+ dt: number;
177
+ ecs: ECSpresso<Cfg>;
178
+ }): void;
176
179
  /**
177
180
  * Lifecycle hook called when the system is initialized
178
181
  * This is called when ECSpresso.initialize() is invoked, after resources are initialized
179
182
  * Use this for one-time initialization that depends on resources
180
183
  * @param ecs The ECSpresso instance providing access to all ECS functionality
181
184
  */
182
- onInitialize?(ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void | Promise<void>;
185
+ onInitialize?(ecs: ECSpresso<Cfg>): void | Promise<void>;
183
186
  /**
184
187
  * Lifecycle hook called when the system is detached from the ECS
185
188
  * @param ecs The ECSpresso instance providing access to all ECS functionality
186
189
  */
187
- onDetach?(ecs: import("./ecspresso").default<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
190
+ onDetach?(ecs: import("./ecspresso").default<Cfg>): void;
191
+ /**
192
+ * Per-query callbacks that fire once per entity the first time it appears
193
+ * in a query's results. Fires before process. Automatic cleanup when
194
+ * entity leaves query (component removed, entity destroyed) so re-entry
195
+ * fires the callback again.
196
+ */
197
+ onEntityEnter?: Record<string, (ctx: {
198
+ entity: FilteredEntity<Cfg['components'], WithComponents, WithoutComponents>;
199
+ ecs: ECSpresso<Cfg>;
200
+ }) => void>;
188
201
  /**
189
202
  * Event handlers for specific event types
190
203
  */
191
204
  eventHandlers?: {
192
- [EventName in keyof EventTypes]?: {
193
- /**
194
- * Event handler function
195
- * @param data The event data specific to this event type
196
- * @param ecs The ECSpresso instance providing access to all ECS functionality
197
- */
198
- handler(data: EventTypes[EventName], ecs: ECSpresso<ComponentTypes, EventTypes, ResourceTypes, AssetTypes, ScreenStates>): void;
199
- };
205
+ [EventName in keyof Cfg['events']]?: (ctx: {
206
+ data: Cfg['events'][EventName];
207
+ ecs: ECSpresso<Cfg>;
208
+ }) => void;
200
209
  };
201
210
  }
202
- export type { Merge, MergeAll, TypesAreCompatible, ComponentsOf, EventsOf, ResourcesOf, LabelsOf, GroupsOf, AssetGroupNamesOf, ReactiveQueryNamesOf, AssetTypesOf, ScreenStatesOf, ComponentsOfWorld, EventsOfWorld, AssetsOfWorld, ScreenStatesOfWorld } from './type-utils';
211
+ /**
212
+ * Typed world interface for plugin helpers and structural typing.
213
+ *
214
+ * Generic over component types `C`:
215
+ * - `BaseWorld` (no param): defaults to `{}`, meaning component-accessing methods
216
+ * cannot be called (keys resolve to `never`). Use for functions that only need
217
+ * `removeEntity`, `getResource`, etc.
218
+ * - `BaseWorld<MyComponents>`: narrows `getComponent`, `hasComponent`, `markChanged`,
219
+ * `spawn`, and command buffer methods to the declared component map.
220
+ *
221
+ * Structural typing ensures any `ECSpresso<Cfg>` where `Cfg['components']` is a
222
+ * superset of `C` satisfies `BaseWorld<C>`.
223
+ */
224
+ type _BaseWorldCfg<C extends Record<string, any>> = WorldConfigFrom<C, Record<string, any>, Record<string, any>, Record<string, unknown>, Record<string, any>>;
225
+ type _EventBus = import("./event-bus").default<Record<string, any>>;
226
+ export type BaseWorld<C extends Record<string, any> = {}> = Pick<ECSpresso<_BaseWorldCfg<C>>, 'getComponent' | 'hasComponent' | 'removeEntity' | 'spawn' | 'markChanged' | 'getResource' | 'hasResource'> & {
227
+ eventBus: Pick<_EventBus, 'publish'>;
228
+ commands: Pick<import("./command-buffer").default<_BaseWorldCfg<C>>, 'spawn' | 'removeEntity' | 'addComponent' | 'removeComponent'>;
229
+ };
230
+ export type { Merge, MergeAll, TypesAreCompatible, ComponentsOf, EventsOf, ResourcesOf, LabelsOf, GroupsOf, AssetGroupNamesOf, ReactiveQueryNamesOf, AssetTypesOf, ScreenStatesOf, ComponentsOfWorld, EventsOfWorld, AssetsOfWorld, ScreenStatesOfWorld, AnyECSpresso, AnyPlugin, EventNameMatching, ChannelOfWorld, WorldConfig, EmptyConfig, WorldConfigFrom, MergeConfigs, ConfigsAreCompatible, ConfigOf, WithComponents, WithEvents, WithResources, WithAssets, WithScreens } from './type-utils';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * BFS cycle detection for required component graphs.
3
- * Shared by ECSpresso and Bundle.
3
+ * Shared by ECSpresso and Plugin.
4
4
  *
5
5
  * @param trigger The component that triggers the requirement
6
6
  * @param newRequired The component about to be added as required
@@ -2,8 +2,8 @@
2
2
  * Shared Narrowphase Module
3
3
  *
4
4
  * Provides contact-computing narrowphase tests and a generic collision
5
- * iteration pipeline used by both the collision bundle (event-only) and
6
- * the physics2D bundle (impulse response).
5
+ * iteration pipeline used by both the collision plugin (event-only) and
6
+ * the physics2D plugin (impulse response).
7
7
  */
8
8
  import type { SpatialIndex } from './spatial-hash';
9
9
  /** Contact result from a narrowphase test. Normal points from A toward B. */
@@ -31,7 +31,7 @@ export interface BaseColliderInfo<L extends string = string> {
31
31
  /**
32
32
  * Build a BaseColliderInfo from raw entity/collider component data.
33
33
  * Returns null if the entity has neither an AABB nor circle collider.
34
- * Shared by collision bundle (event-only) and physics2D bundle (impulse response).
34
+ * Shared by collision plugin (event-only) and physics2D plugin (impulse response).
35
35
  */
36
36
  export declare function buildBaseColliderInfo<L extends string>(entityId: number, x: number, y: number, layer: L, collidesWith: readonly L[], aabb: {
37
37
  width: number;
@@ -44,11 +44,11 @@ export declare function buildBaseColliderInfo<L extends string>(entityId: number
44
44
  offsetY?: number;
45
45
  } | undefined): BaseColliderInfo<L> | null;
46
46
  /**
47
- * Retrieve the optional spatialIndex resource, returning null when absent.
48
- * Centralizes the cross-bundle typed lookup so individual bundles don't each
47
+ * Retrieve the optional spatialIndex resource, returning undefined when absent.
48
+ * Centralizes the cross-plugin typed lookup so individual plugins don't each
49
49
  * need to import SpatialIndex or repeat the tryGetResource pattern.
50
50
  */
51
- export declare function tryGetSpatialIndex(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex | null;
51
+ export declare function tryGetSpatialIndex(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex | undefined;
52
52
  export declare function computeAABBvsAABB(ax: number, ay: number, ahw: number, ahh: number, bx: number, by: number, bhw: number, bhh: number): Contact | null;
53
53
  export declare function computeCircleVsCircle(ax: number, ay: number, ar: number, bx: number, by: number, br: number): Contact | null;
54
54
  export declare function computeAABBvsCircle(aabbX: number, aabbY: number, ahw: number, ahh: number, circleX: number, circleY: number, radius: number): Contact | null;
@@ -60,4 +60,4 @@ export declare function computeContact(a: BaseColliderInfo, b: BaseColliderInfo)
60
60
  * Uses a context parameter forwarded to the callback to avoid
61
61
  * per-frame closure allocation.
62
62
  */
63
- export declare function detectCollisions<I extends BaseColliderInfo, C>(colliders: I[], spatialIndex: SpatialIndex | null, onContact: (a: I, b: I, contact: Contact, context: C) => void, context: C): void;
63
+ export declare function detectCollisions<I extends BaseColliderInfo, C>(colliders: I[], spatialIndex: SpatialIndex | undefined, onContact: (a: I, b: I, contact: Contact, context: C) => void, context: C): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecspresso",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,57 +11,65 @@
11
11
  "import": "./dist/index.js",
12
12
  "types": "./dist/index.d.ts"
13
13
  },
14
- "./bundles/renderers/renderer2D": {
15
- "import": "./dist/bundles/renderers/renderer2D.js",
16
- "types": "./dist/bundles/renderers/renderer2D.d.ts"
14
+ "./plugins/renderers/renderer2D": {
15
+ "import": "./dist/plugins/renderers/renderer2D.js",
16
+ "types": "./dist/plugins/renderers/renderer2D.d.ts"
17
17
  },
18
- "./bundles/bounds": {
19
- "import": "./dist/bundles/bounds.js",
20
- "types": "./dist/bundles/bounds.d.ts"
18
+ "./plugins/bounds": {
19
+ "import": "./dist/plugins/bounds.js",
20
+ "types": "./dist/plugins/bounds.d.ts"
21
21
  },
22
- "./bundles/camera": {
23
- "import": "./dist/bundles/camera.js",
24
- "types": "./dist/bundles/camera.d.ts"
22
+ "./plugins/camera": {
23
+ "import": "./dist/plugins/camera.js",
24
+ "types": "./dist/plugins/camera.d.ts"
25
25
  },
26
- "./bundles/collision": {
27
- "import": "./dist/bundles/collision.js",
28
- "types": "./dist/bundles/collision.d.ts"
26
+ "./plugins/collision": {
27
+ "import": "./dist/plugins/collision.js",
28
+ "types": "./dist/plugins/collision.d.ts"
29
29
  },
30
- "./bundles/diagnostics": {
31
- "import": "./dist/bundles/diagnostics.js",
32
- "types": "./dist/bundles/diagnostics.d.ts"
30
+ "./plugins/diagnostics": {
31
+ "import": "./dist/plugins/diagnostics.js",
32
+ "types": "./dist/plugins/diagnostics.d.ts"
33
33
  },
34
- "./bundles/input": {
35
- "import": "./dist/bundles/input.js",
36
- "types": "./dist/bundles/input.d.ts"
34
+ "./plugins/input": {
35
+ "import": "./dist/plugins/input.js",
36
+ "types": "./dist/plugins/input.d.ts"
37
37
  },
38
- "./bundles/physics2D": {
39
- "import": "./dist/bundles/physics2D.js",
40
- "types": "./dist/bundles/physics2D.d.ts"
38
+ "./plugins/physics2D": {
39
+ "import": "./dist/plugins/physics2D.js",
40
+ "types": "./dist/plugins/physics2D.d.ts"
41
41
  },
42
- "./bundles/spatial-index": {
43
- "import": "./dist/bundles/spatial-index.js",
44
- "types": "./dist/bundles/spatial-index.d.ts"
42
+ "./plugins/spatial-index": {
43
+ "import": "./dist/plugins/spatial-index.js",
44
+ "types": "./dist/plugins/spatial-index.d.ts"
45
45
  },
46
- "./bundles/state-machine": {
47
- "import": "./dist/bundles/state-machine.js",
48
- "types": "./dist/bundles/state-machine.d.ts"
46
+ "./plugins/state-machine": {
47
+ "import": "./dist/plugins/state-machine.js",
48
+ "types": "./dist/plugins/state-machine.d.ts"
49
49
  },
50
- "./bundles/timers": {
51
- "import": "./dist/bundles/timers.js",
52
- "types": "./dist/bundles/timers.d.ts"
50
+ "./plugins/timers": {
51
+ "import": "./dist/plugins/timers.js",
52
+ "types": "./dist/plugins/timers.d.ts"
53
53
  },
54
- "./bundles/transform": {
55
- "import": "./dist/bundles/transform.js",
56
- "types": "./dist/bundles/transform.d.ts"
54
+ "./plugins/transform": {
55
+ "import": "./dist/plugins/transform.js",
56
+ "types": "./dist/plugins/transform.d.ts"
57
57
  },
58
- "./bundles/tween": {
59
- "import": "./dist/bundles/tween.js",
60
- "types": "./dist/bundles/tween.d.ts"
58
+ "./plugins/tween": {
59
+ "import": "./dist/plugins/tween.js",
60
+ "types": "./dist/plugins/tween.d.ts"
61
61
  },
62
- "./bundles/audio": {
63
- "import": "./dist/bundles/audio.js",
64
- "types": "./dist/bundles/audio.d.ts"
62
+ "./plugins/audio": {
63
+ "import": "./dist/plugins/audio.js",
64
+ "types": "./dist/plugins/audio.d.ts"
65
+ },
66
+ "./plugins/sprite-animation": {
67
+ "import": "./dist/plugins/sprite-animation.js",
68
+ "types": "./dist/plugins/sprite-animation.d.ts"
69
+ },
70
+ "./plugins/particles": {
71
+ "import": "./dist/plugins/particles.js",
72
+ "types": "./dist/plugins/particles.d.ts"
65
73
  }
66
74
  },
67
75
  "publishConfig": {
@@ -84,13 +92,13 @@
84
92
  "devDependencies": {
85
93
  "@types/bun": "latest",
86
94
  "@types/howler": "^2.2.12",
87
- "@types/three": "^0.180.0",
88
- "howler": "^2.2.0",
89
- "pixi.js": "^8.15.0",
90
- "three": "^0.180.0"
95
+ "@types/three": "^0.183.1",
96
+ "howler": "^2.2.4",
97
+ "pixi.js": "^8.16.0",
98
+ "three": "^0.183.2"
91
99
  },
92
100
  "peerDependencies": {
93
- "typescript": "^5.9.2",
101
+ "typescript": "^5.9.3",
94
102
  "pixi.js": "^8.0.0",
95
103
  "howler": "^2.2.0"
96
104
  },