-
Notifications
You must be signed in to change notification settings - Fork 3
/
engine.ts
240 lines (213 loc) · 6.69 KB
/
engine.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
import { System, SystemListener, SystemMode } from './system';
import { AbstractEntity } from './entity';
import { Dispatcher } from './dispatcher';
import { Collection } from './collection';
import { EntityCollection } from './entity.collection';
/**
* System which is synced within an engine.
*/
type SyncedSystem = System & {
/**
* System listener mapping for engine specific caching purposes.
*/
__ecsEngineListener: SystemListener;
/**
* The list of listeners for this system.
*/
_lockedListeners: SystemListener[];
};
/**
* The listener interface for a listener on an engine.
*/
export interface EngineListener<T extends AbstractEntity = AbstractEntity> {
/**
* Called as soon as the given system gets added to the engine.
*
* @param systems
*/
onAddedSystems?(...systems: System[]): void;
/**
* Called as soon as the given system gets removed from the engine.
*
* @param systems
*/
onRemovedSystems?(...systems: System[]): void;
/**
* Called as soon as all systems got cleared from the engine.
*/
onClearedSystems?(): void;
/**
* Called as soon as an error occurred on in an active system during update.
*
* @param error The error that occurred.
* @param system The system on which the error occurred.
*/
onErrorBySystem?(error: Error, system: System): void;
/**
* Called as soon as the given entity gets added to the engine.
*
* @param entities
*/
onAddedEntities?(...entities: T[]): void;
/**
* Called as soon as the given entity gets removed from the engine.
*
* @param entities
*/
onRemovedEntities?(...entities: AbstractEntity[]): void;
/**
* Called as soon as all entities got cleared from the engine.
*/
onClearedEntities?(): void;
}
/**
* Defines how an engine executes its active systems.
*/
export enum EngineMode {
/**
* Execute all systems by priority without waiting for them to resolve.
*/
DEFAULT = 'runDefault',
/**
* Execute all systems by priority. Successive systems
* will wait until the current executing system resolves or rejects.
*/
SUCCESSIVE = 'runSuccessive',
/**
* Start all systems by priority, but run them all in parallel.
*/
PARALLEL = 'runParallel',
}
/**
* An engine puts entities and systems together.
* It holds for each type a collection, which can be queried by each system.
*
* The @see {Engine#update} method has to be called in order to perform updates on each system in a certain order.
* The engine takes care of updating only active systems in any point of time.
*
*/
export class Engine<T extends AbstractEntity = AbstractEntity> extends Dispatcher<EngineListener> {
/**
* The internal list of all systems in this engine.
*/
protected _systems = new Collection<System>();
/**
* The frozen list of active systems which is used to iterate during the update.
*/
protected _activeSystems: System[] = [];
/**
* The internal list of all entities in this engine.
*/
protected _entities = new EntityCollection<T>();
/**
* Creates an instance of Engine.
*/
constructor() {
super();
this._systems.addListener(
{
onAdded: (...systems: SyncedSystem[]) => {
this._systems.sort((a, b) => a.priority - b.priority);
systems.forEach(system => {
system.engine = this;
this.updatedActiveSystems();
const systemListener: SystemListener = {
onActivated: () => this.updatedActiveSystems(),
onDeactivated: () => this.updatedActiveSystems(),
onError: error => this.dispatch('onErrorBySystem', error, system),
};
system.__ecsEngineListener = systemListener;
system.addListener(systemListener, true);
});
this.dispatch('onAddedSystems', ...systems);
},
onRemoved: (...systems: SyncedSystem[]) => {
systems.forEach(system => {
system.engine = null;
this.updatedActiveSystems();
const systemListener = system.__ecsEngineListener;
const locked = system._lockedListeners;
locked.splice(locked.indexOf(systemListener), 1);
system.removeListener(systemListener);
});
this.dispatch('onRemovedSystems', ...systems);
},
onCleared: () => this.dispatch('onClearedSystems'),
},
true
);
this._entities.addListener(
{
onAdded: (...entities: T[]) => this.dispatch('onAddedEntities', ...entities),
onRemoved: (...entities: T[]) => this.dispatch('onRemovedEntities', ...entities),
onCleared: () => this.dispatch('onClearedEntities'),
},
true
);
this.updatedActiveSystems();
}
/**
* A snapshot of all entities in this engine.
*/
get entities(): EntityCollection<T> {
return this._entities;
}
/**
* A snapshot of all systems in this engine.
*/
get systems(): Collection<System> {
return this._systems;
}
/**
* A snapshot of all active systems in this engine.
*/
get activeSystems(): readonly System[] {
return this._activeSystems;
}
/**
* Updates the internal active system list.
*/
protected updatedActiveSystems(): void {
this._activeSystems = this.systems.filter(system => system.active);
Object.freeze(this._activeSystems);
}
/**
* Updates all systems in this engine by the given delta value.
*
* @param [options]
* @param [mode = EngineMode.DEFAULT]
*/
run<T>(options?: T, mode: EngineMode = EngineMode.DEFAULT): void | Promise<void> {
return this[mode].call(this, options);
}
/**
* Updates all systems in this engine by the given delta value,
* without waiting for a resolve or reject of each system.
*
* @param [options]
*/
protected runDefault<T>(options?: T): void {
const length = this._activeSystems.length;
for (let i = 0; i < length; i++) this._activeSystems[i].run(options, SystemMode.SYNC);
}
/**
* Updates all systems in this engine by the given delta value,
* by waiting for a system to resolve or reject before continuing with the next one.
*
* @param [options]
*/
protected async runSuccessive<T>(options?: T): Promise<void> {
const length = this._activeSystems.length;
for (let i = 0; i < length; i++) await this._activeSystems[i].run(options, SystemMode.SYNC);
}
/**
* Updates all systems in this engine by the given delta value,
* by running all systems in parallel and waiting for all systems to resolve or reject.
*
* @param [options]
*/
protected async runParallel<T>(options?: T): Promise<void> {
const mapped = this._activeSystems.map(system => system.run(options, SystemMode.ASYNC));
await Promise.all(mapped);
}
}