mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-14 04:29:18 +00:00
Remove eagerly
This commit is contained in:
parent
037035a9a1
commit
917c951d55
2 changed files with 153 additions and 73 deletions
127
jecs.luau
127
jecs.luau
|
@ -54,6 +54,8 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
archetypes: (self: Query<T...>) -> { Archetype },
|
archetypes: (self: Query<T...>) -> { Archetype },
|
||||||
cached: (self: Query<T...>) -> Query<T...>,
|
cached: (self: Query<T...>) -> Query<T...>,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
|
filter_with: { Id<any> }?,
|
||||||
|
filter_without: { Id<any> }?
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
|
@ -92,13 +94,14 @@ type archetype = {
|
||||||
}
|
}
|
||||||
|
|
||||||
type componentrecord = {
|
type componentrecord = {
|
||||||
|
component: i53,
|
||||||
records: { [number]: number },
|
records: { [number]: number },
|
||||||
counts: { [i53]: number },
|
counts: { [i53]: number },
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
|
||||||
on_add: ((entity: i53, id: i53, value: any?) -> ())?,
|
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
on_change: ((entity: i53, id: i53, value: any) -> ())?,
|
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
on_remove: ((entity: i53, id: i53) -> ())?,
|
on_remove: ((entity: i53, id: i53) -> ())?,
|
||||||
|
|
||||||
wildcard_pairs: { [number]: componentrecord },
|
wildcard_pairs: { [number]: componentrecord },
|
||||||
|
@ -166,9 +169,9 @@ export type World = {
|
||||||
|
|
||||||
observable: Map<Id, Map<Id, { Observer }>>,
|
observable: Map<Id, Map<Id, { Observer }>>,
|
||||||
|
|
||||||
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
added: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
||||||
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
removed: <T>(World, Entity<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
||||||
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T) -> ()) -> () -> (),
|
changed: <T>(World, Entity<T>, <e>(e: Entity<e>, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
||||||
|
|
||||||
--- Enforce a check on entities to be created within desired range
|
--- Enforce a check on entities to be created within desired range
|
||||||
range: (self: World, range_begin: number, range_end: number?) -> (),
|
range: (self: World, range_begin: number, range_end: number?) -> (),
|
||||||
|
@ -262,8 +265,8 @@ export type ComponentRecord = {
|
||||||
flags: number,
|
flags: number,
|
||||||
size: number,
|
size: number,
|
||||||
|
|
||||||
on_add: (<T>(entity: Entity, id: Entity<T>, value: T?) -> ())?,
|
on_add: (<T>(entity: Entity, id: Entity<T>, value: T, oldarchetype: Archetype) -> ())?,
|
||||||
on_change: (<T>(entity: Entity, id: Entity<T>, value: T) -> ())?,
|
on_change: (<T>(entity: Entity, id: Entity<T>, value: T, oldArchetype: Archetype) -> ())?,
|
||||||
on_remove: ((entity: Entity, id: Entity) -> ())?,
|
on_remove: ((entity: Entity, id: Entity) -> ())?,
|
||||||
}
|
}
|
||||||
export type ComponentIndex = Map<Id, ComponentRecord>
|
export type ComponentIndex = Map<Id, ComponentRecord>
|
||||||
|
@ -924,14 +927,6 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
|
||||||
|
|
||||||
idr_t.size += 1
|
idr_t.size += 1
|
||||||
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
|
||||||
|
|
||||||
-- Hypothetically this should only capture leaf component records
|
|
||||||
local idr_t_wc_pairs = idr_t.wildcard_pairs
|
|
||||||
if not idr_t_wc_pairs then
|
|
||||||
idr_t_wc_pairs = {} :: {[i53]: componentrecord }
|
|
||||||
idr_t.wildcard_pairs = idr_t_wc_pairs
|
|
||||||
end
|
|
||||||
idr_t_wc_pairs[component_id] = idr
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2058,6 +2053,7 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
local dst_types = ids
|
local dst_types = ids
|
||||||
local to = archetype_ensure(world, dst_types)
|
local to = archetype_ensure(world, dst_types)
|
||||||
new_entity(entity, r, to)
|
new_entity(entity, r, to)
|
||||||
|
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
||||||
for i, id in ids do
|
for i, id in ids do
|
||||||
local value = values[i]
|
local value = values[i]
|
||||||
local cdr = component_index[id]
|
local cdr = component_index[id]
|
||||||
|
@ -2066,11 +2062,11 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
if value then
|
if value then
|
||||||
r.archetype.columns_map[id][r.row] = value
|
r.archetype.columns_map[id][r.row] = value
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, value :: any)
|
on_add(entity, id, value, ROOT_ARCHETYPE)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id)
|
on_add(entity, id, nil, ROOT_ARCHETYPE)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2112,10 +2108,10 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
local on_change = idr.on_change
|
local on_change = idr.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, from)
|
||||||
end
|
end
|
||||||
elseif on_add then
|
elseif on_add then
|
||||||
on_add(entity, id)
|
on_add(entity, id, nil, from)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2306,7 +2302,6 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
local function inner_entity_move(
|
local function inner_entity_move(
|
||||||
entity_index: entityindex,
|
|
||||||
entity: i53,
|
entity: i53,
|
||||||
record: record,
|
record: record,
|
||||||
to: archetype
|
to: archetype
|
||||||
|
@ -2380,7 +2375,7 @@ local function world_new()
|
||||||
-- and just set the data directly.
|
-- and just set the data directly.
|
||||||
local on_change = idr.on_change
|
local on_change = idr.on_change
|
||||||
if on_change then
|
if on_change then
|
||||||
on_change(entity, id, data)
|
on_change(entity, id, data, src)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local to: archetype
|
local to: archetype
|
||||||
|
@ -2450,7 +2445,7 @@ local function world_new()
|
||||||
|
|
||||||
if from then
|
if from then
|
||||||
-- If there was a previous archetype, then the entity needs to move the archetype
|
-- 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, record, to)
|
||||||
else
|
else
|
||||||
new_entity(entity, record, to)
|
new_entity(entity, record, to)
|
||||||
end
|
end
|
||||||
|
@ -2460,7 +2455,7 @@ local function world_new()
|
||||||
|
|
||||||
local on_add = idr.on_add
|
local on_add = idr.on_add
|
||||||
if on_add then
|
if on_add then
|
||||||
on_add(entity, id, data)
|
on_add(entity, id, data, src)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2470,7 +2465,6 @@ local function world_new()
|
||||||
entity: i53,
|
entity: i53,
|
||||||
id: i53
|
id: i53
|
||||||
): ()
|
): ()
|
||||||
local entity_index = world.entity_index
|
|
||||||
local record = entity_index_try_get_unsafe(entity :: number)
|
local record = entity_index_try_get_unsafe(entity :: number)
|
||||||
if not record then
|
if not record then
|
||||||
return
|
return
|
||||||
|
@ -2549,7 +2543,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
|
|
||||||
if from then
|
if from then
|
||||||
inner_entity_move(entity_index, entity, record, to)
|
inner_entity_move(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)
|
||||||
|
@ -2559,7 +2553,7 @@ local function world_new()
|
||||||
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, nil, src)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2593,7 +2587,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type Listener<T> = (e: i53, id: i53, value: T?) -> ()
|
type Listener<T> = (e: i53, id: i53, value: T, oldarchetype: archetype) -> ()
|
||||||
|
|
||||||
world.added = function<T>(_: world, component: i53, fn: Listener<T>)
|
world.added = function<T>(_: world, component: i53, fn: Listener<T>)
|
||||||
local listeners = signals.added[component]
|
local listeners = signals.added[component]
|
||||||
|
@ -2601,9 +2595,9 @@ local function world_new()
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.added[component] = listeners
|
signals.added[component] = listeners
|
||||||
|
|
||||||
local function on_add(entity, id, value)
|
local function on_add(entity, id, value, oldarchetype)
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
for _, listener in listeners :: { Listener<T> } do
|
||||||
listener(entity, id, value)
|
listener(entity, id, value, oldarchetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local existing_hook = world_get(world, component, EcsOnAdd) :: Listener<T>
|
local existing_hook = world_get(world, component, EcsOnAdd) :: Listener<T>
|
||||||
|
@ -2644,9 +2638,9 @@ local function world_new()
|
||||||
if not listeners then
|
if not listeners then
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.changed[component] = listeners
|
signals.changed[component] = listeners
|
||||||
local function on_change(entity, id, value: any)
|
local function on_change(entity, id, value, oldarchetype)
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
for _, listener in listeners :: { Listener<T> } do
|
||||||
listener(entity, id, value)
|
listener(entity, id, value, oldarchetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local existing_hook = world_get(world, component, EcsOnChange) :: Listener<T>
|
local existing_hook = world_get(world, component, EcsOnChange) :: Listener<T>
|
||||||
|
@ -2686,7 +2680,7 @@ local function world_new()
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.removed[component] = listeners
|
signals.removed[component] = listeners
|
||||||
local function on_remove(entity, id)
|
local function on_remove(entity, id)
|
||||||
for _, listener in listeners :: { Listener<T> } do
|
for _, listener in listeners :: { (...any) -> () } do
|
||||||
listener(entity, id)
|
listener(entity, id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -2881,7 +2875,7 @@ local function world_new()
|
||||||
|
|
||||||
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)
|
inner_entity_move(entity, record, to)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2945,7 +2939,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)
|
inner_entity_move(e, r, to)
|
||||||
end
|
end
|
||||||
|
|
||||||
archetype_destroy(world, idr_archetype)
|
archetype_destroy(world, idr_archetype)
|
||||||
|
@ -2967,53 +2961,41 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if idr_t then
|
if idr_t then
|
||||||
for id, cr in idr_t.wildcard_pairs do
|
local archetype_ids = idr_t.records
|
||||||
local flags = cr.flags
|
for archetype_id in archetype_ids do
|
||||||
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
local idr_t_archetype = archetypes[archetype_id]
|
||||||
local on_remove = cr.on_remove
|
local idr_t_types = idr_t_archetype.types
|
||||||
if flags_delete_mask then
|
local entities = idr_t_archetype.entities
|
||||||
for archetype_id in cr.records do
|
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
for _, id in idr_t_types do
|
||||||
local entities = idr_t_archetype.entities
|
if not ECS_IS_PAIR(id) then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local object = entity_index_get_alive(
|
||||||
|
entity_index, ECS_PAIR_SECOND(id))
|
||||||
|
if object ~= entity then
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
local id_record = component_index[id]
|
||||||
|
local flags = id_record.flags
|
||||||
|
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
|
||||||
|
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]
|
||||||
world_delete(world, child)
|
world_delete(world, child)
|
||||||
end
|
end
|
||||||
end
|
break
|
||||||
else
|
else
|
||||||
for archetype_id in cr.records do
|
|
||||||
local idr_t_archetype = archetypes[archetype_id]
|
|
||||||
local entities = idr_t_archetype.entities
|
|
||||||
-- archetype_traverse_remove is not idempotent meaning
|
|
||||||
-- this access is actually unsafe because it can
|
|
||||||
-- incorrectly cache an edge despite a node of the
|
|
||||||
-- component id on the archetype does not exist. This
|
|
||||||
-- requires careful testing to ensure correct values are
|
|
||||||
-- being passed to the arguments.
|
|
||||||
local to = archetype_traverse_remove(world, id, idr_t_archetype)
|
|
||||||
|
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local e = entities[i]
|
local child = entities[i]
|
||||||
local r = eindex_sparse_array[ECS_ID(e :: number)]
|
world_remove(world, child, id)
|
||||||
if on_remove then
|
|
||||||
on_remove(e, id)
|
|
||||||
|
|
||||||
local from = r.archetype
|
|
||||||
if from ~= idr_t_archetype then
|
|
||||||
-- unfortunately the on_remove hook allows a window where `e` can have changed archetype
|
|
||||||
-- this is hypothetically not that expensive of an operation anyways
|
|
||||||
to = archetype_traverse_remove(world, id, from)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
inner_entity_move(entity_index, e, r, to)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
for archetype_id in cr.records do
|
for archetype_id in archetype_ids do
|
||||||
archetype_destroy(world, archetypes[archetype_id])
|
archetype_destroy(world, archetypes[archetype_id])
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3055,7 +3037,7 @@ local function world_new()
|
||||||
for i = #entities, 1, -1 do
|
for i = #entities, 1, -1 do
|
||||||
local e = entities[i]
|
local e = entities[i]
|
||||||
local r = entity_index_try_get_unsafe(e) :: record
|
local r = entity_index_try_get_unsafe(e) :: record
|
||||||
inner_entity_move(entity_index, e, r, node)
|
inner_entity_move(e, r, node)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -3065,6 +3047,7 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -24,6 +24,104 @@ 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
|
||||||
|
|
||||||
|
SKIP()
|
||||||
|
TEST("jecs delete", function()
|
||||||
|
do CASE "delete children"
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
|
local Health = world:component()
|
||||||
|
local Poison = world:component()
|
||||||
|
local FriendsWith = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, Poison, 5)
|
||||||
|
world:set(e, Health, 50)
|
||||||
|
|
||||||
|
local children = {}
|
||||||
|
for i = 1, 10 do
|
||||||
|
local child = world:entity()
|
||||||
|
world:set(child, Poison, 9999)
|
||||||
|
world:set(child, Health, 100)
|
||||||
|
world:add(child, pair(jecs.ChildOf, e))
|
||||||
|
table.insert(children, child)
|
||||||
|
end
|
||||||
|
|
||||||
|
BENCH("delete children of entity", function()
|
||||||
|
world:delete(e)
|
||||||
|
end)
|
||||||
|
|
||||||
|
for i, child in children do
|
||||||
|
CHECK(not world:contains(child))
|
||||||
|
CHECK(not world:has(child, pair(jecs.ChildOf, e)))
|
||||||
|
CHECK(not world:has(child, Health))
|
||||||
|
end
|
||||||
|
|
||||||
|
e = world:entity()
|
||||||
|
|
||||||
|
local friends = {}
|
||||||
|
for i = 1, 10 do
|
||||||
|
local friend = world:entity()
|
||||||
|
world:set(friend, Poison, 9999)
|
||||||
|
world:set(friend, Health, 100)
|
||||||
|
world:add(friend, pair(FriendsWith, e))
|
||||||
|
for j = 1, 10 do
|
||||||
|
world:add(friend, world:component())
|
||||||
|
end
|
||||||
|
table.insert(friends, friend)
|
||||||
|
end
|
||||||
|
|
||||||
|
BENCH("remove friends of entity", function()
|
||||||
|
world:delete(e)
|
||||||
|
end)
|
||||||
|
|
||||||
|
for i, friend in friends do
|
||||||
|
CHECK(not world:has(friend, pair(FriendsWith, e)))
|
||||||
|
CHECK(world:has(friend, Health))
|
||||||
|
CHECK(world:contains(friend))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("pepe", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
local t = world:entity()
|
||||||
|
local c = world:component()
|
||||||
|
world:add(c, t)
|
||||||
|
|
||||||
|
local component = world:component()
|
||||||
|
local lifetime = world:component()
|
||||||
|
|
||||||
|
local tag = world:entity()
|
||||||
|
local rel1 = world:entity()
|
||||||
|
local rel2 = world:entity()
|
||||||
|
local rel3 = world:entity()
|
||||||
|
|
||||||
|
local destroyed = false
|
||||||
|
|
||||||
|
world:removed(lifetime, function(e)
|
||||||
|
destroyed = true
|
||||||
|
end)
|
||||||
|
|
||||||
|
local parent = world:entity()
|
||||||
|
world:set(parent, component, "foo")
|
||||||
|
world:add(parent, jecs.pair(rel1, component))
|
||||||
|
|
||||||
|
local other1 = world:entity()
|
||||||
|
world:add(other1, tag)
|
||||||
|
world:add(other1, jecs.pair(jecs.ChildOf, parent))
|
||||||
|
world:add(other1, jecs.pair(rel1, component))
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:set(child, lifetime, "")
|
||||||
|
world:add(child, jecs.pair(jecs.ChildOf, parent))
|
||||||
|
world:add(child, jecs.pair(rel3, parent))
|
||||||
|
world:add(child, jecs.pair(rel2, other1))
|
||||||
|
|
||||||
|
world:delete(parent)
|
||||||
|
|
||||||
|
CHECK(destroyed)
|
||||||
|
CHECK(not world:contains(child))
|
||||||
|
end)
|
||||||
|
|
||||||
TEST("ardi", function()
|
TEST("ardi", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
@ -856,7 +954,6 @@ TEST("world:delete()", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
local C = world:component()
|
|
||||||
world:set(A, jecs.OnRemove, function(entity, id)
|
world:set(A, jecs.OnRemove, function(entity, id)
|
||||||
world:set(entity, B, true)
|
world:set(entity, B, true)
|
||||||
end)
|
end)
|
||||||
|
|
Loading…
Reference in a new issue