From 44534b7c8180b0966aac0ce69f8eb4dde0d02cd5 Mon Sep 17 00:00:00 2001 From: Ukendio Date: Sat, 1 Feb 2025 13:07:55 +0100 Subject: [PATCH] Improve deletion --- .../StarterPlayerScripts/systems/lol.luau | 67 ++++++++++++++ .../StarterPlayerScripts/systems/lol2.luau | 90 +++++++++++++++++++ examples/luau/queries/changetracking.luau | 31 ++++--- jecs.luau | 30 +++---- test/tests.luau | 15 ++++ 5 files changed, 199 insertions(+), 34 deletions(-) create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau create mode 100644 demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau new file mode 100644 index 00000000..75f9fa4e --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol.luau @@ -0,0 +1,67 @@ +--!optimize 2 +--!native +--!strict + +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jecs = require(ReplicatedStorage.ecs) +local __ = jecs.Wildcard +local std = ReplicatedStorage.std + +local world = require(std.world) + +local Position = world:component() :: jecs.Entity +local Previous = jecs.Rest +local pre = jecs.pair(Position, Previous) + +local added = world + :query(Position) + :without(pre) + :cached() +local changed = world + :query(Position, pre) + :cached() +local removed = world + :query(pre) + :without(Position) + :cached() + +local children = {} +for i = 1, 10 do + local e = world:entity() + world:set(e, Position, vector.create(i, i, i)) + table.insert(children, e) +end +local function flip() + return math.random() > 0.5 +end +local function system() + for i, child in children do + world:set(child, Position, vector.create(i,i,i)) + end + for e, p in added:iter() do + world:set(e, pre, p) + end + for i, child in children do + if flip() then + world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) + end + end + for e, new, old in changed:iter() do + if new ~= old then + world:set(e, pre, new) + end + end + + for i, child in children do + world:remove(child, Position) + end + + for e in removed:iter() do + world:remove(e, pre) + end +end +local scheduler = require(std.scheduler) + +scheduler.SYSTEM(system) + +return 0 diff --git a/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau new file mode 100644 index 00000000..3b40d08b --- /dev/null +++ b/demo/src/StarterPlayer/StarterPlayerScripts/systems/lol2.luau @@ -0,0 +1,90 @@ +--!optimize 2 +--!native +--!strict + +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local jecs = require(ReplicatedStorage.ecs) +local __ = jecs.Wildcard +local std = ReplicatedStorage.std + +local world = require(std.world) + +local Position = world:component() :: jecs.Entity +local Previous = jecs.Rest +local pre = jecs.pair(Position, Previous) + +local added = world + :query(Position) + :without(pre) + :cached() +local changed = world + :query(Position, pre) + :cached() +local removed = world + :query(pre) + :without(Position) + :cached() + +local children = {} +for i = 1, 10 do + local e = world:entity() + world:set(e, Position, vector.create(i, i, i)) + table.insert(children, e) +end +local function flip() + return math.random() > 0.5 +end +local entity_index = world.entity_index +local function copy(archetypes, id) + for _, archetype in archetypes do + + local to = jecs.archetype_traverse_add(world, pre, archetype) + local columns = to.columns + local records = to.records + local old = columns[records[pre].column] + local new = columns[records[id].column] + + if to ~= archetype then + for _, entity in archetype.entities do + local r = jecs.entity_index_try_get_fast(entity_index, entity) + jecs.entity_move(entity_index, entity, r, to) + end + end + + table.move(new, 1, #new, 1, old) + + end +end +local function system2() + for i, child in children do + world:set(child, Position, vector.create(i,i,i)) + end + for e, p in added:iter() do + end + copy(added:archetypes(), Position) + for i, child in children do + if flip() then + world:set(child, Position, vector.create(i + 1, i + 1, i + 1)) + end + end + + for e, new, old in changed:iter() do + if new ~= old then + end + end + + copy(changed:archetypes(), Position) + + for i, child in children do + world:remove(child, Position) + end + + for e in removed:iter() do + world:remove(e, pre) + end +end +local scheduler = require(std.scheduler) + +scheduler.SYSTEM(system2) + +return 0 diff --git a/examples/luau/queries/changetracking.luau b/examples/luau/queries/changetracking.luau index bcaa0328..be2ba6c0 100644 --- a/examples/luau/queries/changetracking.luau +++ b/examples/luau/queries/changetracking.luau @@ -1,4 +1,5 @@ local jecs = require("@jecs") +local pair = jecs.pair local world = jecs.World.new() local Name = world:component() @@ -12,39 +13,36 @@ local function name(e) return world:get(e, Name) end -local Position = named(world.component, "Position") :: jecs.Entity +local Position = named(world.component, "Position") :: jecs.Entity local Previous = jecs.Rest -local PreviousPosition = jecs.pair(Previous, Position) local added = world :query(Position) - :without(PreviousPosition) + :without(pair(Previous, Position)) :cached() local changed = world - :query(Position, PreviousPosition) + :query(Position, pair(Previous, Position)) :cached() local removed = world - :query(PreviousPosition) + :query(pair(Previous, Position)) :without(Position) :cached() local e1 = named(world.entity, "e1") -world:set(e1, Position, Vector3.new(10, 20, 30)) - +world:set(e1, Position, vector.create(10, 20, 30)) local e2 = named(world.entity, "e2") -world:set(e2, Position, Vector3.new(10, 20, 30)) - -for e, p in added:iter() do - print(`Added {e}: \{{p.X}, {p.Y}, {p.Z}}`) - world:set(e, PreviousPosition, p) +world:set(e2, Position, vector.create(10, 20, 30)) +for entity, p in added do + print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`) + world:set(entity, pair(Previous, Position), p) end -world:set(e1, Position, "") +world:set(e1, Position, vector.create(999, 999, 1998)) -for e, new, old in changed:iter() do +for e, new, old in changed do if new ~= old then - print(`{name(new)}'s Position changed from \{{old.X}, {old.Y}, {old.Z}\} to \{{new.X}, {new.Y}, {new.Z}\}`) - world:set(e, PreviousPosition, new) + print(`{name(e)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`) + world:set(e, pair(Previous, Position), new) end end @@ -52,6 +50,7 @@ world:remove(e2, Position) for e in removed:iter() do print(`Position was removed from {name(e)}`) + world:remove(e, pair(Previous, Position)) end -- Output: diff --git a/jecs.luau b/jecs.luau index c0b4cf33..6ad5fb09 100644 --- a/jecs.luau +++ b/jecs.luau @@ -1144,6 +1144,9 @@ do end end + local sparse_array = entity_index.sparse_array + local dense_array = entity_index.dense_array + if idr_t then for archetype_id in idr_t.cache do local children = {} @@ -1174,24 +1177,14 @@ do else local on_remove = id_record.hooks.on_remove local to = archetype_traverse_remove(world, id, idr_t_archetype) - if on_remove then - if to then - for i = n, 1, -1 do - local child = children[i] - on_remove(children[i]) - local r = entity_index_try_get_fast(entity_index, child) :: Record - entity_move(entity_index, child, r, to) - end - else - for i = n, 1, -1 do - local child = children[i] - on_remove(child) - end + local empty = #to.types == 0 + for i = n, 1, -1 do + local child = children[i] + if on_remove then + on_remove(child) end - elseif to then - for i = n, 1, -1 do - local child = children[i] - local r = entity_index_try_get_fast(entity_index, child) :: Record + local r = sparse_array[ECS_ENTITY_T_LO(child)] + if not empty then entity_move(entity_index, child, r, to) end end @@ -1203,7 +1196,6 @@ do end end - local dense_array = entity_index.dense_array local index_of_deleted_entity = record.dense local index_of_last_alive_entity = entity_index.alive_count entity_index.alive_count = index_of_last_alive_entity - 1 @@ -2362,6 +2354,8 @@ return { archetype_traverse_add = archetype_traverse_add, archetype_traverse_remove = archetype_traverse_remove, + entity_move = entity_move, + entity_index_try_get = entity_index_try_get, entity_index_try_get_any = entity_index_try_get_any, entity_index_try_get_fast = entity_index_try_get_fast, diff --git a/test/tests.luau b/test/tests.luau index 8425b700..aa7e16aa 100644 --- a/test/tests.luau +++ b/test/tests.luau @@ -1045,6 +1045,21 @@ TEST("world:component()", function() end) TEST("world:delete", function() + do CASE "delete recycled entity id used as component" + local world = world_new() + local id = world:entity() + world:add(id, jecs.Component) + + local e = world:entity() + world:set(e, id, 1) + CHECK(world:get(e, id) == 1) + world:delete(id) + local recycled = world:entity() + world:add(recycled, jecs.Component) + world:set(e, recycled, 1) + CHECK(world:has(recycled, jecs.Component)) + CHECK(world:get(e, recycled) == 1) + end do CASE("bug: Empty entity does not respect cleanup policy") local world = world_new()