Inline into world:set

This commit is contained in:
Ukendio 2025-07-04 00:10:28 +02:00
parent a40bfab47b
commit 8b7ddf5c46
4 changed files with 156 additions and 54 deletions

View file

@ -21,7 +21,6 @@ local C4 = ecs:entity()
ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete)) ecs:add(C4, pair(jecs.OnDeleteTarget, jecs.Delete))
local E1 = mcs:component() local E1 = mcs:component()
local E2 = mcs:entity() local E2 = mcs:entity()
mcs:add(E2, mirror.Exclusive)
mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete)) mcs:add(E2, pair(jecs.OnDeleteTarget, jecs.Delete))
local E3 = mcs:entity() local E3 = mcs:entity()
mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete)) mcs:add(E3, pair(jecs.OnDeleteTarget, jecs.Delete))
@ -35,17 +34,17 @@ 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, pair(E2, E3)) mcs:add(m, pair(E4, E4))
mcs:add(m, pair(E2, E4)) mcs:add(m, pair(E4, 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, pair(C2, C3)) ecs:add(j, pair(C4, C4))
ecs:add(j, pair(C2, C4)) ecs:add(j, pair(C4, C4))
end end
end, end,
}, },

View file

@ -2269,7 +2269,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
@ -2398,6 +2398,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

View file

@ -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...>,
}, },
@ -186,10 +196,9 @@ 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 = 0b0001 local ECS_ID_DELETE = 0b01
local ECS_ID_IS_TAG = 0b0010 local ECS_ID_IS_TAG = 0b10
local ECS_ID_IS_EXCLUSIVE = 0b0100 local ECS_ID_MASK = 0b00
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
@ -205,8 +214,7 @@ 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 EcsExclusive = HI_COMPONENT_ID + 14 local EcsRest = 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)
@ -675,7 +683,6 @@ 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
@ -690,10 +697,6 @@ 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)
@ -715,8 +718,7 @@ 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 = {
@ -927,22 +929,9 @@ 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)
if ECS_IS_PAIR(id::number) then
local first = ECS_PAIR_FIRST(id::number)
local idr = world.component_index[ECS_PAIR(first, EcsWildcard)]
if idr and bit32.btest(idr.flags, EcsExclusive) then
local cr = idr.records[from.id]
if cr then
dst[cr] = id
return archetype_ensure(world, dst)
end
end
end
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)
@ -2426,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
@ -2909,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) -> ()>,
@ -2924,7 +2913,6 @@ 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>,

View file

@ -25,19 +25,6 @@ local entity_visualiser = require("@tools/entity_visualiser")
local dwi = entity_visualiser.stringify local dwi = entity_visualiser.stringify
TEST("exclusive", function() TEST("exclusive", function()
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)
-- print(jecs.entity_index_try_get(world.entity_index, e).archetype.type) -- print(jecs.entity_index_try_get(world.entity_index, e).archetype.type)
end) end)
@ -159,6 +146,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)