diff --git a/CHANGELOG.md b/CHANGELOG.md index 82ea8e52f..7032df182 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -186,6 +186,7 @@ are doing mtv adjustments during precollision. ### Updates +- Perf improvement to retrieving components with `ex.Entity.get()` which widely improves engine performance - Non-breaking parameters that reference `delta` to `elapsedMs` to better communicate intent and units - Perf improvements to `ex.ParticleEmitter` * Use the same integrator as the MotionSystem in the tight loop diff --git a/src/engine/EntityComponentSystem/Entity.ts b/src/engine/EntityComponentSystem/Entity.ts index 604cfc2e9..976f8026f 100644 --- a/src/engine/EntityComponentSystem/Entity.ts +++ b/src/engine/EntityComponentSystem/Entity.ts @@ -118,9 +118,6 @@ export class Entity implements OnIniti public componentValues: Component[] = []; private _componentsToRemove: ComponentCtor[] = []; - private _instanceOfComponentCacheDirty = true; - private _instanceOfComponentCache = new Map(); - constructor(options: EntityOptions); constructor(components?: TKnownComponents[], name?: string); constructor(componentsOrOptions?: TKnownComponents[] | EntityOptions, name?: string) { @@ -259,31 +256,8 @@ export class Entity implements OnIniti return true; } - private _getCachedInstanceOfType( - type: ComponentCtor - ): MaybeKnownComponent | undefined { - if (this._instanceOfComponentCacheDirty) { - this._instanceOfComponentCacheDirty = false; - this._instanceOfComponentCache.clear(); - } - - if (this._instanceOfComponentCache.has(type)) { - return this._instanceOfComponentCache.get(type) as MaybeKnownComponent; - } - - for (let compIndex = 0; compIndex < this.componentValues.length; compIndex++) { - const instance = this.componentValues[compIndex]; - if (instance instanceof type) { - this._instanceOfComponentCache.set(type, instance); - return instance as MaybeKnownComponent; - } - } - return undefined; - } - get(type: ComponentCtor): MaybeKnownComponent { - const maybeComponent = this._getCachedInstanceOfType(type); - return maybeComponent ?? (this.components.get(type) as MaybeKnownComponent); + return this.components.get(type) as MaybeKnownComponent; } private _parent: Entity | null = null; @@ -415,13 +389,23 @@ export class Entity implements OnIniti return this; } + private _getClassHierarchyRoot(componentType: ComponentCtor): ComponentCtor { + let current = componentType; + let parent = Object.getPrototypeOf(current.prototype)?.constructor; + + while (parent && parent !== Object && parent !== Component) { + current = parent; + parent = Object.getPrototypeOf(current.prototype)?.constructor; + } + return current; + } + /** * Adds a component to the entity * @param component Component or Entity to add copy of components from * @param force Optionally overwrite any existing components of the same type */ public addComponent(component: TComponent, force: boolean = false): Entity { - this._instanceOfComponentCacheDirty = true; // if component already exists, skip if not forced if (this.has(component.constructor as ComponentCtor)) { if (force) { @@ -441,9 +425,10 @@ export class Entity implements OnIniti } component.owner = this; + const rootComponent = this._getClassHierarchyRoot(component.constructor as ComponentCtor); + this.components.set(rootComponent, component); this.components.set(component.constructor, component); this.componentValues.push(component); - this._instanceOfComponentCache.set(component.constructor, component); if (component.onAdd) { component.onAdd(this); } @@ -483,8 +468,10 @@ export class Entity implements OnIniti this.componentValues.splice(componentIndex, 1); } } + + const rootComponent = this._getClassHierarchyRoot(type); + this.components.delete(rootComponent); this.components.delete(type); // remove after the notify to preserve typing - this._instanceOfComponentCacheDirty = true; } else { this._componentsToRemove.push(type); }