mani-game-engine 1.0.0-pre.8 → 1.0.0-pre.80

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/src/entity.ts CHANGED
@@ -1,28 +1,126 @@
1
1
  import {GameEngine} from './gameEngine';
2
2
  import {Signal} from 'mani-signal';
3
3
  import {Class} from './types';
4
- import {entityComponents} from './ecsInjector';
4
+ import {ID, putIfAbsent} from './injector';
5
+ import {EcsInjector} from './ecsInjector';
6
+ import {entitySignalHandlers} from './systemContext';
5
7
 
6
8
  let idCounter = 0;
7
9
 
8
10
  export class Entity {
9
11
 
10
- readonly id: number;
11
12
  readonly children = new Set<Entity>();
12
- private gameEngine?: GameEngine;
13
+ readonly gameEngine?: GameEngine;
14
+
15
+ get isAdded() {
16
+ return !!this.gameEngine;
17
+ }
13
18
 
14
19
  // TODO: use single rootEntity class instead of undefined?
15
20
  private _parent?: Entity;
16
21
  readonly parentChanged = new Signal();
17
22
  readonly onRemove = new Signal();
18
23
  private markRemoval = false;
24
+ private _detached = false;
25
+ readonly _signalMap: Map<ID, Signal> = new Map<ID, Signal>();
26
+
27
+ // this is filled by the injector
28
+ private _components?: Map<Class, InstanceType<any>>;
29
+ get components(): ReadonlyMap<Class, InstanceType<any>> {
30
+ if (!this._components) {
31
+ this._components = new Map<Class, InstanceType<any>>();
32
+ EcsInjector.entityComponents.get(this.constructor as Class)!.forEach((field, componentClass) =>
33
+ this._components!.set(componentClass, (this as any)[field]));
34
+ }
35
+ return this._components;
36
+ }
37
+
38
+
39
+ private _dynamicComponents = new Map<Class, InstanceType<any>>();
40
+
41
+ readonly dynamicComponentAdded = new Signal<InstanceType<any>>();
42
+ readonly dynamicComponentRemoved = new Signal<InstanceType<any>>();
43
+
44
+ get dynamicComponents(): ReadonlyMap<Class, InstanceType<any>> {
45
+ return this._dynamicComponents;
46
+ }
47
+
48
+ // TODO: move the logic to gameEngine?
49
+ addDynamicComponent<T extends Class>(component: InstanceType<T>):this {
50
+ if (this.hasDynamicComponentClass(component.constructor)) {
51
+ throw new Error('component of this class already added');
52
+ }
53
+ this._dynamicComponents.set(component.constructor as T, component);
54
+
55
+ this.dynamicComponentAdded.dispatch(component);
56
+ if (!this.gameEngine) return this;
57
+
58
+ const systemContexts = this.gameEngine.injector.createSystemsForDynamicComponents(component.constructor, this);
59
+
60
+ if (!systemContexts) return this;
61
+ const preparePromises = [];
62
+ for (const context of systemContexts) {
63
+ context.system.onPrepare && preparePromises.push(context.system.onPrepare());
64
+ }
65
+ for (const context of systemContexts) {
66
+ this.gameEngine['entitySystemMap'].get(this)?.add(context);
67
+ }
68
+
69
+ if (preparePromises.length === 0) {
70
+ this.gameEngine['startSystems'](systemContexts, () => {});
71
+ } else {
72
+ Promise.all(preparePromises).then(() => this.gameEngine?.['startSystems'](systemContexts));
73
+ }
74
+ return this;
75
+ }
19
76
 
20
- constructor() {
21
- this.id = idCounter++;
77
+ hasDynamicComponentClass<T extends Class>(componentClass: T): boolean {
78
+ return this._dynamicComponents.has(componentClass);
22
79
  }
23
80
 
81
+ // TODO: move the logic to gameEngine?
82
+ removeDynamicComponentClass<T extends Class>(componentClass: T) {
83
+ const systemContexts = this.gameEngine?.['entitySystemMap'].get(this);
84
+ systemContexts?.forEach(systemContext => {
85
+ const systemToDependencies = this.gameEngine?.injector['dynamicComponentDependencyMap'];
86
+ const dependencies = systemToDependencies?.get((systemContext.system as any).constructor);
87
+ if (!dependencies) return;
88
+ if (dependencies.some(dependency => dependency.type === componentClass)) {
89
+ this.gameEngine?.['disposeContext']?.(systemContext);
90
+ systemContexts?.delete(systemContext);
91
+ }
92
+ });
93
+ const component = this._dynamicComponents.get(componentClass);
94
+ if (!this._dynamicComponents.delete(componentClass)) {
95
+ throw new Error('component not found: ' + JSON.stringify(componentClass));
96
+ }
97
+ this.dynamicComponentRemoved.dispatch(component);
98
+ }
99
+
100
+ removeDynamicComponent<T extends Class>(component: InstanceType<T>) {
101
+ this.removeDynamicComponentClass(component.constructor as T);
102
+ }
103
+
104
+ getDynamicComponent<T extends Class>(componentClass: T): InstanceType<T> | undefined {
105
+ return this._dynamicComponents.get(componentClass);
106
+ }
107
+
108
+ getDynamicComponentOrThrow<T extends Class>(componentClass: T): InstanceType<T> {
109
+ const component = this._dynamicComponents.get(componentClass);
110
+ if (!component) {
111
+ throw new Error(`DynamicComponent '${componentClass.name}' not found in entity of type '${this.constructor.name}'.`);
112
+ }
113
+ return component;
114
+ }
115
+
116
+ get detached(): boolean { return this._detached; }
117
+
24
118
  async onBeforeRemove?(): Promise<void>;
25
119
 
120
+ getSignal<T>(id: ID): Signal<T> {
121
+ return putIfAbsent(this._signalMap, id, () => new Signal<any>());
122
+ }
123
+
26
124
  private setParent(parent: Entity | undefined) {
27
125
  if (parent === this._parent) {
28
126
  //TODO: i think this check can be removed
@@ -37,7 +135,7 @@ export class Entity {
37
135
  }
38
136
 
39
137
  getComponent<T extends Class>(componentClass: T): InstanceType<T> | undefined {
40
- const componentMap = entityComponents.get(this.constructor as Class);
138
+ const componentMap = EcsInjector.entityComponents.get(this.constructor as Class);
41
139
  if (!componentMap) {
42
140
  throw new Error(`No components in entity of type '${this.constructor.name}'.`);
43
141
  }
@@ -45,6 +143,18 @@ export class Entity {
45
143
  return key && (<any>this)[key];
46
144
  }
47
145
 
146
+ getComponentOrThrow<T extends Class>(componentClass: T): InstanceType<T> {
147
+ const componentMap = EcsInjector.entityComponents.get(this.constructor as Class);
148
+ if (!componentMap) {
149
+ throw new Error(`No components in entity of type '${this.constructor.name}'.`);
150
+ }
151
+ const key = componentMap.get(componentClass as T);
152
+ if (!key) {
153
+ throw new Error(`Component '${componentClass.name}' not found in entity of type '${this.constructor.name}'.`);
154
+ }
155
+ return (<any>this)[key];
156
+ }
157
+
48
158
  async add(entity: Entity) {
49
159
  if (entity.parent === this) {
50
160
  throw new Error('Entity is already a child');
@@ -85,9 +195,15 @@ export class Entity {
85
195
  if (this.gameEngine) {
86
196
  return await this.gameEngine.remove(entity);
87
197
  }
198
+ entity._signalMap.forEach(signal => signal.detachAll());
88
199
  }
89
200
 
90
201
  async detach() {
202
+ if (this._detached) {
203
+ throw new Error('entity already detached');
204
+ }
205
+ this._detached = true;
206
+ // TODO: mark for detach and prevent another call, because this.gameEngine is set to undefined until next frame
91
207
  if (this.parent) {
92
208
  await this.parent.remove(this);
93
209
  } else {
@@ -113,4 +229,8 @@ export class Entity {
113
229
  }
114
230
  return target;
115
231
  }
232
+
233
+ hasDynamicComponents() {
234
+ return this._dynamicComponents.size > 0;
235
+ }
116
236
  }