mani-game-engine 1.0.0-pre.3 → 1.0.0-pre.30
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/LICENSE +1 -1
- package/lib/clock.d.ts +34 -20
- package/lib/clock.js +55 -15
- package/lib/clock.js.map +1 -1
- package/lib/ecsInjector.d.ts +12 -6
- package/lib/ecsInjector.js +55 -29
- package/lib/ecsInjector.js.map +1 -1
- package/lib/entity.d.ts +8 -7
- package/lib/entity.js +29 -17
- package/lib/entity.js.map +1 -1
- package/lib/gameEngine.d.ts +44 -19
- package/lib/gameEngine.js +213 -103
- package/lib/gameEngine.js.map +1 -1
- package/lib/index.d.ts +9 -3
- package/lib/index.js +24 -5
- package/lib/index.js.map +1 -1
- package/lib/injector.d.ts +121 -0
- package/lib/injector.js +290 -0
- package/lib/injector.js.map +1 -0
- package/lib/scope/scopeContext.d.ts +57 -0
- package/lib/scope/scopeContext.js +255 -0
- package/lib/scope/scopeContext.js.map +1 -0
- package/lib/systemContext.d.ts +12 -0
- package/lib/systemContext.js +24 -0
- package/lib/systemContext.js.map +1 -0
- package/lib/types.d.ts +28 -9
- package/lib/types.js +17 -0
- package/lib/types.js.map +1 -1
- package/lib/utils/map2k.js +5 -1
- package/lib/utils/map2k.js.map +1 -1
- package/package.json +15 -21
- package/src/clock.ts +90 -29
- package/src/ecsInjector.ts +42 -19
- package/src/entity.ts +24 -14
- package/src/gameEngine.ts +271 -137
- package/src/index.ts +12 -3
- package/src/injector.ts +363 -0
- package/src/scope/scopeContext.ts +320 -0
- package/src/systemContext.ts +27 -0
- package/src/types.ts +27 -10
package/src/gameEngine.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {Action, Class, GameEnginePlugin,
|
|
1
|
+
import {Action, Class, EngineSignals, GameEnginePlugin, OnFixedUpdateParams, OnUpdateParams, Service, System} from './types';
|
|
2
2
|
import {Signal, SignalBinding} from 'mani-signal';
|
|
3
3
|
import {Entity} from './entity';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import {EcsInjector, signalHandlers, staticSignalHandlers} from './ecsInjector';
|
|
5
|
+
import {Clock, GameClock} from './clock';
|
|
6
|
+
import {SystemContext} from './systemContext';
|
|
7
|
+
import {ID, putIfAbsent} from './injector';
|
|
7
8
|
|
|
8
|
-
type AddEntry = [Entity
|
|
9
|
-
type RemoveEntry = [Entity
|
|
9
|
+
type AddEntry = [Entity, () => void];
|
|
10
|
+
type RemoveEntry = [Entity, () => void];
|
|
10
11
|
|
|
11
12
|
const defaultOptions = {
|
|
12
13
|
fixedDeltaTime: 1 / 60,
|
|
13
|
-
autoStart: true,
|
|
14
14
|
plugins: [] as GameEnginePlugin[],
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -25,55 +25,151 @@ type GameEngineActionParams<K extends keyof GameEngineActions> = GameEngineActio
|
|
|
25
25
|
type GameAction<K extends keyof GameEngineActions> = (engine: GameEngine, params: GameEngineActionParams<K>) => Promise<any> | any;
|
|
26
26
|
|
|
27
27
|
export class GameEngine {
|
|
28
|
-
readonly onRender = new Signal();
|
|
29
|
-
readonly onUpdate = new Signal<OnUpdateParams>();
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
readonly onPrepare: Signal;
|
|
30
|
+
readonly onStart: Signal;
|
|
31
|
+
readonly onEnd: Signal;
|
|
32
|
+
readonly onRender: Signal<OnUpdateParams>;
|
|
33
|
+
readonly onUpdate: Signal<OnUpdateParams>;
|
|
34
|
+
readonly onFixedUpdate: Signal<OnFixedUpdateParams>;
|
|
35
|
+
readonly onPrePhysicsUpdate: Signal<OnFixedUpdateParams>;
|
|
36
|
+
readonly onPhysicsUpdate: Signal<OnFixedUpdateParams>;
|
|
37
|
+
readonly onPostPhysicsUpdate: Signal<OnFixedUpdateParams>;
|
|
38
|
+
readonly onLateFixedUpdate: Signal<OnFixedUpdateParams>;
|
|
39
|
+
readonly onLateUpdate: Signal<OnUpdateParams>;
|
|
40
|
+
|
|
41
|
+
private readonly onEarlyUpdateSet = new Set<System>();
|
|
42
|
+
private readonly onUpdateSet = new Set<System>();
|
|
43
|
+
private readonly onFixedUpdateSet = new Set<System>();
|
|
44
|
+
private readonly onPreFixedUpdateSet = new Set<System>();
|
|
45
|
+
private readonly onPhysicsUpdateSet = new Set<System>();
|
|
46
|
+
private readonly onPrePhysicsUpdateSet = new Set<System>();
|
|
47
|
+
private readonly onPostPhysicsUpdateSet = new Set<System>();
|
|
48
|
+
private readonly onLateFixedUpdateSet = new Set<System>();
|
|
49
|
+
private readonly onLateUpdateSet = new Set<System>();
|
|
50
|
+
private readonly onRenderUpdateSet = new Set<System>();
|
|
51
|
+
|
|
52
|
+
private readonly entitySystemMap = new Map<Entity, SystemContext[]>();
|
|
53
|
+
|
|
54
|
+
private readonly serviceClasses = new Set<Class<Service>>();
|
|
55
|
+
private readonly services: Service[] = [];
|
|
41
56
|
|
|
42
57
|
private framePromise!: Promise<any>;
|
|
43
58
|
private resolveFrame: () => void = () => undefined;
|
|
44
59
|
|
|
60
|
+
private addQueue: AddEntry[] = [];
|
|
61
|
+
private removeQueue: RemoveEntry[] = [];
|
|
62
|
+
|
|
63
|
+
private _started = false;
|
|
64
|
+
|
|
65
|
+
get started(): boolean { return this._started; }
|
|
66
|
+
|
|
45
67
|
private _frameCount = 0;
|
|
68
|
+
get frameCount(): number { return this._frameCount; }
|
|
46
69
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
70
|
+
private _fixedFrameCount = 0;
|
|
71
|
+
get fixedFrameCount(): number { return this._fixedFrameCount; }
|
|
50
72
|
|
|
51
|
-
get numEntities(): number {
|
|
52
|
-
return 0;
|
|
53
|
-
}
|
|
73
|
+
get numEntities(): number { return this.entitySystemMap.size;}
|
|
54
74
|
|
|
55
75
|
// TODO inject clock into game engine
|
|
56
76
|
// TODO inject space into game engine
|
|
57
77
|
|
|
78
|
+
constructor(
|
|
79
|
+
readonly injector: EcsInjector<Class<System>>,
|
|
80
|
+
readonly clock: Clock,
|
|
81
|
+
) {
|
|
82
|
+
this.injector.map(GameEngine).toValue(this);
|
|
83
|
+
|
|
84
|
+
this.onPrepare = this.injector.getSignal(EngineSignals.OnPrepare);
|
|
85
|
+
this.onStart = this.injector.getSignal(EngineSignals.OnStart);
|
|
86
|
+
this.onEnd = this.injector.getSignal(EngineSignals.OnEnd);
|
|
87
|
+
this.onRender = this.injector.getSignal(EngineSignals.OnRender);
|
|
88
|
+
this.onFixedUpdate = this.injector.getSignal(EngineSignals.OnFixedUpdate);
|
|
89
|
+
this.onUpdate = this.injector.getSignal(EngineSignals.OnUpdate);
|
|
90
|
+
this.onLateUpdate = this.injector.getSignal(EngineSignals.OnLateUpdate);
|
|
91
|
+
this.onPrePhysicsUpdate = this.injector.getSignal(EngineSignals.OnPrePhysicsUpdate);
|
|
92
|
+
this.onPhysicsUpdate = this.injector.getSignal(EngineSignals.OnPhysicsUpdate);
|
|
93
|
+
this.onPostPhysicsUpdate = this.injector.getSignal(EngineSignals.OnPostPhysicsUpdate);
|
|
94
|
+
this.onLateFixedUpdate = this.injector.getSignal(EngineSignals.onLateFixedUpdate);
|
|
95
|
+
|
|
96
|
+
this.setupClock();
|
|
97
|
+
this.prepareFrame();
|
|
98
|
+
}
|
|
99
|
+
|
|
58
100
|
static createDefault(options?: GameEngineOptions) {
|
|
59
101
|
const opts = {...defaultOptions, ...options};
|
|
60
|
-
const clock = new
|
|
102
|
+
const clock = new GameClock({
|
|
61
103
|
fixedDeltaTime: opts.fixedDeltaTime,
|
|
62
|
-
autoStart:
|
|
104
|
+
autoStart: false,
|
|
63
105
|
});
|
|
64
|
-
const injector = new EcsInjector<Class<
|
|
106
|
+
const injector = new EcsInjector<Class<System>>();
|
|
65
107
|
const engine = new GameEngine(injector, clock);
|
|
66
108
|
engine.installPlugins(opts.plugins);
|
|
67
109
|
return engine;
|
|
68
110
|
}
|
|
69
111
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
static createDefaultWithInjector(injector: EcsInjector<Class<System>>, options?: GameEngineOptions) {
|
|
113
|
+
const opts = {...defaultOptions, ...options};
|
|
114
|
+
const clock = new GameClock({
|
|
115
|
+
fixedDeltaTime: opts.fixedDeltaTime,
|
|
116
|
+
autoStart: false,
|
|
117
|
+
});
|
|
118
|
+
const engine = new GameEngine(injector, clock);
|
|
119
|
+
engine.installPlugins(opts.plugins);
|
|
120
|
+
return engine;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
addService(service: Service): void {
|
|
124
|
+
this.mapAnnotatedSignals(service);
|
|
125
|
+
service.onStart && service.onStart();
|
|
126
|
+
service.onPreFixedUpdate && this.onPreFixedUpdateSet.add(service);
|
|
127
|
+
service.onFixedUpdate && this.onFixedUpdateSet.add(service);
|
|
128
|
+
service.onUpdate && this.onUpdateSet.add(service);
|
|
129
|
+
service.onLateUpdate && this.onLateUpdateSet.add(service);
|
|
130
|
+
service.onPrePhysicsUpdate && this.onPrePhysicsUpdateSet.add(service);
|
|
131
|
+
service.onPhysicsUpdate && this.onPhysicsUpdateSet.add(service);
|
|
132
|
+
service.onPostPhysicsUpdate && this.onPostPhysicsUpdateSet.add(service);
|
|
133
|
+
service.onLateFixedUpdate && this.onLateFixedUpdateSet.add(service);
|
|
134
|
+
service.onRender && this.onRenderUpdateSet.add(service);
|
|
135
|
+
this.services.push(service);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private installPlugins(plugins: GameEnginePlugin[]) {
|
|
139
|
+
plugins.forEach(plugin => {
|
|
140
|
+
plugin.onPrepare && plugin.onPrepare(this);
|
|
141
|
+
});
|
|
142
|
+
plugins.forEach(plugin => {
|
|
143
|
+
plugin.onStart && plugin.onStart(this);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
registerSystem(systemClass: Class<System>): void {
|
|
148
|
+
this.mapStaticAnnotatedSignals(systemClass);
|
|
149
|
+
if (this._started) throw new Error('Register Systems before engine is started');
|
|
150
|
+
this.injector.registerSystem(systemClass);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
registerService(serviceClass: Class<Service>): void {
|
|
154
|
+
this.mapStaticAnnotatedSignals(serviceClass);
|
|
155
|
+
if (this._started) throw new Error('Register Services before engine is started');
|
|
156
|
+
this.injector.map(serviceClass).toSingleton();
|
|
157
|
+
this.serviceClasses.add(serviceClass);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
getService<T extends Class<Service>>(serviceClass: T): InstanceType<T> {
|
|
161
|
+
return this.injector.get(serviceClass);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
start() {
|
|
165
|
+
this._started = true;
|
|
166
|
+
//TODO: throw when registering services or systems when engine is running
|
|
167
|
+
for (const serviceClass of this.serviceClasses) {
|
|
168
|
+
const service = this.injector.get(serviceClass);
|
|
169
|
+
this.addService(service);
|
|
170
|
+
}
|
|
171
|
+
this.clock.start();
|
|
172
|
+
this.onStart.dispatch();
|
|
77
173
|
}
|
|
78
174
|
|
|
79
175
|
private setupClock() {
|
|
@@ -85,143 +181,223 @@ export class GameEngine {
|
|
|
85
181
|
}
|
|
86
182
|
});
|
|
87
183
|
|
|
88
|
-
this.clock.onFixedUpdate.add((
|
|
184
|
+
this.clock.onFixedUpdate.add((params) => {
|
|
185
|
+
const {time, deltaTime} = params;
|
|
89
186
|
for (const system of this.onPreFixedUpdateSet) {
|
|
90
|
-
system.onPreFixedUpdate!();
|
|
187
|
+
system.onPreFixedUpdate!(time, deltaTime);
|
|
91
188
|
}
|
|
92
189
|
for (const system of this.onFixedUpdateSet) {
|
|
93
190
|
system.onFixedUpdate!(time, deltaTime);
|
|
94
191
|
}
|
|
192
|
+
this.onFixedUpdate.dispatch(params);
|
|
95
193
|
for (const system of this.onPhysicsUpdateSet) {
|
|
96
194
|
system.onPhysicsUpdate!(time, deltaTime);
|
|
97
195
|
}
|
|
196
|
+
this.onPhysicsUpdate.dispatch(params);
|
|
197
|
+
this.onPostPhysicsUpdate.dispatch(params);
|
|
98
198
|
for (const system of this.onPostPhysicsUpdateSet) {
|
|
99
199
|
system.onPostPhysicsUpdate!(time, deltaTime);
|
|
100
200
|
}
|
|
201
|
+
this.onLateFixedUpdate.dispatch(params);
|
|
202
|
+
for (const system of this.onLateFixedUpdateSet) {
|
|
203
|
+
system.onLateFixedUpdate!(time, deltaTime);
|
|
204
|
+
}
|
|
205
|
+
this._fixedFrameCount++;
|
|
101
206
|
});
|
|
102
207
|
|
|
103
|
-
|
|
208
|
+
// TODO: update from outside
|
|
209
|
+
this.clock.onUpdate.add(onUpdateParams => {
|
|
210
|
+
const {time, deltaTime, alpha} = onUpdateParams;
|
|
104
211
|
for (const system of this.onUpdateSet) {
|
|
105
|
-
|
|
106
212
|
system.onUpdate!(time, deltaTime, alpha);
|
|
107
213
|
}
|
|
214
|
+
//TODO: merge with framerate to single signal?
|
|
215
|
+
|
|
216
|
+
this.onUpdate.dispatch(onUpdateParams);
|
|
108
217
|
for (const system of this.onLateUpdateSet) {
|
|
109
218
|
system.onLateUpdate!(time, deltaTime, alpha);
|
|
110
219
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
// const drawTime = 1 / 30;
|
|
115
|
-
// while (performance.now() < ts + drawTime * 1000) {
|
|
116
|
-
//
|
|
117
|
-
// }
|
|
220
|
+
this.onLateUpdate.dispatch(onUpdateParams);
|
|
221
|
+
|
|
118
222
|
for (const system of this.onRenderUpdateSet) {
|
|
119
|
-
system.onRender!();
|
|
223
|
+
system.onRender!(time, deltaTime, alpha);
|
|
120
224
|
}
|
|
121
|
-
this.onRender.dispatch();
|
|
225
|
+
this.onRender.dispatch(onUpdateParams);
|
|
122
226
|
this._frameCount++;
|
|
123
227
|
});
|
|
124
228
|
}
|
|
125
229
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
plugin.onPrepare && plugin.onPrepare(this);
|
|
129
|
-
});
|
|
130
|
-
plugins.forEach(plugin => {
|
|
131
|
-
plugin.onStart && plugin.onStart(this);
|
|
132
|
-
});
|
|
230
|
+
add(entity: Entity) {
|
|
231
|
+
return new Promise<void>(resolve => this.addQueue.push([entity, resolve]));
|
|
133
232
|
}
|
|
134
233
|
|
|
135
|
-
|
|
136
|
-
this.
|
|
234
|
+
waitFrame() {
|
|
235
|
+
return this.framePromise;
|
|
137
236
|
}
|
|
138
237
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
service.onFixedUpdate && this.onFixedUpdateSet.add(service);
|
|
144
|
-
service.onUpdate && this.onUpdateSet.add(service);
|
|
145
|
-
service.onLateUpdate && this.onLateUpdateSet.add(service);
|
|
146
|
-
service.onPhysicsUpdate && this.onPhysicsUpdateSet.add(service);
|
|
147
|
-
service.onPostPhysicsUpdate && this.onPostPhysicsUpdateSet.add(service);
|
|
148
|
-
service.onRender && this.onRenderUpdateSet.add(service);
|
|
238
|
+
async remove(entity: Entity) {
|
|
239
|
+
if ((<any>entity).markRemoval) return;
|
|
240
|
+
(<any>entity).markRemoval = true;
|
|
241
|
+
return new Promise<void>(resolve => this.removeQueue.push([entity, resolve]));
|
|
149
242
|
}
|
|
150
243
|
|
|
151
|
-
|
|
152
|
-
|
|
244
|
+
stop() {
|
|
245
|
+
// this.injector.dispose();
|
|
246
|
+
this.clock.stop();
|
|
247
|
+
}
|
|
153
248
|
|
|
154
|
-
|
|
155
|
-
return
|
|
249
|
+
runAction<T extends Action>(action: T): ReturnType<T> {
|
|
250
|
+
return action(this);
|
|
156
251
|
}
|
|
157
252
|
|
|
158
|
-
|
|
159
|
-
return this.
|
|
253
|
+
getSignal<T>(signalId: ID): Signal<T> {
|
|
254
|
+
return this.injector.getSignal<T>(signalId);
|
|
160
255
|
}
|
|
161
256
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
257
|
+
private signalMappings = new Map<Class, [string, Signal][]>();
|
|
258
|
+
private staticSignalMappings = new Map<Class, [string, Signal][]>();
|
|
259
|
+
private signalMapBindings = new Map<Object, SignalBinding[]>();
|
|
260
|
+
|
|
261
|
+
// TODO: staticSignalMapBindings needed if we want to unregister Services or Systems
|
|
262
|
+
|
|
263
|
+
private mapStaticAnnotatedSignals(target: Class) {
|
|
264
|
+
let staticSignalMap = this.getStaticSignalMap(target);
|
|
265
|
+
if (staticSignalMap.length === 0) return;
|
|
266
|
+
|
|
267
|
+
const bindings = [];
|
|
268
|
+
for (const [key, signal] of staticSignalMap) {
|
|
269
|
+
bindings.push(signal.add((<any>target)[key], target));
|
|
270
|
+
}
|
|
271
|
+
// TODO: ignore bindings for now (store them if we implement unregister Service or Systems
|
|
166
272
|
}
|
|
167
273
|
|
|
168
|
-
private
|
|
274
|
+
private mapAnnotatedSignals(target: Object) {
|
|
275
|
+
const signalMap = this.getSignalMap(target.constructor);
|
|
276
|
+
if (signalMap.length === 0) return;
|
|
277
|
+
|
|
278
|
+
const bindings = [];
|
|
279
|
+
for (const [key, signal] of signalMap) {
|
|
280
|
+
bindings.push(signal.add((<any>target)[key], target));
|
|
281
|
+
}
|
|
282
|
+
this.signalMapBindings.set(target, bindings);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
private unmapAnnotatedSignals(target: Object) {
|
|
286
|
+
const signalBindings = this.signalMapBindings.get(target);
|
|
287
|
+
if (!signalBindings) return;
|
|
288
|
+
for (const binding of signalBindings) {
|
|
289
|
+
binding.detach();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private getSignalMap(target: Function) {
|
|
294
|
+
// TODO: we could speed thinks up by introducing "invisible" flags on the systems/services to avoid a map check
|
|
295
|
+
return putIfAbsent(this.signalMappings, target, (): [string, Signal][] => {
|
|
296
|
+
const handlers = signalHandlers.get(target);
|
|
297
|
+
if (!handlers) return [];
|
|
298
|
+
const result: [string, Signal][] = [];
|
|
299
|
+
for (const [key, signalId] of handlers) {
|
|
300
|
+
result.push([key, this.getSignal(signalId)]);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private getStaticSignalMap(target: Class) {
|
|
307
|
+
// TODO: we could speed thinks up by introducing "invisible" flags on the systems/services to avoid a map check
|
|
308
|
+
return putIfAbsent(this.staticSignalMappings, target, (): [string, Signal][] => {
|
|
309
|
+
const handlers = staticSignalHandlers.get(target);
|
|
310
|
+
if (!handlers) return [];
|
|
311
|
+
const result: [string, Signal][] = [];
|
|
312
|
+
for (const [key, signalId] of handlers) {
|
|
313
|
+
result.push([key, this.getSignal(signalId)]);
|
|
314
|
+
}
|
|
315
|
+
return result;
|
|
316
|
+
});
|
|
317
|
+
}
|
|
169
318
|
|
|
170
|
-
|
|
319
|
+
dispose() {
|
|
320
|
+
this.clock.stop();
|
|
321
|
+
this.clock.dispose();
|
|
322
|
+
for (const systems of this.entitySystemMap.values()) {
|
|
323
|
+
for (const system of systems) {
|
|
324
|
+
system.system.onEnd && system.system.onEnd();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const service of this.services) {
|
|
328
|
+
service.onEnd && service.onEnd();
|
|
329
|
+
}
|
|
330
|
+
this.onEnd.dispatch();
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private prepareFrame() {
|
|
334
|
+
for (const [entity, resolve] of this.removeQueue) {
|
|
335
|
+
const entities = entity.getAllEntities();
|
|
171
336
|
for (const entity of entities) {
|
|
172
337
|
(<any>entity).markRemoval = false;
|
|
173
|
-
if (!(<any>entity).gameEngine)
|
|
338
|
+
if (!(<any>entity).gameEngine) {
|
|
339
|
+
console.log(entity);
|
|
340
|
+
// throw new Error('Entity not active');
|
|
341
|
+
}
|
|
174
342
|
(<any>entity).gameEngine = undefined;
|
|
175
343
|
|
|
176
|
-
const
|
|
344
|
+
const systemContexts = this.entitySystemMap.get(entity);
|
|
177
345
|
this.entitySystemMap.delete(entity);
|
|
178
|
-
if (
|
|
179
|
-
for (const
|
|
346
|
+
if (systemContexts) {
|
|
347
|
+
for (const context of systemContexts) {
|
|
348
|
+
context.dispose();
|
|
349
|
+
const system = context.system;
|
|
180
350
|
this.unmapAnnotatedSignals(system);
|
|
181
351
|
system.onEnd && system.onEnd();
|
|
182
352
|
system.onPreFixedUpdate && this.onPreFixedUpdateSet.delete(system);
|
|
183
353
|
system.onFixedUpdate && this.onFixedUpdateSet.delete(system);
|
|
184
|
-
system.onEarlyUpdate && this.onEarlyUpdateSet.delete(system);
|
|
185
354
|
system.onUpdate && this.onUpdateSet.delete(system);
|
|
186
355
|
system.onLateUpdate && this.onLateUpdateSet.delete(system);
|
|
187
356
|
system.onPhysicsUpdate && this.onPhysicsUpdateSet.delete(system);
|
|
357
|
+
system.onPrePhysicsUpdate && this.onPrePhysicsUpdateSet.delete(system);
|
|
188
358
|
system.onPostPhysicsUpdate && this.onPostPhysicsUpdateSet.delete(system);
|
|
359
|
+
system.onLateFixedUpdate && this.onLateFixedUpdateSet.delete(system);
|
|
189
360
|
system.onRender && this.onRenderUpdateSet.delete(system);
|
|
190
361
|
}
|
|
191
362
|
}
|
|
192
363
|
entity.onRemove.dispatch();
|
|
364
|
+
entity.onRemove.detachAll();
|
|
193
365
|
}
|
|
194
366
|
|
|
195
367
|
resolve();
|
|
196
368
|
}
|
|
197
369
|
this.removeQueue = [];
|
|
198
370
|
|
|
199
|
-
for (const [
|
|
371
|
+
for (const [entity, resolve] of this.addQueue) {
|
|
200
372
|
const preparePromises = [];
|
|
201
|
-
const
|
|
373
|
+
const nexSystemContexts: SystemContext[] = [];
|
|
374
|
+
const entities = entity.getAllEntities();
|
|
202
375
|
for (const entity of entities) {
|
|
203
376
|
if ((<any>entity).gameEngine) throw new Error('Entity already added to a gameEngine');
|
|
204
377
|
(<any>entity).gameEngine = this;
|
|
205
|
-
const
|
|
206
|
-
for (const
|
|
207
|
-
|
|
208
|
-
|
|
378
|
+
const systemContexts = this.injector.createSystems(entity);
|
|
379
|
+
for (const context of systemContexts) {
|
|
380
|
+
// TODO: implement synced mode where async onPrepares aren't allowed
|
|
381
|
+
context.system.onPrepare && preparePromises.push(context.system.onPrepare());
|
|
382
|
+
nexSystemContexts.push(context);
|
|
209
383
|
}
|
|
210
|
-
this.entitySystemMap.set(entity,
|
|
384
|
+
this.entitySystemMap.set(entity, systemContexts);
|
|
211
385
|
}
|
|
212
386
|
|
|
213
387
|
const startSystems = () => {
|
|
214
388
|
// TODO: check if a "global" method is faster than this inner method
|
|
215
|
-
for (const
|
|
389
|
+
for (const context of nexSystemContexts) {
|
|
390
|
+
const system = context.system;
|
|
216
391
|
this.mapAnnotatedSignals(system);
|
|
217
392
|
system.onStart && system.onStart();
|
|
218
393
|
system.onPreFixedUpdate && this.onPreFixedUpdateSet.add(system);
|
|
219
394
|
system.onFixedUpdate && this.onFixedUpdateSet.add(system);
|
|
220
|
-
system.onEarlyUpdate && this.onEarlyUpdateSet.add(system);
|
|
221
395
|
system.onUpdate && this.onUpdateSet.add(system);
|
|
222
396
|
system.onLateUpdate && this.onLateUpdateSet.add(system);
|
|
223
397
|
system.onPhysicsUpdate && this.onPhysicsUpdateSet.add(system);
|
|
398
|
+
system.onPrePhysicsUpdate && this.onPrePhysicsUpdateSet.add(system);
|
|
224
399
|
system.onPostPhysicsUpdate && this.onPostPhysicsUpdateSet.add(system);
|
|
400
|
+
system.onLateFixedUpdate && this.onLateFixedUpdateSet.add(system);
|
|
225
401
|
system.onRender && this.onRenderUpdateSet.add(system);
|
|
226
402
|
}
|
|
227
403
|
resolve();
|
|
@@ -237,52 +413,10 @@ export class GameEngine {
|
|
|
237
413
|
this.addQueue = [];
|
|
238
414
|
// create promise for next frame
|
|
239
415
|
this.resolveFrame();
|
|
240
|
-
this.framePromise = new Promise<
|
|
416
|
+
this.framePromise = new Promise<void>(resolve => {
|
|
241
417
|
this.resolveFrame = resolve;
|
|
242
418
|
});
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
runAction<T extends Action>(action: T): ReturnType<T> {
|
|
246
|
-
return action(this);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
getSignal<T>(signalId: ID): Signal<T> {
|
|
250
|
-
return this.injector.getSignal<T>(signalId);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
signalMappings = new Map<Class, [string, Signal][]>();
|
|
254
|
-
signalMapBindings = new Map<Object, SignalBinding[]>();
|
|
255
|
-
|
|
256
|
-
private mapAnnotatedSignals(target: Object) {
|
|
257
|
-
const signalMap = this.getSignalMap(target.constructor);
|
|
258
|
-
if (signalMap.length === 0) return;
|
|
259
|
-
|
|
260
|
-
const bindings = [];
|
|
261
|
-
for (const [key, signal] of signalMap) {
|
|
262
|
-
bindings.push(signal.add((<any>target)[key], target));
|
|
263
|
-
}
|
|
264
|
-
this.signalMapBindings.set(target, bindings);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
private unmapAnnotatedSignals(target: Object) {
|
|
268
|
-
const signalBindings = this.signalMapBindings.get(target);
|
|
269
|
-
if (!signalBindings) return;
|
|
270
|
-
for (const binding of signalBindings) {
|
|
271
|
-
binding.detach();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private getSignalMap(target: Function) {
|
|
276
|
-
// TODO: we could speed thinks up by introducing "invisible" flags on the systems/services to avoid a map check
|
|
277
|
-
return putIfAbsent(this.signalMappings, target, (): [string, Signal][] => {
|
|
278
|
-
const handlers = signalHandlers.get(target);
|
|
279
|
-
if (!handlers) return [];
|
|
280
|
-
const result: [string, Signal][] = [];
|
|
281
|
-
for (const [key, signalId] of handlers) {
|
|
282
|
-
result.push([key, this.getSignal(signalId)]);
|
|
283
|
-
}
|
|
284
|
-
return result;
|
|
285
|
-
});
|
|
419
|
+
this.onPrepare.dispatch();
|
|
286
420
|
}
|
|
287
421
|
}
|
|
288
422
|
//
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
export * from './gameEngine';
|
|
2
2
|
export * from './entity';
|
|
3
3
|
export * from './types';
|
|
4
|
-
export
|
|
4
|
+
export * from './scope/scopeContext';
|
|
5
|
+
export * from './systemContext';
|
|
6
|
+
export {EcsInjector, EntityComponent, GetComponent, GetEntity, GetContext, GetSignal, OnSignal} from './ecsInjector';
|
|
5
7
|
|
|
6
8
|
// intellij doesnt auto resolve imports if we just export * from an external dependency
|
|
7
|
-
import {Inject, InjectId, Injector, InjectType} from 'mani-injector';
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
import {ID, Inject, InjectId, Injector, InjectType, putIfAbsent} from './injector';
|
|
11
|
+
import {Signal, SignalBinding, SignalCallback} from 'mani-signal';
|
|
12
|
+
import {Clock} from './clock';
|
|
13
|
+
|
|
14
|
+
export {Injector, InjectType, InjectId, Inject, putIfAbsent, ID};
|
|
15
|
+
|
|
16
|
+
export {Clock};
|
|
17
|
+
|
|
18
|
+
export {Signal, SignalBinding, SignalCallback};
|