mirror of
https://github.com/Ukendio/jecs.git
synced 2025-04-25 17:40:02 +00:00
Merge branch 'main' into add-world-iter
This commit is contained in:
commit
8a1880b82f
13 changed files with 1075 additions and 3702 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -50,3 +50,6 @@ WallyPatches
|
||||||
roblox.toml
|
roblox.toml
|
||||||
sourcemap.json
|
sourcemap.json
|
||||||
drafts/*.lua
|
drafts/*.lua
|
||||||
|
|
||||||
|
*.code-workspace
|
||||||
|
roblox.yml
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
287
lib/init.lua
287
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,17 +178,17 @@ local World = {}
|
||||||
World.__index = World
|
World.__index = World
|
||||||
function World.new()
|
function World.new()
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
entityIndex = {} :: EntityIndex,
|
archetypeIndex = {};
|
||||||
componentIndex = {} :: 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
|
||||||
|
@ -192,21 +197,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 +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
|
||||||
|
@ -238,7 +241,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,38 +262,47 @@ 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 there was no source archetype then it should return the ROOT_ARCHETYPE
|
-- If there was no source archetype then it should return the ROOT_ARCHETYPE
|
||||||
if not world.ROOT_ARCHETYPE then
|
local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
|
||||||
local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
|
if not ROOT_ARCHETYPE then
|
||||||
world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
|
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)
|
|
||||||
|
|
||||||
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: 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] = {} :: Record
|
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)
|
||||||
|
@ -314,7 +326,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,28 +338,30 @@ 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)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
-- 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]
|
||||||
|
|
||||||
|
@ -360,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)
|
||||||
|
@ -396,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
|
||||||
|
@ -431,13 +448,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)
|
||||||
|
@ -449,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
|
||||||
|
@ -475,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]
|
||||||
|
@ -499,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],
|
||||||
|
@ -546,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)
|
||||||
|
@ -568,8 +582,9 @@ 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
|
end
|
||||||
|
|
||||||
function World.delete(world: World, entityId: i53)
|
function World.delete(world: World, entityId: i53)
|
||||||
|
@ -584,12 +599,14 @@ function World.delete(world: World, entityId: i53)
|
||||||
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()
|
||||||
|
@ -599,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
|
||||||
|
@ -611,24 +629,25 @@ 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
|
||||||
|
|
||||||
|
@ -664,8 +683,8 @@ function World.__iter(world: World): () -> (number?, unknown?)
|
||||||
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;
|
||||||
})
|
})
|
||||||
|
|
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;
|
||||||
})
|
})
|
||||||
|
|
1421
newMatter.lua
1421
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]
|
|
@ -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