• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 14804036802

02 May 2025 09:58PM UTC coverage: 5.927% (-83.4%) from 89.28%
14804036802

Pull #3404

github

web-flow
Merge 5c103d7f8 into 0f2ccaeb2
Pull Request #3404: feat: added Graph module to Math

234 of 8383 branches covered (2.79%)

229 of 246 new or added lines in 1 file covered. (93.09%)

13145 existing lines in 208 files now uncovered.

934 of 15759 relevant lines covered (5.93%)

4.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

1.01
/src/engine/EntityComponentSystem/Entity.ts
1
import { Component, ComponentCtor, isComponentCtor } from './Component';
2

3
import { Observable, Message } from '../Util/Observable';
4
import { OnInitialize, OnPreUpdate, OnPostUpdate, OnAdd, OnRemove } from '../Interfaces/LifecycleEvents';
5
import { Engine } from '../Engine';
6
import { InitializeEvent, PreUpdateEvent, PostUpdateEvent, AddEvent, RemoveEvent } from '../Events';
7
import { KillEvent } from '../Events';
8
import { EventEmitter, EventKey, Handler, Subscription } from '../EventEmitter';
9
import { Scene } from '../Scene';
10
import { removeItemFromArray } from '../Util/Util';
11
import { MaybeKnownComponent } from './Types';
12
import { Logger } from '../Util/Log';
13

14
/**
15
 * Interface holding an entity component pair
16
 */
17
export interface EntityComponent {
18
  component: Component;
19
  entity: Entity;
20
}
21

22
/**
23
 * AddedComponent message
24
 */
25
export class AddedComponent implements Message<EntityComponent> {
26
  readonly type: 'Component Added' = 'Component Added';
×
27
  constructor(public data: EntityComponent) {}
×
28
}
29

30
/**
31
 * Type guard to know if message is f an Added Component
32
 */
33
export function isAddedComponent(x: Message<EntityComponent>): x is AddedComponent {
34
  return !!x && x.type === 'Component Added';
×
35
}
36

37
/**
38
 * RemovedComponent message
39
 */
40
export class RemovedComponent implements Message<EntityComponent> {
41
  readonly type: 'Component Removed' = 'Component Removed';
×
42
  constructor(public data: EntityComponent) {}
×
43
}
44

45
/**
46
 * Type guard to know if message is for a Removed Component
47
 */
48
export function isRemovedComponent(x: Message<EntityComponent>): x is RemovedComponent {
49
  return !!x && x.type === 'Component Removed';
×
50
}
51

52
/**
53
 * Built in events supported by all entities
54
 */
55
export type EntityEvents = {
56
  initialize: InitializeEvent;
57
  //@ts-ignore
58
  add: AddEvent;
59
  //@ts-ignore
60
  remove: RemoveEvent;
61
  preupdate: PreUpdateEvent;
62
  postupdate: PostUpdateEvent;
63
  kill: KillEvent;
64
};
65

66
export const EntityEvents = {
1✔
67
  Add: 'add',
68
  Remove: 'remove',
69
  Initialize: 'initialize',
70
  PreUpdate: 'preupdate',
71
  PostUpdate: 'postupdate',
72
  Kill: 'kill'
73
} as const;
74

75
export interface EntityOptions<TComponents extends Component> {
76
  name?: string;
77
  components?: TComponents[];
78
  silenceWarnings?: boolean;
79
}
80

81
/**
82
 * An Entity is the base type of anything that can have behavior in Excalibur, they are part of the built in entity component system
83
 *
84
 * Entities can be strongly typed with the components they contain
85
 *
86
 * ```typescript
87
 * const entity = new Entity<ComponentA | ComponentB>();
88
 * entity.components.a; // Type ComponentA
89
 * entity.components.b; // Type ComponentB
90
 * ```
91
 */
92
export class Entity<TKnownComponents extends Component = any> implements OnInitialize, OnPreUpdate, OnPostUpdate, OnAdd, OnRemove {
93
  private static _ID = 0;
1✔
94
  /**
95
   * The unique identifier for the entity
96
   */
UNCOV
97
  public id: number = Entity._ID++;
×
98

UNCOV
99
  public name = `Entity#${this.id}`;
×
100

101
  /**
102
   * Listen to or emit events for an entity
103
   */
UNCOV
104
  public events = new EventEmitter<EntityEvents>();
×
UNCOV
105
  private _tags = new Set<string>();
×
UNCOV
106
  public componentAdded$ = new Observable<Component>();
×
UNCOV
107
  public componentRemoved$ = new Observable<Component>();
×
UNCOV
108
  public tagAdded$ = new Observable<string>();
×
UNCOV
109
  public tagRemoved$ = new Observable<string>();
×
110
  /**
111
   * Current components on the entity
112
   *
113
   * **Do not modify**
114
   *
115
   * Use addComponent/removeComponent otherwise the ECS will not be notified of changes.
116
   */
UNCOV
117
  public readonly components = new Map<Function, Component>();
×
UNCOV
118
  public componentValues: Component[] = [];
×
UNCOV
119
  private _componentsToRemove: ComponentCtor[] = [];
×
120

121
  constructor(options: EntityOptions<TKnownComponents>);
122
  constructor(components?: TKnownComponents[], name?: string);
123
  constructor(componentsOrOptions?: TKnownComponents[] | EntityOptions<TKnownComponents>, name?: string) {
124
    let componentsToAdd!: TKnownComponents[];
125
    let nameToAdd: string | undefined;
UNCOV
126
    let silence = false;
×
UNCOV
127
    if (Array.isArray(componentsOrOptions)) {
×
UNCOV
128
      componentsToAdd = componentsOrOptions;
×
UNCOV
129
      nameToAdd = name;
×
UNCOV
130
    } else if (componentsOrOptions && typeof componentsOrOptions === 'object') {
×
UNCOV
131
      const { components, name, silenceWarnings } = componentsOrOptions;
×
UNCOV
132
      componentsToAdd = components ?? [];
×
UNCOV
133
      nameToAdd = name;
×
UNCOV
134
      silence = !!silenceWarnings;
×
135
    }
UNCOV
136
    if (nameToAdd) {
×
UNCOV
137
      this.name = nameToAdd;
×
138
    }
UNCOV
139
    if (componentsToAdd) {
×
UNCOV
140
      for (const component of componentsToAdd) {
×
UNCOV
141
        this.addComponent(component);
×
142
      }
143
    }
144

UNCOV
145
    if (process.env.NODE_ENV === 'development') {
×
UNCOV
146
      if (!silence) {
×
UNCOV
147
        setTimeout(() => {
×
UNCOV
148
          if (!this.scene && !this.isInitialized) {
×
UNCOV
149
            Logger.getInstance().warn(`Entity "${this.name || this.id}" was not added to a scene.`);
×
150
          }
151
        }, 5000);
152
      }
153
    }
154
  }
155

156
  /**
157
   * The current scene that the entity is in, if any
158
   */
UNCOV
159
  public scene: Scene | null = null;
×
160

161
  /**
162
   * Whether this entity is active, if set to false it will be reclaimed
163
   * @deprecated use isActive
164
   */
165
  public get active(): boolean {
UNCOV
166
    return this.isActive;
×
167
  }
168

169
  /**
170
   * Whether this entity is active, if set to false it will be reclaimed
171
   * @deprecated use isActive
172
   */
173
  public set active(val: boolean) {
UNCOV
174
    this.isActive = val;
×
175
  }
176

177
  /**
178
   * Whether this entity is active, if set to false it will be reclaimed
179
   */
UNCOV
180
  public isActive: boolean = true;
×
181

182
  /**
183
   * Kill the entity, means it will no longer be updated. Kills are deferred to the end of the update.
184
   * If parented it will be removed from the parent when killed.
185
   */
186
  public kill() {
UNCOV
187
    if (this.isActive) {
×
UNCOV
188
      this.isActive = false;
×
UNCOV
189
      this.unparent();
×
190
    }
UNCOV
191
    this.emit('kill', new KillEvent(this));
×
192
  }
193

194
  public isKilled() {
UNCOV
195
    return !this.isActive;
×
196
  }
197

198
  /**
199
   * Specifically get the tags on the entity from {@apilink TagsComponent}
200
   */
201
  public get tags(): Set<string> {
UNCOV
202
    return this._tags;
×
203
  }
204

205
  /**
206
   * Check if a tag exists on the entity
207
   * @param tag name to check for
208
   */
209
  public hasTag(tag: string): boolean {
UNCOV
210
    return this._tags.has(tag);
×
211
  }
212

213
  /**
214
   * Adds a tag to an entity
215
   * @param tag
216
   */
217
  public addTag(tag: string): Entity<TKnownComponents> {
UNCOV
218
    this._tags.add(tag);
×
UNCOV
219
    this.tagAdded$.notifyAll(tag);
×
UNCOV
220
    return this;
×
221
  }
222

223
  /**
224
   * Removes a tag on the entity
225
   *
226
   * Removals are deferred until the end of update
227
   * @param tag
228
   */
229
  public removeTag(tag: string): Entity<TKnownComponents> {
UNCOV
230
    this._tags.delete(tag);
×
UNCOV
231
    this.tagRemoved$.notifyAll(tag);
×
UNCOV
232
    return this;
×
233
  }
234

235
  /**
236
   * The types of the components on the Entity
237
   */
238
  public get types(): ComponentCtor[] {
UNCOV
239
    return Array.from(this.components.keys()) as ComponentCtor[];
×
240
  }
241

242
  /**
243
   * Returns all component instances on entity
244
   */
245
  public getComponents(): Component[] {
UNCOV
246
    return Array.from(this.components.values());
×
247
  }
248

249
  /**
250
   * Verifies that an entity has all the required types
251
   * @param requiredTypes
252
   */
253
  hasAll<TComponent extends Component>(requiredTypes: ComponentCtor<TComponent>[]): boolean {
UNCOV
254
    for (let i = 0; i < requiredTypes.length; i++) {
×
UNCOV
255
      if (!this.components.has(requiredTypes[i])) {
×
UNCOV
256
        return false;
×
257
      }
258
    }
UNCOV
259
    return true;
×
260
  }
261

262
  /**
263
   * Verifies that an entity has all the required tags
264
   * @param requiredTags
265
   */
266
  hasAllTags(requiredTags: string[]): boolean {
UNCOV
267
    for (let i = 0; i < requiredTags.length; i++) {
×
UNCOV
268
      if (!this.tags.has(requiredTags[i])) {
×
UNCOV
269
        return false;
×
270
      }
271
    }
UNCOV
272
    return true;
×
273
  }
274

275
  get<TComponent extends Component>(type: ComponentCtor<TComponent>): MaybeKnownComponent<TComponent, TKnownComponents> {
UNCOV
276
    return this.components.get(type) as MaybeKnownComponent<TComponent, TKnownComponents>;
×
277
  }
278

UNCOV
279
  private _parent: Entity | null = null;
×
280
  public get parent(): Entity | null {
UNCOV
281
    return this._parent;
×
282
  }
283

UNCOV
284
  public childrenAdded$ = new Observable<Entity>();
×
UNCOV
285
  public childrenRemoved$ = new Observable<Entity>();
×
286

UNCOV
287
  private _children: Entity[] = [];
×
288
  /**
289
   * Get the direct children of this entity
290
   */
291
  public get children(): readonly Entity[] {
UNCOV
292
    return this._children;
×
293
  }
294

295
  /**
296
   * Unparents this entity, if there is a parent. Otherwise it does nothing.
297
   */
298
  public unparent() {
UNCOV
299
    if (this._parent) {
×
UNCOV
300
      this._parent.removeChild(this);
×
UNCOV
301
      this._parent = null;
×
302
    }
303
  }
304

305
  /**
306
   * Adds an entity to be a child of this entity
307
   * @param entity
308
   */
309
  public addChild(entity: Entity): Entity {
UNCOV
310
    if (entity.parent === null) {
×
UNCOV
311
      if (this.getAncestors().includes(entity)) {
×
UNCOV
312
        throw new Error('Cycle detected, cannot add entity');
×
313
      }
UNCOV
314
      this._children.push(entity);
×
UNCOV
315
      entity._parent = this;
×
UNCOV
316
      this.childrenAdded$.notifyAll(entity);
×
317
    } else {
UNCOV
318
      throw new Error('Entity already has a parent, cannot add without unparenting');
×
319
    }
UNCOV
320
    return this;
×
321
  }
322

323
  /**
324
   * Remove an entity from children if it exists
325
   * @param entity
326
   */
327
  public removeChild(entity: Entity): Entity {
UNCOV
328
    if (entity.parent === this) {
×
UNCOV
329
      removeItemFromArray(entity, this._children);
×
UNCOV
330
      entity._parent = null;
×
UNCOV
331
      this.childrenRemoved$.notifyAll(entity);
×
332
    }
UNCOV
333
    return this;
×
334
  }
335

336
  /**
337
   * Removes all children from this entity
338
   */
339
  public removeAllChildren(): Entity {
340
    // Avoid modifying the array issue by walking backwards
UNCOV
341
    for (let i = this.children.length - 1; i >= 0; i--) {
×
UNCOV
342
      this.removeChild(this.children[i]);
×
343
    }
UNCOV
344
    return this;
×
345
  }
346

347
  /**
348
   * Returns a list of parent entities starting with the topmost parent. Includes the current entity.
349
   */
350
  public getAncestors(): Entity[] {
UNCOV
351
    const result: Entity[] = [this];
×
UNCOV
352
    let current = this.parent;
×
UNCOV
353
    while (current) {
×
UNCOV
354
      result.push(current);
×
UNCOV
355
      current = current.parent;
×
356
    }
UNCOV
357
    return result.reverse();
×
358
  }
359

360
  /**
361
   * Returns a list of all the entities that descend from this entity. Includes the current entity.
362
   */
363
  public getDescendants(): Entity[] {
UNCOV
364
    let result: Entity[] = [this];
×
UNCOV
365
    let queue: Entity[] = [this];
×
UNCOV
366
    while (queue.length > 0) {
×
UNCOV
367
      const curr = queue.pop();
×
UNCOV
368
      if (curr) {
×
UNCOV
369
        queue = queue.concat(curr.children);
×
UNCOV
370
        result = result.concat(curr.children);
×
371
      }
372
    }
UNCOV
373
    return result;
×
374
  }
375

376
  /**
377
   * Creates a deep copy of the entity and a copy of all its components
378
   */
379
  public clone(): Entity {
UNCOV
380
    const newEntity = new Entity();
×
UNCOV
381
    for (const c of this.types) {
×
UNCOV
382
      const componentInstance = this.get(c);
×
UNCOV
383
      if (componentInstance) {
×
UNCOV
384
        newEntity.addComponent(componentInstance.clone());
×
385
      }
386
    }
UNCOV
387
    for (const child of this.children) {
×
UNCOV
388
      newEntity.addChild(child.clone());
×
389
    }
UNCOV
390
    return newEntity;
×
391
  }
392

393
  /**
394
   * Adds a copy of all the components from another template entity as a "prefab"
395
   * @param templateEntity Entity to use as a template
396
   * @param force Force component replacement if it already exists on the target entity
397
   */
398
  public addTemplate(templateEntity: Entity, force: boolean = false): Entity {
×
UNCOV
399
    for (const c of templateEntity.getComponents()) {
×
UNCOV
400
      this.addComponent(c.clone(), force);
×
401
    }
UNCOV
402
    for (const child of templateEntity.children) {
×
UNCOV
403
      this.addChild(child.clone().addTemplate(child));
×
404
    }
UNCOV
405
    return this;
×
406
  }
407

408
  private _getClassHierarchyRoot(componentType: ComponentCtor): ComponentCtor {
UNCOV
409
    let current = componentType;
×
UNCOV
410
    let parent = Object.getPrototypeOf(current.prototype)?.constructor;
×
411

UNCOV
412
    while (parent && parent !== Object && parent !== Component) {
×
UNCOV
413
      current = parent;
×
UNCOV
414
      parent = Object.getPrototypeOf(current.prototype)?.constructor;
×
415
    }
UNCOV
416
    return current;
×
417
  }
418

419
  /**
420
   * Adds a component to the entity
421
   * @param component Component or Entity to add copy of components from
422
   * @param force Optionally overwrite any existing components of the same type
423
   */
424
  public addComponent<TComponent extends Component>(component: TComponent, force: boolean = false): Entity<TKnownComponents | TComponent> {
×
425
    // if component already exists, skip if not forced
UNCOV
426
    if (this.has(component.constructor as ComponentCtor)) {
×
UNCOV
427
      if (force) {
×
428
        // Remove existing component type if exists when forced
UNCOV
429
        this.removeComponent(component.constructor as ComponentCtor, true);
×
430
      } else {
431
        // early exit component exits
UNCOV
432
        return this as Entity<TKnownComponents | TComponent>;
×
433
      }
434
    }
435

436
    // TODO circular dependencies will be a problem
UNCOV
437
    if (component.dependencies && component.dependencies.length) {
×
UNCOV
438
      for (const ctor of component.dependencies) {
×
UNCOV
439
        this.addComponent(new ctor());
×
440
      }
441
    }
442

UNCOV
443
    component.owner = this;
×
UNCOV
444
    const rootComponent = this._getClassHierarchyRoot(component.constructor as ComponentCtor);
×
UNCOV
445
    this.components.set(rootComponent, component);
×
UNCOV
446
    this.components.set(component.constructor, component);
×
UNCOV
447
    this.componentValues.push(component);
×
UNCOV
448
    if (component.onAdd) {
×
UNCOV
449
      component.onAdd(this);
×
450
    }
451

UNCOV
452
    this.componentAdded$.notifyAll(component);
×
UNCOV
453
    return this as Entity<TKnownComponents | TComponent>;
×
454
  }
455

456
  /**
457
   * Removes a component from the entity, by default removals are deferred to the end of entity update to avoid consistency issues
458
   *
459
   * Components can be force removed with the `force` flag, the removal is not deferred and happens immediately
460
   * @param typeOrInstance
461
   * @param force
462
   */
463
  public removeComponent<TComponent extends Component>(
464
    typeOrInstance: ComponentCtor<TComponent> | TComponent,
465
    force = false
×
466
  ): Entity<Exclude<TKnownComponents, TComponent>> {
467
    let type: ComponentCtor<TComponent>;
UNCOV
468
    if (isComponentCtor(typeOrInstance)) {
×
UNCOV
469
      type = typeOrInstance;
×
470
    } else {
UNCOV
471
      type = typeOrInstance.constructor as ComponentCtor<TComponent>;
×
472
    }
473

UNCOV
474
    if (force) {
×
UNCOV
475
      const componentToRemove = this.components.get(type);
×
UNCOV
476
      if (componentToRemove) {
×
UNCOV
477
        this.componentRemoved$.notifyAll(componentToRemove);
×
UNCOV
478
        componentToRemove.owner = undefined;
×
UNCOV
479
        if (componentToRemove.onRemove) {
×
UNCOV
480
          componentToRemove.onRemove(this);
×
481
        }
UNCOV
482
        const componentIndex = this.componentValues.indexOf(componentToRemove);
×
UNCOV
483
        if (componentIndex > -1) {
×
UNCOV
484
          this.componentValues.splice(componentIndex, 1);
×
485
        }
486
      }
487

UNCOV
488
      const rootComponent = this._getClassHierarchyRoot(type);
×
UNCOV
489
      this.components.delete(rootComponent);
×
UNCOV
490
      this.components.delete(type); // remove after the notify to preserve typing
×
491
    } else {
UNCOV
492
      this._componentsToRemove.push(type);
×
493
    }
494

UNCOV
495
    return this as any;
×
496
  }
497

498
  public clearComponents() {
UNCOV
499
    const components = this.types;
×
UNCOV
500
    for (const c of components) {
×
UNCOV
501
      this.removeComponent(c);
×
502
    }
503
  }
504

505
  /**
506
   * @hidden
507
   * @internal
508
   */
509
  public processComponentRemoval() {
UNCOV
510
    for (const type of this._componentsToRemove) {
×
UNCOV
511
      this.removeComponent(type, true);
×
512
    }
UNCOV
513
    this._componentsToRemove.length = 0;
×
514
  }
515

516
  /**
517
   * Check if a component type exists
518
   * @param type
519
   */
520
  public has<TComponent extends Component>(type: ComponentCtor<TComponent>): boolean {
UNCOV
521
    return this.components.has(type);
×
522
  }
523

UNCOV
524
  private _isInitialized = false;
×
UNCOV
525
  private _isAdded = false;
×
526

527
  /**
528
   * Gets whether the actor is Initialized
529
   */
530
  public get isInitialized(): boolean {
UNCOV
531
    return this._isInitialized;
×
532
  }
533

534
  public get isAdded(): boolean {
UNCOV
535
    return this._isAdded;
×
536
  }
537

538
  /**
539
   * Initializes this entity, meant to be called by the Scene before first update not by users of Excalibur.
540
   *
541
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
542
   * @internal
543
   */
544
  public _initialize(engine: Engine) {
UNCOV
545
    if (!this.isInitialized) {
×
UNCOV
546
      this.onInitialize(engine);
×
UNCOV
547
      this.events.emit('initialize', new InitializeEvent(engine, this));
×
UNCOV
548
      this._isInitialized = true;
×
549
    }
550
  }
551

552
  /**
553
   * Adds this Actor, meant to be called by the Scene when Actor is added.
554
   *
555
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
556
   * @internal
557
   */
558
  public _add(engine: Engine) {
UNCOV
559
    if (!this.isAdded && this.isActive) {
×
UNCOV
560
      this.onAdd(engine);
×
UNCOV
561
      this.events.emit('add', new AddEvent(engine, this));
×
UNCOV
562
      this._isAdded = true;
×
563
    }
564
  }
565

566
  /**
567
   * Removes Actor, meant to be called by the Scene when Actor is added.
568
   *
569
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
570
   * @internal
571
   */
572
  public _remove(engine: Engine) {
UNCOV
573
    if (this.isAdded && !this.isActive) {
×
UNCOV
574
      this.onRemove(engine);
×
UNCOV
575
      this.events.emit('remove', new RemoveEvent(engine, this));
×
UNCOV
576
      this._isAdded = false;
×
577
    }
578
  }
579

580
  /**
581
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
582
   *
583
   * Internal _preupdate handler for {@apilink onPreUpdate} lifecycle event
584
   * @internal
585
   */
586
  public _preupdate(engine: Engine, elapsed: number): void {
UNCOV
587
    this.events.emit('preupdate', new PreUpdateEvent(engine, elapsed, this));
×
UNCOV
588
    this.onPreUpdate(engine, elapsed);
×
589
  }
590

591
  /**
592
   * It is not recommended that internal excalibur methods be overridden, do so at your own risk.
593
   *
594
   * Internal _preupdate handler for {@apilink onPostUpdate} lifecycle event
595
   * @internal
596
   */
597
  public _postupdate(engine: Engine, elapsed: number): void {
UNCOV
598
    this.events.emit('postupdate', new PostUpdateEvent(engine, elapsed, this));
×
UNCOV
599
    this.onPostUpdate(engine, elapsed);
×
600
  }
601

602
  /**
603
   * `onInitialize` is called before the first update of the entity. This method is meant to be
604
   * overridden.
605
   *
606
   * Synonymous with the event handler `.on('initialize', (evt) => {...})`
607
   */
608
  public onInitialize(engine: Engine): void {
609
    // Override me
610
  }
611

612
  /**
613
   * `onAdd` is called when Actor is added to scene. This method is meant to be
614
   * overridden.
615
   *
616
   * Synonymous with the event handler `.on('add', (evt) => {...})`
617
   */
618
  public onAdd(engine: Engine): void {
619
    // Override me
620
  }
621

622
  /**
623
   * `onRemove` is called when Actor is added to scene. This method is meant to be
624
   * overridden.
625
   *
626
   * Synonymous with the event handler `.on('remove', (evt) => {...})`
627
   */
628
  public onRemove(engine: Engine): void {
629
    // Override me
630
  }
631

632
  /**
633
   * Safe to override onPreUpdate lifecycle event handler. Synonymous with `.on('preupdate', (evt) =>{...})`
634
   *
635
   * `onPreUpdate` is called directly before an entity is updated.
636
   */
637
  public onPreUpdate(engine: Engine, elapsed: number): void {
638
    // Override me
639
  }
640

641
  /**
642
   * Safe to override onPostUpdate lifecycle event handler. Synonymous with `.on('postupdate', (evt) =>{...})`
643
   *
644
   * `onPostUpdate` is called directly after an entity is updated.
645
   */
646
  public onPostUpdate(engine: Engine, elapsed: number): void {
647
    // Override me
648
  }
649

650
  /**
651
   *
652
   * Entity update lifecycle, called internally
653
   * @internal
654
   * @param engine
655
   * @param elapsed
656
   */
657
  public update(engine: Engine, elapsed: number): void {
UNCOV
658
    this._initialize(engine);
×
UNCOV
659
    this._add(engine);
×
UNCOV
660
    this._preupdate(engine, elapsed);
×
UNCOV
661
    for (const child of this.children) {
×
662
      child.update(engine, elapsed);
×
663
    }
UNCOV
664
    this._postupdate(engine, elapsed);
×
UNCOV
665
    this._remove(engine);
×
666
  }
667

668
  public emit<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, event: EntityEvents[TEventName]): void;
669
  public emit(eventName: string, event?: any): void;
670
  public emit<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, event?: any): void {
UNCOV
671
    this.events.emit(eventName, event);
×
672
  }
673

674
  public on<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
675
  public on(eventName: string, handler: Handler<unknown>): Subscription;
676
  public on<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
UNCOV
677
    return this.events.on(eventName, handler);
×
678
  }
679

680
  public once<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): Subscription;
681
  public once(eventName: string, handler: Handler<unknown>): Subscription;
682
  public once<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler: Handler<any>): Subscription {
683
    return this.events.once(eventName, handler);
×
684
  }
685

686
  public off<TEventName extends EventKey<EntityEvents>>(eventName: TEventName, handler: Handler<EntityEvents[TEventName]>): void;
687
  public off(eventName: string, handler: Handler<unknown>): void;
688
  public off(eventName: string): void;
689
  public off<TEventName extends EventKey<EntityEvents> | string>(eventName: TEventName, handler?: Handler<any>): void {
690
    if (handler) {
×
691
      this.events.off(eventName, handler);
×
692
    } else {
693
      this.events.off(eventName);
×
694
    }
695
  }
696
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc