mirror of
https://github.com/Ukendio/jecs.git
synced 2026-02-04 15:15:21 +00:00
escape for deletion
This commit is contained in:
parent
94f3eb0cc1
commit
4db046b925
7 changed files with 301 additions and 151 deletions
116
addons/ob.luau
116
addons/ob.luau
|
|
@ -137,9 +137,11 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local callback_added: ((jecs.Entity) -> ())?
|
local callback_added: ((jecs.Entity) -> ())?
|
||||||
local callback_removed: ((jecs.Entity) -> ())?
|
local callback_removed: ((jecs.Entity) -> ())?
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
local function emplaced<a>(
|
||||||
entity: jecs.Entity<T>,
|
entity: jecs.Entity,
|
||||||
id: jecs.Id<a>
|
id: jecs.Id<a>,
|
||||||
|
value: a,
|
||||||
|
oldarchetype: jecs.Archetype
|
||||||
)
|
)
|
||||||
if callback_added == nil then
|
if callback_added == nil then
|
||||||
return
|
return
|
||||||
|
|
@ -148,22 +150,26 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
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) :: jecs.Record
|
||||||
|
|
||||||
local archetype = r.archetype
|
if archetypes[r.archetype.id] then
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
|
||||||
callback_added(entity)
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function removed(entity: jecs.Entity, component)
|
local function removed(entity: jecs.Entity, component: jecs.Component, delete:boolean?)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
if callback_removed == nil then
|
if callback_removed == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
if not archetypes[r.archetype.id] then
|
local src = r.archetype
|
||||||
return
|
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, component, src)
|
||||||
|
|
||||||
|
if not archetypes[dst.id] then
|
||||||
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
callback_removed(entity)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local cleanup = {}
|
local cleanup = {}
|
||||||
|
|
@ -174,7 +180,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
local wc = tgt == jecs.w
|
||||||
|
|
||||||
local onadded = world:added(rel, function(entity, id)
|
local onadded = world:added(rel, function(entity, id, _, oldarchetype)
|
||||||
if callback_added == nil then
|
if callback_added == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -186,9 +192,10 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
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) :: jecs.Record
|
||||||
|
|
||||||
local archetype = r.archetype
|
if archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||||
|
print("???")
|
||||||
if archetypes[archetype.id] then
|
end
|
||||||
|
if not archetypes[oldarchetype.id] and archetypes[r.archetype.id] then
|
||||||
callback_added(entity)
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
@ -199,11 +206,11 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
if not wc and id ~= term then
|
if not wc and id ~= term then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
if not archetypes[r.archetype.id] then
|
if archetypes[r.archetype.id] then
|
||||||
return
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
callback_removed(entity)
|
|
||||||
end)
|
end)
|
||||||
table.insert(cleanup, onadded)
|
table.insert(cleanup, onadded)
|
||||||
table.insert(cleanup, onremoved)
|
table.insert(cleanup, onremoved)
|
||||||
|
|
@ -223,7 +230,7 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
local rel = jecs.ECS_PAIR_FIRST(term)
|
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||||
local tgt = jecs.ECS_PAIR_SECOND(term)
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
local wc = tgt == jecs.w
|
local wc = tgt == jecs.w
|
||||||
local onadded = world:added(rel, function(entity, id)
|
local onadded = world:added(rel, function(entity, id, _, oldarchetype)
|
||||||
if callback_removed == nil then
|
if callback_removed == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -232,14 +239,23 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if not archetype then
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
return
|
||||||
if archetypes[dst.id] then
|
end
|
||||||
callback_removed(entity)
|
|
||||||
end
|
-- NOTE(marcus): This check that it was presently in
|
||||||
|
-- the query but distinctively leaves is important as
|
||||||
|
-- sometimes it could be too eager to report that it
|
||||||
|
-- removed a component even though the entity is not
|
||||||
|
-- apart of the monitor
|
||||||
|
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||||
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
local onremoved = world:removed(rel, function(entity, id)
|
local onremoved = world:removed(rel, function(entity, id, delete)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
if callback_added == nil then
|
if callback_added == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
@ -248,40 +264,58 @@ local function monitors_new(query: Query<...any>): Monitor
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if not archetype then
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
return
|
||||||
if archetypes[dst.id] then
|
end
|
||||||
callback_added(entity)
|
|
||||||
end
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
table.insert(cleanup, onadded)
|
table.insert(cleanup, onadded)
|
||||||
table.insert(cleanup, onremoved)
|
table.insert(cleanup, onremoved)
|
||||||
else
|
else
|
||||||
local onadded = world:added(term, function(entity, id)
|
local onadded = world:added(term, function(entity, id, _, oldarchetype)
|
||||||
if callback_removed == nil then
|
if callback_removed == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if not archetype then
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
return
|
||||||
if archetypes[dst.id] then
|
end
|
||||||
callback_removed(entity)
|
|
||||||
end
|
-- NOTE(marcus): Sometimes OnAdd listeners for excluded
|
||||||
|
-- terms are too eager to report that it is leaving the
|
||||||
|
-- monitor even though the entity is not apart of it
|
||||||
|
-- already.
|
||||||
|
if archetypes[oldarchetype.id] and not archetypes[archetype.id] then
|
||||||
|
callback_removed(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
local onremoved = world:removed(term, function(entity, id)
|
local onremoved = world:removed(term, function(entity, id, delete)
|
||||||
|
if delete then
|
||||||
|
return
|
||||||
|
end
|
||||||
if callback_added == nil then
|
if callback_added == nil then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local r = jecs.record(world, entity)
|
local r = jecs.record(world, entity)
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
if archetype then
|
if not archetype then
|
||||||
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
return
|
||||||
if archetypes[dst.id] then
|
end
|
||||||
callback_added(entity)
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
end
|
|
||||||
|
-- NOTE(marcus): Inversely with the opposite operation, you
|
||||||
|
-- only need to check if it is going to enter the query once
|
||||||
|
-- because world:remove already stipulates that it is
|
||||||
|
-- idempotent so that this hook won't be invoked if it is
|
||||||
|
-- was already removed.
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_added(entity)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
table.insert(cleanup, onadded)
|
table.insert(cleanup, onadded)
|
||||||
|
|
|
||||||
37
benches/100k.luau
Executable file
37
benches/100k.luau
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
--!optimize 2
|
||||||
|
--!native
|
||||||
|
|
||||||
|
local testkit = require("@testkit")
|
||||||
|
local BENCH, START = testkit.benchmark()
|
||||||
|
local function TITLE(title: string)
|
||||||
|
print()
|
||||||
|
print(testkit.color.white(title))
|
||||||
|
end
|
||||||
|
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local mirror = require("@mirror")
|
||||||
|
|
||||||
|
do
|
||||||
|
TITLE(testkit.color.white_underline("Jecs query"))
|
||||||
|
local world = jecs.world() :: jecs.World
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
for i = 1, 100_000 do
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, A, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
local archetypes = world:query(A):archetypes()
|
||||||
|
|
||||||
|
BENCH("", function()
|
||||||
|
for _, archetype in archetypes do
|
||||||
|
local column = archetype.columns[1]
|
||||||
|
for row, entity in archetype.entities do
|
||||||
|
local data = column[row]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
@ -4,9 +4,9 @@
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
|
|
||||||
local jecs = require(ReplicatedStorage.Lib:Clone())
|
local jecs = require(ReplicatedStorage.Lib:Clone())
|
||||||
local mirror = require(ReplicatedStorage.mirror:Clone())
|
local chrono = require(ReplicatedStorage.chronoecs:Clone())
|
||||||
local mcs = mirror.world()
|
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
|
local ccs = chrono.new()
|
||||||
|
|
||||||
local D1 = ecs:component()
|
local D1 = ecs:component()
|
||||||
local D2 = ecs:component()
|
local D2 = ecs:component()
|
||||||
|
|
@ -17,75 +17,75 @@ local D6 = ecs:component()
|
||||||
local D7 = ecs:component()
|
local D7 = ecs:component()
|
||||||
local D8 = ecs:component()
|
local D8 = ecs:component()
|
||||||
|
|
||||||
local E1 = mcs:component()
|
local E1 = ccs:component()
|
||||||
local E2 = mcs:component()
|
local E2 = ccs:component()
|
||||||
local E3 = mcs:component()
|
local E3 = ccs:component()
|
||||||
local E4 = mcs:component()
|
local E4 = ccs:component()
|
||||||
local E5 = mcs:component()
|
local E5 = ccs:component()
|
||||||
local E6 = mcs:component()
|
local E6 = ccs:component()
|
||||||
local E7 = mcs:component()
|
local E7 = ccs:component()
|
||||||
local E8 = mcs:component()
|
local E8 = ccs:component()
|
||||||
|
|
||||||
|
local d_components = {}
|
||||||
|
local e_components = {}
|
||||||
|
|
||||||
|
for i = 1, 150 do
|
||||||
|
ecs:component()
|
||||||
|
ccs:component()
|
||||||
|
end
|
||||||
|
|
||||||
local function flip()
|
local function flip()
|
||||||
return math.random() >= 0.3
|
return math.random() >= 0.5
|
||||||
end
|
end
|
||||||
|
|
||||||
local N = 2 ^ 16 - 2
|
local N = 2 ^ 16 - 2
|
||||||
|
|
||||||
for i = 1, N do
|
for i = 1, N do
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
local m = mcs:entity()
|
local m = ccs:entity()
|
||||||
|
|
||||||
if flip() then
|
|
||||||
ecs:add(entity, entity)
|
|
||||||
mcs:add(m, m)
|
|
||||||
end
|
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D1, true)
|
ecs:set(entity, D1, true)
|
||||||
mcs:set(m, E1, true)
|
ccs:add(m, E1)
|
||||||
|
ccs:set(m, E1, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D2, true)
|
ecs:set(entity, D2, true)
|
||||||
mcs:set(m, E2, true)
|
ccs:add(m, E2)
|
||||||
|
ccs:set(m, E2, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D3, true)
|
ecs:set(entity, D3, true)
|
||||||
mcs:set(m, E3, true)
|
ccs:add(m, E3)
|
||||||
|
ccs:set(m, E3, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D4, true)
|
ecs:set(entity, D4, true)
|
||||||
mcs:set(m, E4, true)
|
ccs:add(m, E4)
|
||||||
|
ccs:set(m, E4, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D5, true)
|
ecs:set(entity, D5, true)
|
||||||
mcs:set(m, E5, true)
|
ccs:add(m, E4)
|
||||||
|
ccs:set(m, E5, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D6, true)
|
ecs:set(entity, D6, true)
|
||||||
mcs:set(m, E6, true)
|
ccs:add(m, E6)
|
||||||
|
ccs:set(m, E6, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
ecs:set(entity, D7, true)
|
ecs:set(entity, D7, true)
|
||||||
mcs:set(m, E7, true)
|
ccs:add(m, E7)
|
||||||
|
ccs:set(m, E7, true)
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
|
ccs:add(m, E8)
|
||||||
ecs:set(entity, D8, true)
|
ecs:set(entity, D8, true)
|
||||||
mcs:set(m, E8, true)
|
ccs:set(m, E8, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local count = 0
|
|
||||||
|
|
||||||
for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do
|
|
||||||
count += #archetype.entities
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
local mq = mcs:query(E2, E4, E6, E8):cached()
|
|
||||||
local jq = ecs:query(D2, D4, D6, D8):cached()
|
|
||||||
|
|
||||||
print(count, #jq:archetypes())
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
return
|
return
|
||||||
|
|
@ -102,13 +102,13 @@ return {
|
||||||
-- end
|
-- end
|
||||||
-- end,
|
-- end,
|
||||||
--
|
--
|
||||||
Mirror = function()
|
chrono = function()
|
||||||
for entityId, firstComponent in mq do
|
for entityId, firstComponent in ccs:view(E2, E4, E6, E8) do
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function()
|
Jecs = function()
|
||||||
for entityId, firstComponent in jq do
|
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
142
jecs.luau
142
jecs.luau
|
|
@ -63,37 +63,20 @@ end
|
||||||
|
|
||||||
type function ecs_id_t(first: type, second: type)
|
type function ecs_id_t(first: type, second: type)
|
||||||
local __T = types.singleton("__T")
|
local __T = types.singleton("__T")
|
||||||
|
|
||||||
|
local p = ecs_pair_t(Entity(first), Entity(second))
|
||||||
if second:is("nil") then
|
if second:is("nil") then
|
||||||
local p = ecs_pair_t(Entity(first), Entity(second))
|
return first
|
||||||
|
|
||||||
-- Create component type that matches Component<T> structure exactly
|
|
||||||
-- This should be structurally compatible with Component<T>
|
|
||||||
local component_type = types.newtable()
|
|
||||||
component_type:setproperty(__T, first)
|
|
||||||
|
|
||||||
-- Union order: component first, then pair
|
|
||||||
-- This helps Luau recognize Component<T> as assignable
|
|
||||||
-- local u = types.unionof(component_type, p)
|
|
||||||
-- return u
|
|
||||||
return component_type
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local function entity(ty: type)
|
return p
|
||||||
local e = types.newtable()
|
|
||||||
e:setproperty(__T, ty)
|
|
||||||
return e
|
|
||||||
end
|
|
||||||
|
|
||||||
local e1 = entity(first)
|
|
||||||
local e2 = entity(second)
|
|
||||||
return ecs_pair_t(e1, e2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
export type Entity<T = nil> = { __T: T }
|
export type Entity<T = nil> = { __T: T }
|
||||||
export type Id2<T = any> = { __T: T }
|
export type Id<T = any> = { __T: T }
|
||||||
export type Pair<First=any, Second=any> = ecs_pair_t<Entity<First>, Entity<Second>>
|
export type Pair<First=any, Second=any> = ecs_pair_t<Entity<First>, Entity<Second>>
|
||||||
export type Component<T=any> = { __T: T }
|
export type Component<T=any> = { __T: T }
|
||||||
export type Id<First, Second=nil> = ecs_id_t<First, Second>
|
export type Id2<First, Second=nil> = ecs_id_t<First, Second>
|
||||||
|
|
||||||
export type Item<T...> = (self: Query<T...>) -> (Entity, T...)
|
export type Item<T...> = (self: Query<T...>) -> (Entity, T...)
|
||||||
export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
|
||||||
|
|
@ -110,8 +93,8 @@ export type CachedQuery<T...> = typeof(setmetatable(
|
||||||
cached: (self: CachedQuery<T...>) -> CachedQuery<T...>,
|
cached: (self: CachedQuery<T...>) -> CachedQuery<T...>,
|
||||||
has: (CachedQuery<T...>, Entity) -> boolean,
|
has: (CachedQuery<T...>, Entity) -> boolean,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
filter_with: { Id<any, any> }?,
|
filter_with: { Id<any> }?,
|
||||||
filter_without: { Id<any, any> }?,
|
filter_without: { Id<any> }?,
|
||||||
archetypes_map: { [number]: number },
|
archetypes_map: { [number]: number },
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
|
|
@ -129,8 +112,8 @@ export type Query<T...> = typeof(setmetatable(
|
||||||
cached: (self: Query<T...>) -> CachedQuery<T...>,
|
cached: (self: Query<T...>) -> CachedQuery<T...>,
|
||||||
has: (Query<T...>, Entity) -> boolean,
|
has: (Query<T...>, Entity) -> boolean,
|
||||||
ids: { Id<any> },
|
ids: { Id<any> },
|
||||||
filter_with: { Id<any, any> }?,
|
filter_with: { Id<any> }?,
|
||||||
filter_without: { Id<any, any> }?,
|
filter_without: { Id<any> }?,
|
||||||
-- world: World
|
-- world: World
|
||||||
},
|
},
|
||||||
{} :: {
|
{} :: {
|
||||||
|
|
@ -177,7 +160,7 @@ type componentrecord = {
|
||||||
|
|
||||||
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_add: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
on_change: ((entity: i53, id: i53, value: any, oldarchetype: archetype) -> ())?,
|
||||||
on_remove: ((entity: i53, id: i53) -> ())?,
|
on_remove: ((entity: i53, id: i53, delete: boolean?) -> ())?,
|
||||||
|
|
||||||
wildcard_pairs: { [number]: componentrecord },
|
wildcard_pairs: { [number]: componentrecord },
|
||||||
}
|
}
|
||||||
|
|
@ -228,7 +211,7 @@ type world = {
|
||||||
|
|
||||||
added: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
added: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
||||||
changed: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
changed: (world, i53, (e: i53, id: i53, value: any?) -> ()) -> () -> (),
|
||||||
removed: (world, i53, (e: i53, id: i53) -> ()) -> () -> (),
|
removed: (world, i53, (e: i53, id: i53, delete: boolean?) -> ()) -> () -> (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -246,7 +229,7 @@ export type World = {
|
||||||
observable: Map<Component, Map<Component, { Observer }>>,
|
observable: Map<Component, Map<Component, { Observer }>>,
|
||||||
|
|
||||||
added: <T>(World, Component<T>, (e: Entity, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
added: <T>(World, Component<T>, (e: Entity, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
||||||
removed: <T>(World, Component<T>, (e: Entity, id: Id<T>) -> ()) -> () -> (),
|
removed: <T>(World, Component<T>, (e: Entity, id: Id<T>, delete: boolean?) -> ()) -> () -> (),
|
||||||
changed: <T>(World, Component<T>, (e: Entity, id: Id<T>, value: T, oldarchetype: Archetype) -> ()) -> () -> (),
|
changed: <T>(World, Component<T>, (e: Entity, 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
|
||||||
|
|
@ -2248,20 +2231,21 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
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
|
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]
|
||||||
|
if value then
|
||||||
|
r.archetype.columns_map[id][r.row] = value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i, id in ids do
|
||||||
local cdr = component_index[id]
|
local cdr = component_index[id]
|
||||||
|
|
||||||
local on_add = cdr.on_add
|
local on_add = cdr.on_add
|
||||||
if value then
|
if on_add then
|
||||||
r.archetype.columns_map[id][r.row] = value
|
local value = values[i]
|
||||||
if on_add then
|
on_add(entity, id, value, ROOT_ARCHETYPE)
|
||||||
on_add(entity, id, value, ROOT_ARCHETYPE)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if on_add then
|
|
||||||
on_add(entity, id, nil, ROOT_ARCHETYPE)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
|
|
@ -2287,25 +2271,43 @@ local function ecs_bulk_insert(world: world, entity: i53, ids: { i53 }, values:
|
||||||
|
|
||||||
if from ~= to then
|
if from ~= to then
|
||||||
entity_move(entity_index, entity, r, to)
|
entity_move(entity_index, entity, r, to)
|
||||||
end
|
|
||||||
|
|
||||||
for i, set in emplaced do
|
for i, id in ids do
|
||||||
local id = ids[i]
|
local value = values[i] :: any
|
||||||
local idr = component_index[id]
|
|
||||||
|
|
||||||
local value = values[i] :: any
|
if value ~= nil then
|
||||||
|
r.archetype.columns_map[id][r.row] = value
|
||||||
local on_add = idr.on_add
|
end
|
||||||
|
end
|
||||||
if value ~= nil then
|
|
||||||
r.archetype.columns_map[id][r.row] = value
|
for i, exists in emplaced do
|
||||||
local on_change = idr.on_change
|
local value = values[i]
|
||||||
local hook = if set then on_change else on_add
|
local id = ids[i]
|
||||||
if hook then
|
local idr = component_index[id]
|
||||||
hook(entity, id, value :: any, from)
|
if exists then
|
||||||
|
local on_change = idr.on_change
|
||||||
|
if on_change then
|
||||||
|
on_change(entity, id, value, from)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
local on_add = idr.on_add
|
||||||
|
if on_add then
|
||||||
|
on_add(entity, id, value, from)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for i, id in ids do
|
||||||
|
local value = values[i] :: any
|
||||||
|
local idr = component_index[id]
|
||||||
|
local on_change = idr.on_change
|
||||||
|
if on_change then
|
||||||
|
on_change(entity, id, value, from)
|
||||||
|
end
|
||||||
|
|
||||||
|
if value ~= nil then
|
||||||
|
r.archetype.columns_map[id][r.row] = value
|
||||||
end
|
end
|
||||||
elseif on_add then
|
|
||||||
on_add(entity, id, nil, from)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -2783,7 +2785,9 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
type Listener<T> = (e: i53, id: i53, value: T, oldarchetype: archetype) -> ()
|
type Listener<T> =
|
||||||
|
& ((e: i53, id: i53, value: T, oldarchetype: archetype) -> ())
|
||||||
|
& ((e: i53, id: i53, delete: boolean?) -> ())
|
||||||
|
|
||||||
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]
|
||||||
|
|
@ -2839,7 +2843,7 @@ local function world_new()
|
||||||
listener(entity, id, value, oldarchetype)
|
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>?
|
||||||
if existing_hook then
|
if existing_hook then
|
||||||
table.insert(listeners, existing_hook)
|
table.insert(listeners, existing_hook)
|
||||||
end
|
end
|
||||||
|
|
@ -2870,14 +2874,14 @@ local function world_new()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
world.removed = function<T>(_: world, component: i53, fn: (i53, i53) -> ())
|
world.removed = function<T>(_: world, component: i53, fn: (i53, i53, boolean?) -> ())
|
||||||
local listeners = signals.removed[component]
|
local listeners = signals.removed[component]
|
||||||
if not listeners then
|
if not listeners then
|
||||||
listeners = {}
|
listeners = {}
|
||||||
signals.removed[component] = listeners
|
signals.removed[component] = listeners
|
||||||
local function on_remove(entity, id)
|
local function on_remove(entity, id, delete)
|
||||||
for _, listener in listeners :: { (...any) -> () } do
|
for _, listener in listeners :: { (...any) -> () } do
|
||||||
listener(entity, id)
|
listener(entity, id, delete)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -3088,7 +3092,7 @@ local function world_new()
|
||||||
local idr = component_index[id]
|
local idr = component_index[id]
|
||||||
local on_remove = idr.on_remove
|
local on_remove = idr.on_remove
|
||||||
if on_remove then
|
if on_remove then
|
||||||
on_remove(entity, id)
|
on_remove(entity, id, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
archetype_delete(world, record.archetype, record.row)
|
archetype_delete(world, record.archetype, record.row)
|
||||||
|
|
@ -3096,11 +3100,11 @@ local function world_new()
|
||||||
|
|
||||||
local component_index = world.component_index
|
local component_index = world.component_index
|
||||||
local archetypes = world.archetypes
|
local archetypes = world.archetypes
|
||||||
local tgt = ECS_PAIR(EcsWildcard, entity::number)
|
local tgt = ECS_PAIR(EcsWildcard, entity)
|
||||||
local rel = ECS_PAIR(entity::number, EcsWildcard)
|
local rel = ECS_PAIR(entity, EcsWildcard)
|
||||||
|
|
||||||
local idr_t = component_index[tgt]
|
local idr_t = component_index[tgt]
|
||||||
local idr = component_index[entity::number]
|
local idr = component_index[entity]
|
||||||
local idr_r = component_index[rel]
|
local idr_r = component_index[rel]
|
||||||
|
|
||||||
if idr then
|
if idr then
|
||||||
|
|
@ -3228,7 +3232,7 @@ local function world_new()
|
||||||
local tr_count = counts[archetype_id]
|
local tr_count = counts[archetype_id]
|
||||||
local idr_r_types = idr_r_archetype.types
|
local idr_r_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]
|
local id = idr_r_types[i]
|
||||||
node = archetype_traverse_remove(world, id, node)
|
node = archetype_traverse_remove(world, id, node)
|
||||||
local on_remove = component_index[id].on_remove
|
local on_remove = component_index[id].on_remove
|
||||||
if on_remove then
|
if on_remove then
|
||||||
|
|
@ -3444,10 +3448,10 @@ return {
|
||||||
pair = ECS_PAIR :: <P, O>(first: Entity<P>, second: Entity<O>) -> Pair<P, O>,
|
pair = ECS_PAIR :: <P, O>(first: Entity<P>, second: Entity<O>) -> Pair<P, O>,
|
||||||
|
|
||||||
IS_PAIR = ECS_IS_PAIR :: (pair: Component) -> boolean,
|
IS_PAIR = ECS_IS_PAIR :: (pair: Component) -> boolean,
|
||||||
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Id<P, O>) -> Component<P>,
|
ECS_PAIR_FIRST = ECS_PAIR_FIRST :: <P, O>(pair: Id<P>) -> Component<P>,
|
||||||
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Id<P, O>) -> Component<O>,
|
ECS_PAIR_SECOND = ECS_PAIR_SECOND :: <P, O>(pair: Id<P>) -> Component<O>,
|
||||||
pair_first = ecs_pair_first :: <P, O>(world: World, pair: Id<P, O>) -> Component<P>,
|
pair_first = ecs_pair_first :: <P, O>(world: World, pair: Id<P>) -> Component<P>,
|
||||||
pair_second = ecs_pair_second :: <P, O>(world: World, pair: Id<P, O>) -> Component<O>,
|
pair_second = ecs_pair_second :: <P, O>(world: World, pair: Id<P>) -> Component<O>,
|
||||||
entity_index_get_alive = entity_index_get_alive,
|
entity_index_get_alive = entity_index_get_alive,
|
||||||
|
|
||||||
archetype_append_to_records = archetype_append_to_records,
|
archetype_append_to_records = archetype_append_to_records,
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,68 @@ TEST("addons/ob::observer", function()
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
FOCUS()
|
||||||
TEST("addons/ob::monitor", function()
|
TEST("addons/ob::monitor", function()
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
|
do CASE [[should not invoke monitor.added callback unless it wasn't apart
|
||||||
|
of the monitor]]
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
local monitor = ob.monitor(world:query(A):without(B, C))
|
||||||
|
local c = 0
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(e, B)
|
||||||
|
world:add(e, C)
|
||||||
|
-- left
|
||||||
|
CHECK(c==1)
|
||||||
|
|
||||||
|
world:remove(e, B)
|
||||||
|
world:remove(e, C)
|
||||||
|
-- re-enters once
|
||||||
|
CHECK(c==2)
|
||||||
|
|
||||||
|
world:remove(e, B)
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "deleted entity should only exit monitor once"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local Destroy = world:entity()
|
||||||
|
|
||||||
|
local c = 0
|
||||||
|
|
||||||
|
local monitor = ob.monitor(world:query(A, B):without(Destroy))
|
||||||
|
|
||||||
|
monitor.added(function()
|
||||||
|
print("enter")
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, B)
|
||||||
|
|
||||||
|
world:add(e, Destroy)
|
||||||
|
|
||||||
|
world:delete(e)
|
||||||
|
print(c)
|
||||||
|
CHECK(c==1)
|
||||||
|
end
|
||||||
|
|
||||||
do CASE [[should not invoke callbacks with a related but non-queried pair that
|
do CASE [[should not invoke callbacks with a related but non-queried pair that
|
||||||
while the entity still matches against the query]]
|
while the entity still matches against the query]]
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
|
|
||||||
11
test/ecr.luau
Executable file
11
test/ecr.luau
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
local function component()
|
||||||
|
local id = 1
|
||||||
|
local v
|
||||||
|
local function instance()
|
||||||
|
return id, v
|
||||||
|
end
|
||||||
|
return function(value)
|
||||||
|
v = value
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -292,14 +292,18 @@ TEST("bulk", function()
|
||||||
world:changed(c3, counter)
|
world:changed(c3, counter)
|
||||||
|
|
||||||
local e = world:entity()
|
local e = world:entity()
|
||||||
jecs.bulk_insert(world, e, {c1,c2,c3}, {1,2,3})
|
jecs.bulk_insert(world, e, {c1,c2}, {1,2,3})
|
||||||
|
|
||||||
jecs.bulk_insert(world, e, {c1,c2,c3}, {4,5,6})
|
jecs.bulk_insert(world, e, {c1,c2,c3}, {4,5,6})
|
||||||
|
|
||||||
CHECK(world:has(e, c1, c2, c3))
|
CHECK(world:has(e, c1, c2, c3))
|
||||||
CHECK(count == 3)
|
CHECK(count == 2)
|
||||||
|
|
||||||
|
jecs.bulk_insert(world, e, {c1,c2,c3}, {7,8,9})
|
||||||
|
CHECK(count == 2+3)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
do CASE "Should bulk add with hooks moving archetypes without previous"
|
do CASE "Should bulk add with hooks moving archetypes without previous"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local c1, c2, c3 = world:component(), world:component(), world:component()
|
local c1, c2, c3 = world:component(), world:component(), world:component()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue