mani-game-engine 1.0.0-pre.1 → 1.0.0-pre.100
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 +21 -0
- package/README.md +69 -0
- package/lib/clock.d.ts +64 -0
- package/lib/clock.js +147 -0
- package/lib/clock.js.map +1 -0
- package/lib/ecsInjector.d.ts +46 -0
- package/lib/ecsInjector.js +224 -0
- package/lib/ecsInjector.js.map +1 -0
- package/lib/entity.d.ts +44 -0
- package/lib/entity.js +212 -0
- package/lib/entity.js.map +1 -0
- package/lib/gameEngine.d.ts +101 -0
- package/lib/gameEngine.js +505 -0
- package/lib/gameEngine.js.map +1 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +36 -0
- package/lib/index.js.map +1 -0
- package/lib/injector.d.ts +125 -0
- package/lib/injector.js +305 -0
- package/lib/injector.js.map +1 -0
- package/lib/scope/scopeContext.d.ts +76 -0
- package/lib/scope/scopeContext.js +306 -0
- package/lib/scope/scopeContext.js.map +1 -0
- package/lib/systemContext.d.ts +18 -0
- package/lib/systemContext.js +54 -0
- package/lib/systemContext.js.map +1 -0
- package/lib/types.d.ts +80 -0
- package/lib/types.js +20 -0
- package/lib/types.js.map +1 -0
- package/lib/utils/map2k.d.ts +6 -0
- package/lib/utils/map2k.js +44 -0
- package/lib/utils/map2k.js.map +1 -0
- package/package.json +32 -6
- package/src/clock.ts +205 -0
- package/src/ecsInjector.ts +282 -0
- package/src/entity.ts +257 -0
- package/src/gameEngine.ts +621 -0
- package/src/index.ts +32 -0
- package/src/injector.ts +410 -0
- package/src/scope/scopeContext.ts +392 -0
- package/src/systemContext.ts +58 -0
- package/src/types.ts +95 -0
- package/src/utils/map2k.ts +52 -0
- package/clockService.d.ts +0 -12
- package/clockService.js +0 -30
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import {Class, EcsInjector, Signal, SignalBinding, SignalCallback} from '../index';
|
|
2
|
+
import {signalHandlers} from '../ecsInjector';
|
|
3
|
+
import {ID, putIfAbsent} from '../injector';
|
|
4
|
+
|
|
5
|
+
export type ScopeSignalOptions = { keepAlive?: boolean | Scope[], group?: string }
|
|
6
|
+
|
|
7
|
+
export const scopeSignalHandlers = new Map<Object, [string, ID, ScopeSignalOptions?][]>();
|
|
8
|
+
|
|
9
|
+
export const OnScopeSignal = (id: ID, options?: ScopeSignalOptions) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
10
|
+
if (target instanceof Function) {
|
|
11
|
+
throw new Error('only allowed on non static methods');
|
|
12
|
+
} else {
|
|
13
|
+
const mappingList = putIfAbsent(scopeSignalHandlers, target.constructor, (): [string, ID, ScopeSignalOptions?][] => []);
|
|
14
|
+
mappingList.push([propertyKey, id, options]);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const SCOPE_CONTEXT = {
|
|
19
|
+
log: true,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface Scope {
|
|
23
|
+
[propName: string]: any; // disable weak type detection
|
|
24
|
+
onEnter?(): void;
|
|
25
|
+
onExit?(): void;
|
|
26
|
+
onSubReturn?(): void;
|
|
27
|
+
onSubExit?(): void;
|
|
28
|
+
onActivate?(): void;
|
|
29
|
+
onDeactivate?(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class ScopeSignalBinding {
|
|
33
|
+
readonly keepAlive?: boolean | Scope[];
|
|
34
|
+
private group?: string;
|
|
35
|
+
|
|
36
|
+
constructor(private signalBinding: SignalBinding, options?: ScopeSignalOptions) {
|
|
37
|
+
this.keepAlive = options?.keepAlive;
|
|
38
|
+
this.group = options?.group;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
activate() {
|
|
42
|
+
this.signalBinding.setActive(true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
deactivate() {
|
|
46
|
+
this.signalBinding.setActive(false);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
detach() {
|
|
50
|
+
this.signalBinding.detach();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// TODO: rename to ScopeStack to something like InternalScopeTreeSingleton with an interface
|
|
54
|
+
class ScopeStack {
|
|
55
|
+
readonly stack: ScopeContext[] = [];
|
|
56
|
+
queuedScopeChanges: (() => Promise<void>)[] = [];
|
|
57
|
+
ongoingChange = false;
|
|
58
|
+
target?: Scope;
|
|
59
|
+
|
|
60
|
+
constructor(readonly rootScope: ScopeContext) {
|
|
61
|
+
this.stack.push(rootScope);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
get rootContext() {
|
|
65
|
+
return this.stack[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get activeContext() {
|
|
69
|
+
return this.stack[this.stack.length - 1];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type ScopeMapping = (params: {
|
|
74
|
+
injector: EcsInjector,
|
|
75
|
+
registerScopeService: (serviceClass: Class) => void,
|
|
76
|
+
onEnter: Signal<ScopeContext>,
|
|
77
|
+
onExit: Signal<ScopeContext>,
|
|
78
|
+
onSubReturn: Signal<ScopeContext>,
|
|
79
|
+
onSubExit: Signal<ScopeContext>,
|
|
80
|
+
onActivate: Signal<ScopeContext>,
|
|
81
|
+
onDeactivate: Signal<ScopeContext>,
|
|
82
|
+
|
|
83
|
+
}) => any | Promise<void>;
|
|
84
|
+
|
|
85
|
+
interface AddScopeSignalOptions extends ScopeSignalOptions {
|
|
86
|
+
context?: unknown;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export type ScopeChangeParams = {
|
|
90
|
+
from: ScopeContext;
|
|
91
|
+
to: ScopeContext;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// TODO: maybe move Signal resolver to Injector and dont use EcsInjector here?
|
|
95
|
+
export class ScopeContext<T extends Class = Class<Scope>> {
|
|
96
|
+
readonly scope!: InstanceType<T>;
|
|
97
|
+
readonly injector: EcsInjector;
|
|
98
|
+
private readonly stack: ScopeStack; // TODO: naming
|
|
99
|
+
private readonly signalBindings: ScopeSignalBinding[] = [];
|
|
100
|
+
|
|
101
|
+
// Scope services will be active (signals bounds) in this scope and all sub scopes
|
|
102
|
+
// when this scope exits, the signals will be unbound
|
|
103
|
+
private serviceBindings: SignalBinding[] = [];
|
|
104
|
+
private closed = false;
|
|
105
|
+
private muteKeepAliveSignals = false;
|
|
106
|
+
|
|
107
|
+
readonly onEnter = new Signal<ScopeContext>();
|
|
108
|
+
readonly onExit = new Signal<ScopeContext>();
|
|
109
|
+
readonly onSubReturn = new Signal<ScopeContext>();
|
|
110
|
+
readonly onSubExit = new Signal<ScopeContext>();
|
|
111
|
+
readonly onActivate = new Signal<ScopeContext>();
|
|
112
|
+
readonly onDeactivate = new Signal<ScopeContext>();
|
|
113
|
+
private readonly creationTime: DOMHighResTimeStamp;
|
|
114
|
+
readonly onScopeChange: Signal<ScopeChangeParams>;
|
|
115
|
+
|
|
116
|
+
get runtime() {
|
|
117
|
+
return (performance.now() - this.creationTime) / 1000;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
constructor(injector: EcsInjector, scopeClass: T, mapping?: ScopeMapping, private parent?: ScopeContext, resolve?: () => void) {
|
|
121
|
+
this.creationTime = performance.now();
|
|
122
|
+
if (!parent) {
|
|
123
|
+
this.stack = new ScopeStack(this);
|
|
124
|
+
this.onScopeChange = new Signal<ScopeChangeParams>();
|
|
125
|
+
|
|
126
|
+
} else {
|
|
127
|
+
this.stack = parent.stack;
|
|
128
|
+
this.onScopeChange = parent.onScopeChange;
|
|
129
|
+
this.stack.stack.push(this);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// TODO: disable this for the rootscope?
|
|
133
|
+
this.injector = injector.createChild();
|
|
134
|
+
|
|
135
|
+
//TODO: i think it would really be better if we implement an injector.injectIntoUnmapped() instead of mapping here
|
|
136
|
+
this.injector.map(scopeClass).toSingleton();
|
|
137
|
+
this.injector.map(ScopeContext).toValue(this);
|
|
138
|
+
|
|
139
|
+
const result = mapping?.({
|
|
140
|
+
injector: this.injector,
|
|
141
|
+
registerScopeService: serviceClass => {
|
|
142
|
+
this.injector.map(serviceClass).toSingleton();
|
|
143
|
+
this.mapServiceSignals(this.injector.get(serviceClass));
|
|
144
|
+
},
|
|
145
|
+
onEnter: this.onEnter,
|
|
146
|
+
onExit: this.onExit,
|
|
147
|
+
onSubReturn: this.onSubReturn,
|
|
148
|
+
onSubExit: this.onSubExit,
|
|
149
|
+
onActivate: this.onActivate,
|
|
150
|
+
onDeactivate: this.onDeactivate,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const completeScopeChange = () => {
|
|
154
|
+
(this.scope as Scope) = this.injector.get(scopeClass);
|
|
155
|
+
this.mapScopeSignals();
|
|
156
|
+
this.updateSignalBindings();
|
|
157
|
+
// TODO: maybe the scope needs to prepare resources or something before it can react to signals, so we should move onEnter bevore the signal mapping and make it async (not working in constructor though)
|
|
158
|
+
this.scope.onEnter?.();
|
|
159
|
+
this.onEnter.dispatch(this);
|
|
160
|
+
this.scope.onActivate?.();
|
|
161
|
+
this.onActivate.dispatch(this);
|
|
162
|
+
resolve?.();
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (result instanceof Promise) {
|
|
166
|
+
result.then(completeScopeChange);
|
|
167
|
+
} else {
|
|
168
|
+
completeScopeChange();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
get isRoot(): boolean {
|
|
173
|
+
return this === this.stack.rootScope;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// TODO: test this
|
|
177
|
+
get target() {
|
|
178
|
+
return this.stack.target;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
get isActive(): boolean {
|
|
182
|
+
return this === this.stack.activeContext;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get activeContext() { return this.stack.activeContext;}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* returns the new scope only if there is currently no ongoing scope change happening
|
|
189
|
+
* @param scopeClass
|
|
190
|
+
* @param mapping
|
|
191
|
+
*/
|
|
192
|
+
async enterScope(scopeClass: Class, mapping?: ScopeMapping) {
|
|
193
|
+
let newContext: ScopeContext | undefined;
|
|
194
|
+
const doChange = async () => {
|
|
195
|
+
const oldContext = this.activeContext;
|
|
196
|
+
this.activeContext.scope.onSubExit?.();
|
|
197
|
+
this.activeContext.onSubExit.dispatch(this);
|
|
198
|
+
this.activeContext.scope.onDeactivate?.();
|
|
199
|
+
this.activeContext.onDeactivate.dispatch(this);
|
|
200
|
+
|
|
201
|
+
await new Promise<void>(resolve => {
|
|
202
|
+
newContext = new ScopeContext(this.activeContext.injector, scopeClass, mapping, this.activeContext, resolve);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.logStack();
|
|
206
|
+
this.onScopeChange.dispatch({
|
|
207
|
+
from: oldContext,
|
|
208
|
+
to: newContext!,
|
|
209
|
+
})
|
|
210
|
+
const nextChange = this.stack.queuedScopeChanges.shift();
|
|
211
|
+
if (nextChange) {
|
|
212
|
+
await nextChange();
|
|
213
|
+
} else {
|
|
214
|
+
this.stack.ongoingChange = false;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
// TODO: this queued changes are somewhat experimental
|
|
218
|
+
if (!this.stack.ongoingChange) {
|
|
219
|
+
this.stack.ongoingChange = true;
|
|
220
|
+
await doChange();
|
|
221
|
+
} else {
|
|
222
|
+
this.stack.queuedScopeChanges.push(doChange);
|
|
223
|
+
}
|
|
224
|
+
return newContext!;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Exits this scope (and all open subscopes=), if a scope class is given. all scopes from the stack are closed until after the scope with the given class
|
|
229
|
+
*/
|
|
230
|
+
async exitScope(target?: Scope) {
|
|
231
|
+
const doChange = async () => {
|
|
232
|
+
if (this.closed) throw new Error(`Scope already closed`);
|
|
233
|
+
// TODO: check if target is in stack?
|
|
234
|
+
this.stack.target = target || this.scope.constructor;
|
|
235
|
+
while (true) {
|
|
236
|
+
const ctx = this.stack.stack.pop();
|
|
237
|
+
if (!ctx) {
|
|
238
|
+
throw new Error('no scope in stack');
|
|
239
|
+
}
|
|
240
|
+
ctx!.exitThis();
|
|
241
|
+
if (ctx!.scope.constructor === this.stack.target) {
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
this.stack.target = undefined;
|
|
246
|
+
const nextChange = this.stack.queuedScopeChanges.shift();
|
|
247
|
+
if (nextChange) {
|
|
248
|
+
await nextChange();
|
|
249
|
+
} else {
|
|
250
|
+
this.stack.ongoingChange = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
};
|
|
254
|
+
if (!this.stack.ongoingChange) {
|
|
255
|
+
this.stack.ongoingChange = true;
|
|
256
|
+
await doChange();
|
|
257
|
+
} else {
|
|
258
|
+
this.stack.queuedScopeChanges.push(doChange);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
closeSubScopes() {
|
|
263
|
+
this.stack.target = this.scope.constructor;
|
|
264
|
+
while (!this.isActive) {
|
|
265
|
+
const ctx = this.stack.stack.pop();
|
|
266
|
+
if (!ctx) {
|
|
267
|
+
throw new Error('no scope in stack');
|
|
268
|
+
}
|
|
269
|
+
ctx.exitThis();
|
|
270
|
+
}
|
|
271
|
+
this.stack.target = undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private logStack() {
|
|
275
|
+
if (SCOPE_CONTEXT.log) {
|
|
276
|
+
console.debug('%c' + this.stack.stack.map(c => c.scope.constructor.name).join(' -> '), 'color:yellow');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
getStackClasses(): Scope[] {
|
|
281
|
+
return this.stack.stack.map(context => context.scope.constructor);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
addScopeSignal<T>(signal: Signal<T>, callback: SignalCallback<T>, params?: AddScopeSignalOptions) {
|
|
285
|
+
const signalBinding = new ScopeSignalBinding(signal.add(callback, {context:params?.context}), params);
|
|
286
|
+
this.signalBindings.push(signalBinding);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
disableKeepAliveSignals() {
|
|
290
|
+
this.muteKeepAliveSignals = true;
|
|
291
|
+
this.updateSignalBindings();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
enableKeepAliveSignals() {
|
|
295
|
+
this.muteKeepAliveSignals = false;
|
|
296
|
+
this.updateSignalBindings();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private updateSignalBindings(muteKeepAlive = false) {
|
|
300
|
+
for (const binding of this.signalBindings) {
|
|
301
|
+
if (this.isActive) {
|
|
302
|
+
binding.activate();
|
|
303
|
+
} else if (muteKeepAlive) {
|
|
304
|
+
binding.deactivate();
|
|
305
|
+
} else if (binding.keepAlive === true) {
|
|
306
|
+
binding.activate();
|
|
307
|
+
} else if (!!(binding.keepAlive)) {
|
|
308
|
+
// is array of scopes
|
|
309
|
+
// OPT: cache active context?
|
|
310
|
+
if (binding.keepAlive.indexOf(this.activeContext.scope.constructor) != -1) {
|
|
311
|
+
binding.activate();
|
|
312
|
+
} else {
|
|
313
|
+
binding.deactivate();
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
binding.deactivate();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
muteKeepAlive = muteKeepAlive || this.muteKeepAliveSignals;
|
|
320
|
+
this.parent?.updateSignalBindings(muteKeepAlive);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private detachSignalBindings() {
|
|
324
|
+
for (const signalBinding of this.signalBindings) {
|
|
325
|
+
signalBinding.detach();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private mapScopeSignals() {
|
|
330
|
+
const handlers = scopeSignalHandlers.get(this.scope.constructor);
|
|
331
|
+
if (!handlers) return;
|
|
332
|
+
|
|
333
|
+
for (const [field, id, options] of handlers) {
|
|
334
|
+
const signal = this.injector.getSignal(id);
|
|
335
|
+
const callback = (<any>this.scope) [field];
|
|
336
|
+
this.addScopeSignal(signal, callback, {...options, context: this.scope});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private mapServiceSignals(scopeService: Object) {
|
|
341
|
+
const handlers = signalHandlers.get(scopeService.constructor);
|
|
342
|
+
if (!handlers) return;
|
|
343
|
+
|
|
344
|
+
for (const [field, id] of handlers) {
|
|
345
|
+
const signal = this.injector.getSignal(id);
|
|
346
|
+
const signalBinding = signal.add((<any>scopeService) [field], {context:scopeService});
|
|
347
|
+
this.serviceBindings.push(signalBinding);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
private detachServiceBindings() {
|
|
352
|
+
for (const binding of this.serviceBindings) {
|
|
353
|
+
binding.detach();
|
|
354
|
+
}
|
|
355
|
+
this.serviceBindings = [];
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private exitThis() {
|
|
359
|
+
|
|
360
|
+
// TODO: BUG: when there is a scope change within another scope change there is some confusion about the call order
|
|
361
|
+
if (!this.parent) {
|
|
362
|
+
throw new Error('can\'t exit root scope?!');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this.logStack();
|
|
366
|
+
this.detachSignalBindings();
|
|
367
|
+
this.detachServiceBindings();
|
|
368
|
+
this.scope.onDeactivate?.();
|
|
369
|
+
this.onDeactivate.dispatch(this);
|
|
370
|
+
|
|
371
|
+
this.scope.onExit?.();
|
|
372
|
+
this.onExit.dispatch(this);
|
|
373
|
+
this.onScopeChange.dispatch({from: this, to: this.parent});
|
|
374
|
+
// this.injector._dispose();
|
|
375
|
+
|
|
376
|
+
this.parent.updateSignalBindings();
|
|
377
|
+
this.parent.scope.onSubReturn?.();
|
|
378
|
+
this.parent.onSubReturn.dispatch(this);
|
|
379
|
+
this.onSubReturn.dispatch(this);
|
|
380
|
+
this.parent.scope.onActivate?.();
|
|
381
|
+
this.parent.onActivate.dispatch(this);
|
|
382
|
+
this.closed = true;
|
|
383
|
+
|
|
384
|
+
this.onEnter.detachAll();
|
|
385
|
+
this.onExit.detachAll();
|
|
386
|
+
this.onSubReturn.detachAll();
|
|
387
|
+
this.onSubExit.detachAll();
|
|
388
|
+
this.onActivate.detachAll();
|
|
389
|
+
this.onDeactivate.detachAll();
|
|
390
|
+
|
|
391
|
+
}
|
|
392
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import {System} from './types';
|
|
2
|
+
import {Entity, GameEngine, ID, putIfAbsent, Signal, SignalBinding, SignalCallback} from './index';
|
|
3
|
+
import {AddSignalOptions} from 'mani-signal';
|
|
4
|
+
|
|
5
|
+
export const entitySignalHandlers = new Map<Object, [string, ID][]>();
|
|
6
|
+
export const OnEntitySignal = (id: ID) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
|
|
7
|
+
if (target instanceof Function) {
|
|
8
|
+
throw new Error('only allowed on non static methods');
|
|
9
|
+
} else {
|
|
10
|
+
const mappingList = putIfAbsent(entitySignalHandlers, target.constructor, (): [string, ID][] => []);
|
|
11
|
+
mappingList.push([propertyKey, id]);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export class SystemContext<T extends System = System> {
|
|
16
|
+
readonly system!: T;
|
|
17
|
+
readonly onDispose = new Signal();
|
|
18
|
+
private readonly abortController = new AbortController();
|
|
19
|
+
get abortSignal() {
|
|
20
|
+
return this.abortController.signal;
|
|
21
|
+
}
|
|
22
|
+
private signalBindings = new Set<SignalBinding>();
|
|
23
|
+
|
|
24
|
+
constructor(readonly entity: Entity) {
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// TODO: is this a bit hacky?
|
|
28
|
+
addSignalById<S>(signalId: string | symbol, callback: SignalCallback<S>, options: AddSignalOptions = {}) {
|
|
29
|
+
this.signalBindings.add(((this.entity as any).gameEngine as GameEngine).getSignal<S>(signalId).add(callback, options));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// returns a function that can be called to remove the signal
|
|
33
|
+
addSignal<S>(signal: Signal<S>, callback: SignalCallback<S>, options: AddSignalOptions = {}) {
|
|
34
|
+
const binding = signal.add(callback, options);
|
|
35
|
+
this.signalBindings.add(binding);
|
|
36
|
+
return () => {
|
|
37
|
+
binding.detach();
|
|
38
|
+
this.signalBindings.delete(binding);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
addSignalOnce<S>(signal: Signal<S>, callback: SignalCallback<S>, options: AddSignalOptions = {}) {
|
|
43
|
+
const binding = signal.addOnce(callback, options);
|
|
44
|
+
this.signalBindings.add(binding);
|
|
45
|
+
return () => {
|
|
46
|
+
binding.detach();
|
|
47
|
+
this.signalBindings.delete(binding);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
dispose() {
|
|
53
|
+
this.abortController.abort();
|
|
54
|
+
for (const binding of this.signalBindings) binding.detach();
|
|
55
|
+
this.onDispose.dispatch();
|
|
56
|
+
this.onDispose.detachAll();
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {GameEngine} from './gameEngine';
|
|
2
|
+
import {Entity} from './entity';
|
|
3
|
+
|
|
4
|
+
export interface System {
|
|
5
|
+
[propName: string]: any;
|
|
6
|
+
onPrepare?(): Promise<void>;
|
|
7
|
+
onStart?(): void;
|
|
8
|
+
onEnd?(): void | Promise<void>;
|
|
9
|
+
onPreFixedUpdate?(time: number, deltaTime: number): void;
|
|
10
|
+
onFixedUpdate?(time: number, deltaTime: number): void;
|
|
11
|
+
onPhysicsUpdate?(time: number, deltaTime: number): void;
|
|
12
|
+
onUpdate?(time: number, deltaTime: number, alpha: number): void;
|
|
13
|
+
onLateUpdate?(time: number, deltaTime: number, alpha: number): void;
|
|
14
|
+
onPrePhysicsUpdate?(time: number, deltaTime: number): void;
|
|
15
|
+
onPostPhysicsUpdate?(time: number, deltaTime: number): void;
|
|
16
|
+
onLateFixedUpdate?(time: number, deltaTime: number): void;
|
|
17
|
+
onRender?(time: number, deltaTime: number, alpha: number): void;
|
|
18
|
+
onAddEntity?(entity: Entity): void;
|
|
19
|
+
onRemoveEntity?(entity: Entity): void;
|
|
20
|
+
// new(...args: any[]): System;
|
|
21
|
+
// new(): GameSystem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// export interface GameSystemClass {
|
|
25
|
+
// new(...args: any[]): GameSystem;
|
|
26
|
+
// }
|
|
27
|
+
|
|
28
|
+
export interface Service {
|
|
29
|
+
[propName: string]: any; // disable weak type detection
|
|
30
|
+
onPrepare?(): Promise<void>;
|
|
31
|
+
onStart?(): void;
|
|
32
|
+
onEnd?(): void;
|
|
33
|
+
onPreFixedUpdate?(time: number, deltaTime: number): void;
|
|
34
|
+
onPostPhysicsUpdate?(time: number, deltaTime: number): void;
|
|
35
|
+
onLateFixedUpdate?(time: number, deltaTime: number): void;
|
|
36
|
+
onFixedUpdate?(time: number, deltaTime: number): void;
|
|
37
|
+
onPhysicsUpdate?(time: number, deltaTime: number): void;
|
|
38
|
+
onPrePhysicsUpdate?(time: number, deltaTime: number): void;
|
|
39
|
+
onUpdate?(time: number, deltaTime: number, alpha: number): void;
|
|
40
|
+
onLateUpdate?(time: number, deltaTime: number, alpha: number): void;
|
|
41
|
+
onRender?(time: number, deltaTime: number, alpha: number): void;
|
|
42
|
+
onAddEntity?(entity: Entity): void;
|
|
43
|
+
onRemoveEntity?(entity: Entity): void;
|
|
44
|
+
// new(): GameSystem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type GameEngineCallback = (gameEngine: GameEngine) => void;
|
|
48
|
+
|
|
49
|
+
export interface GameEnginePlugin {
|
|
50
|
+
// systems?: (SystemClass | GameSystemClass)[];
|
|
51
|
+
// services?: Service[];
|
|
52
|
+
onPrepare?: GameEngineCallback;
|
|
53
|
+
onStart?: GameEngineCallback;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CommandClass {
|
|
57
|
+
name?: string;
|
|
58
|
+
|
|
59
|
+
new(...args: any[]): any;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type OnUpdateParams = {
|
|
63
|
+
time: number;
|
|
64
|
+
deltaTime: number;
|
|
65
|
+
alpha: number;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type OnFixedUpdateParams = {
|
|
69
|
+
time: number;
|
|
70
|
+
deltaTime: number;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type OnEarlyUpdateParams = { time: number, deltaTime: number };
|
|
74
|
+
|
|
75
|
+
export type AsyncAction<T = unknown> = (gameEngine: GameEngine) => Promise<T>;
|
|
76
|
+
// export type Action<T = unknown> = (gameEngine: GameEngine) => T;
|
|
77
|
+
export type Action<T = any> = (gameEngine: GameEngine) => T;
|
|
78
|
+
export type Class<T = any> = { new(...args: any[]): T; }
|
|
79
|
+
|
|
80
|
+
export const EngineSignals = {
|
|
81
|
+
OnStart: Symbol('OnStart'),
|
|
82
|
+
OnEnd: Symbol('OnEnd'),
|
|
83
|
+
OnUpdate: Symbol('OnUpdate'),
|
|
84
|
+
OnLateUpdate: Symbol('OnLateUpdate'),
|
|
85
|
+
OnFixedUpdate: Symbol('OnFixedUpdate'),
|
|
86
|
+
OnLateFixedUpdate: Symbol('OnLateFixedUpdate'),
|
|
87
|
+
OnPreFixedUpdate: Symbol('OnPreFixedUpdate'),
|
|
88
|
+
OnPrePhysicsUpdate: Symbol('OnPrePhysicsUpdate'),
|
|
89
|
+
OnPhysicsUpdate: Symbol('OnPhysicsUpdate'),
|
|
90
|
+
OnPostPhysicsUpdate: Symbol('OnPostPhysicsUpdate'),
|
|
91
|
+
OnRender: Symbol('OnRender'),
|
|
92
|
+
OnPrepare: Symbol('OnPrepare'),
|
|
93
|
+
OnAddEntity: Symbol('OnAddEntity'),
|
|
94
|
+
OnRemoveEntity: Symbol('OnRemoveEntity'),
|
|
95
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class Map2k<K, V> {
|
|
2
|
+
map = new Map<K, Map<K, V>>();
|
|
3
|
+
|
|
4
|
+
set(key1: K, key2: K, value: V) {
|
|
5
|
+
let first = this.map.get(key1);
|
|
6
|
+
if(!first) {
|
|
7
|
+
first = new Map<K, V>();
|
|
8
|
+
this.map.set(key1, first);
|
|
9
|
+
}
|
|
10
|
+
let second = this.map.get(key2);
|
|
11
|
+
if(!second) {
|
|
12
|
+
second = new Map<K, V>();
|
|
13
|
+
this.map.set(key2, second);
|
|
14
|
+
}
|
|
15
|
+
first.set(key2, value);
|
|
16
|
+
second.set(key1, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// putIfAbsent(key1: K, key2: K, cb: () => V): V {
|
|
20
|
+
// let first = this.map.get(key1);
|
|
21
|
+
// let value;
|
|
22
|
+
// if(!first) {
|
|
23
|
+
// first = new Map<K, V>();
|
|
24
|
+
// this.map.set(key1, first);
|
|
25
|
+
// value = cb();
|
|
26
|
+
// first.set(key2, value);
|
|
27
|
+
// }
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
get(key1: K, key2: K): V | undefined {
|
|
31
|
+
const first = this.map.get(key1);
|
|
32
|
+
return first && first.get(key2);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
delete(key1:K, key2:K) {
|
|
36
|
+
let first = this.map.get(key1);
|
|
37
|
+
if (first) {
|
|
38
|
+
first.delete(key2);
|
|
39
|
+
if (first.size === 0) {
|
|
40
|
+
this.map.delete(key1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
let second = this.map.get(key2);
|
|
44
|
+
if (second) {
|
|
45
|
+
second.delete(key1);
|
|
46
|
+
if (second.size === 0) {
|
|
47
|
+
this.map.delete(key2)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
}
|
package/clockService.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { Space } from 'mani-ecs-test/space';
|
|
2
|
-
export interface Updatable {
|
|
3
|
-
update(time: number, deltaTime: number): void;
|
|
4
|
-
}
|
|
5
|
-
export declare class ClockService {
|
|
6
|
-
private frameLoop;
|
|
7
|
-
private updateTime;
|
|
8
|
-
constructor(space: Space);
|
|
9
|
-
timeScale: number;
|
|
10
|
-
start(): void;
|
|
11
|
-
stop(): void;
|
|
12
|
-
}
|
package/clockService.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { FrameLoop } from 'mani-frameloop/frameLoop';
|
|
2
|
-
export class ClockService {
|
|
3
|
-
constructor(space) {
|
|
4
|
-
this.updateTime = 0;
|
|
5
|
-
this.frameLoop = new FrameLoop(1 / 60, () => {
|
|
6
|
-
space.prepareFrame();
|
|
7
|
-
}, (time, deltaTime) => {
|
|
8
|
-
space.preFixedUpdate();
|
|
9
|
-
space.fixedUpdate(time, deltaTime);
|
|
10
|
-
space.prePhysicUpdate(time, deltaTime);
|
|
11
|
-
space.postPhysicUpdate(time, deltaTime);
|
|
12
|
-
}, (time, deltaTime, alpha) => {
|
|
13
|
-
space.update(time, deltaTime, alpha);
|
|
14
|
-
space.lateUpdate(time, deltaTime, alpha);
|
|
15
|
-
});
|
|
16
|
-
this.start();
|
|
17
|
-
}
|
|
18
|
-
get timeScale() {
|
|
19
|
-
return this.frameLoop.timeScale;
|
|
20
|
-
}
|
|
21
|
-
set timeScale(value) {
|
|
22
|
-
this.frameLoop.timeScale = value;
|
|
23
|
-
}
|
|
24
|
-
start() {
|
|
25
|
-
this.frameLoop.start();
|
|
26
|
-
}
|
|
27
|
-
stop() {
|
|
28
|
-
this.frameLoop.stop();
|
|
29
|
-
}
|
|
30
|
-
}
|