mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-03 18:39:19 +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
 | 
			
		||||
sourcemap.json
 | 
			
		||||
drafts/*.lua
 | 
			
		||||
 | 
			
		||||
*.code-workspace
 | 
			
		||||
roblox.yml
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,4 +3,4 @@ wally = "upliftgames/wally@0.3.1"
 | 
			
		|||
rojo = "rojo-rbx/rojo@7.4.1"
 | 
			
		||||
stylua = "johnnymorganz/stylua@0.19.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,299 +1,200 @@
 | 
			
		|||
--!optimize 2
 | 
			
		||||
--!native
 | 
			
		||||
 | 
			
		||||
local testkit = require('../testkit')
 | 
			
		||||
local testkit = require("../testkit")
 | 
			
		||||
local BENCH, START = testkit.benchmark()
 | 
			
		||||
local function TITLE(title: string)
 | 
			
		||||
    print()
 | 
			
		||||
    print(testkit.color.white(title))
 | 
			
		||||
	print()
 | 
			
		||||
	print(testkit.color.white(title))
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
do TITLE (testkit.color.white_underline("Jecs query"))
 | 
			
		||||
    local ecs = jecs.World.new()
 | 
			
		||||
    do TITLE "one component in common"
 | 
			
		||||
do
 | 
			
		||||
	TITLE(testkit.color.white_underline("Jecs query"))
 | 
			
		||||
	local ecs = jecs.World.new()
 | 
			
		||||
	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()
 | 
			
		||||
				for _ in world:query(A) do
 | 
			
		||||
				end
 | 
			
		||||
			end)
 | 
			
		||||
 | 
			
		||||
            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("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("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
 | 
			
		||||
 | 
			
		||||
            BENCH("8 component", function()
 | 
			
		||||
                for _ in world:query(A, B, C, D, E, F, G, H) do end
 | 
			
		||||
            end)
 | 
			
		||||
        end
 | 
			
		||||
		local D1 = ecs:component()
 | 
			
		||||
		local D2 = ecs:component()
 | 
			
		||||
		local D3 = ecs:component()
 | 
			
		||||
		local D4 = ecs:component()
 | 
			
		||||
		local D5 = ecs:component()
 | 
			
		||||
		local D6 = ecs:component()
 | 
			
		||||
		local D7 = ecs:component()
 | 
			
		||||
		local D8 = ecs:component()
 | 
			
		||||
 | 
			
		||||
        local D1 = ecs:component()
 | 
			
		||||
        local D2 = ecs:component()
 | 
			
		||||
        local D3 = ecs:component()
 | 
			
		||||
        local D4 = ecs:component()
 | 
			
		||||
        local D5 = ecs:component()
 | 
			
		||||
        local D6 = ecs:component()
 | 
			
		||||
        local D7 = ecs:component()
 | 
			
		||||
        local D8 = ecs:component()
 | 
			
		||||
		local function flip()
 | 
			
		||||
			return math.random() >= 0.15
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
        local function flip() 
 | 
			
		||||
            return math.random() >= 0.15
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        local added = 0
 | 
			
		||||
		local added = 0
 | 
			
		||||
		local archetypes = {}
 | 
			
		||||
        for i = 1, 2^16-2 do 
 | 
			
		||||
            local entity = ecs:entity()
 | 
			
		||||
		for i = 1, 2 ^ 16 - 2 do
 | 
			
		||||
			local entity = ecs:entity()
 | 
			
		||||
 | 
			
		||||
            local combination = ""
 | 
			
		||||
			local combination = ""
 | 
			
		||||
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "B"
 | 
			
		||||
                ecs:set(entity, D2, {value = true})
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "C"
 | 
			
		||||
                ecs:set(entity, D3, { value = true })
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "D"
 | 
			
		||||
                ecs:set(entity, D4, { value = true})
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "E"
 | 
			
		||||
                ecs:set(entity, D5, { value = true})
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "F"
 | 
			
		||||
                ecs:set(entity, D6, {value = true})
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "G"
 | 
			
		||||
                ecs:set(entity, D7, { value = true})
 | 
			
		||||
            end
 | 
			
		||||
            if flip() then 
 | 
			
		||||
                combination ..= "H"
 | 
			
		||||
                ecs:set(entity, D8, {value = true})
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "B"
 | 
			
		||||
				ecs:set(entity, D2, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "C"
 | 
			
		||||
				ecs:set(entity, D3, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "D"
 | 
			
		||||
				ecs:set(entity, D4, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "E"
 | 
			
		||||
				ecs:set(entity, D5, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "F"
 | 
			
		||||
				ecs:set(entity, D6, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "G"
 | 
			
		||||
				ecs:set(entity, D7, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "H"
 | 
			
		||||
				ecs:set(entity, D8, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
            end
 | 
			
		||||
 | 
			
		||||
            if #combination == 7 then 
 | 
			
		||||
                added += 1
 | 
			
		||||
                ecs:set(entity, D1, { value = true})
 | 
			
		||||
            end
 | 
			
		||||
			if #combination == 7 then
 | 
			
		||||
				added += 1
 | 
			
		||||
				ecs:set(entity, D1, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			archetypes[combination] = true
 | 
			
		||||
        end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		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)
 | 
			
		||||
    end
 | 
			
		||||
		view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
do TITLE(testkit.color.white_underline("OldMatter query")) 
 | 
			
		||||
do
 | 
			
		||||
	TITLE(testkit.color.white_underline("Mirror query"))
 | 
			
		||||
	local ecs = mirror.World.new()
 | 
			
		||||
	do
 | 
			
		||||
		TITLE("one component in common")
 | 
			
		||||
 | 
			
		||||
    local ecs = oldMatter.World.new()
 | 
			
		||||
    local component = oldMatter.component
 | 
			
		||||
		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)
 | 
			
		||||
 | 
			
		||||
    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("2 component", function()
 | 
			
		||||
				for _ in world:query(A, B) do
 | 
			
		||||
				end
 | 
			
		||||
			end)
 | 
			
		||||
 | 
			
		||||
            BENCH("1 component", function()
 | 
			
		||||
                for _ in world:query(A) do end
 | 
			
		||||
            end)
 | 
			
		||||
			BENCH("4 component", function()
 | 
			
		||||
				for _ in world:query(A, B, C, D) do
 | 
			
		||||
				end
 | 
			
		||||
			end)
 | 
			
		||||
 | 
			
		||||
            BENCH("2 component", function()
 | 
			
		||||
                for _ in world:query(A, B) do end
 | 
			
		||||
            end)
 | 
			
		||||
			BENCH("8 component", function()
 | 
			
		||||
				for _ in world:query(A, B, C, D, E, F, G, H) do
 | 
			
		||||
				end
 | 
			
		||||
			end)
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
            BENCH("4 component", function()
 | 
			
		||||
                for _ in world:query(A, B, C, D) do 
 | 
			
		||||
                end
 | 
			
		||||
            end)
 | 
			
		||||
		local D1 = ecs:component()
 | 
			
		||||
		local D2 = ecs:component()
 | 
			
		||||
		local D3 = ecs:component()
 | 
			
		||||
		local D4 = ecs:component()
 | 
			
		||||
		local D5 = ecs:component()
 | 
			
		||||
		local D6 = ecs:component()
 | 
			
		||||
		local D7 = ecs:component()
 | 
			
		||||
		local D8 = ecs:component()
 | 
			
		||||
 | 
			
		||||
            BENCH("8 component", function()
 | 
			
		||||
                for _ in world:query(A, B, C, D, E, F, G, H) do end
 | 
			
		||||
            end)
 | 
			
		||||
        end
 | 
			
		||||
		local function flip()
 | 
			
		||||
			return math.random() >= 0.15
 | 
			
		||||
		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 added = 0
 | 
			
		||||
		local archetypes = {}
 | 
			
		||||
        for i = 1, 2^16-2 do 
 | 
			
		||||
            local entity = ecs:spawn()
 | 
			
		||||
		for i = 1, 2 ^ 16 - 2 do
 | 
			
		||||
			local entity = ecs:entity()
 | 
			
		||||
 | 
			
		||||
            local combination = ""
 | 
			
		||||
			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}))
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "B"
 | 
			
		||||
				ecs:set(entity, D2, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "C"
 | 
			
		||||
				ecs:set(entity, D3, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "D"
 | 
			
		||||
				ecs:set(entity, D4, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "E"
 | 
			
		||||
				ecs:set(entity, D5, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "F"
 | 
			
		||||
				ecs:set(entity, D6, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "G"
 | 
			
		||||
				ecs:set(entity, D7, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			if flip() then
 | 
			
		||||
				combination ..= "H"
 | 
			
		||||
				ecs:set(entity, D8, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
            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
 | 
			
		||||
			if #combination == 7 then
 | 
			
		||||
				added += 1
 | 
			
		||||
				ecs:set(entity, D1, {value = true})
 | 
			
		||||
			end
 | 
			
		||||
			archetypes[combination] = true
 | 
			
		||||
        end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local a = 0
 | 
			
		||||
		for _ in archetypes do a+= 1 end
 | 
			
		||||
 | 
			
		||||
        view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
 | 
			
		||||
    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
 | 
			
		||||
		for _ in archetypes do
 | 
			
		||||
			a += 1
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -2,41 +2,37 @@
 | 
			
		|||
--!native
 | 
			
		||||
 | 
			
		||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
			
		||||
local rgb = require(ReplicatedStorage.rgb)
 | 
			
		||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
			
		||||
local jecs = require(ReplicatedStorage.Lib)
 | 
			
		||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
			
		||||
local jecs = require(ReplicatedStorage.Lib)
 | 
			
		||||
local rgb = require(ReplicatedStorage.rgb)
 | 
			
		||||
local newWorld = Matter.World.new()
 | 
			
		||||
local ecs = jecs.World.new()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
return {
 | 
			
		||||
	ParameterGenerator = function()
 | 
			
		||||
        local registry2 = ecr.registry()
 | 
			
		||||
		local registry2 = ecr.registry()
 | 
			
		||||
 | 
			
		||||
		return registry2
 | 
			
		||||
	end,
 | 
			
		||||
	end;
 | 
			
		||||
 | 
			
		||||
	Functions = {
 | 
			
		||||
		Matter = function()
 | 
			
		||||
            for i = 1, 1000 do 
 | 
			
		||||
               newWorld:spawn()             
 | 
			
		||||
            end
 | 
			
		||||
		end,
 | 
			
		||||
 | 
			
		||||
			for i = 1, 1000 do
 | 
			
		||||
				newWorld:spawn()
 | 
			
		||||
			end
 | 
			
		||||
		end;
 | 
			
		||||
 | 
			
		||||
		ECR = function(_, registry2)
 | 
			
		||||
            for i = 1, 1000 do
 | 
			
		||||
                registry2.create()
 | 
			
		||||
            end
 | 
			
		||||
		end,
 | 
			
		||||
 | 
			
		||||
			for i = 1, 1000 do
 | 
			
		||||
				registry2.create()
 | 
			
		||||
			end
 | 
			
		||||
		end;
 | 
			
		||||
 | 
			
		||||
		Jecs = function()
 | 
			
		||||
            for i = 1, 1000 do 
 | 
			
		||||
                ecs:entity()
 | 
			
		||||
            end
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
	},
 | 
			
		||||
			for i = 1, 1000 do
 | 
			
		||||
				ecs:entity()
 | 
			
		||||
			end
 | 
			
		||||
		end;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										295
									
								
								lib/init.lua
									
									
									
									
									
								
							
							
						
						
									
										295
									
								
								lib/init.lua
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -6,10 +6,10 @@
 | 
			
		|||
type i53 = number
 | 
			
		||||
type i24 = number
 | 
			
		||||
 | 
			
		||||
type Ty = { i53 }
 | 
			
		||||
type Ty = {i53}
 | 
			
		||||
type ArchetypeId = number
 | 
			
		||||
 | 
			
		||||
type Column = { any }
 | 
			
		||||
type Column = {any}
 | 
			
		||||
 | 
			
		||||
type Archetype = {
 | 
			
		||||
	id: number,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,9 +20,9 @@ type Archetype = {
 | 
			
		|||
		},
 | 
			
		||||
	},
 | 
			
		||||
	types: Ty,
 | 
			
		||||
    type: string | number,
 | 
			
		||||
	entities: { number },
 | 
			
		||||
	columns: { Column },
 | 
			
		||||
	type: string | number,
 | 
			
		||||
	entities: {number},
 | 
			
		||||
	columns: {Column},
 | 
			
		||||
	records: {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +31,12 @@ type Record = {
 | 
			
		|||
	row: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EntityIndex = { [i24]: Record }
 | 
			
		||||
type ComponentIndex = { [i24]: ArchetypeMap}
 | 
			
		||||
type EntityIndex = {[i24]: Record}
 | 
			
		||||
type ComponentIndex = {[i24]: ArchetypeMap}
 | 
			
		||||
 | 
			
		||||
type ArchetypeRecord = number
 | 
			
		||||
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord } , size: number }
 | 
			
		||||
type Archetypes = { [ArchetypeId]: Archetype }
 | 
			
		||||
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
 | 
			
		||||
type Archetypes = {[ArchetypeId]: Archetype}
 | 
			
		||||
 | 
			
		||||
type ArchetypeDiff = {
 | 
			
		||||
	added: Ty,
 | 
			
		||||
| 
						 | 
				
			
			@ -82,24 +82,27 @@ local function transitionArchetype(
 | 
			
		|||
	end
 | 
			
		||||
 | 
			
		||||
	-- Move the entity from the source to the destination archetype.
 | 
			
		||||
	destinationEntities[destinationRow] = sourceEntities[sourceRow]
 | 
			
		||||
	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
 | 
			
		||||
	if sourceRow ~= movedAway then
 | 
			
		||||
		sourceEntities[sourceRow] = sourceEntities[movedAway]
 | 
			
		||||
		entityIndex[sourceEntities[movedAway]].row = sourceRow
 | 
			
		||||
		local atMovedAway = sourceEntities[movedAway]
 | 
			
		||||
		sourceEntities[sourceRow] = atMovedAway
 | 
			
		||||
		entityIndex[atMovedAway].row = sourceRow
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	sourceEntities[movedAway] = nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function archetypeAppend(entity: i53, archetype: Archetype): i24
 | 
			
		||||
local function archetypeAppend(entity: number, archetype: Archetype): number
 | 
			
		||||
	local entities = archetype.entities
 | 
			
		||||
	table.insert(entities, entity)
 | 
			
		||||
	return #entities
 | 
			
		||||
	local length = #entities + 1
 | 
			
		||||
	entities[length] = entity
 | 
			
		||||
	return length
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
 | 
			
		||||
| 
						 | 
				
			
			@ -122,47 +125,49 @@ local function hash(arr): string | number
 | 
			
		|||
	return table.concat(arr, "_")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?)
 | 
			
		||||
	local destinationCount = #to.types
 | 
			
		||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?)
 | 
			
		||||
	local destinationIds = to.types
 | 
			
		||||
	local records = to.records
 | 
			
		||||
	local id = to.id
 | 
			
		||||
 | 
			
		||||
	for i = 1, destinationCount do
 | 
			
		||||
		local destinationId = destinationIds[i]
 | 
			
		||||
	for i, destinationId in destinationIds do
 | 
			
		||||
		local archetypesMap = componentIndex[destinationId]
 | 
			
		||||
 | 
			
		||||
		if not componentIndex[destinationId] then
 | 
			
		||||
			componentIndex[destinationId] = { size = 0, sparse = {} }
 | 
			
		||||
		if not archetypesMap then
 | 
			
		||||
			archetypesMap = {size = 0, sparse = {}}
 | 
			
		||||
			componentIndex[destinationId] = archetypesMap
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local archetypesMap = componentIndex[destinationId]
 | 
			
		||||
		archetypesMap.sparse[to.id] = i
 | 
			
		||||
		to.records[destinationId] = i
 | 
			
		||||
		archetypesMap.sparse[id] = i
 | 
			
		||||
		records[destinationId] = i
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
	world.nextArchetypeId = (world.nextArchetypeId::number)+ 1
 | 
			
		||||
    local id = world.nextArchetypeId
 | 
			
		||||
	local id = world.nextArchetypeId + 1
 | 
			
		||||
	world.nextArchetypeId = id
 | 
			
		||||
 | 
			
		||||
	local columns = {} :: { any }
 | 
			
		||||
	local length = #types
 | 
			
		||||
	local columns = table.create(length) :: {any}
 | 
			
		||||
 | 
			
		||||
	for _ in types do
 | 
			
		||||
		table.insert(columns, {})
 | 
			
		||||
	for index in types do
 | 
			
		||||
		columns[index] = {}
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local archetype = {
 | 
			
		||||
		id = id,
 | 
			
		||||
		types = types,
 | 
			
		||||
		type = ty,
 | 
			
		||||
		columns = columns,
 | 
			
		||||
		entities = {},
 | 
			
		||||
		edges = {},
 | 
			
		||||
		records = {},
 | 
			
		||||
		columns = columns;
 | 
			
		||||
		edges = {};
 | 
			
		||||
		entities = {};
 | 
			
		||||
		id = id;
 | 
			
		||||
		records = {};
 | 
			
		||||
		type = ty;
 | 
			
		||||
		types = types;
 | 
			
		||||
	}
 | 
			
		||||
	world.archetypeIndex[ty] = archetype
 | 
			
		||||
	world.archetypes[id] = archetype
 | 
			
		||||
	if #types > 0 then 
 | 
			
		||||
	if length > 0 then
 | 
			
		||||
		createArchetypeRecords(world.componentIndex, archetype, prev)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -173,40 +178,40 @@ local World = {}
 | 
			
		|||
World.__index = World
 | 
			
		||||
function World.new()
 | 
			
		||||
	local self = setmetatable({
 | 
			
		||||
		entityIndex = {} :: EntityIndex,
 | 
			
		||||
		componentIndex = {} :: ComponentIndex,
 | 
			
		||||
		archetypes = {},
 | 
			
		||||
		archetypeIndex = {},
 | 
			
		||||
        ROOT_ARCHETYPE = (nil :: any) :: Archetype,
 | 
			
		||||
        nextEntityId = 0,
 | 
			
		||||
		nextComponentId = 0,
 | 
			
		||||
        nextArchetypeId = 0,
 | 
			
		||||
    archetypeIndex = {};
 | 
			
		||||
		archetypes = {};
 | 
			
		||||
		componentIndex = {};
 | 
			
		||||
		entityIndex = {};
 | 
			
		||||
		hooks = {
 | 
			
		||||
			[ON_ADD] = {}
 | 
			
		||||
		}
 | 
			
		||||
			[ON_ADD] = {};
 | 
			
		||||
		};
 | 
			
		||||
		nextArchetypeId = 0;
 | 
			
		||||
		nextComponentId = 0;
 | 
			
		||||
		nextEntityId = 0;
 | 
			
		||||
		ROOT_ARCHETYPE = (nil :: any) :: Archetype;
 | 
			
		||||
	}, World)
 | 
			
		||||
    return self
 | 
			
		||||
	return self
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function emit(world, eventDescription)
 | 
			
		||||
	local event = eventDescription.event
 | 
			
		||||
 | 
			
		||||
	table.insert(world.hooks[event], {
 | 
			
		||||
		ids = eventDescription.ids,
 | 
			
		||||
		archetype = eventDescription.archetype,
 | 
			
		||||
		otherArchetype = eventDescription.otherArchetype,
 | 
			
		||||
		offset = eventDescription.offset
 | 
			
		||||
		archetype = eventDescription.archetype;
 | 
			
		||||
		ids = eventDescription.ids;
 | 
			
		||||
		offset = eventDescription.offset;
 | 
			
		||||
		otherArchetype = eventDescription.otherArchetype;
 | 
			
		||||
	})
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
 | 
			
		||||
	if #added > 0 then
 | 
			
		||||
		emit(world, {
 | 
			
		||||
			event = ON_ADD,
 | 
			
		||||
			ids = added,
 | 
			
		||||
			archetype = archetype,
 | 
			
		||||
			otherArchetype = otherArchetype,
 | 
			
		||||
			offset = row,
 | 
			
		||||
			archetype = archetype;
 | 
			
		||||
			event = ON_ADD;
 | 
			
		||||
			ids = added;
 | 
			
		||||
			offset = row;
 | 
			
		||||
			otherArchetype = otherArchetype;
 | 
			
		||||
		})
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -227,10 +232,8 @@ local function ensureArchetype(world: World, types, prev)
 | 
			
		|||
	return archetypeOf(world, types, prev)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function findInsert(types: { i53 }, toAdd: i53)
 | 
			
		||||
	local count = #types
 | 
			
		||||
	for i = 1, count do
 | 
			
		||||
		local id = types[i]
 | 
			
		||||
local function findInsert(types: {i53}, toAdd: i53)
 | 
			
		||||
	for i, id in types do
 | 
			
		||||
		if id == toAdd then
 | 
			
		||||
			return -1
 | 
			
		||||
		end
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +241,7 @@ local function findInsert(types: { i53 }, toAdd: i53)
 | 
			
		|||
			return i
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return count + 1
 | 
			
		||||
	return #types + 1
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
 | 
			
		||||
| 
						 | 
				
			
			@ -259,38 +262,47 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
local function ensureEdge(archetype: Archetype, componentId: i53)
 | 
			
		||||
	if not archetype.edges[componentId] then
 | 
			
		||||
		archetype.edges[componentId] = {} :: any
 | 
			
		||||
	local edges = archetype.edges
 | 
			
		||||
	local edge = edges[componentId]
 | 
			
		||||
	if not edge then
 | 
			
		||||
		edge = {} :: any
 | 
			
		||||
		edges[componentId] = edge
 | 
			
		||||
	end
 | 
			
		||||
	return archetype.edges[componentId]
 | 
			
		||||
	return edge
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
 | 
			
		||||
	if not from then
 | 
			
		||||
		-- 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
 | 
			
		||||
		local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
 | 
			
		||||
		if not ROOT_ARCHETYPE then
 | 
			
		||||
			ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
 | 
			
		||||
			world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never
 | 
			
		||||
		end
 | 
			
		||||
		from = 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
 | 
			
		||||
		-- faster traversals to adjacent archetypes.
 | 
			
		||||
		edge.add = findArchetypeWith(world, from, componentId)
 | 
			
		||||
		add = findArchetypeWith(world, from, componentId)
 | 
			
		||||
		edge.add = add :: never
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return edge.add
 | 
			
		||||
	return add
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function ensureRecord(entityIndex: EntityIndex, entityId: i53): Record
 | 
			
		||||
	local id = entityId
 | 
			
		||||
	if not entityIndex[id] then
 | 
			
		||||
		entityIndex[id] = {} :: Record
 | 
			
		||||
local function ensureRecord(entityIndex, entityId: i53): Record
 | 
			
		||||
	local record = entityIndex[entityId]
 | 
			
		||||
 | 
			
		||||
	if not record then
 | 
			
		||||
		record = {}
 | 
			
		||||
		entityIndex[entityId] = record
 | 
			
		||||
	end
 | 
			
		||||
	return entityIndex[id] :: Record
 | 
			
		||||
 | 
			
		||||
	return record :: Record
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
			-- When there is no previous archetype it should create the archetype
 | 
			
		||||
			newEntity(entityId, record, to)
 | 
			
		||||
			onNotifyAdd(world, to, from, record.row, { componentId })
 | 
			
		||||
			onNotifyAdd(world, to, from, record.row, {componentId})
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -326,28 +338,30 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype
 | 
			
		|||
	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
 | 
			
		||||
	local edge = ensureEdge(from, componentId)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if not edge.remove then
 | 
			
		||||
	local remove = edge.remove
 | 
			
		||||
	if not remove then
 | 
			
		||||
		local to = table.clone(from.types)
 | 
			
		||||
		table.remove(to, table.find(to, componentId))
 | 
			
		||||
		edge.remove = ensureArchetype(world, to, from)
 | 
			
		||||
		remove = ensureArchetype(world, to, from)
 | 
			
		||||
		edge.remove = remove :: never
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return edge.remove
 | 
			
		||||
	return remove
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
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 destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
 | 
			
		||||
 | 
			
		||||
	if sourceArchetype and not (sourceArchetype == destinationArchetype) then
 | 
			
		||||
		moveEntity(world.entityIndex, entityId, record, destinationArchetype)
 | 
			
		||||
		moveEntity(entityIndex, entityId, record, destinationArchetype)
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
-- 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 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?)
 | 
			
		||||
	local id = entityId
 | 
			
		||||
    local componentIndex = world.componentIndex
 | 
			
		||||
	local record = world.entityIndex[id]
 | 
			
		||||
	if not record then
 | 
			
		||||
		return nil
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local va = get(componentIndex, record, a)
 | 
			
		||||
	local va = get(record, a)
 | 
			
		||||
 | 
			
		||||
	if b == nil then
 | 
			
		||||
		return va
 | 
			
		||||
	elseif c == nil then
 | 
			
		||||
		return va, get(componentIndex, record, b)
 | 
			
		||||
		return va, get(record, b)
 | 
			
		||||
	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
 | 
			
		||||
		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
 | 
			
		||||
		error("args exceeded")
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function noop(self: Query, ...: i53): () -> (number, ...any)
 | 
			
		||||
	return function()
 | 
			
		||||
	end :: any
 | 
			
		||||
-- the less creation the better
 | 
			
		||||
local function actualNoOperation() end
 | 
			
		||||
local function noop(_self: Query, ...: i53): () -> (number, ...any)
 | 
			
		||||
	return actualNoOperation :: any
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local EmptyQuery = {
 | 
			
		||||
	__iter = noop,
 | 
			
		||||
	without = noop
 | 
			
		||||
	__iter = noop;
 | 
			
		||||
	without = noop;
 | 
			
		||||
}
 | 
			
		||||
EmptyQuery.__index = EmptyQuery
 | 
			
		||||
setmetatable(EmptyQuery, EmptyQuery)
 | 
			
		||||
| 
						 | 
				
			
			@ -396,19 +410,22 @@ setmetatable(EmptyQuery, EmptyQuery)
 | 
			
		|||
export type Query = typeof(EmptyQuery)
 | 
			
		||||
 | 
			
		||||
function World.query(world: World, ...: i53): Query
 | 
			
		||||
	local compatibleArchetypes = {}
 | 
			
		||||
	local components = { ... }
 | 
			
		||||
	local archetypes = world.archetypes
 | 
			
		||||
	local queryLength = #components
 | 
			
		||||
 | 
			
		||||
	if queryLength == 0 then 
 | 
			
		||||
	-- breaking?
 | 
			
		||||
	if (...) == nil then
 | 
			
		||||
		error("Missing components")
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local compatibleArchetypes = {}
 | 
			
		||||
	local length = 0
 | 
			
		||||
 | 
			
		||||
	local components = {...}
 | 
			
		||||
	local archetypes = world.archetypes
 | 
			
		||||
	local queryLength = #components
 | 
			
		||||
 | 
			
		||||
	local firstArchetypeMap
 | 
			
		||||
	local componentIndex = world.componentIndex
 | 
			
		||||
 | 
			
		||||
	for i, componentId in components do 
 | 
			
		||||
	for _, componentId in components do
 | 
			
		||||
		local map = componentIndex[componentId]
 | 
			
		||||
		if not map then
 | 
			
		||||
			return EmptyQuery
 | 
			
		||||
| 
						 | 
				
			
			@ -422,7 +439,7 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
	for id in firstArchetypeMap.sparse do
 | 
			
		||||
		local archetype = archetypes[id]
 | 
			
		||||
		local archetypeRecords = archetype.records
 | 
			
		||||
        local indices = {}
 | 
			
		||||
		local indices = {}
 | 
			
		||||
		local skip = false
 | 
			
		||||
 | 
			
		||||
		for i, componentId in components do
 | 
			
		||||
| 
						 | 
				
			
			@ -431,13 +448,15 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
				skip = true
 | 
			
		||||
				break
 | 
			
		||||
			end
 | 
			
		||||
			indices[i] = archetypeRecords[componentId]
 | 
			
		||||
			indices[i] = index
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		if skip then
 | 
			
		||||
			continue
 | 
			
		||||
		end
 | 
			
		||||
		table.insert(compatibleArchetypes, { archetype, indices })
 | 
			
		||||
 | 
			
		||||
		length += 1
 | 
			
		||||
		compatibleArchetypes[length] = {archetype, indices}
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
 | 
			
		||||
| 
						 | 
				
			
			@ -449,16 +468,19 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
	preparedQuery.__index = preparedQuery
 | 
			
		||||
 | 
			
		||||
	function preparedQuery:without(...)
 | 
			
		||||
		local components = { ... }
 | 
			
		||||
		local withoutComponents = {...}
 | 
			
		||||
		for i = #compatibleArchetypes, 1, -1 do
 | 
			
		||||
			local archetype = compatibleArchetypes[i][1]
 | 
			
		||||
			local records = archetype.records
 | 
			
		||||
			local shouldRemove = false
 | 
			
		||||
			for _, componentId in components do 
 | 
			
		||||
				if archetype.records[componentId] then 
 | 
			
		||||
 | 
			
		||||
			for _, componentId in withoutComponents do
 | 
			
		||||
				if records[componentId] then
 | 
			
		||||
					shouldRemove = true
 | 
			
		||||
					break
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			if shouldRemove then
 | 
			
		||||
				table.remove(compatibleArchetypes, i)
 | 
			
		||||
			end
 | 
			
		||||
| 
						 | 
				
			
			@ -475,7 +497,6 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
	local lastRow
 | 
			
		||||
	local queryOutput = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	function preparedQuery:__iter()
 | 
			
		||||
		return function()
 | 
			
		||||
			local archetype = compatibleArchetype[1]
 | 
			
		||||
| 
						 | 
				
			
			@ -499,16 +520,9 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
			elseif queryLength == 2 then
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row]
 | 
			
		||||
			elseif queryLength == 3 then
 | 
			
		||||
				return entityId, 
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
					columns[tr[2]][row],
 | 
			
		||||
					columns[tr[3]][row]
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
 | 
			
		||||
			elseif queryLength == 4 then
 | 
			
		||||
				return entityId, 
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
					columns[tr[2]][row],
 | 
			
		||||
					columns[tr[3]][row],
 | 
			
		||||
					columns[tr[4]][row]
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
 | 
			
		||||
			elseif queryLength == 5 then
 | 
			
		||||
				return entityId,
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
| 
						 | 
				
			
			@ -546,7 +560,7 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
			end
 | 
			
		||||
 | 
			
		||||
			for i in components do
 | 
			
		||||
				queryOutput[i] = tr[i][row]
 | 
			
		||||
				queryOutput[i] = columns[tr[i]][row]
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return entityId, unpack(queryOutput, 1, queryLength)
 | 
			
		||||
| 
						 | 
				
			
			@ -568,8 +582,9 @@ function World.component(world: World)
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
function World.entity(world: World)
 | 
			
		||||
	world.nextEntityId += 1
 | 
			
		||||
	return world.nextEntityId + REST
 | 
			
		||||
	local nextEntityId = world.nextEntityId + 1
 | 
			
		||||
	world.nextEntityId = nextEntityId
 | 
			
		||||
	return nextEntityId + REST
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function World.delete(world: World, entityId: i53)
 | 
			
		||||
| 
						 | 
				
			
			@ -584,12 +599,14 @@ function World.delete(world: World, entityId: i53)
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
function World.observer(world: World, ...)
 | 
			
		||||
	local componentIds = { ... }
 | 
			
		||||
	local componentIds = {...}
 | 
			
		||||
	local idsCount = #componentIds
 | 
			
		||||
	local hooks = world.hooks
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		event = function(event)
 | 
			
		||||
			local hook = world.hooks[event]
 | 
			
		||||
			world.hooks[event] = nil
 | 
			
		||||
			local hook = hooks[event]
 | 
			
		||||
			hooks[event] = nil
 | 
			
		||||
 | 
			
		||||
			local last, change
 | 
			
		||||
			return function()
 | 
			
		||||
| 
						 | 
				
			
			@ -599,10 +616,11 @@ function World.observer(world: World, ...)
 | 
			
		|||
				end
 | 
			
		||||
 | 
			
		||||
				local matched = false
 | 
			
		||||
				local ids = change.ids
 | 
			
		||||
 | 
			
		||||
				while not matched do
 | 
			
		||||
					local skip = false
 | 
			
		||||
					for _, id in change.ids do 
 | 
			
		||||
					for _, id in ids do
 | 
			
		||||
						if not table.find(componentIds, id) then
 | 
			
		||||
							skip = true
 | 
			
		||||
							break
 | 
			
		||||
| 
						 | 
				
			
			@ -611,24 +629,25 @@ function World.observer(world: World, ...)
 | 
			
		|||
 | 
			
		||||
					if skip then
 | 
			
		||||
						last, change = next(hook, last)
 | 
			
		||||
						ids = change.ids
 | 
			
		||||
						continue
 | 
			
		||||
					end
 | 
			
		||||
 | 
			
		||||
					matched = true
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				local queryOutput = {}
 | 
			
		||||
				local queryOutput = table.create(idsCount)
 | 
			
		||||
				local row = change.offset
 | 
			
		||||
				local archetype = change.archetype
 | 
			
		||||
				local columns = archetype.columns
 | 
			
		||||
				local archetypeRecords = archetype.records
 | 
			
		||||
				for _, id in componentIds do 
 | 
			
		||||
					table.insert(queryOutput, columns[archetypeRecords[id]][row])
 | 
			
		||||
				for index, id in componentIds do
 | 
			
		||||
					queryOutput[index] = columns[archetypeRecords[id]][row]
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				return archetype.entities[row], unpack(queryOutput, 1, #queryOutput)
 | 
			
		||||
				return archetype.entities[row], unpack(queryOutput, 1, idsCount)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		end;
 | 
			
		||||
	}
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -664,8 +683,8 @@ function World.__iter(world: World): () -> (number?, unknown?)
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
return table.freeze({
 | 
			
		||||
	World = World,
 | 
			
		||||
	ON_ADD = ON_ADD,
 | 
			
		||||
	ON_REMOVE = ON_REMOVE,
 | 
			
		||||
	ON_SET = ON_SET
 | 
			
		||||
	World = World;
 | 
			
		||||
	ON_ADD = ON_ADD;
 | 
			
		||||
	ON_REMOVE = ON_REMOVE;
 | 
			
		||||
	ON_SET = ON_SET;
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										393
									
								
								mirror/init.lua
									
									
									
									
									
								
							
							
						
						
									
										393
									
								
								mirror/init.lua
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -6,10 +6,10 @@
 | 
			
		|||
type i53 = number
 | 
			
		||||
type i24 = number
 | 
			
		||||
 | 
			
		||||
type Ty = { i53 }
 | 
			
		||||
type Ty = {i53}
 | 
			
		||||
type ArchetypeId = number
 | 
			
		||||
 | 
			
		||||
type Column = { any }
 | 
			
		||||
type Column = {any}
 | 
			
		||||
 | 
			
		||||
type Archetype = {
 | 
			
		||||
	id: number,
 | 
			
		||||
| 
						 | 
				
			
			@ -20,9 +20,9 @@ type Archetype = {
 | 
			
		|||
		},
 | 
			
		||||
	},
 | 
			
		||||
	types: Ty,
 | 
			
		||||
    type: string | number,
 | 
			
		||||
	entities: { number },
 | 
			
		||||
	columns: { Column },
 | 
			
		||||
	type: string | number,
 | 
			
		||||
	entities: {number},
 | 
			
		||||
	columns: {Column},
 | 
			
		||||
	records: {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +31,12 @@ type Record = {
 | 
			
		|||
	row: number,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EntityIndex = { [i24]: Record }
 | 
			
		||||
type ComponentIndex = { [i24]: ArchetypeMap}
 | 
			
		||||
type EntityIndex = {[i24]: Record}
 | 
			
		||||
type ComponentIndex = {[i24]: ArchetypeMap}
 | 
			
		||||
 | 
			
		||||
type ArchetypeRecord = number
 | 
			
		||||
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord } , size: number }
 | 
			
		||||
type Archetypes = { [ArchetypeId]: Archetype }
 | 
			
		||||
type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number}
 | 
			
		||||
type Archetypes = {[ArchetypeId]: Archetype}
 | 
			
		||||
 | 
			
		||||
type ArchetypeDiff = {
 | 
			
		||||
	added: Ty,
 | 
			
		||||
| 
						 | 
				
			
			@ -51,38 +51,58 @@ local REST = HI_COMPONENT_ID + 4
 | 
			
		|||
 | 
			
		||||
local function transitionArchetype(
 | 
			
		||||
	entityIndex: EntityIndex,
 | 
			
		||||
	destinationArchetype: Archetype,
 | 
			
		||||
	to: Archetype,
 | 
			
		||||
	destinationRow: i24,
 | 
			
		||||
	sourceArchetype: Archetype,
 | 
			
		||||
	from: Archetype,
 | 
			
		||||
	sourceRow: i24
 | 
			
		||||
)
 | 
			
		||||
	local columns = sourceArchetype.columns
 | 
			
		||||
	local sourceEntities = sourceArchetype.entities
 | 
			
		||||
	local destinationEntities = destinationArchetype.entities
 | 
			
		||||
	local destinationColumns = destinationArchetype.columns
 | 
			
		||||
	local columns = from.columns
 | 
			
		||||
	local sourceEntities = from.entities
 | 
			
		||||
	local destinationEntities = to.entities
 | 
			
		||||
	local destinationColumns = to.columns
 | 
			
		||||
	local tr = to.records
 | 
			
		||||
	local types = from.types
 | 
			
		||||
 | 
			
		||||
	for componentId, column in columns do
 | 
			
		||||
		local targetColumn = destinationColumns[componentId]
 | 
			
		||||
	for i, column in columns do
 | 
			
		||||
		-- 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
 | 
			
		||||
			targetColumn[destinationRow] = column[sourceRow]
 | 
			
		||||
		end
 | 
			
		||||
		column[sourceRow] = column[#column]
 | 
			
		||||
		column[#column] = nil
 | 
			
		||||
		-- If the entity is the last row in the archetype then swapping it would be meaningless.
 | 
			
		||||
		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
 | 
			
		||||
 | 
			
		||||
	destinationEntities[destinationRow] = sourceEntities[sourceRow]
 | 
			
		||||
	entityIndex[sourceEntities[sourceRow]].row = destinationRow
 | 
			
		||||
	-- Move the entity from the source to the destination archetype.
 | 
			
		||||
	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
 | 
			
		||||
	sourceEntities[sourceRow] = sourceEntities[movedAway]
 | 
			
		||||
	entityIndex[sourceEntities[movedAway]].row = sourceRow
 | 
			
		||||
	if sourceRow ~= movedAway then
 | 
			
		||||
		local atMovedAway = sourceEntities[movedAway]
 | 
			
		||||
		sourceEntities[sourceRow] = atMovedAway
 | 
			
		||||
		entityIndex[atMovedAway].row = sourceRow
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	sourceEntities[movedAway] = nil
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function archetypeAppend(entity: i53, archetype: Archetype): i24
 | 
			
		||||
local function archetypeAppend(entity: number, archetype: Archetype): number
 | 
			
		||||
	local entities = archetype.entities
 | 
			
		||||
	table.insert(entities, entity)
 | 
			
		||||
	return #entities
 | 
			
		||||
	local length = #entities + 1
 | 
			
		||||
	entities[length] = entity
 | 
			
		||||
	return length
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
 | 
			
		||||
| 
						 | 
				
			
			@ -105,47 +125,51 @@ local function hash(arr): string | number
 | 
			
		|||
	return table.concat(arr, "_")
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?)
 | 
			
		||||
	local destinationCount = #to.types
 | 
			
		||||
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, _from: Archetype?)
 | 
			
		||||
	local destinationIds = to.types
 | 
			
		||||
	local records = to.records
 | 
			
		||||
	local id = to.id
 | 
			
		||||
 | 
			
		||||
	for i = 1, destinationCount do
 | 
			
		||||
		local destinationId = destinationIds[i]
 | 
			
		||||
	for i, destinationId in destinationIds do
 | 
			
		||||
		local archetypesMap = componentIndex[destinationId]
 | 
			
		||||
 | 
			
		||||
		if not componentIndex[destinationId] then
 | 
			
		||||
			componentIndex[destinationId] = { size = 0, sparse = {} }
 | 
			
		||||
		if not archetypesMap then
 | 
			
		||||
			archetypesMap = {size = 0, sparse = {}}
 | 
			
		||||
			componentIndex[destinationId] = archetypesMap
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		local archetypesMap = componentIndex[destinationId]
 | 
			
		||||
		archetypesMap.sparse[to.id] = i
 | 
			
		||||
		to.records[destinationId] = i
 | 
			
		||||
		archetypesMap.sparse[id] = i
 | 
			
		||||
		records[destinationId] = i
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
	world.nextArchetypeId = (world.nextArchetypeId::number)+ 1
 | 
			
		||||
    local id = world.nextArchetypeId
 | 
			
		||||
	local id = world.nextArchetypeId + 1
 | 
			
		||||
	world.nextArchetypeId = id
 | 
			
		||||
 | 
			
		||||
	local columns = {} :: { any }
 | 
			
		||||
	local length = #types
 | 
			
		||||
	local columns = table.create(length) :: {any}
 | 
			
		||||
 | 
			
		||||
	for _ in types do
 | 
			
		||||
		table.insert(columns, {})
 | 
			
		||||
	for index in types do
 | 
			
		||||
		columns[index] = {}
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local archetype = {
 | 
			
		||||
		id = id,
 | 
			
		||||
		types = types,
 | 
			
		||||
		type = ty,
 | 
			
		||||
		columns = columns,
 | 
			
		||||
		entities = {},
 | 
			
		||||
		edges = {},
 | 
			
		||||
		records = {},
 | 
			
		||||
		columns = columns;
 | 
			
		||||
		edges = {};
 | 
			
		||||
		entities = {};
 | 
			
		||||
		id = id;
 | 
			
		||||
		records = {};
 | 
			
		||||
		type = ty;
 | 
			
		||||
		types = types;
 | 
			
		||||
	}
 | 
			
		||||
	world.archetypeIndex[ty] = archetype
 | 
			
		||||
	world.archetypes[id] = archetype
 | 
			
		||||
	createArchetypeRecords(world.componentIndex, archetype, prev)
 | 
			
		||||
	if length > 0 then
 | 
			
		||||
		createArchetypeRecords(world.componentIndex, archetype, prev)
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return archetype
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -154,53 +178,51 @@ local World = {}
 | 
			
		|||
World.__index = World
 | 
			
		||||
function World.new()
 | 
			
		||||
	local self = setmetatable({
 | 
			
		||||
		entityIndex = {},
 | 
			
		||||
		componentIndex = {},
 | 
			
		||||
		archetypes = {},
 | 
			
		||||
		archetypeIndex = {},
 | 
			
		||||
        ROOT_ARCHETYPE = (nil :: any) :: Archetype,
 | 
			
		||||
        nextEntityId = 0,
 | 
			
		||||
		nextComponentId = 0,
 | 
			
		||||
        nextArchetypeId = 0,
 | 
			
		||||
		archetypeIndex = {};
 | 
			
		||||
		archetypes = {};
 | 
			
		||||
		componentIndex = {};
 | 
			
		||||
		entityIndex = {};
 | 
			
		||||
		hooks = {
 | 
			
		||||
			[ON_ADD] = {}
 | 
			
		||||
		}
 | 
			
		||||
			[ON_ADD] = {};
 | 
			
		||||
		};
 | 
			
		||||
		nextArchetypeId = 0;
 | 
			
		||||
		nextComponentId = 0;
 | 
			
		||||
		nextEntityId = 0;
 | 
			
		||||
		ROOT_ARCHETYPE = (nil :: any) :: Archetype;
 | 
			
		||||
	}, World)
 | 
			
		||||
    return self
 | 
			
		||||
	return self
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function emit(world, eventDescription)
 | 
			
		||||
	local event = eventDescription.event
 | 
			
		||||
 | 
			
		||||
	table.insert(world.hooks[event], {
 | 
			
		||||
		ids = eventDescription.ids,
 | 
			
		||||
		archetype = eventDescription.archetype,
 | 
			
		||||
		otherArchetype = eventDescription.otherArchetype,
 | 
			
		||||
		offset = eventDescription.offset
 | 
			
		||||
		archetype = eventDescription.archetype;
 | 
			
		||||
		ids = eventDescription.ids;
 | 
			
		||||
		offset = eventDescription.offset;
 | 
			
		||||
		otherArchetype = eventDescription.otherArchetype;
 | 
			
		||||
	})
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
local function onNotifyAdd(world, archetype, otherArchetype, row: number, added: Ty)
 | 
			
		||||
	if #added > 0 then
 | 
			
		||||
		emit(world, {
 | 
			
		||||
			event = ON_ADD,
 | 
			
		||||
			ids = added,
 | 
			
		||||
			archetype = archetype,
 | 
			
		||||
			otherArchetype = otherArchetype,
 | 
			
		||||
			offset = row,
 | 
			
		||||
			archetype = archetype;
 | 
			
		||||
			event = ON_ADD;
 | 
			
		||||
			ids = added;
 | 
			
		||||
			offset = row;
 | 
			
		||||
			otherArchetype = otherArchetype;
 | 
			
		||||
		})
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export type World = typeof(World.new())
 | 
			
		||||
 | 
			
		||||
local function ensureArchetype(world: World, types, prev)
 | 
			
		||||
	if #types < 1 then
 | 
			
		||||
		return world.ROOT_ARCHETYPE
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local ty = hash(types)
 | 
			
		||||
	local archetype = world.archetypeIndex[ty]
 | 
			
		||||
	if archetype then
 | 
			
		||||
| 
						 | 
				
			
			@ -210,10 +232,8 @@ local function ensureArchetype(world: World, types, prev)
 | 
			
		|||
	return archetypeOf(world, types, prev)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function findInsert(types: { i53 }, toAdd: i53)
 | 
			
		||||
	local count = #types
 | 
			
		||||
	for i = 1, count do
 | 
			
		||||
		local id = types[i]
 | 
			
		||||
local function findInsert(types: {i53}, toAdd: i53)
 | 
			
		||||
	for i, id in types do
 | 
			
		||||
		if id == toAdd then
 | 
			
		||||
			return -1
 | 
			
		||||
		end
 | 
			
		||||
| 
						 | 
				
			
			@ -221,13 +241,18 @@ local function findInsert(types: { i53 }, toAdd: i53)
 | 
			
		|||
			return i
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
	return count + 1
 | 
			
		||||
	return #types + 1
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
 | 
			
		||||
	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)
 | 
			
		||||
	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
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -237,88 +262,108 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53
 | 
			
		|||
end
 | 
			
		||||
 | 
			
		||||
local function ensureEdge(archetype: Archetype, componentId: i53)
 | 
			
		||||
	if not archetype.edges[componentId] then
 | 
			
		||||
		archetype.edges[componentId] = {} :: any
 | 
			
		||||
	local edges = archetype.edges
 | 
			
		||||
	local edge = edges[componentId]
 | 
			
		||||
	if not edge then
 | 
			
		||||
		edge = {} :: any
 | 
			
		||||
		edges[componentId] = edge
 | 
			
		||||
	end
 | 
			
		||||
	return archetype.edges[componentId]
 | 
			
		||||
	return edge
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype
 | 
			
		||||
	if not from then
 | 
			
		||||
		if not world.ROOT_ARCHETYPE then 
 | 
			
		||||
            local ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
 | 
			
		||||
            world.ROOT_ARCHETYPE = ROOT_ARCHETYPE
 | 
			
		||||
        end
 | 
			
		||||
		from = world.ROOT_ARCHETYPE
 | 
			
		||||
		-- If there was no source archetype then it should return the ROOT_ARCHETYPE
 | 
			
		||||
		local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE
 | 
			
		||||
		if not ROOT_ARCHETYPE then
 | 
			
		||||
			ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
 | 
			
		||||
			world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never
 | 
			
		||||
		end
 | 
			
		||||
		from = ROOT_ARCHETYPE
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local edge = ensureEdge(from, componentId)
 | 
			
		||||
 | 
			
		||||
	if not edge.add then
 | 
			
		||||
		edge.add = findArchetypeWith(world, from, componentId)
 | 
			
		||||
	local add = edge.add
 | 
			
		||||
	if not add then
 | 
			
		||||
		-- 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
 | 
			
		||||
 | 
			
		||||
	return edge.add
 | 
			
		||||
	return add
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function ensureRecord(entityIndex, entityId: i53): Record
 | 
			
		||||
	local id = entityId
 | 
			
		||||
	if not entityIndex[id] then
 | 
			
		||||
		entityIndex[id] = {}
 | 
			
		||||
	local record = entityIndex[entityId]
 | 
			
		||||
 | 
			
		||||
	if not record then
 | 
			
		||||
		record = {}
 | 
			
		||||
		entityIndex[entityId] = record
 | 
			
		||||
	end
 | 
			
		||||
	return entityIndex[id] :: Record
 | 
			
		||||
 | 
			
		||||
	return record :: Record
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function World.set(world: World, entityId: i53, componentId: i53, data: unknown)
 | 
			
		||||
	local record = ensureRecord(world.entityIndex, entityId)
 | 
			
		||||
	local sourceArchetype = record.archetype
 | 
			
		||||
	local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype)
 | 
			
		||||
	local from = record.archetype
 | 
			
		||||
	local to = archetypeTraverseAdd(world, componentId, from)
 | 
			
		||||
 | 
			
		||||
	if sourceArchetype == destinationArchetype then 
 | 
			
		||||
		local archetypeRecord = destinationArchetype.records[componentId]
 | 
			
		||||
		destinationArchetype.columns[archetypeRecord][record.row] = data
 | 
			
		||||
	if from == to then
 | 
			
		||||
		-- If the archetypes are the same it can avoid moving the entity
 | 
			
		||||
		-- and just set the data directly.
 | 
			
		||||
		local archetypeRecord = to.records[componentId]
 | 
			
		||||
		from.columns[archetypeRecord][record.row] = data
 | 
			
		||||
		-- Should fire an OnSet event here.
 | 
			
		||||
		return
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	if sourceArchetype then
 | 
			
		||||
		moveEntity(world.entityIndex, entityId, record, destinationArchetype)
 | 
			
		||||
	if from then
 | 
			
		||||
		-- If there was a previous archetype, then the entity needs to move the archetype
 | 
			
		||||
		moveEntity(world.entityIndex, entityId, record, to)
 | 
			
		||||
	else
 | 
			
		||||
		if #destinationArchetype.types > 0 then
 | 
			
		||||
			newEntity(entityId, record, destinationArchetype)
 | 
			
		||||
			onNotifyAdd(world, destinationArchetype, sourceArchetype, record.row, { componentId })
 | 
			
		||||
		if #to.types > 0 then
 | 
			
		||||
			-- When there is no previous archetype it should create the archetype
 | 
			
		||||
			newEntity(entityId, record, to)
 | 
			
		||||
			onNotifyAdd(world, to, from, record.row, {componentId})
 | 
			
		||||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local archetypeRecord = destinationArchetype.records[componentId]
 | 
			
		||||
	destinationArchetype.columns[archetypeRecord][record.row] = data
 | 
			
		||||
	local archetypeRecord = to.records[componentId]
 | 
			
		||||
	to.columns[archetypeRecord][record.row] = data
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
 | 
			
		||||
	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
 | 
			
		||||
	local edge = ensureEdge(from, componentId)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if not edge.remove then
 | 
			
		||||
	local remove = edge.remove
 | 
			
		||||
	if not remove then
 | 
			
		||||
		local to = table.clone(from.types)
 | 
			
		||||
		table.remove(to, table.find(to, componentId))
 | 
			
		||||
		edge.remove = ensureArchetype(world, to, from)
 | 
			
		||||
		remove = ensureArchetype(world, to, from)
 | 
			
		||||
		edge.remove = remove :: never
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	return edge.remove
 | 
			
		||||
	return remove
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
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 destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
 | 
			
		||||
 | 
			
		||||
	if sourceArchetype and not (sourceArchetype == destinationArchetype) then
 | 
			
		||||
		moveEntity(world.entityIndex, entityId, record, destinationArchetype)
 | 
			
		||||
		moveEntity(entityIndex, entityId, record, destinationArchetype)
 | 
			
		||||
	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 archetypeRecord = componentIndex[componentId].sparse[archetype.id]
 | 
			
		||||
	local archetypeRecord = archetype.records[componentId]
 | 
			
		||||
 | 
			
		||||
	if not archetypeRecord then
 | 
			
		||||
		return nil
 | 
			
		||||
| 
						 | 
				
			
			@ -329,35 +374,35 @@ end
 | 
			
		|||
 | 
			
		||||
function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53?, e: i53?)
 | 
			
		||||
	local id = entityId
 | 
			
		||||
    local componentIndex = world.componentIndex
 | 
			
		||||
	local record = world.entityIndex[id]
 | 
			
		||||
	if not record then
 | 
			
		||||
		return nil
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local va = get(componentIndex, record, a)
 | 
			
		||||
	local va = get(record, a)
 | 
			
		||||
 | 
			
		||||
	if b == nil then
 | 
			
		||||
		return va
 | 
			
		||||
	elseif c == nil then
 | 
			
		||||
		return va, get(componentIndex, record, b)
 | 
			
		||||
		return va, get(record, b)
 | 
			
		||||
	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
 | 
			
		||||
		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
 | 
			
		||||
		error("args exceeded")
 | 
			
		||||
	end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function noop(self: Query, ...: i53): () -> (number, ...any)
 | 
			
		||||
	return function()
 | 
			
		||||
	end :: any
 | 
			
		||||
-- the less creation the better
 | 
			
		||||
local function actualNoOperation() end
 | 
			
		||||
local function noop(_self: Query, ...: i53): () -> (number, ...any)
 | 
			
		||||
	return actualNoOperation :: any
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local EmptyQuery = {
 | 
			
		||||
	__iter = noop,
 | 
			
		||||
	without = noop
 | 
			
		||||
	__iter = noop;
 | 
			
		||||
	without = noop;
 | 
			
		||||
}
 | 
			
		||||
EmptyQuery.__index = EmptyQuery
 | 
			
		||||
setmetatable(EmptyQuery, EmptyQuery)
 | 
			
		||||
| 
						 | 
				
			
			@ -365,19 +410,22 @@ setmetatable(EmptyQuery, EmptyQuery)
 | 
			
		|||
export type Query = typeof(EmptyQuery)
 | 
			
		||||
 | 
			
		||||
function World.query(world: World, ...: i53): Query
 | 
			
		||||
	local compatibleArchetypes = {}
 | 
			
		||||
	local components = { ... }
 | 
			
		||||
	local archetypes = world.archetypes
 | 
			
		||||
	local queryLength = #components
 | 
			
		||||
 | 
			
		||||
	if queryLength == 0 then 
 | 
			
		||||
	-- breaking?
 | 
			
		||||
	if (...) == nil then
 | 
			
		||||
		error("Missing components")
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local compatibleArchetypes = {}
 | 
			
		||||
	local length = 0
 | 
			
		||||
 | 
			
		||||
	local components = {...}
 | 
			
		||||
	local archetypes = world.archetypes
 | 
			
		||||
	local queryLength = #components
 | 
			
		||||
 | 
			
		||||
	local firstArchetypeMap
 | 
			
		||||
	local componentIndex = world.componentIndex
 | 
			
		||||
 | 
			
		||||
	for i, componentId in components do 
 | 
			
		||||
	for _, componentId in components do
 | 
			
		||||
		local map = componentIndex[componentId]
 | 
			
		||||
		if not map then
 | 
			
		||||
			return EmptyQuery
 | 
			
		||||
| 
						 | 
				
			
			@ -388,27 +436,27 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
		end
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local i = 0
 | 
			
		||||
	for id in firstArchetypeMap.sparse do
 | 
			
		||||
		local archetype = archetypes[id]
 | 
			
		||||
		local archetypeRecords = archetype.records
 | 
			
		||||
        local indices = {}
 | 
			
		||||
		local indices = {}
 | 
			
		||||
		local skip = false
 | 
			
		||||
 | 
			
		||||
		for j, componentId in components do 
 | 
			
		||||
		for i, componentId in components do
 | 
			
		||||
			local index = archetypeRecords[componentId]
 | 
			
		||||
			if not index then
 | 
			
		||||
				skip = true
 | 
			
		||||
				break
 | 
			
		||||
			end
 | 
			
		||||
			indices[j] = archetypeRecords[componentId]
 | 
			
		||||
			indices[i] = index
 | 
			
		||||
		end
 | 
			
		||||
 | 
			
		||||
		if skip then
 | 
			
		||||
			continue
 | 
			
		||||
		end
 | 
			
		||||
		i += 1
 | 
			
		||||
		table.insert(compatibleArchetypes, { archetype, indices })
 | 
			
		||||
 | 
			
		||||
		length += 1
 | 
			
		||||
		compatibleArchetypes[length] = {archetype, indices}
 | 
			
		||||
	end
 | 
			
		||||
 | 
			
		||||
	local lastArchetype, compatibleArchetype = next(compatibleArchetypes)
 | 
			
		||||
| 
						 | 
				
			
			@ -420,16 +468,19 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
	preparedQuery.__index = preparedQuery
 | 
			
		||||
 | 
			
		||||
	function preparedQuery:without(...)
 | 
			
		||||
		local components = { ... }
 | 
			
		||||
		local withoutComponents = {...}
 | 
			
		||||
		for i = #compatibleArchetypes, 1, -1 do
 | 
			
		||||
			local archetype = compatibleArchetypes[i][1]
 | 
			
		||||
			local records = archetype.records
 | 
			
		||||
			local shouldRemove = false
 | 
			
		||||
			for _, componentId in components do 
 | 
			
		||||
				if archetype.records[componentId] then 
 | 
			
		||||
 | 
			
		||||
			for _, componentId in withoutComponents do
 | 
			
		||||
				if records[componentId] then
 | 
			
		||||
					shouldRemove = true
 | 
			
		||||
					break
 | 
			
		||||
				end
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			if shouldRemove then
 | 
			
		||||
				table.remove(compatibleArchetypes, i)
 | 
			
		||||
			end
 | 
			
		||||
| 
						 | 
				
			
			@ -446,7 +497,6 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
	local lastRow
 | 
			
		||||
	local queryOutput = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	function preparedQuery:__iter()
 | 
			
		||||
		return function()
 | 
			
		||||
			local archetype = compatibleArchetype[1]
 | 
			
		||||
| 
						 | 
				
			
			@ -470,16 +520,9 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
			elseif queryLength == 2 then
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row]
 | 
			
		||||
			elseif queryLength == 3 then
 | 
			
		||||
				return entityId, 
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
					columns[tr[2]][row],
 | 
			
		||||
					columns[tr[3]][row]
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row]
 | 
			
		||||
			elseif queryLength == 4 then
 | 
			
		||||
				return entityId, 
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
					columns[tr[2]][row],
 | 
			
		||||
					columns[tr[3]][row],
 | 
			
		||||
					columns[tr[4]][row]
 | 
			
		||||
				return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row]
 | 
			
		||||
			elseif queryLength == 5 then
 | 
			
		||||
				return entityId,
 | 
			
		||||
					columns[tr[1]][row],
 | 
			
		||||
| 
						 | 
				
			
			@ -517,7 +560,7 @@ function World.query(world: World, ...: i53): Query
 | 
			
		|||
			end
 | 
			
		||||
 | 
			
		||||
			for i in components do
 | 
			
		||||
				queryOutput[i] = tr[i][row]
 | 
			
		||||
				queryOutput[i] = columns[tr[i]][row]
 | 
			
		||||
			end
 | 
			
		||||
 | 
			
		||||
			return entityId, unpack(queryOutput, 1, queryLength)
 | 
			
		||||
| 
						 | 
				
			
			@ -530,24 +573,40 @@ end
 | 
			
		|||
function World.component(world: World)
 | 
			
		||||
	local componentId = world.nextComponentId + 1
 | 
			
		||||
	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
 | 
			
		||||
	world.nextComponentId = componentId
 | 
			
		||||
	return componentId
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function World.entity(world: World)
 | 
			
		||||
	world.nextEntityId += 1
 | 
			
		||||
	return world.nextEntityId + REST
 | 
			
		||||
	local nextEntityId = world.nextEntityId + 1
 | 
			
		||||
	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
 | 
			
		||||
 | 
			
		||||
function World.observer(world: World, ...)
 | 
			
		||||
	local componentIds = { ... }
 | 
			
		||||
	local componentIds = {...}
 | 
			
		||||
	local idsCount = #componentIds
 | 
			
		||||
	local hooks = world.hooks
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		event = function(event)
 | 
			
		||||
			local hook = world.hooks[event]
 | 
			
		||||
			world.hooks[event] = nil
 | 
			
		||||
			local hook = hooks[event]
 | 
			
		||||
			hooks[event] = nil
 | 
			
		||||
 | 
			
		||||
			local last, change
 | 
			
		||||
			return function()
 | 
			
		||||
| 
						 | 
				
			
			@ -557,10 +616,11 @@ function World.observer(world: World, ...)
 | 
			
		|||
				end
 | 
			
		||||
 | 
			
		||||
				local matched = false
 | 
			
		||||
				local ids = change.ids
 | 
			
		||||
 | 
			
		||||
				while not matched do
 | 
			
		||||
					local skip = false
 | 
			
		||||
					for _, id in change.ids do 
 | 
			
		||||
					for _, id in ids do
 | 
			
		||||
						if not table.find(componentIds, id) then
 | 
			
		||||
							skip = true
 | 
			
		||||
							break
 | 
			
		||||
| 
						 | 
				
			
			@ -569,30 +629,31 @@ function World.observer(world: World, ...)
 | 
			
		|||
 | 
			
		||||
					if skip then
 | 
			
		||||
						last, change = next(hook, last)
 | 
			
		||||
						ids = change.ids
 | 
			
		||||
						continue
 | 
			
		||||
					end
 | 
			
		||||
 | 
			
		||||
					matched = true
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				local queryOutput = {}
 | 
			
		||||
				local queryOutput = table.create(idsCount)
 | 
			
		||||
				local row = change.offset
 | 
			
		||||
				local archetype = change.archetype
 | 
			
		||||
				local columns = archetype.columns
 | 
			
		||||
				local archetypeRecords = archetype.records
 | 
			
		||||
				for _, id in componentIds do 
 | 
			
		||||
					table.insert(queryOutput, columns[archetypeRecords[id]][row])
 | 
			
		||||
				for index, id in componentIds do
 | 
			
		||||
					queryOutput[index] = columns[archetypeRecords[id]][row]
 | 
			
		||||
				end
 | 
			
		||||
 | 
			
		||||
				return archetype.entities[row], unpack(queryOutput, 1, #queryOutput)
 | 
			
		||||
				return archetype.entities[row], unpack(queryOutput, 1, idsCount)
 | 
			
		||||
			end
 | 
			
		||||
		end
 | 
			
		||||
		end;
 | 
			
		||||
	}
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return table.freeze({
 | 
			
		||||
	World = World,
 | 
			
		||||
	ON_ADD = ON_ADD,
 | 
			
		||||
	ON_REMOVE = ON_REMOVE,
 | 
			
		||||
	ON_SET = ON_SET
 | 
			
		||||
	World = World;
 | 
			
		||||
	ON_ADD = ON_ADD;
 | 
			
		||||
	ON_REMOVE = ON_REMOVE;
 | 
			
		||||
	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"
 | 
			
		||||
Matter = "matter-ecs/matter@0.8.0"
 | 
			
		||||
ecr = "centau/ecr@0.8.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue