Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions src/ecs/entities/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,4 @@ import { DenseList } from '../../datastructures/index.js'
import { EntityLocation } from './location.js'

/** @augments {DenseList<EntityLocation>} */
export class Entities extends DenseList {
reserve() {
return this.push(new EntityLocation(
/** @type {ArchetypeId}*/(-1),
/** @type {TableRow}*/(-1)
))
}
}
export class Entities extends DenseList {}
30 changes: 27 additions & 3 deletions src/ecs/entities/entity.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { packInto64Int, unpackFrom64Int } from '../../algorithms/packnumber.js'

export class Entity {

/**
Expand All @@ -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)
}
}

Expand Down
8 changes: 7 additions & 1 deletion src/ecs/entities/entitycell.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/ecs/entities/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export class EntityLocation {
*/
index

/**
* @type {number}
*/
generation = 0

/**
* @type {ArchetypeId}
*/
Expand Down
6 changes: 3 additions & 3 deletions src/ecs/query/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
27 changes: 19 additions & 8 deletions src/ecs/registry.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
}

Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/ecs/tests/entities/entitycell.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.', () => {
Expand Down
4 changes: 2 additions & 2 deletions src/ecs/tests/query.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions src/ecs/tests/world.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()])
Expand All @@ -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([])
Expand Down