mani-game-engine 1.0.0-pre.9 → 1.0.0-pre.90

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/clock.ts CHANGED
@@ -6,110 +6,191 @@ const MIN_TIME_SCALE = 0.00001;
6
6
  // type OnUpdateParams = { time: number, deltaTime: number, alpha: number; };
7
7
 
8
8
  const defaultOptions = {
9
- autoStart: true,
10
- fixedDeltaTime: 1 / 60,
11
- maxFrameTime: 0.25,
12
- timeScale: 1,
9
+ autoStart: true,
10
+ fixedDeltaTime: 1 / 60,
11
+ maxFrameTime: 0.25,
12
+ timeScale: 1,
13
13
  };
14
14
 
15
+ export type Timeout = {
16
+ recall?: number;
17
+ callback: Function;
18
+ callTime: number;
19
+ };
15
20
  export type GameClockOptions = Partial<typeof defaultOptions>;
16
21
 
17
22
  export interface Clock {
18
-
19
- onPrepare: Signal<unknown>;
20
- onEarlyUpdate: Signal<OnEarlyUpdateParams>;
21
- onUpdate: Signal<OnUpdateParams>;
22
- onFixedUpdate: Signal<OnFixedUpdateParams>;
23
- timeScale: number;
24
- start(): this;
25
- stop(): this;
23
+ readonly gameTime: number;
24
+ onPrepare: Signal<unknown>;
25
+ onEarlyUpdate: Signal<OnEarlyUpdateParams>;
26
+ onUpdate: Signal<OnUpdateParams>;
27
+ onFixedUpdate: Signal<OnFixedUpdateParams>;
28
+ timeScale: number;
29
+ start(): this;
30
+ stop(): this;
31
+ dispose(): void;
32
+ setTimeout(callback: Function, delay: number): number;
33
+ setInterval(callback: Function, delay: number): number;
34
+ clearTimeout(timeoutId: number): boolean;
35
+ fixedTween(duration: number, from: number, to: number, onUpdate: (t: number) => void): Promise<void>;
36
+ tween(duration: number, from: number, to: number, onUpdate: (t: number) => void): Promise<void>;
26
37
  }
27
38
 
28
39
  export class GameClock implements Clock {
29
- private isRunning = false;
30
- private requestId = 0;
31
- private currentTime = 0;
32
- private accumulator = 0;
33
- private gameTime = 0;
34
-
35
- private readonly _fixedDeltaTime: number;
36
- private update = (time: number) => {
37
- const newTime = time * 0.001;
38
- if (!this.isRunning) {
39
- this.currentTime = newTime;
40
- this.isRunning = true;
41
- }
40
+ get gameTime(): number {
41
+ return this._gameTime;
42
+ }
43
+ get animationTime(): number {
44
+ return this._animationTime;
45
+ }
46
+ private _animationTime = 0;
47
+ private nextTimeoutId = 0;
48
+ private timeouts = new Map<number, Timeout>();
49
+ private isRunning = false;
50
+ private requestId = 0;
51
+ private currentTime = 0;
52
+ private accumulator = 0;
53
+ private _gameTime = 0;
54
+
55
+ private readonly _fixedDeltaTime: number;
56
+ private update = (time: number) => {
57
+ const newTime = time * 0.001;
58
+ if (!this.isRunning) {
59
+ this.currentTime = newTime;
60
+ this.isRunning = true;
61
+ }
42
62
 
43
- let frameTime = (newTime - this.currentTime) * this.timeScale;
63
+ let frameTime = (newTime - this.currentTime) * this.timeScale;
44
64
 
45
- if (frameTime > this.maxFrameTime) {
46
- frameTime = this.maxFrameTime;
47
- }
65
+ if (frameTime > this.maxFrameTime) {
66
+ frameTime = this.maxFrameTime;
67
+ }
48
68
 
49
- this.currentTime = newTime;
50
- this.accumulator += frameTime;
51
- let isMultipleFixedUpdate = false;
52
-
53
- this.onPrepare.dispatch();
54
- this.onEarlyUpdate.dispatch({time: this.gameTime, deltaTime: frameTime});
55
- while (this.accumulator >= this._fixedDeltaTime) {
56
- if (isMultipleFixedUpdate) {
57
- this.onPrepare.dispatch();
58
- }
59
- this.onFixedUpdate.dispatch({time: this.gameTime, deltaTime: this._fixedDeltaTime});
60
- isMultipleFixedUpdate = true;
61
- this.accumulator -= this._fixedDeltaTime;
62
- this.gameTime += this._fixedDeltaTime;
63
- }
64
- const alpha = this.accumulator / this._fixedDeltaTime;
65
- this.onUpdate.dispatch({time: this.gameTime + alpha * this._fixedDeltaTime, deltaTime: frameTime, alpha: alpha});
66
- if (this.isRunning) {
67
- this.requestId = requestAnimationFrame(this.update);
69
+ this.currentTime = newTime;
70
+ this.accumulator += frameTime;
71
+
72
+ while (this.accumulator >= this._fixedDeltaTime) {
73
+ this.onEarlyUpdate.dispatch({time: this._gameTime, deltaTime: frameTime});
74
+ this.onPrepare.dispatch();
75
+ for (const [id, timeOut] of this.timeouts) {
76
+ if (this._gameTime >= timeOut.callTime) {
77
+ timeOut.callback();
78
+ if (timeOut.recall) {
79
+ timeOut.callTime += timeOut.recall;
80
+ } else {
81
+ this.timeouts.delete(id);
82
+ }
68
83
  }
69
- };
84
+ }
85
+ this.onFixedUpdate.dispatch({time: this._gameTime, deltaTime: this._fixedDeltaTime});
86
+ this.accumulator -= this._fixedDeltaTime;
87
+ this._gameTime += this._fixedDeltaTime;
88
+ }
89
+ const alpha = this.accumulator / this._fixedDeltaTime;
90
+ this._animationTime = this._gameTime + alpha * this._fixedDeltaTime;
91
+ this.onUpdate.dispatch({time: this._animationTime, deltaTime: frameTime, alpha: alpha});
92
+ if (this.isRunning) {
93
+ this.requestId = requestAnimationFrame(this.update);
94
+ }
95
+ };
70
96
 
71
- private maxFrameTime: number;
97
+ private readonly maxFrameTime: number;
72
98
 
73
- readonly onPrepare = new Signal();
74
- readonly onEarlyUpdate = new Signal<OnEarlyUpdateParams>();
75
- readonly onUpdate = new Signal<OnUpdateParams>();
76
- readonly onFixedUpdate = new Signal<OnFixedUpdateParams>();
99
+ readonly onPrepare = new Signal();
100
+ readonly onEarlyUpdate = new Signal<OnEarlyUpdateParams>();
101
+ readonly onUpdate = new Signal<OnUpdateParams>();
102
+ readonly onFixedUpdate = new Signal<OnFixedUpdateParams>();
77
103
 
78
- constructor(options?: GameClockOptions) {
79
- const opts = {...defaultOptions, ...options} as Required<GameClockOptions>;
80
- this._fixedDeltaTime = opts.fixedDeltaTime;
81
- this.maxFrameTime = opts.maxFrameTime;
82
- this.timeScale = opts.timeScale;
104
+ constructor(options?: GameClockOptions) {
105
+ const opts = {...defaultOptions, ...options} as Required<GameClockOptions>;
106
+ this._fixedDeltaTime = opts.fixedDeltaTime;
107
+ this.maxFrameTime = opts.maxFrameTime;
108
+ this.timeScale = opts.timeScale;
83
109
 
84
- opts.autoStart && this.start();
85
- }
110
+ opts.autoStart && this.start();
111
+ }
86
112
 
87
- get fixedDeltaTime(): number { return this._fixedDeltaTime; }
113
+ dispose(): void {
114
+ this.onPrepare.detachAll();
115
+ this.onEarlyUpdate.detachAll();
116
+ this.onUpdate.detachAll();
117
+ this.onFixedUpdate.detachAll();
118
+ }
88
119
 
89
- private _timeScale = 1;
120
+ get fixedDeltaTime(): number { return this._fixedDeltaTime; }
90
121
 
91
- get timeScale(): number {
92
- return this._timeScale;
93
- }
122
+ private _timeScale = 1;
94
123
 
95
- set timeScale(value: number) {
96
- this._timeScale = Math.max(value, MIN_TIME_SCALE);
97
- }
124
+ get timeScale(): number {
125
+ return this._timeScale;
126
+ }
98
127
 
99
- start(): this {
100
- if (this.isRunning) {
101
- return this;
102
- }
103
- this.requestId = requestAnimationFrame(this.update);
104
- return this;
128
+ set timeScale(value: number) {
129
+ this._timeScale = Math.max(value, MIN_TIME_SCALE);
130
+ }
131
+
132
+ start(): this {
133
+ if (this.isRunning) {
134
+ return this;
105
135
  }
136
+ this.requestId = requestAnimationFrame(this.update);
137
+ return this;
138
+ }
106
139
 
107
- stop(): this {
108
- if (!this.isRunning) {
109
- return this;
110
- }
111
- this.isRunning = false;
112
- cancelAnimationFrame(this.requestId);
113
- return this;
140
+ stop(): this {
141
+ if (!this.isRunning) {
142
+ return this;
114
143
  }
144
+ this.isRunning = false;
145
+ cancelAnimationFrame(this.requestId);
146
+ return this;
147
+ }
148
+
149
+ setTimeout(callback: Function, delay: number) {
150
+ const timeOut: Timeout = {callback, callTime: this._gameTime + delay};
151
+ this.nextTimeoutId++;
152
+ this.timeouts.set(this.nextTimeoutId, timeOut);
153
+ return this.nextTimeoutId;
154
+ }
155
+
156
+ setInterval(callback: Function, delay: number) {
157
+ const timeOut: Timeout = {callback, callTime: this._gameTime + delay, recall: delay};
158
+ this.nextTimeoutId++;
159
+ this.timeouts.set(this.nextTimeoutId, timeOut);
160
+ return this.nextTimeoutId;
161
+ }
162
+
163
+ async fixedTween(duration: number, from: number, to: number, onUpdate: (t: number) => void) {
164
+ return new Promise<void>((resolve) => {
165
+ const startTime = this.gameTime;
166
+
167
+ const binding = this.onFixedUpdate.add(({time}) => {
168
+ const t = Math.min(1, (this.gameTime - startTime) / duration);
169
+ onUpdate(from + (to - from) * t);
170
+ if (t >= 1) {
171
+ binding.detach();
172
+ resolve();
173
+ }
174
+ });
175
+ });
176
+ }
177
+
178
+ async tween(duration: number, from: number, to: number, onUpdate: (t: number) => void) {
179
+ return new Promise<void>((resolve) => {
180
+ const startTime = this._animationTime;
181
+
182
+ const binding = this.onUpdate.add(({time}) => {
183
+ const t = Math.min(1, (this._animationTime - startTime) / duration);
184
+ onUpdate(from + (to - from) * t);
185
+ if (t >= 1) {
186
+ binding.detach();
187
+ resolve();
188
+ }
189
+ });
190
+ });
191
+ }
192
+
193
+ clearTimeout(timeoutId: number) {
194
+ return this.timeouts.delete(timeoutId);
195
+ }
115
196
  }
@@ -1,32 +1,43 @@
1
1
  // TODO: what if systems for entity without components are created?
2
- import {Class, createDependencyAnnotation, Dependency, ID, Injector, putIfAbsent, ResolverContext, ResolverFunction} from 'mani-injector';
3
2
  import {Signal} from 'mani-signal';
4
- import {SystemContext} from './systemContext';
3
+ import {Class, createDependencyAnnotation, Dependency, ID, Injector, putIfAbsent, ResolverContext, ResolverFunction} from './injector';
4
+ import {entitySignalHandlers, SystemContext} from './systemContext';
5
+ import {Entity} from './entity';
5
6
 
6
7
  type ComponentClass = Class;
7
8
  type EntityClass = Class;
8
9
 
9
10
  type SystemResolvers<T extends Class> = [T, ResolverFunction[]]
10
11
  type ComponentDependency = Dependency & { kind: 'component'; index: number; type: Class; };
12
+ type DynamicComponentDependency = Dependency & { kind: 'dynamic'; index: number; type: Class; };
11
13
  type EntityDependency = Dependency & { kind: 'entity'; index: number; };
12
14
  type ContextDependency = Dependency & { kind: 'context'; index: number; };
13
15
  type SignalDependency = Dependency & { kind: 'signal'; index: number; id: ID; };
16
+ type EntitySignalDependency = Dependency & { kind: 'entitySignal'; index: number; id: ID; };
14
17
 
15
18
  type EntityResolverContext = ResolverContext & { entityClass: Object; };
16
19
 
17
- export const entityComponents = new Map<EntityClass, Map<ComponentClass, string>>();
18
-
19
20
  export const GetComponent = createDependencyAnnotation((type, index): ComponentDependency => ({kind: 'component', type, index}));
21
+ export const GetDynamicComponent = createDependencyAnnotation((type, index): DynamicComponentDependency => ({
22
+ kind: 'dynamic',
23
+ type,
24
+ index,
25
+ }));
20
26
  export const GetEntity = createDependencyAnnotation((_type, index): EntityDependency => ({kind: 'entity', index}));
21
27
  export const GetContext = createDependencyAnnotation((_type, index): ContextDependency => ({kind: 'context', index}));
22
28
  export const GetSignal = (id: ID) => createDependencyAnnotation((_type, index): SignalDependency => ({kind: 'signal', index, id}));
29
+ export const GetEntitySignal = (id: ID) => createDependencyAnnotation((_type, index): EntitySignalDependency => ({
30
+ kind: 'entitySignal',
31
+ index,
32
+ id,
33
+ }));
23
34
  export const EntityComponent = (target: object, propertyKey: string): any => {
24
35
  const entityClass = target.constructor;
25
36
  const componentClass = Reflect.getMetadata('design:type', target, propertyKey);
26
37
  if (componentClass === Object) {
27
38
  throw new Error(`Object component type not allowed. Forgot to specify type of ${entityClass.name}.${propertyKey}?`);
28
39
  }
29
- const componentSet = putIfAbsent(entityComponents, entityClass, () => new Map<ComponentClass, string>());
40
+ const componentSet = putIfAbsent(EcsInjector.entityComponents, entityClass, () => new Map<ComponentClass, string>());
30
41
  componentSet.set(componentClass, propertyKey);
31
42
  };
32
43
 
@@ -42,17 +53,18 @@ export const OnSignal = (id: ID) => (target: any, propertyKey: string, descripto
42
53
  }
43
54
  };
44
55
 
45
- const getComponentDependencies = (system: Class) => {
46
- const dependencies = Injector.dependencyMap.get(system);
47
- return dependencies
48
- ? dependencies.filter((dependency): dependency is ComponentDependency => dependency.kind === 'component')
49
- : [];
56
+ const extractDependenciesWithType = <T extends Dependency>(type: string) => {
57
+ const result = new Map<Class, T[]>();
58
+ Injector.dependencyMap.forEach((dependency, system) => {
59
+ putIfAbsent(result, system, () => [...dependency.filter((dependency) => dependency.kind === type)]);
60
+ });
61
+ return result;
50
62
  };
51
63
 
52
64
  const getEntityClassesForComponentDependencies = (componentTypes: Class[]): Class[] => {
53
65
  // TODO: refactor class, use map, filter etc...
54
66
  const result = [];
55
- for (const [entityClass, componentMap] of entityComponents) {
67
+ for (const [entityClass, componentMap] of EcsInjector.entityComponents) {
56
68
  let allDependenciesMet = true;
57
69
  for (const componentType of componentTypes) {
58
70
  if (!componentMap.has(componentType)) {
@@ -74,10 +86,21 @@ const componentResolver = (context: ResolverContext, dependency: Dependency): Re
74
86
  throw new Error(`Could not resolve Component ${type.name}. @GetComponent only allowed in system scope.`);
75
87
  }
76
88
  const entityClass = (context as EntityResolverContext).entityClass;
77
- const key = entityComponents.get(<any>entityClass as Class)!.get((dependency as ComponentDependency).type);
89
+ const key = EcsInjector.entityComponents.get(<any>entityClass as Class)!.get((dependency as ComponentDependency).type);
78
90
  return (context: SystemContext) => (context.entity as any)[key!];
79
91
  };
80
92
 
93
+ const dynamicComponentResolver = (context: ResolverContext, dependency: Dependency): ResolverFunction => {
94
+ const kind = context.kind;
95
+ const type = context.type;
96
+ if (kind !== 'system') {
97
+ throw new Error(`Could not resolve Component ${type.name}. @GetComponent only allowed in system scope.`);
98
+ }
99
+ // const entityClass = (context as EntityResolverContext).entityClass;
100
+ // const key = entityComponents.get(<any>entityClass as Class)!.get((dependency as ComponentDependency).type);
101
+ return (context: SystemContext) => context.entity.getDynamicComponent((dependency as DynamicComponentDependency).type);
102
+ };
103
+
81
104
  const entityResolver = ({type, kind}: ResolverContext, _dependency: Dependency): ResolverFunction => {
82
105
  if (kind !== 'system') {
83
106
  throw new Error(`Could not resolve Entity in ${type.name}. @GetEntity only allowed in system scope.`);
@@ -85,6 +108,13 @@ const entityResolver = ({type, kind}: ResolverContext, _dependency: Dependency):
85
108
  return (context: SystemContext) => context.entity;
86
109
  };
87
110
 
111
+ const entitySignalResolver = ({type, kind}: ResolverContext, dependency: Dependency): ResolverFunction => {
112
+ if (kind !== 'system') {
113
+ throw new Error(`Could not resolve Entity in ${type.name}. @GetEntity only allowed in system scope.`);
114
+ }
115
+ return (context: SystemContext) => (<Entity>context.entity).getSignal((<EntitySignalDependency>dependency).id);
116
+ };
117
+
88
118
  const contextResolver = ({type, kind}: ResolverContext, _dependency: Dependency): ResolverFunction => {
89
119
  if (kind !== 'system') {
90
120
  throw new Error(`Could not resolve Context in ${type.name}. @GetContext only allowed in system scope.`);
@@ -95,22 +125,31 @@ const contextResolver = ({type, kind}: ResolverContext, _dependency: Dependency)
95
125
  // const isSignalResolver = (dependency: Dependency): dependency is SignalDependency => dependency.kind === 'signal';
96
126
 
97
127
  export class EcsInjector<SystemClass extends Class = Class> extends Injector {
98
- // protected readonly entitySystemMap = new Map<EntityClass, SystemClass[]>();
128
+ static warnNoMatchingEntities = true;
129
+ static entityComponents = new Map<EntityClass, Map<ComponentClass, string>>();
130
+ protected componentDependencyMap = extractDependenciesWithType<ComponentDependency>('component');
131
+ protected dynamicComponentDependencyMap = extractDependenciesWithType<DynamicComponentDependency>('dynamic');
99
132
  protected entitySystemMap: Map<Class, SystemClass[]>;
100
- protected entitySystemResolverTuples: Map<Class, SystemResolvers<SystemClass>[]>;
101
- private signalMap: Map<ID, Signal>;
133
+ protected dynamicComponentSystemMap: Map<Class, SystemClass[]>;
134
+ protected entitySystemResolverTuples: Map<Class, Map<SystemClass, ResolverFunction[]>>;
135
+ private readonly signalMap: Map<ID, Signal>;
102
136
 
103
137
  constructor(parent?: EcsInjector<SystemClass>) {
104
138
  super(parent);
105
139
  this.signalMap = parent ? parent.signalMap : new Map<ID, Signal>();
106
- this.entitySystemMap = parent ? parent.entitySystemMap : new Map<EntityClass, SystemClass[]>();
107
- this.entitySystemResolverTuples = parent ? parent.entitySystemResolverTuples : new Map<EntityClass, SystemResolvers<SystemClass>[]>();
140
+
141
+ // TODO: do we need to have this maps in every child injector?
142
+ this.entitySystemMap = new Map<EntityClass, SystemClass[]>();
143
+ this.dynamicComponentSystemMap = new Map<EntityClass, SystemClass[]>();
144
+ this.entitySystemResolverTuples = new Map<Class, Map<SystemClass, ResolverFunction[]>>();
145
+
108
146
  this.map(EcsInjector).toValue(this);
109
147
 
110
148
  if (!parent) {
111
149
  // only add extension resolvers to the main/parent injector
112
150
  this.addExtensionResolver('entity', entityResolver);
113
151
  this.addExtensionResolver('component', componentResolver);
152
+ this.addExtensionResolver('dynamic', dynamicComponentResolver);
114
153
  this.addExtensionResolver('context', contextResolver);
115
154
 
116
155
  const signalResolver = ({kind}: ResolverContext, dependency: Dependency): ResolverFunction => {
@@ -118,17 +157,30 @@ export class EcsInjector<SystemClass extends Class = Class> extends Injector {
118
157
  return (entity: any) => signal;
119
158
  };
120
159
  this.addExtensionResolver('signal', signalResolver);
160
+ this.addExtensionResolver('entitySignal', entitySignalResolver);
121
161
  }
122
162
  }
123
163
 
164
+ //TODO: this needs some overthinking...
165
+ // newSignalScope() {
166
+ // this.signalMap = new Map<ID, Signal>();
167
+ // }
168
+
124
169
  registerSystem<T extends SystemClass>(systemClass: T) {
125
170
  // TODO: check if system is already mapped
126
- const componentDependencies = getComponentDependencies(systemClass).map(dependency => dependency.type);
171
+ const componentDependencies = this.componentDependencyMap.get(systemClass)?.map(it => it.type) || [];
172
+ const dynamicComponentDependencies = this.dynamicComponentDependencyMap.get(systemClass) || [];
173
+
174
+ for (const dynamicComponentDependency of dynamicComponentDependencies) {
175
+ const systemClassesForDynamicComponent = putIfAbsent(this.dynamicComponentSystemMap, dynamicComponentDependency.type, () => [] as SystemClass[]);
176
+ systemClassesForDynamicComponent.push(systemClass);
177
+ }
178
+
127
179
  if (componentDependencies.length === 0) {
128
180
  throw new Error(`${systemClass.name} needs at least one component dependency.`);
129
181
  }
130
182
  const entityClasses = getEntityClassesForComponentDependencies(componentDependencies);
131
- if (entityClasses.length === 0) {
183
+ if (entityClasses.length === 0 && EcsInjector.warnNoMatchingEntities) {
132
184
  console.warn(`System '${systemClass.name}' has no matching entities.`);
133
185
  return;
134
186
  }
@@ -138,39 +190,81 @@ export class EcsInjector<SystemClass extends Class = Class> extends Injector {
138
190
  }
139
191
  }
140
192
 
141
- createSystems(entity: Object): SystemContext[] {
142
- const systemResolverTuples = putIfAbsent(this.entitySystemResolverTuples, entity.constructor, (): SystemResolvers<SystemClass>[] => {
143
- const systems = this.entitySystemMap.get(entity.constructor as Class);
144
- if (!systems) {
145
- return [];
146
- }
193
+ createSystemsForDynamicComponents(dynamicComponentClass: Class, entity: Entity) {
194
+ const potentialSystems = this.getSystemResolverTuples(entity);
195
+ const systems = this.dynamicComponentSystemMap.get(dynamicComponentClass)!;
147
196
 
148
- const result: SystemResolvers<SystemClass>[] = [];
149
- for (const systemClass of systems) {
150
- result.push([systemClass, this.createResolverArray({type: systemClass, kind: 'system', entityClass: entity.constructor})]);
197
+ if (!systems) {
198
+ return undefined;
199
+ }
200
+
201
+ const finalSystemResolverEntries = new Map<SystemClass, ResolverFunction[]>();
202
+ for (const system of systems) {
203
+ const resolverTuples = potentialSystems.get(system);
204
+ if (resolverTuples) {
205
+ finalSystemResolverEntries.set(system, resolverTuples);
151
206
  }
152
- return result;
153
- });
154
- const systemInstances: SystemContext[] = [];
155
- for (const [system, resolver] of systemResolverTuples) {
207
+ }
208
+
209
+ return this.createSystems(finalSystemResolverEntries, entity);
210
+ }
211
+
212
+ createSystemsForEntity(entity: Entity): Set<SystemContext> {
213
+ const systemResolverTuples = this.getSystemResolverTuples(entity);
214
+ return this.createSystems(systemResolverTuples, entity);
215
+ }
216
+
217
+ private createSystems(systemResolverMap: Map<SystemClass, ResolverFunction[]>, entity: Entity) {
218
+ const systemInstances = new Set<SystemContext>();
219
+ for (const [system, resolver] of systemResolverMap) {
156
220
  const args = new Array(resolver.length);
157
221
 
222
+ const dynamicDependencies = this.dynamicComponentDependencyMap.get(system) || [];
223
+ const hasUnfulfilledDynamicDependencies = dynamicDependencies.some((dependency, index) => {
224
+ return !entity.hasDynamicComponentClass(dependency.type);
225
+ });
226
+ if (hasUnfulfilledDynamicDependencies) {
227
+ continue;
228
+ }
229
+
158
230
  const systemContext = new SystemContext<InstanceType<SystemClass>>(entity);
159
231
 
160
232
  for (let i = 0; i < args.length; i++) {
161
233
  args[i] = resolver[i](systemContext);
162
234
  }
163
235
 
164
- // friend class :)
165
- (systemContext as any).system = new system(...args); // TODO: new class(...args) is very slow in firefox :/
236
+ const newSystem = new system(...args); // TODO: new class(...args) is very slow in firefox :/
166
237
 
167
- // let systemInstance = new (system.bind(system, args[0], args[1], args[2], args[3], args[4]));
238
+ // map entity signals
239
+ const entitySignals = entitySignalHandlers.get(system);
240
+ if (entitySignals?.length) {
241
+ for (const [propertyKey, id] of entitySignals) {
242
+ entity.getSignal(id).add(newSystem[propertyKey], {context: newSystem});
243
+ }
244
+ }
245
+
246
+ // friend class :)
247
+ (systemContext as any).system = newSystem;
168
248
 
169
- systemInstances.push(systemContext);
249
+ systemInstances.add(systemContext);
170
250
  }
171
251
  return systemInstances;
172
252
  }
173
253
 
254
+ private getSystemResolverTuples(entity: Entity) {
255
+ return putIfAbsent(this.entitySystemResolverTuples, entity.constructor, (): Map<SystemClass, ResolverFunction[]> => {
256
+ const systems = this.entitySystemMap.get(entity.constructor as Class);
257
+ if (!systems) return [] as any;
258
+
259
+ // const result: SystemResolvers<SystemClass>[] = [];
260
+ const result = new Map<SystemClass, ResolverFunction[]>();
261
+ for (const systemClass of systems) {
262
+ result.set(systemClass, this.createResolverArray({type: systemClass, kind: 'system', entityClass: entity.constructor}));
263
+ }
264
+ return result;
265
+ });
266
+ }
267
+
174
268
  getSignal<T>(id: ID): Signal<T> {
175
269
  return putIfAbsent(this.signalMap, id, () => new Signal<any>());
176
270
  }