|
1 | 1 | import { Engine } from '../../packages/@dcl/ecs/src/engine' |
2 | 2 | import { |
3 | 3 | Entity, |
| 4 | + EntityState, |
4 | 5 | EntityUtils, |
5 | 6 | IEntityContainer, |
6 | 7 | RESERVED_STATIC_ENTITIES, |
@@ -272,6 +273,66 @@ describe('Reserved entity range ownership', () => { |
272 | 273 | // The renderer applies scene component ops on the three named static entities — that is how |
273 | 274 | // InputModifier.deleteFrom(engine.PlayerEntity) clears an input lock — so a removal must |
274 | 275 | // still purge them, even though their ids are reserved and never released. |
| 276 | + // engine.getEntityState must delegate to the container rather than alias its method. An |
| 277 | + // aliased reference binds `this` to the engine, so a container that uses `this` reports the |
| 278 | + // wrong state through the public API while the engine — which calls the container directly — |
| 279 | + // reports the right one. removeEntity now classifies via getEntityState, so a divergence |
| 280 | + // there means the public API contradicts the engine's own behaviour. |
| 281 | + describe('when a custom container depends on `this`', () => { |
| 282 | + const CUSTOM_BOUND = 1000 |
| 283 | + |
| 284 | + let engine: ReturnType<typeof Engine> |
| 285 | + let rendererOwned: Entity |
| 286 | + |
| 287 | + beforeEach(() => { |
| 288 | + class ThisDependentContainer { |
| 289 | + readonly bound = CUSTOM_BOUND |
| 290 | + private readonly used = new Set<number>() |
| 291 | + private counter = CUSTOM_BOUND |
| 292 | + generateEntity(): Entity { |
| 293 | + const entity = EntityUtils.toEntityId(this.counter++, 0) |
| 294 | + this.used.add(entity as number) |
| 295 | + return entity |
| 296 | + } |
| 297 | + removeEntity(entity: Entity): boolean { |
| 298 | + if (EntityUtils.fromEntityId(entity)[0] < this.bound) return false |
| 299 | + this.used.delete(entity as number) |
| 300 | + return true |
| 301 | + } |
| 302 | + getEntityState(entity: Entity): EntityState { |
| 303 | + if (EntityUtils.fromEntityId(entity)[0] < this.bound) return EntityState.Reserved |
| 304 | + return this.used.has(entity as number) ? EntityState.UsedEntity : EntityState.Unknown |
| 305 | + } |
| 306 | + getExistingEntities(): Set<Entity> { |
| 307 | + return new Set([...this.used] as Entity[]) |
| 308 | + } |
| 309 | + releaseRemovedEntities(): Entity[] { |
| 310 | + return [] |
| 311 | + } |
| 312 | + updateRemovedEntity(): boolean { |
| 313 | + return false |
| 314 | + } |
| 315 | + updateUsedEntity(): boolean { |
| 316 | + return false |
| 317 | + } |
| 318 | + } |
| 319 | + engine = Engine({ onChangeFunction: () => {}, entityContainer: new ThisDependentContainer() }) |
| 320 | + rendererOwned = EntityUtils.toEntityId(700, 0) |
| 321 | + }) |
| 322 | + |
| 323 | + it('should report the state the container reports, not undefined-compared garbage', () => { |
| 324 | + expect(engine.getEntityState(rendererOwned)).toBe(EntityState.Reserved) |
| 325 | + }) |
| 326 | + |
| 327 | + it('should agree with what removeEntity actually does', () => { |
| 328 | + expect(engine.removeEntity(rendererOwned)).toBe(false) |
| 329 | + }) |
| 330 | + |
| 331 | + it('should still classify a container-owned entity as releasable', () => { |
| 332 | + expect(engine.removeEntity(engine.addEntity())).toBe(true) |
| 333 | + }) |
| 334 | + }) |
| 335 | + |
275 | 336 | describe('when a scene removes a named static entity', () => { |
276 | 337 | let engine: ReturnType<typeof Engine> |
277 | 338 | let InputLock: ReturnType<ReturnType<typeof Engine>['defineComponent']> |
|
0 commit comments