mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-04 10:59:18 +00:00 
			
		
		
		
	Add exclusive relations (#250)
* Add exclusive relationship * Remove focus * Remove whitespace * Make ChildOf exclusive * Test exclusive relation perf * Inline into world:add * Inline into world:set * Fix benchmark of remove
This commit is contained in:
		
							parent
							
								
									b425150b0c
								
							
						
					
					
						commit
						155d51a080
					
				
					 4 changed files with 307 additions and 70 deletions
				
			
		| 
						 | 
					@ -4,15 +4,17 @@
 | 
				
			||||||
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
					local ReplicatedStorage = game:GetService("ReplicatedStorage")
 | 
				
			||||||
local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
					local Matter = require(ReplicatedStorage.DevPackages.Matter)
 | 
				
			||||||
local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
					local ecr = require(ReplicatedStorage.DevPackages.ecr)
 | 
				
			||||||
local jecs = require(ReplicatedStorage.Lib)
 | 
					local jecs = require(ReplicatedStorage.Lib:Clone())
 | 
				
			||||||
local pair = jecs.pair
 | 
					local pair = jecs.pair
 | 
				
			||||||
local ecs = jecs.world()
 | 
					local ecs = jecs.world()
 | 
				
			||||||
local mirror = require(ReplicatedStorage.mirror)
 | 
					local mirror = require(ReplicatedStorage.mirror:Clone())
 | 
				
			||||||
local mcs = mirror.world()
 | 
					local mcs = mirror.world()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local C1 = ecs:component()
 | 
					local C1 = ecs:component()
 | 
				
			||||||
local C2 = ecs:entity()
 | 
					local C2 = ecs:entity()
 | 
				
			||||||
ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					ecs:add(C2, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
 | 
					ecs:add(C2, jecs.Exclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local C3 = ecs:entity()
 | 
					local C3 = ecs:entity()
 | 
				
			||||||
ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
					ecs:add(C3, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
local C4 = ecs:entity()
 | 
					local C4 = ecs:entity()
 | 
				
			||||||
| 
						 | 
					@ -32,17 +34,18 @@ return {
 | 
				
			||||||
	Functions = {
 | 
						Functions = {
 | 
				
			||||||
		Mirror = function()
 | 
							Mirror = function()
 | 
				
			||||||
			local m = mcs:entity()
 | 
								local m = mcs:entity()
 | 
				
			||||||
			for i = 1, 1000 do
 | 
								for i = 1, 100 do
 | 
				
			||||||
				mcs:add(m, E3)
 | 
									mcs:add(m, pair(E2, E3))
 | 
				
			||||||
				mcs:remove(m, E3)
 | 
									mcs:remove(m, pair(E2, E3))
 | 
				
			||||||
 | 
									mcs:add(m, pair(E2, E4))
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end,
 | 
							end,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Jecs = function()
 | 
							Jecs = function()
 | 
				
			||||||
			local j = ecs:entity()
 | 
								local j = ecs:entity()
 | 
				
			||||||
			for i = 1, 1000 do
 | 
								for i = 1, 100 do
 | 
				
			||||||
				ecs:add(j, C3)
 | 
									ecs:add(j, pair(C2, C3))
 | 
				
			||||||
				ecs:remove(j, C3)
 | 
									ecs:add(j, pair(C2, C4))
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end,
 | 
							end,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										167
									
								
								jecs.luau
									
									
									
									
									
								
							
							
						
						
									
										167
									
								
								jecs.luau
									
									
									
									
									
								
							| 
						 | 
					@ -196,9 +196,10 @@ local ECS_ENTITY_MASK =       bit32.lshift(1, 24)
 | 
				
			||||||
local ECS_GENERATION_MASK =   bit32.lshift(1, 16)
 | 
					local ECS_GENERATION_MASK =   bit32.lshift(1, 16)
 | 
				
			||||||
local ECS_PAIR_OFFSET = 	                 2^48
 | 
					local ECS_PAIR_OFFSET = 	                 2^48
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local ECS_ID_DELETE =                 	     0b01
 | 
					local ECS_ID_DELETE =                 	     0b0001
 | 
				
			||||||
local ECS_ID_IS_TAG =                 	     0b10
 | 
					local ECS_ID_IS_TAG =                 	     0b0010
 | 
				
			||||||
local ECS_ID_MASK =                   	     0b00
 | 
					local ECS_ID_IS_EXCLUSIVE =                  0b0100
 | 
				
			||||||
 | 
					local ECS_ID_MASK =                   	     0b0000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local HI_COMPONENT_ID =                       256
 | 
					local HI_COMPONENT_ID =                       256
 | 
				
			||||||
local EcsOnAdd =             HI_COMPONENT_ID +  1
 | 
					local EcsOnAdd =             HI_COMPONENT_ID +  1
 | 
				
			||||||
| 
						 | 
					@ -214,7 +215,8 @@ local EcsRemove =            HI_COMPONENT_ID + 10
 | 
				
			||||||
local EcsName =              HI_COMPONENT_ID + 11
 | 
					local EcsName =              HI_COMPONENT_ID + 11
 | 
				
			||||||
local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
 | 
					local EcsOnArchetypeCreate = HI_COMPONENT_ID + 12
 | 
				
			||||||
local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
 | 
					local EcsOnArchetypeDelete = HI_COMPONENT_ID + 13
 | 
				
			||||||
local EcsRest =              HI_COMPONENT_ID + 14
 | 
					local EcsExclusive =         HI_COMPONENT_ID + 14
 | 
				
			||||||
 | 
					local EcsRest =              HI_COMPONENT_ID + 15
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local NULL_ARRAY = table.freeze({}) :: Column
 | 
					local NULL_ARRAY = table.freeze({}) :: Column
 | 
				
			||||||
local NULL = newproxy(false)
 | 
					local NULL = newproxy(false)
 | 
				
			||||||
| 
						 | 
					@ -319,9 +321,9 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function entity_index_try_get_any(
 | 
					local function entity_index_try_get_any(
 | 
				
			||||||
	entity_index: EntityIndex,
 | 
						entity_index: EntityIndex,
 | 
				
			||||||
	entity: number
 | 
						entity: Entity
 | 
				
			||||||
): Record?
 | 
					): Record?
 | 
				
			||||||
	local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity)]
 | 
						local r = entity_index.sparse_array[ECS_ENTITY_T_LO(entity::number)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if not r or r.dense == 0 then
 | 
						if not r or r.dense == 0 then
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
| 
						 | 
					@ -344,6 +346,20 @@ local function entity_index_try_get(entity_index: EntityIndex, entity: Entity):
 | 
				
			||||||
	return r
 | 
						return r
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					local function entity_index_try_get_fast(entity_index: EntityIndex, entity: Entity): Record?
 | 
				
			||||||
 | 
						local r = entity_index_try_get_any(entity_index, entity)
 | 
				
			||||||
 | 
						if r then
 | 
				
			||||||
 | 
							local r_dense = r.dense
 | 
				
			||||||
 | 
							-- if r_dense > entity_index.alive_count then
 | 
				
			||||||
 | 
							-- 	return nil
 | 
				
			||||||
 | 
							-- end
 | 
				
			||||||
 | 
							if entity_index.dense_array[r_dense] ~= entity then
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
						return r
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function entity_index_is_alive<T>(entity_index: EntityIndex, entity: Entity<T>): boolean
 | 
					local function entity_index_is_alive<T>(entity_index: EntityIndex, entity: Entity<T>): boolean
 | 
				
			||||||
	return entity_index_try_get(entity_index, entity) ~= nil
 | 
						return entity_index_try_get(entity_index, entity) ~= nil
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -683,6 +699,7 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
 | 
				
			||||||
	local is_pair = ECS_IS_PAIR(id :: number)
 | 
						local is_pair = ECS_IS_PAIR(id :: number)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local has_delete = false
 | 
						local has_delete = false
 | 
				
			||||||
 | 
						local is_exclusive = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if is_pair then
 | 
						if is_pair then
 | 
				
			||||||
		relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id :: number)) :: i53
 | 
							relation = entity_index_get_alive(entity_index, ECS_PAIR_FIRST(id :: number)) :: i53
 | 
				
			||||||
| 
						 | 
					@ -697,6 +714,10 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
 | 
				
			||||||
		if cleanup_policy_target == EcsDelete then
 | 
							if cleanup_policy_target == EcsDelete then
 | 
				
			||||||
			has_delete = true
 | 
								has_delete = true
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if world_has_one_inline(world, relation, EcsExclusive) then
 | 
				
			||||||
 | 
								is_exclusive = true
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
		local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
 | 
							local cleanup_policy = world_target(world, relation, EcsOnDelete, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -718,7 +739,8 @@ local function id_record_ensure(world: World, id: Entity): ComponentRecord
 | 
				
			||||||
	flags = bit32.bor(
 | 
						flags = bit32.bor(
 | 
				
			||||||
		flags,
 | 
							flags,
 | 
				
			||||||
		if has_delete then ECS_ID_DELETE else 0,
 | 
							if has_delete then ECS_ID_DELETE else 0,
 | 
				
			||||||
		if is_tag then ECS_ID_IS_TAG else 0
 | 
							if is_tag then ECS_ID_IS_TAG else 0,
 | 
				
			||||||
 | 
							if is_exclusive then ECS_ID_IS_EXCLUSIVE else 0
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	idr = {
 | 
						idr = {
 | 
				
			||||||
| 
						 | 
					@ -929,9 +951,10 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function find_archetype_with(world: World, id: Id, from: Archetype): Archetype
 | 
					local function find_archetype_with(world: World, id: Id, from: Archetype): Archetype
 | 
				
			||||||
	local id_types = from.types
 | 
						local id_types = from.types
 | 
				
			||||||
 | 
						local dst = table.clone(id_types)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local at = find_insert(id_types :: { number } , id :: number)
 | 
						local at = find_insert(id_types :: { number } , id :: number)
 | 
				
			||||||
	local dst = table.clone(id_types)
 | 
					
 | 
				
			||||||
	table.insert(dst, at, id)
 | 
						table.insert(dst, at, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return archetype_ensure(world, dst)
 | 
						return archetype_ensure(world, dst)
 | 
				
			||||||
| 
						 | 
					@ -967,8 +990,6 @@ local function world_component(world: World): i53
 | 
				
			||||||
	return id
 | 
						return id
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
local function archetype_fast_delete_last(columns: { Column }, column_count: number)
 | 
					local function archetype_fast_delete_last(columns: { Column }, column_count: number)
 | 
				
			||||||
	for i, column in columns do
 | 
						for i, column in columns do
 | 
				
			||||||
		if column ~= NULL_ARRAY then
 | 
							if column ~= NULL_ARRAY then
 | 
				
			||||||
| 
						 | 
					@ -2207,6 +2228,59 @@ local function world_new()
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local from = record.archetype
 | 
							local from = record.archetype
 | 
				
			||||||
 | 
							if ECS_IS_PAIR(id::number) then
 | 
				
			||||||
 | 
								local src = from or ROOT_ARCHETYPE
 | 
				
			||||||
 | 
								local edge = archetype_edges[src.id]
 | 
				
			||||||
 | 
								local to = edge[id]
 | 
				
			||||||
 | 
								local idr: ComponentRecord
 | 
				
			||||||
 | 
								if not to then
 | 
				
			||||||
 | 
									local first = ECS_PAIR_FIRST(id::number)
 | 
				
			||||||
 | 
									local wc = ECS_PAIR(first, EcsWildcard)
 | 
				
			||||||
 | 
									idr = component_index[wc]
 | 
				
			||||||
 | 
									if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
				
			||||||
 | 
										local cr = idr.records[src.id]
 | 
				
			||||||
 | 
										if cr then
 | 
				
			||||||
 | 
											local on_remove = idr.hooks.on_remove
 | 
				
			||||||
 | 
											local id_types = src.types
 | 
				
			||||||
 | 
											if on_remove then
 | 
				
			||||||
 | 
												on_remove(entity, id_types[cr])
 | 
				
			||||||
 | 
												src = record.archetype
 | 
				
			||||||
 | 
												id_types = src.types
 | 
				
			||||||
 | 
												cr = idr.records[src.id]
 | 
				
			||||||
 | 
											end
 | 
				
			||||||
 | 
											local dst = table.clone(id_types)
 | 
				
			||||||
 | 
											dst[cr] = id
 | 
				
			||||||
 | 
											to = archetype_ensure(world, dst)
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
											to = find_archetype_with(world, id, src)
 | 
				
			||||||
 | 
											idr = component_index[id]
 | 
				
			||||||
 | 
										end
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
										to = find_archetype_with(world, id, src)
 | 
				
			||||||
 | 
										idr = component_index[id]
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
									edge[id] = to
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									idr = component_index[id]
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								if from == to then
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								if from then
 | 
				
			||||||
 | 
									entity_move(entity_index, entity, record, to)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									if #to.types > 0 then
 | 
				
			||||||
 | 
										new_entity(entity, record, to)
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								local on_add = idr.hooks.on_add
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if on_add then
 | 
				
			||||||
 | 
									on_add(entity, id)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
		local to = archetype_traverse_add(world, id, from)
 | 
							local to = archetype_traverse_add(world, id, from)
 | 
				
			||||||
		if from == to then
 | 
							if from == to then
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
| 
						 | 
					@ -2219,7 +2293,7 @@ local function world_new()
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local idr = world.component_index[id]
 | 
							local idr = component_index[id]
 | 
				
			||||||
		local on_add = idr.hooks.on_add
 | 
							local on_add = idr.hooks.on_add
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if on_add then
 | 
							if on_add then
 | 
				
			||||||
| 
						 | 
					@ -2348,6 +2422,74 @@ local function world_new()
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local from: Archetype = record.archetype
 | 
							local from: Archetype = record.archetype
 | 
				
			||||||
 | 
							if ECS_IS_PAIR(id::number) then
 | 
				
			||||||
 | 
								local src = from or ROOT_ARCHETYPE
 | 
				
			||||||
 | 
								local edge = archetype_edges[src.id]
 | 
				
			||||||
 | 
								local to = edge[id]
 | 
				
			||||||
 | 
								local idr: ComponentRecord
 | 
				
			||||||
 | 
								if not to then
 | 
				
			||||||
 | 
									local first = ECS_PAIR_FIRST(id::number)
 | 
				
			||||||
 | 
									local wc = ECS_PAIR(first, EcsWildcard)
 | 
				
			||||||
 | 
									idr = component_index[wc]
 | 
				
			||||||
 | 
									if idr and bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) then
 | 
				
			||||||
 | 
										local cr = idr.records[src.id]
 | 
				
			||||||
 | 
										if cr then
 | 
				
			||||||
 | 
											local on_remove = idr.hooks.on_remove
 | 
				
			||||||
 | 
											local id_types = src.types
 | 
				
			||||||
 | 
											if on_remove then
 | 
				
			||||||
 | 
												on_remove(entity, id_types[cr])
 | 
				
			||||||
 | 
												src = record.archetype
 | 
				
			||||||
 | 
												id_types = src.types
 | 
				
			||||||
 | 
												cr = idr.records[src.id]
 | 
				
			||||||
 | 
											end
 | 
				
			||||||
 | 
											local dst = table.clone(id_types)
 | 
				
			||||||
 | 
											dst[cr] = id
 | 
				
			||||||
 | 
											to = archetype_ensure(world, dst)
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
											to = find_archetype_with(world, id, src)
 | 
				
			||||||
 | 
											idr = component_index[id]
 | 
				
			||||||
 | 
										end
 | 
				
			||||||
 | 
									else
 | 
				
			||||||
 | 
										to = find_archetype_with(world, id, src)
 | 
				
			||||||
 | 
										idr = component_index[id]
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
									edge[id] = to
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									idr = component_index[id]
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								local idr_hooks = idr.hooks
 | 
				
			||||||
 | 
								if from == to then
 | 
				
			||||||
 | 
									local column = to.columns_map[id]
 | 
				
			||||||
 | 
									column[record.row] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									-- If the archetypes are the same it can avoid moving the entity
 | 
				
			||||||
 | 
									-- and just set the data directly.
 | 
				
			||||||
 | 
									local on_change = idr_hooks.on_change
 | 
				
			||||||
 | 
									if on_change then
 | 
				
			||||||
 | 
										on_change(entity, id, data)
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if from then
 | 
				
			||||||
 | 
									entity_move(entity_index, entity, record, to)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									if #to.types > 0 then
 | 
				
			||||||
 | 
										new_entity(entity, record, to)
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								local column = to.columns_map[id]
 | 
				
			||||||
 | 
								column[record.row] = data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								local on_add = idr.hooks.on_add
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if on_add then
 | 
				
			||||||
 | 
									on_add(entity, id)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
		local to: Archetype = inner_archetype_traverse_add(id, from)
 | 
							local to: Archetype = inner_archetype_traverse_add(id, from)
 | 
				
			||||||
		local idr = component_index[id]
 | 
							local idr = component_index[id]
 | 
				
			||||||
		local idr_hooks = idr.hooks
 | 
							local idr_hooks = idr.hooks
 | 
				
			||||||
| 
						 | 
					@ -2563,7 +2705,6 @@ local function world_new()
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local function inner_world_delete<T>(world: World, entity: Entity<T>)
 | 
						local function inner_world_delete<T>(world: World, entity: Entity<T>)
 | 
				
			||||||
| 
						 | 
					@ -2850,6 +2991,7 @@ local function world_new()
 | 
				
			||||||
	inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
 | 
						inner_world_set(world, EcsRest, EcsRest, "jecs.Rest")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
 | 
						inner_world_add(world, EcsChildOf, ECS_PAIR(EcsOnDeleteTarget, EcsDelete))
 | 
				
			||||||
 | 
						inner_world_add(world, EcsChildOf, EcsExclusive)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i = EcsRest + 1, ecs_max_tag_id do
 | 
						for i = EcsRest + 1, ecs_max_tag_id do
 | 
				
			||||||
		entity_index_new_id(entity_index)
 | 
							entity_index_new_id(entity_index)
 | 
				
			||||||
| 
						 | 
					@ -2913,6 +3055,7 @@ return {
 | 
				
			||||||
	Delete = (EcsDelete :: any) :: Entity,
 | 
						Delete = (EcsDelete :: any) :: Entity,
 | 
				
			||||||
	Remove = (EcsRemove :: any) :: Entity,
 | 
						Remove = (EcsRemove :: any) :: Entity,
 | 
				
			||||||
	Name = (EcsName :: any) :: Entity<string>,
 | 
						Name = (EcsName :: any) :: Entity<string>,
 | 
				
			||||||
 | 
						Exclusive = EcsExclusive :: Entity,
 | 
				
			||||||
	Rest = (EcsRest :: any) :: Entity,
 | 
						Rest = (EcsRest :: any) :: Entity,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
 | 
						pair = (ECS_PAIR :: any) :: <P, O>(first: Id<P>, second: Id<O>) -> Pair<P, O>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										109
									
								
								mirror.luau
									
									
									
									
									
								
							
							
						
						
									
										109
									
								
								mirror.luau
									
									
									
									
									
								
							| 
						 | 
					@ -42,8 +42,18 @@ export type Iter<T...> = (query: Query<T...>) -> () -> (Entity, T...)
 | 
				
			||||||
export type Query<T...> = typeof(setmetatable(
 | 
					export type Query<T...> = typeof(setmetatable(
 | 
				
			||||||
	{} :: {
 | 
						{} :: {
 | 
				
			||||||
		iter: Iter<T...>,
 | 
							iter: Iter<T...>,
 | 
				
			||||||
		with: (self: Query<T...>, ...Id) -> Query<T...>,
 | 
							with:
 | 
				
			||||||
		without: (self: Query<T...>, ...Id) -> Query<T...>,
 | 
								(<a>(Query<T...>, Id<a>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
 | 
				
			||||||
 | 
							without:
 | 
				
			||||||
 | 
								(<a>(Query<T...>, Id<a>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b>(Query<T...>, Id<a>, Id<b>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c>(Query<T...>, Id<a>, Id<b>, Id<c>) -> Query<T...>)
 | 
				
			||||||
 | 
								& (<a, b, c, d>(Query<T...>, Id<a>, Id<b>, Id<c>, Id) -> Query<T...>),
 | 
				
			||||||
		archetypes: (self: Query<T...>) -> { Archetype },
 | 
							archetypes: (self: Query<T...>) -> { Archetype },
 | 
				
			||||||
		cached: (self: Query<T...>) -> Query<T...>,
 | 
							cached: (self: Query<T...>) -> Query<T...>,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
| 
						 | 
					@ -439,6 +449,7 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function archetype_move(
 | 
					local function archetype_move(
 | 
				
			||||||
	entity_index: EntityIndex,
 | 
						entity_index: EntityIndex,
 | 
				
			||||||
 | 
						entity: Entity,
 | 
				
			||||||
	to: Archetype,
 | 
						to: Archetype,
 | 
				
			||||||
	dst_row: i24,
 | 
						dst_row: i24,
 | 
				
			||||||
	from: Archetype,
 | 
						from: Archetype,
 | 
				
			||||||
| 
						 | 
					@ -452,48 +463,58 @@ local function archetype_move(
 | 
				
			||||||
	local id_types = from.types
 | 
						local id_types = from.types
 | 
				
			||||||
	local columns_map = to.columns_map
 | 
						local columns_map = to.columns_map
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, column in src_columns do
 | 
						if src_row ~= last then
 | 
				
			||||||
		if column == NULL_ARRAY then
 | 
					 | 
				
			||||||
			continue
 | 
					 | 
				
			||||||
		end
 | 
					 | 
				
			||||||
		-- 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 dst_column = columns_map[id_types[i]]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		-- Sometimes target column may not exist, e.g. when you remove a component.
 | 
					 | 
				
			||||||
		if dst_column then
 | 
					 | 
				
			||||||
			dst_column[dst_row] = column[src_row]
 | 
					 | 
				
			||||||
		end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		-- If the entity is the last row in the archetype then swapping it would be meaningless.
 | 
							-- If the entity is the last row in the archetype then swapping it would be meaningless.
 | 
				
			||||||
		if src_row ~= last then
 | 
					
 | 
				
			||||||
 | 
							for i, column in src_columns do
 | 
				
			||||||
 | 
								if column == NULL_ARRAY then
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								-- 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 dst_column = columns_map[id_types[i]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								-- Sometimes target column may not exist, e.g. when you remove a component.
 | 
				
			||||||
 | 
								if dst_column then
 | 
				
			||||||
 | 
									dst_column[dst_row] = column[src_row]
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			-- Swap rempves columns to ensure there are no holes in the archetype.
 | 
								-- Swap rempves columns to ensure there are no holes in the archetype.
 | 
				
			||||||
			column[src_row] = column[last]
 | 
								column[src_row] = column[last]
 | 
				
			||||||
 | 
								column[last] = nil
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		column[last] = nil
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local moved = #src_entities
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	-- Move the entity from the source to the destination archetype.
 | 
							-- Move the entity from the source to the destination archetype.
 | 
				
			||||||
	-- 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 e1 = src_entities[src_row]
 | 
					 | 
				
			||||||
	local e2 = src_entities[moved]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if src_row ~= moved then
 | 
							local e2 = src_entities[last]
 | 
				
			||||||
		src_entities[src_row] = e2
 | 
							src_entities[src_row] = e2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local sparse_array = entity_index.sparse_array
 | 
				
			||||||
 | 
							local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)]
 | 
				
			||||||
 | 
							record2.row = src_row
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
							for i, column in src_columns do
 | 
				
			||||||
 | 
								if column == NULL_ARRAY then
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								-- 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 dst_column = columns_map[id_types[i]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								-- Sometimes target column may not exist, e.g. when you remove a component.
 | 
				
			||||||
 | 
								if dst_column then
 | 
				
			||||||
 | 
									dst_column[dst_row] = column[src_row]
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								column[last] = nil
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	src_entities[moved] = nil :: any
 | 
						src_entities[last] = nil :: any
 | 
				
			||||||
	dst_entities[dst_row] = e1
 | 
						dst_entities[dst_row] = entity
 | 
				
			||||||
 | 
					 | 
				
			||||||
	local sparse_array = entity_index.sparse_array
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	local record1 = sparse_array[ECS_ENTITY_T_LO(e1 :: number)]
 | 
					 | 
				
			||||||
	local record2 = sparse_array[ECS_ENTITY_T_LO(e2 :: number)]
 | 
					 | 
				
			||||||
	record1.row = dst_row
 | 
					 | 
				
			||||||
	record2.row = src_row
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function archetype_append(
 | 
					local function archetype_append(
 | 
				
			||||||
| 
						 | 
					@ -526,7 +547,7 @@ local function entity_move(
 | 
				
			||||||
	local sourceRow = record.row
 | 
						local sourceRow = record.row
 | 
				
			||||||
	local from = record.archetype
 | 
						local from = record.archetype
 | 
				
			||||||
	local dst_row = archetype_append(entity, to)
 | 
						local dst_row = archetype_append(entity, to)
 | 
				
			||||||
	archetype_move(entity_index, to, dst_row, from, sourceRow)
 | 
						archetype_move(entity_index, entity, to, dst_row, from, sourceRow)
 | 
				
			||||||
	record.archetype = to
 | 
						record.archetype = to
 | 
				
			||||||
	record.row = dst_row
 | 
						record.row = dst_row
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -744,7 +765,7 @@ local function archetype_register(world: World, archetype: Archetype)
 | 
				
			||||||
	local columns = archetype.columns
 | 
						local columns = archetype.columns
 | 
				
			||||||
	for i, component_id in archetype.types do
 | 
						for i, component_id in archetype.types do
 | 
				
			||||||
		local idr = id_record_ensure(world, component_id)
 | 
							local idr = id_record_ensure(world, component_id)
 | 
				
			||||||
		local is_tag = bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
 | 
							local is_tag = bit32.btest(idr.flags, ECS_ID_IS_TAG)
 | 
				
			||||||
		local column = if is_tag then NULL_ARRAY else {}
 | 
							local column = if is_tag then NULL_ARRAY else {}
 | 
				
			||||||
		columns[i] = column
 | 
							columns[i] = column
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2394,7 +2415,7 @@ local function world_new()
 | 
				
			||||||
				return entity
 | 
									return entity
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				for i = eindex_max_id + 1, index do
 | 
									for i = eindex_max_id + 1, index do
 | 
				
			||||||
					eindex_sparse_array[i]	= { dense = i } :: Record
 | 
										eindex_sparse_array[i]= { dense = i } :: Record
 | 
				
			||||||
					eindex_dense_array[i] = i
 | 
										eindex_dense_array[i] = i
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
				entity_index.max_id = index
 | 
									entity_index.max_id = index
 | 
				
			||||||
| 
						 | 
					@ -2459,8 +2480,8 @@ local function world_new()
 | 
				
			||||||
				local idr_archetype = archetypes[archetype_id]
 | 
									local idr_archetype = archetypes[archetype_id]
 | 
				
			||||||
				local entities = idr_archetype.entities
 | 
									local entities = idr_archetype.entities
 | 
				
			||||||
				local n = #entities
 | 
									local n = #entities
 | 
				
			||||||
 | 
									table.move(entities, 1, n, count + 1, queue)
 | 
				
			||||||
				count += n
 | 
									count += n
 | 
				
			||||||
				table.move(entities, 1, n, #queue + 1, queue)
 | 
					 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
			for _, e in queue do
 | 
								for _, e in queue do
 | 
				
			||||||
				inner_world_remove(world, e, entity)
 | 
									inner_world_remove(world, e, entity)
 | 
				
			||||||
| 
						 | 
					@ -2572,7 +2593,7 @@ local function world_new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if idr then
 | 
							if idr then
 | 
				
			||||||
			local flags = idr.flags
 | 
								local flags = idr.flags
 | 
				
			||||||
			if bit32.band(flags, ECS_ID_DELETE) ~= 0 then
 | 
								if bit32.btest(flags, ECS_ID_DELETE) then
 | 
				
			||||||
				for archetype_id in idr.records do
 | 
									for archetype_id in idr.records do
 | 
				
			||||||
					local idr_archetype = archetypes[archetype_id]
 | 
										local idr_archetype = archetypes[archetype_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2647,8 +2668,8 @@ local function world_new()
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
					local id_record = component_index[id]
 | 
										local id_record = component_index[id]
 | 
				
			||||||
					local flags = id_record.flags
 | 
										local flags = id_record.flags
 | 
				
			||||||
					local flags_delete_mask: number = bit32.band(flags, ECS_ID_DELETE)
 | 
										local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
 | 
				
			||||||
					if flags_delete_mask ~= 0 then
 | 
										if flags_delete_mask then
 | 
				
			||||||
						for i = #entities, 1, -1 do
 | 
											for i = #entities, 1, -1 do
 | 
				
			||||||
							local child = entities[i]
 | 
												local child = entities[i]
 | 
				
			||||||
							inner_world_delete(world, child)
 | 
												inner_world_delete(world, child)
 | 
				
			||||||
| 
						 | 
					@ -2690,7 +2711,7 @@ local function world_new()
 | 
				
			||||||
		if idr_r then
 | 
							if idr_r then
 | 
				
			||||||
			local archetype_ids = idr_r.records
 | 
								local archetype_ids = idr_r.records
 | 
				
			||||||
			local flags = idr_r.flags
 | 
								local flags = idr_r.flags
 | 
				
			||||||
			if (bit32.band(flags, ECS_ID_DELETE) :: number) ~= 0 then
 | 
								if bit32.btest(flags, ECS_ID_DELETE) then
 | 
				
			||||||
				for archetype_id in archetype_ids do
 | 
									for archetype_id in archetype_ids do
 | 
				
			||||||
					local idr_r_archetype = archetypes[archetype_id]
 | 
										local idr_r_archetype = archetypes[archetype_id]
 | 
				
			||||||
					local entities = idr_r_archetype.entities
 | 
										local entities = idr_r_archetype.entities
 | 
				
			||||||
| 
						 | 
					@ -2868,7 +2889,7 @@ end
 | 
				
			||||||
local function ecs_is_tag(world: World, entity: Entity): boolean
 | 
					local function ecs_is_tag(world: World, entity: Entity): boolean
 | 
				
			||||||
	local idr = world.component_index[entity]
 | 
						local idr = world.component_index[entity]
 | 
				
			||||||
	if idr then
 | 
						if idr then
 | 
				
			||||||
		return bit32.band(idr.flags, ECS_ID_IS_TAG) ~= 0
 | 
							return bit32.btest(idr.flags, ECS_ID_IS_TAG)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
	return not world_has_one_inline(world, entity, EcsComponent)
 | 
						return not world_has_one_inline(world, entity, EcsComponent)
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -2877,7 +2898,7 @@ return {
 | 
				
			||||||
	world = world_new :: () -> World,
 | 
						world = world_new :: () -> World,
 | 
				
			||||||
	component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
 | 
						component = (ECS_COMPONENT :: any) :: <T>() -> Entity<T>,
 | 
				
			||||||
	tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
 | 
						tag = (ECS_TAG :: any) :: <T>() -> Entity<T>,
 | 
				
			||||||
	meta = (ECS_META :: any) :: <T>(id: Entity, id: Id<T>, value: T) -> Entity<T>,
 | 
						meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
 | 
				
			||||||
	is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
 | 
						is_tag = (ecs_is_tag :: any) :: <T>(World, Id<T>) -> boolean,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
 | 
					    OnAdd = (EcsOnAdd :: any) :: Entity<<T>(entity: Entity, id: Id<T>, data: T) -> ()>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -142,6 +142,66 @@ TEST("repro", function()
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST("world:add()", function()
 | 
					TEST("world:add()", function()
 | 
				
			||||||
 | 
						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:add(e, pair(A, B))
 | 
				
			||||||
 | 
							world:add(e, pair(A, C))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e, 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.Rest :: number + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(A, jecs.Exclusive)
 | 
				
			||||||
 | 
							local on_remove_call = false
 | 
				
			||||||
 | 
							world:set(A, jecs.OnRemove, function(e, id)
 | 
				
			||||||
 | 
								CHECK(e == e_ptr)
 | 
				
			||||||
 | 
								CHECK(id == jecs.pair(A, B))
 | 
				
			||||||
 | 
								on_remove_call = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local on_add_call_count = 0
 | 
				
			||||||
 | 
							world:set(A, jecs.OnAdd, function(e, id)
 | 
				
			||||||
 | 
								on_add_call_count += 1
 | 
				
			||||||
 | 
								if on_add_call_count == 1 then
 | 
				
			||||||
 | 
									CHECK(e == e_ptr)
 | 
				
			||||||
 | 
									CHECK(id == jecs.pair(A, B))
 | 
				
			||||||
 | 
								elseif on_add_call_count == 2 then
 | 
				
			||||||
 | 
									CHECK(e == e_ptr)
 | 
				
			||||||
 | 
									CHECK(id == jecs.pair(A, C))
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									CHECK(false)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e = world:entity()
 | 
				
			||||||
 | 
							CHECK(e == e_ptr)
 | 
				
			||||||
 | 
							world:add(e, pair(A, B))
 | 
				
			||||||
 | 
							CHECK(on_add_call_count == 1)
 | 
				
			||||||
 | 
							world:add(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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do CASE "idempotent"
 | 
						do CASE "idempotent"
 | 
				
			||||||
		local world = jecs.world()
 | 
							local world = jecs.world()
 | 
				
			||||||
		local d = dwi(world)
 | 
							local d = dwi(world)
 | 
				
			||||||
| 
						 | 
					@ -193,6 +253,9 @@ TEST("world:children()", function()
 | 
				
			||||||
	local e3 = world:entity()
 | 
						local e3 = world:entity()
 | 
				
			||||||
	world:add(e3, pair(ChildOf, e1))
 | 
						world:add(e3, pair(ChildOf, e1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CHECK(world:has(e2, pair(ChildOf, e1)))
 | 
				
			||||||
 | 
						CHECK(world:has(e3, pair(ChildOf, e1)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local count = 0
 | 
						local count = 0
 | 
				
			||||||
	for entity in world:children(e1) do
 | 
						for entity in world:children(e1) do
 | 
				
			||||||
		count += 1
 | 
							count += 1
 | 
				
			||||||
| 
						 | 
					@ -1669,7 +1732,9 @@ end)
 | 
				
			||||||
TEST("#repro2", function()
 | 
					TEST("#repro2", function()
 | 
				
			||||||
	local world = jecs.world()
 | 
						local world = jecs.world()
 | 
				
			||||||
	local Lifetime = world:component() :: Id<number>
 | 
						local Lifetime = world:component() :: Id<number>
 | 
				
			||||||
 | 
						world:set(Lifetime, jecs.Name, "Lifetime")
 | 
				
			||||||
	local Particle = world:entity()
 | 
						local Particle = world:entity()
 | 
				
			||||||
 | 
						world:set(Particle, jecs.Name, "Particle")
 | 
				
			||||||
	local Beam = world:entity()
 | 
						local Beam = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local entity = world:entity()
 | 
						local entity = world:entity()
 | 
				
			||||||
| 
						 | 
					@ -1677,19 +1742,24 @@ TEST("#repro2", function()
 | 
				
			||||||
	world:set(entity, pair(Lifetime, Beam), 2)
 | 
						world:set(entity, pair(Lifetime, Beam), 2)
 | 
				
			||||||
	world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
 | 
						world:set(entity, pair(4 :: any, 5 :: any), 6) -- noise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CHECK(world:get(entity, pair(Lifetime, Particle)) == 1)
 | 
				
			||||||
 | 
						CHECK(world:get(entity, pair(Lifetime, Beam)) == 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CHECK(world:target(entity, Lifetime, 0) == Particle)
 | 
				
			||||||
 | 
						CHECK(world:target(entity, Lifetime, 1) == Beam)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	-- entity_visualizer.components(world, entity)
 | 
						-- entity_visualizer.components(world, entity)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						-- print(CHECK(world:has(jecs.ChildOf, jecs.Exclusive)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for e in world:each(pair(Lifetime, __)) do
 | 
						for e in world:each(pair(Lifetime, __)) do
 | 
				
			||||||
		local i = 0
 | 
							local i = 0
 | 
				
			||||||
		local nth = world:target(e, Lifetime, i)
 | 
							local nth = world:target(e, Lifetime, i)
 | 
				
			||||||
		while nth do
 | 
							while nth do
 | 
				
			||||||
			-- entity_visualizer.components(world, e)
 | 
								-- entity_visualizer.components(world, e)
 | 
				
			||||||
 | 
					 | 
				
			||||||
			local data = world:get(e, pair(Lifetime, nth)) :: number
 | 
								local data = world:get(e, pair(Lifetime, nth)) :: number
 | 
				
			||||||
			data -= 1
 | 
							 	if data > 0 then
 | 
				
			||||||
		 	if data <= 0 then
 | 
									data -= 1
 | 
				
			||||||
                world:remove(e, pair(Lifetime, nth))
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                world:set(e, pair(Lifetime, nth), data)
 | 
					                world:set(e, pair(Lifetime, nth), data)
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
			i += 1
 | 
								i += 1
 | 
				
			||||||
| 
						 | 
					@ -1697,7 +1767,7 @@ TEST("#repro2", function()
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CHECK(not world:has(entity, pair(Lifetime, Particle)))
 | 
						CHECK(world:get(entity, pair(Lifetime, Particle)) == 0)
 | 
				
			||||||
	CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
 | 
						CHECK(world:get(entity, pair(Lifetime, Beam)) == 1)
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue