@jael-ecs/core 1.1.1 → 1.2.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
@@ -25,6 +25,7 @@ _A modern, performant, and user-friendly Entity Component System library written
25
25
  - [EventRegistry](#event-registry)
26
26
  - [Best Practices](#best-practices)
27
27
  - [Advanced Usage](#advanced-usage)
28
+ - [Planned Features](#planned-features)
28
29
  - [Contributing](#contributing)
29
30
  - [Acknowledgments](#acknowledgments)
30
31
 
@@ -89,15 +90,6 @@ const movementSystem: System = {
89
90
  position.x += velocity.dx * (Time.delta || 0.016);
90
91
  position.y += velocity.dy * (Time.delta || 0.016);
91
92
  });
92
-
93
- // Get entity ids from query
94
- for (const entityId of query.ids) {
95
- const position = this.world.getComponent<Position>(entityId, "position");
96
- const velocity = this.world.getComponent<Velocity>(entityId, "velocity");
97
-
98
- position.x += velocity.dx * (Time.delta || 0.016);
99
- position.y += velocity.dy * (Time.delta || 0.016);
100
- }
101
93
  },
102
94
  };
103
95
 
@@ -163,21 +155,21 @@ world.removeSystem(yourSystem);
163
155
  #### Events
164
156
 
165
157
  ```typescript
166
- // Listen to world events ( returns entity proxy for easy reading )
167
- world.on("entityCreated", ({ entity }) => {
168
- console.log("Entity created:", entity);
158
+ // Listen to world events
159
+ world.on("entityCreated", ({ entityId }) => {
160
+ console.log("Entity created:", entityId);
169
161
  });
170
162
 
171
- world.on("entityDestroyed", ({ entity }) => {
172
- console.log("Entity destroyed:", entity);
163
+ world.on("entityDestroyed", ({ entityId }) => {
164
+ console.log("Entity destroyed:", entityId);
173
165
  });
174
166
 
175
- world.on("componentAdded", ({ entity, component }) => {
176
- console.log(`Component ${component} added to entity`);
167
+ world.on("componentAdded", ({ entityId, component }) => {
168
+ console.log(`Component ${component} added to entity ${entityId}`);
177
169
  });
178
170
 
179
- world.on("componentRemoved", ({ entity, component }) => {
180
- console.log(`Component ${component} removed from entity`);
171
+ world.on("componentRemoved", ({ entityId, component }) => {
172
+ console.log(`Component ${component} removed from entity ${entityId}`);
181
173
  });
182
174
 
183
175
  world.on("updated", () => {
@@ -206,8 +198,7 @@ const posExist = entity.has("position");
206
198
  // Get curren value of the component
207
199
  const compSchema = entity.get("position");
208
200
 
209
- entity.id // Returns unique entity id from proxy
210
-
201
+ entity.id; // Returns unique entity id from proxy
211
202
  ```
212
203
 
213
204
  ### System
@@ -287,18 +278,16 @@ const complexQuery2 = world.include("position", "health").exclude("static");
287
278
  // Iterate through entities as proxy
288
279
  query.entities.forEach((entity) => {
289
280
  // Process Entity proxy
290
- })
281
+ });
291
282
 
292
283
  // Iterate through entities ids
293
- for(const entityId of query.ids){
284
+ for (const entityId of query.ids) {
294
285
  // Process Entity id
295
286
  }
296
287
 
297
-
298
-
299
288
  // Get the first value of the query
300
289
  const first = query.entities[0];
301
- const firstId = query.ids.first()
290
+ const firstId = query.ids.first();
302
291
 
303
292
  // Check query size
304
293
  const count = query.size();
@@ -388,7 +377,7 @@ world.on("entityCreated", (data) => {
388
377
  });
389
378
 
390
379
  // Emit events (handled internally by World)
391
- world.emit("entityCreated", { entity: someEntity });
380
+ world.emit("entityCreated", { entityId });
392
381
 
393
382
  // Remove listeners
394
383
  world.off("entityCreated", handler);
@@ -450,9 +439,9 @@ class MovementSystem implements System {
450
439
  }
451
440
 
452
441
  update() {
453
- for(const entityId of this.movementQuery.ids){
454
- // Process movement using entityId
455
- }
442
+ this.movementQuery.entities.forEach((entity) => {
443
+ // Handle entity movement
444
+ })
456
445
  }
457
446
  }
458
447
 
@@ -469,9 +458,9 @@ const movementSystem: MovementSystem = {
469
458
  }
470
459
 
471
460
  update(){
472
- for (const entityId of this.movementQuery.ids) {
473
- // Process movement
474
- }
461
+ this.movementQuery?.entities.forEach((entity) => {
462
+ // Handle entity movement
463
+ })
475
464
  }
476
465
  }
477
466
 
@@ -514,6 +503,13 @@ world.on("playerScored", ({ points }) => {
514
503
  });
515
504
  ```
516
505
 
506
+ ## Planned Features
507
+
508
+ - Implement basic one level tag manager.
509
+ - Instancing / Prefab system with searilzation.
510
+ - Entity with childrens and parents.
511
+ - React wrapper (?)
512
+
517
513
  ## Contributing
518
514
 
519
515
  Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
@@ -542,6 +538,7 @@ npm run build
542
538
  ## Acknowledgments
543
539
 
544
540
  - Inspiration from ECS frameworks like [ECSY](https://github.com/ecsyjs/ecsy) and [Bevy](https://github.com/bevyengine/bevy)
541
+ - Deep digging documentation at [Austin Morlan Post](https://austinmorlan.com/posts/entity_component_system/)
545
542
  - TypeScript for providing excellent type safety and developer experience
546
543
 
547
544
  ---
@@ -12,14 +12,14 @@ export interface ComponentManagerEvents {
12
12
  };
13
13
  }
14
14
  export declare class ComponentManager extends EventRegistry<ComponentManagerEvents> {
15
- componentSet: {
16
- [k: number]: ComponentSchema;
17
- };
18
- world: World;
15
+ private componentSet;
16
+ private world;
17
+ dirtyEntities: Set<number>;
19
18
  constructor(world: World);
20
19
  clearComponentSchema(entityId: number): void;
21
20
  addComponent<K extends keyof ComponentSchema>(entityId: number, key: K, value: ComponentSchema[K]): void;
22
21
  getComponent<K extends keyof ComponentSchema>(entityId: number, key: K): ComponentSchema[K] | undefined;
22
+ cleanDirtyEntities(): void;
23
23
  hasComponent<K extends keyof ComponentSchema>(entityId: number, key: K): boolean;
24
24
  removeComponent<K extends keyof ComponentSchema>(entityId: number, key: K): void;
25
25
  }
@@ -1,14 +1,12 @@
1
- export type Event<V> = {
2
- [key: string]: V;
3
- };
4
- export type EventCallback<V> = (event: V[keyof V]) => void;
5
- export default class EventRegistry<E extends Event<any> = {}> {
1
+ export default class EventRegistry<E extends {
2
+ [key: string]: any;
3
+ } = {}> {
6
4
  private _listeners;
7
- on(type: keyof E, callback: EventCallback<E>): void;
8
- off(type: keyof E, callback: EventCallback<E>): void;
9
- once(type: keyof E, callback: EventCallback<E>): void;
10
- clearEvent(type: keyof E): void;
5
+ on<K extends string>(type: K, callback: (e: E[K]) => void): void;
6
+ off<K extends string>(type: K, callback: (e: E[K]) => void): void;
7
+ once<K extends string>(type: K, callback: (e: E[K]) => void): void;
8
+ clearEvent<K extends string>(type: K): void;
11
9
  clearAllEvents(): void;
12
- contains(type: keyof E, callback: EventCallback<E>): boolean;
13
- emit(type: keyof E, data: E[keyof E]): void;
10
+ contains<K extends string>(type: K, callback: (e: E[K]) => void): boolean;
11
+ emit<K extends string>(type: K, data?: E[K]): void;
14
12
  }
package/dist/Query.d.ts CHANGED
@@ -13,18 +13,18 @@ export interface QueryEvents {
13
13
  export declare class Query extends EventRegistry<QueryEvents> {
14
14
  private config;
15
15
  private entityMap;
16
- private entityInstancesCache;
17
16
  private world;
17
+ private lastVersion;
18
+ dirty: boolean;
18
19
  constructor(config: QueryConfig, world: World);
19
20
  hasComponents(entityId: number): boolean;
20
21
  size(): number;
21
22
  get hash(): number;
22
23
  get ids(): SparseSet<number>;
23
- private getCachedEntity;
24
24
  get entities(): Entity[];
25
25
  include(...comps: string[]): Query;
26
26
  exclude(...comps: string[]): Query;
27
- private _checkExistingEntities;
28
- checkEntities(): void;
27
+ markDirty(): void;
28
+ checkEntities(entities?: Set<number>): void;
29
29
  static getHash(config: QueryConfig): number;
30
30
  }
package/dist/World.d.ts CHANGED
@@ -6,17 +6,17 @@ import { SparseSet } from './SparseSet';
6
6
  import { SystemManager, System } from './SystemManager';
7
7
  export interface WorldEvents {
8
8
  entityCreated: {
9
- entity: Entity;
9
+ entityId: number;
10
10
  };
11
11
  entityDestroyed: {
12
- entity: Entity;
12
+ entityId: number;
13
13
  };
14
14
  componentAdded: {
15
- entity: Entity;
15
+ entityId: number;
16
16
  component: keyof ComponentSchema;
17
17
  };
18
18
  componentRemoved: {
19
- entity: Entity;
19
+ entityId: number;
20
20
  component: keyof ComponentSchema;
21
21
  };
22
22
  updated: void;
@@ -26,6 +26,7 @@ export default class World extends EventRegistry<WorldEvents> {
26
26
  componentManager: ComponentManager;
27
27
  systemManager: SystemManager;
28
28
  queries: Map<number, Query>;
29
+ version: number;
29
30
  constructor();
30
31
  getEntity(id: number): Entity | undefined;
31
32
  get entityIds(): SparseSet<number>;
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class o{_listeners=new Map;on(e,t){if(this.contains(e,t))return;const s=this._listeners.get(e);s?s.add(t):this._listeners.set(e,new Set([t]))}off(e,t){if(!this.contains(e,t))return;const s=this._listeners.get(e);s&&s.delete(t)}once(e,t){const s=(n=>{t(n),this.off(e,s)});this.on(e,s)}clearEvent(e){this._listeners.get(e)&&this._listeners.get(e)?.clear()}clearAllEvents(){this._listeners.forEach(e=>e.clear()),this._listeners.clear()}contains(e,t){return this._listeners.get(e)?this._listeners.get(e).has(t):!1}emit(e,t){this._listeners.get(e)&&this._listeners.get(e)?.forEach(s=>{s(t)})}}class u{systemList=[];addSystem(e){this.systemList.push(e),e.init?.(),this.reorder()}reorder(){this.systemList.sort((e,t)=>e.priority-t.priority)}has(e){return this.systemList.indexOf(e)>0}removeSystem(e){if(!this.has(e))return;const t=this.systemList.indexOf(e);t>=0&&(this.systemList.splice(t,1),e.exit?.(),this.reorder())}}class d{denseValues=[];sparse=new Map;[Symbol.iterator](){let e=this.values.length;const t={value:void 0,done:!1};return{next:()=>(t.value=this.values[--e],t.done=e<0,t)}}get values(){return this.denseValues}first(){return this.denseValues[0]}add(e){this.has(e)||(this.denseValues.push(e),this.sparse.set(e,this.denseValues.length-1))}indexOf(e){const t=this.sparse.get(e);return t!==void 0?t:-1}remove(e){if(!this.has(e))return;const t=this.sparse.get(e);this.sparse.delete(e);const s=this.denseValues[this.denseValues.length-1];s!==e&&(this.denseValues[t]=s,this.sparse.set(s,t)),this.denseValues.pop()}forEach(e){for(let t of this)e(t)}size(){return this.denseValues.length}clear(){for(let e of this)this.remove(e)}has(e){return this.sparse.has(e)}}class a{id;_world;constructor(e,t){this.id=t,this._world=e}add(e,t){this._world.addComponent(this.id,e,t)}remove(e){this._world.removeComponent(this.id,e)}has(e){return this._world.componentManager.hasComponent(this.id,e)}get(e){return this._world.componentManager.getComponent(this.id,e)}}class l extends o{entityMap=new d;nextId=0;_world;constructor(e){super(),this._world=e}get entities(){return this.entityMap}create(){const e=this.nextId++;return this.entities.add(e),this.emit("create",e),e}exist(e){return this.entities.has(e)}size(){return this.entities.size()}destroy(e){return this.entities.remove(e),this.emit("destroy",e),e}}class m extends o{componentSet={};world;constructor(e){super(),this.world=e}clearComponentSchema(e){this.componentSet[e]&&delete this.componentSet[e]}addComponent(e,t,s){if(!this.world.exist(e))return;const n=this.componentSet[e];n?n[t]=s:this.componentSet[e]={[t]:s},this.emit("add",{entityId:e,component:t})}getComponent(e,t){if(this.hasComponent(e,t))return this.componentSet[e][t]}hasComponent(e,t){const s=this.componentSet[e];return s?t in s:!1}removeComponent(e,t){if(!this.componentSet[e])return;const s=this.componentSet[e];s&&s[t]!==void 0&&(delete s[t],Object.keys(s).length===0&&delete this.componentSet[e],this.emit("remove",{entityId:e,component:t}))}}class c extends o{config;entityMap;entityInstancesCache;world;constructor(e,t){super(),this.config=e,this.world=t,this.entityInstancesCache=new Map,this.entityMap=new d,this.on("removed",s=>{this.entityInstancesCache.delete(s)})}hasComponents(e){const t=this.world.componentManager;return this.config.include?.every(s=>t.getComponent(e,s))&&this.config.exclude?.every(s=>!t.getComponent(e,s))}size(){return this.ids.size()}get hash(){return c.getHash(this.config)}get ids(){return this.entityMap}getCachedEntity(e){const t=this.entityInstancesCache.get(e);if(t)return t;{const s=this.world.getEntity(e);return s?(this.entityInstancesCache.set(e,s),s):void 0}}get entities(){const e=[],t=this.entityMap.size()>100;return this.entityMap.forEach(s=>{if(t){const n=this.getCachedEntity(s);n&&e.push(n)}else{const n=this.world.getEntity(s);n&&e.push(n)}}),e}include(...e){return this.world.include(...e)}exclude(...e){return this.world.exclude(...e)}_checkExistingEntities(){for(let e of this.ids)this.world.exist(e)||(this.emit("removed",e),this.entityMap.remove(e))}checkEntities(){this.entityInstancesCache.clear();for(let e of this.world.entityIds)this.hasComponents(e)&&(this.entityMap.add(e),this.emit("added",e));this._checkExistingEntities()}static getHash(e){const t=e.include?.map(i=>i.trim()).filter(i=>i).join("_"),s=e.exclude?.map(i=>i.trim()).filter(i=>i).join("_"),n="in_"+t+"_out_"+s;let h=0;for(const i of n)h=(h<<5)-h+i.charCodeAt(0),h|=0;return h}}class p extends o{_startTime=0;_oldTime=0;_requestId=0;running=!1;delta=0;elapsed=0;constructor(){super()}_loop(){let e=0;if(this.running){const t=performance.now();e=(t-this._oldTime)/1e3,this._oldTime=t,this.elapsed+=e}this.delta=e,this.emit("update"),this._requestId=requestAnimationFrame(this._loop.bind(this))}start(){this._startTime=performance.now(),this._oldTime=this._startTime,this.elapsed=0,this.delta=0,this.running=!0,this._loop()}stop(){this.running=!1,cancelAnimationFrame(this._requestId),this._requestId=0}}let g=new p;class f extends o{entityManager;componentManager;systemManager;queries;constructor(){super(),this.entityManager=new l(this),this.componentManager=new m(this),this.systemManager=new u,this.entityManager.on("create",e=>{this.emit("entityCreated",{entity:new a(this,e)}),this._updateQueries()}),this.entityManager.on("destroy",e=>{this.emit("entityDestroyed",{entity:new a(this,e)}),this._updateQueries(),this.componentManager.clearComponentSchema(e)}),this.componentManager.on("add",({entityId:e,component:t})=>{this.emit("componentAdded",{entity:new a(this,e),component:t}),this._updateQueries()}),this.componentManager.on("remove",({entityId:e,component:t})=>{this.getEntity(e)&&this.emit("componentRemoved",{entity:new a(this,e),component:t}),this._updateQueries()}),this.queries=new Map}getEntity(e){return this.exist(e)?new a(this,e):void 0}get entityIds(){return this.entityManager.entities}query(e){const t=c.getHash(e);let n=this.queries.get(t);return n||(n=new c(e,this),this.queries.set(t,n),this._updateQueries()),n}_updateQueries(){this.queries.forEach(e=>e.checkEntities())}exist(e){return this.entityManager.exist(e)}include(...e){return this.query({include:e,exclude:[]})}exclude(...e){return this.query({include:[],exclude:e})}create(){return this.entityManager.create()}destroy(e){this.entityManager.destroy(e)}addSystem(e){this.systemManager.addSystem(e)}removeSystem(e){this.systemManager.removeSystem(e)}addComponent(e,t,s){this.componentManager.addComponent(e,t,s)}getComponent(e,t){return this.componentManager.getComponent(e,t)}removeComponent(e,t){this.componentManager.removeComponent(e,t)}update(){this.systemManager.systemList.forEach(e=>{e.update()}),this.emit("updated")}}exports.ComponentManager=m;exports.EntityManager=l;exports.EventRegistry=o;exports.Query=c;exports.SparseSet=d;exports.SystemManager=u;exports.Time=g;exports.World=f;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class o{_listeners=new Map;on(e,t){if(this.contains(e,t))return;const s=this._listeners.get(e);s?s.add(t):this._listeners.set(e,new Set([t]))}off(e,t){if(!this.contains(e,t))return;const s=this._listeners.get(e);s&&s.delete(t)}once(e,t){const s=i=>{t(i),this.off(e,s)};this.on(e,s)}clearEvent(e){this._listeners.get(e)&&this._listeners.get(e)?.clear()}clearAllEvents(){this._listeners.forEach(e=>e.clear()),this._listeners.clear()}contains(e,t){return this._listeners.get(e)?this._listeners.get(e).has(t):!1}emit(e,t){this._listeners.get(e)&&this._listeners.get(e)?.forEach(s=>{s(t)})}}class c{systemList=[];addSystem(e){this.systemList.push(e),e.init?.(),this.reorder()}reorder(){this.systemList.sort((e,t)=>e.priority-t.priority)}has(e){return this.systemList.indexOf(e)>0}removeSystem(e){if(!this.has(e))return;const t=this.systemList.indexOf(e);t>=0&&(this.systemList.splice(t,1),e.exit?.(),this.reorder())}}class d{denseValues=[];sparse=new Map;[Symbol.iterator](){let e=this.values.length;const t={value:void 0,done:!1};return{next:()=>(t.value=this.values[--e],t.done=e<0,t)}}get values(){return this.denseValues}first(){return this.denseValues[0]}add(e){this.has(e)||(this.denseValues.push(e),this.sparse.set(e,this.denseValues.length-1))}indexOf(e){const t=this.sparse.get(e);return t!==void 0?t:-1}remove(e){if(!this.has(e))return;const t=this.sparse.get(e);this.sparse.delete(e);const s=this.denseValues[this.denseValues.length-1];s!==e&&(this.denseValues[t]=s,this.sparse.set(s,t)),this.denseValues.pop()}forEach(e){for(let t of this)e(t)}size(){return this.denseValues.length}clear(){for(let e of this)this.remove(e)}has(e){return this.sparse.has(e)}}class m{id;_world;constructor(e,t){this.id=t,this._world=e}add(e,t){this._world.addComponent(this.id,e,t)}remove(e){this._world.removeComponent(this.id,e)}has(e){return this._world.componentManager.hasComponent(this.id,e)}get(e){return this._world.componentManager.getComponent(this.id,e)}}class l extends o{entityMap=new d;nextId=0;_world;constructor(e){super(),this._world=e}get entities(){return this.entityMap}create(){const e=this.nextId++;return this.entities.add(e),this.emit("create",e),e}exist(e){return this.entities.has(e)}size(){return this.entities.size()}destroy(e){return this.entities.remove(e),this.emit("destroy",e),e}}class u extends o{componentSet={};world;dirtyEntities=new Set;constructor(e){super(),this.world=e}clearComponentSchema(e){this.componentSet[e]&&delete this.componentSet[e]}addComponent(e,t,s){if(!this.world.exist(e))return;const i=this.componentSet[e];i?i[t]=s:this.componentSet[e]={[t]:s},this.dirtyEntities.add(e),this.emit("add",{entityId:e,component:t})}getComponent(e,t){if(this.hasComponent(e,t))return this.componentSet[e][t]}cleanDirtyEntities(){this.dirtyEntities.clear()}hasComponent(e,t){const s=this.componentSet[e];return s?t in s:!1}removeComponent(e,t){if(!this.componentSet[e])return;const s=this.componentSet[e];s&&s[t]!==void 0&&(delete s[t],Object.keys(s).length===0&&delete this.componentSet[e],this.dirtyEntities.add(e),this.emit("remove",{entityId:e,component:t}))}}class h extends o{config;entityMap;world;lastVersion=0;dirty;constructor(e,t){super(),this.config=e,this.world=t,this.entityMap=new d,this.dirty=!1,this.world.on("entityDestroyed",({entityId:s})=>{this.entityMap.has(s)&&(this.emit("removed",s),this.entityMap.remove(s))})}hasComponents(e){const t=this.world.componentManager;return this.config.include?.every(s=>t.getComponent(e,s))&&this.config.exclude?.every(s=>!t.getComponent(e,s))}size(){return this.ids.size()}get hash(){return h.getHash(this.config)}get ids(){return this.entityMap}get entities(){const e=[];return this.entityMap.forEach(t=>{const s=this.world.getEntity(t);s&&e.push(s)}),e}include(...e){return this.world.include(...e)}exclude(...e){return this.world.exclude(...e)}markDirty(){this.dirty=!0}checkEntities(e){if(!this.dirty||this.world.version===this.lastVersion)return;const t=e||this.world.entityIds;for(let s of t)this.hasComponents(s)&&(this.entityMap.add(s),this.emit("added",s));this.dirty=!1,this.lastVersion=this.world.version}static getHash(e){const t=e.include?.map(n=>n.trim()).filter(n=>n).join("_"),s=e.exclude?.map(n=>n.trim()).filter(n=>n).join("_"),i="in_"+t+"_out_"+s;let a=0;for(const n of i)a=(a<<5)-a+n.charCodeAt(0),a|=0;return a}}class p extends o{_startTime=0;_oldTime=0;_requestId=0;running=!1;delta=0;elapsed=0;constructor(){super()}_loop(){let e=0;if(this.running){const t=performance.now();e=(t-this._oldTime)/1e3,this._oldTime=t,this.elapsed+=e}this.delta=e,this.emit("update"),this._requestId=requestAnimationFrame(this._loop.bind(this))}start(){this._startTime=performance.now(),this._oldTime=this._startTime,this.elapsed=0,this.delta=0,this.running=!0,this._loop()}stop(){this.running=!1,cancelAnimationFrame(this._requestId),this._requestId=0}}let g=new p;class f extends o{entityManager;componentManager;systemManager;queries;version;constructor(){super(),this.entityManager=new l(this),this.componentManager=new u(this),this.systemManager=new c,this.version=0,this.entityManager.on("create",e=>{this.emit("entityCreated",{entityId:e}),this._updateQueries()}),this.entityManager.on("destroy",e=>{this.emit("entityDestroyed",{entityId:e}),this._updateQueries(),this.componentManager.clearComponentSchema(e)}),this.componentManager.on("add",({entityId:e,component:t})=>{this.emit("componentAdded",{entityId:e,component:t}),this._updateQueries()}),this.componentManager.on("remove",({entityId:e,component:t})=>{this.getEntity(e)&&this.emit("componentRemoved",{entityId:e,component:t}),this._updateQueries()}),this.queries=new Map}getEntity(e){return this.exist(e)?new m(this,e):void 0}get entityIds(){return this.entityManager.entities}query(e){const t=h.getHash(e);let i=this.queries.get(t);return i||(i=new h(e,this),this.queries.set(t,i),this._updateQueries()),i}_updateQueries(){const e=this.componentManager.dirtyEntities;this.queries.forEach(t=>{t.markDirty(),e.size>0&&t.checkEntities()}),this.componentManager.cleanDirtyEntities(),this.version++}exist(e){return this.entityManager.exist(e)}include(...e){return this.query({include:e,exclude:[]})}exclude(...e){return this.query({include:[],exclude:e})}create(){return this.entityManager.create()}destroy(e){this.entityManager.destroy(e)}addSystem(e){this.systemManager.addSystem(e)}removeSystem(e){this.systemManager.removeSystem(e)}addComponent(e,t,s){this.componentManager.addComponent(e,t,s)}getComponent(e,t){return this.componentManager.getComponent(e,t)}removeComponent(e,t){this.componentManager.removeComponent(e,t)}update(){this.systemManager.systemList.forEach(e=>{e.update()}),this.emit("updated")}}exports.ComponentManager=u;exports.EntityManager=l;exports.EventRegistry=o;exports.Query=h;exports.SparseSet=d;exports.SystemManager=c;exports.Time=g;exports.World=f;
@@ -1,4 +1,4 @@
1
- class a {
1
+ class h {
2
2
  _listeners = /* @__PURE__ */ new Map();
3
3
  on(e, t) {
4
4
  if (this.contains(e, t))
@@ -13,9 +13,9 @@ class a {
13
13
  s && s.delete(t);
14
14
  }
15
15
  once(e, t) {
16
- const s = ((n) => {
17
- t(n), this.off(e, s);
18
- });
16
+ const s = (i) => {
17
+ t(i), this.off(e, s);
18
+ };
19
19
  this.on(e, s);
20
20
  }
21
21
  clearEvent(e) {
@@ -33,7 +33,7 @@ class a {
33
33
  });
34
34
  }
35
35
  }
36
- class u {
36
+ class c {
37
37
  systemList = [];
38
38
  addSystem(e) {
39
39
  this.systemList.push(e), e.init?.(), this.reorder();
@@ -98,7 +98,7 @@ class d {
98
98
  return this.sparse.has(e);
99
99
  }
100
100
  }
101
- class h {
101
+ class l {
102
102
  id;
103
103
  _world;
104
104
  constructor(e, t) {
@@ -136,7 +136,7 @@ class h {
136
136
  return this._world.componentManager.getComponent(this.id, e);
137
137
  }
138
138
  }
139
- class l extends a {
139
+ class u extends h {
140
140
  entityMap = new d();
141
141
  nextId = 0;
142
142
  _world;
@@ -160,9 +160,10 @@ class l extends a {
160
160
  return this.entities.remove(e), this.emit("destroy", e), e;
161
161
  }
162
162
  }
163
- class m extends a {
163
+ class m extends h {
164
164
  componentSet = {};
165
165
  world;
166
+ dirtyEntities = /* @__PURE__ */ new Set();
166
167
  constructor(e) {
167
168
  super(), this.world = e;
168
169
  }
@@ -171,13 +172,16 @@ class m extends a {
171
172
  }
172
173
  addComponent(e, t, s) {
173
174
  if (!this.world.exist(e)) return;
174
- const n = this.componentSet[e];
175
- n ? n[t] = s : this.componentSet[e] = { [t]: s }, this.emit("add", { entityId: e, component: t });
175
+ const i = this.componentSet[e];
176
+ i ? i[t] = s : this.componentSet[e] = { [t]: s }, this.dirtyEntities.add(e), this.emit("add", { entityId: e, component: t });
176
177
  }
177
178
  getComponent(e, t) {
178
179
  if (this.hasComponent(e, t))
179
180
  return this.componentSet[e][t];
180
181
  }
182
+ cleanDirtyEntities() {
183
+ this.dirtyEntities.clear();
184
+ }
181
185
  hasComponent(e, t) {
182
186
  const s = this.componentSet[e];
183
187
  return s ? t in s : !1;
@@ -185,17 +189,18 @@ class m extends a {
185
189
  removeComponent(e, t) {
186
190
  if (!this.componentSet[e]) return;
187
191
  const s = this.componentSet[e];
188
- s && s[t] !== void 0 && (delete s[t], Object.keys(s).length === 0 && delete this.componentSet[e], this.emit("remove", { entityId: e, component: t }));
192
+ s && s[t] !== void 0 && (delete s[t], Object.keys(s).length === 0 && delete this.componentSet[e], this.dirtyEntities.add(e), this.emit("remove", { entityId: e, component: t }));
189
193
  }
190
194
  }
191
- class c extends a {
195
+ class a extends h {
192
196
  config;
193
197
  entityMap;
194
- entityInstancesCache;
195
198
  world;
199
+ lastVersion = 0;
200
+ dirty;
196
201
  constructor(e, t) {
197
- super(), this.config = e, this.world = t, this.entityInstancesCache = /* @__PURE__ */ new Map(), this.entityMap = new d(), this.on("removed", (s) => {
198
- this.entityInstancesCache.delete(s);
202
+ super(), this.config = e, this.world = t, this.entityMap = new d(), this.dirty = !1, this.world.on("entityDestroyed", ({ entityId: s }) => {
203
+ this.entityMap.has(s) && (this.emit("removed", s), this.entityMap.remove(s));
199
204
  });
200
205
  }
201
206
  hasComponents(e) {
@@ -210,30 +215,16 @@ class c extends a {
210
215
  return this.ids.size();
211
216
  }
212
217
  get hash() {
213
- return c.getHash(this.config);
218
+ return a.getHash(this.config);
214
219
  }
215
220
  get ids() {
216
221
  return this.entityMap;
217
222
  }
218
- getCachedEntity(e) {
219
- const t = this.entityInstancesCache.get(e);
220
- if (t)
221
- return t;
222
- {
223
- const s = this.world.getEntity(e);
224
- return s ? (this.entityInstancesCache.set(e, s), s) : void 0;
225
- }
226
- }
227
223
  get entities() {
228
- const e = [], t = this.entityMap.size() > 100;
229
- return this.entityMap.forEach((s) => {
230
- if (t) {
231
- const n = this.getCachedEntity(s);
232
- n && e.push(n);
233
- } else {
234
- const n = this.world.getEntity(s);
235
- n && e.push(n);
236
- }
224
+ const e = [];
225
+ return this.entityMap.forEach((t) => {
226
+ const s = this.world.getEntity(t);
227
+ s && e.push(s);
237
228
  }), e;
238
229
  }
239
230
  include(...e) {
@@ -242,25 +233,25 @@ class c extends a {
242
233
  exclude(...e) {
243
234
  return this.world.exclude(...e);
244
235
  }
245
- _checkExistingEntities() {
246
- for (let e of this.ids)
247
- this.world.exist(e) || (this.emit("removed", e), this.entityMap.remove(e));
236
+ markDirty() {
237
+ this.dirty = !0;
248
238
  }
249
- checkEntities() {
250
- this.entityInstancesCache.clear();
251
- for (let e of this.world.entityIds)
252
- this.hasComponents(e) && (this.entityMap.add(e), this.emit("added", e));
253
- this._checkExistingEntities();
239
+ checkEntities(e) {
240
+ if (!this.dirty || this.world.version === this.lastVersion) return;
241
+ const t = e || this.world.entityIds;
242
+ for (let s of t)
243
+ this.hasComponents(s) && (this.entityMap.add(s), this.emit("added", s));
244
+ this.dirty = !1, this.lastVersion = this.world.version;
254
245
  }
255
246
  static getHash(e) {
256
- const t = e.include?.map((i) => i.trim()).filter((i) => i).join("_"), s = e.exclude?.map((i) => i.trim()).filter((i) => i).join("_"), n = "in_" + t + "_out_" + s;
247
+ const t = e.include?.map((n) => n.trim()).filter((n) => n).join("_"), s = e.exclude?.map((n) => n.trim()).filter((n) => n).join("_"), i = "in_" + t + "_out_" + s;
257
248
  let o = 0;
258
- for (const i of n)
259
- o = (o << 5) - o + i.charCodeAt(0), o |= 0;
249
+ for (const n of i)
250
+ o = (o << 5) - o + n.charCodeAt(0), o |= 0;
260
251
  return o;
261
252
  }
262
253
  }
263
- class p extends a {
254
+ class p extends h {
264
255
  _startTime = 0;
265
256
  _oldTime = 0;
266
257
  _requestId = 0;
@@ -286,45 +277,49 @@ class p extends a {
286
277
  }
287
278
  }
288
279
  let g = new p();
289
- class f extends a {
280
+ class f extends h {
290
281
  entityManager;
291
282
  componentManager;
292
283
  systemManager;
293
284
  queries;
285
+ version;
294
286
  constructor() {
295
- super(), this.entityManager = new l(this), this.componentManager = new m(this), this.systemManager = new u(), this.entityManager.on("create", (e) => {
287
+ super(), this.entityManager = new u(this), this.componentManager = new m(this), this.systemManager = new c(), this.version = 0, this.entityManager.on("create", (e) => {
296
288
  this.emit("entityCreated", {
297
- entity: new h(this, e)
289
+ entityId: e
298
290
  }), this._updateQueries();
299
291
  }), this.entityManager.on("destroy", (e) => {
300
292
  this.emit("entityDestroyed", {
301
- entity: new h(this, e)
293
+ entityId: e
302
294
  }), this._updateQueries(), this.componentManager.clearComponentSchema(e);
303
295
  }), this.componentManager.on("add", ({ entityId: e, component: t }) => {
304
296
  this.emit("componentAdded", {
305
- entity: new h(this, e),
297
+ entityId: e,
306
298
  component: t
307
299
  }), this._updateQueries();
308
300
  }), this.componentManager.on("remove", ({ entityId: e, component: t }) => {
309
301
  this.getEntity(e) && this.emit("componentRemoved", {
310
- entity: new h(this, e),
302
+ entityId: e,
311
303
  component: t
312
304
  }), this._updateQueries();
313
305
  }), this.queries = /* @__PURE__ */ new Map();
314
306
  }
315
307
  getEntity(e) {
316
- return this.exist(e) ? new h(this, e) : void 0;
308
+ return this.exist(e) ? new l(this, e) : void 0;
317
309
  }
318
310
  get entityIds() {
319
311
  return this.entityManager.entities;
320
312
  }
321
313
  query(e) {
322
- const t = c.getHash(e);
323
- let n = this.queries.get(t);
324
- return n || (n = new c(e, this), this.queries.set(t, n), this._updateQueries()), n;
314
+ const t = a.getHash(e);
315
+ let i = this.queries.get(t);
316
+ return i || (i = new a(e, this), this.queries.set(t, i), this._updateQueries()), i;
325
317
  }
326
318
  _updateQueries() {
327
- this.queries.forEach((e) => e.checkEntities());
319
+ const e = this.componentManager.dirtyEntities;
320
+ this.queries.forEach((t) => {
321
+ t.markDirty(), e.size > 0 && t.checkEntities();
322
+ }), this.componentManager.cleanDirtyEntities(), this.version++;
328
323
  }
329
324
  exist(e) {
330
325
  return this.entityManager.exist(e);
@@ -364,11 +359,11 @@ class f extends a {
364
359
  }
365
360
  export {
366
361
  m as ComponentManager,
367
- l as EntityManager,
368
- a as EventRegistry,
369
- c as Query,
362
+ u as EntityManager,
363
+ h as EventRegistry,
364
+ a as Query,
370
365
  d as SparseSet,
371
- u as SystemManager,
366
+ c as SystemManager,
372
367
  g as Time,
373
368
  f as World
374
369
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jael-ecs/core",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Entity Component System library with typescript",
5
5
  "keywords": [
6
6
  "ecs",