@draug/engine 1.0.18 → 1.0.20

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 CHANGED
@@ -51,9 +51,9 @@ __export(index_exports, {
51
51
  PluginBase: () => PluginBase,
52
52
  PluginError: () => PluginError,
53
53
  PluginsManager: () => PluginsManager,
54
+ Resource: () => Resource,
54
55
  ResourcesManager: () => ResourcesManager,
55
56
  Runtime: () => Runtime,
56
- SingletonStorage: () => SingletonStorage,
57
57
  System: () => System,
58
58
  SystemBase: () => SystemBase,
59
59
  SystemError: () => SystemError,
@@ -65,6 +65,7 @@ __export(index_exports, {
65
65
  createEventKey: () => createEventKey,
66
66
  entry: () => entry,
67
67
  getPluginMetadata: () => getPluginMetadata,
68
+ getResourceMetadata: () => getResourceMetadata,
68
69
  getSystemMetadata: () => getSystemMetadata,
69
70
  isPlugin: () => isPlugin,
70
71
  isSystem: () => isSystem,
@@ -149,7 +150,8 @@ function System(props) {
149
150
  const requiredComponents = new Set(props.requiredComponents);
150
151
  const computeAfter = new Set(props.computeAfter);
151
152
  const phase = props.phase ?? 1 /* MAIN */;
152
- const metadata = { query, requiredComponents, computeAfter, phase };
153
+ const name = props.name ?? target.name;
154
+ const metadata = { query, requiredComponents, computeAfter, phase, name };
153
155
  systemTarget[SystemMetadataSymbol] = metadata;
154
156
  };
155
157
  }
@@ -195,7 +197,8 @@ var SystemsManager = class {
195
197
  this.requiredComponents_.add(c);
196
198
  for (const c of requiredComponents)
197
199
  this.requiredComponents_.add(c);
198
- this.logger.debug(() => `[Systems]: "${ctor.name}" was registered`);
200
+ const meta = getSystemMetadata(ctor);
201
+ this.logger.debug(() => `[Systems]: system "${meta.name}" was registered`);
199
202
  }
200
203
  build() {
201
204
  this.buildSystemsArray();
@@ -214,7 +217,7 @@ var SystemsManager = class {
214
217
  throw new Error("System not registered");
215
218
  return s;
216
219
  }
217
- update(dt) {
220
+ update(time) {
218
221
  if (this.dirty_)
219
222
  this.rebuild();
220
223
  this.world.events.swapAll();
@@ -224,7 +227,7 @@ var SystemsManager = class {
224
227
  s.compute({
225
228
  world: this.world,
226
229
  entities,
227
- dt,
230
+ time,
228
231
  logger: this.logger
229
232
  });
230
233
  }
@@ -311,7 +314,7 @@ var EntityRef = class {
311
314
 
312
315
  // src/ecs/constant.ts
313
316
  var ECS_DEFAULTS = {
314
- MAX_ENTITY_COUNT: Math.pow(2, 16)
317
+ MAX_ENTITY_COUNT: Math.pow(2, 12)
315
318
  };
316
319
 
317
320
  // src/ecs/events-buffer.ts
@@ -376,17 +379,16 @@ var ObjectPool = class {
376
379
  pool_;
377
380
  factory_;
378
381
  cursor_;
379
- constructor(factory, initialSize = 1024) {
382
+ constructor(factory, initialSize = 0) {
380
383
  this.pool_ = new Array(initialSize);
381
- for (let i = 0; i < initialSize; i++) {
382
- this.pool_[i] = factory();
383
- }
384
384
  this.factory_ = factory;
385
385
  this.cursor_ = initialSize - 1;
386
386
  }
387
387
  acquire() {
388
- if (this.cursor_ < 0) this.grow();
389
- return this.pool_[this.cursor_--];
388
+ if (this.cursor_ >= 0) {
389
+ return this.pool_[this.cursor_--];
390
+ }
391
+ return this.factory_();
390
392
  }
391
393
  release(obj) {
392
394
  this.pool_[++this.cursor_] = obj;
@@ -402,17 +404,18 @@ var ObjectPool = class {
402
404
 
403
405
  // src/ecs/components/component-storage.ts
404
406
  var import_bitmap_index = require("bitmap-index");
405
- var import_ts_sparse_set = require("ts-sparse-set");
406
407
  var ComponentStorage = class {
407
408
  bits_;
408
- set_;
409
+ data_ = [];
410
+ entityIds_ = [];
411
+ indexMap_ = /* @__PURE__ */ new Map();
409
412
  pool_;
410
413
  id_ = 0;
414
+ size_ = 0;
411
415
  cls;
412
416
  constructor(cap = ECS_DEFAULTS.MAX_ENTITY_COUNT, factory, cls) {
413
- this.set_ = new import_ts_sparse_set.SparseSet(cap);
414
417
  this.bits_ = new import_bitmap_index.Bitmap(cap);
415
- this.pool_ = new ObjectPool(factory, cap);
418
+ this.pool_ = new ObjectPool(factory, 0);
416
419
  this.cls = cls;
417
420
  }
418
421
  bitmap() {
@@ -425,99 +428,103 @@ var ComponentStorage = class {
425
428
  return this.id_ = id2;
426
429
  }
427
430
  add(id2, initFn) {
431
+ if (this.indexMap_.has(id2)) {
432
+ throw new Error(`[ComponentStorage "${this.cls.name}"]: Entity ${id2} already has this component`);
433
+ }
428
434
  const obj = this.pool_.acquire();
429
435
  initFn?.(obj);
430
- const value = this.set_.add(id2, obj);
436
+ const index = this.data_.length;
437
+ this.data_.push(obj);
438
+ this.entityIds_.push(id2);
439
+ this.indexMap_.set(id2, index);
431
440
  this.bits_.set(id2);
432
- return value;
441
+ this.size_++;
442
+ return obj;
433
443
  }
434
444
  remove(id2) {
435
- const obj = this.set_.get(id2);
436
- if (!obj) return;
445
+ const index = this.indexMap_.get(id2);
446
+ if (index === void 0) return;
437
447
  this.bits_.remove(id2);
438
- this.pool_.release(obj);
439
- this.set_.remove(id2);
448
+ const lastIndex = this.data_.length - 1;
449
+ const lastEntityId = this.entityIds_[lastIndex];
450
+ const removedObj = this.data_[index];
451
+ if (index !== lastIndex) {
452
+ this.data_[index] = this.data_[lastIndex];
453
+ this.entityIds_[index] = lastEntityId;
454
+ this.indexMap_.set(lastEntityId, index);
455
+ }
456
+ this.data_.pop();
457
+ this.entityIds_.pop();
458
+ this.indexMap_.delete(id2);
459
+ this.pool_.release(removedObj);
460
+ this.size_--;
440
461
  }
441
462
  get(id2) {
442
- return this.set_.get(id2);
463
+ const index = this.indexMap_.get(id2);
464
+ return index !== void 0 ? this.data_[index] : null;
443
465
  }
444
466
  tryGet(id2) {
445
- const x = this.set_.get(id2);
446
- if (!x)
467
+ const index = this.indexMap_.get(id2);
468
+ if (index === void 0)
447
469
  throw new Error(`[ComponentStorage "${this.cls.name}"]: Requesting non-existing item with ID ${id2}.`);
448
- return x;
470
+ return this.data_[index];
449
471
  }
450
472
  writeComponentsToBuf(ids, out) {
451
473
  let len = 0;
452
474
  for (const id2 of ids) {
453
- const obj = this.set_.get(id2);
454
- if (obj !== null) out[len++] = obj;
475
+ const index = this.indexMap_.get(id2);
476
+ if (index !== void 0) out[len++] = this.data_[index];
455
477
  }
456
478
  return len;
457
479
  }
458
480
  has(id2) {
459
481
  return this.bits_.contains(id2);
460
482
  }
461
- entityIds() {
462
- return Array.from(this.bits_);
463
- }
464
483
  size() {
465
- return this.bits_.count();
484
+ return this.size_;
466
485
  }
467
486
  forEach(cb) {
468
- this.bits_.range((x) => cb(x));
487
+ for (const id2 of this.entityIds_) {
488
+ cb(id2);
489
+ }
469
490
  }
470
491
  };
471
492
 
472
- // src/ecs/components/singleton-storage.ts
473
- var SingletonStorage = class {
474
- constructor(factory) {
475
- this.factory = factory;
476
- }
477
- factory;
478
- value = null;
479
- entityId = null;
480
- bitmap() {
481
- throw new Error("Singletone component cannot has a bitmap!");
482
- }
483
- add(id2, initFn) {
484
- if (this.value !== null)
485
- throw new Error("Singleton already initiated");
486
- this.entityId = id2;
487
- this.value = this.factory();
488
- initFn?.(this.value);
489
- return this.value;
490
- }
491
- remove(id2) {
492
- if (!this.validateId(id2))
493
- return;
494
- this.value = null;
495
- this.entityId = null;
496
- }
497
- get(id2) {
498
- if (!this.validateId(id2))
499
- return null;
500
- return this.value;
501
- }
502
- tryGet(id2) {
503
- if (!this.validateId(id2))
504
- throw new Error("[SingletoneStorage]: ID missmatch.");
505
- return this.value;
506
- }
507
- has(id2) {
508
- return this.validateId(id2);
509
- }
510
- size() {
511
- return this.value !== null ? 1 : 0;
512
- }
513
- forEach(cb) {
514
- if (this.entityId !== null)
515
- cb(this.entityId);
493
+ // src/ecs/components/utils.ts
494
+ var registry = /* @__PURE__ */ new Map();
495
+ var id = 0;
496
+ var ComponentMetadataSymbol = /* @__PURE__ */ Symbol("component");
497
+ function Component(options) {
498
+ return (target) => {
499
+ const metadata = {
500
+ name: options.name,
501
+ id: ++id
502
+ };
503
+ registry.set(target, metadata.id);
504
+ target[ComponentMetadataSymbol] = metadata;
505
+ };
506
+ }
507
+ function getComponentId(ctor) {
508
+ const id2 = registry.get(ctor);
509
+ if (id2 === void 0) {
510
+ throw new Error(`Component not registered: ${ctor.name}`);
516
511
  }
517
- validateId(id2) {
518
- return this.entityId !== null && id2 === this.entityId;
512
+ return id2;
513
+ }
514
+ var ErrNotComponent = class extends Error {
515
+ constructor(ctor) {
516
+ super(`Class ${ctor.name} is not a Component. Use @Component decorator to define components.`);
519
517
  }
520
518
  };
519
+ function getComponentMetadata(component) {
520
+ if (isComponent(component)) {
521
+ return component[ComponentMetadataSymbol];
522
+ }
523
+ throw new ErrNotComponent(component);
524
+ }
525
+ function isComponent(ctor) {
526
+ return ComponentMetadataSymbol in ctor;
527
+ }
521
528
 
522
529
  // src/ecs/components/manager.ts
523
530
  var ComponentAlreadyRegisteredError = class extends Error {
@@ -526,9 +533,11 @@ var ComponentAlreadyRegisteredError = class extends Error {
526
533
  }
527
534
  };
528
535
  var ComponentsManager = class {
529
- constructor(maxEntityCount = ECS_DEFAULTS.MAX_ENTITY_COUNT) {
536
+ constructor(logger, maxEntityCount = ECS_DEFAULTS.MAX_ENTITY_COUNT) {
537
+ this.logger = logger;
530
538
  this.maxEntityCount = maxEntityCount;
531
539
  }
540
+ logger;
532
541
  maxEntityCount;
533
542
  storages_ = /* @__PURE__ */ new Map();
534
543
  currId_ = 0;
@@ -538,19 +547,10 @@ var ComponentsManager = class {
538
547
  register(component, opts) {
539
548
  if (this.storages_.has(component))
540
549
  return this.storages_.get(component);
541
- let store;
542
- switch (opts?.storageType) {
543
- case 2 /* SINGLETON_STORAGE */:
544
- store = this.createSingletonStore(opts);
545
- break;
546
- case 1 /* COMPONENT_STORAGE */:
547
- store = this.createComponentStore(component, opts);
548
- break;
549
- default:
550
- store = this.createComponentStore(component, opts);
551
- break;
552
- }
550
+ const store = this.createComponentStore(component, opts);
553
551
  this.storages_.set(component, store);
552
+ const meta = getComponentMetadata(component);
553
+ this.logger.debug(() => `[Components]: Registered component "${meta.name}"`);
554
554
  return store;
555
555
  }
556
556
  createComponentStore(component, opts) {
@@ -559,12 +559,6 @@ var ComponentsManager = class {
559
559
  store._internalSetId(this.nextId());
560
560
  return store;
561
561
  }
562
- createSingletonStore(opts) {
563
- if (!opts?.factory) {
564
- throw new Error("For singletone storage provide factory is required!");
565
- }
566
- return new SingletonStorage(opts.factory);
567
- }
568
562
  getStorage(component) {
569
563
  const store = this.storages_.get(component);
570
564
  if (store === void 0)
@@ -576,24 +570,30 @@ var ComponentsManager = class {
576
570
  }
577
571
  };
578
572
 
579
- // src/ecs/components/utils.ts
580
- var registry = /* @__PURE__ */ new Map();
581
- var id = 0;
582
- function Component() {
573
+ // src/ecs/resources/resources.ts
574
+ var ResourceMetadataSymbol = /* @__PURE__ */ Symbol("resource");
575
+ function Resource(params) {
583
576
  return (target) => {
584
- if (registry.has(target)) return;
585
- registry.set(target, ++id);
577
+ const metadata = {
578
+ name: params.name
579
+ };
580
+ target[ResourceMetadataSymbol] = metadata;
586
581
  };
587
582
  }
588
- function getComponentId(ctor) {
589
- const id2 = registry.get(ctor);
590
- if (id2 === void 0) {
591
- throw new Error(`Component not registered: ${ctor.name}`);
583
+ var ErrNotResource = class extends Error {
584
+ constructor(ctor) {
585
+ super(`Class ${ctor.name} is not a Resource. Use @Resource decorator to define resources.`);
592
586
  }
593
- return id2;
587
+ };
588
+ function getResourceMetadata(resource) {
589
+ if (isResource(resource)) {
590
+ return resource[ResourceMetadataSymbol];
591
+ }
592
+ throw new ErrNotResource(resource);
593
+ }
594
+ function isResource(ctor) {
595
+ return ResourceMetadataSymbol in ctor;
594
596
  }
595
-
596
- // src/ecs/resources/resources.ts
597
597
  var ResourcesManager = class {
598
598
  constructor(logger) {
599
599
  this.logger = logger;
@@ -602,13 +602,15 @@ var ResourcesManager = class {
602
602
  items_ = /* @__PURE__ */ new Map();
603
603
  insert(type, value) {
604
604
  this.items_.set(type, value);
605
- this.logger.debug(() => `[Resources]: Inserted new Resource "${type.name}"`);
605
+ const metadata = getResourceMetadata(type);
606
+ this.logger.debug(() => `[Resources]: Inserted new Resource "${metadata.name}"`);
606
607
  return value;
607
608
  }
608
609
  get(type) {
609
610
  const value = this.items_.get(type);
611
+ const meta = getResourceMetadata(type);
610
612
  if (!value)
611
- throw new Error(`Resource of class ${type.name} does not exist!`);
613
+ throw new Error(`Resource of class ${meta.name} does not exist!`);
612
614
  return value;
613
615
  }
614
616
  getOrInsert(type, factory) {
@@ -620,7 +622,8 @@ var ResourcesManager = class {
620
622
  return value;
621
623
  }
622
624
  remove(type) {
623
- this.logger.debug(() => `[Resources]: Removed resource "${type.name}"`);
625
+ const meta = getResourceMetadata(type);
626
+ this.logger.debug(() => `[Resources]: Removed resource "${meta.name}"`);
624
627
  this.items_.delete(type);
625
628
  }
626
629
  };
@@ -654,7 +657,10 @@ var Commands = class {
654
657
  }
655
658
  };
656
659
  this.add(cmd);
657
- this.logger.debug(() => `[Commands.createEntity]: Created new entity with ID ${id2}. Linked components: [${entries.map((x) => x[0].name).join(", ")}]`);
660
+ this.logger.debug(() => {
661
+ const components = entries.map((x) => getComponentMetadata(x[0]).name).join(", ");
662
+ return `[Commands.createEntity]: Created new entity with ID ${id2}. Linked components: [${components}]`;
663
+ });
658
664
  return id2;
659
665
  }
660
666
  };
@@ -854,30 +860,33 @@ var PluginsManager = class {
854
860
  if (!isPlugin(plugin))
855
861
  throw new ErrMissingPluginMetadata(plugin);
856
862
  const metadata = getPluginMetadata(plugin);
857
- if (this.plugins_.has(metadata.id))
863
+ if (this.plugins_.has(plugin))
858
864
  return;
859
865
  const entry2 = {
860
866
  ctor: plugin,
861
867
  ctorParams: constructorProps,
862
868
  metadata
863
869
  };
864
- this.plugins_.set(metadata.id, entry2);
870
+ this.plugins_.set(plugin, entry2);
865
871
  this.logger.debug(() => `[Plugins]: Installed plugin ${metadata.name} (${metadata.version})`);
866
872
  }
867
873
  build() {
874
+ if (this.plugins_.size === 0) {
875
+ return;
876
+ }
868
877
  const nodes = /* @__PURE__ */ new Map();
869
878
  for (const id2 of this.plugins_.keys()) {
870
879
  nodes.set(id2, new DAGNode(id2));
871
880
  }
872
- for (const [id2, entry2] of this.plugins_) {
873
- const node = nodes.get(id2);
881
+ for (const [plugin, entry2] of this.plugins_) {
882
+ const node = nodes.get(plugin);
874
883
  const depPlugins = entry2.metadata.dependencies?.plugins ?? [];
875
884
  for (const dep of depPlugins) {
876
- const depNode = nodes.get(dep.id);
885
+ const depNode = nodes.get(dep.plugin);
877
886
  if (!depNode) {
878
- throw new ErrMissingPluginDependency(id2, dep.id);
887
+ throw new ErrMissingPluginDependency(plugin, dep.plugin);
879
888
  }
880
- node.vertices.push(depNode);
889
+ depNode.vertices.push(node);
881
890
  }
882
891
  }
883
892
  let sortedNodes;
@@ -907,20 +916,18 @@ var PluginsManager = class {
907
916
  p.instance?.onAfterWorldInit?.(world);
908
917
  }
909
918
  }
910
- getPluginMetadata(pluginOrId) {
911
- const id2 = this.resolveId(pluginOrId);
912
- const entry2 = this.plugins_.get(id2);
913
- if (!entry2) throw new ErrUnknownPlugin(id2);
919
+ getPluginMetadata(plugin) {
920
+ const entry2 = this.plugins_.get(plugin);
921
+ if (!entry2) throw new ErrUnknownPlugin(plugin);
914
922
  return entry2.metadata;
915
923
  }
916
- getPluginInstance(pluginOrId) {
924
+ getPluginInstance(plugin) {
925
+ const entry2 = this.plugins_.get(plugin);
926
+ if (!entry2) throw new ErrUnknownPlugin(plugin);
927
+ if (!entry2.instance) throw new ErrPluginNotInit(plugin);
917
928
  if (!this.isInitiated_) {
918
- throw new Error("Plugin instance is not initiated yet. Use PluginManager.build() before use plugins.");
929
+ throw new ErrPluginNotInit(plugin);
919
930
  }
920
- const id2 = this.resolveId(pluginOrId);
921
- const entry2 = this.plugins_.get(id2);
922
- if (!entry2) throw new ErrUnknownPlugin(id2);
923
- if (!entry2.instance) throw new ErrPluginNotInit(id2);
924
931
  return entry2.instance;
925
932
  }
926
933
  resolveId(pluginOrId) {
@@ -946,9 +953,13 @@ var World3 = class {
946
953
  plugins;
947
954
  logger;
948
955
  entityRefs_ = /* @__PURE__ */ new Map();
956
+ updatesCount_ = 0;
957
+ get updatesCount() {
958
+ return this.updatesCount_;
959
+ }
949
960
  constructor(params) {
950
961
  this.entities = new EntitiesManager(params.logger);
951
- this.components = new ComponentsManager(params.maxEntityCount ?? ECS_DEFAULTS.MAX_ENTITY_COUNT);
962
+ this.components = new ComponentsManager(params.logger, params.maxEntityCount ?? ECS_DEFAULTS.MAX_ENTITY_COUNT);
952
963
  this.systems = new SystemsManager(this, params.logger);
953
964
  this.events = new EventBus();
954
965
  this.resources = new ResourcesManager(params.logger);
@@ -991,9 +1002,10 @@ var World3 = class {
991
1002
  this.queries.invalidate(component);
992
1003
  return c;
993
1004
  }
994
- update(dt) {
995
- this.systems.update(dt);
1005
+ update(clock) {
1006
+ this.systems.update(clock.getTime());
996
1007
  this.commands.flush(this);
1008
+ this.updatesCount_++;
997
1009
  }
998
1010
  build() {
999
1011
  this.plugins.build();
@@ -1020,21 +1032,28 @@ var Clock = class {
1020
1032
  }
1021
1033
  timeSource_;
1022
1034
  lastTimeMs_;
1023
- elapsedTime_ = 0;
1024
- dt_ = 0;
1025
- get dt() {
1026
- return this.dt_;
1035
+ ellapsedTime_ = 0;
1036
+ delta_ = 0;
1037
+ time_ = {
1038
+ delta: 0,
1039
+ elapsed: 0
1040
+ };
1041
+ get deltaMs() {
1042
+ return this.delta_;
1027
1043
  }
1028
1044
  get ellapsedTime() {
1029
- return this.elapsedTime_;
1045
+ return this.ellapsedTime_;
1030
1046
  }
1031
1047
  tick() {
1032
1048
  const now = this.timeSource_.now();
1033
- const dt = now - this.lastTimeMs_;
1034
- this.dt_ = dt;
1035
- this.elapsedTime_ += dt;
1049
+ const dt = Math.min(now - this.lastTimeMs_, 100);
1050
+ this.delta_ = this.time_.delta = dt / 1e3;
1051
+ this.ellapsedTime_ = this.time_.elapsed += dt / 1e3;
1036
1052
  this.lastTimeMs_ = now;
1037
1053
  }
1054
+ getTime() {
1055
+ return this.time_;
1056
+ }
1038
1057
  };
1039
1058
 
1040
1059
  // src/runtime/loop.ts
@@ -1053,7 +1072,7 @@ var Loop = class {
1053
1072
  const loop = () => {
1054
1073
  if (!this.running) return;
1055
1074
  this.clock.tick();
1056
- this.stepFn(this.clock.dt, world);
1075
+ this.stepFn(this.clock.deltaMs, world);
1057
1076
  this.platformLoop(loop);
1058
1077
  };
1059
1078
  this.platformLoop(loop);
@@ -1238,7 +1257,8 @@ var Engine = class {
1238
1257
  this.runtime = new Runtime(params.loop);
1239
1258
  this.logger = params.logger ?? new NoopLogger();
1240
1259
  this.world = new World3({
1241
- logger: this.logger
1260
+ logger: this.logger,
1261
+ maxEntityCount: params.maxEntityCount ?? ECS_DEFAULTS.MAX_ENTITY_COUNT
1242
1262
  });
1243
1263
  }
1244
1264
  init() {
@@ -1281,9 +1301,9 @@ var Engine = class {
1281
1301
  PluginBase,
1282
1302
  PluginError,
1283
1303
  PluginsManager,
1304
+ Resource,
1284
1305
  ResourcesManager,
1285
1306
  Runtime,
1286
- SingletonStorage,
1287
1307
  System,
1288
1308
  SystemBase,
1289
1309
  SystemError,
@@ -1295,6 +1315,7 @@ var Engine = class {
1295
1315
  createEventKey,
1296
1316
  entry,
1297
1317
  getPluginMetadata,
1318
+ getResourceMetadata,
1298
1319
  getSystemMetadata,
1299
1320
  isPlugin,
1300
1321
  isSystem,