@draug/engine 1.0.0 → 1.0.2
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/dist/index.cjs +946 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +373 -0
- package/dist/index.d.ts +373 -0
- package/dist/index.js +883 -0
- package/dist/index.js.map +1 -0
- package/package.json +20 -6
- package/ecs/command.ts +0 -44
- package/ecs/components/component-storage.ts +0 -86
- package/ecs/components/index.ts +0 -12
- package/ecs/components/manager.ts +0 -91
- package/ecs/components/singleton-storage.ts +0 -51
- package/ecs/components/types.ts +0 -14
- package/ecs/components/utils.ts +0 -18
- package/ecs/constant.ts +0 -3
- package/ecs/entity.ts +0 -44
- package/ecs/events-buffer.ts +0 -62
- package/ecs/query.ts +0 -169
- package/ecs/resources/index.ts +0 -1
- package/ecs/resources/resources.ts +0 -28
- package/ecs/system.ts +0 -214
- package/ecs/world.ts +0 -99
- package/index.ts +0 -74
- package/plugin/index.ts +0 -15
- package/plugin/plugin.ts +0 -200
- package/runtime/clock.ts +0 -31
- package/runtime/game-loop.ts +0 -32
- package/runtime/runtime.ts +0 -12
- package/tsconfig.json +0 -24
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Clock: () => Clock,
|
|
24
|
+
Commands: () => Commands,
|
|
25
|
+
Component: () => Component,
|
|
26
|
+
ComponentAlreadyRegisteredError: () => ComponentAlreadyRegisteredError,
|
|
27
|
+
ComponentStorage: () => ComponentStorage,
|
|
28
|
+
ComponentsManager: () => ComponentsManager,
|
|
29
|
+
EntitiesManager: () => EntitiesManager,
|
|
30
|
+
EntityMaskNotFoundError: () => EntityMaskNotFoundError,
|
|
31
|
+
EntityRef: () => EntityRef,
|
|
32
|
+
ErrMissingPluginMetadata: () => ErrMissingPluginMetadata,
|
|
33
|
+
ErrMissingSystemMetadata: () => ErrMissingSystemMetadata,
|
|
34
|
+
ErrNotAPlugin: () => ErrNotAPlugin,
|
|
35
|
+
ErrNotASystem: () => ErrNotASystem,
|
|
36
|
+
ErrPluginNotInit: () => ErrPluginNotInit,
|
|
37
|
+
ErrUnknownPlugin: () => ErrUnknownPlugin,
|
|
38
|
+
EventBuffer: () => EventBuffer,
|
|
39
|
+
EventBus: () => EventBus,
|
|
40
|
+
GameLoop: () => GameLoop,
|
|
41
|
+
Plugin: () => Plugin,
|
|
42
|
+
PluginBase: () => PluginBase,
|
|
43
|
+
PluginError: () => PluginError,
|
|
44
|
+
PluginsManager: () => PluginsManager,
|
|
45
|
+
ResourcesManager: () => ResourcesManager,
|
|
46
|
+
Runtime: () => Runtime,
|
|
47
|
+
SingletonStorage: () => SingletonStorage,
|
|
48
|
+
System: () => System,
|
|
49
|
+
SystemBase: () => SystemBase,
|
|
50
|
+
SystemError: () => SystemError,
|
|
51
|
+
SystemsManager: () => SystemsManager,
|
|
52
|
+
UnregisteredComponentStorageError: () => UnregisteredComponentStorageError,
|
|
53
|
+
World: () => World3,
|
|
54
|
+
createEventKey: () => createEventKey,
|
|
55
|
+
entry: () => entry,
|
|
56
|
+
getPluginMetadata: () => getPluginMetadata,
|
|
57
|
+
getSystemMetadata: () => getSystemMetadata,
|
|
58
|
+
isPlugin: () => isPlugin,
|
|
59
|
+
isSystem: () => isSystem
|
|
60
|
+
});
|
|
61
|
+
module.exports = __toCommonJS(index_exports);
|
|
62
|
+
|
|
63
|
+
// src/ecs/system.ts
|
|
64
|
+
var import_dag = require("@draug/core/graph/dag");
|
|
65
|
+
var SystemError = class extends Error {
|
|
66
|
+
constructor(target) {
|
|
67
|
+
super(`[System Error] (System "${target.name}".`);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var ErrNotASystem = class extends Error {
|
|
71
|
+
constructor(target) {
|
|
72
|
+
super(`Provided class "${target.name}" is not a System! Extend your class from SystemBase.`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var ErrMissingSystemMetadata = class extends SystemError {
|
|
76
|
+
constructor(target) {
|
|
77
|
+
super(target);
|
|
78
|
+
this.message = `${this.message}: Missing system metadata! Define system class with @System decorator.`;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
var SystemMetadataSymbol = /* @__PURE__ */ Symbol("system");
|
|
82
|
+
function System(props) {
|
|
83
|
+
return (target) => {
|
|
84
|
+
const systemTarget = target;
|
|
85
|
+
if ("__proto__" in systemTarget && systemTarget.__proto__ !== SystemBase) {
|
|
86
|
+
throw new ErrNotASystem(target);
|
|
87
|
+
}
|
|
88
|
+
const query = { ...props.query };
|
|
89
|
+
const requiredComponents = new Set(props.requiredComponents);
|
|
90
|
+
const computeAfter = new Set(props.computeAfter);
|
|
91
|
+
const metadata = { query, requiredComponents, computeAfter };
|
|
92
|
+
systemTarget[SystemMetadataSymbol] = metadata;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function getSystemMetadata(system) {
|
|
96
|
+
if (hasMetadata(system)) {
|
|
97
|
+
return system[SystemMetadataSymbol];
|
|
98
|
+
}
|
|
99
|
+
throw new ErrMissingSystemMetadata(system);
|
|
100
|
+
}
|
|
101
|
+
function hasMetadata(ctor) {
|
|
102
|
+
return SystemMetadataSymbol in ctor;
|
|
103
|
+
}
|
|
104
|
+
function isSystem(ctor) {
|
|
105
|
+
return hasMetadata(ctor);
|
|
106
|
+
}
|
|
107
|
+
var SystemBase = class {
|
|
108
|
+
};
|
|
109
|
+
var SystemsManager = class {
|
|
110
|
+
constructor(world) {
|
|
111
|
+
this.world = world;
|
|
112
|
+
}
|
|
113
|
+
world;
|
|
114
|
+
systems_ = /* @__PURE__ */ new Map();
|
|
115
|
+
executionOrder_ = [];
|
|
116
|
+
requiredComponents_ = /* @__PURE__ */ new Set();
|
|
117
|
+
dirty_ = true;
|
|
118
|
+
getRequiredComponents() {
|
|
119
|
+
return Array.from(this.requiredComponents_);
|
|
120
|
+
}
|
|
121
|
+
register(sys) {
|
|
122
|
+
const ctor = sys.constructor;
|
|
123
|
+
if (this.systems_.has(ctor)) throw new Error("Duplicate system");
|
|
124
|
+
const { query, requiredComponents } = getSystemMetadata(ctor);
|
|
125
|
+
this.systems_.set(ctor, sys);
|
|
126
|
+
const q = query;
|
|
127
|
+
for (const c of q.include ?? [])
|
|
128
|
+
this.requiredComponents_.add(c);
|
|
129
|
+
for (const c of q.exclude ?? [])
|
|
130
|
+
this.requiredComponents_.add(c);
|
|
131
|
+
for (const c of q.anyOf ?? [])
|
|
132
|
+
this.requiredComponents_.add(c);
|
|
133
|
+
for (const c of requiredComponents)
|
|
134
|
+
this.requiredComponents_.add(c);
|
|
135
|
+
}
|
|
136
|
+
build() {
|
|
137
|
+
this.buildSystemsArray();
|
|
138
|
+
for (const sys of this.systems_.values())
|
|
139
|
+
sys.onInit?.(this.world);
|
|
140
|
+
}
|
|
141
|
+
rebuild() {
|
|
142
|
+
this.build();
|
|
143
|
+
}
|
|
144
|
+
get(ctor) {
|
|
145
|
+
const s = this.systems_.get(ctor);
|
|
146
|
+
if (!s)
|
|
147
|
+
throw new Error("System not registered");
|
|
148
|
+
return s;
|
|
149
|
+
}
|
|
150
|
+
update(dt) {
|
|
151
|
+
if (this.dirty_)
|
|
152
|
+
this.rebuild();
|
|
153
|
+
this.world.events.swapAll();
|
|
154
|
+
for (const s of this.executionOrder_) {
|
|
155
|
+
const { query } = getSystemMetadata(s.constructor);
|
|
156
|
+
const entities = this.world.query(query);
|
|
157
|
+
s.compute({ entities, world: this.world, dt });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
buildSystemsArray() {
|
|
161
|
+
const map = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const [ctor, system] of this.systems_.entries()) {
|
|
163
|
+
map.set(ctor, new import_dag.DAGNode(system));
|
|
164
|
+
}
|
|
165
|
+
for (const ctor of this.systems_.keys()) {
|
|
166
|
+
const currentNode = map.get(ctor);
|
|
167
|
+
const { computeAfter } = getSystemMetadata(ctor);
|
|
168
|
+
for (const depCtor of computeAfter ?? []) {
|
|
169
|
+
const depNode = map.get(depCtor);
|
|
170
|
+
if (!depNode) {
|
|
171
|
+
throw new Error(`Dependency ${depCtor.name} not registered`);
|
|
172
|
+
}
|
|
173
|
+
depNode.vertices.push(currentNode);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.executionOrder_ = (0, import_dag.topologicalSort)(map.values()).map((x) => x.data);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// src/ecs/entity.ts
|
|
181
|
+
var UnregisteredComponentStorageError = class extends Error {
|
|
182
|
+
constructor(component) {
|
|
183
|
+
super(`Cannot get storage for component ${component.name}. Seems like it's not registered in world.`);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var EntityMaskNotFoundError = class extends Error {
|
|
187
|
+
constructor(id2) {
|
|
188
|
+
super(`Cannot find bitmask for entity [${id2}]. Seems like it's not registered in the EntityManager.`);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
var EntitiesManager = class {
|
|
192
|
+
id_ = 0;
|
|
193
|
+
nextId() {
|
|
194
|
+
return ++this.id_;
|
|
195
|
+
}
|
|
196
|
+
create() {
|
|
197
|
+
return this.nextId();
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
var EntityRef = class {
|
|
201
|
+
constructor(world, id2) {
|
|
202
|
+
this.world = world;
|
|
203
|
+
this.id = id2;
|
|
204
|
+
}
|
|
205
|
+
world;
|
|
206
|
+
id;
|
|
207
|
+
with(...components) {
|
|
208
|
+
return components.map((c) => {
|
|
209
|
+
const s = this.world.components.getStorage(c);
|
|
210
|
+
return s.tryGet(this.id);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
// src/ecs/constant.ts
|
|
216
|
+
var ECS_DEFAULTS = {
|
|
217
|
+
MAX_ENTITY_COUNT: Math.pow(2, 16)
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// src/ecs/events-buffer.ts
|
|
221
|
+
var EventBuffer = class {
|
|
222
|
+
readBuf = [];
|
|
223
|
+
writeBuf = [];
|
|
224
|
+
write(event) {
|
|
225
|
+
this.writeBuf.push(event);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Advances the buffer to the next frame.
|
|
229
|
+
*
|
|
230
|
+
* Performs a double-buffer flip:
|
|
231
|
+
* - Promotes all events written during the previous frame (`writeBuf`)
|
|
232
|
+
* to be readable in the current frame (`readBuf`).
|
|
233
|
+
* - Reuses the previous `readBuf` as the new `writeBuf` and clears it
|
|
234
|
+
* to collect events for the next frame.
|
|
235
|
+
*
|
|
236
|
+
* After calling this method:
|
|
237
|
+
* - `get()` will return a stable snapshot of events produced in the previous frame.
|
|
238
|
+
* - `add()` will write into an empty buffer for the current frame.
|
|
239
|
+
*
|
|
240
|
+
* Guarantees:
|
|
241
|
+
* - No events written during the current frame are visible until the next `swap()`.
|
|
242
|
+
* - Readers observe a consistent, immutable snapshot within a frame.
|
|
243
|
+
*
|
|
244
|
+
* Expected to be called exactly once per frame, before system execution.
|
|
245
|
+
*/
|
|
246
|
+
swap() {
|
|
247
|
+
const tmp = this.readBuf;
|
|
248
|
+
this.readBuf = this.writeBuf;
|
|
249
|
+
this.writeBuf = tmp;
|
|
250
|
+
this.writeBuf.length = 0;
|
|
251
|
+
}
|
|
252
|
+
read() {
|
|
253
|
+
return this.readBuf;
|
|
254
|
+
}
|
|
255
|
+
size() {
|
|
256
|
+
return this.readBuf.length;
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
function createEventKey(description) {
|
|
260
|
+
return Symbol(description);
|
|
261
|
+
}
|
|
262
|
+
var EventBus = class {
|
|
263
|
+
storage = /* @__PURE__ */ new Map();
|
|
264
|
+
swapAll() {
|
|
265
|
+
this.storage.forEach((s) => s.swap());
|
|
266
|
+
}
|
|
267
|
+
getBuffer(key) {
|
|
268
|
+
let buf = this.storage.get(key);
|
|
269
|
+
if (!buf) {
|
|
270
|
+
buf = new EventBuffer();
|
|
271
|
+
this.storage.set(key, buf);
|
|
272
|
+
}
|
|
273
|
+
return buf;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/ecs/components/component-storage.ts
|
|
278
|
+
var import_pool = require("@draug/core/memory/pool");
|
|
279
|
+
var import_bitmap_index = require("bitmap-index");
|
|
280
|
+
var import_ts_sparse_set = require("ts-sparse-set");
|
|
281
|
+
var ComponentStorage = class {
|
|
282
|
+
bits_;
|
|
283
|
+
set_;
|
|
284
|
+
pool_;
|
|
285
|
+
id_ = 0;
|
|
286
|
+
cls;
|
|
287
|
+
constructor(cap = ECS_DEFAULTS.MAX_ENTITY_COUNT, factory, cls) {
|
|
288
|
+
this.set_ = new import_ts_sparse_set.SparseSet(cap);
|
|
289
|
+
this.bits_ = new import_bitmap_index.Bitmap(cap);
|
|
290
|
+
this.pool_ = new import_pool.ObjectPool(factory, cap);
|
|
291
|
+
this.cls = cls;
|
|
292
|
+
}
|
|
293
|
+
bitmap() {
|
|
294
|
+
return this.bits_;
|
|
295
|
+
}
|
|
296
|
+
get id() {
|
|
297
|
+
return this.id_;
|
|
298
|
+
}
|
|
299
|
+
_internalSetId(id2) {
|
|
300
|
+
return this.id_ = id2;
|
|
301
|
+
}
|
|
302
|
+
add(id2, initFn) {
|
|
303
|
+
const obj = this.pool_.acquire();
|
|
304
|
+
initFn?.(obj);
|
|
305
|
+
const value = this.set_.add(id2, obj);
|
|
306
|
+
this.bits_.set(id2);
|
|
307
|
+
return value;
|
|
308
|
+
}
|
|
309
|
+
remove(id2) {
|
|
310
|
+
const obj = this.set_.get(id2);
|
|
311
|
+
if (!obj) return;
|
|
312
|
+
this.bits_.remove(id2);
|
|
313
|
+
this.pool_.release(obj);
|
|
314
|
+
this.set_.remove(id2);
|
|
315
|
+
}
|
|
316
|
+
get(id2) {
|
|
317
|
+
return this.set_.get(id2);
|
|
318
|
+
}
|
|
319
|
+
tryGet(id2) {
|
|
320
|
+
const x = this.set_.get(id2);
|
|
321
|
+
if (!x)
|
|
322
|
+
throw new Error(`[ComponentStorage "${this.cls.name}"]: Requesting non-existing item with ID ${id2}.`);
|
|
323
|
+
return x;
|
|
324
|
+
}
|
|
325
|
+
writeComponentsToBuf(ids, out) {
|
|
326
|
+
let len = 0;
|
|
327
|
+
for (const id2 of ids) {
|
|
328
|
+
const obj = this.set_.get(id2);
|
|
329
|
+
if (obj !== null) out[len++] = obj;
|
|
330
|
+
}
|
|
331
|
+
return len;
|
|
332
|
+
}
|
|
333
|
+
has(id2) {
|
|
334
|
+
return this.bits_.contains(id2);
|
|
335
|
+
}
|
|
336
|
+
entityIds() {
|
|
337
|
+
return Array.from(this.bits_);
|
|
338
|
+
}
|
|
339
|
+
size() {
|
|
340
|
+
return this.bits_.count();
|
|
341
|
+
}
|
|
342
|
+
forEach(cb) {
|
|
343
|
+
this.bits_.range((x) => cb(x));
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/ecs/components/singleton-storage.ts
|
|
348
|
+
var SingletonStorage = class {
|
|
349
|
+
constructor(factory) {
|
|
350
|
+
this.factory = factory;
|
|
351
|
+
}
|
|
352
|
+
factory;
|
|
353
|
+
value = null;
|
|
354
|
+
entityId = null;
|
|
355
|
+
bitmap() {
|
|
356
|
+
throw new Error("Singletone component cannot has a bitmap!");
|
|
357
|
+
}
|
|
358
|
+
add(id2, initFn) {
|
|
359
|
+
if (this.value !== null)
|
|
360
|
+
throw new Error("Singleton already initiated");
|
|
361
|
+
this.entityId = id2;
|
|
362
|
+
this.value = this.factory();
|
|
363
|
+
initFn?.(this.value);
|
|
364
|
+
return this.value;
|
|
365
|
+
}
|
|
366
|
+
remove(id2) {
|
|
367
|
+
if (!this.validateId(id2))
|
|
368
|
+
return;
|
|
369
|
+
this.value = null;
|
|
370
|
+
this.entityId = null;
|
|
371
|
+
}
|
|
372
|
+
get(id2) {
|
|
373
|
+
if (!this.validateId(id2))
|
|
374
|
+
return null;
|
|
375
|
+
return this.value;
|
|
376
|
+
}
|
|
377
|
+
tryGet(id2) {
|
|
378
|
+
if (!this.validateId(id2))
|
|
379
|
+
throw new Error("[SingletoneStorage]: ID missmatch.");
|
|
380
|
+
return this.value;
|
|
381
|
+
}
|
|
382
|
+
has(id2) {
|
|
383
|
+
return this.validateId(id2);
|
|
384
|
+
}
|
|
385
|
+
size() {
|
|
386
|
+
return this.value !== null ? 1 : 0;
|
|
387
|
+
}
|
|
388
|
+
forEach(cb) {
|
|
389
|
+
if (this.entityId !== null)
|
|
390
|
+
cb(this.entityId);
|
|
391
|
+
}
|
|
392
|
+
validateId(id2) {
|
|
393
|
+
return this.entityId !== null && id2 === this.entityId;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// src/ecs/components/manager.ts
|
|
398
|
+
var ComponentAlreadyRegisteredError = class extends Error {
|
|
399
|
+
constructor(component) {
|
|
400
|
+
super(`Component ${component.name} already registered!`);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var ComponentsManager = class {
|
|
404
|
+
constructor(maxEntityCount = ECS_DEFAULTS.MAX_ENTITY_COUNT) {
|
|
405
|
+
this.maxEntityCount = maxEntityCount;
|
|
406
|
+
}
|
|
407
|
+
maxEntityCount;
|
|
408
|
+
storages_ = /* @__PURE__ */ new Map();
|
|
409
|
+
currId_ = 0;
|
|
410
|
+
nextId() {
|
|
411
|
+
return ++this.currId_;
|
|
412
|
+
}
|
|
413
|
+
register(component, opts) {
|
|
414
|
+
if (this.storages_.has(component))
|
|
415
|
+
return this.storages_.get(component);
|
|
416
|
+
let store;
|
|
417
|
+
switch (opts?.storageType) {
|
|
418
|
+
case 2 /* SINGLETON_STORAGE */:
|
|
419
|
+
store = this.createSingletonStore(opts);
|
|
420
|
+
break;
|
|
421
|
+
case 1 /* COMPONENT_STORAGE */:
|
|
422
|
+
store = this.createComponentStore(component, opts);
|
|
423
|
+
break;
|
|
424
|
+
default:
|
|
425
|
+
store = this.createComponentStore(component, opts);
|
|
426
|
+
break;
|
|
427
|
+
}
|
|
428
|
+
this.storages_.set(component, store);
|
|
429
|
+
return store;
|
|
430
|
+
}
|
|
431
|
+
createComponentStore(component, opts) {
|
|
432
|
+
const factory = opts?.factory ?? ((...args) => new component(...args));
|
|
433
|
+
const store = new ComponentStorage(this.maxEntityCount, factory, component);
|
|
434
|
+
store._internalSetId(this.nextId());
|
|
435
|
+
return store;
|
|
436
|
+
}
|
|
437
|
+
createSingletonStore(opts) {
|
|
438
|
+
if (!opts?.factory) {
|
|
439
|
+
throw new Error("For singletone storage provide factory is required!");
|
|
440
|
+
}
|
|
441
|
+
return new SingletonStorage(opts.factory);
|
|
442
|
+
}
|
|
443
|
+
getStorage(component) {
|
|
444
|
+
const store = this.storages_.get(component);
|
|
445
|
+
if (store === void 0)
|
|
446
|
+
throw new UnregisteredComponentStorageError(component);
|
|
447
|
+
return store;
|
|
448
|
+
}
|
|
449
|
+
getComponentId(ctor) {
|
|
450
|
+
return getComponentId(ctor);
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
// src/ecs/components/utils.ts
|
|
455
|
+
var registry = /* @__PURE__ */ new Map();
|
|
456
|
+
var id = 0;
|
|
457
|
+
function Component() {
|
|
458
|
+
return (target) => {
|
|
459
|
+
if (registry.has(target)) return;
|
|
460
|
+
registry.set(target, ++id);
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function getComponentId(ctor) {
|
|
464
|
+
const id2 = registry.get(ctor);
|
|
465
|
+
if (id2 === void 0) {
|
|
466
|
+
throw new Error(`Component not registered: ${ctor.name}`);
|
|
467
|
+
}
|
|
468
|
+
return id2;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/ecs/resources/resources.ts
|
|
472
|
+
var ResourcesManager = class {
|
|
473
|
+
items_ = /* @__PURE__ */ new Map();
|
|
474
|
+
insert(type, value) {
|
|
475
|
+
this.items_.set(type, value);
|
|
476
|
+
return value;
|
|
477
|
+
}
|
|
478
|
+
get(type) {
|
|
479
|
+
const value = this.items_.get(type);
|
|
480
|
+
if (!value)
|
|
481
|
+
throw new Error(`Resource of class ${type.name} does not exist!`);
|
|
482
|
+
return value;
|
|
483
|
+
}
|
|
484
|
+
getOrInsert(type, factory) {
|
|
485
|
+
let value = this.items_.get(type) ?? null;
|
|
486
|
+
if (value === null) {
|
|
487
|
+
value = factory();
|
|
488
|
+
this.items_.set(type, value);
|
|
489
|
+
}
|
|
490
|
+
return value;
|
|
491
|
+
}
|
|
492
|
+
remove(type) {
|
|
493
|
+
this.items_.delete(type);
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// src/ecs/command.ts
|
|
498
|
+
function entry(component, init = () => {
|
|
499
|
+
}) {
|
|
500
|
+
return [component, init];
|
|
501
|
+
}
|
|
502
|
+
var Commands = class {
|
|
503
|
+
constructor(world) {
|
|
504
|
+
this.world = world;
|
|
505
|
+
}
|
|
506
|
+
world;
|
|
507
|
+
commandsQueue_ = [];
|
|
508
|
+
add(cmd) {
|
|
509
|
+
this.commandsQueue_.push(cmd);
|
|
510
|
+
}
|
|
511
|
+
flush(world) {
|
|
512
|
+
for (const cmd of this.commandsQueue_)
|
|
513
|
+
cmd(world);
|
|
514
|
+
this.commandsQueue_.length = 0;
|
|
515
|
+
}
|
|
516
|
+
createEntity(...entries) {
|
|
517
|
+
const id2 = this.world.entities.create();
|
|
518
|
+
const cmd = (world) => {
|
|
519
|
+
for (const [cls, initFn] of entries) {
|
|
520
|
+
world.addComponent(id2, cls, initFn);
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
this.add(cmd);
|
|
524
|
+
return id2;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
// src/ecs/query.ts
|
|
529
|
+
var import_bitmap_index2 = require("bitmap-index");
|
|
530
|
+
var QueryManager = class {
|
|
531
|
+
constructor(world) {
|
|
532
|
+
this.world = world;
|
|
533
|
+
}
|
|
534
|
+
world;
|
|
535
|
+
cache = /* @__PURE__ */ new Map();
|
|
536
|
+
get(params) {
|
|
537
|
+
const key = this.getKey(params);
|
|
538
|
+
let entry2 = this.cache.get(key);
|
|
539
|
+
if (!entry2) {
|
|
540
|
+
entry2 = {
|
|
541
|
+
params,
|
|
542
|
+
bitmap: this.compute(params),
|
|
543
|
+
dirty: false,
|
|
544
|
+
deps: this.collectDeps(params)
|
|
545
|
+
};
|
|
546
|
+
this.cache.set(key, entry2);
|
|
547
|
+
}
|
|
548
|
+
if (entry2.dirty) {
|
|
549
|
+
entry2.bitmap = this.compute(entry2.params);
|
|
550
|
+
entry2.dirty = false;
|
|
551
|
+
}
|
|
552
|
+
let targetBitmap = entry2.bitmap;
|
|
553
|
+
if (params.excludeEntitiesIds?.length) {
|
|
554
|
+
targetBitmap = entry2.bitmap.clone();
|
|
555
|
+
const excludeBm = new import_bitmap_index2.Bitmap();
|
|
556
|
+
for (const id2 of params.excludeEntitiesIds) {
|
|
557
|
+
excludeBm.set(id2);
|
|
558
|
+
}
|
|
559
|
+
targetBitmap.andNot(excludeBm);
|
|
560
|
+
}
|
|
561
|
+
if (params.filter) {
|
|
562
|
+
const result = [];
|
|
563
|
+
targetBitmap.range((id2) => {
|
|
564
|
+
if (params.filter(id2)) result.push(id2);
|
|
565
|
+
});
|
|
566
|
+
return result;
|
|
567
|
+
}
|
|
568
|
+
return this.extractIds(targetBitmap);
|
|
569
|
+
}
|
|
570
|
+
invalidate(component) {
|
|
571
|
+
for (const entry2 of this.cache.values()) {
|
|
572
|
+
if (entry2.deps.has(component)) {
|
|
573
|
+
entry2.dirty = true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
getKey(q) {
|
|
578
|
+
return [
|
|
579
|
+
this.ids(q.include),
|
|
580
|
+
this.ids(q.exclude),
|
|
581
|
+
this.ids(q.anyOf)
|
|
582
|
+
].join("|");
|
|
583
|
+
}
|
|
584
|
+
ids(arr) {
|
|
585
|
+
if (!arr || arr.length === 0) return "";
|
|
586
|
+
return arr.map((c) => this.world.components.getComponentId(c)).sort((a, b) => a - b).join(",");
|
|
587
|
+
}
|
|
588
|
+
collectDeps(q) {
|
|
589
|
+
const set = /* @__PURE__ */ new Set();
|
|
590
|
+
q.include?.forEach((c) => set.add(c));
|
|
591
|
+
q.exclude?.forEach((c) => set.add(c));
|
|
592
|
+
q.anyOf?.forEach((c) => set.add(c));
|
|
593
|
+
return set;
|
|
594
|
+
}
|
|
595
|
+
compute(params) {
|
|
596
|
+
let result = this.combineBitmaps(params.include, "and");
|
|
597
|
+
const any = this.combineBitmaps(params.anyOf, "or");
|
|
598
|
+
if (any) {
|
|
599
|
+
result = result ? result.and(any) : any;
|
|
600
|
+
}
|
|
601
|
+
if (!result) {
|
|
602
|
+
return new import_bitmap_index2.Bitmap();
|
|
603
|
+
}
|
|
604
|
+
this.applyExclusions(result, params.exclude);
|
|
605
|
+
return result;
|
|
606
|
+
}
|
|
607
|
+
combineBitmaps(components, op) {
|
|
608
|
+
if (!components?.length) return null;
|
|
609
|
+
let result = null;
|
|
610
|
+
let hasAtLeastOneValid = false;
|
|
611
|
+
for (const c of components) {
|
|
612
|
+
const bm = this.world.components.getStorage(c)?.bitmap();
|
|
613
|
+
if (!bm) {
|
|
614
|
+
if (op === "and") {
|
|
615
|
+
return new import_bitmap_index2.Bitmap();
|
|
616
|
+
}
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
hasAtLeastOneValid = true;
|
|
620
|
+
if (!result) {
|
|
621
|
+
result = bm.clone();
|
|
622
|
+
} else {
|
|
623
|
+
op === "and" ? result.and(bm) : result.or(bm);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (op === "or" && !hasAtLeastOneValid) {
|
|
627
|
+
return new import_bitmap_index2.Bitmap();
|
|
628
|
+
}
|
|
629
|
+
return result;
|
|
630
|
+
}
|
|
631
|
+
applyExclusions(target, excludeComponents) {
|
|
632
|
+
if (excludeComponents?.length) {
|
|
633
|
+
for (const c of excludeComponents) {
|
|
634
|
+
const bm = this.world.components.getStorage(c)?.bitmap();
|
|
635
|
+
if (bm) target.andNot(bm);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
extractIds(bitmap) {
|
|
640
|
+
const result = [];
|
|
641
|
+
bitmap.range((id2) => {
|
|
642
|
+
result.push(id2);
|
|
643
|
+
});
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
// src/plugin/plugin.ts
|
|
649
|
+
var import_dag2 = require("@draug/core/graph/dag");
|
|
650
|
+
var PluginMetadataSymbol = /* @__PURE__ */ Symbol("plugin");
|
|
651
|
+
function Plugin(metadata) {
|
|
652
|
+
return (target) => {
|
|
653
|
+
if ("__proto__" in target && target.__proto__ !== PluginBase)
|
|
654
|
+
throw new ErrNotAPlugin(target);
|
|
655
|
+
target[PluginMetadataSymbol] = metadata;
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
function getPluginMetadata(plugin) {
|
|
659
|
+
if (hasMetadata2(plugin)) {
|
|
660
|
+
return plugin[PluginMetadataSymbol];
|
|
661
|
+
}
|
|
662
|
+
throw new ErrMissingPluginMetadata(plugin);
|
|
663
|
+
}
|
|
664
|
+
function hasMetadata2(ctor) {
|
|
665
|
+
return PluginMetadataSymbol in ctor;
|
|
666
|
+
}
|
|
667
|
+
function isPlugin(ctor) {
|
|
668
|
+
return hasMetadata2(ctor);
|
|
669
|
+
}
|
|
670
|
+
var PluginBase = class {
|
|
671
|
+
onPluginLoad;
|
|
672
|
+
onPluginUnload;
|
|
673
|
+
onAfterWorldInit;
|
|
674
|
+
};
|
|
675
|
+
var PluginError = class extends Error {
|
|
676
|
+
constructor(pluginId) {
|
|
677
|
+
super(`Plugin error! Plugin [${pluginId}]`);
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
var ErrNotAPlugin = class extends Error {
|
|
681
|
+
constructor(target) {
|
|
682
|
+
super(`Provided class ${target.name} is not a Plugin! Every plugin must extends of PluginBase class.`);
|
|
683
|
+
}
|
|
684
|
+
};
|
|
685
|
+
var ErrMissingPluginMetadata = class extends Error {
|
|
686
|
+
constructor(plugin) {
|
|
687
|
+
super(`Provided class ${plugin.name}: Missing plugin metadata! Define plugin class with @Plugin decorator.`);
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
var ErrUnknownPlugin = class extends PluginError {
|
|
691
|
+
constructor(pluginId) {
|
|
692
|
+
super(pluginId);
|
|
693
|
+
this.message = `${super.message}: Plugin not found in manager.`;
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
var ErrPluginNotInit = class extends PluginError {
|
|
697
|
+
constructor(pluginId) {
|
|
698
|
+
super(pluginId);
|
|
699
|
+
this.message = `${super.message}: Plugin not initiated yet. You must use PluginsManager.build() before getting instance.`;
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
var ErrMissingPluginDependency = class extends PluginError {
|
|
703
|
+
constructor(pluginId, missingDepId) {
|
|
704
|
+
super(pluginId);
|
|
705
|
+
this.message = `${super.message}: Missing required dependency [${missingDepId}]. Install it first.`;
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
var ErrDAGCycleDetectedPlugin = class extends Error {
|
|
709
|
+
constructor() {
|
|
710
|
+
super(`Cycle detected in plugin dependencies!`);
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
var PluginsManager = class {
|
|
714
|
+
plugins_ = /* @__PURE__ */ new Map();
|
|
715
|
+
isInitiated_ = false;
|
|
716
|
+
install(plugin, ...constructorProps) {
|
|
717
|
+
if (!isPlugin(plugin))
|
|
718
|
+
throw new ErrMissingPluginMetadata(plugin);
|
|
719
|
+
const metadata = getPluginMetadata(plugin);
|
|
720
|
+
if (this.plugins_.has(metadata.id))
|
|
721
|
+
return;
|
|
722
|
+
const entry2 = {
|
|
723
|
+
ctor: plugin,
|
|
724
|
+
ctorParams: constructorProps,
|
|
725
|
+
metadata
|
|
726
|
+
};
|
|
727
|
+
this.plugins_.set(metadata.id, entry2);
|
|
728
|
+
}
|
|
729
|
+
build() {
|
|
730
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
731
|
+
for (const id2 of this.plugins_.keys()) {
|
|
732
|
+
nodes.set(id2, new import_dag2.DAGNode(id2));
|
|
733
|
+
}
|
|
734
|
+
for (const [id2, entry2] of this.plugins_) {
|
|
735
|
+
const node = nodes.get(id2);
|
|
736
|
+
const depPlugins = entry2.metadata.dependencies?.plugins ?? [];
|
|
737
|
+
for (const dep of depPlugins) {
|
|
738
|
+
const depNode = nodes.get(dep.id);
|
|
739
|
+
if (!depNode) {
|
|
740
|
+
throw new ErrMissingPluginDependency(id2, dep.id);
|
|
741
|
+
}
|
|
742
|
+
node.vertices.push(depNode);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
let sortedNodes;
|
|
746
|
+
try {
|
|
747
|
+
sortedNodes = (0, import_dag2.topologicalSort)(nodes.values());
|
|
748
|
+
} catch (e) {
|
|
749
|
+
if (e instanceof import_dag2.ErrDAGCycleDetected) {
|
|
750
|
+
throw new ErrDAGCycleDetectedPlugin();
|
|
751
|
+
}
|
|
752
|
+
throw e;
|
|
753
|
+
}
|
|
754
|
+
for (const node of sortedNodes) {
|
|
755
|
+
const entry2 = this.plugins_.get(node.data);
|
|
756
|
+
const { ctor, ctorParams } = entry2;
|
|
757
|
+
entry2.instance = new ctor(...ctorParams);
|
|
758
|
+
}
|
|
759
|
+
this.isInitiated_ = true;
|
|
760
|
+
}
|
|
761
|
+
getPluginMetadata(pluginOrId) {
|
|
762
|
+
const id2 = this.resolveId(pluginOrId);
|
|
763
|
+
const entry2 = this.plugins_.get(id2);
|
|
764
|
+
if (!entry2) throw new ErrUnknownPlugin(id2);
|
|
765
|
+
return entry2.metadata;
|
|
766
|
+
}
|
|
767
|
+
getPluginInstance(pluginOrId) {
|
|
768
|
+
if (!this.isInitiated_) {
|
|
769
|
+
throw new Error("Plugin instance is not initiated yet. Use PluginManager.build() before use plugins.");
|
|
770
|
+
}
|
|
771
|
+
const id2 = this.resolveId(pluginOrId);
|
|
772
|
+
const entry2 = this.plugins_.get(id2);
|
|
773
|
+
if (!entry2) throw new ErrUnknownPlugin(id2);
|
|
774
|
+
if (!entry2.instance) throw new ErrPluginNotInit(id2);
|
|
775
|
+
return entry2.instance;
|
|
776
|
+
}
|
|
777
|
+
resolveId(pluginOrId) {
|
|
778
|
+
if (typeof pluginOrId === "string") {
|
|
779
|
+
return pluginOrId;
|
|
780
|
+
}
|
|
781
|
+
if (!isPlugin(pluginOrId)) {
|
|
782
|
+
throw new ErrMissingPluginMetadata(pluginOrId);
|
|
783
|
+
}
|
|
784
|
+
return getPluginMetadata(pluginOrId).id;
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// src/ecs/world.ts
|
|
789
|
+
var World3 = class {
|
|
790
|
+
entities = new EntitiesManager();
|
|
791
|
+
components = new ComponentsManager();
|
|
792
|
+
systems = new SystemsManager(this);
|
|
793
|
+
events = new EventBus();
|
|
794
|
+
resources = new ResourcesManager();
|
|
795
|
+
commands = new Commands(this);
|
|
796
|
+
queries = new QueryManager(this);
|
|
797
|
+
plugins = new PluginsManager();
|
|
798
|
+
entityRefs_ = /* @__PURE__ */ new Map();
|
|
799
|
+
constructor(maxEntityCount = ECS_DEFAULTS.MAX_ENTITY_COUNT) {
|
|
800
|
+
this.components = new ComponentsManager(maxEntityCount);
|
|
801
|
+
}
|
|
802
|
+
getEntityRef(id2) {
|
|
803
|
+
let ref = this.entityRefs_.get(id2);
|
|
804
|
+
if (!ref) {
|
|
805
|
+
ref = new EntityRef(this, id2);
|
|
806
|
+
this.entityRefs_.set(id2, ref);
|
|
807
|
+
}
|
|
808
|
+
return ref;
|
|
809
|
+
}
|
|
810
|
+
query(params) {
|
|
811
|
+
return this.queries.get(params);
|
|
812
|
+
}
|
|
813
|
+
removeComponent(entity, component) {
|
|
814
|
+
const id2 = typeof entity === "number" ? entity : entity.id;
|
|
815
|
+
const storage = this.components.getStorage(component);
|
|
816
|
+
storage.remove(id2);
|
|
817
|
+
this.queries.invalidate(component);
|
|
818
|
+
}
|
|
819
|
+
addComponent(entity, component, initFn) {
|
|
820
|
+
const storage = this.components.getStorage(component);
|
|
821
|
+
let id2;
|
|
822
|
+
if (typeof entity === "number") {
|
|
823
|
+
id2 = entity;
|
|
824
|
+
} else {
|
|
825
|
+
id2 = entity.id;
|
|
826
|
+
}
|
|
827
|
+
const c = storage.add(id2, (o) => {
|
|
828
|
+
if (initFn) {
|
|
829
|
+
initFn(o);
|
|
830
|
+
}
|
|
831
|
+
return o;
|
|
832
|
+
});
|
|
833
|
+
this.queries.invalidate(component);
|
|
834
|
+
return c;
|
|
835
|
+
}
|
|
836
|
+
update(dt) {
|
|
837
|
+
this.systems.update(dt);
|
|
838
|
+
this.commands.flush(this);
|
|
839
|
+
}
|
|
840
|
+
build() {
|
|
841
|
+
this.plugins.build();
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
|
|
845
|
+
// src/runtime/clock.ts
|
|
846
|
+
var Clock = class {
|
|
847
|
+
constructor(timeSource_) {
|
|
848
|
+
this.timeSource_ = timeSource_;
|
|
849
|
+
this.lastTimeMs_ = timeSource_.now();
|
|
850
|
+
}
|
|
851
|
+
timeSource_;
|
|
852
|
+
lastTimeMs_;
|
|
853
|
+
elapsedTime_ = 0;
|
|
854
|
+
dt_ = 0;
|
|
855
|
+
get dt() {
|
|
856
|
+
return this.dt_;
|
|
857
|
+
}
|
|
858
|
+
get ellapsedTime() {
|
|
859
|
+
return this.elapsedTime_;
|
|
860
|
+
}
|
|
861
|
+
tick() {
|
|
862
|
+
const now = this.timeSource_.now();
|
|
863
|
+
const dt = now - this.lastTimeMs_;
|
|
864
|
+
this.dt_ = dt;
|
|
865
|
+
this.elapsedTime_ += dt;
|
|
866
|
+
this.lastTimeMs_ = now;
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// src/runtime/game-loop.ts
|
|
871
|
+
var GameLoop = class {
|
|
872
|
+
constructor(clock, stepFn) {
|
|
873
|
+
this.clock = clock;
|
|
874
|
+
this.stepFn = stepFn;
|
|
875
|
+
}
|
|
876
|
+
clock;
|
|
877
|
+
stepFn;
|
|
878
|
+
running = false;
|
|
879
|
+
start(platformLoop) {
|
|
880
|
+
this.running = true;
|
|
881
|
+
const loop = () => {
|
|
882
|
+
if (!this.running) return;
|
|
883
|
+
this.clock.tick();
|
|
884
|
+
this.stepFn(this.clock.dt);
|
|
885
|
+
platformLoop(loop);
|
|
886
|
+
};
|
|
887
|
+
platformLoop(loop);
|
|
888
|
+
}
|
|
889
|
+
stop() {
|
|
890
|
+
this.running = false;
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
// src/runtime/runtime.ts
|
|
895
|
+
var Runtime = class {
|
|
896
|
+
constructor(world, assets) {
|
|
897
|
+
this.world = world;
|
|
898
|
+
this.assets = assets;
|
|
899
|
+
}
|
|
900
|
+
world;
|
|
901
|
+
assets;
|
|
902
|
+
update(dt) {
|
|
903
|
+
this.world.update(dt);
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
907
|
+
0 && (module.exports = {
|
|
908
|
+
Clock,
|
|
909
|
+
Commands,
|
|
910
|
+
Component,
|
|
911
|
+
ComponentAlreadyRegisteredError,
|
|
912
|
+
ComponentStorage,
|
|
913
|
+
ComponentsManager,
|
|
914
|
+
EntitiesManager,
|
|
915
|
+
EntityMaskNotFoundError,
|
|
916
|
+
EntityRef,
|
|
917
|
+
ErrMissingPluginMetadata,
|
|
918
|
+
ErrMissingSystemMetadata,
|
|
919
|
+
ErrNotAPlugin,
|
|
920
|
+
ErrNotASystem,
|
|
921
|
+
ErrPluginNotInit,
|
|
922
|
+
ErrUnknownPlugin,
|
|
923
|
+
EventBuffer,
|
|
924
|
+
EventBus,
|
|
925
|
+
GameLoop,
|
|
926
|
+
Plugin,
|
|
927
|
+
PluginBase,
|
|
928
|
+
PluginError,
|
|
929
|
+
PluginsManager,
|
|
930
|
+
ResourcesManager,
|
|
931
|
+
Runtime,
|
|
932
|
+
SingletonStorage,
|
|
933
|
+
System,
|
|
934
|
+
SystemBase,
|
|
935
|
+
SystemError,
|
|
936
|
+
SystemsManager,
|
|
937
|
+
UnregisteredComponentStorageError,
|
|
938
|
+
World,
|
|
939
|
+
createEventKey,
|
|
940
|
+
entry,
|
|
941
|
+
getPluginMetadata,
|
|
942
|
+
getSystemMetadata,
|
|
943
|
+
isPlugin,
|
|
944
|
+
isSystem
|
|
945
|
+
});
|
|
946
|
+
//# sourceMappingURL=index.cjs.map
|