Compare commits

...

8 commits

Author SHA1 Message Date
Clown
121deb8243
Merge 96bed9bd7e into 13facc3719 2025-07-03 11:13:20 -04:00
Ukendio
13facc3719 Add inference for IDs in methods
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-07-03 01:41:06 +02:00
Ukendio
a6ba9f4bd5 Update networking example
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-07-02 20:26:57 +02:00
Ukendio
53f705ac2e Compare archetype move performance boost
Some checks failed
analysis / Run Luau Analyze (push) Has been cancelled
deploy-docs / build (push) Has been cancelled
publish-npm / publish (push) Has been cancelled
unit-testing / Run Luau Tests (push) Has been cancelled
deploy-docs / Deploy (push) Has been cancelled
2025-06-30 23:11:05 +02:00
Ukendio
ff4b0bf612 Use btest instead of band 2025-06-30 22:53:40 +02:00
renyang19910211
9b57189c3a
Fix receive_replication.luau removed issue (#243)
Some checks are pending
analysis / Run Luau Analyze (push) Waiting to run
deploy-docs / build (push) Waiting to run
deploy-docs / Deploy (push) Blocked by required conditions
publish-npm / publish (push) Waiting to run
unit-testing / Run Luau Tests (push) Waiting to run
2025-06-30 22:41:29 +02:00
Ukendio
4ff492ceaf Optimize moving archetype 2025-06-30 22:37:30 +02:00
YetAnotherClown
96bed9bd7e Empty Commit 2025-02-25 11:28:04 -05:00
17 changed files with 1769 additions and 1404 deletions

View file

@ -5,8 +5,7 @@ local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.Lib:Clone())
local ecs = jecs.world()
local mirror = require(ReplicatedStorage.mirror:Clone())
local mcs = mirror.World.new()
local mcs = mirror.world()
local C1 = ecs:component()
local C2 = ecs:component()

View file

@ -2,7 +2,7 @@
--!native
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.DevPackages.matter)
local Matter = require(ReplicatedStorage.DevPackages.Matter)
local ecr = require(ReplicatedStorage.DevPackages.ecr)
local newWorld = Matter.World.new()

View file

@ -8,7 +8,7 @@ local jecs = require(ReplicatedStorage.Lib)
local pair = jecs.pair
local ecs = jecs.world()
local mirror = require(ReplicatedStorage.mirror)
local mcs = mirror.World.new()
local mcs = mirror.world()
local C1 = ecs:component()
local C2 = ecs:entity()
@ -32,7 +32,7 @@ return {
Functions = {
Mirror = function()
local m = mcs:entity()
for i = 1, 100 do
for i = 1, 1000 do
mcs:add(m, E3)
mcs:remove(m, E3)
end
@ -40,7 +40,7 @@ return {
Jecs = function()
local j = ecs:entity()
for i = 1, 100 do
for i = 1, 1000 do
ecs:add(j, C3)
ecs:remove(j, C3)
end

View file

@ -1,3 +1,5 @@
--!strict
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local jecs = require(ReplicatedStorage.ecs)
local types = require("./types")
@ -5,31 +7,58 @@ local types = require("./types")
local Networked = jecs.tag()
local NetworkedPair = jecs.tag()
local Renderable = jecs.component() :: jecs.Id<Instance>
jecs.meta(Renderable, Networked)
local InstanceMapping = jecs.component() :: jecs.Id<Instance>
jecs.meta(InstanceMapping, jecs.OnAdd, function(component)
jecs.meta(component, jecs.OnAdd, function(entity, _, instance)
if RunService:IsServer() then
instance:SetAttribute("entity_server")
else
instance:SetAttribute("entity_client")
end
end)
end)
local function networked_id(ct)
jecs.meta(ct, Networked)
return ct
end
local function networked_pair(ct)
jecs.meta(ct, NetworkedPair)
return ct
end
local function instance_mapping_id(ct)
jecs.meta(ct, InstanceMapping)
return ct
end
local Poison = jecs.component() :: jecs.Id<number>
jecs.meta(Poison, Networked)
local Health = jecs.component() :: jecs.Id<number>
jecs.meta(Health, Networked)
local Player = jecs.component() :: jecs.Id<Player>
jecs.meta(Player, Networked)
local Renderable = jecs.component() :: types.Id<Instance>
local Poison = jecs.component() :: types.Id<number>
local Health = jecs.component() :: types.Id<number>
local Player = jecs.component() :: types.Id<Player>
local Debuff = jecs.tag() :: types.Entity
local Lifetime = jecs.component() :: types.Id<{
duration: number,
created: number
}>
local Destroy = jecs.tag()
local components = {
Renderable = Renderable,
Player = Player,
Poison = Poison,
Health = Health,
Renderable = networked_id(instance_mapping_id(Renderable)),
Player = networked_id(Player),
Poison = networked_id(Poison),
Health = networked_id(Health),
Lifetime = networked_id(Lifetime),
Debuff = networked_id(Debuff),
Destroy = networked_id(Destroy),
-- We have to define that some builtin IDs can also be networked
ChildOf = networked_pair(jecs.ChildOf),
Networked = Networked,
NetworkedPair = NetworkedPair,
}
for name, component in components do
for name, component in components :: {[string]: types.Id<any> } do
jecs.meta(component, jecs.Name, name)
end

View file

@ -10,4 +10,5 @@ local world = observers_add(jecs.world())
local systems = ReplicatedStorage.systems
SYSTEM(world, systems.receive_replication)
SYSTEM(world, systems.entities_delete)
RUN(world)

View file

@ -0,0 +1,13 @@
--!strict
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local types = require(ReplicatedStorage.types)
local ct = require(ReplicatedStorage.components)
local function entities_delete(world: types.World, dt: number)
for e in world:each(ct.Destroy) do
world:delete(e)
end
end
return entities_delete

View file

@ -2,45 +2,51 @@ local types = require("../types")
local jecs = require(game:GetService("ReplicatedStorage").ecs)
local remotes = require("../remotes")
local collect = require("../collect")
local client_ids = {}
local components = require("../components")
local function ecs_map_get(world, id)
local deserialised_id = client_ids[id]
local client_ids: {[jecs.Entity]: jecs.Entity } = {}
if not deserialised_id then
if world:has(id, jecs.Name) then
deserialised_id = world:entity(id)
local function ecs_ensure_entity(world: jecs.World, id: jecs.Entity)
local e = 0
local ser_id = id
local deser_id = client_ids[ser_id]
if deser_id then
if deser_id == 0 then
local new_id = world:entity()
client_ids[ser_id] = new_id
deser_id = new_id
end
else
deserialised_id = world:entity()
if not world:exists(ser_id)
or (world:contains(ser_id) and not world:get(ser_id, jecs.Name))
then
deser_id = world:entity()
else
if world:contains(ser_id) and world:get(ser_id, jecs.Name) then
deser_id = ser_id
else
deser_id = world:entity()
end
end
client_ids[ser_id] = deser_id
end
client_ids[id] = deserialised_id
end
e = deser_id
-- local deserialised_id = client_ids[id]
-- if not deserialised_id then
-- if world:has(id, jecs.Name) then
-- deserialised_id = world:entity(id)
-- else
-- if world:exists(id) then
-- deserialised_id = world:entity()
-- else
-- deserialised_id = world:entity(id)
-- end
-- end
-- client_ids[id] = deserialised_id
-- end
return deserialised_id
return e
end
local function ecs_make_alive_id(world, id)
local rel = jecs.ECS_PAIR_FIRST(id)
local tgt = jecs.ECS_PAIR_SECOND(id)
-- local rel_render = `e{jecs.ECS_ID(rel)}v{jecs.ECS_GENERATION(rel)}`
-- local tgt_render = `e{jecs.ECS_ID(tgt)}v{jecs.ECS_GENERATION(tgt)}`
local function ecs_deser_pairs(world, token)
local tokens = string.split(token, ",")
local rel = tonumber(tokens[1])
local tgt = tonumber(tokens[2])
rel = ecs_map_get(world, rel)
tgt = ecs_map_get(world, tgt)
rel = ecs_ensure_entity(world, rel)
tgt = ecs_ensure_entity(world, tgt)
return jecs.pair(rel, tgt)
end
@ -48,24 +54,30 @@ end
local snapshots = collect(remotes.replication.OnClientEvent)
return function(world: types.World)
for entity in world:each(components.Destroy) do
client_ids[entity] = nil
end
for snapshot in snapshots do
for id, map in snapshot do
id = tonumber(id)
if jecs.IS_PAIR(id) then
id = ecs_make_alive_id(world, id)
for ser_id, map in snapshot do
local id = tonumber(ser_id)
if not id then
id = ecs_deser_pairs(world, ser_id)
else
id = ecs_ensure_entity(world, id)
end
local set = map.set
if set then
if jecs.is_tag(world, id) then
for _, entity in set do
entity = ecs_map_get(world, entity)
entity = ecs_ensure_entity(world, entity)
world:add(entity, id)
end
else
local t = os.clock()
local values = map.values
for i, entity in set do
entity = ecs_map_get(world, entity)
entity = ecs_ensure_entity(world, entity)
world:set(entity, id, values[i])
end
end
@ -74,11 +86,12 @@ return function(world: types.World)
local removed = map.removed
if removed then
for i, e in removed do
if not world:contains(e) then
for _, entity in removed do
entity = ecs_ensure_entity(world, entity)
if not world:contains(entity) then
continue
end
world:remove(e, id)
world:remove(entity, id)
end
end
end

View file

@ -15,5 +15,8 @@ local systems = ServerScriptService.systems
SYSTEM(world, systems.replication)
SYSTEM(world, systems.players_added)
SYSTEM(world, systems.poison_hurts)
SYSTEM(world, systems.health_regen)
SYSTEM(world, systems.lifetimes_expire)
SYSTEM(world, systems.life_is_painful)
SYSTEM(world, ReplicatedStorage.systems.entities_delete)
RUN(world, 0)

View file

@ -0,0 +1,15 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
return function(world: types.World, dt: number)
for e in world:query(ct.Player):without(ct.Health) do
world:set(e, ct.Health, 100)
end
for e, health in world:query(ct.Health) do
if math.random() < 1 / 60 / 30 then
world:set(e, ct.Health, 100)
end
end
end

View file

@ -1,12 +1,19 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
local jecs = require(ReplicatedStorage.ecs)
return function(world: types.World, dt: number)
for e in world:query(ct.Player):without(ct.Health) do
world:set(e, ct.Health, 100)
if math.random() < (1 / 60 / 7) then
for e in world:each(ct.Health) do
local poison = world:entity()
world:add(poison, ct.Debuff)
world:add(poison, jecs.pair(jecs.ChildOf, e))
world:set(poison, ct.Poison, 10)
world:set(poison, ct.Lifetime, {
duration = 3,
created = os.clock()
})
end
for e in world:query(ct.Player, ct.Health):without(ct.Poison) do
world:set(e, ct.Poison, 10)
end
end

View file

@ -0,0 +1,12 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
local types = require(ReplicatedStorage.types)
return function(world: types.World, dt: number)
for e, lifetime in world:query(ct.Lifetime) do
if os.clock() > lifetime.created + lifetime.duration then
world:add(e, ct.Destroy)
end
end
end

View file

@ -12,9 +12,13 @@ return function(world: types.World, dt: number)
for entity, player in world:query(ct.Player):without(ct.Renderable) do
local character = player.Character
if character then
if not character then
continue
end
if not character.Parent then
continue
end
world:set(entity, ct.Renderable, character)
end
end
end

View file

@ -1,12 +1,18 @@
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ct = require(ReplicatedStorage.components)
return function(world, dt)
for e, poison, health in world:query(ct.Poison, ct.Health) do
local health_after_tick = health - poison * dt * 0.05
if health_after_tick < 0 then
world:remove(e, ct.Health)
local types = require(ReplicatedStorage.types)
local jecs = require(ReplicatedStorage.ecs)
return function(world: types.World, dt: number)
for e, poison_tick in world:query(ct.Poison, jecs.pair(jecs.ChildOf, jecs.w)) do
local tgt = world:target(e, jecs.ChildOf)
local health = world:get(tgt, ct.Health)
if not health then
continue
end
world:set(e, ct.Health, health_after_tick)
if math.random() < 1 / 60 / 1 and health > 1 then
world:set(tgt, ct.Health, health - 1)
end
end
end

View file

@ -1,9 +1,13 @@
--!strict
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local types = require("../../ReplicatedStorage/types")
local ct = require("../../ReplicatedStorage/components")
local ct = require(ReplicatedStorage.components)
local components = ct :: { [string]: jecs.Entity }
local remotes = require(ReplicatedStorage.remotes)
local jecs = require(ReplicatedStorage.ecs)
local remotes = require("../../ReplicatedStorage/remotes")
local components = ct :: {[string]: jecs.Entity }
local collect = require(ReplicatedStorage.collect)
local ty = require(ReplicatedStorage.types)
return function(world: ty.World)
@ -19,9 +23,10 @@ return function(world: ty.World)
local networked_pairs = {}
for component in world:each(ct.Networked) do
local name = world:get(component, jecs.Name) :: string
local name = assert(world:get(component, jecs.Name), "Invalid component")
if components[name] == nil then
continue
error("Invalid component:"..name)
end
storages[component] = {}
@ -32,14 +37,15 @@ return function(world: ty.World)
for relation in world:each(ct.NetworkedPair) do
local name = world:get(relation, jecs.Name) :: string
if not components[name] then
continue
error("Invalid component")
end
table.insert(networked_pairs, relation)
end
for _, component in networked_components do
local name = world:get(component, jecs.Name) :: string
local name = world:get(component, jecs.Name)
if not components[name] then
-- error("Invalid component")
error(`Networked Component (%id{component}%name{name})`)
end
local is_tag = jecs.is_tag(world, component)
@ -103,12 +109,16 @@ return function(world: ty.World)
end)
end
-- local requested_snapshots = collect(remotes.request_snapshot.OnServerEvent)
local players_added = collect(Players.PlayerAdded)
return function()
local snapshot_lazy: ty.Snapshot
local set_ids_lazy: { jecs.Entity }
-- In the future maybe it should be requested by the player instead when they
-- are ready to receive the replication. Otherwise streaming could be complicated
-- with intances references being nil.
for player in players_added do
if not snapshot_lazy then
snapshot_lazy, set_ids_lazy = {}, {}
@ -126,7 +136,7 @@ return function(world: ty.World)
if is_tag then
set_values = table.create(entities_len, true)
else
local column = archetype.columns[archetype.records[component]]
local column = archetype.columns_map[component]
table.move(column, 1, entities_len, set_n + 1, set_values)
end
@ -135,7 +145,15 @@ return function(world: ty.World)
local set = table.move(set_ids_lazy, 1, set_n, 1, {})
snapshot_lazy[tostring(component)] = {
local ser_id: string = nil :: any
if jecs.IS_PAIR(component) then
ser_id = `{jecs.pair_first(world, component)},{jecs.pair_first(world, component)}`
else
ser_id = tostring(component)
end
snapshot_lazy[ser_id] = {
set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil,
}
@ -159,7 +177,7 @@ return function(world: ty.World)
set_n += 1
set_ids[set_n] = e
set_values[set_n] = v or true
elseif world:contains(e) then
elseif not world:contains(e) then
removed_n += 1
removed_ids[removed_n] = e
end
@ -176,7 +194,15 @@ return function(world: ty.World)
if dirty then
local removed = table.move(removed_ids, 1, removed_n, 1, {}) :: { jecs.Entity }
local set = table.move(set_ids, 1, set_n, 1, {}) :: { jecs.Entity }
snapshot[tostring(component)] = {
local ser_id: string = nil :: any
if jecs.IS_PAIR(component) then
ser_id = `{jecs.pair_first(world, component)},{jecs.pair_second(world, component)}`
else
ser_id = tostring(component)
end
snapshot[ser_id] = {
set = if set_n > 0 then set else nil,
values = if set_n > 0 then set_values else nil,
removed = if removed_n > 0 then removed else nil

View file

@ -42,8 +42,18 @@ export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
export type Query<T...> = typeof(setmetatable(
{} :: {
iter: Iter<T...>,
with: (self: Query<T...>, ...Id) -> Query<T...>,
without: (self: Query<T...>, ...Id) -> Query<T...>,
with:
(<a>(Query<T...>, Id<a>) -> Query<T...>)
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
without:
(<a>(Query<T...>, Id<a>) -> Query<T...>)
& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
archetypes: (self: Query<T...>) -> { Archetype },
cached: (self: Query<T...>) -> Query<T...>,
},
@ -439,6 +449,7 @@ end
local function archetype_move(
entity_index: EntityIndex,
entity: Entity,
to: Archetype,
dst_row: i24,
from: Archetype,
@ -452,6 +463,9 @@ local function archetype_move(
local id_types = from.types
local columns_map = to.columns_map
if src_row ~= last then
-- If the entity is the last row in the archetype then swapping it would be meaningless.
for i, column in src_columns do
if column == NULL_ARRAY then
continue
@ -465,35 +479,42 @@ local function archetype_move(
dst_column[dst_row] = column[src_row]
end
-- If the entity is the last row in the archetype then swapping it would be meaningless.
if src_row ~= last then
-- Swap rempves columns to ensure there are no holes in the archetype.
column[src_row] = column[last]
end
column[last] = nil
end
local moved = #src_entities
-- Move the entity from the source to the destination archetype.
-- Because we have swapped columns we now have to update the records
-- corresponding to the entities' rows that were swapped.
local e1 = src_entities[src_row]
local e2 = src_entities[moved]
if src_row ~= moved then
local e2 = src_entities[last]
src_entities[src_row] = e2
end
src_entities[moved] = nil :: any
dst_entities[dst_row] = e1
local sparse_array = entity_index.sparse_array
local record1 = sparse_array[ECS_ENTITY_T_LO(e1 :: number)]
local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)]
record1.row = dst_row
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 archetype_append(
@ -526,7 +547,7 @@ local function entity_move(
local sourceRow = record.row
local from = record.archetype
local dst_row = archetype_append(entity, to)
archetype_move(entity_index, to, dst_row, from, sourceRow)
archetype_move(entity_index, entity, to, dst_row, from, sourceRow)
record.archetype = to
record.row = dst_row
end
@ -744,7 +765,7 @@ local function archetype_register(world: World, archetype: Archetype)
local columns = archetype.columns
for i, component_id in archetype.types do
local idr = id_record_ensure(world, component_id)
local is_tag = bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
local column = if is_tag then NULL_ARRAY else {}
columns[i] = column
@ -2394,7 +2415,7 @@ local function world_new()
return entity
else
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
end
entity_index.max_id = index
@ -2459,8 +2480,8 @@ local function world_new()
local idr_archetype = archetypes[archetype_id]
local entities = idr_archetype.entities
local n = #entities
table.move(entities, 1, n, count + 1, queue)
count += n
table.move(entities, 1, n, #queue + 1, queue)
end
for _, e in queue do
inner_world_remove(world, e, entity)
@ -2572,7 +2593,7 @@ local function world_new()
if idr then
local flags = idr.flags
if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
if bit32.btest(flags, ECS_ID_DELETE) then
for archetype_id in idr.records do
local idr_archetype = archetypes[archetype_id]
@ -2647,8 +2668,8 @@ local function world_new()
end
local id_record = component_index[id]
local flags = id_record.flags
local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
if flags_delete_mask ~= 0 then
local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
if flags_delete_mask then
for i = #entities, 1, -1 do
local child = entities[i]
inner_world_delete(world, child)
@ -2690,7 +2711,7 @@ local function world_new()
if idr_r then
local archetype_ids = idr_r.records
local flags = idr_r.flags
if (bit32.band(flags, ECS_ID_DELETE) :: number) ~= 0 then
if bit32.btest(flags, ECS_ID_DELETE) then
for archetype_id in archetype_ids do
local idr_r_archetype = archetypes[archetype_id]
local entities = idr_r_archetype.entities
@ -2868,7 +2889,7 @@ end
local function ecs_is_tag(world: World, entity: Entity): boolean
local idr = world.component_index[entity]
if idr then
return bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
return bit32.btest(idr.flags, ECS_ID_IS_TAG)
end
return not world_has_one_inline(world, entity, EcsComponent)
end
@ -2877,7 +2898,7 @@ return {
world = world_new :: () -> World,
component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
meta = (ECS_META :: any) :: <T>(id: Entity, id: Id<T>, value: T) -> Entity<T>,
meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,

File diff suppressed because it is too large Load diff

View file

@ -22,60 +22,8 @@ type Entity<T=nil> = jecs.Entity<T>
type Id<T=unknown> = jecs.Id<T>
local entity_visualiser = require("@tools/entity_visualiser")
local lifetime_tracker_add = require("@tools/lifetime_tracker")
local dwi = entity_visualiser.stringify
TEST("repro#", function()
do CASE "pair(OnDelete, Delete)"
local world = jecs.world()
local ct = world:component()
world:add(ct, jecs.pair(jecs.OnDelete, jecs.Delete))
local e1 = world:entity()
local e2 = world:entity()
local dummy = world:entity()
world:add(e1, ct)
world:add(e2, jecs.pair(ct, dummy))
world:delete(dummy)
CHECK(world:contains(e2))
world:delete(ct)
CHECK(not world:contains(e1))
end
do CASE "pair(OnDeleteTarget, Delete)"
print("start")
local world = jecs.world()
local ct = world:component()
world:add(ct, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
local e1 = world:entity()
local e2 = world:entity()
-- local dummy = world:entity()
print("flags")
world:add(e1, ct)
print(world.component_index[ct].flags)
-- world:add(e2, jecs.pair(ct, dummy))
-- world:delete(dummy)
-- CHECK(not world:contains(e2))
world:delete(ct)
CHECK(world:contains(e1))
end
end)
TEST("bulk", function()
local world = jecs.world()
local A = world:component()
@ -433,8 +381,51 @@ TEST("world:contains()", function()
end)
TEST("world:delete()", function()
do CASE "pair(OnDelete, Delete)"
local world = jecs.world()
local ct = world:component()
world:add(ct, jecs.pair(jecs.OnDelete, jecs.Delete))
local e1 = world:entity()
local e2 = world:entity()
local dummy = world:entity()
world:add(e1, ct)
world:add(e2, jecs.pair(ct, dummy))
world:delete(dummy)
CHECK(world:contains(e2))
world:delete(ct)
CHECK(not world:contains(e1))
end
do CASE "pair(OnDeleteTarget, Delete)"
local world = jecs.world()
local ct = world:component()
world:add(ct, jecs.pair(jecs.OnDeleteTarget, jecs.Delete))
local e1 = world:entity()
local e2 = world:entity()
local dummy = world:entity()
world:add(e1, ct)
world:add(e2, jecs.pair(ct, dummy))
world:delete(dummy)
CHECK(not world:contains(e2))
world:delete(ct)
CHECK(world:contains(e1))
end
do CASE "remove (*, R) pairs when relationship is invalidated"
print("-------")
local world = jecs.world()
local e1 = world:entity()
local e2 = world:entity()