From c42be8be5bfc7bcbee793acdd3dbff75d4641e4d Mon Sep 17 00:00:00 2001 From: Erik Onarheim Date: Sat, 12 Feb 2022 00:16:50 -0600 Subject: [PATCH] perf: Add `zIndexChanged$` + Improve pointer system perf (#2242) This PR adds `zIndexChanged$` to the `ex.TransformComponent` in order to know when the z index changes. This allows the `ex.PointerSystem` to maintain a sorted list which is much faster than using the ECS Query. --- CHANGELOG.md | 1 + .../Components/TransformComponent.ts | 19 ++++++- src/engine/Input/PointerSystem.ts | 54 ++++++++++++++++--- src/spec/TransformComponentSpec.ts | 11 ++++ 4 files changed, 77 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bdfcb7b4..5da3f829a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added faster `ex.BoundingBox.transform(...)` implementation. - Added faster `ex.BoundingBox.overlap(...)` implementation. - Added `ex.Vector.min(...)` and `ex.Vector.max(...)` to find the min/max of each vector component between 2 vectors. +- Added `ex.TransformComponent.zIndexChange$` observable to watch when z index changes. ### Fixed diff --git a/src/engine/EntityComponentSystem/Components/TransformComponent.ts b/src/engine/EntityComponentSystem/Components/TransformComponent.ts index 033534c2c..d5f04bbee 100644 --- a/src/engine/EntityComponentSystem/Components/TransformComponent.ts +++ b/src/engine/EntityComponentSystem/Components/TransformComponent.ts @@ -2,6 +2,7 @@ import { Matrix, MatrixLocations } from '../../Math/matrix'; import { VectorView } from '../../Math/vector-view'; import { Vector, vec } from '../../Math/vector'; import { Component } from '../Component'; +import { Observable } from '../../Util/Observable'; export interface Transform { /** @@ -191,11 +192,27 @@ export class TransformComponent extends Component<'ex.transform'> implements Tra } } + /** + * Observable that emits when the z index changes on this component + */ + public zIndexChanged$ = new Observable(); + private _z = 0; + /** * The z-index ordering of the entity, a higher values are drawn on top of lower values. * For example z=99 would be drawn on top of z=0. */ - public z: number = 0; + public get z(): number { + return this._z; + } + + public set z(val: number) { + const oldz = this._z; + this._z = val; + if (oldz !== val) { + this.zIndexChanged$.notifyAll(val); + } + } /** * The rotation of the entity in radians. For example `Math.PI` radians is the same as 180 degrees. diff --git a/src/engine/Input/PointerSystem.ts b/src/engine/Input/PointerSystem.ts index d226b6b32..07ffd3b56 100644 --- a/src/engine/Input/PointerSystem.ts +++ b/src/engine/Input/PointerSystem.ts @@ -1,6 +1,15 @@ import { ColliderComponent } from '../Collision/ColliderComponent'; import { Engine } from '../Engine'; -import { System, TransformComponent, SystemType, Entity, CoordPlane } from '../EntityComponentSystem'; +import { + System, + TransformComponent, + SystemType, + Entity, + CoordPlane, + AddedEntity, + RemovedEntity, + isAddedSystemEntity +} from '../EntityComponentSystem'; import { GraphicsComponent } from '../Graphics/GraphicsComponent'; import { Scene } from '../Scene'; import { PointerComponent } from './PointerComponent'; @@ -39,9 +48,40 @@ export class PointerSystem extends System this._receiver = this._engine.input.pointers; } - public sort(a: Entity, b: Entity) { - // Sort from high to low, because things on 'top' receive the pointer events first - return b.get(TransformComponent).z - a.get(TransformComponent).z; + private _sortedTransforms: TransformComponent[] = []; + private _sortedEntities: Entity[] = []; + + private _zHasChanged = false; + private _zIndexUpdate = () => { + this._zHasChanged = true; + }; + + public preupdate(): void { + if (this._zHasChanged) { + this._sortedTransforms.sort((a, b) => { + return b.z - a.z; + }); + this._sortedEntities = this._sortedTransforms.map(t => t.owner); + this._zHasChanged = false; + } + } + + public notify(entityAddedOrRemoved: AddedEntity | RemovedEntity): void { + if (isAddedSystemEntity(entityAddedOrRemoved)) { + const tx = entityAddedOrRemoved.data.get(TransformComponent); + this._sortedTransforms.push(tx); + this._sortedEntities.push(tx.owner); + tx.zIndexChanged$.subscribe(this._zIndexUpdate); + this._zHasChanged = true; + } else { + const tx = entityAddedOrRemoved.data.get(TransformComponent); + tx.zIndexChanged$.unsubscribe(this._zIndexUpdate); + const index = this._sortedTransforms.indexOf(tx); + if (index > -1) { + this._sortedTransforms.splice(index, 1); + this._sortedEntities.splice(index, 1); + } + } } public entityCurrentlyUnderPointer(entity: Entity, pointerId: number) { @@ -73,12 +113,12 @@ export class PointerSystem extends System this.currentFrameEntityToPointers.set(entity.id, pointers.concat(pointerId)); } - public update(entities: Entity[]): void { + public update(_entities: Entity[]): void { // Locate all the pointer/entity mappings - this._processPointerToEntity(entities); + this._processPointerToEntity(this._sortedEntities); // Dispatch pointer events on entities - this._dispatchEvents(entities); + this._dispatchEvents(this._sortedEntities); // Clear last frame's events this._receiver.update(); diff --git a/src/spec/TransformComponentSpec.ts b/src/spec/TransformComponentSpec.ts index 7c9e15552..c8117f667 100644 --- a/src/spec/TransformComponentSpec.ts +++ b/src/spec/TransformComponentSpec.ts @@ -145,4 +145,15 @@ describe('A TransformComponent', () => { expect(childTx.getGlobalTransform().rotation).toBe(Math.PI); expect(childTx.getGlobalTransform().scale).toBeVector(ex.vec(2, 3)); }); + + it('can observe a z index change', () => { + const tx = new ex.TransformComponent(); + const zSpy = jasmine.createSpy('zSpy'); + tx.zIndexChanged$.subscribe(zSpy); + + tx.z = 19; + + expect(zSpy).toHaveBeenCalledWith(19); + expect(tx.z).toBe(19); + }); });