diff --git a/src/ecs/entities/entities.js b/src/ecs/entities/entities.js index d7909ff1..9bdb924f 100644 --- a/src/ecs/entities/entities.js +++ b/src/ecs/entities/entities.js @@ -3,11 +3,4 @@ import { DenseList } from '../../datastructures/index.js' import { EntityLocation } from './location.js' /** @augments {DenseList} */ -export class Entities extends DenseList { - reserve() { - return this.push(new EntityLocation( - /** @type {ArchetypeId}*/(-1), - /** @type {TableRow}*/(-1) - )) - } -} \ No newline at end of file +export class Entities extends DenseList {} \ No newline at end of file diff --git a/src/ecs/entities/entity.js b/src/ecs/entities/entity.js index 2511a7f7..3001934f 100644 --- a/src/ecs/entities/entity.js +++ b/src/ecs/entities/entity.js @@ -1,3 +1,5 @@ +import { packInto64Int, unpackFrom64Int } from '../../algorithms/packnumber.js' + export class Entity { /** @@ -6,25 +8,47 @@ export class Entity { */ index + /** + * @readonly + * @type {number} + */ + generation + /** * @param {number} index + * @param {number} generation */ - constructor(index){ + constructor(index, generation){ this.index = index + this.generation = generation } /** * @param {Entity} other */ equals(other){ - return this.index === other.index + return ( + this.index === other.index && + this.generation === other.generation + ) } /** * @returns {EntityId} */ id(){ - return this.index + const { index, generation } = this + + return packInto64Int(index, generation) + } + + /** + * @param {EntityId} id + */ + static from(id){ + const [index, generation] = unpackFrom64Int(id) + + return new Entity(index, generation) } } diff --git a/src/ecs/entities/entitycell.js b/src/ecs/entities/entitycell.js index c7d99fbc..a076294c 100644 --- a/src/ecs/entities/entitycell.js +++ b/src/ecs/entities/entitycell.js @@ -38,8 +38,14 @@ export class EntityCell { */ constructor(world, entity) { const entities = world.getEntities() + const location = entities.get(entity.index) + + if(location && location.generation === entity.generation){ + this.location = location + } else { + this.location = new EntityLocation() + } - this.location = entities.get(entity.index) || new EntityLocation() this.tables = world.getTables() this.archetypes = world.getArchetypes() this.entity = entity diff --git a/src/ecs/entities/location.js b/src/ecs/entities/location.js index 273ffeb8..67131118 100644 --- a/src/ecs/entities/location.js +++ b/src/ecs/entities/location.js @@ -12,6 +12,11 @@ export class EntityLocation { */ index + /** + * @type {number} + */ + generation = 0 + /** * @type {ArchetypeId} */ diff --git a/src/ecs/query/query.js b/src/ecs/query/query.js index ea4894fc..36e34334 100644 --- a/src/ecs/query/query.js +++ b/src/ecs/query/query.js @@ -107,10 +107,10 @@ export class Query { */ get(entity) { const { world, descriptors, tableIds } = this - const entities = world.getEntities() - const location = entities.get(entity.index) + const cell = world.getEntity(entity) + const { location } = cell - if (!location) return null + if (!cell.exists()) return null const { tableId, index } = location const table = world.getTables().get(tableId) diff --git a/src/ecs/registry.js b/src/ecs/registry.js index 81fe13d4..57a82f5c 100644 --- a/src/ecs/registry.js +++ b/src/ecs/registry.js @@ -134,13 +134,20 @@ export class World { */ spawn(components) { const entityIndex = this.entities.reserve() + let location = this.entities.get(entityIndex) - // SAFETY: the entity was reserved in this function so we know its there. - const location = /** @type {EntityLocation}*/ (this.entities.get(entityIndex)) + if(!location){ + const newLocation = new EntityLocation() + + this.entities.set(entityIndex, newLocation) + location = newLocation + } + + location.generation += 1 // SAFETY:Object constructors can be casted from `Function` to `Constructor` - const newIds = (components.map((c) => typeid( /** @type {Constructor} */ (c.constructor)))) - const entity = new Entity(entityIndex) + const newIds = (components.map((c) => typeid( /** @type {Constructor} */(c.constructor)))) + const entity = new Entity(entityIndex, location.generation) newIds.push(typeid(Entity)) components.push(entity) @@ -166,7 +173,7 @@ export class World { insert(entity, components) { const location = this.entities.get(entity.index) - if (!location) { + if (!location || location.generation !== entity.generation) { return } @@ -212,7 +219,7 @@ export class World { remove(entity, components) { const location = this.entities.get(entity.index) - if (!location) { + if (!location || location.generation !== entity.generation) { return } @@ -265,7 +272,9 @@ export class World { despawn(entity) { const location = this.entities.get(entity.index) - if (!location) return + if (!location || location.generation !== entity.generation){ + return + } const { archetypeId, tableId, index } = location const archetype = this.archetypes.get(archetypeId) @@ -303,7 +312,9 @@ export class World { get(entity, type) { const location = this.entities.get(entity.index) - if (!location) return null + if (!location || location.generation !== entity.generation) { + return null + } const { tableId, index } = location const table = this.tables.get(tableId) diff --git a/src/ecs/tests/entities/entitycell.test.js b/src/ecs/tests/entities/entitycell.test.js index 2fdd7db1..65ec7c4f 100644 --- a/src/ecs/tests/entities/entitycell.test.js +++ b/src/ecs/tests/entities/entitycell.test.js @@ -28,8 +28,8 @@ describe("Testing `EntityCell`", () => { const cell1 = world.getEntity(entity1) const cell2 = world.getEntity(entity2) - deepStrictEqual(cell1.id(), new Entity(0)) - deepStrictEqual(cell2.id(), new Entity(1)) + deepStrictEqual(cell1.id(), new Entity(0, 1)) + deepStrictEqual(cell2.id(), new Entity(1, 1)) }) test('`EntityCell` tests entity existence correctly.', () => { diff --git a/src/ecs/tests/query.test.js b/src/ecs/tests/query.test.js index bc58a801..b915a0dd 100644 --- a/src/ecs/tests/query.test.js +++ b/src/ecs/tests/query.test.js @@ -75,7 +75,7 @@ describe("Testing `Query`", () => { test('query for single component, specific entity', () => { const world = createWorld() const query = new Query(world, [A]) - const entity = new Entity(0) + const entity = new Entity(0, 1) const components = query.get(entity) assert(components) @@ -89,7 +89,7 @@ describe("Testing `Query`", () => { test('query for multiple components, specific entity', () => { const world = createWorld() const query = new Query(world, [A, B, C]) - const entity = new Entity(0) + const entity = new Entity(0, 1) const components = query.get(entity) assert(components) diff --git a/src/ecs/tests/world.test.js b/src/ecs/tests/world.test.js index be0622cc..772cadd4 100644 --- a/src/ecs/tests/world.test.js +++ b/src/ecs/tests/world.test.js @@ -28,6 +28,26 @@ describe("Testing `World`", () => { deepStrictEqual(components, [typeid(A), typeid(B), typeid(C), typeid(Entity)]) }) + test('Entity generation starts at one.', () => { + const world = new World() + const entity = world.spawn([new A(), new B(), new C()]) + + deepStrictEqual(entity,new Entity(0,1)) + }) + + test('Entity generation is incremented.', () => { + const world = new World() + const entity1 = world.spawn([new A(), new B(), new C()]) + world.despawn(entity1) + const entity2 = world.spawn([new A(), new B(), new C()]) + world.despawn(entity2) + const entity3 = world.spawn([new A(), new B(), new C()]) + + deepStrictEqual(entity1,new Entity(0,1)) + deepStrictEqual(entity2,new Entity(0,2)) + deepStrictEqual(entity3,new Entity(0,3)) + }) + test('Entity is despawned correctly from a world.', () => { const world = new World() const entity = world.spawn([new A(), new B(), new C()]) @@ -38,6 +58,21 @@ describe("Testing `World`", () => { deepStrictEqual(components, []) }) + test('Invalidated entity cannot be accessed on same index', () => { + const world = new World() + const entity1 = world.spawn([new A()]) + world.despawn(entity1) + const entity2 = world.spawn([new A()]) + + const cell1 = world.getEntity(entity1) + const cell2 = world.getEntity(entity2) + + deepStrictEqual(entity1,new Entity(0,1)) + deepStrictEqual(entity2,new Entity(0,2)) + deepStrictEqual(cell1.exists(), false) + deepStrictEqual(cell2.exists(), true) + }) + test('Components are inserted into an entity.', () => { const world = new World() const entity = world.spawn([])