mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-02 18:19:18 +00:00 
			
		
		
		
	Fix cycles (#129)
This commit is contained in:
		
							parent
							
								
									2ed869ba93
								
							
						
					
					
						commit
						b3da0745bc
					
				
					 2 changed files with 126 additions and 129 deletions
				
			
		
							
								
								
									
										159
									
								
								src/init.luau
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								src/init.luau
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -992,98 +992,6 @@ do
 | 
			
		|||
    	else
 | 
			
		||||
    		archetype_fast_delete(columns, column_count, row, types, delete)
 | 
			
		||||
    	end
 | 
			
		||||
 | 
			
		||||
    	local component_index = world.componentIndex
 | 
			
		||||
 | 
			
		||||
    	local archetypes = world.archetypes
 | 
			
		||||
 | 
			
		||||
        local idr = component_index[delete]
 | 
			
		||||
       	if idr then
 | 
			
		||||
      		local children = {}
 | 
			
		||||
      		for archetype_id in idr.cache do
 | 
			
		||||
     			local idr_archetype = archetypes[archetype_id]
 | 
			
		||||
 | 
			
		||||
     			for i, child in idr_archetype.entities do
 | 
			
		||||
    				table.insert(children, child)
 | 
			
		||||
     			end
 | 
			
		||||
      		end
 | 
			
		||||
      		local flags = idr.flags
 | 
			
		||||
      		if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
 | 
			
		||||
     			for _, child in children do
 | 
			
		||||
    				-- Cascade deletion to children
 | 
			
		||||
    				world_delete(world, child)
 | 
			
		||||
     			end
 | 
			
		||||
      		else
 | 
			
		||||
     			for _, child in children do
 | 
			
		||||
    				world_remove(world, child, delete)
 | 
			
		||||
     			end
 | 
			
		||||
      		end
 | 
			
		||||
       	end
 | 
			
		||||
    	-- TODO: iterate each linked record.
 | 
			
		||||
    	-- local r = ECS_PAIR(delete, EcsWildcard)
 | 
			
		||||
    	-- local idr_r = component_index[r]
 | 
			
		||||
    	-- if idr_r then
 | 
			
		||||
    	--     -- Doesn't work for relations atm
 | 
			
		||||
    	--     for archetype_id in idr_o.cache do
 | 
			
		||||
    	--         local children = {}
 | 
			
		||||
    	--         local idr_r_archetype = archetypes[archetype_id]
 | 
			
		||||
    	--         local idr_r_types = idr_r_archetype.types
 | 
			
		||||
 | 
			
		||||
    	--         for _, child in idr_r_archetype.entities do
 | 
			
		||||
    	--             table.insert(children, child)
 | 
			
		||||
    	--         end
 | 
			
		||||
 | 
			
		||||
    	--         for _, id in idr_r_types do
 | 
			
		||||
    	--             local relation = ECS_ENTITY_T_HI(id)
 | 
			
		||||
    	--             if world_target(world, child, relation) == delete then
 | 
			
		||||
    	--                 world_remove(world, child, ECS_PAIR(relation, delete))
 | 
			
		||||
    	--             end
 | 
			
		||||
    	--         end
 | 
			
		||||
    	--     end
 | 
			
		||||
    	-- end
 | 
			
		||||
 | 
			
		||||
    	local o = ECS_PAIR(EcsWildcard, delete)
 | 
			
		||||
    	local idr_o = component_index[o]
 | 
			
		||||
 | 
			
		||||
    	if idr_o then
 | 
			
		||||
    		for archetype_id in idr_o.cache do
 | 
			
		||||
    			local children = {}
 | 
			
		||||
    			local idr_o_archetype = archetypes[archetype_id]
 | 
			
		||||
    			-- In the future, this needs to be optimized to only
 | 
			
		||||
    			-- look for linked records instead of doing this linearly
 | 
			
		||||
 | 
			
		||||
    			local idr_o_types = idr_o_archetype.types
 | 
			
		||||
 | 
			
		||||
    			for _, child in idr_o_archetype.entities do
 | 
			
		||||
    				table.insert(children, child)
 | 
			
		||||
    			end
 | 
			
		||||
 | 
			
		||||
    			for _, id in idr_o_types do
 | 
			
		||||
    				if not ECS_IS_PAIR(id) then
 | 
			
		||||
    					continue
 | 
			
		||||
    				end
 | 
			
		||||
 | 
			
		||||
    				local id_record = component_index[id]
 | 
			
		||||
 | 
			
		||||
    				if id_record then
 | 
			
		||||
    					local flags = id_record.flags
 | 
			
		||||
    					if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
 | 
			
		||||
    						for _, child in children do
 | 
			
		||||
    							-- Cascade deletions of it has Delete as component trait
 | 
			
		||||
    							world_delete(world, child, destruct)
 | 
			
		||||
    						end
 | 
			
		||||
    					else
 | 
			
		||||
    						local object = ECS_ENTITY_T_LO(id)
 | 
			
		||||
    						if object == delete then
 | 
			
		||||
    							for _, child in children do
 | 
			
		||||
    								world_remove(world, child, id)
 | 
			
		||||
    							end
 | 
			
		||||
    						end
 | 
			
		||||
    					end
 | 
			
		||||
    				end
 | 
			
		||||
    			end
 | 
			
		||||
    		end
 | 
			
		||||
    	end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    function world_delete(world: World, entity: i53, destruct: boolean?)
 | 
			
		||||
| 
						 | 
				
			
			@ -1103,6 +1011,73 @@ do
 | 
			
		|||
    		archetype_delete(world, archetype, row, destruct)
 | 
			
		||||
    	end
 | 
			
		||||
 | 
			
		||||
        local delete = entity
 | 
			
		||||
        local component_index = world.componentIndex
 | 
			
		||||
        local archetypes = world.archetypes
 | 
			
		||||
        local tgt = ECS_PAIR(EcsWildcard, delete)
 | 
			
		||||
       	local idr_t = component_index[tgt]
 | 
			
		||||
        local idr = component_index[delete]
 | 
			
		||||
 | 
			
		||||
        if idr then
 | 
			
		||||
            local children = {}
 | 
			
		||||
            for archetype_id in idr.cache do
 | 
			
		||||
                local idr_archetype = archetypes[archetype_id]
 | 
			
		||||
 | 
			
		||||
                for i, child in idr_archetype.entities do
 | 
			
		||||
               	    table.insert(children, child)
 | 
			
		||||
                end
 | 
			
		||||
            end
 | 
			
		||||
            local flags = idr.flags
 | 
			
		||||
            if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
 | 
			
		||||
                for _, child in children do
 | 
			
		||||
         			-- Cascade deletion to children
 | 
			
		||||
         			world_delete(world, child)
 | 
			
		||||
                end
 | 
			
		||||
            else
 | 
			
		||||
                for _, child in children do
 | 
			
		||||
                   	world_remove(world, child, delete)
 | 
			
		||||
                end
 | 
			
		||||
            end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        if idr_t then
 | 
			
		||||
            for archetype_id in idr_t.cache do
 | 
			
		||||
                local children = {}
 | 
			
		||||
                local idr_o_archetype = archetypes[archetype_id]
 | 
			
		||||
 | 
			
		||||
                local idr_o_types = idr_o_archetype.types
 | 
			
		||||
 | 
			
		||||
                for _, child in idr_o_archetype.entities do
 | 
			
		||||
                    table.insert(children, child)
 | 
			
		||||
                end
 | 
			
		||||
 | 
			
		||||
                for _, id in idr_o_types do
 | 
			
		||||
         			if not ECS_IS_PAIR(id) then
 | 
			
		||||
                        continue
 | 
			
		||||
         			end
 | 
			
		||||
 | 
			
		||||
         			local id_record = component_index[id]
 | 
			
		||||
 | 
			
		||||
         			if id_record then
 | 
			
		||||
        				local flags = id_record.flags
 | 
			
		||||
        				if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
 | 
			
		||||
            				for _, child in children do
 | 
			
		||||
                				-- Cascade deletions of it has Delete as component trait
 | 
			
		||||
                				world_delete(world, child, destruct)
 | 
			
		||||
            				end
 | 
			
		||||
        				else
 | 
			
		||||
            				local object = ECS_ENTITY_T_LO(id)
 | 
			
		||||
            				if object == delete then
 | 
			
		||||
            				    for _, child in children do
 | 
			
		||||
                    				world_remove(world, child, id)
 | 
			
		||||
            				    end
 | 
			
		||||
            				end
 | 
			
		||||
        				end
 | 
			
		||||
         			end
 | 
			
		||||
          		end
 | 
			
		||||
           	end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
    	record.archetype = nil :: any
 | 
			
		||||
    	entityIndex.sparse[entity] = nil
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,10 @@ local function debug_world_inspect(world)
 | 
			
		|||
    }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
local function name(world, e)
 | 
			
		||||
    return world:get(e, jecs.Name)
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
TEST("world:entity()", function()
 | 
			
		||||
    do CASE "unique IDs"
 | 
			
		||||
        local world = jecs.World.new()
 | 
			
		||||
| 
						 | 
				
			
			@ -254,7 +258,6 @@ TEST("world:query()", function()
 | 
			
		|||
        for x in q:iter() do
 | 
			
		||||
            counter += 1
 | 
			
		||||
        end
 | 
			
		||||
        print(counter)
 | 
			
		||||
        CHECK(counter == 2)
 | 
			
		||||
    end
 | 
			
		||||
    do CASE "tag"
 | 
			
		||||
| 
						 | 
				
			
			@ -764,32 +767,32 @@ TEST("world:clear()", function()
 | 
			
		|||
end)
 | 
			
		||||
 | 
			
		||||
TEST("world:has()", function()
 | 
			
		||||
do CASE "should find Tag on entity"
 | 
			
		||||
    local world = jecs.World.new()
 | 
			
		||||
    do CASE "should find Tag on entity"
 | 
			
		||||
        local world = jecs.World.new()
 | 
			
		||||
 | 
			
		||||
    local Tag = world:entity()
 | 
			
		||||
        local Tag = world:entity()
 | 
			
		||||
 | 
			
		||||
    local e = world:entity()
 | 
			
		||||
    world:add(e, Tag)
 | 
			
		||||
        local e = world:entity()
 | 
			
		||||
        world:add(e, Tag)
 | 
			
		||||
 | 
			
		||||
    CHECK(world:has(e, Tag))
 | 
			
		||||
end
 | 
			
		||||
        CHECK(world:has(e, Tag))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
do CASE "should return false when missing one tag"
 | 
			
		||||
    local world = jecs.World.new()
 | 
			
		||||
    do CASE "should return false when missing one tag"
 | 
			
		||||
        local world = jecs.World.new()
 | 
			
		||||
 | 
			
		||||
    local A = world:entity()
 | 
			
		||||
    local B = world:entity()
 | 
			
		||||
    local C = world:entity()
 | 
			
		||||
    local D = world:entity()
 | 
			
		||||
        local A = world:entity()
 | 
			
		||||
        local B = world:entity()
 | 
			
		||||
        local C = world:entity()
 | 
			
		||||
        local D = world:entity()
 | 
			
		||||
 | 
			
		||||
    local e = world:entity()
 | 
			
		||||
    world:add(e, A)
 | 
			
		||||
    world:add(e, C)
 | 
			
		||||
    world:add(e, D)
 | 
			
		||||
        local e = world:entity()
 | 
			
		||||
        world:add(e, A)
 | 
			
		||||
        world:add(e, C)
 | 
			
		||||
        world:add(e, D)
 | 
			
		||||
 | 
			
		||||
    CHECK(world:has(e, A, B, C, D) == false)
 | 
			
		||||
end
 | 
			
		||||
        CHECK(world:has(e, A, B, C, D) == false)
 | 
			
		||||
    end
 | 
			
		||||
end)
 | 
			
		||||
 | 
			
		||||
TEST("world:component()", function()
 | 
			
		||||
| 
						 | 
				
			
			@ -820,27 +823,46 @@ TEST("world:component()", function()
 | 
			
		|||
end)
 | 
			
		||||
 | 
			
		||||
TEST("world:delete", function()
 | 
			
		||||
    do CASE "bug: Empty entity does not respect cleanup policy"
 | 
			
		||||
        local world = world_new()
 | 
			
		||||
        local parent = world:entity()
 | 
			
		||||
        local tag = world:entity()
 | 
			
		||||
 | 
			
		||||
        local child = world:entity()
 | 
			
		||||
        world:add(child, jecs.pair(jecs.ChildOf, parent))
 | 
			
		||||
        world:delete(parent)
 | 
			
		||||
 | 
			
		||||
        CHECK(not world:contains(parent))
 | 
			
		||||
        CHECK(not world:contains(child))
 | 
			
		||||
 | 
			
		||||
        local entity = world:entity()
 | 
			
		||||
        world:add(entity, tag)
 | 
			
		||||
        world:delete(tag)
 | 
			
		||||
        CHECK(world:contains(entity))
 | 
			
		||||
        CHECK(not world:contains(tag))
 | 
			
		||||
        CHECK(not world:has(entity, tag)) -- => true
 | 
			
		||||
    end
 | 
			
		||||
    do CASE("should allow deleting components")
 | 
			
		||||
		local world = jecs.World.new()
 | 
			
		||||
        local world = jecs.World.new()
 | 
			
		||||
 | 
			
		||||
		local Health = world:component()
 | 
			
		||||
		local Poison = world:component()
 | 
			
		||||
        local Health = world:component()
 | 
			
		||||
        local Poison = world:component()
 | 
			
		||||
 | 
			
		||||
		local id = world:entity()
 | 
			
		||||
		world:set(id, Poison, 5)
 | 
			
		||||
		world:set(id, Health, 50)
 | 
			
		||||
		local id1 = world:entity()
 | 
			
		||||
		world:set(id1, Poison, 500)
 | 
			
		||||
		world:set(id1, Health, 50)
 | 
			
		||||
        local id = world:entity()
 | 
			
		||||
        world:set(id, Poison, 5)
 | 
			
		||||
        world:set(id, Health, 50)
 | 
			
		||||
        local id1 = world:entity()
 | 
			
		||||
        world:set(id1, Poison, 500)
 | 
			
		||||
        world:set(id1, Health, 50)
 | 
			
		||||
 | 
			
		||||
		world:delete(id)
 | 
			
		||||
		CHECK(not world:contains(id))
 | 
			
		||||
		CHECK(world:get(id, Poison) == nil)
 | 
			
		||||
		CHECK(world:get(id, Health) == nil)
 | 
			
		||||
        world:delete(id)
 | 
			
		||||
        CHECK(not world:contains(id))
 | 
			
		||||
        CHECK(world:get(id, Poison) == nil)
 | 
			
		||||
        CHECK(world:get(id, Health) == nil)
 | 
			
		||||
 | 
			
		||||
		CHECK(world:get(id1, Poison) == 500)
 | 
			
		||||
		CHECK(world:get(id1, Health) == 50)
 | 
			
		||||
	end
 | 
			
		||||
        CHECK(world:get(id1, Poison) == 500)
 | 
			
		||||
        CHECK(world:get(id1, Health) == 50)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
	do CASE "delete entities using another Entity as component with Delete cleanup action"
 | 
			
		||||
        local world = jecs.World.new()
 | 
			
		||||
| 
						 | 
				
			
			@ -918,7 +940,7 @@ TEST("world:delete", function()
 | 
			
		|||
        end)
 | 
			
		||||
 | 
			
		||||
        for i, friend in friends do
 | 
			
		||||
  		    CHECK(not world:has(friends, pair(jecs.ChildOf, e)))
 | 
			
		||||
  		    CHECK(not world:has(friend, pair(FriendsWith, e)))
 | 
			
		||||
 			CHECK(world:has(friend, Health))
 | 
			
		||||
  		end
 | 
			
		||||
    end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue