mirror of
https://github.com/Ukendio/jecs.git
synced 2025-09-23 16:39:17 +00:00
Compare commits
19 commits
v0.9.0-rc.
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
9514fb758a | ||
|
e2ab3be3e5 | ||
|
35cb0bca4e | ||
|
7e1f43aff5 | ||
|
690e9ec4d7 | ||
|
8ed8c2a0e0 | ||
|
23bf021f01 | ||
|
4ed2fb7a40 | ||
|
e16e4a04e4 | ||
|
456713c2d5 | ||
|
3dacb2af80 | ||
|
1eecaac96f | ||
|
d8b2d36c52 | ||
|
af093713b4 | ||
|
549fe97622 | ||
|
b0e73857b9 | ||
|
917c951d55 | ||
|
037035a9a1 | ||
|
29a66d92c2 |
17 changed files with 1417 additions and 744 deletions
253
addons/ob.luau
253
addons/ob.luau
|
@ -10,29 +10,28 @@ type Entity<T> = jecs.Entity<T>
|
||||||
|
|
||||||
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
|
export type Iter<T...> = (Observer<T...>) -> () -> (jecs.Entity, T...)
|
||||||
|
|
||||||
export type Observer<T...> = typeof(setmetatable(
|
export type Observer<T...> = {
|
||||||
{} :: {
|
disconnect: (Observer<T...>) -> (),
|
||||||
iter: Iter<T...>,
|
}
|
||||||
entities: { Entity<nil> },
|
|
||||||
disconnect: (Observer<T...>) -> ()
|
export type Monitor<T...> = {
|
||||||
},
|
disconnect: (Observer<T...>) -> (),
|
||||||
{} :: {
|
added: ((jecs.Entity) -> ()) -> (),
|
||||||
__iter: Iter<T...>,
|
removed: ((jecs.Entity) -> ()) -> ()
|
||||||
}
|
}
|
||||||
))
|
|
||||||
|
|
||||||
local function observers_new<T...>(
|
local function observers_new<T...>(
|
||||||
query: Query<T...>,
|
query: Query<T...>,
|
||||||
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
|
callback: ((Entity<nil>) -> ())
|
||||||
): Observer<T...>
|
): Observer<T...>
|
||||||
|
|
||||||
query:cached()
|
query:cached()
|
||||||
|
|
||||||
|
|
||||||
local world = (query :: Query<T...> & { world: World }).world
|
local world = (query :: Query<T...> & { world: World }).world
|
||||||
callback = callback
|
callback = callback
|
||||||
|
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
local terms = query.ids
|
local terms = query.filter_with :: { jecs.Id }
|
||||||
local first = terms[1]
|
local first = terms[1]
|
||||||
|
|
||||||
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
@ -47,8 +46,10 @@ local function observers_new<T...>(
|
||||||
end
|
end
|
||||||
|
|
||||||
local entity_index = world.entity_index :: any
|
local entity_index = world.entity_index :: any
|
||||||
local i = 0
|
|
||||||
local entities = {}
|
for _, archetype in query:archetypes() do
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
local function emplaced<T, a>(
|
||||||
entity: jecs.Entity<T>,
|
entity: jecs.Entity<T>,
|
||||||
|
@ -60,20 +61,59 @@ local function observers_new<T...>(
|
||||||
local archetype = r.archetype
|
local archetype = r.archetype
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
callback(entity)
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, id, value)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local cleanup = {}
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms do
|
||||||
if jecs.IS_PAIR(term) then
|
if jecs.IS_PAIR(term) then
|
||||||
term = jecs.ECS_PAIR_FIRST(term)
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
end
|
end
|
||||||
world:added(term, emplaced)
|
local onadded = world:added(term, emplaced)
|
||||||
world:changed(term, emplaced)
|
local onchanged = world:changed(term, emplaced)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onchanged)
|
||||||
|
end
|
||||||
|
|
||||||
|
local without = query.filter_without
|
||||||
|
if without then
|
||||||
|
for _, term in without do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
|
local wc = tgt == jecs.w
|
||||||
|
local onremoved = world:removed(rel, function(entity, id)
|
||||||
|
if not wc and id ~= term then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
else
|
||||||
|
local onremoved = world:removed(term, function(entity, id)
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function disconnect()
|
local function disconnect()
|
||||||
|
@ -86,44 +126,28 @@ local function observers_new<T...>(
|
||||||
observers_on_delete,
|
observers_on_delete,
|
||||||
observer_on_delete
|
observer_on_delete
|
||||||
))
|
))
|
||||||
end
|
|
||||||
|
|
||||||
local function iter()
|
table.clear(archetypes)
|
||||||
local row = i
|
|
||||||
return function()
|
for _, disconnect in cleanup do
|
||||||
if row == 0 then
|
disconnect()
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local observer = {
|
local observer = {
|
||||||
disconnect = disconnect,
|
disconnect = disconnect,
|
||||||
entities = entities,
|
|
||||||
__iter = iter,
|
|
||||||
iter = iter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(observer, observer)
|
|
||||||
|
|
||||||
return (observer :: any) :: Observer<T...>
|
return (observer :: any) :: Observer<T...>
|
||||||
end
|
end
|
||||||
|
|
||||||
local function monitors_new<T...>(
|
local function monitors_new<T...>(query: Query<T...>): Monitor<T...>
|
||||||
query: Query<T...>,
|
|
||||||
callback: ((Entity<nil>, Id<any>, value: any?) -> ())?
|
|
||||||
): Observer<T...>
|
|
||||||
|
|
||||||
query:cached()
|
query:cached()
|
||||||
|
|
||||||
local world = (query :: Query<T...> & { world: World }).world
|
local world = (query :: Query<T...> & { world: World }).world
|
||||||
|
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
local terms = query.ids
|
local terms = query.filter_with :: { jecs.Id<any> }
|
||||||
local first = terms[1]
|
local first = terms[1]
|
||||||
|
|
||||||
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
local observers_on_create = world.observable[jecs.ArchetypeCreate][first]
|
||||||
|
@ -136,42 +160,129 @@ local function monitors_new<T...>(
|
||||||
observer_on_delete.callback = function(archetype)
|
observer_on_delete.callback = function(archetype)
|
||||||
archetypes[archetype.id] = nil
|
archetypes[archetype.id] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for _, archetype in query:archetypes() do
|
||||||
|
archetypes[archetype.id] = true
|
||||||
|
end
|
||||||
local entity_index = world.entity_index :: any
|
local entity_index = world.entity_index :: any
|
||||||
local i = 0
|
|
||||||
local entities = {}
|
local callback_added: ((jecs.Entity) -> ())?
|
||||||
|
local callback_removed: ((jecs.Entity) -> ())?
|
||||||
|
|
||||||
local function emplaced<T, a>(
|
local function emplaced<T, a>(
|
||||||
entity: jecs.Entity<T>,
|
entity: jecs.Entity<T>,
|
||||||
id: jecs.Id<a>,
|
id: jecs.Id<a>,
|
||||||
value: a?
|
value: a?
|
||||||
)
|
)
|
||||||
|
if callback_added == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
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
|
local archetype = r.archetype
|
||||||
|
|
||||||
if archetypes[archetype.id] then
|
if archetypes[archetype.id] then
|
||||||
i += 1
|
callback_added(entity)
|
||||||
entities[i] = entity
|
|
||||||
if callback ~= nil then
|
|
||||||
callback(entity, jecs.OnAdd)
|
|
||||||
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
|
if callback_removed == nil then
|
||||||
if callback ~= nil then
|
return
|
||||||
callback(entity, EcsOnRemove)
|
|
||||||
end
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
if not archetypes[r.archetype.id] then
|
||||||
|
return
|
||||||
end
|
end
|
||||||
|
callback_removed(entity)
|
||||||
|
end
|
||||||
|
|
||||||
|
local cleanup = {}
|
||||||
|
|
||||||
for _, term in terms do
|
for _, term in terms do
|
||||||
if jecs.IS_PAIR(term) then
|
if jecs.IS_PAIR(term) then
|
||||||
term = jecs.ECS_PAIR_FIRST(term)
|
term = jecs.ECS_PAIR_FIRST(term)
|
||||||
end
|
end
|
||||||
world:added(term, emplaced)
|
local onadded = world:added(term, emplaced)
|
||||||
world:removed(term, removed)
|
local onremoved = world:removed(term, removed)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
|
|
||||||
|
local without = query.filter_without
|
||||||
|
if without then
|
||||||
|
for _, term in without do
|
||||||
|
if jecs.IS_PAIR(term) then
|
||||||
|
local rel = jecs.ECS_PAIR_FIRST(term)
|
||||||
|
local tgt = jecs.ECS_PAIR_SECOND(term)
|
||||||
|
local wc = tgt == jecs.w
|
||||||
|
local onadded = world:added(rel, function(entity, id)
|
||||||
|
if callback_removed == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not wc and id ~= term then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_removed(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local onremoved = world:removed(rel, function(entity, id)
|
||||||
|
if callback_added == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not wc and id ~= term then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_added(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
else
|
||||||
|
local onadded = world:added(term, function(entity, id)
|
||||||
|
if callback_removed == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_removed(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
local onremoved = world:removed(term, function(entity, id)
|
||||||
|
if callback_added == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = jecs.record(world, entity)
|
||||||
|
local archetype = r.archetype
|
||||||
|
if archetype then
|
||||||
|
local dst = jecs.archetype_traverse_remove(world, id, archetype)
|
||||||
|
if archetypes[dst.id] then
|
||||||
|
callback_added(entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
table.insert(cleanup, onadded)
|
||||||
|
table.insert(cleanup, onremoved)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function disconnect()
|
local function disconnect()
|
||||||
|
@ -184,31 +295,29 @@ local function monitors_new<T...>(
|
||||||
observers_on_delete,
|
observers_on_delete,
|
||||||
observer_on_delete
|
observer_on_delete
|
||||||
))
|
))
|
||||||
|
|
||||||
|
table.clear(archetypes)
|
||||||
|
|
||||||
|
for _, disconnect in cleanup do
|
||||||
|
disconnect()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function iter()
|
local function monitor_added(callback)
|
||||||
local row = i
|
callback_added = callback
|
||||||
return function()
|
|
||||||
if row == 0 then
|
|
||||||
i = 0
|
|
||||||
table.clear(entities)
|
|
||||||
end
|
|
||||||
local entity = entities[row]
|
|
||||||
row -= 1
|
|
||||||
return entity
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function monitor_removed(callback)
|
||||||
|
callback_removed = callback
|
||||||
end
|
end
|
||||||
|
|
||||||
local observer = {
|
local observer = {
|
||||||
disconnect = disconnect,
|
disconnect = disconnect,
|
||||||
entities = entities,
|
added = monitor_added,
|
||||||
__iter = iter,
|
removed = monitor_removed
|
||||||
iter = iter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setmetatable(observer, observer)
|
return (observer :: any) :: Monitor<T...>
|
||||||
|
|
||||||
return (observer :: any) :: Observer<T...>
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -199,6 +199,7 @@ do
|
||||||
end
|
end
|
||||||
|
|
||||||
local q = world:query(A, B, C, D)
|
local q = world:query(A, B, C, D)
|
||||||
|
q:archetypes()
|
||||||
START()
|
START()
|
||||||
for id in q do
|
for id in q do
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,15 +11,22 @@ end
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local mirror = require("@mirror")
|
local mirror = require("@mirror")
|
||||||
|
|
||||||
type i53 = number
|
|
||||||
|
|
||||||
do
|
do
|
||||||
TITLE(testkit.color.white_underline("Jecs query"))
|
TITLE(testkit.color.white_underline("Jecs query"))
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world() :: jecs.World
|
||||||
do
|
do
|
||||||
TITLE("one component in common")
|
TITLE("one component in common")
|
||||||
|
|
||||||
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
local function view_bench(world: jecs.World,
|
||||||
|
A: jecs.Id,
|
||||||
|
B: jecs.Id,
|
||||||
|
C: jecs.Id,
|
||||||
|
D: jecs.Id,
|
||||||
|
E: jecs.Id,
|
||||||
|
F: jecs.Id,
|
||||||
|
G: jecs.Id,
|
||||||
|
H: jecs.Id
|
||||||
|
)
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do
|
for _ in world:query(A) do
|
||||||
end
|
end
|
||||||
|
@ -131,11 +138,21 @@ end
|
||||||
|
|
||||||
do
|
do
|
||||||
TITLE(testkit.color.white_underline("Mirror query"))
|
TITLE(testkit.color.white_underline("Mirror query"))
|
||||||
local ecs = mirror.World.new()
|
local ecs = mirror.World.new() :: jecs.World
|
||||||
do
|
do
|
||||||
TITLE("one component in common")
|
TITLE("one component in common")
|
||||||
|
|
||||||
local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
local function view_bench(
|
||||||
|
world: mirror.World,
|
||||||
|
A: jecs.Id,
|
||||||
|
B: jecs.Id,
|
||||||
|
C: jecs.Id,
|
||||||
|
D: jecs.Id,
|
||||||
|
E: jecs.Id,
|
||||||
|
F: jecs.Id,
|
||||||
|
G: jecs.Id,
|
||||||
|
H: jecs.Id
|
||||||
|
)
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do
|
for _ in world:query(A) do
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,33 +2,12 @@
|
||||||
--!native
|
--!native
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
|
||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
|
||||||
local newWorld = Matter.World.new()
|
|
||||||
|
|
||||||
local jecs = require(ReplicatedStorage.Lib:Clone())
|
local jecs = require(ReplicatedStorage.Lib:Clone())
|
||||||
local mirror = require(ReplicatedStorage.mirror:Clone())
|
local mirror = require(ReplicatedStorage.mirror:Clone())
|
||||||
local mcs = mirror.world()
|
local mcs = mirror.world()
|
||||||
local ecs = jecs.world()
|
local ecs = jecs.world()
|
||||||
|
|
||||||
local A1 = Matter.component()
|
|
||||||
local A2 = Matter.component()
|
|
||||||
local A3 = Matter.component()
|
|
||||||
local A4 = Matter.component()
|
|
||||||
local A5 = Matter.component()
|
|
||||||
local A6 = Matter.component()
|
|
||||||
local A7 = Matter.component()
|
|
||||||
local A8 = Matter.component()
|
|
||||||
|
|
||||||
local B1 = ecr.component()
|
|
||||||
local B2 = ecr.component()
|
|
||||||
local B3 = ecr.component()
|
|
||||||
local B4 = ecr.component()
|
|
||||||
local B5 = ecr.component()
|
|
||||||
local B6 = ecr.component()
|
|
||||||
local B7 = ecr.component()
|
|
||||||
local B8 = ecr.component()
|
|
||||||
|
|
||||||
local D1 = ecs:component()
|
local D1 = ecs:component()
|
||||||
local D2 = ecs:component()
|
local D2 = ecs:component()
|
||||||
local D3 = ecs:component()
|
local D3 = ecs:component()
|
||||||
|
@ -47,90 +26,53 @@ local E6 = mcs:component()
|
||||||
local E7 = mcs:component()
|
local E7 = mcs:component()
|
||||||
local E8 = mcs:component()
|
local E8 = mcs:component()
|
||||||
|
|
||||||
local registry2 = ecr.registry()
|
|
||||||
|
|
||||||
local function flip()
|
local function flip()
|
||||||
return math.random() >= 0.25
|
return math.random() >= 0.3
|
||||||
end
|
end
|
||||||
|
|
||||||
local N = 2 ^ 16 - 2
|
local N = 2 ^ 16 - 2
|
||||||
local archetypes = {}
|
|
||||||
|
|
||||||
local hm = 0
|
|
||||||
for i = 1, N do
|
for i = 1, N do
|
||||||
local id = registry2.create()
|
|
||||||
local combination = ""
|
|
||||||
local n = newWorld:spawn()
|
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
local m = mcs:entity()
|
local m = mcs:entity()
|
||||||
|
|
||||||
if flip() then
|
if flip() then
|
||||||
registry2:set(id, B1, { value = true })
|
ecs:add(entity, entity)
|
||||||
ecs:set(entity, D1, { value = true })
|
mcs:add(m, m)
|
||||||
newWorld:insert(n, A1({ value = true }))
|
|
||||||
mcs:set(m, E1, { value = 2 })
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "B"
|
ecs:set(entity, D1, true)
|
||||||
registry2:set(id, B2, { value = true })
|
mcs:set(m, E1, true)
|
||||||
ecs:set(entity, D2, { value = true })
|
|
||||||
mcs:set(m, E2, { value = 2 })
|
|
||||||
newWorld:insert(n, A2({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "C"
|
ecs:set(entity, D2, true)
|
||||||
registry2:set(id, B3, { value = true })
|
mcs:set(m, E2, true)
|
||||||
ecs:set(entity, D3, { value = true })
|
|
||||||
mcs:set(m, E3, { value = 2 })
|
|
||||||
newWorld:insert(n, A3({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "D"
|
ecs:set(entity, D3, true)
|
||||||
registry2:set(id, B4, { value = true })
|
mcs:set(m, E3, true)
|
||||||
ecs:set(entity, D4, { value = true })
|
|
||||||
mcs:set(m, E4, { value = 2 })
|
|
||||||
|
|
||||||
newWorld:insert(n, A4({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "E"
|
ecs:set(entity, D4, true)
|
||||||
registry2:set(id, B5, { value = true })
|
mcs:set(m, E4, true)
|
||||||
ecs:set(entity, D5, { value = true })
|
|
||||||
mcs:set(m, E5, { value = 2 })
|
|
||||||
|
|
||||||
newWorld:insert(n, A5({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "F"
|
ecs:set(entity, D5, true)
|
||||||
registry2:set(id, B6, { value = true })
|
mcs:set(m, E5, true)
|
||||||
ecs:set(entity, D6, { value = true })
|
|
||||||
mcs:set(m, E6, { value = 2 })
|
|
||||||
newWorld:insert(n, A6({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "G"
|
ecs:set(entity, D6, true)
|
||||||
registry2:set(id, B7, { value = true })
|
mcs:set(m, E6, true)
|
||||||
ecs:set(entity, D7, { value = true })
|
|
||||||
mcs:set(m, E7, { value = 2 })
|
|
||||||
newWorld:insert(n, A7({ value = true }))
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "H"
|
ecs:set(entity, D7, true)
|
||||||
registry2:set(id, B8, { value = true })
|
mcs:set(m, E7, true)
|
||||||
newWorld:insert(n, A8({ value = true }))
|
|
||||||
ecs:set(entity, D8, { value = true })
|
|
||||||
mcs:set(m, E8, { value = 2 })
|
|
||||||
end
|
end
|
||||||
|
if flip() then
|
||||||
if combination:find("BCDF") then
|
ecs:set(entity, D8, true)
|
||||||
if not archetypes[combination] then
|
mcs:set(m, E8, true)
|
||||||
print(combination)
|
|
||||||
end
|
end
|
||||||
hm += 1
|
|
||||||
end
|
|
||||||
archetypes[combination] = true
|
|
||||||
end
|
end
|
||||||
print("TEST", hm)
|
|
||||||
|
|
||||||
local count = 0
|
local count = 0
|
||||||
|
|
||||||
|
@ -138,7 +80,11 @@ for _, archetype in ecs:query(D2, D4, D6, D8):archetypes() do
|
||||||
count += #archetype.entities
|
count += #archetype.entities
|
||||||
end
|
end
|
||||||
|
|
||||||
print(count)
|
|
||||||
|
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()
|
||||||
|
@ -157,12 +103,12 @@ return {
|
||||||
-- end,
|
-- end,
|
||||||
--
|
--
|
||||||
Mirror = function()
|
Mirror = function()
|
||||||
for entityId, firstComponent in mcs:query(E2, E4, E6, E8) do
|
for entityId, firstComponent in mq do
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
Jecs = function()
|
Jecs = function()
|
||||||
for entityId, firstComponent in ecs:query(D2, D4, D6, D8) do
|
for entityId, firstComponent in jq do
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local world = jecs.World.new()
|
local world = jecs.world()
|
||||||
|
|
||||||
local Position = world:component()
|
local Position = world:component() :: jecs.Id<vector>
|
||||||
local Walking = world:component()
|
local Walking = world:component()
|
||||||
local Name = world:component()
|
local Name = world:component() :: jecs.Id<string>
|
||||||
|
|
||||||
|
local function name(e: jecs.Entity): string
|
||||||
|
return assert(world:get(e, Name))
|
||||||
|
end
|
||||||
|
|
||||||
-- Create an entity with name Bob
|
-- Create an entity with name Bob
|
||||||
local bob = world:entity()
|
local bob = world:entity()
|
||||||
|
|
||||||
-- The set operation finds or creates a component, and sets it.
|
-- The set operation finds or creates a component, and sets it.
|
||||||
world:set(bob, Position, Vector3.new(10, 20, 30))
|
world:set(bob, Position, vector.create(10, 20, 30))
|
||||||
-- Name the entity Bob
|
-- Name the entity Bob
|
||||||
world:set(bob, Name, "Bob")
|
world:set(bob, Name, "Bob")
|
||||||
-- The add operation adds a component without setting a value. This is
|
-- The add operation adds a component without setting a value. This is
|
||||||
|
@ -18,15 +22,16 @@ world:add(bob, Walking)
|
||||||
|
|
||||||
-- Get the value for the Position component
|
-- Get the value for the Position component
|
||||||
local pos = world:get(bob, Position)
|
local pos = world:get(bob, Position)
|
||||||
print(`\{{pos.X}, {pos.Y}, {pos.Z}\}`)
|
assert(pos)
|
||||||
|
print(`\{{pos.x}, {pos.y}, {pos.z}\}`)
|
||||||
|
|
||||||
-- Overwrite the value of the Position component
|
-- Overwrite the value of the Position component
|
||||||
world:set(bob, Position, Vector3.new(40, 50, 60))
|
world:set(bob, Position, vector.create(40, 50, 60))
|
||||||
|
|
||||||
local alice = world:entity()
|
local alice = world:entity()
|
||||||
-- Create another named entity
|
-- Create another named entity
|
||||||
world:set(alice, Name, "Alice")
|
world:set(alice, Name, "Alice")
|
||||||
world:set(alice, Position, Vector3.new(10, 20, 30))
|
world:set(alice, Position, vector.create(10, 20, 30))
|
||||||
world:add(alice, Walking)
|
world:add(alice, Walking)
|
||||||
|
|
||||||
-- Remove tag
|
-- Remove tag
|
||||||
|
@ -34,7 +39,7 @@ world:remove(alice, Walking)
|
||||||
|
|
||||||
-- Iterate all entities with Position
|
-- Iterate all entities with Position
|
||||||
for entity, p in world:query(Position) do
|
for entity, p in world:query(Position) do
|
||||||
print(`{entity}: \{{p.X}, {p.Y}, {p.Z}\}`)
|
print(`{name(entity)}: \{{p.x}, {p.y}, {p.z}\}`)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Output:
|
-- Output:
|
||||||
|
|
60
examples/luau/queries/archetypes/targets.luau
Executable file
60
examples/luau/queries/archetypes/targets.luau
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
-- Using world:target is the recommended way to grab the target in a wildcard
|
||||||
|
-- query. However the random access can add up in very hot paths. Accessing its
|
||||||
|
-- column can drastically improve performance, especially when there are
|
||||||
|
-- multiple adjacent pairs.
|
||||||
|
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
local __ = jecs.Wildcard
|
||||||
|
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
|
local Likes = world:entity()
|
||||||
|
local function name(e, name: string): string
|
||||||
|
if name then
|
||||||
|
world:set(e, jecs.Name, name)
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
return assert(world:get(e, jecs.Name))
|
||||||
|
end
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
name(e1, "e1")
|
||||||
|
local e2 = world:entity()
|
||||||
|
name(e2, "e2")
|
||||||
|
local e3 = world:entity()
|
||||||
|
name(e3, "e3")
|
||||||
|
|
||||||
|
world:add(e1, pair(Likes, e2))
|
||||||
|
world:add(e1, pair(Likes, e3))
|
||||||
|
|
||||||
|
local likes = jecs.component_record(world, pair(Likes, __))
|
||||||
|
assert(likes)
|
||||||
|
|
||||||
|
local likes_cr = likes.records
|
||||||
|
local likes_counts = likes.counts
|
||||||
|
|
||||||
|
local archetypes = world:query(pair(Likes, __)):archetypes()
|
||||||
|
|
||||||
|
for _, archetype in archetypes do
|
||||||
|
local types = archetype.types
|
||||||
|
|
||||||
|
-- Get the starting index which is what the (R, *) alias is at
|
||||||
|
local wc = likes_cr[archetype.id]
|
||||||
|
local count = likes_counts[archetype.id]
|
||||||
|
|
||||||
|
local entities = archetype.entities
|
||||||
|
for i = #entities, 1, -1 do
|
||||||
|
-- It is generally a good idea to iterate backwards on arrays if you
|
||||||
|
-- need to delete the iterated entity to prevent iterator invalidation
|
||||||
|
local entity = entities[i]
|
||||||
|
for cr = wc, wc + count - 1 do
|
||||||
|
local person = jecs.pair_second(world, types[cr])
|
||||||
|
print(`entity ${entity} likes ${person}`)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- entity $273 likes $275
|
||||||
|
-- entity $273 likes $274
|
44
examples/luau/queries/archetypes/visibility_cascades.luau
Executable file
44
examples/luau/queries/archetypes/visibility_cascades.luau
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
-- To get the most out of performance, you can lift the inner loop of queries to
|
||||||
|
-- the system in which you can do archetype-specific optimizations like finding
|
||||||
|
-- the parent once per archetype rather than per entity.
|
||||||
|
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local pair = jecs.pair
|
||||||
|
local ChildOf = jecs.ChildOf
|
||||||
|
local __ = jecs.Wildcard
|
||||||
|
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
|
local Position = world:component() :: jecs.Id<vector>
|
||||||
|
local Visible = world:entity()
|
||||||
|
|
||||||
|
local parent = world:entity()
|
||||||
|
world:set(parent, Position, vector.zero)
|
||||||
|
world:add(parent, Visible)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:set(child, Position, vector.one)
|
||||||
|
world:add(child, pair(ChildOf, parent))
|
||||||
|
|
||||||
|
local parents = jecs.component_record(world, pair(ChildOf, __))
|
||||||
|
assert(parents)
|
||||||
|
|
||||||
|
local parent_cr = parents.records
|
||||||
|
|
||||||
|
local archetypes = world:query(Position, pair(ChildOf, __)):archetypes()
|
||||||
|
|
||||||
|
for _, archetype in archetypes do
|
||||||
|
local types = archetype.types
|
||||||
|
local p = jecs.pair_second(world, types[parent_cr[archetype.id]])
|
||||||
|
if world:has(p, Visible) then
|
||||||
|
local columns = archetype.columns_map
|
||||||
|
local positions = columns[Position]
|
||||||
|
for row, entity in archetype.entities do
|
||||||
|
local pos = positions[row]
|
||||||
|
print(`Child ${entity} of ${p} is visible at {pos}`)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Output:
|
||||||
|
-- Child $274 of $273 is visibile at 1,1,1
|
|
@ -1,5 +1,5 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local world = jecs.World.new()
|
local world = jecs.world()
|
||||||
|
|
||||||
local Position = world:component()
|
local Position = world:component()
|
||||||
local Velocity = world:component()
|
local Velocity = world:component()
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
local jecs = require("@jecs")
|
local jecs = require("@jecs")
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
|
|
||||||
local world = jecs.World.new()
|
local world = jecs.world()
|
||||||
local Name = world:component()
|
local Name = world:component() :: jecs.Id<string>
|
||||||
|
|
||||||
local function named(ctr, name)
|
|
||||||
local e = ctr(world)
|
local function name(e: jecs.Entity): string
|
||||||
world:set(e, Name, name)
|
return assert(world:get(e, Name))
|
||||||
return e
|
|
||||||
end
|
|
||||||
local function name(e)
|
|
||||||
return world:get(e, Name)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local Position = named(world.component, "Position") :: jecs.Entity<vector>
|
local Position = world:component() :: jecs.Id<vector>
|
||||||
|
world:set(Position, Name, "Position")
|
||||||
local Previous = jecs.Rest
|
local Previous = jecs.Rest
|
||||||
|
|
||||||
local added = world
|
local added = world
|
||||||
|
@ -29,10 +26,14 @@ local removed = world
|
||||||
:cached()
|
:cached()
|
||||||
|
|
||||||
|
|
||||||
local e1 = named(world.entity, "e1")
|
local e1 = world:entity()
|
||||||
|
world:set(e1, Name, "e1")
|
||||||
world:set(e1, Position, vector.create(10, 20, 30))
|
world:set(e1, Position, vector.create(10, 20, 30))
|
||||||
local e2 = named(world.entity, "e2")
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:set(e2, Name, "e2")
|
||||||
world:set(e2, Position, vector.create(10, 20, 30))
|
world:set(e2, Position, vector.create(10, 20, 30))
|
||||||
|
|
||||||
for entity, p in added do
|
for entity, p in added do
|
||||||
print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`)
|
print(`Added {name(entity)}: \{{p.x}, {p.y}, {p.z}}`)
|
||||||
world:set(entity, pair(Previous, Position), p)
|
world:set(entity, pair(Previous, Position), p)
|
||||||
|
@ -40,10 +41,10 @@ end
|
||||||
|
|
||||||
world:set(e1, Position, vector.create(999, 999, 1998))
|
world:set(e1, Position, vector.create(999, 999, 1998))
|
||||||
|
|
||||||
for _, archetype in changed:archetypes() do
|
for entity, new, old in changed do
|
||||||
if new ~= old then
|
if new ~= old then
|
||||||
print(`{name(e)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`)
|
print(`{name(entity)}'s Position changed from \{{old.x}, {old.y}, {old.z}\} to \{{new.x}, {new.y}, {new.z}\}`)
|
||||||
world:set(e, pair(Previous, Position), new)
|
world:set(entity, pair(Previous, Position), new)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -3,32 +3,47 @@ local pair = jecs.pair
|
||||||
local ChildOf = jecs.ChildOf
|
local ChildOf = jecs.ChildOf
|
||||||
local __ = jecs.Wildcard
|
local __ = jecs.Wildcard
|
||||||
local Name = jecs.Name
|
local Name = jecs.Name
|
||||||
local world = jecs.World.new()
|
local world = jecs.world()
|
||||||
|
|
||||||
type Id<T = nil> = number & { __T: T }
|
local Voxel = world:component() :: jecs.Id
|
||||||
local Voxel = world:component() :: Id
|
local Position = world:component() :: jecs.Id<vector>
|
||||||
local Position = world:component() :: Id<Vector3>
|
local Perception = world:component() :: jecs.Id<{
|
||||||
local Perception = world:component() :: Id<{
|
|
||||||
range: number,
|
range: number,
|
||||||
fov: number,
|
fov: number,
|
||||||
dir: Vector3,
|
dir: vector,
|
||||||
}>
|
}>
|
||||||
local PrimaryPart = world:component() :: Id<Part>
|
type part = {
|
||||||
|
Position: vector
|
||||||
|
}
|
||||||
|
local PrimaryPart = world:component() :: jecs.Id<part>
|
||||||
|
|
||||||
local local_player = game:GetService("Players").LocalPlayer
|
local local_player = {
|
||||||
|
Character = {
|
||||||
|
PrimaryPart = {
|
||||||
|
Position = vector.create(50, 0, 30)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local workspace = {
|
||||||
|
CurrentCamera = {
|
||||||
|
CFrame = {
|
||||||
|
LookVector = vector.create(0, 0, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
local function distance(a: Vector3, b: Vector3)
|
local function distance(a: vector, b: vector)
|
||||||
return (b - a).Magnitude
|
return vector.magnitude((b - a))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_in_fov(a: Vector3, b: Vector3, forward_dir: Vector3, fov_angle: number)
|
local function is_in_fov(a: vector, b: vector, forward_dir: vector, fov_angle: number)
|
||||||
local to_target = b - a
|
local to_target = b - a
|
||||||
|
|
||||||
local forward_xz = Vector3.new(forward_dir.X, 0, forward_dir.Z).Unit
|
local forward_xz = vector.normalize(vector.create(forward_dir.x, 0, forward_dir.z))
|
||||||
local to_target_xz = Vector3.new(to_target.X, 0, to_target.Z).Unit
|
local to_target_xz = vector.normalize(vector.create(to_target.x, 0, to_target.z))
|
||||||
|
|
||||||
local angle_to_target = math.deg(math.atan2(to_target_xz.Z, to_target_xz.X))
|
local angle_to_target = math.deg(math.atan2(to_target_xz.z, to_target_xz.x))
|
||||||
local forward_angle = math.deg(math.atan2(forward_xz.Z, forward_xz.X))
|
local forward_angle = math.deg(math.atan2(forward_xz.z, forward_xz.z))
|
||||||
|
|
||||||
local angle_difference = math.abs(forward_angle - angle_to_target)
|
local angle_difference = math.abs(forward_angle - angle_to_target)
|
||||||
|
|
||||||
|
@ -42,7 +57,7 @@ end
|
||||||
local map = {}
|
local map = {}
|
||||||
local grid = 50
|
local grid = 50
|
||||||
|
|
||||||
local function add_to_voxel(source: number, position: Vector3, prev_voxel_id: number?)
|
local function add_to_voxel(source: jecs.Entity, position: vector, prev_voxel_id: jecs.Entity?)
|
||||||
local hash = position // grid
|
local hash = position // grid
|
||||||
local voxel_id = map[hash]
|
local voxel_id = map[hash]
|
||||||
if not voxel_id then
|
if not voxel_id then
|
||||||
|
@ -79,7 +94,7 @@ local function update_camera_direction(dt: number)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function perceive_enemies(dt: number)
|
local function perceive_enemies(dt: number)
|
||||||
local it = world:query(Perception, Position, PrimaryPart)
|
local it = world:query(Perception, Position, PrimaryPart):iter()
|
||||||
-- There is only going to be one entity matching the query
|
-- There is only going to be one entity matching the query
|
||||||
local e, self_perception, self_position, self_primary_part = it()
|
local e, self_perception, self_position, self_primary_part = it()
|
||||||
|
|
||||||
|
@ -93,28 +108,28 @@ local function perceive_enemies(dt: number)
|
||||||
|
|
||||||
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
|
if is_in_fov(self_position, target_position, self_perception.dir, self_perception.fov) then
|
||||||
local p = target_position
|
local p = target_position
|
||||||
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.X}, {p.Y}, {p.Z})`)
|
print(`Entity {world:get(e, Name)} can see target {world:get(enemy, Name)} at ({p.x}, {p.y}, {p.z})`)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local player = world:entity()
|
local player = world:entity()
|
||||||
world:set(player, Perception, {
|
world:set(player, Perception, {
|
||||||
range = 100,
|
range = 200,
|
||||||
fov = 90,
|
fov = 90,
|
||||||
dir = Vector3.new(1, 0, 0),
|
dir = vector.create(1, 0, 0),
|
||||||
})
|
})
|
||||||
world:set(player, Name, "LocalPlayer")
|
world:set(player, Name, "LocalPlayer")
|
||||||
local primary_part = (local_player.Character :: Model).PrimaryPart :: Part
|
local primary_part = local_player.Character.PrimaryPart
|
||||||
world:set(player, PrimaryPart, primary_part)
|
world:set(player, PrimaryPart, primary_part)
|
||||||
world:set(player, Position, Vector3.zero)
|
world:set(player, Position, vector.zero)
|
||||||
|
|
||||||
local enemy = world:entity()
|
local enemy = world:entity()
|
||||||
world:set(enemy, Name, "Enemy $1")
|
world:set(enemy, Name, "Enemy $1")
|
||||||
world:set(enemy, Position, Vector3.new(50, 0, 20))
|
world:set(enemy, Position, vector.create(50, 0, 20))
|
||||||
|
|
||||||
add_to_voxel(player, primary_part.Position)
|
add_to_voxel(player, primary_part.Position)
|
||||||
add_to_voxel(enemy, world)
|
add_to_voxel(enemy, assert(world:get(enemy, Position)))
|
||||||
|
|
||||||
local dt = 1 / 60
|
local dt = 1 / 60
|
||||||
reconcile_client_owned_assembly_to_voxel(dt)
|
reconcile_client_owned_assembly_to_voxel(dt)
|
||||||
|
|
20
jecs.d.ts
vendored
20
jecs.d.ts
vendored
|
@ -184,6 +184,11 @@ export class World {
|
||||||
*/
|
*/
|
||||||
cleanup(): void;
|
cleanup(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all instances of specified component
|
||||||
|
*/
|
||||||
|
// purge<T>(component: Id<T>): void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all components and relationships from the given entity, but
|
* Clears all components and relationships from the given entity, but
|
||||||
* does not delete the entity from the world.
|
* does not delete the entity from the world.
|
||||||
|
@ -231,6 +236,12 @@ export class World {
|
||||||
*/
|
*/
|
||||||
contains(entity: Entity): boolean;
|
contains(entity: Entity): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an entity exists in the world.
|
||||||
|
* @param entity The entity to verify.
|
||||||
|
*/
|
||||||
|
contains(entity: number): entity is Entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an entity with the given ID is currently alive, ignoring its generation.
|
* Checks if an entity with the given ID is currently alive, ignoring its generation.
|
||||||
* @param entity The entity to verify.
|
* @param entity The entity to verify.
|
||||||
|
@ -339,12 +350,19 @@ export type ComponentRecord = {
|
||||||
export function component_record(world: World, id: Id): ComponentRecord;
|
export function component_record(world: World, id: Id): ComponentRecord;
|
||||||
|
|
||||||
type TagToUndefined<T> = T extends TagDiscriminator ? undefined : T
|
type TagToUndefined<T> = T extends TagDiscriminator ? undefined : T
|
||||||
|
type TrimOptional<T extends unknown[]> = T extends [...infer L, infer R]
|
||||||
|
? unknown extends R
|
||||||
|
? L | T | TrimOptional<L>
|
||||||
|
: R extends undefined
|
||||||
|
? L | T | TrimOptional<L>
|
||||||
|
: T
|
||||||
|
: T
|
||||||
|
|
||||||
export function bulk_insert<const C extends Id[]>(
|
export function bulk_insert<const C extends Id[]>(
|
||||||
world: World,
|
world: World,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
ids: C,
|
ids: C,
|
||||||
values: { [K in keyof C]: TagToUndefined<InferComponent<C[K]>> },
|
values: TrimOptional<{ [K in keyof C]: TagToUndefined<InferComponent<C[K]>> }>,
|
||||||
): void;
|
): void;
|
||||||
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void;
|
export function bulk_remove(world: World, entity: Entity, ids: Id[]): void;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@rbxts/jecs",
|
"name": "@rbxts/jecs",
|
||||||
"version": "0.9.0-rc.12",
|
"version": "0.9.0",
|
||||||
"description": "Stupidly fast Entity Component System",
|
"description": "Stupidly fast Entity Component System",
|
||||||
"main": "jecs.luau",
|
"main": "jecs.luau",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
392
test/addons/ob.luau
Executable file
392
test/addons/ob.luau
Executable file
|
@ -0,0 +1,392 @@
|
||||||
|
local jecs = require("@jecs")
|
||||||
|
local testkit = require("@testkit")
|
||||||
|
local test = testkit.test()
|
||||||
|
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
||||||
|
local FOCUS = test.FOCUS
|
||||||
|
local ob = require("@addons/ob")
|
||||||
|
|
||||||
|
TEST("addons/ob::observer", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
do CASE "should match against archetypes that were already created"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:add(e1, A)
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.observer(world:query(A), function()
|
||||||
|
c+=1
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:remove(e1, A)
|
||||||
|
world:add(e1, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter observer at query:without(pair(R, t1)) when adding pair(R, t2)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, jecs.pair(B, D))
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
|
||||||
|
ob.observer(world:query(A):without(jecs.pair(B, C)), function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter observer at query:without(pair(R, t1)) when removing pair(R, t1)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, jecs.pair(B, D))
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.observer(world:query(A):without(jecs.pair(B, D)), function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:remove(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:remove(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==3)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter observer at query:without(pair(R, *)) when adding pair(R, t1)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.observer(world:query(A):without(jecs.pair(B, jecs.w)), function() c+= 1 end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter observer at query:without(t1) when removing t1"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.observer(world:query(A):without(B), function() c+= 1 end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, B)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, B)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:remove(child, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "observers should accept pairs"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
ob.observer(world:query(jecs.pair(A, B)), function() c+= 1 end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 2)
|
||||||
|
|
||||||
|
world:remove(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Ensure ordering between signals and observers"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local count = 1
|
||||||
|
local function counter()
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
ob.observer(world:query(A, B), counter)
|
||||||
|
|
||||||
|
world:added(A, counter)
|
||||||
|
world:added(A, counter)
|
||||||
|
|
||||||
|
for _ in world:query(A) do
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
CHECK(count == 3)
|
||||||
|
|
||||||
|
world:add(e, B)
|
||||||
|
CHECK(count == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Rematch entities in observers"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
local count = 1
|
||||||
|
local function counter()
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
ob.observer(world:query(A), counter)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 2)
|
||||||
|
world:remove(e, A)
|
||||||
|
CHECK(count == 2)
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 3)
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 4)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Call off pairs"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
local callcount = 1
|
||||||
|
world:added(A, function(entity)
|
||||||
|
callcount += 1
|
||||||
|
end)
|
||||||
|
world:added(A, function(entity)
|
||||||
|
callcount += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
local e2 = world:entity()
|
||||||
|
|
||||||
|
world:add(e2, jecs.pair(A, e))
|
||||||
|
world:add(e, jecs.pair(A, e2))
|
||||||
|
CHECK(callcount == 1 + 2 * 2)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
TEST("addons/ob::monitor", function()
|
||||||
|
local world = jecs.world()
|
||||||
|
do CASE "should match against archetypes that were already created"
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:add(e1, A)
|
||||||
|
|
||||||
|
local monitor = ob.monitor(world:query(A))
|
||||||
|
local c = 1
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
world:remove(e1, A)
|
||||||
|
world:add(e1, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter monitor at query:without(pair(R, t1)) when adding pair(R, t2)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, jecs.pair(B, D))
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local r = 1
|
||||||
|
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, C)))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
r += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==1)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter monitor at query:without(pair(R, t1)) when removing pair(R, t1)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:add(e, A)
|
||||||
|
world:add(e, jecs.pair(B, D))
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local r = 1
|
||||||
|
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, D)))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
r += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==2)
|
||||||
|
world:remove(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==2)
|
||||||
|
world:remove(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==3)
|
||||||
|
CHECK(r==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter monitor at query:without(pair(R, *)) when adding pair(R, t1)"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local r = 1
|
||||||
|
local monitor = ob.monitor(world:query(A):without(jecs.pair(B, jecs.w)))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
r += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:add(child, jecs.pair(B, D))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==2)
|
||||||
|
world:add(child, jecs.pair(B, C))
|
||||||
|
CHECK(c==2)
|
||||||
|
CHECK(r==2)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Should enter monitor at query:without(t1) when removing t1"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local monitor = ob.monitor(world:query(A):without(B))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, B)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:add(child, A)
|
||||||
|
CHECK(c==1)
|
||||||
|
world:remove(child, B)
|
||||||
|
CHECK(c==2)
|
||||||
|
world:remove(child, A)
|
||||||
|
CHECK(c==3)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "monitors should accept pairs"
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local c = 1
|
||||||
|
local monitor = ob.monitor(world:query(jecs.pair(A, B)))
|
||||||
|
monitor.added(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
monitor.removed(function()
|
||||||
|
c += 1
|
||||||
|
end)
|
||||||
|
|
||||||
|
local child = world:entity()
|
||||||
|
world:add(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 2)
|
||||||
|
|
||||||
|
world:remove(child, jecs.pair(A, B))
|
||||||
|
CHECK(c == 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "Don't report changed components in monitor"
|
||||||
|
local A = world:component()
|
||||||
|
local count = 1
|
||||||
|
local function counter()
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
local monitor = ob.monitor(world:query(A))
|
||||||
|
monitor.added(counter)
|
||||||
|
monitor.removed(counter)
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 2)
|
||||||
|
world:remove(e, A)
|
||||||
|
CHECK(count == 3)
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 4)
|
||||||
|
world:set(e, A, false)
|
||||||
|
CHECK(count == 4)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return FINISH()
|
|
@ -1,114 +0,0 @@
|
||||||
local jecs = require("@jecs")
|
|
||||||
local testkit = require("@testkit")
|
|
||||||
local test = testkit.test()
|
|
||||||
local CASE, TEST, FINISH, CHECK = test.CASE, test.TEST, test.FINISH, test.CHECK
|
|
||||||
local FOCUS = test.FOCUS
|
|
||||||
local ob = require("@addons/ob")
|
|
||||||
|
|
||||||
TEST("addons/observers", function()
|
|
||||||
|
|
||||||
local world = jecs.world()
|
|
||||||
do CASE "monitors should accept pairs"
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local c = 1
|
|
||||||
ob.monitor(world:query(jecs.pair(A, B)), function (_, event)
|
|
||||||
c += 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
local child = world:entity()
|
|
||||||
world:add(child, jecs.pair(A, B))
|
|
||||||
CHECK(c == 2)
|
|
||||||
|
|
||||||
world:remove(child, jecs.pair(A, B))
|
|
||||||
CHECK(c == 3)
|
|
||||||
end
|
|
||||||
do CASE "Ensure ordering between signals and observers"
|
|
||||||
local A = world:component()
|
|
||||||
local B = world:component()
|
|
||||||
|
|
||||||
local count = 1
|
|
||||||
local function counter()
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
ob.observer(world:query(A, B), counter)
|
|
||||||
|
|
||||||
world:added(A, counter)
|
|
||||||
world:added(A, counter)
|
|
||||||
|
|
||||||
for _ in world:query(A) do
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:add(e, A)
|
|
||||||
CHECK(count == 3)
|
|
||||||
|
|
||||||
world:add(e, B)
|
|
||||||
CHECK(count == 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Rematch entities in observers"
|
|
||||||
local A = world:component()
|
|
||||||
|
|
||||||
local count = 1
|
|
||||||
local function counter()
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
ob.observer(world:query(A), counter)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 2)
|
|
||||||
world:remove(e, A)
|
|
||||||
CHECK(count == 2)
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 3)
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Don't report changed components in monitor"
|
|
||||||
local A = world:component()
|
|
||||||
local count = 1
|
|
||||||
local function counter()
|
|
||||||
count += 1
|
|
||||||
end
|
|
||||||
|
|
||||||
ob.monitor(world:query(A), counter)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 2)
|
|
||||||
world:remove(e, A)
|
|
||||||
CHECK(count == 3)
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 4)
|
|
||||||
world:set(e, A, false)
|
|
||||||
CHECK(count == 4)
|
|
||||||
end
|
|
||||||
|
|
||||||
do CASE "Call off pairs"
|
|
||||||
local A = world:component()
|
|
||||||
|
|
||||||
local callcount = 1
|
|
||||||
world:added(A, function(entity)
|
|
||||||
callcount += 1
|
|
||||||
end)
|
|
||||||
world:added(A, function(entity)
|
|
||||||
callcount += 1
|
|
||||||
end)
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
local e2 = world:entity()
|
|
||||||
|
|
||||||
world:add(e2, jecs.pair(A, e))
|
|
||||||
world:add(e, jecs.pair(A, e2))
|
|
||||||
CHECK(callcount == 1 + 2 * 2)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
return FINISH()
|
|
357
test/tests.luau
357
test/tests.luau
|
@ -24,39 +24,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
TEST("ardi", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
local r = world:entity()
|
|
||||||
world:add(r, jecs.pair(jecs.OnDelete, jecs.Delete))
|
|
||||||
|
|
||||||
local e = world:entity()
|
|
||||||
local e1 = world:entity()
|
|
||||||
world:add(e, jecs.pair(r, e1))
|
|
||||||
|
|
||||||
world:delete(r)
|
|
||||||
CHECK(not world:contains(e))
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("dai", function()
|
|
||||||
local world = jecs.world()
|
|
||||||
local C = world:component()
|
|
||||||
|
|
||||||
world:set(C, jecs.Name, "C")
|
|
||||||
CHECK(world:get(C, jecs.Name) == "C")
|
|
||||||
world:entity(2000)
|
|
||||||
CHECK(world:get(C, jecs.Name) == "C")
|
|
||||||
end)
|
|
||||||
|
|
||||||
TEST("another axen banger", function()
|
|
||||||
-- taken from jecs.luau
|
|
||||||
local world = jecs.world()
|
|
||||||
world:range(2000, 3000)
|
|
||||||
|
|
||||||
local e0v1_id = jecs.ECS_COMBINE(1000, 1) -- id can be both within or outside the world's range
|
|
||||||
local e0v1 = world:entity(e0v1_id)
|
|
||||||
assert(world:contains(e0v1)) -- fails
|
|
||||||
end)
|
|
||||||
TEST("Ensure archetype edges get cleaned", function()
|
TEST("Ensure archetype edges get cleaned", function()
|
||||||
local A = jecs.component()
|
local A = jecs.component()
|
||||||
local B = jecs.component()
|
local B = jecs.component()
|
||||||
|
@ -91,6 +58,7 @@ TEST("Ensure archetype edges get cleaned", function()
|
||||||
CHECK(false)
|
CHECK(false)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("repeated entity cached query", function()
|
TEST("repeated entity cached query", function()
|
||||||
local pair = jecs.pair
|
local pair = jecs.pair
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
@ -362,6 +330,20 @@ TEST("bulk", function()
|
||||||
CHECK(world:get(e, c2) == 123)
|
CHECK(world:get(e, c2) == 123)
|
||||||
CHECK(world:get(e, c3) == "hello")
|
CHECK(world:get(e, c3) == "hello")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "Should ensure archetype ids are sorted"
|
||||||
|
local world = jecs.world()
|
||||||
|
local c1, c2, c3 = world:component(), world:component(), world:component()
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
jecs.bulk_insert(world, e, { c2, c1 }, { 2, 1 })
|
||||||
|
jecs.bulk_insert(world, e, { c1 }, { 1 })
|
||||||
|
world:set(e, c3, 3)
|
||||||
|
|
||||||
|
CHECK(world:get(e, c1) == 1)
|
||||||
|
CHECK(world:get(e, c2) == 2)
|
||||||
|
CHECK(world:get(e, c3) == 3)
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("repro", function()
|
TEST("repro", function()
|
||||||
|
@ -550,9 +532,9 @@ TEST("world:add()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:children()", function()
|
TEST("world:children()", function()
|
||||||
local world = jecs.world()
|
|
||||||
local C = jecs.component()
|
local C = jecs.component()
|
||||||
local T = jecs.tag()
|
local T = jecs.tag()
|
||||||
|
local world = jecs.world()
|
||||||
|
|
||||||
local e1 = world:entity()
|
local e1 = world:entity()
|
||||||
world:set(e1, C, true)
|
world:set(e1, C, true)
|
||||||
|
@ -591,100 +573,146 @@ TEST("world:children()", function()
|
||||||
jecs.ECS_META_RESET()
|
jecs.ECS_META_RESET()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:clear()", function()
|
-- TEST("world:purge()", function()
|
||||||
do CASE "should remove its components"
|
-- do CASE "should remove all instances of specified component"
|
||||||
|
-- local world = jecs.world()
|
||||||
|
-- local A = world:component()
|
||||||
|
-- local B = world:component()
|
||||||
|
|
||||||
|
-- local e = world:entity()
|
||||||
|
-- local e1 = world:entity()
|
||||||
|
-- local _e2 = world:entity()
|
||||||
|
|
||||||
|
-- world:set(e, A, true)
|
||||||
|
-- world:set(e, B, true)
|
||||||
|
|
||||||
|
-- world:set(e1, A, true)
|
||||||
|
-- world:set(e1, B, true)
|
||||||
|
|
||||||
|
-- CHECK(world:get(e, A))
|
||||||
|
-- CHECK(world:get(e, B))
|
||||||
|
|
||||||
|
-- world:purge(A)
|
||||||
|
-- CHECK(world:get(e, A) == nil)
|
||||||
|
-- CHECK(world:get(e, B))
|
||||||
|
-- CHECK(world:get(e1, A) == nil)
|
||||||
|
-- CHECK(world:get(e1, B))
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- do CASE "remove purged component from entities"
|
||||||
|
-- local world = jecs.world()
|
||||||
|
-- local A = world:component()
|
||||||
|
-- local B = world:component()
|
||||||
|
-- local C = world:component()
|
||||||
|
|
||||||
|
-- do
|
||||||
|
-- local id1 = world:entity()
|
||||||
|
-- local id2 = world:entity()
|
||||||
|
-- local id3 = world:entity()
|
||||||
|
|
||||||
|
-- world:set(id1, A, true)
|
||||||
|
|
||||||
|
-- world:set(id2, A, true)
|
||||||
|
-- world:set(id2, B, true)
|
||||||
|
|
||||||
|
-- world:set(id3, A, true)
|
||||||
|
-- world:set(id3, B, true)
|
||||||
|
-- world:set(id3, C, true)
|
||||||
|
|
||||||
|
-- world:purge(A)
|
||||||
|
|
||||||
|
-- CHECK(not world:has(id1, A))
|
||||||
|
-- CHECK(not world:has(id2, A))
|
||||||
|
-- CHECK(not world:has(id3, A))
|
||||||
|
|
||||||
|
-- CHECK(world:has(id2, B))
|
||||||
|
-- CHECK(world:has(id3, B, C))
|
||||||
|
|
||||||
|
-- world:purge(C)
|
||||||
|
|
||||||
|
-- CHECK(world:has(id2, B))
|
||||||
|
-- CHECK(world:has(id3, B))
|
||||||
|
|
||||||
|
-- CHECK(world:contains(A))
|
||||||
|
-- CHECK(world:contains(C))
|
||||||
|
-- CHECK(world:has(A, jecs.Component))
|
||||||
|
-- CHECK(world:has(B, jecs.Component))
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- do
|
||||||
|
-- local id1 = world:entity()
|
||||||
|
-- local id2 = world:entity()
|
||||||
|
-- local id3 = world:entity()
|
||||||
|
|
||||||
|
-- local tgt = world:entity()
|
||||||
|
|
||||||
|
-- world:add(id1, pair(A, tgt))
|
||||||
|
-- world:add(id1, pair(B, tgt))
|
||||||
|
-- world:add(id1, pair(C, tgt))
|
||||||
|
|
||||||
|
-- world:add(id2, pair(A, tgt))
|
||||||
|
-- world:add(id2, pair(B, tgt))
|
||||||
|
-- world:add(id2, pair(C, tgt))
|
||||||
|
|
||||||
|
-- world:add(id3, pair(A, tgt))
|
||||||
|
-- world:add(id3, pair(B, tgt))
|
||||||
|
-- world:add(id3, pair(C, tgt))
|
||||||
|
|
||||||
|
-- world:purge(B)
|
||||||
|
-- CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
|
||||||
|
-- CHECK(not world:has(id1, pair(B, tgt)))
|
||||||
|
-- CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
|
||||||
|
-- CHECK(not world:has(id1, pair(B, tgt)))
|
||||||
|
-- CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
|
||||||
|
|
||||||
|
-- end
|
||||||
|
|
||||||
|
-- end
|
||||||
|
-- end)
|
||||||
|
|
||||||
|
TEST("world:clear", function()
|
||||||
|
do CASE "remove all components on entity"
|
||||||
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 e = world:entity()
|
local e = world:entity()
|
||||||
local e1 = world:entity()
|
|
||||||
local _e2 = world:entity()
|
|
||||||
|
|
||||||
world:set(e, A, true)
|
world:set(e, A, true)
|
||||||
world:set(e, B, true)
|
world:set(e, B, true)
|
||||||
|
|
||||||
world:set(e1, A, true)
|
world:clear(e)
|
||||||
world:set(e1, B, true)
|
|
||||||
|
|
||||||
CHECK(world:get(e, A))
|
CHECK(world:contains(e))
|
||||||
CHECK(world:get(e, B))
|
CHECK(not world:has(e, A))
|
||||||
|
CHECK(not world:has(e, B))
|
||||||
world:clear(A)
|
print(jecs.record(world, e).archetype == nil::any)
|
||||||
CHECK(world:get(e, A) == nil)
|
|
||||||
CHECK(world:get(e, B))
|
|
||||||
CHECK(world:get(e1, A) == nil)
|
|
||||||
CHECK(world:get(e1, B))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE "remove cleared ID from entities"
|
do CASE "should invoke hooks"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local A = world:component()
|
local A = world:component()
|
||||||
|
local called = 0
|
||||||
|
world:set(A, jecs.OnRemove, function()
|
||||||
|
called += 1
|
||||||
|
end)
|
||||||
|
|
||||||
local B = world:component()
|
local B = world:component()
|
||||||
local C = world:component()
|
world:set(B, jecs.OnRemove, function()
|
||||||
|
called += 1
|
||||||
|
end)
|
||||||
|
|
||||||
do
|
local e = world:entity()
|
||||||
local id1 = world:entity()
|
|
||||||
local id2 = world:entity()
|
|
||||||
local id3 = world:entity()
|
|
||||||
|
|
||||||
world:set(id1, A, true)
|
world:set(e, A, true)
|
||||||
|
world:set(e, B, true)
|
||||||
|
|
||||||
world:set(id2, A, true)
|
world:clear(e)
|
||||||
world:set(id2, B, true)
|
|
||||||
|
|
||||||
world:set(id3, A, true)
|
|
||||||
world:set(id3, B, true)
|
|
||||||
world:set(id3, C, true)
|
|
||||||
|
|
||||||
world:clear(A)
|
|
||||||
|
|
||||||
CHECK(not world:has(id1, A))
|
|
||||||
CHECK(not world:has(id2, A))
|
|
||||||
CHECK(not world:has(id3, A))
|
|
||||||
|
|
||||||
CHECK(world:has(id2, B))
|
|
||||||
CHECK(world:has(id3, B, C))
|
|
||||||
|
|
||||||
world:clear(C)
|
|
||||||
|
|
||||||
CHECK(world:has(id2, B))
|
|
||||||
CHECK(world:has(id3, B))
|
|
||||||
|
|
||||||
CHECK(world:contains(A))
|
|
||||||
CHECK(world:contains(C))
|
|
||||||
CHECK(world:has(A, jecs.Component))
|
|
||||||
CHECK(world:has(B, jecs.Component))
|
|
||||||
end
|
|
||||||
|
|
||||||
do
|
|
||||||
local id1 = world:entity()
|
|
||||||
local id2 = world:entity()
|
|
||||||
local id3 = world:entity()
|
|
||||||
|
|
||||||
local tgt = world:entity()
|
|
||||||
|
|
||||||
world:add(id1, pair(A, tgt))
|
|
||||||
world:add(id1, pair(B, tgt))
|
|
||||||
world:add(id1, pair(C, tgt))
|
|
||||||
|
|
||||||
world:add(id2, pair(A, tgt))
|
|
||||||
world:add(id2, pair(B, tgt))
|
|
||||||
world:add(id2, pair(C, tgt))
|
|
||||||
|
|
||||||
world:add(id3, pair(A, tgt))
|
|
||||||
world:add(id3, pair(B, tgt))
|
|
||||||
world:add(id3, pair(C, tgt))
|
|
||||||
|
|
||||||
world:clear(B)
|
|
||||||
CHECK(world:has(id1, pair(A, tgt), pair(C, tgt)))
|
|
||||||
CHECK(not world:has(id1, pair(B, tgt)))
|
|
||||||
CHECK(world:has(id2, pair(A, tgt), pair(C, tgt)))
|
|
||||||
CHECK(not world:has(id1, pair(B, tgt)))
|
|
||||||
CHECK(world:has(id3, pair(A, tgt), pair(C, tgt)))
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
CHECK(world:contains(e))
|
||||||
|
CHECK(not world:has(e, A))
|
||||||
|
CHECK(not world:has(e, B))
|
||||||
|
CHECK(called == 2)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -756,6 +784,63 @@ TEST("world:contains()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:delete()", function()
|
TEST("world:delete()", function()
|
||||||
|
do CASE "OnDelete cleanup policy cascades deletion to entites with idr_r pairs"
|
||||||
|
local world = jecs.world()
|
||||||
|
local r = world:entity()
|
||||||
|
world:add(r, jecs.pair(jecs.OnDelete, jecs.Delete))
|
||||||
|
|
||||||
|
local e = world:entity()
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:add(e, jecs.pair(r, e1))
|
||||||
|
|
||||||
|
world:delete(r)
|
||||||
|
CHECK(not world:contains(e))
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "OnDeleteTarget works correctly regardless of adjacent archetype iteration order"
|
||||||
|
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
|
||||||
|
if true then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "Should delete children in different archetypes if they have the same parent"
|
do CASE "Should delete children in different archetypes if they have the same parent"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
|
|
||||||
|
@ -810,7 +895,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)
|
||||||
|
@ -1281,7 +1365,6 @@ TEST("world:added", function()
|
||||||
end
|
end
|
||||||
|
|
||||||
do CASE ""
|
do CASE ""
|
||||||
local world = jecs.world()
|
|
||||||
local IsNearby = world:component()
|
local IsNearby = world:component()
|
||||||
world:set(IsNearby, jecs.Name, "IsNearby")
|
world:set(IsNearby, jecs.Name, "IsNearby")
|
||||||
local person1, person2 = world:entity(), world:entity()
|
local person1, person2 = world:entity(), world:entity()
|
||||||
|
@ -1380,7 +1463,7 @@ TEST("world:range()", function()
|
||||||
CHECK(world.entity_index.alive_count == 400)
|
CHECK(world.entity_index.alive_count == 400)
|
||||||
CHECK(e)
|
CHECK(e)
|
||||||
end
|
end
|
||||||
do CASE "axen"
|
do CASE "entity ID reuse works correctly across different world ranges"
|
||||||
local base = jecs.world()
|
local base = jecs.world()
|
||||||
base:range(1_000, 2_000)
|
base:range(1_000, 2_000)
|
||||||
|
|
||||||
|
@ -1492,6 +1575,24 @@ TEST("world:range()", function()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
TEST("world:entity()", function()
|
TEST("world:entity()", function()
|
||||||
|
do CASE "entity mirroring preserves IDs across world ranges"
|
||||||
|
local world = jecs.world()
|
||||||
|
world:range(2000, 3000)
|
||||||
|
|
||||||
|
local e0v1_id = jecs.ECS_COMBINE(1000, 1) -- id can be both within or outside the world's range
|
||||||
|
local e0v1 = world:entity(e0v1_id)
|
||||||
|
CHECK(world:contains(e0v1)) -- fails
|
||||||
|
end
|
||||||
|
|
||||||
|
do CASE "component names persist after entity creation"
|
||||||
|
local world = jecs.world()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
world:set(C, jecs.Name, "C")
|
||||||
|
CHECK(world:get(C, jecs.Name) == "C")
|
||||||
|
world:entity(2000)
|
||||||
|
CHECK(world:get(C, jecs.Name) == "C")
|
||||||
|
end
|
||||||
do CASE "desired id"
|
do CASE "desired id"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local id = world:entity()
|
local id = world:entity()
|
||||||
|
@ -1607,6 +1708,36 @@ end)
|
||||||
TEST("world:query()", function()
|
TEST("world:query()", function()
|
||||||
local N = 2^8
|
local N = 2^8
|
||||||
|
|
||||||
|
do CASE "queries should accept zero-ids provided they use :with for the leading component"
|
||||||
|
local world = jecs.world()
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
local e1 = world:entity()
|
||||||
|
world:set(e1, A, "A")
|
||||||
|
|
||||||
|
local e2 = world:entity()
|
||||||
|
world:set(e2, A, "A")
|
||||||
|
world:set(e2, B, "B")
|
||||||
|
|
||||||
|
for e, a in world:query():with(A) do
|
||||||
|
CHECK(e == e1 or e == e2)
|
||||||
|
CHECK(a == nil)
|
||||||
|
if e == e1 then
|
||||||
|
CHECK(world:has(e1, A))
|
||||||
|
CHECK(not world:has(e1, B))
|
||||||
|
elseif e == e2 then
|
||||||
|
CHECK(world:has(e2, A, B))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for e, a in world:query():with(A):without(B) do
|
||||||
|
CHECK(e == e1)
|
||||||
|
CHECK(a == nil)
|
||||||
|
CHECK(world:has(e1, A))
|
||||||
|
CHECK(not world:has(e1, B))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
do CASE "cached"
|
do CASE "cached"
|
||||||
local world = jecs.world()
|
local world = jecs.world()
|
||||||
local Foo = world:component()
|
local Foo = world:component()
|
||||||
|
@ -2510,7 +2641,7 @@ TEST("#repro2", function()
|
||||||
local entity = world:entity()
|
local entity = world:entity()
|
||||||
world:set(entity, pair(Lifetime, Particle), 1)
|
world:set(entity, pair(Lifetime, Particle), 1)
|
||||||
world:set(entity, pair(Lifetime, Beam), 2)
|
world:set(entity, pair(Lifetime, Beam), 2)
|
||||||
world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
|
world:set(entity, pair(world:component(), world:component()), 6) -- noise
|
||||||
|
|
||||||
CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
|
CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
|
||||||
CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
|
CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "ukendio/jecs"
|
name = "ukendio/jecs"
|
||||||
version = "0.9.0-rc.12"
|
version = "0.9.0"
|
||||||
registry = "https://github.com/UpliftGames/wally-index"
|
registry = "https://github.com/UpliftGames/wally-index"
|
||||||
realm = "shared"
|
realm = "shared"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
Loading…
Reference in a new issue