Compare commits

..

1 commit

Author SHA1 Message Date
Clown
30d2e2604c
Merge 96bed9bd7e into 8fd32978b4 2025-07-04 23:34:03 +03:00
6 changed files with 252 additions and 336 deletions

View file

@ -1,62 +1,42 @@
--!strict
local jecs = require("@jecs") local jecs = require("@jecs")
export type PatchedWorld = jecs.World & { export type PatchedWorld = jecs.World & {
added: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T?) -> ()) -> () -> (), added: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (), removed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id) -> ()) -> () -> (),
changed: <T>(PatchedWorld, jecs.Id<T>, <e>(e: jecs.Entity<e>, id: jecs.Id<T>, value: T) -> ()) -> () -> (), changed: <T>(PatchedWorld, jecs.Id<T>, (e: jecs.Entity, id: jecs.Id, value: T) -> ()) -> () -> (),
observer: <T...>( observer: (
PatchedWorld, PatchedWorld,
jecs.Query<T...>, any,
(<a>(jecs.Entity, jecs.Id<a>, a) -> ())? (jecs.Entity) -> ()
) -> () -> (jecs.Entity), ) -> (),
monitor: ( monitor: (
PatchedWorld, PatchedWorld,
any, any,
(<a>(jecs.Entity, jecs.Id<a>) -> ())? (jecs.Entity, jecs.Id) -> ()
) -> () ) -> ()
} }
local function observers_new( local function observers_new(world, query, callback)
world: PatchedWorld, local terms = query.filter_with :: { jecs.Id }
query: any, if not terms then
callback: (<T, a>(jecs.Entity<T>, jecs.Id<a>, value: a?) -> ())? local ids = query.ids
) query.filter_with = ids
query = query:cached() terms = ids
local archetypes = {}
local terms = query.ids
local first = terms[1]
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
observers_on_create[#observers_on_create].callback = function(archetype)
archetypes[archetype.id] = true
end
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
observers_on_delete[#observers_on_delete].callback = function(archetype)
archetypes[archetype.id] = nil
end end
local entity_index = world.entity_index :: any local entity_index = world.entity_index :: any
local i = 0 local function emplaced(entity, id, value)
local entities = {}
local function emplaced<T, a>(
entity: jecs.Entity<T>,
id: jecs.Id<a>,
value: a?
)
local r = jecs.entity_index_try_get_fast( local r = jecs.entity_index_try_get_fast(
entity_index, entity :: any) :: jecs.Record entity_index, entity :: any)
if not r then
return
end
local archetype = r.archetype local archetype = r.archetype
if archetypes[archetype.id] then if jecs.query_match(query, archetype) then
i += 1 callback(entity)
entities[i] = entity
if callback ~= nil then
callback(entity, id, value)
end
end end
end end
@ -64,19 +44,6 @@ local function observers_new(
world:added(term, emplaced) world:added(term, emplaced)
world:changed(term, emplaced) world:changed(term, emplaced)
end end
return function()
local row = i
return function()
if row == 0 then
i = 0
table.clear(entities)
end
local entity = entities[row]
row -= 1
return entity
end
end
end end
local function join(world, component) local function join(world, component)
@ -124,47 +91,41 @@ local function join(world, component)
end end
local function monitors_new(world, query, callback) local function monitors_new(world, query, callback)
query = query:cached() local terms = query.filter_with :: { jecs.Id }
if not terms then
local archetypes = {} local ids = query.ids
local terms = query.ids query.filter_with = ids
local first = terms[1] terms = ids
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
observers_on_create[#observers_on_create].callback = function(archetype)
archetypes[archetype.id] = true
end
local observers_on_delete = world.observable[jecs.ArchetypeDelete][first]
observers_on_delete[#observers_on_delete].callback = function(archetype)
archetypes[archetype.id] = nil
end end
local entity_index = world.entity_index :: any local entity_index = world.entity_index :: any
local i = 0 local function emplaced(entity: jecs.Entity)
local entities = {}
local function emplaced<T, a>(
entity: jecs.Entity<T>,
id: jecs.Id<a>,
value: a?
)
local r = jecs.entity_index_try_get_fast( local r = jecs.entity_index_try_get_fast(
entity_index, entity :: any) :: jecs.Record entity_index, entity :: any)
if not r then
return
end
local archetype = r.archetype local archetype = r.archetype
if archetypes[archetype.id] then if jecs.query_match(query, archetype) then
i += 1 callback(entity, jecs.OnAdd)
entities[i] = entity
if callback ~= nil then
callback(entity, id, value)
end
end end
end end
local function removed(entity: jecs.Entity, component: jecs.Id) local function removed(entity: jecs.Entity, component: jecs.Id)
local EcsOnRemove = jecs.OnRemove :: jecs.Id local r = jecs.entity_index_try_get_fast(
if callback ~= nil then entity_index, entity :: any)
if not r then
return
end
local archetype = r.archetype
if jecs.query_match(query, archetype) then
local EcsOnRemove = jecs.OnRemove :: jecs.Id
callback(entity, EcsOnRemove) callback(entity, EcsOnRemove)
end end
end end
@ -173,19 +134,6 @@ local function monitors_new(world, query, callback)
world:added(term, emplaced) world:added(term, emplaced)
world:removed(term, removed) world:removed(term, removed)
end end
return function()
local row = i
return function()
if row == 0 then
i = 0
table.clear(entities)
end
local entity = entities[row]
row -= 1
return entity
end
end
end end
local function observers_add(world: jecs.World): PatchedWorld local function observers_add(world: jecs.World): PatchedWorld
@ -221,7 +169,7 @@ local function observers_add(world: jecs.World): PatchedWorld
local idr = world.component_index[component] local idr = world.component_index[component]
if idr then if idr then
idr.on_add = on_add idr.hooks.on_add = on_add
else else
world:set(component, jecs.OnAdd, on_add) world:set(component, jecs.OnAdd, on_add)
end end
@ -255,7 +203,7 @@ local function observers_add(world: jecs.World): PatchedWorld
end end
local idr = world.component_index[component] local idr = world.component_index[component]
if idr then if idr then
idr.on_change = on_change idr.hooks.on_change = on_change
else else
world:set(component, jecs.OnChange, on_change) world:set(component, jecs.OnChange, on_change)
end end
@ -290,7 +238,7 @@ local function observers_add(world: jecs.World): PatchedWorld
local idr = world.component_index[component] local idr = world.component_index[component]
if idr then if idr then
idr.on_remove = on_remove idr.hooks.on_remove = on_remove
else else
world:set(component, jecs.OnRemove, on_remove) world:set(component, jecs.OnRemove, on_remove)
end end

1
jecs.d.ts vendored
View file

@ -310,7 +310,6 @@ export declare const OnDeleteTarget: Tag;
export declare const Delete: Tag; export declare const Delete: Tag;
export declare const Remove: Tag; export declare const Remove: Tag;
export declare const Name: Entity<string>; export declare const Name: Entity<string>;
export declare const Exclusive: Tag;
export declare const Rest: Entity; export declare const Rest: Entity;
export type ComponentRecord = { export type ComponentRecord = {

411
jecs.luau
View file

@ -42,12 +42,14 @@ 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: (<a>(Query<T...>, Id<a>) -> Query<T...>) with:
(<a>(Query<T...>, Id<a>) -> Query<T...>)
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> 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>(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...>), & (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
without: (<a>(Query<T...>, Id<a>) -> Query<T...>) without:
(<a>(Query<T...>, Id<a>) -> Query<T...>)
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> 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>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>) & (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
@ -170,10 +172,11 @@ export type ComponentRecord = {
counts: { [Id]: number }, counts: { [Id]: number },
flags: number, flags: number,
size: number, size: number,
hooks: {
on_add: (<T>(entity: Entity, id: Entity<T>, value: T?) -> ())?, on_add: (<T>(entity: Entity, id: Entity<T>, value: T?) -> ())?,
on_change: (<T>(entity: Entity, id: Entity<T>, value: T) -> ())?, on_change: (<T>(entity: Entity, id: Entity<T>, value: T) -> ())?,
on_remove: ((entity: Entity, id: Entity) -> ())?, on_remove: ((entity: Entity, id: Entity) -> ())?,
},
} }
export type ComponentIndex = Map<Id, ComponentRecord> export type ComponentIndex = Map<Id, ComponentRecord>
export type Archetypes = { [Id]: Archetype } export type Archetypes = { [Id]: Archetype }
@ -193,10 +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 = 0b0001 local ECS_ID_DELETE = 0b0001
local ECS_ID_IS_TAG = 0b0010 local ECS_ID_IS_TAG = 0b0010
local ECS_ID_IS_EXCLUSIVE = 0b0100 local ECS_ID_IS_EXCLUSIVE = 0b0100
local ECS_ID_MASK = 0b0000 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
@ -745,10 +748,11 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
records = {}, records = {},
counts = {}, counts = {},
flags = flags, flags = flags,
hooks = {
on_add = on_add, on_add = on_add,
on_change = on_change, on_change = on_change,
on_remove = on_remove, on_remove = on_remove,
},
} :: ComponentRecord } :: ComponentRecord
component_index[id] = idr component_index[id] = idr
@ -1027,7 +1031,7 @@ local function archetype_delete(world: World, archetype: Archetype, row: number)
for _, id in id_types do for _, id in id_types do
local idr = component_index[id] local idr = component_index[id]
local on_remove = idr.on_remove local on_remove = idr.hooks.on_remove
if on_remove then if on_remove then
on_remove(delete, id) on_remove(delete, id)
end end
@ -1061,13 +1065,6 @@ local function archetype_destroy(world: World, archetype: Archetype)
local columns_map = archetype.columns_map local columns_map = archetype.columns_map
for id in columns_map do for id in columns_map do
local idr = component_index[id]
idr.records[archetype_id] = nil :: any
idr.counts[archetype_id] = nil
idr.size -= 1
if idr.size == 0 then
component_index[id] = nil :: any
end
local observer_list = find_observers(world, EcsOnArchetypeDelete, id) local observer_list = find_observers(world, EcsOnArchetypeDelete, id)
if not observer_list then if not observer_list then
continue continue
@ -1078,6 +1075,16 @@ local function archetype_destroy(world: World, archetype: Archetype)
end end
end end
end end
for id in columns_map do
local idr = component_index[id]
idr.records[archetype_id] = nil :: any
idr.counts[archetype_id] = nil
idr.size -= 1
if idr.size == 0 then
component_index[id] = nil :: any
end
end
end end
local function NOOP() end local function NOOP() end
@ -2023,7 +2030,7 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, va
local value = values[i] local value = values[i]
local cdr = component_index[id] local cdr = component_index[id]
local on_add = cdr.on_add local on_add = cdr.hooks.on_add
if value then if value then
columns_map[id][row] = value columns_map[id][row] = value
if on_add then if on_add then
@ -2068,11 +2075,11 @@ local function ecs_bulk_insert(world: World, entity: Entity, ids: { Entity }, va
local value = values[i] :: any local value = values[i] :: any
local on_add = idr.on_add local on_add = idr.hooks.on_add
if value ~= nil then if value ~= nil then
columns_map[id][row] = value columns_map[id][row] = value
local on_change = idr.on_change local on_change = idr.hooks.on_change
local hook = if set then on_change else on_add local hook = if set then on_change else on_add
if hook then if hook then
hook(entity, id, value :: any) hook(entity, id, value :: any)
@ -2107,7 +2114,7 @@ local function ecs_bulk_remove(world: World, entity: Entity, ids: { Entity })
remove[id] = true remove[id] = true
local idr = component_index[id] local idr = component_index[id]
local on_remove = idr.on_remove local on_remove = idr.hooks.on_remove
if on_remove then if on_remove then
on_remove(entity, id) on_remove(entity, id)
end end
@ -2174,83 +2181,13 @@ local function world_new()
local function inner_entity_index_try_get_any(entity: number): Record? local function inner_entity_index_try_get_any(entity: number): Record?
local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)] local r = eindex_sparse_array[ECS_ENTITY_T_LO(entity)]
if not r then if not r or r.dense == 0 then
return nil return nil
end end
return r return r
end end
local function inner_archetype_move(
entity: Entity,
to: Archetype,
dst_row: i24,
from: Archetype,
src_row: i24
)
local src_columns = from.columns
local dst_entities = to.entities
local src_entities = from.entities
local last = #src_entities
local id_types = from.types
local columns_map = to.columns_map
if src_row ~= last then
for i, column in src_columns do
if column == NULL_ARRAY then
continue
end
local dst_column = columns_map[id_types[i]]
if dst_column then
dst_column[dst_row] = column[src_row]
end
column[src_row] = column[last]
column[last] = nil
end
local e2 = src_entities[last]
src_entities[src_row] = e2
local record2 = eindex_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
src_entities[last] = nil :: any
dst_entities[dst_row] = entity
end
local function inner_entity_move(
entity_index: EntityIndex,
entity: Entity,
record: Record,
to: Archetype
)
local sourceRow = record.row
local from = record.archetype
local dst_row = archetype_append(entity, to)
inner_archetype_move(entity, to, dst_row, from, sourceRow)
record.archetype = to
record.row = dst_row
end
-- local function inner_entity_index_try_get(entity: number): Record? -- local function inner_entity_index_try_get(entity: number): Record?
-- local r = inner_entity_index_try_get_any(entity) -- local r = inner_entity_index_try_get_any(entity)
-- if r then -- if r then
@ -2303,7 +2240,7 @@ local function world_new()
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then if idr and 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.hooks.on_remove
local id_types = src.types local id_types = src.types
if on_remove then if on_remove then
on_remove(entity, id_types[cr]) on_remove(entity, id_types[cr])
@ -2330,14 +2267,14 @@ local function world_new()
return return
end end
if from then if from then
inner_entity_move(entity_index, entity, record, to) entity_move(entity_index, entity, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
new_entity(entity, record, to) new_entity(entity, record, to)
end end
end end
local on_add = idr.on_add local on_add = idr.hooks.on_add
if on_add then if on_add then
on_add(entity, id) on_add(entity, id)
@ -2349,7 +2286,7 @@ local function world_new()
return return
end end
if from then if from then
inner_entity_move(entity_index, entity, record, to) entity_move(entity_index, entity, record, to)
else else
if #to.types > 0 then if #to.types > 0 then
new_entity(entity, record, to) new_entity(entity, record, to)
@ -2357,7 +2294,7 @@ local function world_new()
end end
local idr = component_index[id] local idr = component_index[id]
local on_add = idr.on_add local on_add = idr.hooks.on_add
if on_add then if on_add then
on_add(entity, id) on_add(entity, id)
@ -2485,73 +2422,104 @@ local function world_new()
end end
local from: Archetype = record.archetype local from: Archetype = record.archetype
local src = from or ROOT_ARCHETYPE if ECS_IS_PAIR(id::number) then
local column = src.columns_map[id] local src = from or ROOT_ARCHETYPE
if column then local edge = archetype_edges[src.id]
local idr = component_index[id] local to = edge[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 ECS_IS_PAIR(id::number) then if not to then
local edge = archetype_edges[src.id] local first = ECS_PAIR_FIRST(id::number)
to = edge[id] local wc = ECS_PAIR(first, EcsWildcard)
if not to then idr = component_index[wc]
local first = ECS_PAIR_FIRST(id::number) if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
local wc = ECS_PAIR(first, EcsWildcard) local cr = idr.records[src.id]
idr = component_index[wc] if cr then
if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then local on_remove = idr.hooks.on_remove
local cr = idr.records[src.id] local id_types = src.types
if cr then if on_remove then
local on_remove = idr.on_remove on_remove(entity, id_types[cr])
local id_types = src.types src = record.archetype
if on_remove then id_types = src.types
on_remove(entity, id_types[cr]) cr = idr.records[src.id]
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 end
local dst = table.clone(id_types)
dst[cr] = id
to = archetype_ensure(world, dst)
else else
to = find_archetype_with(world, id, src) to = find_archetype_with(world, id, src)
idr = component_index[id] idr = component_index[id]
end end
edge[id] = to
else else
to = find_archetype_with(world, id, src)
idr = component_index[id] idr = component_index[id]
end end
edge[id] = to
else else
to = inner_archetype_traverse_add(id, from)
idr = component_index[id] idr = component_index[id]
end 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 if from then
-- If there was a previous archetype, then the entity needs to move the archetype entity_move(entity_index, entity, record, to)
inner_entity_move(entity_index, entity, record, to)
else else
new_entity(entity, record, to) if #to.types > 0 then
new_entity(entity, record, to)
end
end end
column = to.columns_map[id] local column = to.columns_map[id]
column[record.row] = data column[record.row] = data
local on_add = idr.on_add local on_add = idr.hooks.on_add
if on_add then if on_add then
on_add(entity, id, data) on_add(entity, id)
end end
return
end
local to: Archetype = inner_archetype_traverse_add(id, from)
local idr = component_index[id]
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
-- If there was a previous archetype, then the entity needs to move the archetype
entity_move(entity_index, entity, record, to)
else
new_entity(entity, record, to)
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, data)
end end
end end
@ -2589,7 +2557,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
@ -2629,14 +2597,14 @@ local function world_new()
if from.columns_map[id] then if from.columns_map[id] then
local idr = world.component_index[id] local idr = world.component_index[id]
local on_remove = idr.on_remove local on_remove = idr.hooks.on_remove
if on_remove then if on_remove then
on_remove(entity, id) on_remove(entity, id)
end end
local to = archetype_traverse_remove(world, id, record.archetype) local to = archetype_traverse_remove(world, id, record.archetype)
inner_entity_move(entity_index, entity, record, to) entity_move(entity_index, entity, record, to)
end end
end end
@ -2663,13 +2631,16 @@ local function world_new()
end end
if idr_t then if idr_t then
local queue: { i53 }
local ids: Map<i53, boolean>
local count = 0
local archetype_ids = idr_t.records local archetype_ids = idr_t.records
for archetype_id in archetype_ids do for archetype_id in archetype_ids do
local idr_t_archetype = archetypes[archetype_id] local idr_t_archetype = archetypes[archetype_id]
local idr_t_types = idr_t_archetype.types local idr_t_types = idr_t_archetype.types
local entities = idr_t_archetype.entities local entities = idr_t_archetype.entities
local removal_queued = false
local node = idr_t_archetype
for _, id in idr_t_types do for _, id in idr_t_types do
if not ECS_IS_PAIR(id::number) then if not ECS_IS_PAIR(id::number) then
@ -2680,54 +2651,64 @@ local function world_new()
if object ~= entity then if object ~= entity then
continue continue
end end
node = archetype_traverse_remove(world, id, node) if not ids then
local on_remove = component_index[id].on_remove ids = {} :: { [i53]: boolean }
if on_remove then
for _, entity in entities do
on_remove(entity, id)
end
end end
ids[id] = true
removal_queued = true
end end
for i = #entities, 1, -1 do if not removal_queued then
local e = entities[i] continue
local r = inner_entity_index_try_get_unsafe(e::number) :: Record end
inner_entity_move(entity_index, e, r, node)
if not queue then
queue = {} :: { i53 }
end
local n = #entities
table.move(entities, 1, n, count + 1, queue)
count += n
end
for id in ids do
for _, child in queue do
inner_world_remove(world, child, id)
end end
end end
end end
if idr_r then if idr_r then
local count = 0
local archetype_ids = idr_r.records local archetype_ids = idr_r.records
local ids = {}
local queue = {}
local records = idr_r.records local records = idr_r.records
local counts = idr_r.counts local counts = idr_r.counts
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 node = idr_r_archetype
local entities = idr_r_archetype.entities local entities = idr_r_archetype.entities
local tr = records[archetype_id] local tr = records[archetype_id]
local tr_count = counts[archetype_id] local tr_count = counts[archetype_id]
local types = idr_r_archetype.types local types = idr_r_archetype.types
for i = tr, tr + tr_count - 1 do for i = tr, tr + tr_count - 1 do
local id = types[i] ids[types[i]] = true
node = archetype_traverse_remove(world, id, idr_r_archetype)
local on_remove = component_index[id].on_remove
if on_remove then
for _, entity in entities do
on_remove(entity, id)
end
end
end end
for i = #entities, 1, -1 do local n = #entities
local e = entities[i] table.move(entities, 1, n, count + 1, queue)
local r = inner_entity_index_try_get_unsafe(e::number) :: Record count += n
inner_entity_move(entity_index, e, r, node) end
for _, e in queue do
for id in ids do
inner_world_remove(world, e, id)
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>)
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
@ -2766,7 +2747,7 @@ local function world_new()
archetype_destroy(world, idr_archetype) archetype_destroy(world, idr_archetype)
end end
else else
local on_remove = idr.on_remove local on_remove = idr.hooks.on_remove
if on_remove then if on_remove 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]
@ -2783,7 +2764,7 @@ local function world_new()
-- this is hypothetically not that expensive of an operation anyways -- this is hypothetically not that expensive of an operation anyways
to = archetype_traverse_remove(world, entity, from) to = archetype_traverse_remove(world, entity, from)
end end
inner_entity_move(entity_index, e, r, to) entity_move(entity_index, e, r, to)
end end
archetype_destroy(world, idr_archetype) archetype_destroy(world, idr_archetype)
@ -2804,15 +2785,19 @@ local function world_new()
end end
end end
end end
if idr_t then if idr_t then
local children: { i53 }
local ids: Map<i53, boolean>
local count = 0
local archetype_ids = idr_t.records local archetype_ids = idr_t.records
for archetype_id in archetype_ids do for archetype_id in archetype_ids do
local idr_t_archetype = archetypes[archetype_id] local idr_t_archetype = archetypes[archetype_id]
local node = idr_t_archetype
local idr_t_types = idr_t_archetype.types local idr_t_types = idr_t_archetype.types
local entities = idr_t_archetype.entities local entities = idr_t_archetype.entities
local removal_queued = false
local deleted = false
for _, id in idr_t_types do for _, id in idr_t_types do
if not ECS_IS_PAIR(id::number) then if not ECS_IS_PAIR(id::number) then
continue continue
@ -2830,24 +2815,31 @@ local function world_new()
local child = entities[i] local child = entities[i]
inner_world_delete(world, child) inner_world_delete(world, child)
end end
deleted = true
break break
else else
node = archetype_traverse_remove(world, id, node) if not ids then
local on_remove = component_index[id].on_remove ids = {} :: { [i53]: boolean }
if on_remove then
for _, entity in entities do
on_remove(entity, id)
end
end end
ids[id] = true
removal_queued = true
end end
end end
if not deleted then if not removal_queued then
for i = #entities, 1, -1 do continue
local e = entities[i] end
local r = inner_entity_index_try_get_unsafe(e::number) :: Record if not children then
inner_entity_move(entity_index, e, r, node) children = {} :: { i53 }
end
local n = #entities
table.move(entities, 1, n, count + 1, children)
count += n
end
if ids then
for _, child in children do
for id in ids do
inner_world_remove(world, child, id)
end end
end end
end end
@ -2871,29 +2863,28 @@ local function world_new()
archetype_destroy(world, idr_r_archetype) archetype_destroy(world, idr_r_archetype)
end end
else else
local children = {}
local count = 0
local ids = {}
local counts = idr_r.counts local counts = idr_r.counts
local records = idr_r.records local records = idr_r.records
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 node = idr_r_archetype
local entities = idr_r_archetype.entities local entities = idr_r_archetype.entities
local tr = records[archetype_id] local tr = records[archetype_id]
local tr_count = counts[archetype_id] local tr_count = counts[archetype_id]
local types = idr_r_archetype.types local types = idr_r_archetype.types
for i = tr, tr + tr_count - 1 do for i = tr, tr + tr_count - 1 do
local id = types[i] ids[types[i]] = true
node = archetype_traverse_remove(world, id, node)
local on_remove = component_index[id].on_remove
if on_remove then
for _, entity in entities do
on_remove(entity, id)
end
end
end end
for i = #entities, 1, -1 do
local e = entities[i] local n = #entities
local r = inner_entity_index_try_get_unsafe(e::number) :: Record table.move(entities, 1, n, count + 1, children)
inner_entity_move(entity_index, e, r, node) count += n
end
for _, child in children do
for id in ids do
inner_world_remove(world, child, id)
end end
end end
@ -2903,19 +2894,21 @@ local function world_new()
end end
end end
local dense_array = entity_index.dense_array
local dense = record.dense local dense = record.dense
local i_swap = entity_index.alive_count local i_swap = entity_index.alive_count
entity_index.alive_count = i_swap - 1 entity_index.alive_count = i_swap - 1
local e_swap = eindex_dense_array[i_swap] local e_swap = dense_array[i_swap]
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
r_swap.dense = dense r_swap.dense = dense
record.archetype = nil :: any record.archetype = nil :: any
record.row = nil :: any record.row = nil :: any
record.dense = i_swap record.dense = i_swap
eindex_dense_array[dense] = e_swap dense_array[dense] = e_swap
eindex_dense_array[i_swap] = ECS_GENERATION_INC(entity :: number) dense_array[i_swap] = ECS_GENERATION_INC(entity :: number)
end end
local function inner_world_exists<T>(world: World, entity: Entity<T>): boolean local function inner_world_exists<T>(world: World, entity: Entity<T>): boolean
@ -3063,8 +3056,6 @@ return {
Remove = (EcsRemove :: any) :: Entity, Remove = (EcsRemove :: any) :: Entity,
Name = (EcsName :: any) :: Entity<string>, Name = (EcsName :: any) :: Entity<string>,
Exclusive = EcsExclusive :: Entity, Exclusive = EcsExclusive :: Entity,
ArchetypeCreate = EcsOnArchetypeCreate,
ArchetypeDelete = EcsOnArchetypeDelete,
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

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

View file

@ -659,6 +659,7 @@ TEST("world:delete()", function()
CHECK(not world:has(id1, Health)) CHECK(not world:has(id1, Health))
end end
do CASE "delete children" do CASE "delete children"
local world = jecs.world() local world = jecs.world()
@ -845,16 +846,6 @@ TEST("world:delete()", function()
CHECK(not world:contains(bob)) CHECK(not world:contains(bob))
CHECK(not world:contains(alice)) CHECK(not world:contains(alice))
end end
do CASE "deleted entity should not be able to be operated on"
local world = jecs.world()
local e = world:entity()
local A = world:component()
world:set(e, A, true)
world:delete(e)
world:set(e, A, true)
CHECK(world:has(e, A) == false)
end
end) end)
TEST("world:each()", function() TEST("world:each()", function()
@ -886,19 +877,6 @@ TEST("world:each()", function()
end) end)
TEST("world:range()", function() TEST("world:range()", function()
do CASE "delete outside partitioned range"
local server = jecs.world()
local client = jecs.world()
server:range(0, 1000)
client:range(1000, 5000)
local e1 = server:entity()
CHECK((e1::number)< 1000)
local e2 = client:entity(e1)
CHECK(e2 == e1)
client:delete(e1)
end
do CASE "under range start" do CASE "under range start"
local world = jecs.world() local world = jecs.world()
world:range(400, 1000) world:range(400, 1000)

View file

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