mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 09:30:03 +00:00
Merge branch 'main' of https://github.com/Ukendio/jecs into fix-remove-none-component
This commit is contained in:
commit
79f821ff72
18 changed files with 1237 additions and 3788 deletions
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="logo.png" />
|
<img src="jecs_darkmode.svg#gh-dark-mode-only" width=50%/>
|
||||||
|
<img src="jecs_lightmode.svg#gh-light-mode-only" width=50%/>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](LICENSE-APACHE)
|
[](LICENSE-APACHE)
|
||||||
|
|
|
@ -3,4 +3,4 @@ wally = "upliftgames/wally@0.3.1"
|
||||||
rojo = "rojo-rbx/rojo@7.4.1"
|
rojo = "rojo-rbx/rojo@7.4.1"
|
||||||
stylua = "johnnymorganz/stylua@0.19.1"
|
stylua = "johnnymorganz/stylua@0.19.1"
|
||||||
selene = "kampfkarren/selene@0.26.1"
|
selene = "kampfkarren/selene@0.26.1"
|
||||||
wally-patch-package="Barocena/wally-patch-package@1.2.1"
|
wally-patch-package = "Barocena/wally-patch-package@1.2.1"
|
||||||
|
|
372
benches/exhaustive.lua
Normal file
372
benches/exhaustive.lua
Normal file
|
@ -0,0 +1,372 @@
|
||||||
|
local testkit = require("../testkit")
|
||||||
|
local jecs = require("../lib/init")
|
||||||
|
local ecr = require("../DevPackages/_Index/centau_ecr@0.8.0/ecr/src/ecr")
|
||||||
|
|
||||||
|
|
||||||
|
local BENCH, START = testkit.benchmark()
|
||||||
|
|
||||||
|
local function TITLE(title: string)
|
||||||
|
print()
|
||||||
|
print(testkit.color.white(title))
|
||||||
|
end
|
||||||
|
|
||||||
|
local N = 2^16-2
|
||||||
|
|
||||||
|
type i53 = number
|
||||||
|
|
||||||
|
do TITLE "create"
|
||||||
|
BENCH("entity", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:entity()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- component benchmarks
|
||||||
|
|
||||||
|
--todo: perform the same benchmarks for multiple components.?
|
||||||
|
-- these kind of operations only support 1 component at a time, which is
|
||||||
|
-- a shame, especially for archetypes where moving components is expensive.
|
||||||
|
|
||||||
|
do TITLE "set"
|
||||||
|
BENCH("add 1 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
entities[i] = world:entity()
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:set(entities[i], A, i)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("change 1 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local e = world:entity()
|
||||||
|
world:set(e, A, 1)
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:set(e, A, 2)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE "remove"
|
||||||
|
BENCH("1 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
entities[i] = id
|
||||||
|
world:set(id, A, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:remove(entities[i], A)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE "get"
|
||||||
|
BENCH("1 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
entities[i] = id
|
||||||
|
world:set(id, A, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
-- ? curious why the overhead is roughly 80 ns.
|
||||||
|
world:get(entities[i], A)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("2 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
entities[i] = id
|
||||||
|
world:set(id, A, true)
|
||||||
|
world:set(id, B, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:get(entities[i], A, B)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("3 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
entities[i] = id
|
||||||
|
world:set(id, A, true)
|
||||||
|
world:set(id, B, true)
|
||||||
|
world:set(id, C, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:get(entities[i], A, B, C)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("4 component", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
local entities = {}
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
entities[i] = id
|
||||||
|
world:set(id, A, true)
|
||||||
|
world:set(id, B, true)
|
||||||
|
world:set(id, C, true)
|
||||||
|
world:set(id, D, true)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, START(N) do
|
||||||
|
world:get(entities[i], A, B, C, D)
|
||||||
|
end
|
||||||
|
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE (testkit.color.white_underline("Jecs query"))
|
||||||
|
|
||||||
|
local function count(query: () -> ())
|
||||||
|
local n = 0
|
||||||
|
for _ in query do
|
||||||
|
n += 1
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
local function flip()
|
||||||
|
return math.random() > 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
local function view_bench(
|
||||||
|
world: jecs.World,
|
||||||
|
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53
|
||||||
|
)
|
||||||
|
|
||||||
|
BENCH("1 component", function()
|
||||||
|
START(count(world:query(A)))
|
||||||
|
for _ in world:query(A) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("2 component", function()
|
||||||
|
START(count(world:query(A, B)))
|
||||||
|
for _ in world:query(A, B) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("4 component", function()
|
||||||
|
START(count(world:query(A, B, C, D)))
|
||||||
|
for _ in world:query(A, B, C, D) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("8 component", function()
|
||||||
|
START(count(world:query(A, B, C, D, E, F, G, H)))
|
||||||
|
for _ in world:query(A, B, C, D, E, F, G, H) do end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE "random components"
|
||||||
|
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
local E = world:component()
|
||||||
|
local F = world:component()
|
||||||
|
local G = world:component()
|
||||||
|
local H = world:component()
|
||||||
|
local I = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
if flip() then world:set(id, A, true) end
|
||||||
|
if flip() then world:set(id, B, true) end
|
||||||
|
if flip() then world:set(id, C, true) end
|
||||||
|
if flip() then world:set(id, D, true) end
|
||||||
|
if flip() then world:set(id, E, true) end
|
||||||
|
if flip() then world:set(id, F, true) end
|
||||||
|
if flip() then world:set(id, G, true) end
|
||||||
|
if flip() then world:set(id, H, true) end
|
||||||
|
if flip() then world:set(id, I, true) end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
view_bench(world, A, B, C, D, E, F, G, H, I)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE "one component in common"
|
||||||
|
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local A = world:component()
|
||||||
|
local B = world:component()
|
||||||
|
local C = world:component()
|
||||||
|
local D = world:component()
|
||||||
|
local E = world:component()
|
||||||
|
local F = world:component()
|
||||||
|
local G = world:component()
|
||||||
|
local H = world:component()
|
||||||
|
local I = world:component()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world:entity()
|
||||||
|
local a = true
|
||||||
|
if flip() then world:set(id, B, true) else a = false end
|
||||||
|
if flip() then world:set(id, C, true) else a = false end
|
||||||
|
if flip() then world:set(id, D, true) else a = false end
|
||||||
|
if flip() then world:set(id, E, true) else a = false end
|
||||||
|
if flip() then world:set(id, F, true) else a = false end
|
||||||
|
if flip() then world:set(id, G, true) else a = false end
|
||||||
|
if flip() then world:set(id, H, true) else a = false end
|
||||||
|
if flip() then world:set(id, I, true) else a = false end
|
||||||
|
if a then world:set(id, A, true) end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
view_bench(world, A, B, C, D, E, F, G, H, I)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE (testkit.color.white_underline("ECR query"))
|
||||||
|
|
||||||
|
local A = ecr.component()
|
||||||
|
local B = ecr.component()
|
||||||
|
local C = ecr.component()
|
||||||
|
local D = ecr.component()
|
||||||
|
local E = ecr.component()
|
||||||
|
local F = ecr.component()
|
||||||
|
local G = ecr.component()
|
||||||
|
local H = ecr.component()
|
||||||
|
local I = ecr.component()
|
||||||
|
|
||||||
|
local function count(query: () -> ())
|
||||||
|
local n = 0
|
||||||
|
for _ in query do
|
||||||
|
n += 1
|
||||||
|
end
|
||||||
|
return n
|
||||||
|
end
|
||||||
|
|
||||||
|
local function flip()
|
||||||
|
return math.random() > 0.5
|
||||||
|
end
|
||||||
|
|
||||||
|
local function view_bench(
|
||||||
|
world: ecr.Registry,
|
||||||
|
A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53, I: i53
|
||||||
|
)
|
||||||
|
|
||||||
|
BENCH("1 component", function()
|
||||||
|
START(count(world:view(A)))
|
||||||
|
for _ in world:view(A) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("2 component", function()
|
||||||
|
START(count(world:view(A, B)))
|
||||||
|
for _ in world:view(A, B) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("4 component", function()
|
||||||
|
START(count(world:view(A, B, C, D)))
|
||||||
|
for _ in world:view(A, B, C, D) do end
|
||||||
|
end)
|
||||||
|
|
||||||
|
BENCH("8 component", function()
|
||||||
|
START(count(world:view(A, B, C, D, E, F, G, H)))
|
||||||
|
for _ in world:view(A, B, C, D, E, F, G, H) do end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
do TITLE "random components"
|
||||||
|
local world = ecr.registry()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world.create()
|
||||||
|
if flip() then world:set(id, A, true) end
|
||||||
|
if flip() then world:set(id, B, true) end
|
||||||
|
if flip() then world:set(id, C, true) end
|
||||||
|
if flip() then world:set(id, D, true) end
|
||||||
|
if flip() then world:set(id, E, true) end
|
||||||
|
if flip() then world:set(id, F, true) end
|
||||||
|
if flip() then world:set(id, G, true) end
|
||||||
|
if flip() then world:set(id, H, true) end
|
||||||
|
if flip() then world:set(id, I, true) end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
view_bench(world, A, B, C, D, E, F, G, H, I)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
do TITLE "one component in common"
|
||||||
|
|
||||||
|
local world = ecr.registry()
|
||||||
|
|
||||||
|
for i = 1, N do
|
||||||
|
local id = world.create()
|
||||||
|
local a = true
|
||||||
|
if flip() then world:set(id, B, true) else a = false end
|
||||||
|
if flip() then world:set(id, C, true) else a = false end
|
||||||
|
if flip() then world:set(id, D, true) else a = false end
|
||||||
|
if flip() then world:set(id, E, true) else a = false end
|
||||||
|
if flip() then world:set(id, F, true) else a = false end
|
||||||
|
if flip() then world:set(id, G, true) else a = false end
|
||||||
|
if flip() then world:set(id, H, true) else a = false end
|
||||||
|
if flip() then world:set(id, I, true) else a = false end
|
||||||
|
if a then world:set(id, A, true) end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
view_bench(world, A, B, C, D, E, F, G, H, I)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -1,35 +1,33 @@
|
||||||
--!optimize 2
|
--!optimize 2
|
||||||
--!native
|
--!native
|
||||||
|
|
||||||
local testkit = require('../testkit')
|
local testkit = require("../testkit")
|
||||||
local BENCH, START = testkit.benchmark()
|
local BENCH, START = testkit.benchmark()
|
||||||
local function TITLE(title: string)
|
local function TITLE(title: string)
|
||||||
print()
|
print()
|
||||||
print(testkit.color.white(title))
|
print(testkit.color.white(title))
|
||||||
end
|
end
|
||||||
|
|
||||||
local jecs = require("../mirror/init")
|
local jecs = require("../lib/init")
|
||||||
|
local mirror = require("../mirror/init")
|
||||||
|
|
||||||
local oldMatter = require("../oldMatter")
|
|
||||||
|
|
||||||
local newMatter = require("../newMatter")
|
|
||||||
type i53 = number
|
type i53 = number
|
||||||
|
|
||||||
do TITLE (testkit.color.white_underline("Jecs query"))
|
do
|
||||||
|
TITLE(testkit.color.white_underline("Jecs query"))
|
||||||
local ecs = jecs.World.new()
|
local ecs = jecs.World.new()
|
||||||
do TITLE "one component in common"
|
do
|
||||||
|
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: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do end
|
for _ in world:query(A) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("2 component", function()
|
BENCH("2 component", function()
|
||||||
for _ in world:query(A, B) do end
|
for _ in world:query(A, B) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("4 component", function()
|
BENCH("4 component", function()
|
||||||
|
@ -38,7 +36,8 @@ do TITLE (testkit.color.white_underline("Jecs query"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("8 component", function()
|
BENCH("8 component", function()
|
||||||
for _ in world:query(A, B, C, D, E, F, G, H) do end
|
for _ in world:query(A, B, C, D, E, F, G, H) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -57,7 +56,7 @@ do TITLE (testkit.color.white_underline("Jecs query"))
|
||||||
|
|
||||||
local added = 0
|
local added = 0
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
for i = 1, 2^16-2 do
|
for i = 1, 2 ^ 16 - 2 do
|
||||||
local entity = ecs:entity()
|
local entity = ecs:entity()
|
||||||
|
|
||||||
local combination = ""
|
local combination = ""
|
||||||
|
@ -68,15 +67,15 @@ do TITLE (testkit.color.white_underline("Jecs query"))
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "C"
|
combination ..= "C"
|
||||||
ecs:set(entity, D3, { value = true })
|
ecs:set(entity, D3, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "D"
|
combination ..= "D"
|
||||||
ecs:set(entity, D4, { value = true})
|
ecs:set(entity, D4, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "E"
|
combination ..= "E"
|
||||||
ecs:set(entity, D5, { value = true})
|
ecs:set(entity, D5, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "F"
|
combination ..= "F"
|
||||||
|
@ -84,45 +83,44 @@ do TITLE (testkit.color.white_underline("Jecs query"))
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "G"
|
combination ..= "G"
|
||||||
ecs:set(entity, D7, { value = true})
|
ecs:set(entity, D7, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "H"
|
combination ..= "H"
|
||||||
ecs:set(entity, D8, {value = true})
|
ecs:set(entity, D8, {value = true})
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if #combination == 7 then
|
if #combination == 7 then
|
||||||
added += 1
|
added += 1
|
||||||
ecs:set(entity, D1, { value = true})
|
ecs:set(entity, D1, {value = true})
|
||||||
end
|
end
|
||||||
archetypes[combination] = true
|
archetypes[combination] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local a = 0
|
local a = 0
|
||||||
for _ in archetypes do a+= 1 end
|
for _ in archetypes do
|
||||||
|
a += 1
|
||||||
|
end
|
||||||
|
|
||||||
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
|
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
do TITLE(testkit.color.white_underline("OldMatter query"))
|
do
|
||||||
|
TITLE(testkit.color.white_underline("Mirror query"))
|
||||||
local ecs = oldMatter.World.new()
|
local ecs = mirror.World.new()
|
||||||
local component = oldMatter.component
|
do
|
||||||
|
TITLE("one component in common")
|
||||||
do 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: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53)
|
||||||
BENCH("1 component", function()
|
BENCH("1 component", function()
|
||||||
for _ in world:query(A) do end
|
for _ in world:query(A) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("2 component", function()
|
BENCH("2 component", function()
|
||||||
for _ in world:query(A, B) do end
|
for _ in world:query(A, B) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("4 component", function()
|
BENCH("4 component", function()
|
||||||
|
@ -131,18 +129,19 @@ do TITLE(testkit.color.white_underline("OldMatter query"))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
BENCH("8 component", function()
|
BENCH("8 component", function()
|
||||||
for _ in world:query(A, B, C, D, E, F, G, H) do end
|
for _ in world:query(A, B, C, D, E, F, G, H) do
|
||||||
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
local D1 = component()
|
local D1 = ecs:component()
|
||||||
local D2 = component()
|
local D2 = ecs:component()
|
||||||
local D3 = component()
|
local D3 = ecs:component()
|
||||||
local D4 = component()
|
local D4 = ecs:component()
|
||||||
local D5 = component()
|
local D5 = ecs:component()
|
||||||
local D6 = component()
|
local D6 = ecs:component()
|
||||||
local D7 = component()
|
local D7 = ecs:component()
|
||||||
local D8 = component()
|
local D8 = ecs:component()
|
||||||
|
|
||||||
local function flip()
|
local function flip()
|
||||||
return math.random() >= 0.15
|
return math.random() >= 0.15
|
||||||
|
@ -150,150 +149,52 @@ do TITLE(testkit.color.white_underline("OldMatter query"))
|
||||||
|
|
||||||
local added = 0
|
local added = 0
|
||||||
local archetypes = {}
|
local archetypes = {}
|
||||||
for i = 1, 2^16-2 do
|
for i = 1, 2 ^ 16 - 2 do
|
||||||
local entity = ecs:spawn()
|
local entity = ecs:entity()
|
||||||
|
|
||||||
local combination = ""
|
local combination = ""
|
||||||
|
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "B"
|
combination ..= "B"
|
||||||
ecs:insert(entity, D2({value = true}))
|
ecs:set(entity, D2, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "C"
|
combination ..= "C"
|
||||||
ecs:insert(entity, D3({value = true}))
|
ecs:set(entity, D3, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "D"
|
combination ..= "D"
|
||||||
ecs:insert(entity, D4({value = true}))
|
ecs:set(entity, D4, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "E"
|
combination ..= "E"
|
||||||
ecs:insert(entity, D5({value = true}))
|
ecs:set(entity, D5, {value = true})
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "F"
|
combination ..= "F"
|
||||||
ecs:insert(entity, D6({value = true}))
|
ecs:set(entity, D6, {value = true})
|
||||||
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "G"
|
combination ..= "G"
|
||||||
ecs:insert(entity, D7({value = true}))
|
ecs:set(entity, D7, {value = true})
|
||||||
|
|
||||||
end
|
end
|
||||||
if flip() then
|
if flip() then
|
||||||
combination ..= "H"
|
combination ..= "H"
|
||||||
ecs:insert(entity, D8({value = true}))
|
ecs:set(entity, D8, {value = true})
|
||||||
end
|
end
|
||||||
|
|
||||||
if #combination == 7 then
|
if #combination == 7 then
|
||||||
added += 1
|
added += 1
|
||||||
ecs:insert(entity, D1({value = true}))
|
ecs:set(entity, D1, {value = true})
|
||||||
|
|
||||||
end
|
end
|
||||||
archetypes[combination] = true
|
archetypes[combination] = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local a = 0
|
local a = 0
|
||||||
for _ in archetypes do a+= 1 end
|
for _ in archetypes do
|
||||||
|
a += 1
|
||||||
|
end
|
||||||
|
|
||||||
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
|
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
do TITLE(testkit.color.white_underline("NewMatter query"))
|
|
||||||
|
|
||||||
local ecs = newMatter.World.new()
|
|
||||||
local component = newMatter.component
|
|
||||||
|
|
||||||
do 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
|
|
||||||
)
|
|
||||||
|
|
||||||
BENCH("1 component", function()
|
|
||||||
for _ in world:query(A) do end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("2 component", function()
|
|
||||||
for _ in world:query(A, B) do end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("4 component", function()
|
|
||||||
for _ in world:query(A, B, C, D) do
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|
|
||||||
BENCH("8 component", function()
|
|
||||||
for _ in world:query(A, B, C, D, E, F, G, H) do end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
local D1 = component()
|
|
||||||
local D2 = component()
|
|
||||||
local D3 = component()
|
|
||||||
local D4 = component()
|
|
||||||
local D5 = component()
|
|
||||||
local D6 = component()
|
|
||||||
local D7 = component()
|
|
||||||
local D8 = component()
|
|
||||||
|
|
||||||
local function flip()
|
|
||||||
return math.random() >= 0.15
|
|
||||||
end
|
|
||||||
|
|
||||||
local added = 0
|
|
||||||
local archetypes = {}
|
|
||||||
for i = 1, 2^16-2 do
|
|
||||||
local entity = ecs:spawn()
|
|
||||||
|
|
||||||
local combination = ""
|
|
||||||
|
|
||||||
if flip() then
|
|
||||||
combination ..= "B"
|
|
||||||
ecs:insert(entity, D2({value = true}))
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "C"
|
|
||||||
ecs:insert(entity, D3({value = true}))
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "D"
|
|
||||||
ecs:insert(entity, D4({value = true}))
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "E"
|
|
||||||
ecs:insert(entity, D5({value = true}))
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "F"
|
|
||||||
ecs:insert(entity, D6({value = true}))
|
|
||||||
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "G"
|
|
||||||
ecs:insert(entity, D7({value = true}))
|
|
||||||
|
|
||||||
end
|
|
||||||
if flip() then
|
|
||||||
combination ..= "H"
|
|
||||||
ecs:insert(entity, D8({value = true}))
|
|
||||||
end
|
|
||||||
|
|
||||||
if #combination == 7 then
|
|
||||||
added += 1
|
|
||||||
ecs:insert(entity, D1({value = true}))
|
|
||||||
|
|
||||||
end
|
|
||||||
archetypes[combination] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
local a = 0
|
|
||||||
for _ in archetypes do a+= 1 end
|
|
||||||
|
|
||||||
view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
|
@ -2,41 +2,37 @@
|
||||||
--!native
|
--!native
|
||||||
|
|
||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
||||||
local rgb = require(ReplicatedStorage.rgb)
|
|
||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
local Matter = require(ReplicatedStorage.DevPackages.Matter)
|
||||||
local jecs = require(ReplicatedStorage.Lib)
|
|
||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
local ecr = require(ReplicatedStorage.DevPackages.ecr)
|
||||||
|
local jecs = require(ReplicatedStorage.Lib)
|
||||||
|
local rgb = require(ReplicatedStorage.rgb)
|
||||||
local newWorld = Matter.World.new()
|
local newWorld = Matter.World.new()
|
||||||
local ecs = jecs.World.new()
|
local ecs = jecs.World.new()
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ParameterGenerator = function()
|
ParameterGenerator = function()
|
||||||
local registry2 = ecr.registry()
|
local registry2 = ecr.registry()
|
||||||
|
|
||||||
return registry2
|
return registry2
|
||||||
end,
|
end;
|
||||||
|
|
||||||
Functions = {
|
Functions = {
|
||||||
Matter = function()
|
Matter = function()
|
||||||
for i = 1, 1000 do
|
for i = 1, 1000 do
|
||||||
newWorld:spawn()
|
newWorld:spawn()
|
||||||
end
|
end
|
||||||
end,
|
end;
|
||||||
|
|
||||||
|
|
||||||
ECR = function(_, registry2)
|
ECR = function(_, registry2)
|
||||||
for i = 1, 1000 do
|
for i = 1, 1000 do
|
||||||
registry2.create()
|
registry2.create()
|
||||||
end
|
end
|
||||||
end,
|
end;
|
||||||
|
|
||||||
|
|
||||||
Jecs = function()
|
Jecs = function()
|
||||||
for i = 1, 1000 do
|
for i = 1, 1000 do
|
||||||
ecs:entity()
|
ecs:entity()
|
||||||
end
|
end
|
||||||
end
|
end;
|
||||||
|
};
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
6
jecs_darkmode.svg
Normal file
6
jecs_darkmode.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="47" height="18" viewBox="0 0 47 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 14C5.8 14 6 13.3333 6 13V4H0V0H6H10V13C10 17 6.66667 18 5 18H0V14H5Z" fill="white"/>
|
||||||
|
<path d="M46.5 4V0H39C37.1667 0 33.5 1.1 33.5 5.5C33.5 9.9 36.8333 11 38.5 11H41C41.5 11 42.5 11.3 42.5 12.5C42.5 13.7 41.5 14 41 14H33.5V18H41.5C43.1667 18 46.5 16.9 46.5 12.5C46.5 8.1 43.1667 7 41.5 7H39C38.5 7 37.5 6.7 37.5 5.5C37.5 4.3 38.5 4 39 4H46.5Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.5 0V4H30.5C28.5 4 24.5 5 24.5 9C24.5 11.0835 25.5853 12.3531 26.9078 13.0914L22.4606 14.661C21.2893 13.3156 20.5 11.4775 20.5 9C20.5 1.8 27.1667 0 30.5 0H32.5ZM24.4656 16.3357C26.5037 17.5803 28.8905 18 30.5 18H32.5V14H31.0833L24.4656 16.3357Z" fill="white"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3793 0C24.766 0.241156 24.1568 0.53354 23.571 0.885014C22.1712 1.72492 20.9038 2.91123 20.0606 4.5H11V0H25.3793ZM25.5 4.39421C25.445 4.42876 25.3906 4.46402 25.3368 4.5H25.5V4.39421ZM20.0606 13.5C20.9038 15.0888 22.1712 16.2751 23.571 17.115C24.1568 17.4665 24.766 17.7588 25.3793 18H11V13.5H20.0606ZM19.1854 7C19.0649 7.62348 19 8.28956 19 9C19 9.71044 19.0649 10.3765 19.1854 11H11V7H19.1854Z" fill="white"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
6
jecs_lightmode.svg
Normal file
6
jecs_lightmode.svg
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<svg width="47" height="18" viewBox="0 0 47 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M5 14C5.8 14 6 13.3333 6 13V4H0V0H6H10V13C10 17 6.66667 18 5 18H0V14H5Z" fill="black"/>
|
||||||
|
<path d="M46.5 4V0H39C37.1667 0 33.5 1.1 33.5 5.5C33.5 9.9 36.8333 11 38.5 11H41C41.5 11 42.5 11.3 42.5 12.5C42.5 13.7 41.5 14 41 14H33.5V18H41.5C43.1667 18 46.5 16.9 46.5 12.5C46.5 8.1 43.1667 7 41.5 7H39C38.5 7 37.5 6.7 37.5 5.5C37.5 4.3 38.5 4 39 4H46.5Z" fill="black"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.5 0V4H30.5C28.5 4 24.5 5 24.5 9C24.5 11.0835 25.5853 12.3531 26.9078 13.0914L22.4606 14.661C21.2893 13.3156 20.5 11.4775 20.5 9C20.5 1.8 27.1667 0 30.5 0H32.5ZM24.4656 16.3357C26.5037 17.5803 28.8905 18 30.5 18H32.5V14H31.0833L24.4656 16.3357Z" fill="black"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.3793 0C24.766 0.241156 24.1568 0.53354 23.571 0.885014C22.1712 1.72492 20.9038 2.91123 20.0606 4.5H11V0H25.3793ZM25.5 4.39421C25.445 4.42876 25.3906 4.46402 25.3368 4.5H25.5V4.39421ZM20.0606 13.5C20.9038 15.0888 22.1712 16.2751 23.571 17.115C24.1568 17.4665 24.766 17.7588 25.3793 18H11V13.5H20.0606ZM19.1854 7C19.0649 7.62348 19 8.28956 19 9C19 9.71044 19.0649 10.3765 19.1854 11H11V7H19.1854Z" fill="black"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
384
lib/init.lua
384
lib/init.lua
|
@ -6,10 +6,10 @@
|
||||||
type i53 = number
|
type i53 = number
|
||||||
type i24 = number
|
type i24 = number
|
||||||
|
|
||||||
type Ty = { i53 }
|
type Ty = {i53}
|
||||||
type ArchetypeId = number
|
type ArchetypeId = number
|
||||||
|
|
||||||
type Column = { any }
|
type Column = {any}
|
||||||
|
|
||||||
type Archetype = {
|
type Archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -21,8 +21,8 @@ type Archetype = {
|
||||||
},
|
},
|
||||||
types: Ty,
|
types: Ty,
|
||||||
type: string | number,
|
type: string | number,
|
||||||
entities: { number },
|
entities: {number},
|
||||||
columns: { Column },
|
columns: {Column},
|
||||||
records: {},
|
records: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,12 +31,12 @@ type Record = {
|
||||||
row: number,
|
row: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntityIndex = { [i24]: Record }
|
type EntityIndex = {[i24]: Record}
|
||||||
type ComponentIndex = { [i24]: ArchetypeMap}
|
type ComponentIndex = {[i24]: ArchetypeMap}
|
||||||
|
|
||||||
type ArchetypeRecord = number
|
type ArchetypeRecord = number
|
||||||
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord } , size: number }
|
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
|
||||||
type Archetypes = { [ArchetypeId]: Archetype }
|
type Archetypes = {[ArchetypeId]: Archetype}
|
||||||
|
|
||||||
type ArchetypeDiff = {
|
type ArchetypeDiff = {
|
||||||
added: Ty,
|
added: Ty,
|
||||||
|
@ -82,24 +82,27 @@ local function transitionArchetype(
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move the entity from the source to the destination archetype.
|
-- Move the entity from the source to the destination archetype.
|
||||||
destinationEntities[destinationRow] = sourceEntities[sourceRow]
|
local atSourceRow = sourceEntities[sourceRow]
|
||||||
entityIndex[sourceEntities[sourceRow]].row = destinationRow
|
destinationEntities[destinationRow] = atSourceRow
|
||||||
|
entityIndex[atSourceRow].row = destinationRow
|
||||||
|
|
||||||
-- Because we have swapped columns we now have to update the records
|
-- Because we have swapped columns we now have to update the records
|
||||||
-- corresponding to the entities' rows that were swapped.
|
-- corresponding to the entities' rows that were swapped.
|
||||||
local movedAway = #sourceEntities
|
local movedAway = #sourceEntities
|
||||||
if sourceRow ~= movedAway then
|
if sourceRow ~= movedAway then
|
||||||
sourceEntities[sourceRow] = sourceEntities[movedAway]
|
local atMovedAway = sourceEntities[movedAway]
|
||||||
entityIndex[sourceEntities[movedAway]].row = sourceRow
|
sourceEntities[sourceRow] = atMovedAway
|
||||||
|
entityIndex[atMovedAway].row = sourceRow
|
||||||
end
|
end
|
||||||
|
|
||||||
sourceEntities[movedAway] = nil
|
sourceEntities[movedAway] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeAppend(entity: i53, archetype: Archetype): i24
|
local function archetypeAppend(entity: number, archetype: Archetype): number
|
||||||
local entities = archetype.entities
|
local entities = archetype.entities
|
||||||
table.insert(entities, entity)
|
local length = #entities + 1
|
||||||
return #entities
|
entities[length] = entity
|
||||||
|
return length
|
||||||
end
|
end
|
||||||
|
|
||||||
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
|
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
|
||||||
|
@ -122,47 +125,49 @@ local function hash(arr): string | number
|
||||||
return table.concat(arr, "_")
|
return table.concat(arr, "_")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?)
|
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?)
|
||||||
local destinationCount = #to.types
|
|
||||||
local destinationIds = to.types
|
local destinationIds = to.types
|
||||||
|
local records = to.records
|
||||||
|
local id = to.id
|
||||||
|
|
||||||
for i = 1, destinationCount do
|
for i, destinationId in destinationIds do
|
||||||
local destinationId = destinationIds[i]
|
local archetypesMap = componentIndex[destinationId]
|
||||||
|
|
||||||
if not componentIndex[destinationId] then
|
if not archetypesMap then
|
||||||
componentIndex[destinationId] = { size = 0, sparse = {} }
|
archetypesMap = {size = 0, sparse = {}}
|
||||||
|
componentIndex[destinationId] = archetypesMap
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetypesMap = componentIndex[destinationId]
|
archetypesMap.sparse[id] = i
|
||||||
archetypesMap.sparse[to.id] = i
|
records[destinationId] = i
|
||||||
to.records[destinationId] = i
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype
|
local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype
|
||||||
local ty = hash(types)
|
local ty = hash(types)
|
||||||
|
|
||||||
world.nextArchetypeId = (world.nextArchetypeId::number)+ 1
|
local id = world.nextArchetypeId + 1
|
||||||
local id = world.nextArchetypeId
|
world.nextArchetypeId = id
|
||||||
|
|
||||||
local columns = {} :: { any }
|
local length = #types
|
||||||
|
local columns = table.create(length) :: {any}
|
||||||
|
|
||||||
for _ in types do
|
for index in types do
|
||||||
table.insert(columns, {})
|
columns[index] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype = {
|
local archetype = {
|
||||||
id = id,
|
columns = columns;
|
||||||
types = types,
|
edges = {};
|
||||||
type = ty,
|
entities = {};
|
||||||
columns = columns,
|
id = id;
|
||||||
entities = {},
|
records = {};
|
||||||
edges = {},
|
type = ty;
|
||||||
records = {},
|
types = types;
|
||||||
}
|
}
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[id] = archetype
|
world.archetypes[id] = archetype
|
||||||
if #types > 0 then
|
if length > 0 then
|
||||||
createArchetypeRecords(world.componentIndex, archetype, prev)
|
createArchetypeRecords(world.componentIndex, archetype, prev)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -173,18 +178,19 @@ local World = {}
|
||||||
World.__index = World
|
World.__index = World
|
||||||
function World.new()
|
function World.new()
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
entityIndex = {},
|
archetypeIndex = {};
|
||||||
componentIndex = {},
|
archetypes = {};
|
||||||
archetypes = {},
|
componentIndex = {};
|
||||||
archetypeIndex = {},
|
entityIndex = {};
|
||||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
|
||||||
nextEntityId = 0,
|
|
||||||
nextComponentId = 0,
|
|
||||||
nextArchetypeId = 0,
|
|
||||||
hooks = {
|
hooks = {
|
||||||
[ON_ADD] = {}
|
[ON_ADD] = {};
|
||||||
}
|
};
|
||||||
|
nextArchetypeId = 0;
|
||||||
|
nextComponentId = 0;
|
||||||
|
nextEntityId = 0;
|
||||||
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype;
|
||||||
}, World)
|
}, World)
|
||||||
|
self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -192,21 +198,21 @@ local function emit(world, eventDescription)
|
||||||
local event = eventDescription.event
|
local event = eventDescription.event
|
||||||
|
|
||||||
table.insert(world.hooks[event], {
|
table.insert(world.hooks[event], {
|
||||||
ids = eventDescription.ids,
|
archetype = eventDescription.archetype;
|
||||||
archetype = eventDescription.archetype,
|
ids = eventDescription.ids;
|
||||||
otherArchetype = eventDescription.otherArchetype,
|
offset = eventDescription.offset;
|
||||||
offset = eventDescription.offset
|
otherArchetype = eventDescription.otherArchetype;
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
|
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
|
||||||
if #added > 0 then
|
if #added > 0 then
|
||||||
emit(world, {
|
emit(world, {
|
||||||
event = ON_ADD,
|
archetype = archetype;
|
||||||
ids = added,
|
event = ON_ADD;
|
||||||
archetype = archetype,
|
ids = added;
|
||||||
otherArchetype = otherArchetype,
|
offset = row;
|
||||||
offset = row,
|
otherArchetype = otherArchetype;
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -227,10 +233,8 @@ local function ensureArchetype(world: World, types, prev)
|
||||||
return archetypeOf(world, types, prev)
|
return archetypeOf(world, types, prev)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findInsert(types: { i53 }, toAdd: i53)
|
local function findInsert(types: {i53}, toAdd: i53)
|
||||||
local count = #types
|
for i, id in types do
|
||||||
for i = 1, count do
|
|
||||||
local id = types[i]
|
|
||||||
if id == toAdd then
|
if id == toAdd then
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
|
@ -238,7 +242,7 @@ local function findInsert(types: { i53 }, toAdd: i53)
|
||||||
return i
|
return i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return count + 1
|
return #types + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
|
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
|
||||||
|
@ -259,40 +263,57 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ensureEdge(archetype: Archetype, componentId: i53)
|
local function ensureEdge(archetype: Archetype, componentId: i53)
|
||||||
if not archetype.edges[componentId] then
|
local edges = archetype.edges
|
||||||
archetype.edges[componentId] = {} :: any
|
local edge = edges[componentId]
|
||||||
|
if not edge then
|
||||||
|
edge = {} :: any
|
||||||
|
edges[componentId] = edge
|
||||||
end
|
end
|
||||||
return archetype.edges[componentId]
|
return edge
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
|
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
|
||||||
if not from then
|
from = from or world.ROOT_ARCHETYPE
|
||||||
-- If there was no source archetype then it should return the ROOT_ARCHETYPE
|
|
||||||
if not world.ROOT_ARCHETYPE then
|
|
||||||
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
|
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
|
||||||
end
|
|
||||||
from = world.ROOT_ARCHETYPE
|
|
||||||
end
|
|
||||||
local edge = ensureEdge(from, componentId)
|
|
||||||
|
|
||||||
if not edge.add then
|
local edge = ensureEdge(from, componentId)
|
||||||
|
local add = edge.add
|
||||||
|
if not add then
|
||||||
-- Save an edge using the component ID to the archetype to allow
|
-- Save an edge using the component ID to the archetype to allow
|
||||||
-- faster traversals to adjacent archetypes.
|
-- faster traversals to adjacent archetypes.
|
||||||
edge.add = findArchetypeWith(world, from, componentId)
|
add = findArchetypeWith(world, from, componentId)
|
||||||
|
edge.add = add :: never
|
||||||
end
|
end
|
||||||
|
|
||||||
return edge.add
|
return add
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ensureRecord(entityIndex, entityId: i53): Record
|
local function ensureRecord(entityIndex, entityId: i53): Record
|
||||||
local id = entityId
|
local record = entityIndex[entityId]
|
||||||
if not entityIndex[id] then
|
|
||||||
entityIndex[id] = {}
|
if not record then
|
||||||
|
record = {}
|
||||||
|
entityIndex[entityId] = record
|
||||||
end
|
end
|
||||||
return entityIndex[id] :: Record
|
|
||||||
|
return record :: Record
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function World.add(world: World, entityId: i53, componentId: i53)
|
||||||
|
local record = ensureRecord(world.entityIndex, entityId)
|
||||||
|
local from = record.archetype
|
||||||
|
local to = archetypeTraverseAdd(world, componentId, from)
|
||||||
|
if from then
|
||||||
|
moveEntity(world.entityIndex, entityId, record, to)
|
||||||
|
else
|
||||||
|
if #to.types > 0 then
|
||||||
|
newEntity(entityId, record, to)
|
||||||
|
onNotifyAdd(world, to, from, record.row, { componentId })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Symmetric like `World.add` but idempotent
|
||||||
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
||||||
local record = ensureRecord(world.entityIndex, entityId)
|
local record = ensureRecord(world.entityIndex, entityId)
|
||||||
local from = record.archetype
|
local from = record.archetype
|
||||||
|
@ -314,7 +335,7 @@ function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
||||||
if #to.types > 0 then
|
if #to.types > 0 then
|
||||||
-- When there is no previous archetype it should create the archetype
|
-- When there is no previous archetype it should create the archetype
|
||||||
newEntity(entityId, record, to)
|
newEntity(entityId, record, to)
|
||||||
onNotifyAdd(world, to, from, record.row, { componentId })
|
onNotifyAdd(world, to, from, record.row, {componentId})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -326,21 +347,24 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype
|
||||||
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
|
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
|
||||||
local edge = ensureEdge(from, componentId)
|
local edge = ensureEdge(from, componentId)
|
||||||
|
|
||||||
if not edge.remove then
|
local remove = edge.remove
|
||||||
|
if not remove then
|
||||||
local to = table.clone(from.types)
|
local to = table.clone(from.types)
|
||||||
local at = table.find(to, componentId)
|
local at = table.find(to, componentId)
|
||||||
if not at then
|
if not at then
|
||||||
return
|
return from
|
||||||
end
|
end
|
||||||
table.remove(to, at)
|
table.remove(to, at, component)
|
||||||
edge.remove = ensureArchetype(world, to, from)
|
remove = ensureArchetype(world, to, from)
|
||||||
|
edge.remove = remove :: never
|
||||||
end
|
end
|
||||||
|
|
||||||
return edge.remove
|
return remove
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.remove(world: World, entityId: i53, componentId: i53)
|
function World.remove(world: World, entityId: i53, componentId: i53)
|
||||||
local record = ensureRecord(world.entityIndex, entityId)
|
local entityIndex = world.entityIndex
|
||||||
|
local record = ensureRecord(entityIndex, entityId)
|
||||||
local sourceArchetype = record.archetype
|
local sourceArchetype = record.archetype
|
||||||
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
|
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
|
||||||
if not destinationArchetype then
|
if not destinationArchetype then
|
||||||
|
@ -348,12 +372,12 @@ function World.remove(world: World, entityId: i53, componentId: i53)
|
||||||
end
|
end
|
||||||
|
|
||||||
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
||||||
moveEntity(world.entityIndex, entityId, record, destinationArchetype)
|
moveEntity(entityIndex, entityId, record, destinationArchetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Keeping the function as small as possible to enable inlining
|
-- Keeping the function as small as possible to enable inlining
|
||||||
local function get(componentIndex: { [i24]: ArchetypeMap }, record: Record, componentId: i24)
|
local function get(record: Record, componentId: i24)
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
local archetypeRecord = archetype.records[componentId]
|
local archetypeRecord = archetype.records[componentId]
|
||||||
|
|
||||||
|
@ -366,35 +390,35 @@ end
|
||||||
|
|
||||||
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
|
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
|
||||||
local id = entityId
|
local id = entityId
|
||||||
local componentIndex = world.componentIndex
|
|
||||||
local record = world.entityIndex[id]
|
local record = world.entityIndex[id]
|
||||||
if not record then
|
if not record then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local va = get(componentIndex, record, a)
|
local va = get(record, a)
|
||||||
|
|
||||||
if b == nil then
|
if b == nil then
|
||||||
return va
|
return va
|
||||||
elseif c == nil then
|
elseif c == nil then
|
||||||
return va, get(componentIndex, record, b)
|
return va, get(record, b)
|
||||||
elseif d == nil then
|
elseif d == nil then
|
||||||
return va, get(componentIndex, record, b), get(componentIndex, record, c)
|
return va, get(record, b), get(record, c)
|
||||||
elseif e == nil then
|
elseif e == nil then
|
||||||
return va, get(componentIndex, record, b), get(componentIndex, record, c), get(componentIndex, record, d)
|
return va, get(record, b), get(record, c), get(record, d)
|
||||||
else
|
else
|
||||||
error("args exceeded")
|
error("args exceeded")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function noop(self: Query, ...: i53): () -> (number, ...any)
|
-- the less creation the better
|
||||||
return function()
|
local function actualNoOperation() end
|
||||||
end :: any
|
local function noop(_self: Query, ...: i53): () -> (number, ...any)
|
||||||
|
return actualNoOperation :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
local EmptyQuery = {
|
local EmptyQuery = {
|
||||||
__iter = noop,
|
__iter = noop;
|
||||||
without = noop
|
without = noop;
|
||||||
}
|
}
|
||||||
EmptyQuery.__index = EmptyQuery
|
EmptyQuery.__index = EmptyQuery
|
||||||
setmetatable(EmptyQuery, EmptyQuery)
|
setmetatable(EmptyQuery, EmptyQuery)
|
||||||
|
@ -402,19 +426,22 @@ setmetatable(EmptyQuery, EmptyQuery)
|
||||||
export type Query = typeof(EmptyQuery)
|
export type Query = typeof(EmptyQuery)
|
||||||
|
|
||||||
function World.query(world: World, ...: i53): Query
|
function World.query(world: World, ...: i53): Query
|
||||||
local compatibleArchetypes = {}
|
-- breaking?
|
||||||
local components = { ... }
|
if (...) == nil then
|
||||||
local archetypes = world.archetypes
|
|
||||||
local queryLength = #components
|
|
||||||
|
|
||||||
if queryLength == 0 then
|
|
||||||
error("Missing components")
|
error("Missing components")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local compatibleArchetypes = {}
|
||||||
|
local length = 0
|
||||||
|
|
||||||
|
local components = {...}
|
||||||
|
local archetypes = world.archetypes
|
||||||
|
local queryLength = #components
|
||||||
|
|
||||||
local firstArchetypeMap
|
local firstArchetypeMap
|
||||||
local componentIndex = world.componentIndex
|
local componentIndex = world.componentIndex
|
||||||
|
|
||||||
for i, componentId in components do
|
for _, componentId in components do
|
||||||
local map = componentIndex[componentId]
|
local map = componentIndex[componentId]
|
||||||
if not map then
|
if not map then
|
||||||
return EmptyQuery
|
return EmptyQuery
|
||||||
|
@ -437,13 +464,15 @@ function World.query(world: World, ...: i53): Query
|
||||||
skip = true
|
skip = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
indices[i] = archetypeRecords[componentId]
|
indices[i] = index
|
||||||
end
|
end
|
||||||
|
|
||||||
if skip then
|
if skip then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
table.insert(compatibleArchetypes, { archetype, indices })
|
|
||||||
|
length += 1
|
||||||
|
compatibleArchetypes[length] = {archetype, indices}
|
||||||
end
|
end
|
||||||
|
|
||||||
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
|
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
|
||||||
|
@ -455,16 +484,19 @@ function World.query(world: World, ...: i53): Query
|
||||||
preparedQuery.__index = preparedQuery
|
preparedQuery.__index = preparedQuery
|
||||||
|
|
||||||
function preparedQuery:without(...)
|
function preparedQuery:without(...)
|
||||||
local components = { ... }
|
local withoutComponents = {...}
|
||||||
for i = #compatibleArchetypes, 1, -1 do
|
for i = #compatibleArchetypes, 1, -1 do
|
||||||
local archetype = compatibleArchetypes[i][1]
|
local archetype = compatibleArchetypes[i][1]
|
||||||
|
local records = archetype.records
|
||||||
local shouldRemove = false
|
local shouldRemove = false
|
||||||
for _, componentId in components do
|
|
||||||
if archetype.records[componentId] then
|
for _, componentId in withoutComponents do
|
||||||
|
if records[componentId] then
|
||||||
shouldRemove = true
|
shouldRemove = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
if shouldRemove then
|
||||||
table.remove(compatibleArchetypes, i)
|
table.remove(compatibleArchetypes, i)
|
||||||
end
|
end
|
||||||
|
@ -481,7 +513,6 @@ function World.query(world: World, ...: i53): Query
|
||||||
local lastRow
|
local lastRow
|
||||||
local queryOutput = {}
|
local queryOutput = {}
|
||||||
|
|
||||||
|
|
||||||
function preparedQuery:__iter()
|
function preparedQuery:__iter()
|
||||||
return function()
|
return function()
|
||||||
local archetype = compatibleArchetype[1]
|
local archetype = compatibleArchetype[1]
|
||||||
|
@ -505,16 +536,9 @@ function World.query(world: World, ...: i53): Query
|
||||||
elseif queryLength == 2 then
|
elseif queryLength == 2 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
||||||
elseif queryLength == 3 then
|
elseif queryLength == 3 then
|
||||||
return entityId,
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
|
||||||
columns[tr[1]][row],
|
|
||||||
columns[tr[2]][row],
|
|
||||||
columns[tr[3]][row]
|
|
||||||
elseif queryLength == 4 then
|
elseif queryLength == 4 then
|
||||||
return entityId,
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
|
||||||
columns[tr[1]][row],
|
|
||||||
columns[tr[2]][row],
|
|
||||||
columns[tr[3]][row],
|
|
||||||
columns[tr[4]][row]
|
|
||||||
elseif queryLength == 5 then
|
elseif queryLength == 5 then
|
||||||
return entityId,
|
return entityId,
|
||||||
columns[tr[1]][row],
|
columns[tr[1]][row],
|
||||||
|
@ -552,7 +576,7 @@ function World.query(world: World, ...: i53): Query
|
||||||
end
|
end
|
||||||
|
|
||||||
for i in components do
|
for i in components do
|
||||||
queryOutput[i] = tr[i][row]
|
queryOutput[i] = columns[tr[i]][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return entityId, unpack(queryOutput, 1, queryLength)
|
return entityId, unpack(queryOutput, 1, queryLength)
|
||||||
|
@ -574,28 +598,67 @@ function World.component(world: World)
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.entity(world: World)
|
function World.entity(world: World)
|
||||||
world.nextEntityId += 1
|
local nextEntityId = world.nextEntityId + 1
|
||||||
return world.nextEntityId + REST
|
world.nextEntityId = nextEntityId
|
||||||
|
return nextEntityId + REST
|
||||||
|
end
|
||||||
|
|
||||||
|
-- should reuse this logic in World.set instead of swap removing in transition archetype
|
||||||
|
local function destructColumns(columns, count, row)
|
||||||
|
if row == count then
|
||||||
|
for _, column in columns do
|
||||||
|
column[count] = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
for _, column in columns do
|
||||||
|
column[row] = column[count]
|
||||||
|
column[count] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function archetypeDelete(entityIndex, archetype: Archetype, row: i24, destruct: boolean)
|
||||||
|
local entities = archetype.entities
|
||||||
|
local last = #entities
|
||||||
|
|
||||||
|
local entityToMove = entities[last]
|
||||||
|
--local entityToDelete = entities[row]
|
||||||
|
entities[row] = entityToMove
|
||||||
|
entities[last] = nil
|
||||||
|
|
||||||
|
if row ~= last then
|
||||||
|
local recordToMove = entityIndex[entityToMove]
|
||||||
|
if recordToMove then
|
||||||
|
recordToMove.row = row
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local columns = archetype.columns
|
||||||
|
|
||||||
|
if not destruct then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
destructColumns(columns, last, row)
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.delete(world: World, entityId: i53)
|
function World.delete(world: World, entityId: i53)
|
||||||
local entityIndex = world.entityIndex
|
local entityIndex = world.entityIndex
|
||||||
local record = entityIndex[entityId]
|
local record = entityIndex[entityId]
|
||||||
moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE)
|
local archetype = record.archetype
|
||||||
-- Since we just appended an entity to the ROOT_ARCHETYPE we have to remove it from
|
archetypeDelete(entityIndex, archetype, record.row, true)
|
||||||
-- the entities array and delete the record. We know there won't be the hole since
|
entityIndex[entityId] = nil
|
||||||
-- we are always removing the last row.
|
|
||||||
--world.ROOT_ARCHETYPE.entities[record.row] = nil
|
|
||||||
--entityIndex[entityId] = nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.observer(world: World, ...)
|
function World.observer(world: World, ...)
|
||||||
local componentIds = { ... }
|
local componentIds = {...}
|
||||||
|
local idsCount = #componentIds
|
||||||
|
local hooks = world.hooks
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event = function(event)
|
event = function(event)
|
||||||
local hook = world.hooks[event]
|
local hook = hooks[event]
|
||||||
world.hooks[event] = nil
|
hooks[event] = nil
|
||||||
|
|
||||||
local last, change
|
local last, change
|
||||||
return function()
|
return function()
|
||||||
|
@ -605,10 +668,11 @@ function World.observer(world: World, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
local matched = false
|
local matched = false
|
||||||
|
local ids = change.ids
|
||||||
|
|
||||||
while not matched do
|
while not matched do
|
||||||
local skip = false
|
local skip = false
|
||||||
for _, id in change.ids do
|
for _, id in ids do
|
||||||
if not table.find(componentIds, id) then
|
if not table.find(componentIds, id) then
|
||||||
skip = true
|
skip = true
|
||||||
break
|
break
|
||||||
|
@ -617,30 +681,62 @@ function World.observer(world: World, ...)
|
||||||
|
|
||||||
if skip then
|
if skip then
|
||||||
last, change = next(hook, last)
|
last, change = next(hook, last)
|
||||||
|
ids = change.ids
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
matched = true
|
matched = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local queryOutput = {}
|
local queryOutput = table.create(idsCount)
|
||||||
local row = change.offset
|
local row = change.offset
|
||||||
local archetype = change.archetype
|
local archetype = change.archetype
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
local archetypeRecords = archetype.records
|
local archetypeRecords = archetype.records
|
||||||
for _, id in componentIds do
|
for index, id in componentIds do
|
||||||
table.insert(queryOutput, columns[archetypeRecords[id]][row])
|
queryOutput[index] = columns[archetypeRecords[id]][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return archetype.entities[row], unpack(queryOutput, 1, #queryOutput)
|
return archetype.entities[row], unpack(queryOutput, 1, idsCount)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end;
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function World.__iter(world: World): () -> (number?, unknown?)
|
||||||
|
local entityIndex = world.entityIndex
|
||||||
|
local last
|
||||||
|
|
||||||
|
return function()
|
||||||
|
local entity, record = next(entityIndex, last)
|
||||||
|
if not entity then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
last = entity
|
||||||
|
|
||||||
|
local archetype = record.archetype
|
||||||
|
if not archetype then
|
||||||
|
-- Returns only the entity id as an entity without data should not return
|
||||||
|
-- data and allow the user to get an error if they don't handle the case.
|
||||||
|
return entity
|
||||||
|
end
|
||||||
|
|
||||||
|
local row = record.row
|
||||||
|
local types = archetype.types
|
||||||
|
local columns = archetype.columns
|
||||||
|
local entityData = {}
|
||||||
|
for i, column in columns do
|
||||||
|
-- We use types because the key should be the component ID not the column index
|
||||||
|
entityData[types[i]] = column[row]
|
||||||
|
end
|
||||||
|
|
||||||
|
return entity, entityData
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return table.freeze({
|
return table.freeze({
|
||||||
World = World,
|
World = World;
|
||||||
ON_ADD = ON_ADD,
|
ON_ADD = ON_ADD;
|
||||||
ON_REMOVE = ON_REMOVE,
|
ON_REMOVE = ON_REMOVE;
|
||||||
ON_SET = ON_SET
|
ON_SET = ON_SET;
|
||||||
})
|
})
|
||||||
|
|
|
@ -299,5 +299,38 @@ return function()
|
||||||
expect(world:get(id, Poison)).to.never.be.ok()
|
expect(world:get(id, Poison)).to.never.be.ok()
|
||||||
expect(world:get(id, Health)).to.never.be.ok()
|
expect(world:get(id, Health)).to.never.be.ok()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it("should allow iterating the whole world", function()
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local A, B = world:entity(), world:entity()
|
||||||
|
|
||||||
|
local eA = world:entity()
|
||||||
|
world:set(eA, A, true)
|
||||||
|
local eB = world:entity()
|
||||||
|
world:set(eB, B, true)
|
||||||
|
local eAB = world:entity()
|
||||||
|
world:set(eAB, A, true)
|
||||||
|
world:set(eAB, B, true)
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for id, data in world do
|
||||||
|
count += 1
|
||||||
|
if id == eA then
|
||||||
|
expect(data[A]).to.be.ok()
|
||||||
|
expect(data[B]).to.never.be.ok()
|
||||||
|
elseif id == eB then
|
||||||
|
expect(data[B]).to.be.ok()
|
||||||
|
expect(data[A]).to.never.be.ok()
|
||||||
|
elseif id == eAB then
|
||||||
|
expect(data[A]).to.be.ok()
|
||||||
|
expect(data[B]).to.be.ok()
|
||||||
|
else
|
||||||
|
error("unknown entity", id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(count).to.equal(3)
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
383
mirror/init.lua
383
mirror/init.lua
|
@ -6,10 +6,10 @@
|
||||||
type i53 = number
|
type i53 = number
|
||||||
type i24 = number
|
type i24 = number
|
||||||
|
|
||||||
type Ty = { i53 }
|
type Ty = {i53}
|
||||||
type ArchetypeId = number
|
type ArchetypeId = number
|
||||||
|
|
||||||
type Column = { any }
|
type Column = {any}
|
||||||
|
|
||||||
type Archetype = {
|
type Archetype = {
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -21,8 +21,8 @@ type Archetype = {
|
||||||
},
|
},
|
||||||
types: Ty,
|
types: Ty,
|
||||||
type: string | number,
|
type: string | number,
|
||||||
entities: { number },
|
entities: {number},
|
||||||
columns: { Column },
|
columns: {Column},
|
||||||
records: {},
|
records: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,12 +31,12 @@ type Record = {
|
||||||
row: number,
|
row: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntityIndex = { [i24]: Record }
|
type EntityIndex = {[i24]: Record}
|
||||||
type ComponentIndex = { [i24]: ArchetypeMap}
|
type ComponentIndex = {[i24]: ArchetypeMap}
|
||||||
|
|
||||||
type ArchetypeRecord = number
|
type ArchetypeRecord = number
|
||||||
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord } , size: number }
|
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
|
||||||
type Archetypes = { [ArchetypeId]: Archetype }
|
type Archetypes = {[ArchetypeId]: Archetype}
|
||||||
|
|
||||||
type ArchetypeDiff = {
|
type ArchetypeDiff = {
|
||||||
added: Ty,
|
added: Ty,
|
||||||
|
@ -51,38 +51,58 @@ local REST = HI_COMPONENT_ID + 4
|
||||||
|
|
||||||
local function transitionArchetype(
|
local function transitionArchetype(
|
||||||
entityIndex: EntityIndex,
|
entityIndex: EntityIndex,
|
||||||
destinationArchetype: Archetype,
|
to: Archetype,
|
||||||
destinationRow: i24,
|
destinationRow: i24,
|
||||||
sourceArchetype: Archetype,
|
from: Archetype,
|
||||||
sourceRow: i24
|
sourceRow: i24
|
||||||
)
|
)
|
||||||
local columns = sourceArchetype.columns
|
local columns = from.columns
|
||||||
local sourceEntities = sourceArchetype.entities
|
local sourceEntities = from.entities
|
||||||
local destinationEntities = destinationArchetype.entities
|
local destinationEntities = to.entities
|
||||||
local destinationColumns = destinationArchetype.columns
|
local destinationColumns = to.columns
|
||||||
|
local tr = to.records
|
||||||
|
local types = from.types
|
||||||
|
|
||||||
for componentId, column in columns do
|
for i, column in columns do
|
||||||
local targetColumn = destinationColumns[componentId]
|
-- 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 targetColumn = destinationColumns[tr[types[i]]]
|
||||||
|
|
||||||
|
-- Sometimes target column may not exist, e.g. when you remove a component.
|
||||||
if targetColumn then
|
if targetColumn then
|
||||||
targetColumn[destinationRow] = column[sourceRow]
|
targetColumn[destinationRow] = column[sourceRow]
|
||||||
end
|
end
|
||||||
column[sourceRow] = column[#column]
|
-- If the entity is the last row in the archetype then swapping it would be meaningless.
|
||||||
column[#column] = nil
|
local last = #column
|
||||||
|
if sourceRow ~= last then
|
||||||
|
-- Swap rempves columns to ensure there are no holes in the archetype.
|
||||||
|
column[sourceRow] = column[last]
|
||||||
|
end
|
||||||
|
column[last] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
destinationEntities[destinationRow] = sourceEntities[sourceRow]
|
-- Move the entity from the source to the destination archetype.
|
||||||
entityIndex[sourceEntities[sourceRow]].row = destinationRow
|
local atSourceRow = sourceEntities[sourceRow]
|
||||||
|
destinationEntities[destinationRow] = atSourceRow
|
||||||
|
entityIndex[atSourceRow].row = destinationRow
|
||||||
|
|
||||||
|
-- Because we have swapped columns we now have to update the records
|
||||||
|
-- corresponding to the entities' rows that were swapped.
|
||||||
local movedAway = #sourceEntities
|
local movedAway = #sourceEntities
|
||||||
sourceEntities[sourceRow] = sourceEntities[movedAway]
|
if sourceRow ~= movedAway then
|
||||||
entityIndex[sourceEntities[movedAway]].row = sourceRow
|
local atMovedAway = sourceEntities[movedAway]
|
||||||
|
sourceEntities[sourceRow] = atMovedAway
|
||||||
|
entityIndex[atMovedAway].row = sourceRow
|
||||||
|
end
|
||||||
|
|
||||||
sourceEntities[movedAway] = nil
|
sourceEntities[movedAway] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeAppend(entity: i53, archetype: Archetype): i24
|
local function archetypeAppend(entity: number, archetype: Archetype): number
|
||||||
local entities = archetype.entities
|
local entities = archetype.entities
|
||||||
table.insert(entities, entity)
|
local length = #entities + 1
|
||||||
return #entities
|
entities[length] = entity
|
||||||
|
return length
|
||||||
end
|
end
|
||||||
|
|
||||||
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
|
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
|
||||||
|
@ -105,47 +125,51 @@ local function hash(arr): string | number
|
||||||
return table.concat(arr, "_")
|
return table.concat(arr, "_")
|
||||||
end
|
end
|
||||||
|
|
||||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?)
|
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?)
|
||||||
local destinationCount = #to.types
|
|
||||||
local destinationIds = to.types
|
local destinationIds = to.types
|
||||||
|
local records = to.records
|
||||||
|
local id = to.id
|
||||||
|
|
||||||
for i = 1, destinationCount do
|
for i, destinationId in destinationIds do
|
||||||
local destinationId = destinationIds[i]
|
local archetypesMap = componentIndex[destinationId]
|
||||||
|
|
||||||
if not componentIndex[destinationId] then
|
if not archetypesMap then
|
||||||
componentIndex[destinationId] = { size = 0, sparse = {} }
|
archetypesMap = {size = 0, sparse = {}}
|
||||||
|
componentIndex[destinationId] = archetypesMap
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetypesMap = componentIndex[destinationId]
|
archetypesMap.sparse[id] = i
|
||||||
archetypesMap.sparse[to.id] = i
|
records[destinationId] = i
|
||||||
to.records[destinationId] = i
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype
|
local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype
|
||||||
local ty = hash(types)
|
local ty = hash(types)
|
||||||
|
|
||||||
world.nextArchetypeId = (world.nextArchetypeId::number)+ 1
|
local id = world.nextArchetypeId + 1
|
||||||
local id = world.nextArchetypeId
|
world.nextArchetypeId = id
|
||||||
|
|
||||||
local columns = {} :: { any }
|
local length = #types
|
||||||
|
local columns = table.create(length) :: {any}
|
||||||
|
|
||||||
for _ in types do
|
for index in types do
|
||||||
table.insert(columns, {})
|
columns[index] = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetype = {
|
local archetype = {
|
||||||
id = id,
|
columns = columns;
|
||||||
types = types,
|
edges = {};
|
||||||
type = ty,
|
entities = {};
|
||||||
columns = columns,
|
id = id;
|
||||||
entities = {},
|
records = {};
|
||||||
edges = {},
|
type = ty;
|
||||||
records = {},
|
types = types;
|
||||||
}
|
}
|
||||||
world.archetypeIndex[ty] = archetype
|
world.archetypeIndex[ty] = archetype
|
||||||
world.archetypes[id] = archetype
|
world.archetypes[id] = archetype
|
||||||
|
if length > 0 then
|
||||||
createArchetypeRecords(world.componentIndex, archetype, prev)
|
createArchetypeRecords(world.componentIndex, archetype, prev)
|
||||||
|
end
|
||||||
|
|
||||||
return archetype
|
return archetype
|
||||||
end
|
end
|
||||||
|
@ -154,17 +178,17 @@ local World = {}
|
||||||
World.__index = World
|
World.__index = World
|
||||||
function World.new()
|
function World.new()
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
entityIndex = {},
|
archetypeIndex = {};
|
||||||
componentIndex = {},
|
archetypes = {};
|
||||||
archetypes = {},
|
componentIndex = {};
|
||||||
archetypeIndex = {},
|
entityIndex = {};
|
||||||
ROOT_ARCHETYPE = (nil :: any) :: Archetype,
|
|
||||||
nextEntityId = 0,
|
|
||||||
nextComponentId = 0,
|
|
||||||
nextArchetypeId = 0,
|
|
||||||
hooks = {
|
hooks = {
|
||||||
[ON_ADD] = {}
|
[ON_ADD] = {};
|
||||||
}
|
};
|
||||||
|
nextArchetypeId = 0;
|
||||||
|
nextComponentId = 0;
|
||||||
|
nextEntityId = 0;
|
||||||
|
ROOT_ARCHETYPE = (nil :: any) :: Archetype;
|
||||||
}, World)
|
}, World)
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
@ -173,34 +197,32 @@ local function emit(world, eventDescription)
|
||||||
local event = eventDescription.event
|
local event = eventDescription.event
|
||||||
|
|
||||||
table.insert(world.hooks[event], {
|
table.insert(world.hooks[event], {
|
||||||
ids = eventDescription.ids,
|
archetype = eventDescription.archetype;
|
||||||
archetype = eventDescription.archetype,
|
ids = eventDescription.ids;
|
||||||
otherArchetype = eventDescription.otherArchetype,
|
offset = eventDescription.offset;
|
||||||
offset = eventDescription.offset
|
otherArchetype = eventDescription.otherArchetype;
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
|
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
|
||||||
if #added > 0 then
|
if #added > 0 then
|
||||||
emit(world, {
|
emit(world, {
|
||||||
event = ON_ADD,
|
archetype = archetype;
|
||||||
ids = added,
|
event = ON_ADD;
|
||||||
archetype = archetype,
|
ids = added;
|
||||||
otherArchetype = otherArchetype,
|
offset = row;
|
||||||
offset = row,
|
otherArchetype = otherArchetype;
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
export type World = typeof(World.new())
|
export type World = typeof(World.new())
|
||||||
|
|
||||||
local function ensureArchetype(world: World, types, prev)
|
local function ensureArchetype(world: World, types, prev)
|
||||||
if #types < 1 then
|
if #types < 1 then
|
||||||
return world.ROOT_ARCHETYPE
|
return world.ROOT_ARCHETYPE
|
||||||
end
|
end
|
||||||
|
|
||||||
local ty = hash(types)
|
local ty = hash(types)
|
||||||
local archetype = world.archetypeIndex[ty]
|
local archetype = world.archetypeIndex[ty]
|
||||||
if archetype then
|
if archetype then
|
||||||
|
@ -210,10 +232,8 @@ local function ensureArchetype(world: World, types, prev)
|
||||||
return archetypeOf(world, types, prev)
|
return archetypeOf(world, types, prev)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findInsert(types: { i53 }, toAdd: i53)
|
local function findInsert(types: {i53}, toAdd: i53)
|
||||||
local count = #types
|
for i, id in types do
|
||||||
for i = 1, count do
|
|
||||||
local id = types[i]
|
|
||||||
if id == toAdd then
|
if id == toAdd then
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
|
@ -221,13 +241,18 @@ local function findInsert(types: { i53 }, toAdd: i53)
|
||||||
return i
|
return i
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return count + 1
|
return #types + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
|
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
|
||||||
local types = node.types
|
local types = node.types
|
||||||
|
-- Component IDs are added incrementally, so inserting and sorting
|
||||||
|
-- them each time would be expensive. Instead this insertion sort can find the insertion
|
||||||
|
-- point in the types array.
|
||||||
local at = findInsert(types, componentId)
|
local at = findInsert(types, componentId)
|
||||||
if at == -1 then
|
if at == -1 then
|
||||||
|
-- If it finds a duplicate, it just means it is the same archetype so it can return it
|
||||||
|
-- directly instead of needing to hash types for a lookup to the archetype.
|
||||||
return node
|
return node
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -237,88 +262,108 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ensureEdge(archetype: Archetype, componentId: i53)
|
local function ensureEdge(archetype: Archetype, componentId: i53)
|
||||||
if not archetype.edges[componentId] then
|
local edges = archetype.edges
|
||||||
archetype.edges[componentId] = {} :: any
|
local edge = edges[componentId]
|
||||||
|
if not edge then
|
||||||
|
edge = {} :: any
|
||||||
|
edges[componentId] = edge
|
||||||
end
|
end
|
||||||
return archetype.edges[componentId]
|
return edge
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
|
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
|
||||||
if not from then
|
if not from then
|
||||||
if not world.ROOT_ARCHETYPE then
|
-- If there was no source archetype then it should return the ROOT_ARCHETYPE
|
||||||
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
|
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
if not ROOT_ARCHETYPE then
|
||||||
|
ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
|
||||||
|
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never
|
||||||
end
|
end
|
||||||
from = world.ROOT_ARCHETYPE
|
from = ROOT_ARCHETYPE
|
||||||
end
|
end
|
||||||
|
|
||||||
local edge = ensureEdge(from, componentId)
|
local edge = ensureEdge(from, componentId)
|
||||||
|
local add = edge.add
|
||||||
if not edge.add then
|
if not add then
|
||||||
edge.add = findArchetypeWith(world, from, componentId)
|
-- Save an edge using the component ID to the archetype to allow
|
||||||
|
-- faster traversals to adjacent archetypes.
|
||||||
|
add = findArchetypeWith(world, from, componentId)
|
||||||
|
edge.add = add :: never
|
||||||
end
|
end
|
||||||
|
|
||||||
return edge.add
|
return add
|
||||||
end
|
end
|
||||||
|
|
||||||
local function ensureRecord(entityIndex, entityId: i53): Record
|
local function ensureRecord(entityIndex, entityId: i53): Record
|
||||||
local id = entityId
|
local record = entityIndex[entityId]
|
||||||
if not entityIndex[id] then
|
|
||||||
entityIndex[id] = {}
|
if not record then
|
||||||
|
record = {}
|
||||||
|
entityIndex[entityId] = record
|
||||||
end
|
end
|
||||||
return entityIndex[id] :: Record
|
|
||||||
|
return record :: Record
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
|
||||||
local record = ensureRecord(world.entityIndex, entityId)
|
local record = ensureRecord(world.entityIndex, entityId)
|
||||||
local sourceArchetype = record.archetype
|
local from = record.archetype
|
||||||
local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype)
|
local to = archetypeTraverseAdd(world, componentId, from)
|
||||||
|
|
||||||
if sourceArchetype == destinationArchetype then
|
if from == to then
|
||||||
local archetypeRecord = destinationArchetype.records[componentId]
|
-- If the archetypes are the same it can avoid moving the entity
|
||||||
destinationArchetype.columns[archetypeRecord][record.row] = data
|
-- and just set the data directly.
|
||||||
|
local archetypeRecord = to.records[componentId]
|
||||||
|
from.columns[archetypeRecord][record.row] = data
|
||||||
|
-- Should fire an OnSet event here.
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if sourceArchetype then
|
if from then
|
||||||
moveEntity(world.entityIndex, entityId, record, destinationArchetype)
|
-- If there was a previous archetype, then the entity needs to move the archetype
|
||||||
|
moveEntity(world.entityIndex, entityId, record, to)
|
||||||
else
|
else
|
||||||
if #destinationArchetype.types > 0 then
|
if #to.types > 0 then
|
||||||
newEntity(entityId, record, destinationArchetype)
|
-- When there is no previous archetype it should create the archetype
|
||||||
onNotifyAdd(world, destinationArchetype, sourceArchetype, record.row, { componentId })
|
newEntity(entityId, record, to)
|
||||||
|
onNotifyAdd(world, to, from, record.row, {componentId})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local archetypeRecord = destinationArchetype.records[componentId]
|
local archetypeRecord = to.records[componentId]
|
||||||
destinationArchetype.columns[archetypeRecord][record.row] = data
|
to.columns[archetypeRecord][record.row] = data
|
||||||
end
|
end
|
||||||
|
|
||||||
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
|
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
|
||||||
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
|
local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
|
||||||
local edge = ensureEdge(from, componentId)
|
local edge = ensureEdge(from, componentId)
|
||||||
|
|
||||||
|
local remove = edge.remove
|
||||||
if not edge.remove then
|
if not remove then
|
||||||
local to = table.clone(from.types)
|
local to = table.clone(from.types)
|
||||||
table.remove(to, table.find(to, componentId))
|
table.remove(to, table.find(to, componentId))
|
||||||
edge.remove = ensureArchetype(world, to, from)
|
remove = ensureArchetype(world, to, from)
|
||||||
|
edge.remove = remove :: never
|
||||||
end
|
end
|
||||||
|
|
||||||
return edge.remove
|
return remove
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.remove(world: World, entityId: i53, componentId: i53)
|
function World.remove(world: World, entityId: i53, componentId: i53)
|
||||||
local record = ensureRecord(world.entityIndex, entityId)
|
local entityIndex = world.entityIndex
|
||||||
|
local record = ensureRecord(entityIndex, entityId)
|
||||||
local sourceArchetype = record.archetype
|
local sourceArchetype = record.archetype
|
||||||
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
|
local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
|
||||||
|
|
||||||
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
if sourceArchetype and not (sourceArchetype == destinationArchetype) then
|
||||||
moveEntity(world.entityIndex, entityId, record, destinationArchetype)
|
moveEntity(entityIndex, entityId, record, destinationArchetype)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function get(componentIndex: { [i24]: ArchetypeMap }, record: Record, componentId: i24)
|
-- Keeping the function as small as possible to enable inlining
|
||||||
|
local function get(record: Record, componentId: i24)
|
||||||
local archetype = record.archetype
|
local archetype = record.archetype
|
||||||
local archetypeRecord = componentIndex[componentId].sparse[archetype.id]
|
local archetypeRecord = archetype.records[componentId]
|
||||||
|
|
||||||
if not archetypeRecord then
|
if not archetypeRecord then
|
||||||
return nil
|
return nil
|
||||||
|
@ -329,35 +374,35 @@ end
|
||||||
|
|
||||||
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
|
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
|
||||||
local id = entityId
|
local id = entityId
|
||||||
local componentIndex = world.componentIndex
|
|
||||||
local record = world.entityIndex[id]
|
local record = world.entityIndex[id]
|
||||||
if not record then
|
if not record then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local va = get(componentIndex, record, a)
|
local va = get(record, a)
|
||||||
|
|
||||||
if b == nil then
|
if b == nil then
|
||||||
return va
|
return va
|
||||||
elseif c == nil then
|
elseif c == nil then
|
||||||
return va, get(componentIndex, record, b)
|
return va, get(record, b)
|
||||||
elseif d == nil then
|
elseif d == nil then
|
||||||
return va, get(componentIndex, record, b), get(componentIndex, record, c)
|
return va, get(record, b), get(record, c)
|
||||||
elseif e == nil then
|
elseif e == nil then
|
||||||
return va, get(componentIndex, record, b), get(componentIndex, record, c), get(componentIndex, record, d)
|
return va, get(record, b), get(record, c), get(record, d)
|
||||||
else
|
else
|
||||||
error("args exceeded")
|
error("args exceeded")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function noop(self: Query, ...: i53): () -> (number, ...any)
|
-- the less creation the better
|
||||||
return function()
|
local function actualNoOperation() end
|
||||||
end :: any
|
local function noop(_self: Query, ...: i53): () -> (number, ...any)
|
||||||
|
return actualNoOperation :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
local EmptyQuery = {
|
local EmptyQuery = {
|
||||||
__iter = noop,
|
__iter = noop;
|
||||||
without = noop
|
without = noop;
|
||||||
}
|
}
|
||||||
EmptyQuery.__index = EmptyQuery
|
EmptyQuery.__index = EmptyQuery
|
||||||
setmetatable(EmptyQuery, EmptyQuery)
|
setmetatable(EmptyQuery, EmptyQuery)
|
||||||
|
@ -365,19 +410,22 @@ setmetatable(EmptyQuery, EmptyQuery)
|
||||||
export type Query = typeof(EmptyQuery)
|
export type Query = typeof(EmptyQuery)
|
||||||
|
|
||||||
function World.query(world: World, ...: i53): Query
|
function World.query(world: World, ...: i53): Query
|
||||||
local compatibleArchetypes = {}
|
-- breaking?
|
||||||
local components = { ... }
|
if (...) == nil then
|
||||||
local archetypes = world.archetypes
|
|
||||||
local queryLength = #components
|
|
||||||
|
|
||||||
if queryLength == 0 then
|
|
||||||
error("Missing components")
|
error("Missing components")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local compatibleArchetypes = {}
|
||||||
|
local length = 0
|
||||||
|
|
||||||
|
local components = {...}
|
||||||
|
local archetypes = world.archetypes
|
||||||
|
local queryLength = #components
|
||||||
|
|
||||||
local firstArchetypeMap
|
local firstArchetypeMap
|
||||||
local componentIndex = world.componentIndex
|
local componentIndex = world.componentIndex
|
||||||
|
|
||||||
for i, componentId in components do
|
for _, componentId in components do
|
||||||
local map = componentIndex[componentId]
|
local map = componentIndex[componentId]
|
||||||
if not map then
|
if not map then
|
||||||
return EmptyQuery
|
return EmptyQuery
|
||||||
|
@ -388,27 +436,27 @@ function World.query(world: World, ...: i53): Query
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local i = 0
|
|
||||||
for id in firstArchetypeMap.sparse do
|
for id in firstArchetypeMap.sparse do
|
||||||
local archetype = archetypes[id]
|
local archetype = archetypes[id]
|
||||||
local archetypeRecords = archetype.records
|
local archetypeRecords = archetype.records
|
||||||
local indices = {}
|
local indices = {}
|
||||||
local skip = false
|
local skip = false
|
||||||
|
|
||||||
for j, componentId in components do
|
for i, componentId in components do
|
||||||
local index = archetypeRecords[componentId]
|
local index = archetypeRecords[componentId]
|
||||||
if not index then
|
if not index then
|
||||||
skip = true
|
skip = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
indices[j] = archetypeRecords[componentId]
|
indices[i] = index
|
||||||
end
|
end
|
||||||
|
|
||||||
if skip then
|
if skip then
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
i += 1
|
|
||||||
table.insert(compatibleArchetypes, { archetype, indices })
|
length += 1
|
||||||
|
compatibleArchetypes[length] = {archetype, indices}
|
||||||
end
|
end
|
||||||
|
|
||||||
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
|
local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
|
||||||
|
@ -420,16 +468,19 @@ function World.query(world: World, ...: i53): Query
|
||||||
preparedQuery.__index = preparedQuery
|
preparedQuery.__index = preparedQuery
|
||||||
|
|
||||||
function preparedQuery:without(...)
|
function preparedQuery:without(...)
|
||||||
local components = { ... }
|
local withoutComponents = {...}
|
||||||
for i = #compatibleArchetypes, 1, -1 do
|
for i = #compatibleArchetypes, 1, -1 do
|
||||||
local archetype = compatibleArchetypes[i][1]
|
local archetype = compatibleArchetypes[i][1]
|
||||||
|
local records = archetype.records
|
||||||
local shouldRemove = false
|
local shouldRemove = false
|
||||||
for _, componentId in components do
|
|
||||||
if archetype.records[componentId] then
|
for _, componentId in withoutComponents do
|
||||||
|
if records[componentId] then
|
||||||
shouldRemove = true
|
shouldRemove = true
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if shouldRemove then
|
if shouldRemove then
|
||||||
table.remove(compatibleArchetypes, i)
|
table.remove(compatibleArchetypes, i)
|
||||||
end
|
end
|
||||||
|
@ -446,7 +497,6 @@ function World.query(world: World, ...: i53): Query
|
||||||
local lastRow
|
local lastRow
|
||||||
local queryOutput = {}
|
local queryOutput = {}
|
||||||
|
|
||||||
|
|
||||||
function preparedQuery:__iter()
|
function preparedQuery:__iter()
|
||||||
return function()
|
return function()
|
||||||
local archetype = compatibleArchetype[1]
|
local archetype = compatibleArchetype[1]
|
||||||
|
@ -470,16 +520,9 @@ function World.query(world: World, ...: i53): Query
|
||||||
elseif queryLength == 2 then
|
elseif queryLength == 2 then
|
||||||
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
return entityId, columns[tr[1]][row], columns[tr[2]][row]
|
||||||
elseif queryLength == 3 then
|
elseif queryLength == 3 then
|
||||||
return entityId,
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
|
||||||
columns[tr[1]][row],
|
|
||||||
columns[tr[2]][row],
|
|
||||||
columns[tr[3]][row]
|
|
||||||
elseif queryLength == 4 then
|
elseif queryLength == 4 then
|
||||||
return entityId,
|
return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
|
||||||
columns[tr[1]][row],
|
|
||||||
columns[tr[2]][row],
|
|
||||||
columns[tr[3]][row],
|
|
||||||
columns[tr[4]][row]
|
|
||||||
elseif queryLength == 5 then
|
elseif queryLength == 5 then
|
||||||
return entityId,
|
return entityId,
|
||||||
columns[tr[1]][row],
|
columns[tr[1]][row],
|
||||||
|
@ -517,7 +560,7 @@ function World.query(world: World, ...: i53): Query
|
||||||
end
|
end
|
||||||
|
|
||||||
for i in components do
|
for i in components do
|
||||||
queryOutput[i] = tr[i][row]
|
queryOutput[i] = columns[tr[i]][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return entityId, unpack(queryOutput, 1, queryLength)
|
return entityId, unpack(queryOutput, 1, queryLength)
|
||||||
|
@ -530,24 +573,40 @@ end
|
||||||
function World.component(world: World)
|
function World.component(world: World)
|
||||||
local componentId = world.nextComponentId + 1
|
local componentId = world.nextComponentId + 1
|
||||||
if componentId > HI_COMPONENT_ID then
|
if componentId > HI_COMPONENT_ID then
|
||||||
error("Too many components")
|
-- IDs are partitioned into ranges because component IDs are not nominal,
|
||||||
|
-- so it needs to error when IDs intersect into the entity range.
|
||||||
|
error("Too many components, consider using world:entity() instead to create components.")
|
||||||
end
|
end
|
||||||
world.nextComponentId = componentId
|
world.nextComponentId = componentId
|
||||||
return componentId
|
return componentId
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.entity(world: World)
|
function World.entity(world: World)
|
||||||
world.nextEntityId += 1
|
local nextEntityId = world.nextEntityId + 1
|
||||||
return world.nextEntityId + REST
|
world.nextEntityId = nextEntityId
|
||||||
|
return nextEntityId + REST
|
||||||
|
end
|
||||||
|
|
||||||
|
function World.delete(world: World, entityId: i53)
|
||||||
|
local entityIndex = world.entityIndex
|
||||||
|
local record = entityIndex[entityId]
|
||||||
|
moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE)
|
||||||
|
-- Since we just appended an entity to the ROOT_ARCHETYPE we have to remove it from
|
||||||
|
-- the entities array and delete the record. We know there won't be the hole since
|
||||||
|
-- we are always removing the last row.
|
||||||
|
--world.ROOT_ARCHETYPE.entities[record.row] = nil
|
||||||
|
--entityIndex[entityId] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function World.observer(world: World, ...)
|
function World.observer(world: World, ...)
|
||||||
local componentIds = { ... }
|
local componentIds = {...}
|
||||||
|
local idsCount = #componentIds
|
||||||
|
local hooks = world.hooks
|
||||||
|
|
||||||
return {
|
return {
|
||||||
event = function(event)
|
event = function(event)
|
||||||
local hook = world.hooks[event]
|
local hook = hooks[event]
|
||||||
world.hooks[event] = nil
|
hooks[event] = nil
|
||||||
|
|
||||||
local last, change
|
local last, change
|
||||||
return function()
|
return function()
|
||||||
|
@ -557,10 +616,11 @@ function World.observer(world: World, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
local matched = false
|
local matched = false
|
||||||
|
local ids = change.ids
|
||||||
|
|
||||||
while not matched do
|
while not matched do
|
||||||
local skip = false
|
local skip = false
|
||||||
for _, id in change.ids do
|
for _, id in ids do
|
||||||
if not table.find(componentIds, id) then
|
if not table.find(componentIds, id) then
|
||||||
skip = true
|
skip = true
|
||||||
break
|
break
|
||||||
|
@ -569,30 +629,31 @@ function World.observer(world: World, ...)
|
||||||
|
|
||||||
if skip then
|
if skip then
|
||||||
last, change = next(hook, last)
|
last, change = next(hook, last)
|
||||||
|
ids = change.ids
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
matched = true
|
matched = true
|
||||||
end
|
end
|
||||||
|
|
||||||
local queryOutput = {}
|
local queryOutput = table.create(idsCount)
|
||||||
local row = change.offset
|
local row = change.offset
|
||||||
local archetype = change.archetype
|
local archetype = change.archetype
|
||||||
local columns = archetype.columns
|
local columns = archetype.columns
|
||||||
local archetypeRecords = archetype.records
|
local archetypeRecords = archetype.records
|
||||||
for _, id in componentIds do
|
for index, id in componentIds do
|
||||||
table.insert(queryOutput, columns[archetypeRecords[id]][row])
|
queryOutput[index] = columns[archetypeRecords[id]][row]
|
||||||
end
|
end
|
||||||
|
|
||||||
return archetype.entities[row], unpack(queryOutput, 1, #queryOutput)
|
return archetype.entities[row], unpack(queryOutput, 1, idsCount)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
end;
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
return table.freeze({
|
return table.freeze({
|
||||||
World = World,
|
World = World;
|
||||||
ON_ADD = ON_ADD,
|
ON_ADD = ON_ADD;
|
||||||
ON_REMOVE = ON_REMOVE,
|
ON_REMOVE = ON_REMOVE;
|
||||||
ON_SET = ON_SET
|
ON_SET = ON_SET;
|
||||||
})
|
})
|
||||||
|
|
1499
newMatter.lua
1499
newMatter.lua
File diff suppressed because it is too large
Load diff
1567
oldMatter.lua
1567
oldMatter.lua
File diff suppressed because it is too large
Load diff
4
selene.toml
Normal file
4
selene.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
std = "roblox"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
global_usage = "allow"
|
5
stylua.toml
Normal file
5
stylua.toml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
column_width = 120
|
||||||
|
quote_style = "ForceDouble"
|
||||||
|
|
||||||
|
[sort_requires]
|
||||||
|
enabled = true
|
3
testez-companion.toml
Normal file
3
testez-companion.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
roots = ["ServerStorage"]
|
||||||
|
|
||||||
|
[extraOptions]
|
|
@ -126,6 +126,40 @@ TEST("world:query", function()
|
||||||
CHECK(world:get(id, Poison) == nil)
|
CHECK(world:get(id, Poison) == nil)
|
||||||
CHECK(world:get(id, Health) == 50)
|
CHECK(world:get(id, Health) == 50)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
do CASE "Should allow iterating the whole world"
|
||||||
|
local world = jecs.World.new()
|
||||||
|
|
||||||
|
local A, B = world:entity(), world:entity()
|
||||||
|
|
||||||
|
local eA = world:entity()
|
||||||
|
world:set(eA, A, true)
|
||||||
|
local eB = world:entity()
|
||||||
|
world:set(eB, B, true)
|
||||||
|
local eAB = world:entity()
|
||||||
|
world:set(eAB, A, true)
|
||||||
|
world:set(eAB, B, true)
|
||||||
|
|
||||||
|
local count = 0
|
||||||
|
for id, data in world do
|
||||||
|
count += 1
|
||||||
|
if id == eA then
|
||||||
|
CHECK(data[A] == true)
|
||||||
|
CHECK(data[B] == nil)
|
||||||
|
elseif id == eB then
|
||||||
|
CHECK(data[B] == true)
|
||||||
|
CHECK(data[A] == nil)
|
||||||
|
elseif id == eAB then
|
||||||
|
CHECK(data[A] == true)
|
||||||
|
CHECK(data[B] == true)
|
||||||
|
else
|
||||||
|
error("unknown entity", id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
CHECK(count == 3)
|
||||||
|
end
|
||||||
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
FINISH()
|
FINISH()
|
|
@ -10,6 +10,3 @@ include = ["default.project.json", "lib", "wally.toml", "README.md"]
|
||||||
TestEZ = "roblox/testez@0.4.1"
|
TestEZ = "roblox/testez@0.4.1"
|
||||||
Matter = "matter-ecs/matter@0.8.0"
|
Matter = "matter-ecs/matter@0.8.0"
|
||||||
ecr = "centau/ecr@0.8.0"
|
ecr = "centau/ecr@0.8.0"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue