mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-10-30 16:59:17 +00:00 
			
		
		
		
	wuh
This commit is contained in:
		
							parent
							
								
									b3f8e2504e
								
							
						
					
					
						commit
						f1dc668214
					
				
					 4 changed files with 356 additions and 387 deletions
				
			
		|  | @ -3,4 +3,4 @@ wally = "upliftgames/wally@0.3.1" | ||||||
| rojo = "rojo-rbx/rojo@7.4.1" | rojo = "rojo-rbx/rojo@7.4.1" | ||||||
| stylua = "johnnymorganz/stylua@0.19.1" | stylua = "johnnymorganz/stylua@0.19.1" | ||||||
| selene = "kampfkarren/selene@0.26.1" | selene = "kampfkarren/selene@0.26.1" | ||||||
| wally-patch-package="Barocena/wally-patch-package@1.2.1" | wally-patch-package = "Barocena/wally-patch-package@1.2.1" | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| --!optimize 2 | --!optimize 2 | ||||||
| --!native | --!native | ||||||
| 
 | 
 | ||||||
| local testkit = require('../testkit') | local testkit = require("../testkit") | ||||||
| local BENCH, START = testkit.benchmark() | local BENCH, START = testkit.benchmark() | ||||||
| local function TITLE(title: string) | local function TITLE(title: string) | ||||||
|     print() | 	print() | ||||||
|     print(testkit.color.white(title)) | 	print(testkit.color.white(title)) | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local jecs = require("../mirror/init") | local jecs = require("../mirror/init") | ||||||
|  | @ -15,285 +15,285 @@ local oldMatter = require("../oldMatter") | ||||||
| local newMatter = require("../newMatter") | local newMatter = require("../newMatter") | ||||||
| type i53 = number | type i53 = number | ||||||
| 
 | 
 | ||||||
| do TITLE (testkit.color.white_underline("Jecs query")) | do | ||||||
|     local ecs = jecs.World.new() | 	TITLE(testkit.color.white_underline("Jecs query")) | ||||||
|     do TITLE "one component in common" | 	local ecs = jecs.World.new() | ||||||
|  | 	do | ||||||
|  | 		TITLE("one component in common") | ||||||
| 
 | 
 | ||||||
|         local function view_bench( | 		local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) | ||||||
|             world: jecs.World, | 			BENCH("1 component", function() | ||||||
|             A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53 | 				for _ in world:query(A) do | ||||||
|         ) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("1 component", function() | 			BENCH("2 component", function() | ||||||
|                 for _ in world:query(A) do end | 				for _ in world:query(A, B) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("2 component", function() | 			BENCH("4 component", function() | ||||||
|                 for _ in world:query(A, B) do end | 				for _ in world:query(A, B, C, D) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("4 component", function() | 			BENCH("8 component", function() | ||||||
|                 for _ in world:query(A, B, C, D) do  | 				for _ in world:query(A, B, C, D, E, F, G, H) do | ||||||
|                 end | 				end | ||||||
|             end) | 			end) | ||||||
|  | 		end | ||||||
| 
 | 
 | ||||||
|             BENCH("8 component", function() | 		local D1 = ecs:component() | ||||||
|                 for _ in world:query(A, B, C, D, E, F, G, H) do end | 		local D2 = ecs:component() | ||||||
|             end) | 		local D3 = ecs:component() | ||||||
|         end | 		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 function flip() | ||||||
|         local D2 = ecs:component() | 			return math.random() >= 0.15 | ||||||
|         local D3 = ecs:component() | 		end | ||||||
|         local D4 = ecs:component() |  | ||||||
|         local D5 = ecs:component() |  | ||||||
|         local D6 = ecs:component() |  | ||||||
|         local D7 = ecs:component() |  | ||||||
|         local D8 = ecs:component() |  | ||||||
| 
 | 
 | ||||||
|         local function flip()  | 		local added = 0 | ||||||
|             return math.random() >= 0.15 |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         local added = 0 |  | ||||||
| 		local archetypes = {} | 		local archetypes = {} | ||||||
|         for i = 1, 2^16-2 do  | 		for i = 1, 2 ^ 16 - 2 do | ||||||
|             local entity = ecs:entity() | 			local entity = ecs:entity() | ||||||
| 
 | 
 | ||||||
|             local combination = "" | 			local combination = "" | ||||||
| 
 | 
 | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "B" | 				combination ..= "B" | ||||||
|                 ecs:set(entity, D2, {value = true}) | 				ecs:set(entity, D2, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "C" | 				combination ..= "C" | ||||||
|                 ecs:set(entity, D3, { value = true }) | 				ecs:set(entity, D3, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "D" | 				combination ..= "D" | ||||||
|                 ecs:set(entity, D4, { value = true}) | 				ecs:set(entity, D4, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "E" | 				combination ..= "E" | ||||||
|                 ecs:set(entity, D5, { value = true}) | 				ecs:set(entity, D5, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "F" | 				combination ..= "F" | ||||||
|                 ecs:set(entity, D6, {value = true}) | 				ecs:set(entity, D6, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "G" | 				combination ..= "G" | ||||||
|                 ecs:set(entity, D7, { value = true}) | 				ecs:set(entity, D7, {value = true}) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "H" | 				combination ..= "H" | ||||||
|                 ecs:set(entity, D8, {value = true}) | 				ecs:set(entity, D8, {value = true}) | ||||||
|  | 			end | ||||||
| 
 | 
 | ||||||
|             end | 			if #combination == 7 then | ||||||
| 
 | 				added += 1 | ||||||
|             if #combination == 7 then  | 				ecs:set(entity, D1, {value = true}) | ||||||
|                 added += 1 | 			end | ||||||
|                 ecs:set(entity, D1, { value = true}) |  | ||||||
|             end |  | ||||||
| 			archetypes[combination] = true | 			archetypes[combination] = true | ||||||
|         end | 		end | ||||||
| 
 | 
 | ||||||
| 		local a = 0 | 		local a = 0 | ||||||
| 		for _ in archetypes do a+= 1 end | 		for _ in archetypes do | ||||||
|  | 			a += 1 | ||||||
|  | 		end | ||||||
| 
 | 
 | ||||||
|         view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | 		view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | ||||||
|     end | 	end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| do TITLE(testkit.color.white_underline("OldMatter query"))  | do | ||||||
|  | 	TITLE(testkit.color.white_underline("OldMatter query")) | ||||||
| 
 | 
 | ||||||
|     local ecs = oldMatter.World.new() | 	local ecs = oldMatter.World.new() | ||||||
|     local component = oldMatter.component | 	local component = oldMatter.component | ||||||
| 
 | 
 | ||||||
|     do TITLE "one component in common" | 	do | ||||||
|         local function view_bench( | 		TITLE("one component in common") | ||||||
|             world: jecs.World, | 		local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) | ||||||
|             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() | 			BENCH("2 component", function() | ||||||
|                 for _ in world:query(A) do end | 				for _ in world:query(A, B) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("2 component", function() | 			BENCH("4 component", function() | ||||||
|                 for _ in world:query(A, B) do end | 				for _ in world:query(A, B, C, D) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("4 component", function() | 			BENCH("8 component", function() | ||||||
|                 for _ in world:query(A, B, C, D) do  | 				for _ in world:query(A, B, C, D, E, F, G, H) do | ||||||
|                 end | 				end | ||||||
|             end) | 			end) | ||||||
|  | 		end | ||||||
| 
 | 
 | ||||||
|             BENCH("8 component", function() | 		local D1 = component() | ||||||
|                 for _ in world:query(A, B, C, D, E, F, G, H) do end | 		local D2 = component() | ||||||
|             end) | 		local D3 = component() | ||||||
|         end | 		local D4 = component() | ||||||
|  | 		local D5 = component() | ||||||
|  | 		local D6 = component() | ||||||
|  | 		local D7 = component() | ||||||
|  | 		local D8 = component() | ||||||
| 
 | 
 | ||||||
|         local D1 = component() | 		local function flip() | ||||||
|         local D2 = component() | 			return math.random() >= 0.15 | ||||||
|         local D3 = component() | 		end | ||||||
|         local D4 = component() |  | ||||||
|         local D5 = component() |  | ||||||
|         local D6 = component() |  | ||||||
|         local D7 = component() |  | ||||||
|         local D8 = component() |  | ||||||
| 
 | 
 | ||||||
|         local function flip()  | 		local added = 0 | ||||||
|             return math.random() >= 0.15 |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         local added = 0 |  | ||||||
| 		local archetypes = {} | 		local archetypes = {} | ||||||
|         for i = 1, 2^16-2 do  | 		for i = 1, 2 ^ 16 - 2 do | ||||||
|             local entity = ecs:spawn() | 			local entity = ecs:spawn() | ||||||
| 
 | 
 | ||||||
|             local combination = "" | 			local combination = "" | ||||||
| 
 | 
 | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "B" | 				combination ..= "B" | ||||||
|                 ecs:insert(entity, D2({value =  true})) | 				ecs:insert(entity, D2({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "C" | 				combination ..= "C" | ||||||
|                 ecs:insert(entity, D3({value =  true})) | 				ecs:insert(entity, D3({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "D" | 				combination ..= "D" | ||||||
|                 ecs:insert(entity, D4({value =  true})) | 				ecs:insert(entity, D4({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "E" | 				combination ..= "E" | ||||||
|                 ecs:insert(entity, D5({value =  true})) | 				ecs:insert(entity, D5({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "F" | 				combination ..= "F" | ||||||
|                 ecs:insert(entity, D6({value =  true})) | 				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 | ||||||
| 
 | 
 | ||||||
|             end | 			if #combination == 7 then | ||||||
|             if flip() then  | 				added += 1 | ||||||
|                 combination ..= "G" | 				ecs:insert(entity, D1({value = true})) | ||||||
|                 ecs:insert(entity, D7({value =  true})) | 			end | ||||||
| 
 |  | ||||||
|             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 | 			archetypes[combination] = true | ||||||
|         end | 		end | ||||||
| 
 | 
 | ||||||
| 		local a = 0 | 		local a = 0 | ||||||
| 		for _ in archetypes do a+= 1 end | 		for _ in archetypes do | ||||||
| 
 | 			a += 1 | ||||||
|         view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | 		end | ||||||
|     end |  | ||||||
| 
 | 
 | ||||||
|  | 		view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | ||||||
|  | 	end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| do TITLE(testkit.color.white_underline("NewMatter query"))  | do | ||||||
|  | 	TITLE(testkit.color.white_underline("NewMatter query")) | ||||||
| 
 | 
 | ||||||
|     local ecs = newMatter.World.new() | 	local ecs = newMatter.World.new() | ||||||
|     local component = newMatter.component | 	local component = newMatter.component | ||||||
| 
 | 
 | ||||||
|     do TITLE "one component in common" | 	do | ||||||
|         local function view_bench( | 		TITLE("one component in common") | ||||||
|             world: jecs.World, | 		local function view_bench(world: jecs.World, A: i53, B: i53, C: i53, D: i53, E: i53, F: i53, G: i53, H: i53) | ||||||
|             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() | 			BENCH("2 component", function() | ||||||
|                 for _ in world:query(A) do end | 				for _ in world:query(A, B) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("2 component", function() | 			BENCH("4 component", function() | ||||||
|                 for _ in world:query(A, B) do end | 				for _ in world:query(A, B, C, D) do | ||||||
|             end) | 				end | ||||||
|  | 			end) | ||||||
| 
 | 
 | ||||||
|             BENCH("4 component", function() | 			BENCH("8 component", function() | ||||||
|                 for _ in world:query(A, B, C, D) do  | 				for _ in world:query(A, B, C, D, E, F, G, H) do | ||||||
|                 end | 				end | ||||||
|             end) | 			end) | ||||||
|  | 		end | ||||||
| 
 | 
 | ||||||
|             BENCH("8 component", function() | 		local D1 = component() | ||||||
|                 for _ in world:query(A, B, C, D, E, F, G, H) do end | 		local D2 = component() | ||||||
|             end) | 		local D3 = component() | ||||||
|         end | 		local D4 = component() | ||||||
|  | 		local D5 = component() | ||||||
|  | 		local D6 = component() | ||||||
|  | 		local D7 = component() | ||||||
|  | 		local D8 = component() | ||||||
| 
 | 
 | ||||||
|         local D1 = component() | 		local function flip() | ||||||
|         local D2 = component() | 			return math.random() >= 0.15 | ||||||
|         local D3 = component() | 		end | ||||||
|         local D4 = component() |  | ||||||
|         local D5 = component() |  | ||||||
|         local D6 = component() |  | ||||||
|         local D7 = component() |  | ||||||
|         local D8 = component() |  | ||||||
| 
 | 
 | ||||||
|         local function flip()  | 		local added = 0 | ||||||
|             return math.random() >= 0.15 |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         local added = 0 |  | ||||||
| 		local archetypes = {} | 		local archetypes = {} | ||||||
|         for i = 1, 2^16-2 do  | 		for i = 1, 2 ^ 16 - 2 do | ||||||
|             local entity = ecs:spawn() | 			local entity = ecs:spawn() | ||||||
| 
 | 
 | ||||||
|             local combination = "" | 			local combination = "" | ||||||
| 
 | 
 | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "B" | 				combination ..= "B" | ||||||
|                 ecs:insert(entity, D2({value =  true})) | 				ecs:insert(entity, D2({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "C" | 				combination ..= "C" | ||||||
|                 ecs:insert(entity, D3({value =  true})) | 				ecs:insert(entity, D3({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "D" | 				combination ..= "D" | ||||||
|                 ecs:insert(entity, D4({value =  true})) | 				ecs:insert(entity, D4({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "E" | 				combination ..= "E" | ||||||
|                 ecs:insert(entity, D5({value =  true})) | 				ecs:insert(entity, D5({value = true})) | ||||||
|             end | 			end | ||||||
|             if flip() then  | 			if flip() then | ||||||
|                 combination ..= "F" | 				combination ..= "F" | ||||||
|                 ecs:insert(entity, D6({value =  true})) | 				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 | ||||||
| 
 | 
 | ||||||
|             end | 			if #combination == 7 then | ||||||
|             if flip() then  | 				added += 1 | ||||||
|                 combination ..= "G" | 				ecs:insert(entity, D1({value = true})) | ||||||
|                 ecs:insert(entity, D7({value =  true})) | 			end | ||||||
| 
 |  | ||||||
|             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 | 			archetypes[combination] = true | ||||||
|         end | 		end | ||||||
| 
 | 
 | ||||||
| 		local a = 0 | 		local a = 0 | ||||||
| 		for _ in archetypes do a+= 1 end | 		for _ in archetypes do | ||||||
| 
 | 			a += 1 | ||||||
|         view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | 		end | ||||||
|     end |  | ||||||
| 
 | 
 | ||||||
|  | 		view_bench(ecs, D1, D2, D3, D4, D5, D6, D7, D8) | ||||||
|  | 	end | ||||||
| end | end | ||||||
|  | @ -1,73 +0,0 @@ | ||||||
| --!native |  | ||||||
| --!optimize 2 |  | ||||||
| --!strict |  | ||||||
| 
 |  | ||||||
| export type i53 = number |  | ||||||
| export type i24 = number |  | ||||||
| 
 |  | ||||||
| export type Ty = {i53} |  | ||||||
| export type ArchetypeId = number |  | ||||||
| 
 |  | ||||||
| export type Column = {any} |  | ||||||
| 
 |  | ||||||
| export type Archetype = { |  | ||||||
| 	id: number, |  | ||||||
| 	edges: { |  | ||||||
| 		[i24]: { |  | ||||||
| 			add: Archetype, |  | ||||||
| 			remove: Archetype, |  | ||||||
| 		}, |  | ||||||
| 	}, |  | ||||||
| 	types: Ty, |  | ||||||
| 	type: string | number, |  | ||||||
| 	entities: {number}, |  | ||||||
| 	columns: {Column}, |  | ||||||
| 	records: {}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type Record = { |  | ||||||
| 	archetype: Archetype, |  | ||||||
| 	row: number, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type EntityIndex = {[i24]: Record} |  | ||||||
| export type ComponentIndex = {[i24]: ArchetypeMap} |  | ||||||
| 
 |  | ||||||
| export type ArchetypeRecord = number |  | ||||||
| export type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number} |  | ||||||
| export type Archetypes = {[ArchetypeId]: Archetype} |  | ||||||
| 
 |  | ||||||
| export type ArchetypeDiff = { |  | ||||||
| 	added: Ty, |  | ||||||
| 	removed: Ty, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type Hook = { |  | ||||||
| 	ids: Ty, |  | ||||||
| 	archetype: Archetype, |  | ||||||
| 	otherArchetype: Archetype, |  | ||||||
| 	offset: number, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type EventDescription = Hook & { |  | ||||||
| 	event: number, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type World = { |  | ||||||
| 	archetypeIndex: {[number | string]: Archetype}, |  | ||||||
| 	archetypes: {[number]: Archetype}, |  | ||||||
| 	componentIndex: {[i24]: ArchetypeMap}, |  | ||||||
| 	entityIndex: {[i53]: Record}, |  | ||||||
| 	hooks: {[number]: {Hook}}, |  | ||||||
| 	nextComponentId: number, |  | ||||||
| 	nextEntityId: number, |  | ||||||
| 	ROOT_ARCHETYPE: Archetype, |  | ||||||
| 
 |  | ||||||
| 	get: (self: World) -> (), |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export type WorldStatic = { |  | ||||||
| 	new: () -> World, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| return false |  | ||||||
							
								
								
									
										200
									
								
								lib/init.lua
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								lib/init.lua
									
									
									
									
									
								
							|  | @ -3,29 +3,45 @@ | ||||||
| --!strict | --!strict | ||||||
| --draft 4 | --draft 4 | ||||||
| 
 | 
 | ||||||
| local Types = require(script.Types) | type i53 = number | ||||||
|  | type i24 = number | ||||||
| 
 | 
 | ||||||
| type i53 = Types.i53 | type Ty = {i53} | ||||||
| type i24 = Types.i24 | type ArchetypeId = number | ||||||
| 
 | 
 | ||||||
| type Ty = Types.Ty | type Column = {any} | ||||||
| type ArchetypeId = Types.ArchetypeId |  | ||||||
| 
 | 
 | ||||||
| type Column = Types.Column | type Archetype = { | ||||||
|  | 	id: number, | ||||||
|  | 	edges: { | ||||||
|  | 		[i24]: { | ||||||
|  | 			add: Archetype, | ||||||
|  | 			remove: Archetype, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	types: Ty, | ||||||
|  | 	type: string | number, | ||||||
|  | 	entities: {number}, | ||||||
|  | 	columns: {Column}, | ||||||
|  | 	records: {}, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| type Archetype = Types.Archetype | type Record = { | ||||||
| type Record = Types.Record | 	archetype: Archetype, | ||||||
|  | 	row: number, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| type EntityIndex = Types.EntityIndex | type EntityIndex = {[i24]: Record} | ||||||
| type ComponentIndex = Types.ComponentIndex | type ComponentIndex = {[i24]: ArchetypeMap} | ||||||
| 
 | 
 | ||||||
| type ArchetypeRecord = Types.ArchetypeRecord | type ArchetypeRecord = number | ||||||
| type ArchetypeMap = Types.ArchetypeMap | type ArchetypeMap = {sparse: {[ArchetypeId]: ArchetypeRecord}, size: number} | ||||||
| type Archetypes = Types.Archetypes | type Archetypes = {[ArchetypeId]: Archetype} | ||||||
| 
 | 
 | ||||||
| type ArchetypeDiff = Types.ArchetypeDiff | type ArchetypeDiff = { | ||||||
| 
 | 	added: Ty, | ||||||
| -- type World = Types.World | 	removed: Ty, | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| local HI_COMPONENT_ID = 256 | local HI_COMPONENT_ID = 256 | ||||||
| local ON_ADD = HI_COMPONENT_ID + 1 | local ON_ADD = HI_COMPONENT_ID + 1 | ||||||
|  | @ -66,24 +82,27 @@ local function transitionArchetype( | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	-- Move the entity from the source to the destination archetype. | 	-- Move the entity from the source to the destination archetype. | ||||||
| 	destinationEntities[destinationRow] = sourceEntities[sourceRow] | 	local atSourceRow = sourceEntities[sourceRow] | ||||||
| 	entityIndex[sourceEntities[sourceRow]].row = destinationRow | 	destinationEntities[destinationRow] = atSourceRow | ||||||
|  | 	entityIndex[atSourceRow].row = destinationRow | ||||||
| 
 | 
 | ||||||
| 	-- Because we have swapped columns we now have to update the records | 	-- Because we have swapped columns we now have to update the records | ||||||
| 	-- corresponding to the entities' rows that were swapped. | 	-- corresponding to the entities' rows that were swapped. | ||||||
| 	local movedAway = #sourceEntities | 	local movedAway = #sourceEntities | ||||||
| 	if sourceRow ~= movedAway then | 	if sourceRow ~= movedAway then | ||||||
| 		sourceEntities[sourceRow] = sourceEntities[movedAway] | 		local atMovedAway = sourceEntities[movedAway] | ||||||
| 		entityIndex[sourceEntities[movedAway]].row = sourceRow | 		sourceEntities[sourceRow] = atMovedAway | ||||||
|  | 		entityIndex[atMovedAway].row = sourceRow | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	sourceEntities[movedAway] = nil | 	sourceEntities[movedAway] = nil | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function archetypeAppend(entity: i53, archetype: Archetype): i24 | local function archetypeAppend(entity: number, archetype: Archetype): number | ||||||
| 	local entities = archetype.entities | 	local entities = archetype.entities | ||||||
| 	table.insert(entities, entity) | 	local length = #entities + 1 | ||||||
| 	return #entities | 	entities[length] = entity | ||||||
|  | 	return length | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function newEntity(entityId: i53, record: Record, archetype: Archetype) | local function newEntity(entityId: i53, record: Record, archetype: Archetype) | ||||||
|  | @ -107,32 +126,34 @@ local function hash(arr): string | number | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?) | local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype, from: Archetype?) | ||||||
| 	local destinationCount = #to.types |  | ||||||
| 	local destinationIds = to.types | 	local destinationIds = to.types | ||||||
|  | 	local records = to.records | ||||||
|  | 	local id = to.id | ||||||
| 
 | 
 | ||||||
| 	for i = 1, destinationCount do | 	for i, destinationId in destinationIds do | ||||||
| 		local destinationId = destinationIds[i] | 		local archetypesMap = componentIndex[destinationId] | ||||||
| 
 | 
 | ||||||
| 		if not componentIndex[destinationId] then | 		if not archetypesMap then | ||||||
| 			componentIndex[destinationId] = {size = 0, sparse = {}} | 			archetypesMap = {size = 0, sparse = {}} | ||||||
|  | 			componentIndex[destinationId] = archetypesMap | ||||||
| 		end | 		end | ||||||
| 
 | 
 | ||||||
| 		local archetypesMap = componentIndex[destinationId] | 		archetypesMap.sparse[id] = i | ||||||
| 		archetypesMap.sparse[to.id] = i | 		records[destinationId] = i | ||||||
| 		to.records[destinationId] = i |  | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype | local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archetype | ||||||
| 	local ty = hash(types) | 	local ty = hash(types) | ||||||
| 
 | 
 | ||||||
| 	world.nextArchetypeId = (world.nextArchetypeId :: number) + 1 | 	local id = world.nextArchetypeId + 1 | ||||||
| 	local id = world.nextArchetypeId | 	world.nextArchetypeId = id | ||||||
| 
 | 
 | ||||||
| 	local columns = {} :: {any} | 	local length = #types | ||||||
|  | 	local columns = table.create(length) :: {any} | ||||||
| 
 | 
 | ||||||
| 	for _ in types do | 	for index in types do | ||||||
| 		table.insert(columns, {}) | 		columns[index] = {} | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	local archetype = { | 	local archetype = { | ||||||
|  | @ -146,7 +167,7 @@ local function archetypeOf(world: World, types: {i24}, prev: Archetype?): Archet | ||||||
| 	} | 	} | ||||||
| 	world.archetypeIndex[ty] = archetype | 	world.archetypeIndex[ty] = archetype | ||||||
| 	world.archetypes[id] = archetype | 	world.archetypes[id] = archetype | ||||||
| 	if #types > 0 then | 	if length > 0 then | ||||||
| 		createArchetypeRecords(world.componentIndex, archetype, prev) | 		createArchetypeRecords(world.componentIndex, archetype, prev) | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
|  | @ -212,9 +233,7 @@ local function ensureArchetype(world: World, types, prev) | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function findInsert(types: {i53}, toAdd: i53) | local function findInsert(types: {i53}, toAdd: i53) | ||||||
| 	local count = #types | 	for i, id in types do | ||||||
| 	for i = 1, count do |  | ||||||
| 		local id = types[i] |  | ||||||
| 		if id == toAdd then | 		if id == toAdd then | ||||||
| 			return -1 | 			return -1 | ||||||
| 		end | 		end | ||||||
|  | @ -222,7 +241,7 @@ local function findInsert(types: {i53}, toAdd: i53) | ||||||
| 			return i | 			return i | ||||||
| 		end | 		end | ||||||
| 	end | 	end | ||||||
| 	return count + 1 | 	return #types + 1 | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function findArchetypeWith(world: World, node: Archetype, componentId: i53) | local function findArchetypeWith(world: World, node: Archetype, componentId: i53) | ||||||
|  | @ -243,38 +262,48 @@ local function findArchetypeWith(world: World, node: Archetype, componentId: i53 | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function ensureEdge(archetype: Archetype, componentId: i53) | local function ensureEdge(archetype: Archetype, componentId: i53) | ||||||
| 	if not archetype.edges[componentId] then | 	local edges = archetype.edges | ||||||
| 		archetype.edges[componentId] = {} :: any | 	local edge = edges[componentId] | ||||||
|  | 	if not edge then | ||||||
|  | 		edge = {} :: any | ||||||
|  | 		edges[componentId] = edge | ||||||
| 	end | 	end | ||||||
| 	return archetype.edges[componentId] | 	return edge | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype | local function archetypeTraverseAdd(world: World, componentId: i53, from: Archetype): Archetype | ||||||
| 	if not from then | 	if not from then | ||||||
| 		-- If there was no source archetype then it should return the ROOT_ARCHETYPE | 		-- If there was no source archetype then it should return the ROOT_ARCHETYPE | ||||||
| 		if not world.ROOT_ARCHETYPE then | 		local ROOT_ARCHETYPE = world.ROOT_ARCHETYPE | ||||||
| 			local ROOT_ARCHETYPE = archetypeOf(world, {}, nil) | 		if not ROOT_ARCHETYPE then | ||||||
| 			world.ROOT_ARCHETYPE = ROOT_ARCHETYPE | 			ROOT_ARCHETYPE = archetypeOf(world, {}, nil) | ||||||
|  | 			world.ROOT_ARCHETYPE = ROOT_ARCHETYPE :: never | ||||||
| 		end | 		end | ||||||
| 		from = world.ROOT_ARCHETYPE | 		from = ROOT_ARCHETYPE | ||||||
| 	end | 	end | ||||||
| 	local edge = ensureEdge(from, componentId) |  | ||||||
| 
 | 
 | ||||||
| 	if not edge.add then | 	local edge = ensureEdge(from, componentId) | ||||||
|  | 	local add = edge.add | ||||||
|  | 	if not add then | ||||||
| 		-- Save an edge using the component ID to the archetype to allow | 		-- Save an edge using the component ID to the archetype to allow | ||||||
| 		-- faster traversals to adjacent archetypes. | 		-- faster traversals to adjacent archetypes. | ||||||
| 		edge.add = findArchetypeWith(world, from, componentId) | 		add = findArchetypeWith(world, from, componentId) | ||||||
|  | 		edge.add = add :: never | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	return edge.add | 	return add | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function ensureRecord(entityIndex, entityId: i53): Record | local function ensureRecord(entityIndex, entityId: i53): Record | ||||||
| 	local id = entityId | 	local id = entityId | ||||||
| 	if not entityIndex[id] then | 	local record = entityIndex[id] | ||||||
| 		entityIndex[id] = {} | 
 | ||||||
|  | 	if not record then | ||||||
|  | 		record = {} | ||||||
|  | 		entityIndex[id] = record | ||||||
| 	end | 	end | ||||||
| 	return entityIndex[id] :: Record | 
 | ||||||
|  | 	return record :: Record | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| function World.set(world: World, entityId: i53, componentId: i53, data: unknown) | function World.set(world: World, entityId: i53, componentId: i53, data: unknown) | ||||||
|  | @ -310,27 +339,30 @@ local function archetypeTraverseRemove(world: World, componentId: i53, archetype | ||||||
| 	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype | 	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype | ||||||
| 	local edge = ensureEdge(from, componentId) | 	local edge = ensureEdge(from, componentId) | ||||||
| 
 | 
 | ||||||
| 	if not edge.remove then | 	local remove = edge.remove | ||||||
|  | 	if not remove then | ||||||
| 		local to = table.clone(from.types) | 		local to = table.clone(from.types) | ||||||
| 		table.remove(to, table.find(to, componentId)) | 		table.remove(to, table.find(to, componentId)) | ||||||
| 		edge.remove = ensureArchetype(world, to, from) | 		remove = ensureArchetype(world, to, from) | ||||||
|  | 		edge.remove = remove :: never | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	return edge.remove | 	return remove | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| function World.remove(world: World, entityId: i53, componentId: i53) | function World.remove(world: World, entityId: i53, componentId: i53) | ||||||
| 	local record = ensureRecord(world.entityIndex, entityId) | 	local entityIndex = world.entityIndex | ||||||
|  | 	local record = ensureRecord(entityIndex, entityId) | ||||||
| 	local sourceArchetype = record.archetype | 	local sourceArchetype = record.archetype | ||||||
| 	local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) | 	local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype) | ||||||
| 
 | 
 | ||||||
| 	if sourceArchetype and not (sourceArchetype == destinationArchetype) then | 	if sourceArchetype and not (sourceArchetype == destinationArchetype) then | ||||||
| 		moveEntity(world.entityIndex, entityId, record, destinationArchetype) | 		moveEntity(entityIndex, entityId, record, destinationArchetype) | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| -- Keeping the function as small as possible to enable inlining | -- Keeping the function as small as possible to enable inlining | ||||||
| local function get(componentIndex: {[i24]: ArchetypeMap}, record: Record, componentId: i24): number? | local function get(_componentIndex: {[i24]: ArchetypeMap}, record: Record, componentId: i24) | ||||||
| 	local archetype = record.archetype | 	local archetype = record.archetype | ||||||
| 	local archetypeRecord = archetype.records[componentId] | 	local archetypeRecord = archetype.records[componentId] | ||||||
| 
 | 
 | ||||||
|  | @ -364,7 +396,7 @@ function World.get(world: World, entityId: i53, a: i53, b: i53?, c: i53?, d: i53 | ||||||
| 	end | 	end | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| local function noop(self: Query, ...: i53): () -> (number, ...any) | local function noop(_self: Query, ...: i53): () -> (number, ...any) | ||||||
| 	return function() end :: any | 	return function() end :: any | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
|  | @ -379,6 +411,8 @@ export type Query = typeof(EmptyQuery) | ||||||
| 
 | 
 | ||||||
| function World.query(world: World, ...: i53): Query | function World.query(world: World, ...: i53): Query | ||||||
| 	local compatibleArchetypes = {} | 	local compatibleArchetypes = {} | ||||||
|  | 	local length = 0 | ||||||
|  | 
 | ||||||
| 	local components = {...} | 	local components = {...} | ||||||
| 	local archetypes = world.archetypes | 	local archetypes = world.archetypes | ||||||
| 	local queryLength = #components | 	local queryLength = #components | ||||||
|  | @ -390,7 +424,7 @@ function World.query(world: World, ...: i53): Query | ||||||
| 	local firstArchetypeMap | 	local firstArchetypeMap | ||||||
| 	local componentIndex = world.componentIndex | 	local componentIndex = world.componentIndex | ||||||
| 
 | 
 | ||||||
| 	for i, componentId in components do | 	for _, componentId in components do | ||||||
| 		local map = componentIndex[componentId] | 		local map = componentIndex[componentId] | ||||||
| 		if not map then | 		if not map then | ||||||
| 			return EmptyQuery | 			return EmptyQuery | ||||||
|  | @ -419,7 +453,9 @@ function World.query(world: World, ...: i53): Query | ||||||
| 		if skip then | 		if skip then | ||||||
| 			continue | 			continue | ||||||
| 		end | 		end | ||||||
| 		table.insert(compatibleArchetypes, {archetype, indices}) | 
 | ||||||
|  | 		length += 1 | ||||||
|  | 		compatibleArchetypes[length] = {archetype, indices} | ||||||
| 	end | 	end | ||||||
| 
 | 
 | ||||||
| 	local lastArchetype, compatibleArchetype = next(compatibleArchetypes) | 	local lastArchetype, compatibleArchetype = next(compatibleArchetypes) | ||||||
|  | @ -431,18 +467,18 @@ function World.query(world: World, ...: i53): Query | ||||||
| 	preparedQuery.__index = preparedQuery | 	preparedQuery.__index = preparedQuery | ||||||
| 
 | 
 | ||||||
| 	function preparedQuery:without(...) | 	function preparedQuery:without(...) | ||||||
| 		local components = {...} | 		local withoutComponents = {...} | ||||||
| 		for i = #compatibleArchetypes, 1, -1 do | 		for index = #compatibleArchetypes, 1, -1 do | ||||||
| 			local archetype = compatibleArchetypes[i][1] | 			local archetype = compatibleArchetypes[index][1] | ||||||
| 			local shouldRemove = false | 			local shouldRemove = false | ||||||
| 			for _, componentId in components do | 			for _, componentId in withoutComponents do | ||||||
| 				if archetype.records[componentId] then | 				if archetype.records[componentId] then | ||||||
| 					shouldRemove = true | 					shouldRemove = true | ||||||
| 					break | 					break | ||||||
| 				end | 				end | ||||||
| 			end | 			end | ||||||
| 			if shouldRemove then | 			if shouldRemove then | ||||||
| 				table.remove(compatibleArchetypes, i) | 				table.remove(compatibleArchetypes, index) | ||||||
| 			end | 			end | ||||||
| 		end | 		end | ||||||
| 
 | 
 | ||||||
|  | @ -460,18 +496,20 @@ function World.query(world: World, ...: i53): Query | ||||||
| 	function preparedQuery:__iter() | 	function preparedQuery:__iter() | ||||||
| 		return function() | 		return function() | ||||||
| 			local archetype = compatibleArchetype[1] | 			local archetype = compatibleArchetype[1] | ||||||
| 			local row = next(archetype.entities, lastRow) | 			local entities = archetype.entities | ||||||
|  | 			local row = next(entities, lastRow) | ||||||
| 			while row == nil do | 			while row == nil do | ||||||
| 				lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) | 				lastArchetype, compatibleArchetype = next(compatibleArchetypes, lastArchetype) | ||||||
| 				if lastArchetype == nil then | 				if lastArchetype == nil then | ||||||
| 					return | 					return | ||||||
| 				end | 				end | ||||||
| 				archetype = compatibleArchetype[1] | 				archetype = compatibleArchetype[1] | ||||||
| 				row = next(archetype.entities, row) | 				entities = archetype.entities | ||||||
|  | 				row = next(entities, row) | ||||||
| 			end | 			end | ||||||
| 			lastRow = row | 			lastRow = row | ||||||
| 
 | 
 | ||||||
| 			local entityId = archetype.entities[row :: number] | 			local entityId = entities[row :: number] | ||||||
| 			local columns = archetype.columns | 			local columns = archetype.columns | ||||||
| 			local tr = compatibleArchetype[2] | 			local tr = compatibleArchetype[2] | ||||||
| 
 | 
 | ||||||
|  | @ -519,8 +557,8 @@ function World.query(world: World, ...: i53): Query | ||||||
| 					columns[tr[8]][row] | 					columns[tr[8]][row] | ||||||
| 			end | 			end | ||||||
| 
 | 
 | ||||||
| 			for i in components do | 			for index in components do | ||||||
| 				queryOutput[i] = tr[i][row] | 				queryOutput[index] = tr[index][row] | ||||||
| 			end | 			end | ||||||
| 
 | 
 | ||||||
| 			return entityId, unpack(queryOutput, 1, queryLength) | 			return entityId, unpack(queryOutput, 1, queryLength) | ||||||
|  | @ -542,8 +580,9 @@ function World.component(world: World) | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| function World.entity(world: World) | function World.entity(world: World) | ||||||
| 	world.nextEntityId += 1 | 	local nextEntityId = world.nextEntityId + 1 | ||||||
| 	return world.nextEntityId + REST | 	world.nextEntityId = nextEntityId | ||||||
|  | 	return nextEntityId + REST | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| function World.delete(world: World, entityId: i53) | function World.delete(world: World, entityId: i53) | ||||||
|  | @ -559,11 +598,12 @@ end | ||||||
| 
 | 
 | ||||||
| function World.observer(world: World, ...) | function World.observer(world: World, ...) | ||||||
| 	local componentIds = {...} | 	local componentIds = {...} | ||||||
|  | 	local hooks = world.hooks | ||||||
| 
 | 
 | ||||||
| 	return { | 	return { | ||||||
| 		event = function(event) | 		event = function(event) | ||||||
| 			local hook = world.hooks[event] | 			local hook = hooks[event] | ||||||
| 			world.hooks[event] = nil | 			hooks[event] = nil | ||||||
| 
 | 
 | ||||||
| 			local last, change | 			local last, change | ||||||
| 			return function() | 			return function() | ||||||
|  | @ -573,10 +613,11 @@ function World.observer(world: World, ...) | ||||||
| 				end | 				end | ||||||
| 
 | 
 | ||||||
| 				local matched = false | 				local matched = false | ||||||
|  | 				local ids = change.ids | ||||||
| 
 | 
 | ||||||
| 				while not matched do | 				while not matched do | ||||||
| 					local skip = false | 					local skip = false | ||||||
| 					for _, id in change.ids do | 					for _, id in ids do | ||||||
| 						if not table.find(componentIds, id) then | 						if not table.find(componentIds, id) then | ||||||
| 							skip = true | 							skip = true | ||||||
| 							break | 							break | ||||||
|  | @ -585,6 +626,7 @@ function World.observer(world: World, ...) | ||||||
| 
 | 
 | ||||||
| 					if skip then | 					if skip then | ||||||
| 						last, change = next(hook, last) | 						last, change = next(hook, last) | ||||||
|  | 						ids = change.ids | ||||||
| 						continue | 						continue | ||||||
| 					end | 					end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue