mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-03 18:39:19 +00:00 
			
		
		
		
	Improve relationship performance
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	
This commit is contained in:
		
							parent
							
								
									0b6bfea5c8
								
							
						
					
					
						commit
						8f95309871
					
				
					 5 changed files with 298 additions and 113 deletions
				
			
		| 
						 | 
					@ -2,6 +2,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Unreleased
 | 
					## Unreleased
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					- Added signals that allow listening to relation part of pairs in signals.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					- `OnRemove` hooks so that they are allowed to move entity's archetype even during deletion.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 0.8.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Added
 | 
					### Added
 | 
				
			||||||
- `jecs.Exclusive` trait for making exclusive relationships.
 | 
					- `jecs.Exclusive` trait for making exclusive relationships.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										270
									
								
								jecs.luau
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								jecs.luau
									
									
									
									
									
								
							| 
						 | 
					@ -54,8 +54,6 @@ export type Query<T...> = typeof(setmetatable(
 | 
				
			||||||
		archetypes: (self: Query<T...>) -> { Archetype },
 | 
							archetypes: (self: Query<T...>) -> { Archetype },
 | 
				
			||||||
		cached: (self: Query<T...>) -> Query<T...>,
 | 
							cached: (self: Query<T...>) -> Query<T...>,
 | 
				
			||||||
		ids: { Id<any> },
 | 
							ids: { Id<any> },
 | 
				
			||||||
		patch: (self: Query<T...>, fn: (T...) -> (T...)) -> (),
 | 
					 | 
				
			||||||
		view: (self: Query<T...>) -> View<T...>,
 | 
					 | 
				
			||||||
		-- world: World
 | 
							-- world: World
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{} :: {
 | 
						{} :: {
 | 
				
			||||||
| 
						 | 
					@ -70,6 +68,14 @@ export type Observer = {
 | 
				
			||||||
	query: QueryInner,
 | 
						query: QueryInner,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type query = {
 | 
				
			||||||
 | 
						compatible_archetypes: { archetype },
 | 
				
			||||||
 | 
						ids: { i53 },
 | 
				
			||||||
 | 
						filter_with: { i53 },
 | 
				
			||||||
 | 
						filter_without: { i53 },
 | 
				
			||||||
 | 
						next: () -> (i53, ...any),
 | 
				
			||||||
 | 
						world: World,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type observer = {
 | 
					export type observer = {
 | 
				
			||||||
	callback: (archetype: archetype) -> (),
 | 
						callback: (archetype: archetype) -> (),
 | 
				
			||||||
| 
						 | 
					@ -86,7 +92,7 @@ type archetype = {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type componentrecord = {
 | 
					type componentrecord = {
 | 
				
			||||||
	records: { [i53]: number },
 | 
						records: { [number]: number },
 | 
				
			||||||
	counts: { [i53]: number },
 | 
						counts: { [i53]: number },
 | 
				
			||||||
	flags: number,
 | 
						flags: number,
 | 
				
			||||||
	size: number,
 | 
						size: number,
 | 
				
			||||||
| 
						 | 
					@ -94,6 +100,8 @@ type componentrecord = {
 | 
				
			||||||
	on_add: ((entity: i53, id: i53, value: any?) -> ())?,
 | 
						on_add: ((entity: i53, id: i53, value: any?) -> ())?,
 | 
				
			||||||
	on_change: ((entity: i53, id: i53, value: any) -> ())?,
 | 
						on_change: ((entity: i53, id: i53, value: any) -> ())?,
 | 
				
			||||||
	on_remove: ((entity: i53, id: i53) -> ())?,
 | 
						on_remove: ((entity: i53, id: i53) -> ())?,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						wildcard_pairs: { [number]: componentrecord },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
type record = {
 | 
					type record = {
 | 
				
			||||||
	archetype: archetype,
 | 
						archetype: archetype,
 | 
				
			||||||
| 
						 | 
					@ -889,17 +897,32 @@ local function archetype_create(world: world, id_types: { i53 }, ty, prev: i53?)
 | 
				
			||||||
		if ECS_IS_PAIR(component_id) then
 | 
							if ECS_IS_PAIR(component_id) then
 | 
				
			||||||
			local relation = ECS_PAIR_FIRST(component_id)
 | 
								local relation = ECS_PAIR_FIRST(component_id)
 | 
				
			||||||
			local object = ECS_PAIR_SECOND(component_id)
 | 
								local object = ECS_PAIR_SECOND(component_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local r = ECS_PAIR(relation, EcsWildcard)
 | 
								local r = ECS_PAIR(relation, EcsWildcard)
 | 
				
			||||||
			local idr_r = id_record_ensure(world, r)
 | 
								local idr_r = id_record_ensure(world, r)
 | 
				
			||||||
			idr_r.size += 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								idr_r.size += 1
 | 
				
			||||||
			archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
 | 
								archetype_append_to_records(idr_r, archetype_id, columns_map, r, i, column)
 | 
				
			||||||
 | 
								local idr_r_wc_pairs = idr_r.wildcard_pairs
 | 
				
			||||||
 | 
								if not idr_r_wc_pairs then
 | 
				
			||||||
 | 
									idr_r_wc_pairs = {} :: {[i53]: componentrecord }
 | 
				
			||||||
 | 
									idr_r.wildcard_pairs = idr_r_wc_pairs
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								idr_r_wc_pairs[component_id] = idr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local t = ECS_PAIR(EcsWildcard, object)
 | 
								local t = ECS_PAIR(EcsWildcard, object)
 | 
				
			||||||
			local idr_t = id_record_ensure(world, t)
 | 
								local idr_t = id_record_ensure(world, t)
 | 
				
			||||||
			idr_t.size += 1
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								idr_t.size += 1
 | 
				
			||||||
			archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
 | 
								archetype_append_to_records(idr_t, archetype_id, columns_map, t, i, column)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								-- Hypothetically this should only capture leaf component records
 | 
				
			||||||
 | 
								local idr_t_wc_pairs = idr_t.wildcard_pairs
 | 
				
			||||||
 | 
								if not idr_t_wc_pairs then
 | 
				
			||||||
 | 
									idr_t_wc_pairs = {} :: {[i53]: componentrecord }
 | 
				
			||||||
 | 
									idr_t.wildcard_pairs = idr_t_wc_pairs
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								idr_t_wc_pairs[component_id] = idr
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1086,15 +1109,12 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
local function archetype_delete(world: world, archetype: archetype, row: number)
 | 
					local function archetype_delete(world: world, archetype: archetype, row: number)
 | 
				
			||||||
	local entity_index = world.entity_index
 | 
						local entity_index = world.entity_index
 | 
				
			||||||
	local component_index = world.component_index
 | 
					 | 
				
			||||||
	local columns = archetype.columns
 | 
						local columns = archetype.columns
 | 
				
			||||||
	local id_types = archetype.types
 | 
					 | 
				
			||||||
	local entities = archetype.entities
 | 
						local entities = archetype.entities
 | 
				
			||||||
	local column_count = #entities
 | 
						local column_count = #entities
 | 
				
			||||||
	local last = #entities
 | 
						local last = #entities
 | 
				
			||||||
	local move = entities[last]
 | 
						local move = entities[last]
 | 
				
			||||||
	-- We assume first that the entity is the last in the archetype
 | 
						-- We assume first that the entity is the last in the archetype
 | 
				
			||||||
	local delete = move
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if row ~= last then
 | 
						if row ~= last then
 | 
				
			||||||
		local record_to_move = entity_index_try_get_any(entity_index, move)
 | 
							local record_to_move = entity_index_try_get_any(entity_index, move)
 | 
				
			||||||
| 
						 | 
					@ -1102,18 +1122,9 @@ local function archetype_delete(world: world, archetype: archetype, row: number)
 | 
				
			||||||
			record_to_move.row = row
 | 
								record_to_move.row = row
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		delete = entities[row]
 | 
					 | 
				
			||||||
		entities[row] = move
 | 
							entities[row] = move
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, id in id_types do
 | 
					 | 
				
			||||||
		local idr = component_index[id]
 | 
					 | 
				
			||||||
		local on_remove = idr.on_remove
 | 
					 | 
				
			||||||
		if on_remove then
 | 
					 | 
				
			||||||
			on_remove(delete, id)
 | 
					 | 
				
			||||||
		end
 | 
					 | 
				
			||||||
	end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	entities[last] = nil :: any
 | 
						entities[last] = nil :: any
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if row == last then
 | 
						if row == last then
 | 
				
			||||||
| 
						 | 
					@ -2365,6 +2376,22 @@ local function world_new()
 | 
				
			||||||
		return r
 | 
							return r
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						local function exclusive_traverse_add(
 | 
				
			||||||
 | 
							archetype: archetype,
 | 
				
			||||||
 | 
							cr: number,
 | 
				
			||||||
 | 
							id: i53
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
							local edge = archetype_edges[archetype.id]
 | 
				
			||||||
 | 
							local to = edge[id]
 | 
				
			||||||
 | 
							if not to then
 | 
				
			||||||
 | 
								local dst = table.clone(archetype.types)
 | 
				
			||||||
 | 
								dst[cr] = id
 | 
				
			||||||
 | 
								to = archetype_ensure(world, dst)
 | 
				
			||||||
 | 
								edge[id] = to
 | 
				
			||||||
 | 
							end
 | 
				
			||||||
 | 
							return to
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	local function inner_world_set(world: world, entity: i53, id: i53, data): ()
 | 
						local function inner_world_set(world: world, entity: i53, id: i53, data): ()
 | 
				
			||||||
		local record = inner_entity_index_try_get_unsafe(entity)
 | 
							local record = inner_entity_index_try_get_unsafe(entity)
 | 
				
			||||||
		if not record then
 | 
							if not record then
 | 
				
			||||||
| 
						 | 
					@ -2395,22 +2422,19 @@ local function world_new()
 | 
				
			||||||
				local edge = archetype_edges[src.id]
 | 
									local edge = archetype_edges[src.id]
 | 
				
			||||||
				to = edge[id]
 | 
									to = edge[id]
 | 
				
			||||||
				if to == nil then
 | 
									if to == nil then
 | 
				
			||||||
					if idr and (bit32.btest(idr.flags) == true) then
 | 
										if idr and (bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) == true) then
 | 
				
			||||||
						local cr = idr.records[src.id]
 | 
											local cr = idr.records[src.id]
 | 
				
			||||||
						if cr then
 | 
											if cr then
 | 
				
			||||||
							local on_remove = idr.on_remove
 | 
												local on_remove = idr.on_remove
 | 
				
			||||||
							local id_types = src.types
 | 
												local id_types = src.types
 | 
				
			||||||
							if on_remove then
 | 
												if on_remove then
 | 
				
			||||||
								on_remove(entity, id_types[cr])
 | 
													on_remove(entity, id_types[cr])
 | 
				
			||||||
 | 
					 | 
				
			||||||
								src = record.archetype
 | 
													src = record.archetype
 | 
				
			||||||
								id_types = src.types
 | 
													id_types = src.types
 | 
				
			||||||
								cr = idr.records[src.id]
 | 
													cr = idr.records[src.id]
 | 
				
			||||||
							end
 | 
												end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							local dst = table.clone(id_types)
 | 
												to = exclusive_traverse_add(src, cr, id)
 | 
				
			||||||
							dst[cr] = id
 | 
					 | 
				
			||||||
							to = archetype_ensure(world, dst)
 | 
					 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2425,30 +2449,27 @@ local function world_new()
 | 
				
			||||||
					archetype_edges[(to :: Archetype).id][id] = src
 | 
										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 cr = idr.records[src.id]
 | 
											local on_remove = idr.on_remove
 | 
				
			||||||
						if cr then
 | 
											if on_remove then
 | 
				
			||||||
							local on_remove = idr.on_remove
 | 
												local cr = idr.records[src.id]
 | 
				
			||||||
							local id_types = src.types
 | 
												if cr then
 | 
				
			||||||
							if on_remove then
 | 
													local id_types = src.types
 | 
				
			||||||
								on_remove(entity, id_types[cr])
 | 
													on_remove(entity, id_types[cr])
 | 
				
			||||||
								src = record.archetype
 | 
													local arche = record.archetype
 | 
				
			||||||
								id_types = src.types
 | 
													if src ~= arche then
 | 
				
			||||||
								cr = idr.records[src.id]
 | 
														id_types = arche.types
 | 
				
			||||||
 | 
														cr = idr.records[arche.id]
 | 
				
			||||||
 | 
														to = exclusive_traverse_add(arche, cr, id)
 | 
				
			||||||
 | 
													end
 | 
				
			||||||
							end
 | 
												end
 | 
				
			||||||
							local dst = table.clone(id_types)
 | 
					 | 
				
			||||||
							dst[cr] = id
 | 
					 | 
				
			||||||
							to = archetype_ensure(world, dst)
 | 
					 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
					if not to then
 | 
					 | 
				
			||||||
						to = find_archetype_with(world, id, src)
 | 
					 | 
				
			||||||
					end
 | 
					 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				local edges = archetype_edges
 | 
									local edges = archetype_edges
 | 
				
			||||||
				local edge = edges[src.id]
 | 
									local edge = edges[src.id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				to = edge[id] :: archetype
 | 
									to = edge[id]
 | 
				
			||||||
				if not to then
 | 
									if not to then
 | 
				
			||||||
					to = find_archetype_with(world, id, src)
 | 
										to = find_archetype_with(world, id, src)
 | 
				
			||||||
					edge[id] = to
 | 
										edge[id] = to
 | 
				
			||||||
| 
						 | 
					@ -2501,7 +2522,7 @@ local function world_new()
 | 
				
			||||||
			local edge = archetype_edges[src.id]
 | 
								local edge = archetype_edges[src.id]
 | 
				
			||||||
			to = edge[id]
 | 
								to = edge[id]
 | 
				
			||||||
			if to == nil then
 | 
								if to == nil then
 | 
				
			||||||
				if idr and (bit32.btest(idr.flags) == true) then
 | 
									if idr and (bit32.btest(idr.flags, ECS_ID_IS_EXCLUSIVE) == true) then
 | 
				
			||||||
					local cr = idr.records[src.id]
 | 
										local cr = idr.records[src.id]
 | 
				
			||||||
					if cr then
 | 
										if cr then
 | 
				
			||||||
						local on_remove = idr.on_remove
 | 
											local on_remove = idr.on_remove
 | 
				
			||||||
| 
						 | 
					@ -2514,9 +2535,7 @@ local function world_new()
 | 
				
			||||||
							cr = idr.records[src.id]
 | 
												cr = idr.records[src.id]
 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						local dst = table.clone(id_types)
 | 
											to = exclusive_traverse_add(src, cr, id)
 | 
				
			||||||
						dst[cr] = id
 | 
					 | 
				
			||||||
						to = archetype_ensure(world, dst)
 | 
					 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2531,24 +2550,21 @@ local function world_new()
 | 
				
			||||||
				archetype_edges[(to :: Archetype).id][id] = src
 | 
									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 cr = idr.records[src.id]
 | 
										local on_remove = idr.on_remove
 | 
				
			||||||
					if cr then
 | 
										if on_remove then
 | 
				
			||||||
						local on_remove = idr.on_remove
 | 
											local cr = idr.records[src.id]
 | 
				
			||||||
						local id_types = src.types
 | 
											if cr then
 | 
				
			||||||
						if on_remove then
 | 
												local id_types = src.types
 | 
				
			||||||
							on_remove(entity, id_types[cr])
 | 
												on_remove(entity, id_types[cr])
 | 
				
			||||||
							src = record.archetype
 | 
												local arche = record.archetype
 | 
				
			||||||
							id_types = src.types
 | 
												if src ~= arche then
 | 
				
			||||||
							cr = idr.records[src.id]
 | 
													id_types = arche.types
 | 
				
			||||||
 | 
													cr = idr.records[arche.id]
 | 
				
			||||||
 | 
													to = exclusive_traverse_add(arche, cr, id)
 | 
				
			||||||
 | 
												end
 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
						local dst = table.clone(id_types)
 | 
					 | 
				
			||||||
						dst[cr] = id
 | 
					 | 
				
			||||||
						to = archetype_ensure(world, dst)
 | 
					 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
				if not to then
 | 
					 | 
				
			||||||
					to = find_archetype_with(world, id, src)
 | 
					 | 
				
			||||||
				end
 | 
					 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			local edges = archetype_edges
 | 
								local edges = archetype_edges
 | 
				
			||||||
| 
						 | 
					@ -2626,12 +2642,20 @@ local function world_new()
 | 
				
			||||||
				table.insert(listeners, existing_hook)
 | 
									table.insert(listeners, existing_hook)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
 | 
								local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
 | 
				
			||||||
			if idr then
 | 
					
 | 
				
			||||||
				idr.on_add = on_add
 | 
								if idr_pair then
 | 
				
			||||||
 | 
									for id, cr in idr_pair.wildcard_pairs do
 | 
				
			||||||
 | 
										cr.on_add = on_add
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
									idr_pair.on_add = on_add
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				inner_world_set(world, component, EcsOnAdd, on_add)
 | 
									local idr = component_index[component]
 | 
				
			||||||
 | 
									if idr then
 | 
				
			||||||
 | 
										idr.on_add = on_add
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
								inner_world_set(world, component, EcsOnAdd, on_add)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
		return function()
 | 
							return function()
 | 
				
			||||||
| 
						 | 
					@ -2661,12 +2685,22 @@ local function world_new()
 | 
				
			||||||
				table.insert(listeners, existing_hook)
 | 
									table.insert(listeners, existing_hook)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
 | 
								local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
 | 
				
			||||||
			if idr then
 | 
					
 | 
				
			||||||
				idr.on_change = on_change
 | 
								if idr_pair then
 | 
				
			||||||
 | 
									for _, cr in idr_pair.wildcard_pairs do
 | 
				
			||||||
 | 
										cr.on_change = on_change
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									idr_pair.on_change = on_change
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				inner_world_set(world, component, EcsOnChange, on_change)
 | 
									local idr = component_index[component]
 | 
				
			||||||
 | 
									if idr then
 | 
				
			||||||
 | 
										idr.on_change = on_change
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								inner_world_set(world, component, EcsOnChange, on_change)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
		return function()
 | 
							return function()
 | 
				
			||||||
| 
						 | 
					@ -2687,17 +2721,28 @@ local function world_new()
 | 
				
			||||||
					listener(entity, id)
 | 
										listener(entity, id)
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
 | 
								local existing_hook = inner_world_get(world, component, EcsOnRemove) :: Listener<T>
 | 
				
			||||||
			if existing_hook then
 | 
								if existing_hook then
 | 
				
			||||||
				table.insert(listeners, existing_hook)
 | 
									table.insert(listeners, existing_hook)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			local idr = component_index[ECS_PAIR(component, EcsWildcard)] or component_index[component]
 | 
								local idr_pair = component_index[ECS_PAIR(component, EcsWildcard)]
 | 
				
			||||||
			if idr then
 | 
					
 | 
				
			||||||
				idr.on_remove = on_remove
 | 
								if idr_pair then
 | 
				
			||||||
 | 
									for _, cr in idr_pair.wildcard_pairs do
 | 
				
			||||||
 | 
										cr.on_remove = on_remove
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									idr_pair.on_remove = on_remove
 | 
				
			||||||
			else
 | 
								else
 | 
				
			||||||
				inner_world_set(world, component, EcsOnRemove, on_remove)
 | 
									local idr = component_index[component]
 | 
				
			||||||
 | 
									if idr then
 | 
				
			||||||
 | 
										idr.on_remove = on_remove
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								inner_world_set(world, component, EcsOnRemove, on_remove)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		table.insert(listeners, fn)
 | 
							table.insert(listeners, fn)
 | 
				
			||||||
| 
						 | 
					@ -2860,6 +2905,7 @@ local function world_new()
 | 
				
			||||||
		if from.columns_map[id] then
 | 
							if from.columns_map[id] then
 | 
				
			||||||
			local idr = world.component_index[id]
 | 
								local idr = world.component_index[id]
 | 
				
			||||||
			local on_remove = idr.on_remove
 | 
								local on_remove = idr.on_remove
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if on_remove then
 | 
								if on_remove then
 | 
				
			||||||
				on_remove(entity, id)
 | 
									on_remove(entity, id)
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
| 
						 | 
					@ -2964,12 +3010,16 @@ local function world_new()
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local archetype = record.archetype
 | 
							local archetype = record.archetype
 | 
				
			||||||
		local row = record.row
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if archetype then
 | 
							if archetype then
 | 
				
			||||||
			-- In the future should have a destruct mode for
 | 
								for _, id in archetype.types do
 | 
				
			||||||
			-- deleting archetypes themselves. Maybe requires recycling
 | 
									local idr = component_index[id]
 | 
				
			||||||
			archetype_delete(world, archetype, row)
 | 
									local on_remove = idr.on_remove
 | 
				
			||||||
 | 
									if on_remove then
 | 
				
			||||||
 | 
										on_remove(entity, id)
 | 
				
			||||||
 | 
									end
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
								archetype_delete(world, record.archetype, record.row)
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local component_index = world.component_index
 | 
							local component_index = world.component_index
 | 
				
			||||||
| 
						 | 
					@ -3035,56 +3085,55 @@ local function world_new()
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
		if idr_t then
 | 
							if idr_t then
 | 
				
			||||||
			local archetype_ids = idr_t.records
 | 
								for id, cr in idr_t.wildcard_pairs do
 | 
				
			||||||
			for archetype_id in archetype_ids do
 | 
									local flags = cr.flags
 | 
				
			||||||
				local idr_t_archetype = archetypes[archetype_id]
 | 
									local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
 | 
				
			||||||
				local node = idr_t_archetype
 | 
									local on_remove = cr.on_remove
 | 
				
			||||||
				local idr_t_types = idr_t_archetype.types
 | 
									if flags_delete_mask then
 | 
				
			||||||
				local entities = idr_t_archetype.entities
 | 
										for archetype_id in cr.records do
 | 
				
			||||||
 | 
											local idr_t_archetype = archetypes[archetype_id]
 | 
				
			||||||
				local deleted = false
 | 
											local entities = idr_t_archetype.entities
 | 
				
			||||||
				for _, id in idr_t_types do
 | 
					 | 
				
			||||||
					if not ECS_IS_PAIR(id) then
 | 
					 | 
				
			||||||
						continue
 | 
					 | 
				
			||||||
					end
 | 
					 | 
				
			||||||
					local object = entity_index_get_alive(
 | 
					 | 
				
			||||||
					    entity_index, ECS_PAIR_SECOND(id))
 | 
					 | 
				
			||||||
					if object ~= entity then
 | 
					 | 
				
			||||||
						continue
 | 
					 | 
				
			||||||
					end
 | 
					 | 
				
			||||||
					local id_record = component_index[id]
 | 
					 | 
				
			||||||
					local flags = id_record.flags
 | 
					 | 
				
			||||||
					local flags_delete_mask = bit32.btest(flags, ECS_ID_DELETE)
 | 
					 | 
				
			||||||
					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)
 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
						deleted = true
 | 
					 | 
				
			||||||
						break
 | 
											break
 | 
				
			||||||
					else
 | 
										end
 | 
				
			||||||
						node = archetype_traverse_remove(world, id, node)
 | 
									else
 | 
				
			||||||
						local on_remove = component_index[id].on_remove
 | 
										for archetype_id in cr.records do
 | 
				
			||||||
						if on_remove then
 | 
											local idr_t_archetype = archetypes[archetype_id]
 | 
				
			||||||
							for _, entity in entities do
 | 
											local entities = idr_t_archetype.entities
 | 
				
			||||||
								on_remove(entity, id)
 | 
											-- archetype_traverse_remove is not idempotent meaning
 | 
				
			||||||
 | 
											-- this access is actually unsafe because it can
 | 
				
			||||||
 | 
											-- incorrectly cache an edge despite a node of the
 | 
				
			||||||
 | 
											-- component id on the archetype does not exist. This
 | 
				
			||||||
 | 
											-- requires careful testing to ensure correct values are
 | 
				
			||||||
 | 
											-- being passed to the arguments.
 | 
				
			||||||
 | 
											local to = archetype_traverse_remove(world, id, idr_t_archetype)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											for i = #entities, 1, -1 do
 | 
				
			||||||
 | 
												local e = entities[i]
 | 
				
			||||||
 | 
												local r = eindex_sparse_array[ECS_ID(e :: number)]
 | 
				
			||||||
 | 
												if on_remove then
 | 
				
			||||||
 | 
													on_remove(e, id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
													local from = r.archetype
 | 
				
			||||||
 | 
													if from ~= idr_t_archetype then
 | 
				
			||||||
 | 
														-- unfortunately the on_remove hook allows a window where `e` can have changed archetype
 | 
				
			||||||
 | 
														-- this is hypothetically not that expensive of an operation anyways
 | 
				
			||||||
 | 
														to = archetype_traverse_remove(world, id, from)
 | 
				
			||||||
 | 
													end
 | 
				
			||||||
							end
 | 
												end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												inner_entity_move(entity_index, e, r, to)
 | 
				
			||||||
						end
 | 
											end
 | 
				
			||||||
					end
 | 
										end
 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if not deleted then
 | 
									for archetype_id in cr.records do
 | 
				
			||||||
					for i = #entities, 1, -1 do
 | 
										archetype_destroy(world, archetypes[archetype_id])
 | 
				
			||||||
						local e = entities[i]
 | 
					 | 
				
			||||||
						local r = inner_entity_index_try_get_unsafe(e) :: record
 | 
					 | 
				
			||||||
						inner_entity_move(entity_index, e, r, node)
 | 
					 | 
				
			||||||
					end
 | 
					 | 
				
			||||||
				end
 | 
									end
 | 
				
			||||||
			end
 | 
								end
 | 
				
			||||||
 | 
					 | 
				
			||||||
			for archetype_id in archetype_ids do
 | 
					 | 
				
			||||||
				archetype_destroy(world, archetypes[archetype_id])
 | 
					 | 
				
			||||||
			end
 | 
					 | 
				
			||||||
		end
 | 
							end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if idr_r then
 | 
							if idr_r then
 | 
				
			||||||
| 
						 | 
					@ -3286,6 +3335,9 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
return {
 | 
					return {
 | 
				
			||||||
	world = world_new :: () -> World,
 | 
						world = world_new :: () -> World,
 | 
				
			||||||
 | 
						World = {
 | 
				
			||||||
 | 
							new = world_new
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	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, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
 | 
						meta = (ECS_META :: any) :: <T, a>(id: Entity<T>, id: Id<a>, value: a?) -> Entity<T>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "@rbxts/jecs",
 | 
						"name": "@rbxts/jecs",
 | 
				
			||||||
	"version": "0.9.0-rc.8",
 | 
						"version": "0.9.0-rc.9",
 | 
				
			||||||
	"description": "Stupidly fast Entity Component System",
 | 
						"description": "Stupidly fast Entity Component System",
 | 
				
			||||||
	"main": "jecs.luau",
 | 
						"main": "jecs.luau",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										129
									
								
								test/tests.luau
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								test/tests.luau
									
									
									
									
									
								
							| 
						 | 
					@ -333,6 +333,15 @@ TEST("world:add()", function()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		CHECK(world:has(e, pair(A, B)) == false)
 | 
							CHECK(world:has(e, pair(A, B)) == false)
 | 
				
			||||||
		CHECK(world:has(e, pair(A, C)) == true)
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							-- We have to test the path that checks the uncached method
 | 
				
			||||||
 | 
							local e1 = world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(e1, pair(A, B))
 | 
				
			||||||
 | 
							world:add(e1, pair(A, C))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(world:has(e1, pair(A, B)) == false)
 | 
				
			||||||
 | 
							CHECK(world:has(e1, pair(A, C)) == true)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do CASE "exclusive relations invoke hooks"
 | 
						do CASE "exclusive relations invoke hooks"
 | 
				
			||||||
| 
						 | 
					@ -379,6 +388,44 @@ TEST("world:add()", function()
 | 
				
			||||||
		CHECK(world:has(e, pair(A, C)) == true)
 | 
							CHECK(world:has(e, pair(A, C)) == true)
 | 
				
			||||||
	end
 | 
						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:add(e, C)
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									world:add(e, D)
 | 
				
			||||||
 | 
								end
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
							CHECK(world:has(e, C))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							-- We have to ensure that it actually invokes hooks everytime it
 | 
				
			||||||
 | 
							-- traverses the archetype
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
							CHECK(world:has(e, D))
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do CASE "idempotent"
 | 
						do CASE "idempotent"
 | 
				
			||||||
		local world = jecs.world()
 | 
							local world = jecs.world()
 | 
				
			||||||
		local d = dwi(world)
 | 
							local d = dwi(world)
 | 
				
			||||||
| 
						 | 
					@ -621,6 +668,57 @@ TEST("world:contains()", function()
 | 
				
			||||||
end)
 | 
					end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEST("world:delete()", function()
 | 
					TEST("world:delete()", function()
 | 
				
			||||||
 | 
						do CASE "idr_t//delete_mask@3102..3108"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local A = world:component()
 | 
				
			||||||
 | 
							world:add(A, pair(jecs.OnDeleteTarget, jecs.Delete))
 | 
				
			||||||
 | 
							local B = world:component()
 | 
				
			||||||
 | 
							local B_OnAdd_called = false
 | 
				
			||||||
 | 
							local B_OnRemove_called = false
 | 
				
			||||||
 | 
							world:set(B, jecs.OnAdd, function()
 | 
				
			||||||
 | 
								B_OnAdd_called = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
							world:set(B, jecs.OnRemove, function()
 | 
				
			||||||
 | 
								B_OnRemove_called = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:set(A, jecs.OnRemove, function(entity, id)
 | 
				
			||||||
 | 
								world:set(entity, B, true)
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e1 = world:entity()
 | 
				
			||||||
 | 
							local e2 = world:entity()
 | 
				
			||||||
 | 
							world:set(e2, pair(A, e1), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:delete(e1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(e2, pair(A, e1)))
 | 
				
			||||||
 | 
							CHECK(not world:has(e2, B))
 | 
				
			||||||
 | 
							CHECK(not world:contains(e1))
 | 
				
			||||||
 | 
							CHECK(not world:contains(e2))
 | 
				
			||||||
 | 
							CHECK(B_OnAdd_called)
 | 
				
			||||||
 | 
							-- CHECK(B_OnRemove_called)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do CASE "idr_t//remove//on_remove//changed_archetype@3123..3126"
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local A = world:component()
 | 
				
			||||||
 | 
							local B = world:component()
 | 
				
			||||||
 | 
							local C = world:component()
 | 
				
			||||||
 | 
							world:set(A, jecs.OnRemove, function(entity, id)
 | 
				
			||||||
 | 
								world:set(entity, B, true)
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local e1 = world:entity()
 | 
				
			||||||
 | 
							local e2 = world:entity()
 | 
				
			||||||
 | 
							world:add(e2, pair(A, e2))
 | 
				
			||||||
 | 
							world:set(e2, pair(A, e1), true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:delete(e1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(not world:has(e2, pair(A, e1)))
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do CASE "pair(OnDelete, Delete)"
 | 
						do CASE "pair(OnDelete, Delete)"
 | 
				
			||||||
	    local world = jecs.world()
 | 
						    local world = jecs.world()
 | 
				
			||||||
	    local ct = world:component()
 | 
						    local ct = world:component()
 | 
				
			||||||
| 
						 | 
					@ -1076,19 +1174,45 @@ TEST("world:added", function()
 | 
				
			||||||
		CHECK(ran)
 | 
							CHECK(ran)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						do CASE ""
 | 
				
			||||||
 | 
							local world = jecs.world()
 | 
				
			||||||
 | 
							local IsNearby = world:component()
 | 
				
			||||||
 | 
							world:set(IsNearby, jecs.Name, "IsNearby")
 | 
				
			||||||
 | 
							local person1, person2 = world:entity(), world:entity()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:add(person2, jecs.pair(IsNearby, person1))
 | 
				
			||||||
 | 
							local IsNearby_added_called = false
 | 
				
			||||||
 | 
							world:added(IsNearby, function(...) -- This prints fine
 | 
				
			||||||
 | 
								IsNearby_added_called = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
							local IsNearby_removed_called = false
 | 
				
			||||||
 | 
							world:removed(IsNearby, function(...)
 | 
				
			||||||
 | 
								IsNearby_removed_called = true
 | 
				
			||||||
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world:remove(person2, pair(IsNearby, person1))
 | 
				
			||||||
 | 
							world:add(person2, pair(IsNearby, person1))
 | 
				
			||||||
 | 
							world:remove(person2, pair(IsNearby, person1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							CHECK(IsNearby_added_called)
 | 
				
			||||||
 | 
							CHECK(IsNearby_removed_called)
 | 
				
			||||||
 | 
						end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	do CASE "Should work even if set after the pair has been used"
 | 
						do CASE "Should work even if set after the pair has been used"
 | 
				
			||||||
		local A = world:component()
 | 
							local A = world:component()
 | 
				
			||||||
		local B = world:component()
 | 
							local B = world:component()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		world:set(world:entity(), A, 2)
 | 
					 | 
				
			||||||
		world:set(world:entity(), pair(A, B), 2)
 | 
							world:set(world:entity(), pair(A, B), 2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local ran = false
 | 
				
			||||||
		world:added(A, function()
 | 
							world:added(A, function()
 | 
				
			||||||
			ran = true
 | 
								ran = true
 | 
				
			||||||
		end)
 | 
							end)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		local entity = world:entity()
 | 
							local entity = world:entity()
 | 
				
			||||||
 | 
							print(pair(A, B))
 | 
				
			||||||
		world:set(entity, pair(A, B), 3)
 | 
							world:set(entity, pair(A, B), 3)
 | 
				
			||||||
		CHECK(ran)
 | 
							CHECK(ran)
 | 
				
			||||||
	end
 | 
						end
 | 
				
			||||||
| 
						 | 
					@ -1099,6 +1223,7 @@ TEST("world:added", function()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		world:add(world:entity(), A)
 | 
							world:add(world:entity(), A)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local ran = false
 | 
				
			||||||
		world:added(A, function()
 | 
							world:added(A, function()
 | 
				
			||||||
			ran = true
 | 
								ran = true
 | 
				
			||||||
		end)
 | 
							end)
 | 
				
			||||||
| 
						 | 
					@ -1113,6 +1238,7 @@ TEST("world:added", function()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		world:add(world:entity(), pair(A, B))
 | 
							world:add(world:entity(), pair(A, B))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							local ran = false
 | 
				
			||||||
		world:added(A, function()
 | 
							world:added(A, function()
 | 
				
			||||||
			ran = true
 | 
								ran = true
 | 
				
			||||||
		end)
 | 
							end)
 | 
				
			||||||
| 
						 | 
					@ -2199,7 +2325,6 @@ TEST("#repro", function()
 | 
				
			||||||
	local types1 = { pair(Attacks, e1), pair(Eats, e1) }
 | 
						local types1 = { pair(Attacks, e1), pair(Eats, e1) }
 | 
				
			||||||
	table.sort(types1)
 | 
						table.sort(types1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
	CHECK(d.tbl(e1).type == "")
 | 
						CHECK(d.tbl(e1).type == "")
 | 
				
			||||||
	CHECK(d.tbl(e3).type == table.concat(types1, "_"))
 | 
						CHECK(d.tbl(e3).type == table.concat(types1, "_"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "ukendio/jecs"
 | 
					name = "ukendio/jecs"
 | 
				
			||||||
version = "0.9.0-rc.8"
 | 
					version = "0.9.0-rc.9"
 | 
				
			||||||
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