mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-03 18:39:19 +00:00 
			
		
		
		
	Fix backwards edge traversal for exclusive relationships
This commit is contained in:
		
							parent
							
								
									8f95309871
								
							
						
					
					
						commit
						1d650d12e9
					
				
					 4 changed files with 178 additions and 9 deletions
				
			
		
							
								
								
									
										10
									
								
								jecs.luau
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								jecs.luau
									
									
									
									
									
								
							| 
						 | 
					@ -2443,10 +2443,9 @@ local function world_new()
 | 
				
			||||||
						if not idr then
 | 
											if not idr then
 | 
				
			||||||
							idr = component_index[wc]
 | 
												idr = component_index[wc]
 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
 | 
											edge[id] = to
 | 
				
			||||||
 | 
											archetype_edges[(to :: Archetype).id][id] = src
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
 | 
					 | 
				
			||||||
					edge[id] = to
 | 
					 | 
				
			||||||
					archetype_edges[(to :: Archetype).id][id] = src
 | 
					 | 
				
			||||||
				else
 | 
									else
 | 
				
			||||||
					if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
										if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
				
			||||||
						local on_remove = idr.on_remove
 | 
											local on_remove = idr.on_remove
 | 
				
			||||||
| 
						 | 
					@ -2544,10 +2543,9 @@ local function world_new()
 | 
				
			||||||
					if not idr then
 | 
										if not idr then
 | 
				
			||||||
						idr = component_index[wc]
 | 
											idr = component_index[wc]
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
 | 
										edge[id] = to
 | 
				
			||||||
 | 
										archetype_edges[(to :: Archetype).id][id] = src
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
 | 
					 | 
				
			||||||
				edge[id] = to
 | 
					 | 
				
			||||||
				archetype_edges[(to :: Archetype).id][id] = src
 | 
					 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
									if bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
				
			||||||
					local on_remove = idr.on_remove
 | 
										local on_remove = idr.on_remove
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "@rbxts/jecs",
 | 
						"name": "@rbxts/jecs",
 | 
				
			||||||
	"version": "0.9.0-rc.9",
 | 
						"version": "0.9.0-rc.10",
 | 
				
			||||||
	"description": "Stupidly fast Entity Component System",
 | 
						"description": "Stupidly fast Entity Component System",
 | 
				
			||||||
	"main": "jecs.luau",
 | 
						"main": "jecs.luau",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										173
									
								
								test/tests.luau
									
									
									
									
									
								
							
							
						
						
									
										173
									
								
								test/tests.luau
									
									
									
									
									
								
							| 
						 | 
					@ -24,6 +24,25 @@ type Id<T=unknown> = jecs.Id<T>
 | 
				
			||||||
local entity_visualiser = require("@tools/entity_visualiser")
 | 
					local entity_visualiser = require("@tools/entity_visualiser")
 | 
				
			||||||
local dwi = entity_visualiser.stringify
 | 
					local dwi = entity_visualiser.stringify
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TEST("", function()
 | 
				
			||||||
 | 
						local world = jecs.world()
 | 
				
			||||||
 | 
						local a = world:entity()
 | 
				
			||||||
 | 
						local b = world:entity()
 | 
				
			||||||
 | 
						local c = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						world:add(a, pair(ChildOf, b))
 | 
				
			||||||
 | 
						world:add(a, pair(ChildOf, c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CHECK(not world:has(a, pair(ChildOf, b)))
 | 
				
			||||||
 | 
						CHECK(world:has(a, pair(ChildOf, c)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						world:remove(a, pair(ChildOf, c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CHECK(not world:has(a, pair(ChildOf, b)))
 | 
				
			||||||
 | 
						CHECK(not world:has(a, pair(ChildOf, c)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					end)
 | 
				
			||||||
TEST("ardi", function()
 | 
					TEST("ardi", function()
 | 
				
			||||||
	local world = jecs.world()
 | 
						local world = jecs.world()
 | 
				
			||||||
	local r = world:entity()
 | 
						local r = world:entity()
 | 
				
			||||||
| 
						 | 
					@ -319,7 +338,25 @@ TEST("repro", function()
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST("world:add()", function()
 | 
					TEST("world:add()", function()
 | 
				
			||||||
	do CASE "exclusive relations"
 | 
						do CASE "Removing exclusive pair should traverse backwards on edge"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local a = world:entity()
 | 
				
			||||||
 | 
							local b = world:entity()
 | 
				
			||||||
 | 
							local c = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(a, pair(ChildOf, b))
 | 
				
			||||||
 | 
							world:add(a, pair(ChildOf, c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(ChildOf, b)))
 | 
				
			||||||
 | 
							CHECK(world:has(a, pair(ChildOf, c)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:remove(a, pair(ChildOf, c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(ChildOf, b)))
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(ChildOf, c)))
 | 
				
			||||||
 | 
							CHECK(not world:target(a, ChildOf))
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
						do CASE "Exclusive relations"
 | 
				
			||||||
		local world = jecs.world()
 | 
							local world = jecs.world()
 | 
				
			||||||
		local A = world:component()
 | 
							local A = world:component()
 | 
				
			||||||
		world:add(A, jecs.Exclusive)
 | 
							world:add(A, jecs.Exclusive)
 | 
				
			||||||
| 
						 | 
					@ -2044,6 +2081,140 @@ TEST("world:remove()", function()
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST("world:set()", function()
 | 
					TEST("world:set()", function()
 | 
				
			||||||
 | 
						do CASE "Removing exclusive pair should traverse backwards on edge"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local a = world:entity()
 | 
				
			||||||
 | 
							local b = world:entity()
 | 
				
			||||||
 | 
							local c = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local BattleLink = world:component()
 | 
				
			||||||
 | 
							world:add(BattleLink, jecs.Exclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:set(a, pair(BattleLink, b), {
 | 
				
			||||||
 | 
								timestamp = 1,
 | 
				
			||||||
 | 
								transform = vector.create(1, 2, 3)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							world:set(a, pair(BattleLink, c), {
 | 
				
			||||||
 | 
								timestamp = 2,
 | 
				
			||||||
 | 
								transform = vector.create(1, 2, 3)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(BattleLink, b)))
 | 
				
			||||||
 | 
							CHECK(world:has(a, pair(BattleLink, c)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:remove(a, pair(BattleLink, c))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(BattleLink, b)))
 | 
				
			||||||
 | 
							CHECK(not world:has(a, pair(BattleLink, c)))
 | 
				
			||||||
 | 
							CHECK(not world:target(a, BattleLink))
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do CASE "Exclusive relations"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local A = world:component()
 | 
				
			||||||
 | 
							world:add(A, jecs.Exclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local B = world:component()
 | 
				
			||||||
 | 
							local C = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e = world:entity()
 | 
				
			||||||
 | 
							world:set(e, pair(A, B), true)
 | 
				
			||||||
 | 
							world:set(e, pair(A, C), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							-- We have to test the path that checks the uncached method
 | 
				
			||||||
 | 
							local e1 = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:set(e1, pair(A, B), true)
 | 
				
			||||||
 | 
							world:set(e1, pair(A, C), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e1, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e1, pair(A, C)) == true)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do CASE "exclusive relations invoke hooks"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local A = world:component()
 | 
				
			||||||
 | 
							local B = world:component()
 | 
				
			||||||
 | 
							local C = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e_ptr: jecs.Entity = (jecs.Rest :: any) + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(A, jecs.Exclusive)
 | 
				
			||||||
 | 
							local on_remove_call = false
 | 
				
			||||||
 | 
							world:set(A, jecs.OnRemove, function(e, id)
 | 
				
			||||||
 | 
								on_remove_call = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local on_add_call_count = 0
 | 
				
			||||||
 | 
							world:set(A, jecs.OnAdd, function(e, id)
 | 
				
			||||||
 | 
								on_add_call_count += 1
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e = world:entity()
 | 
				
			||||||
 | 
							CHECK(e == e_ptr)
 | 
				
			||||||
 | 
							world:set(e, pair(A, B))
 | 
				
			||||||
 | 
							CHECK(on_add_call_count == 1)
 | 
				
			||||||
 | 
							world:set(e, pair(A, C))
 | 
				
			||||||
 | 
							CHECK(on_add_call_count == 2)
 | 
				
			||||||
 | 
							CHECK(on_remove_call)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							-- We have to ensure that it actually invokes hooks everytime it
 | 
				
			||||||
 | 
							-- traverses the archetype
 | 
				
			||||||
 | 
							e = world:entity()
 | 
				
			||||||
 | 
							world:add(e, pair(A, B))
 | 
				
			||||||
 | 
							CHECK(on_add_call_count == 3)
 | 
				
			||||||
 | 
							world:add(e, pair(A, C))
 | 
				
			||||||
 | 
							CHECK(on_add_call_count == 4)
 | 
				
			||||||
 | 
							CHECK(on_remove_call)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do CASE "exclusive relations invoke on_remove hooks that should allow side effects"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local A = world:component()
 | 
				
			||||||
 | 
							local B = world:component()
 | 
				
			||||||
 | 
							local C = world:component()
 | 
				
			||||||
 | 
							local D = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(A, jecs.Exclusive)
 | 
				
			||||||
 | 
							local call_count = 0
 | 
				
			||||||
 | 
							world:set(A, jecs.OnRemove, function(e, id)
 | 
				
			||||||
 | 
								call_count += 1
 | 
				
			||||||
 | 
								if call_count == 1 then
 | 
				
			||||||
 | 
									world:set(e, C, true)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									world:set(e, D, true)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e = world:entity()
 | 
				
			||||||
 | 
							world:set(e, pair(A, B), true)
 | 
				
			||||||
 | 
							world:set(e, pair(A, C), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
							CHECK(world:has(e, C))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							-- We have to ensure that it actually invokes hooks everytime it
 | 
				
			||||||
 | 
							-- traverses the archetype
 | 
				
			||||||
 | 
							e = world:entity()
 | 
				
			||||||
 | 
							world:set(e, pair(A, B), true)
 | 
				
			||||||
 | 
							world:set(e, pair(A, C), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
							CHECK(world:has(e, D))
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
	do CASE "archetype move"
 | 
						do CASE "archetype move"
 | 
				
			||||||
		local world = jecs.world()
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "ukendio/jecs"
 | 
					name = "ukendio/jecs"
 | 
				
			||||||
version = "0.9.0-rc.9"
 | 
					version = "0.9.0-rc.10"
 | 
				
			||||||
registry = "https://github.com/UpliftGames/wally-index"
 | 
					registry = "https://github.com/UpliftGames/wally-index"
 | 
				
			||||||
realm = "shared"
 | 
					realm = "shared"
 | 
				
			||||||
license = "MIT"
 | 
					license = "MIT"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue