mirror of
https://github.com/Ukendio/jecs.git
synced 2025-07-06 14:49:17 +00:00
Add exclusive relations (#250)
* Add exclusive relationship * Remove focus * Remove whitespace * Make ChildOf exclusive * Test exclusive relation perf * Inline into world:add * Inline into world:set * Fix benchmark of remove
This commit is contained in:
parent
b425150b0c
commit
155d51a080
4 changed files with 307 additions and 70 deletions
|
@ -4,15 +4,17 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||||
local jecs = require(ReplicatedStorage.Lib)
|
local jecs = require(ReplicatedStorage.Lib:Clone())
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
local mirror = require(ReplicatedStorage.mirror)
|
local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
local mcs = mirror.world()
|
local mcs = mirror.world()
|
||||||
|
|
||||||
local C1 = ecs:component()
|
local C1 = ecs:component()
|
||||||
local C2 = ecs:entity()
|
local C2 = ecs:entity()
|
||||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
|
ecs:add(C2, jecs.Exclusive)
|
||||||
|
|
||||||
local C3 = ecs:entity()
|
local C3 = ecs:entity()
|
||||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
|
||||||
local C4 = ecs:entity()
|
local C4 = ecs:entity()
|
||||||
|
@ -32,17 +34,18 @@ return {
|
||||||
Functions = {
|
Functions = {
|
||||||
Mirror = function()
|
Mirror = function()
|
||||||
local m = mcs:entity()
|
local m = mcs:entity()
|
||||||
for i = 1, 1000 do
|
for i = 1, 100 do
|
||||||
mcs:add(m, E3)
|
mcs:add(m, pair(E2, E3))
|
||||||
mcs:remove(m, E3)
|
mcs:remove(m, pair(E2, E3))
|
||||||
|
mcs:add(m, pair(E2, E4))
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function()
|
Jecs = function()
|
||||||
local j = ecs:entity()
|
local j = ecs:entity()
|
||||||
for i = 1, 1000 do
|
for i = 1, 100 do
|
||||||
ecs:add(j, C3)
|
ecs:add(j, pair(C2, C3))
|
||||||
ecs:remove(j, C3)
|
ecs:add(j, pair(C2, C4))
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
167
jecs.luau
167
jecs.luau
|
@ -196,9 +196,10 @@ local ECS_ENTITY_MASK = bit32.lshift(1, 24)
|
||||||
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
local ECS_GENERATION_MASK = bit32.lshift(1, 16)
|
||||||
local ECS_PAIR_OFFSET = 2^48
|
local ECS_PAIR_OFFSET = 2^48
|
||||||
|
|
||||||
local ECS_ID_DELETE = 0b01
|
local ECS_ID_DELETE = 0b0001
|
||||||
local ECS_ID_IS_TAG = 0b10
|
local ECS_ID_IS_TAG = 0b0010
|
||||||
local ECS_ID_MASK = 0b00
|
local ECS_ID_IS_EXCLUSIVE = 0b0100
|
||||||
|
local ECS_ID_MASK = 0b0000
|
||||||
|
|
||||||
local HI_COMPONENT_ID = 256
|
local HI_COMPONENT_ID = 256
|
||||||
local EcsOnAdd = HI_COMPONENT_ID + 1
|
local EcsOnAdd = HI_COMPONENT_ID + 1
|
||||||
|
@ -214,7 +215,8 @@ local EcsRemove = HI_COMPONENT_ID + 10
|
||||||
local EcsName = HI_COMPONENT_ID + 11
|
local EcsName = HI_COMPONENT_ID + 11
|
||||||
local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
|
local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
|
||||||
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
|
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
|
||||||
local EcsRest = HI_COMPONENT_ID + 14
|
local EcsExclusive = HI_COMPONENT_ID + 14
|
||||||
|
local EcsRest = HI_COMPONENT_ID + 15
|
||||||
|
|
||||||
local NULL_ARRAY = table.freeze({}) :: Column
|
local NULL_ARRAY = table.freeze({}) :: Column
|
||||||
local NULL = newproxy(false)
|
local NULL = newproxy(false)
|
||||||
|
@ -319,9 +321,9 @@ end
|
||||||
|
|
||||||
local function entity_index_try_get_any(
|
local function entity_index_try_get_any(
|
||||||
entity_index: EntityIndex,
|
entity_index: EntityIndex,
|
||||||
entity: number
|
entity: Entity
|
||||||
): Record?
|
): Record?
|
||||||
local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
|
local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity::number)]
|
||||||
|
|
||||||
if not r or r.dense == 0 then
|
if not r or r.dense == 0 then
|
||||||
return nil
|
return nil
|
||||||
|
@ -344,6 +346,20 @@ local function entity_index_try_get(entity_index: EntityIndex, entity: Entity):
|
||||||
return r
|
return r
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function entity_index_try_get_fast(entity_index: EntityIndex, entity: Entity): Record?
|
||||||
|
local r = entity_index_try_get_any(entity_index, entity)
|
||||||
|
if r then
|
||||||
|
local r_dense = r.dense
|
||||||
|
-- if r_dense > entity_index.alive_count then
|
||||||
|
-- return nil
|
||||||
|
-- end
|
||||||
|
if entity_index.dense_array[r_dense] ~= entity then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
local function entity_index_is_alive<T>(entity_index: EntityIndex, entity: Entity<T>): boolean
|
local function entity_index_is_alive<T>(entity_index: EntityIndex, entity: Entity<T>): boolean
|
||||||
return entity_index_try_get(entity_index, entity) ~= nil
|
return entity_index_try_get(entity_index, entity) ~= nil
|
||||||
end
|
end
|
||||||
|
@ -683,6 +699,7 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
|
||||||
local is_pair = ECS_IS_PAIR(id :: number)
|
local is_pair = ECS_IS_PAIR(id :: number)
|
||||||
|
|
||||||
local has_delete = false
|
local has_delete = false
|
||||||
|
local is_exclusive = false
|
||||||
|
|
||||||
if is_pair then
|
if is_pair then
|
||||||
relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id :: number)) :: i53
|
relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id :: number)) :: i53
|
||||||
|
@ -697,6 +714,10 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
|
||||||
if cleanup_policy_target == EcsDelete then
|
if cleanup_policy_target == EcsDelete then
|
||||||
has_delete = true
|
has_delete = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if world_has_one_inline(world, relation, EcsExclusive) then
|
||||||
|
is_exclusive = true
|
||||||
|
end
|
||||||
else
|
else
|
||||||
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
|
local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
|
||||||
|
|
||||||
|
@ -718,7 +739,8 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
|
||||||
flags = bit32.bor(
|
flags = bit32.bor(
|
||||||
flags,
|
flags,
|
||||||
if has_delete then ECS_ID_DELETE else 0,
|
if has_delete then ECS_ID_DELETE else 0,
|
||||||
if is_tag then ECS_ID_IS_TAG else 0
|
if is_tag then ECS_ID_IS_TAG else 0,
|
||||||
|
if is_exclusive then ECS_ID_IS_EXCLUSIVE else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
idr = {
|
idr = {
|
||||||
|
@ -929,9 +951,10 @@ end
|
||||||
|
|
||||||
local function find_archetype_with(world: World, id: Id, from: Archetype): Archetype
|
local function find_archetype_with(world: World, id: Id, from: Archetype): Archetype
|
||||||
local id_types = from.types
|
local id_types = from.types
|
||||||
|
local dst = table.clone(id_types)
|
||||||
|
|
||||||
local at = find_insert(id_types :: { number } , id :: number)
|
local at = find_insert(id_types :: { number } , id :: number)
|
||||||
local dst = table.clone(id_types)
|
|
||||||
table.insert(dst, at, id)
|
table.insert(dst, at, id)
|
||||||
|
|
||||||
return archetype_ensure(world, dst)
|
return archetype_ensure(world, dst)
|
||||||
|
@ -967,8 +990,6 @@ local function world_component(world: World): i53
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function archetype_fast_delete_last(columns: { Column }, column_count: number)
|
local function archetype_fast_delete_last(columns: { Column }, column_count: number)
|
||||||
for i, column in columns do
|
for i, column in columns do
|
||||||
if column ~= NULL_ARRAY then
|
if column ~= NULL_ARRAY then
|
||||||
|
@ -2207,6 +2228,59 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local from = record.archetype
|
local from = record.archetype
|
||||||
|
if ECS_IS_PAIR(id::number) then
|
||||||
|
local src = from or ROOT_ARCHETYPE
|
||||||
|
local edge = archetype_edges[src.id]
|
||||||
|
local to = edge[id]
|
||||||
|
local idr: ComponentRecord
|
||||||
|
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.hooks.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
|
||||||
|
else
|
||||||
|
idr = component_index[id]
|
||||||
|
end
|
||||||
|
if from == to then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if from then
|
||||||
|
entity_move(entity_index, entity, record, to)
|
||||||
|
else
|
||||||
|
if #to.types > 0 then
|
||||||
|
new_entity(entity, record, to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local on_add = idr.hooks.on_add
|
||||||
|
|
||||||
|
if on_add then
|
||||||
|
on_add(entity, id)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
local to = archetype_traverse_add(world, id, from)
|
local to = archetype_traverse_add(world, id, from)
|
||||||
if from == to then
|
if from == to then
|
||||||
return
|
return
|
||||||
|
@ -2219,7 +2293,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local idr = world.component_index[id]
|
local idr = component_index[id]
|
||||||
local on_add = idr.hooks.on_add
|
local on_add = idr.hooks.on_add
|
||||||
|
|
||||||
if on_add then
|
if on_add then
|
||||||
|
@ -2348,6 +2422,74 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local from: Archetype = record.archetype
|
local from: Archetype = record.archetype
|
||||||
|
if ECS_IS_PAIR(id::number) then
|
||||||
|
local src = from or ROOT_ARCHETYPE
|
||||||
|
local edge = archetype_edges[src.id]
|
||||||
|
local to = edge[id]
|
||||||
|
local idr: ComponentRecord
|
||||||
|
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.hooks.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
|
||||||
|
else
|
||||||
|
idr = component_index[id]
|
||||||
|
end
|
||||||
|
local idr_hooks = idr.hooks
|
||||||
|
if from == to then
|
||||||
|
local column = to.columns_map[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_hooks.on_change
|
||||||
|
if on_change then
|
||||||
|
on_change(entity, id, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if from then
|
||||||
|
entity_move(entity_index, entity, record, to)
|
||||||
|
else
|
||||||
|
if #to.types > 0 then
|
||||||
|
new_entity(entity, record, to)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local column = to.columns_map[id]
|
||||||
|
column[record.row] = data
|
||||||
|
|
||||||
|
local on_add = idr.hooks.on_add
|
||||||
|
|
||||||
|
if on_add then
|
||||||
|
on_add(entity, id)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
local to: Archetype = inner_archetype_traverse_add(id, from)
|
local to: Archetype = inner_archetype_traverse_add(id, from)
|
||||||
local idr = component_index[id]
|
local idr = component_index[id]
|
||||||
local idr_hooks = idr.hooks
|
local idr_hooks = idr.hooks
|
||||||
|
@ -2563,7 +2705,6 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inner_world_delete<T>(world: World, entity: Entity<T>)
|
local function inner_world_delete<T>(world: World, entity: Entity<T>)
|
||||||
|
@ -2850,6 +2991,7 @@ local function world_new()
|
||||||
inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
|
inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
|
||||||
|
|
||||||
inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
|
||||||
|
inner_world_add(world, EcsChildOf, 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)
|
||||||
|
@ -2913,6 +3055,7 @@ return {
|
||||||
Delete = (EcsDelete :: any) :: Entity,
|
Delete = (EcsDelete :: any) :: Entity,
|
||||||
Remove = (EcsRemove :: any) :: Entity,
|
Remove = (EcsRemove :: any) :: Entity,
|
||||||
Name = (EcsName :: any) :: Entity<string>,
|
Name = (EcsName :: any) :: Entity<string>,
|
||||||
|
Exclusive = EcsExclusive :: Entity,
|
||||||
Rest = (EcsRest :: any) :: Entity,
|
Rest = (EcsRest :: any) :: Entity,
|
||||||
|
|
||||||
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>,
|
||||||
|
|
109
mirror.luau
109
mirror.luau
|
@ -42,8 +42,18 @@ export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||||
export type Query<T...> = typeof(setmetatable(
|
export type Query<T...> = typeof(setmetatable(
|
||||||
{} :: {
|
{} :: {
|
||||||
iter: Iter<T...>,
|
iter: Iter<T...>,
|
||||||
with: (self: Query<T...>, ...Id) -> Query<T...>,
|
with:
|
||||||
without: (self: Query<T...>, ...Id) -> Query<T...>,
|
(<a>(Query<T...>, Id<a>) -> Query<T...>)
|
||||||
|
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||||
|
without:
|
||||||
|
(<a>(Query<T...>, Id<a>) -> Query<T...>)
|
||||||
|
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
|
||||||
|
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
},
|
},
|
||||||
|
@ -439,6 +449,7 @@ end
|
||||||
|
|
||||||
local function archetype_move(
|
local function archetype_move(
|
||||||
entity_index: EntityIndex,
|
entity_index: EntityIndex,
|
||||||
|
entity: Entity,
|
||||||
to: Archetype,
|
to: Archetype,
|
||||||
dst_row: i24,
|
dst_row: i24,
|
||||||
from: Archetype,
|
from: Archetype,
|
||||||
|
@ -452,48 +463,58 @@ local function archetype_move(
|
||||||
local id_types = from.types
|
local id_types = from.types
|
||||||
local columns_map = to.columns_map
|
local columns_map = to.columns_map
|
||||||
|
|
||||||
for i, column in src_columns do
|
if src_row ~= last then
|
||||||
if column == NULL_ARRAY then
|
|
||||||
continue
|
|
||||||
end
|
|
||||||
-- Retrieves the new column index from the source archetype's record from each component
|
|
||||||
-- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
|
|
||||||
local dst_column = columns_map[id_types[i]]
|
|
||||||
|
|
||||||
-- Sometimes target column may not exist, e.g. when you remove a component.
|
|
||||||
if dst_column then
|
|
||||||
dst_column[dst_row] = column[src_row]
|
|
||||||
end
|
|
||||||
|
|
||||||
-- If the entity is the last row in the archetype then swapping it would be meaningless.
|
-- If the entity is the last row in the archetype then swapping it would be meaningless.
|
||||||
if src_row ~= last then
|
|
||||||
|
for i, column in src_columns do
|
||||||
|
if column == NULL_ARRAY then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
-- Retrieves the new column index from the source archetype's record from each component
|
||||||
|
-- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
|
||||||
|
local dst_column = columns_map[id_types[i]]
|
||||||
|
|
||||||
|
-- Sometimes target column may not exist, e.g. when you remove a component.
|
||||||
|
if dst_column then
|
||||||
|
dst_column[dst_row] = column[src_row]
|
||||||
|
end
|
||||||
|
|
||||||
-- Swap rempves columns to ensure there are no holes in the archetype.
|
-- Swap rempves columns to ensure there are no holes in the archetype.
|
||||||
column[src_row] = column[last]
|
column[src_row] = column[last]
|
||||||
|
column[last] = nil
|
||||||
end
|
end
|
||||||
column[last] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
local moved = #src_entities
|
|
||||||
|
|
||||||
-- Move the entity from the source to the destination archetype.
|
-- Move the entity from the source to the destination archetype.
|
||||||
-- Because we have swapped columns we now have to update the records
|
-- Because we have swapped columns we now have to update the records
|
||||||
-- corresponding to the entities' rows that were swapped.
|
-- corresponding to the entities' rows that were swapped.
|
||||||
local e1 = src_entities[src_row]
|
|
||||||
local e2 = src_entities[moved]
|
|
||||||
|
|
||||||
if src_row ~= moved then
|
local e2 = src_entities[last]
|
||||||
src_entities[src_row] = e2
|
src_entities[src_row] = e2
|
||||||
|
|
||||||
|
local sparse_array = entity_index.sparse_array
|
||||||
|
local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)]
|
||||||
|
record2.row = src_row
|
||||||
|
else
|
||||||
|
for i, column in src_columns do
|
||||||
|
if column == NULL_ARRAY then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
-- Retrieves the new column index from the source archetype's record from each component
|
||||||
|
-- We have to do this because the columns are tightly packed and indexes may not correspond to each other.
|
||||||
|
local dst_column = columns_map[id_types[i]]
|
||||||
|
|
||||||
|
-- Sometimes target column may not exist, e.g. when you remove a component.
|
||||||
|
if dst_column then
|
||||||
|
dst_column[dst_row] = column[src_row]
|
||||||
|
end
|
||||||
|
|
||||||
|
column[last] = nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
src_entities[moved] = nil :: any
|
src_entities[last] = nil :: any
|
||||||
dst_entities[dst_row] = e1
|
dst_entities[dst_row] = entity
|
||||||
|
|
||||||
local sparse_array = entity_index.sparse_array
|
|
||||||
|
|
||||||
local record1 = sparse_array[ECS_ENTITY_T_LO(e1 :: number)]
|
|
||||||
local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)]
|
|
||||||
record1.row = dst_row
|
|
||||||
record2.row = src_row
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetype_append(
|
local function archetype_append(
|
||||||
|
@ -526,7 +547,7 @@ local function entity_move(
|
||||||
local sourceRow = record.row
|
local sourceRow = record.row
|
||||||
local from = record.archetype
|
local from = record.archetype
|
||||||
local dst_row = archetype_append(entity, to)
|
local dst_row = archetype_append(entity, to)
|
||||||
archetype_move(entity_index, to, dst_row, from, sourceRow)
|
archetype_move(entity_index, entity, to, dst_row, from, sourceRow)
|
||||||
record.archetype = to
|
record.archetype = to
|
||||||
record.row = dst_row
|
record.row = dst_row
|
||||||
end
|
end
|
||||||
|
@ -744,7 +765,7 @@ local function archetype_register(world: World, archetype: Archetype)
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
for i, component_id in archetype.types do
|
for i, component_id in archetype.types do
|
||||||
local idr = id_record_ensure(world, component_id)
|
local idr = id_record_ensure(world, component_id)
|
||||||
local is_tag = bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
|
local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
||||||
local column = if is_tag then NULL_ARRAY else {}
|
local column = if is_tag then NULL_ARRAY else {}
|
||||||
columns[i] = column
|
columns[i] = column
|
||||||
|
|
||||||
|
@ -2394,7 +2415,7 @@ local function world_new()
|
||||||
return entity
|
return entity
|
||||||
else
|
else
|
||||||
for i = eindex_max_id + 1, index do
|
for i = eindex_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
|
||||||
entity_index.max_id = index
|
entity_index.max_id = index
|
||||||
|
@ -2459,8 +2480,8 @@ local function world_new()
|
||||||
local idr_archetype = archetypes[archetype_id]
|
local idr_archetype = archetypes[archetype_id]
|
||||||
local entities = idr_archetype.entities
|
local entities = idr_archetype.entities
|
||||||
local n = #entities
|
local n = #entities
|
||||||
|
table.move(entities, 1, n, count + 1, queue)
|
||||||
count += n
|
count += n
|
||||||
table.move(entities, 1, n, #queue + 1, queue)
|
|
||||||
end
|
end
|
||||||
for _, e in queue do
|
for _, e in queue do
|
||||||
inner_world_remove(world, e, entity)
|
inner_world_remove(world, e, entity)
|
||||||
|
@ -2572,7 +2593,7 @@ local function world_new()
|
||||||
|
|
||||||
if idr then
|
if idr then
|
||||||
local flags = idr.flags
|
local flags = idr.flags
|
||||||
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
|
if bit32.btest(flags, ECS_ID_DELETE) 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]
|
||||||
|
|
||||||
|
@ -2647,8 +2668,8 @@ local function world_new()
|
||||||
end
|
end
|
||||||
local id_record = component_index[id]
|
local id_record = component_index[id]
|
||||||
local flags = id_record.flags
|
local flags = id_record.flags
|
||||||
local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
|
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
||||||
if flags_delete_mask ~= 0 then
|
if flags_delete_mask then
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local child = entities[i]
|
local child = entities[i]
|
||||||
inner_world_delete(world, child)
|
inner_world_delete(world, child)
|
||||||
|
@ -2690,7 +2711,7 @@ 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.band(flags, ECS_ID_DELETE) :: number) ~= 0 then
|
if bit32.btest(flags, ECS_ID_DELETE) 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
|
||||||
|
@ -2868,7 +2889,7 @@ end
|
||||||
local function ecs_is_tag(world: World, entity: Entity): boolean
|
local function ecs_is_tag(world: World, entity: Entity): boolean
|
||||||
local idr = world.component_index[entity]
|
local idr = world.component_index[entity]
|
||||||
if idr then
|
if idr then
|
||||||
return bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
|
return bit32.btest(idr.flags, ECS_ID_IS_TAG)
|
||||||
end
|
end
|
||||||
return not world_has_one_inline(world, entity, EcsComponent)
|
return not world_has_one_inline(world, entity, EcsComponent)
|
||||||
end
|
end
|
||||||
|
@ -2877,7 +2898,7 @@ return {
|
||||||
world = world_new :: () -> World,
|
world = world_new :: () -> World,
|
||||||
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
|
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
|
||||||
tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
|
tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
|
||||||
meta = (ECS_META :: any) :: <T>(id: Entity, id: Id<T>, value: T) -> Entity<T>,
|
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
|
||||||
is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
|
is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
|
||||||
|
|
||||||
OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
|
||||||
|
|
|
@ -142,6 +142,66 @@ TEST("repro", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:add()", function()
|
TEST("world:add()", function()
|
||||||
|
do CASE "exclusive relations"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "exclusive relations invoke hooks"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
local e_ptr = jecs.Rest :: number + 1
|
||||||
|
|
||||||
|
world:add(A, jecs.Exclusive)
|
||||||
|
local on_remove_call = false
|
||||||
|
world:set(A, jecs.OnRemove, function(e, id)
|
||||||
|
CHECK(e == e_ptr)
|
||||||
|
CHECK(id == jecs.pair(A, B))
|
||||||
|
on_remove_call = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local on_add_call_count = 0
|
||||||
|
world:set(A, jecs.OnAdd, function(e, id)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
CHECK(e == e_ptr)
|
||||||
|
world:add(e, pair(A, B))
|
||||||
|
CHECK(on_add_call_count == 1)
|
||||||
|
world:add(e, pair(A, C))
|
||||||
|
CHECK(on_add_call_count == 2)
|
||||||
|
CHECK(on_remove_call)
|
||||||
|
|
||||||
|
CHECK(world:has(e, pair(A, B)) == false)
|
||||||
|
CHECK(world:has(e, pair(A, C)) == true)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "idempotent"
|
do CASE "idempotent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local d = dwi(world)
|
local d = dwi(world)
|
||||||
|
@ -193,6 +253,9 @@ TEST("world:children()", function()
|
||||||
local e3 = world:entity()
|
local e3 = world:entity()
|
||||||
world:add(e3, pair(ChildOf, e1))
|
world:add(e3, pair(ChildOf, e1))
|
||||||
|
|
||||||
|
CHECK(world:has(e2, pair(ChildOf, e1)))
|
||||||
|
CHECK(world:has(e3, pair(ChildOf, e1)))
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
for entity in world:children(e1) do
|
for entity in world:children(e1) do
|
||||||
count += 1
|
count += 1
|
||||||
|
@ -1669,7 +1732,9 @@ end)
|
||||||
TEST("#repro2", function()
|
TEST("#repro2", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local Lifetime = world:component() :: Id<number>
|
local Lifetime = world:component() :: Id<number>
|
||||||
|
world:set(Lifetime, jecs.Name, "Lifetime")
|
||||||
local Particle = world:entity()
|
local Particle = world:entity()
|
||||||
|
world:set(Particle, jecs.Name, "Particle")
|
||||||
local Beam = world:entity()
|
local Beam = world:entity()
|
||||||
|
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
|
@ -1677,19 +1742,24 @@ TEST("#repro2", function()
|
||||||
world:set(entity, pair(Lifetime, Beam), 2)
|
world:set(entity, pair(Lifetime, Beam), 2)
|
||||||
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
|
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
|
||||||
|
|
||||||
|
CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
|
||||||
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
|
||||||
|
|
||||||
|
CHECK(world:target(entity, Lifetime, 0) == Particle)
|
||||||
|
CHECK(world:target(entity, Lifetime, 1) == Beam)
|
||||||
|
|
||||||
-- entity_visualizer.components(world, entity)
|
-- entity_visualizer.components(world, entity)
|
||||||
|
|
||||||
|
-- print(CHECK(world:has(jecs.ChildOf, jecs.Exclusive)))
|
||||||
|
|
||||||
for e in world:each(pair(Lifetime, __)) do
|
for e in world:each(pair(Lifetime, __)) do
|
||||||
local i = 0
|
local i = 0
|
||||||
local nth = world:target(e, Lifetime, i)
|
local nth = world:target(e, Lifetime, i)
|
||||||
while nth do
|
while nth do
|
||||||
-- entity_visualizer.components(world, e)
|
-- entity_visualizer.components(world, e)
|
||||||
|
|
||||||
local data = world:get(e, pair(Lifetime, nth)) :: number
|
local data = world:get(e, pair(Lifetime, nth)) :: number
|
||||||
data -= 1
|
if data > 0 then
|
||||||
if data <= 0 then
|
data -= 1
|
||||||
world:remove(e, pair(Lifetime, nth))
|
|
||||||
else
|
|
||||||
world:set(e, pair(Lifetime, nth), data)
|
world:set(e, pair(Lifetime, nth), data)
|
||||||
end
|
end
|
||||||
i += 1
|
i += 1
|
||||||
|
@ -1697,7 +1767,7 @@ TEST("#repro2", function()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
CHECK(not world:has(entity, pair(Lifetime, Particle)))
|
CHECK(world:get(entity, pair(Lifetime, Particle)) == 0)
|
||||||
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue