Compare commits

..

15 commits

Author SHA1 Message Date
Clown
e34352477d
Merge 96bed9bd7e into 78fe5338cf 2025-07-18 22:57:30 +03:00
Ukendio
78fe5338cf index component record after archetype gets created
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-07-18 16:53:32 +02:00
Ukendio
ca0689c92b Bump 2025-07-18 15:14:39 +02:00
Ukendio
117a5e0ca7 Fix on_remove hook not called on cached edge 2025-07-18 15:13:59 +02:00
Ukendio
d99088ea1e Bump rc
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-07-18 00:38:39 +02:00
Ukendio
012b5e2bfa Make cleanup conditions 2025-07-18 00:37:58 +02:00
Ukendio
54b21001ab Bump versions 2025-07-17 23:45:56 +02:00
Ukendio
9c09686a69 always check OnDelete condition 2025-07-17 23:45:04 +02:00
Ukendio
ebc39c8b28 Remove debug msgs
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-07-17 19:38:24 +02:00
Ukendio
7b86084b94 Bump versions 2025-07-17 19:34:29 +02:00
Ukendio
25ceda5cee Remove alive count upvalue 2025-07-17 18:50:21 +02:00
Ukendio
fc56b6f716 Retrieve updated max_id 2025-07-17 18:47:05 +02:00
dai
c30328527a
Add typings for bulk operations (#257)
* Add typings for bulk operations

* Use Id
2025-07-17 18:25:37 +02:00
Ukendio
c3853023d0 Allow creating an entity with a non-zero generation below range 2025-07-17 18:24:44 +02:00
Ukendio
7b253e1c2a Remove archetype recycling 2025-07-17 18:07:11 +02:00
5 changed files with 260 additions and 201 deletions

3
jecs.d.ts vendored
View file

@ -321,3 +321,6 @@ export type ComponentRecord = {
} }
export function component_record(world: World, id: Id): ComponentRecord export function component_record(world: World, id: Id): ComponentRecord
export function bulk_insert<const C extends Id[]>(world: World, entity: Entity, ids: C, values: InferComponents<C>): void
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void

347
jecs.luau
View file

@ -19,8 +19,7 @@ export type Archetype = {
type: string, type: string,
entities: { Entity }, entities: { Entity },
columns: { Column }, columns: { Column },
columns_map: { [Id]: Column }, columns_map: { [Id]: Column }
dead: boolean,
} }
export type QueryInner = { export type QueryInner = {
@ -715,13 +714,13 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
if world_has_one_inline(world, relation, EcsExclusive) then if world_has_one_inline(world, relation, EcsExclusive) then
is_exclusive = true is_exclusive = true
end end
else end
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0) local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
if cleanup_policy == EcsDelete then if cleanup_policy == EcsDelete then
has_delete = true has_delete = true
end end
end
local on_add, on_change, on_remove = world_get(world, local on_add, on_change, on_remove = world_get(world,
relation, EcsOnAdd, EcsOnChange, EcsOnRemove) relation, EcsOnAdd, EcsOnChange, EcsOnRemove)
@ -861,8 +860,7 @@ local function archetype_create(world: World, id_types: { Id }, ty, prev: i53?):
entities = {}, entities = {},
id = archetype_id, id = archetype_id,
type = ty, type = ty,
types = id_types, types = id_types
dead = false,
} }
archetype_register(world, archetype, false) archetype_register(world, archetype, false)
@ -901,10 +899,6 @@ local function archetype_ensure(world: World, id_types: { Id }): Archetype
local ty = hash(id_types) local ty = hash(id_types)
local archetype = world.archetype_index[ty] local archetype = world.archetype_index[ty]
if archetype then if archetype then
if archetype.dead then
archetype_register(world, archetype)
archetype.dead = false :: any
end
return archetype return archetype
end end
@ -1082,8 +1076,8 @@ local function archetype_destroy(world: World, archetype: Archetype)
end end
local archetype_id = archetype.id local archetype_id = archetype.id
-- world.archetypes[archetype_id] = nil :: any world.archetypes[archetype_id] = nil :: any
-- world.archetype_index[archetype.type] = nil :: any world.archetype_index[archetype.type] = nil :: any
local columns_map = archetype.columns_map local columns_map = archetype.columns_map
for id in columns_map do for id in columns_map do
@ -1104,8 +1098,6 @@ local function archetype_destroy(world: World, archetype: Archetype)
end end
end end
end end
archetype.dead = true
end end
local function NOOP() end local function NOOP() end
@ -2033,7 +2025,7 @@ local function world_children<a>(world: World, parent: Id<a>)
return world_each(world, ECS_PAIR(EcsChildOf, parent::number)) return world_each(world, ECS_PAIR(EcsChildOf, parent::number))
end end
local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, values: { any }) local function ecs_bulk_insert(world: World, entity: Entity, ids: { Id }, values: { any })
local entity_index = world.entity_index local entity_index = world.entity_index
local r = entity_index_try_get(entity_index, entity) local r = entity_index_try_get(entity_index, entity)
if not r then if not r then
@ -2111,7 +2103,7 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, va
end end
end end
local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity }) local function ecs_bulk_remove(world: World, entity: Entity, ids: { Id })
local entity_index = world.entity_index local entity_index = world.entity_index
local r = entity_index_try_get(entity_index, entity) local r = entity_index_try_get(entity_index, entity)
if not r then if not r then
@ -2123,7 +2115,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity })
return return
end end
local remove: { [Entity]: boolean } = {} local remove: { [Id]: boolean } = {}
local columns_map = from.columns_map local columns_map = from.columns_map
@ -2162,14 +2154,12 @@ end
local function world_new() local function world_new()
local eindex_dense_array = {} :: { Entity } local eindex_dense_array = {} :: { Entity }
local eindex_sparse_array = {} :: { Record } local eindex_sparse_array = {} :: { Record }
local eindex_alive_count = 0
local eindex_max_id = 0
local entity_index = { local entity_index = {
dense_array = eindex_dense_array, dense_array = eindex_dense_array,
sparse_array = eindex_sparse_array, sparse_array = eindex_sparse_array,
alive_count = eindex_alive_count, alive_count = 0,
max_id = eindex_max_id, max_id = 0,
} :: EntityIndex } :: EntityIndex
local component_index = {} :: ComponentIndex local component_index = {} :: ComponentIndex
@ -2307,28 +2297,66 @@ local function world_new()
return r return r
end end
local function inner_world_add<T, a>( local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
world: World,
entity: Entity<T>,
id: Id<a>
): ()
local entity_index = world.entity_index
local record = inner_entity_index_try_get_unsafe(entity :: number) local record = inner_entity_index_try_get_unsafe(entity :: number)
if not record then if not record then
return return
end end
local from = record.archetype local from: Archetype = record.archetype
if ECS_IS_PAIR(id::number) then
local src = from or ROOT_ARCHETYPE local src = from or ROOT_ARCHETYPE
local edge = archetype_edges[src.id] local column = src.columns_map[id]
local to = edge[id] if column then
local idr = component_index[id]
column[record.row] = data
-- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly.
local on_change = idr.on_change
if on_change then
on_change(entity, id, data)
end
else
local to: Archetype
local idr: ComponentRecord local idr: ComponentRecord
if not to then if ECS_IS_PAIR(id::number) then
local first = ECS_PAIR_FIRST(id::number) local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard) local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc] idr = component_index[wc]
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local edge = archetype_edges[src.id]
to = edge[id]
if to == nil then
if idr and (bit32.btest(idr.flags) == true) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
if not idr then
idr = component_index[wc]
end
end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id] local cr = idr.records[src.id]
if cr then if cr then
local on_remove = idr.on_remove local on_remove = idr.on_remove
@ -2342,47 +2370,131 @@ local function world_new()
local dst = table.clone(id_types) local dst = table.clone(id_types)
dst[cr] = id dst[cr] = id
to = archetype_ensure(world, dst) to = archetype_ensure(world, dst)
else end
end
if not to then
to = find_archetype_with(world, id, src) to = find_archetype_with(world, id, src)
idr = component_index[id] end
end end
else else
local edges = archetype_edges
local edge = edges[src.id]
to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, src) to = find_archetype_with(world, id, src)
idr = component_index[id]
end
edge[id] = to edge[id] = to
else edges[to.id][id] = src
if to.dead then
archetype_register(world, to, true)
edge[id] = to
archetype_edges[to.id][id] = src
to.dead = false
end end
idr = component_index[id] idr = component_index[id]
end end
if from == to then
return
end
if from then if from then
-- If there was a previous archetype, then the entity needs to move the archetype
inner_entity_move(entity_index, entity, record, to) inner_entity_move(entity_index, entity, record, to)
else else
if #to.types > 0 then
new_entity(entity, record, to) new_entity(entity, record, to)
end end
end
column = to.columns_map[id]
column[record.row] = data
local on_add = idr.on_add local on_add = idr.on_add
if on_add then if on_add then
on_add(entity, id) on_add(entity, id, data)
end
end
end end
local function inner_world_add<T, a>(
world: World,
entity: Entity<T>,
id: Id<a>
): ()
local entity_index = world.entity_index
local record = inner_entity_index_try_get_unsafe(entity :: number)
if not record then
return return
end end
local to = archetype_traverse_add(world, id, from)
if from == to then local from = record.archetype
local src = from or ROOT_ARCHETYPE
if src.columns_map[id] then
return return
end end
local to: Archetype
local idr: ComponentRecord
if ECS_IS_PAIR(id::number) then
local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc]
local edge = archetype_edges[src.id]
to = edge[id]
if to == nil then
if idr and (bit32.btest(idr.flags) == true) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
if not idr then
idr = component_index[wc]
end
end
edge[id] = to
archetype_edges[(to :: Archetype).id][id] = src
else
if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
end
end
if not to then
to = find_archetype_with(world, id, src)
end
end
else
local edges = archetype_edges
local edge = edges[src.id]
to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, src)
edge[id] = to
edges[to.id][id] = src
end
idr = component_index[id]
end
if from then if from then
inner_entity_move(entity_index, entity, record, to) inner_entity_move(entity_index, entity, record, to)
else else
@ -2391,7 +2503,6 @@ local function world_new()
end end
end end
local idr = component_index[id]
local on_add = idr.on_add local on_add = idr.on_add
if on_add then if on_add then
@ -2495,102 +2606,6 @@ local function world_new()
return inner_world_target(world, entity, EcsChildOf, 0) return inner_world_target(world, entity, EcsChildOf, 0)
end end
local function inner_archetype_traverse_add(id: Id, from: Archetype): Archetype
from = from or ROOT_ARCHETYPE
if from.columns_map[id] then
return from
end
local edges = archetype_edges
local edge = edges[from.id]
local to = edge[id] :: Archetype
if not to then
to = find_archetype_with(world, id, from)
edge[id] = to
edges[to.id][id] = from
end
return to
end
local function inner_world_set<T, a>(world: World, entity: Entity<T>, id: Id<a>, data: a): ()
local record = inner_entity_index_try_get_unsafe(entity :: number)
if not record then
return
end
local from: Archetype = record.archetype
local src = from or ROOT_ARCHETYPE
local column = src.columns_map[id]
if column then
local idr = component_index[id]
column[record.row] = data
-- If the archetypes are the same it can avoid moving the entity
-- and just set the data directly.
local on_change = idr.on_change
if on_change then
on_change(entity, id, data)
end
else
local to: Archetype
local idr: ComponentRecord
if ECS_IS_PAIR(id::number) then
local edge = archetype_edges[src.id]
to = edge[id]
if not to then
local first = ECS_PAIR_FIRST(id::number)
local wc = ECS_PAIR(first, EcsWildcard)
idr = component_index[wc]
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local cr = idr.records[src.id]
if cr then
local on_remove = idr.on_remove
local id_types = src.types
if on_remove then
on_remove(entity, id_types[cr])
src = record.archetype
id_types = src.types
cr = idr.records[src.id]
end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
else
to = find_archetype_with(world, id, src)
idr = component_index[id]
end
else
to = find_archetype_with(world, id, src)
idr = component_index[id]
end
edge[id] = to
archetype_edges[to.id][id] = src
else
idr = component_index[id]
end
else
to = inner_archetype_traverse_add(id, from)
idr = component_index[id]
end
if from then
-- If there was a previous archetype, then the entity needs to move the archetype
inner_entity_move(entity_index, entity, record, to)
else
new_entity(entity, record, to)
end
column = to.columns_map[id]
column[record.row] = data
local on_add = idr.on_add
if on_add then
on_add(entity, id, data)
end
end
end
local function inner_world_entity<T>(world: World, entity: Entity<T>?): Entity<T> local function inner_world_entity<T>(world: World, entity: Entity<T>?): Entity<T>
if entity then if entity then
local index = ECS_ID(entity :: number) local index = ECS_ID(entity :: number)
@ -2602,8 +2617,6 @@ local function world_new()
if not dense or r.dense == 0 then if not dense or r.dense == 0 then
r.dense = index r.dense = index
dense = index dense = index
local any = eindex_dense_array[dense]
if any == entity then
local e_swap = eindex_dense_array[dense] local e_swap = eindex_dense_array[dense]
local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record local r_swap = inner_entity_index_try_get_any(e_swap :: number) :: Record
@ -2614,7 +2627,6 @@ local function world_new()
eindex_dense_array[dense] = e_swap eindex_dense_array[dense] = e_swap
eindex_dense_array[alive_count] = entity eindex_dense_array[alive_count] = entity
end
return entity return entity
end end
@ -2636,7 +2648,7 @@ local function world_new()
return entity return entity
else else
for i = eindex_max_id + 1, index do for i = entity_index.max_id + 1, index do
eindex_sparse_array[i] = { dense = i } :: Record eindex_sparse_array[i] = { dense = i } :: Record
eindex_dense_array[i] = i eindex_dense_array[i] = i
end end
@ -2801,7 +2813,7 @@ local function world_new()
if idr then if idr then
local flags = idr.flags local flags = idr.flags
if bit32.btest(flags, ECS_ID_DELETE) then if (bit32.btest(flags, ECS_ID_DELETE) == true) then
for archetype_id in idr.records do for archetype_id in idr.records do
local idr_archetype = archetypes[archetype_id] local idr_archetype = archetypes[archetype_id]
@ -2908,7 +2920,8 @@ local function world_new()
if idr_r then if idr_r then
local archetype_ids = idr_r.records local archetype_ids = idr_r.records
local flags = idr_r.flags local flags = idr_r.flags
if bit32.btest(flags, ECS_ID_DELETE) then local has_delete_policy = bit32.btest(flags, ECS_ID_DELETE)
if has_delete_policy then
for archetype_id in archetype_ids do for archetype_id in archetype_ids do
local idr_r_archetype = archetypes[archetype_id] local idr_r_archetype = archetypes[archetype_id]
local entities = idr_r_archetype.entities local entities = idr_r_archetype.entities
@ -3038,8 +3051,10 @@ local function world_new()
inner_world_set(world, EcsWildcard, EcsName, "jecs.Wildcard") inner_world_set(world, EcsWildcard, EcsName, "jecs.Wildcard")
inner_world_set(world, EcsChildOf, EcsName, "jecs.ChildOf") inner_world_set(world, EcsChildOf, EcsName, "jecs.ChildOf")
inner_world_set(world, EcsComponent, EcsName, "jecs.Component") inner_world_set(world, EcsComponent, EcsName, "jecs.Component")
inner_world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete") inner_world_set(world, EcsOnDelete, EcsName, "jecs.OnDelete")
inner_world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget") inner_world_set(world, EcsOnDeleteTarget, EcsName, "jecs.OnDeleteTarget")
inner_world_set(world, EcsDelete, EcsName, "jecs.Delete") inner_world_set(world, EcsDelete, EcsName, "jecs.Delete")
inner_world_set(world, EcsRemove, EcsName, "jecs.Remove") inner_world_set(world, EcsRemove, EcsName, "jecs.Remove")
inner_world_set(world, EcsName, EcsName, "jecs.Name") inner_world_set(world, EcsName, EcsName, "jecs.Name")
@ -3048,6 +3063,9 @@ local function world_new()
inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete)) inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
inner_world_add(world, EcsChildOf, EcsExclusive) inner_world_add(world, EcsChildOf, EcsExclusive)
inner_world_add(world, EcsOnDelete, EcsExclusive)
inner_world_add(world, EcsOnDeleteTarget, EcsExclusive)
for i = EcsRest + 1, ecs_max_tag_id do for i = EcsRest + 1, ecs_max_tag_id do
entity_index_new_id(entity_index) entity_index_new_id(entity_index)
end end
@ -3121,14 +3139,6 @@ return {
pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>, pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
-- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO,
ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION,
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
ECS_ID_DELETE = ECS_ID_DELETE,
ECS_META_RESET = ECS_META_RESET,
IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean, IS_PAIR = (ECS_IS_PAIR :: any) :: <P, O>(pair: Pair<P, O>) -> boolean,
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>, ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Pair<P, O>) -> Id<P>,
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>, ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Pair<P, O>) -> Id<O>,
@ -3136,6 +3146,17 @@ return {
pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>, pair_second = (ecs_pair_second :: any) :: <P, O>(world: World, pair: Pair<P, O>) -> Id<O>,
entity_index_get_alive = entity_index_get_alive, entity_index_get_alive = entity_index_get_alive,
-- Inwards facing API for testing
ECS_ID = ECS_ENTITY_T_LO,
ECS_GENERATION_INC = ECS_GENERATION_INC,
ECS_GENERATION = ECS_GENERATION,
ECS_ID_IS_WILDCARD = ECS_ID_IS_WILDCARD,
ECS_ID_IS_EXCLUSIVE = ECS_ID_IS_EXCLUSIVE,
ECS_ID_DELETE = ECS_ID_DELETE,
ECS_META_RESET = ECS_META_RESET,
ECS_COMBINE = ECS_COMBINE,
ECS_ENTITY_MASK = ECS_ENTITY_MASK,
archetype_append_to_records = archetype_append_to_records, archetype_append_to_records = archetype_append_to_records,
id_record_ensure = id_record_ensure, id_record_ensure = id_record_ensure,
component_record = id_record_get, component_record = id_record_get,

View file

@ -1,6 +1,6 @@
{ {
"name": "@rbxts/jecs", "name": "@rbxts/jecs",
"version": "0.8.3", "version": "0.9.0-rc.3",
"description": "Stupidly fast Entity Component System", "description": "Stupidly fast Entity Component System",
"main": "jecs.luau", "main": "jecs.luau",
"repository": { "repository": {

View file

@ -24,6 +24,38 @@ type Id<T=unknown> = jecs.Id<T>
local entity_visualiser = require("@tools/entity_visualiser") local entity_visualiser = require("@tools/entity_visualiser")
local dwi = entity_visualiser.stringify local dwi = entity_visualiser.stringify
TEST("ardi", function()
local world = jecs.world()
local r = world:entity()
world:add(r, jecs.pair(jecs.OnDelete, jecs.Delete))
local e = world:entity()
local e1 = world:entity()
world:add(e, jecs.pair(r, e1))
world:delete(r)
CHECK(not world:contains(e))
end)
TEST("dai", function()
local world = jecs.world()
local C = world:component()
world:set(C, jecs.Name, "C")
CHECK(world:get(C, jecs.Name) == "C")
world:entity(2000)
CHECK(world:get(C, jecs.Name) == "C")
end)
TEST("another axen banger", function()
-- taken from jecs.luau
local world = jecs.world()
world:range(2000, 3000)
local e0v1_id = jecs.ECS_COMBINE(1000, 1) -- id can be both within or outside the world's range
local e0v1 = world:entity(e0v1_id)
assert(world:contains(e0v1)) -- fails
end)
TEST("Ensure archetype edges get cleaned", function() TEST("Ensure archetype edges get cleaned", function()
local A = jecs.component() local A = jecs.component()
local B = jecs.component() local B = jecs.component()
@ -129,6 +161,7 @@ TEST("repeated pairs", function()
CHECK(count == 1) CHECK(count == 1)
CHECK(world:each(p2)() == e2) -- Fails CHECK(world:each(p2)() == e2) -- Fails
end) end)
TEST("repro", function() TEST("repro", function()
local world = jecs.world() local world = jecs.world()
local data = world:component() local data = world:component()
@ -157,6 +190,7 @@ TEST("repro", function()
end end
CHECK(count == 1) CHECK(count == 1)
count = 0 count = 0
print("----")
world:add(e2v1, jecs.pair(relation, e1v1)) world:add(e2v1, jecs.pair(relation, e1v1))
CHECK(world:has(e2v1, jecs.pair(relation, e1v1))) CHECK(world:has(e2v1, jecs.pair(relation, e1v1)))
@ -164,6 +198,7 @@ TEST("repro", function()
count += 1 count += 1
end end
print(count)
CHECK(count==1) CHECK(count==1)
end) end)
TEST("bulk", function() TEST("bulk", function()
@ -311,23 +346,12 @@ TEST("world:add()", function()
world:add(A, jecs.Exclusive) world:add(A, jecs.Exclusive)
local on_remove_call = false local on_remove_call = false
world:set(A, jecs.OnRemove, function(e, id) world:set(A, jecs.OnRemove, function(e, id)
CHECK(e == e_ptr)
CHECK(id == jecs.pair(A, B))
on_remove_call = true on_remove_call = true
end) end)
local on_add_call_count = 0 local on_add_call_count = 0
world:set(A, jecs.OnAdd, function(e, id) world:set(A, jecs.OnAdd, function(e, id)
on_add_call_count += 1 on_add_call_count += 1
if on_add_call_count == 1 then
CHECK(e == e_ptr)
CHECK(id == jecs.pair(A, B))
elseif on_add_call_count == 2 then
CHECK(e == e_ptr)
CHECK(id == jecs.pair(A, C))
else
CHECK(false)
end
end) end)
@ -342,6 +366,17 @@ TEST("world:add()", function()
CHECK(world:has(e, pair(A, B)) == false) CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true) CHECK(world:has(e, pair(A, C)) == true)
-- We have to ensure that it actually invokes hooks everytime it
-- traverses the archetype
e = world:entity()
world:add(e, pair(A, B))
CHECK(on_add_call_count == 3)
world:add(e, pair(A, C))
CHECK(on_add_call_count == 4)
CHECK(on_remove_call)
CHECK(world:has(e, pair(A, B)) == false)
CHECK(world:has(e, pair(A, C)) == true)
end end
do CASE "idempotent" do CASE "idempotent"
@ -599,9 +634,9 @@ TEST("world:delete()", function()
world:add(e1, ct) world:add(e1, ct)
world:add(e2, jecs.pair(ct, dummy)) world:add(e2, jecs.pair(ct, dummy))
world:delete(dummy) -- world:delete(dummy)
CHECK(world:contains(e2)) -- CHECK(world:contains(e2))
world:delete(ct) world:delete(ct)

View file

@ -1,6 +1,6 @@
[package] [package]
name = "ukendio/jecs" name = "ukendio/jecs"
version = "0.8.3" version = "0.9.0-rc.3"
registry = "https://github.com/UpliftGames/wally-index" registry = "https://github.com/UpliftGames/wally-index"
realm = "shared" realm = "shared"
license = "MIT" license = "MIT"