mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-10-31 09:09:18 +00:00 
			
		
		
		
	Merge branch 'main' into Add-queries-with-tags
This commit is contained in:
		
						commit
						cc33c4d038
					
				
					 8 changed files with 460 additions and 237 deletions
				
			
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -53,7 +53,7 @@ WallyPatches | |||
| # Misc | ||||
| roblox.toml | ||||
| sourcemap.json | ||||
| drafts/*.lua | ||||
| drafts/ | ||||
| 
 | ||||
| # Cached Vitepress (docs) | ||||
| 
 | ||||
|  | @ -61,4 +61,4 @@ drafts/*.lua | |||
| /docs/.vitepress/dist | ||||
| 
 | ||||
| .vitepress/cache | ||||
| .vitepress/dist | ||||
| .vitepress/dist | ||||
|  |  | |||
							
								
								
									
										59
									
								
								docs/api.md
									
									
									
									
									
								
							
							
						
						
									
										59
									
								
								docs/api.md
									
									
									
									
									
								
							|  | @ -1,59 +0,0 @@ | |||
| # API | ||||
| 
 | ||||
| ## World | ||||
| 
 | ||||
| ### World.new() -> `World` | ||||
| Creates a new world. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local world = jecs.World.new() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| import { World } from "@rbxts/jecs"; | ||||
| 
 | ||||
| const world = new World(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### world:entity() -> `Entity<T>` | ||||
| Creates a new entity. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local entity = world:entity() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const entity = world.entity(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### world:component() -> `Entity<T>` | ||||
| Creates a new static component. Keep in mind that components are also entities. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local Health = world:component() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const Health = world.component<number>(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ::: info | ||||
| You should use this when creating static components. | ||||
| 
 | ||||
| For example, a generic Health entity should be created using this. | ||||
| ::: | ||||
							
								
								
									
										132
									
								
								docs/api/query.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								docs/api/query.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,132 @@ | |||
| # Query | ||||
| 
 | ||||
| A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components. | ||||
| 
 | ||||
| ## Functions | ||||
| 
 | ||||
| ### new() | ||||
| ```luau | ||||
| function World.new(): World | ||||
| ``` | ||||
| Creates a new world. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local world = jecs.World.new() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| import { World } from "@rbxts/jecs"; | ||||
| 
 | ||||
| const world = new World(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## entity() | ||||
| ```luau | ||||
| function World:entity(): Entity -- The new entit. | ||||
| ``` | ||||
| Creates a new entity. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local entity = world:entity() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const entity = world.entity(); | ||||
| ``` | ||||
| 
 | ||||
| :: | ||||
| : | ||||
| 
 | ||||
| ### component() | ||||
| ```luau | ||||
| function World:component<T>(): Entity<T> -- The new componen. | ||||
| ``` | ||||
| Creates a new component. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local Health = world:component() :: jecs.Entity<number> | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const Health = world.component<number>(); | ||||
| ``` | ||||
| ::: | ||||
| 
 | ||||
| ::: info | ||||
| You should use this when creating components. | ||||
| 
 | ||||
| For example, a Health type should be created using this. | ||||
| ::: | ||||
| 
 | ||||
| ### get() | ||||
| ```luau | ||||
| function World:get( | ||||
|     entity: Entity, -- The entity | ||||
|     ...: Entity<T> -- The types to fetch | ||||
| ): ... -- Returns the component data in the same order they were passed in | ||||
| ``` | ||||
| Returns the data for each provided type for the corresponding entity. | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### add() | ||||
| ```luau | ||||
| function World:add( | ||||
|     entity: Entity, -- The entity | ||||
|     id: Entity<T> -- The component ID to add | ||||
| ): () | ||||
| ``` | ||||
| Adds a component ID to the entity. | ||||
| 
 | ||||
| This operation adds a single (component) id to an entity. | ||||
| 
 | ||||
| ::: info | ||||
| This function is idempotent, meaning if the entity already has the id, this operation will have no side effects. | ||||
| ::: | ||||
| 
 | ||||
| 
 | ||||
| ### set() | ||||
| ```luau | ||||
| function World:set( | ||||
|     entity: Entity, -- The entity | ||||
|     id: Entity<T>, -- The component ID to set | ||||
|     data: T -- The data of the component's type | ||||
| ): () | ||||
| ``` | ||||
| Adds or changes the entity's component. | ||||
| 
 | ||||
| ### query() | ||||
| ```luau | ||||
| function World:query( | ||||
|     ...: Entity<T> -- The component IDs to query with. Entities that satifies the conditions will be returned | ||||
| ): Query<...Entity<T>> -- Returns the Query which gets the entity and their corresponding data when iterated | ||||
| ``` | ||||
| Creates a [`query`](query) with the given component IDs. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| for id, position, velocity in world:query(Position, Velocity) do | ||||
| 	-- Do something | ||||
| end | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| for (const [id, position, velocity] of world.query(Position, Velocity) { | ||||
|     // Do something | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
							
								
								
									
										70
									
								
								docs/api/world.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								docs/api/world.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,70 @@ | |||
| # World | ||||
| 
 | ||||
| A World contains entities which have components. The World is queryable and can be used to get entities with a specific set of components. | ||||
| 
 | ||||
| ## Functions | ||||
| 
 | ||||
| ### new() | ||||
| ```lua | ||||
| function World.new(): World | ||||
| ``` | ||||
| Creates a new world. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local world = jecs.World.new() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| import { World } from "@rbxts/jecs"; | ||||
| 
 | ||||
| const world = new World(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ## entity() | ||||
| ```luau | ||||
| function World:entity(): Entity | ||||
| ``` | ||||
| Creates a new entity. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local entity = world:entity() | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const entity = world.entity(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ### component()` | ||||
| ```luau | ||||
| function World:component<T>(): Entity<T> | ||||
| ``` | ||||
| Creates a new component. | ||||
| 
 | ||||
| Example: | ||||
| ::: code-group | ||||
| 
 | ||||
| ```luau [luau] | ||||
| local Health = world:component() :: jecs.Entity<number> | ||||
| ``` | ||||
| 
 | ||||
| ```ts [typescript] | ||||
| const Health = world.component<number>(); | ||||
| ``` | ||||
| 
 | ||||
| ::: | ||||
| 
 | ||||
| ::: info | ||||
| You should use this when creating components. | ||||
| 
 | ||||
| For example, a Health type should be created using this. | ||||
| ::: | ||||
|  | @ -1,3 +1 @@ | |||
| ## TODO | ||||
| 
 | ||||
| This is a TODO stub. | ||||
|  | @ -13,8 +13,8 @@ hero: | |||
|       text: Get Started | ||||
|       link: /overview/get-started.md | ||||
|     - theme: alt | ||||
|       text: API Examples | ||||
|       link: /api.md | ||||
|       text: API References | ||||
|       link: /api/ | ||||
| 
 | ||||
| features: | ||||
|   - title: Stupidly Fast | ||||
|  | @ -26,4 +26,4 @@ features: | |||
|   - title: Zero-Dependencies | ||||
|     icon: 📦 | ||||
|     details: Jecs doesn't rely on anything other than itself. | ||||
| --- | ||||
| --- | ||||
|  |  | |||
							
								
								
									
										358
									
								
								src/init.luau
									
									
									
									
									
								
							
							
						
						
									
										358
									
								
								src/init.luau
									
									
									
									
									
								
							|  | @ -1,4 +1,3 @@ | |||
| 
 | ||||
| --!optimize 2 | ||||
| --!native | ||||
| --!strict | ||||
|  | @ -17,6 +16,7 @@ type ArchetypeEdge = { | |||
| 	remove: Archetype, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| type Archetype = { | ||||
| 	id: number, | ||||
| 	edges: { [i53]: ArchetypeEdge }, | ||||
|  | @ -533,6 +533,9 @@ local function world_remove(world: World, entityId: i53, componentId: i53) | |||
| 	local entityIndex = world.entityIndex | ||||
| 	local record = entityIndex.sparse[entityId] | ||||
| 	local sourceArchetype = record.archetype | ||||
| 	if not sourceArchetype then | ||||
| 	   return | ||||
| 	end | ||||
| 	local destinationArchetype = archetype_traverse_remove(world, componentId, sourceArchetype) | ||||
| 
 | ||||
| 	if sourceArchetype and not (sourceArchetype == destinationArchetype) then | ||||
|  | @ -669,6 +672,32 @@ do | |||
|     end | ||||
| end | ||||
| 
 | ||||
| local world_has: (world: World, entityId: number, ...i53) -> boolean | ||||
| do | ||||
|     function world_has(world, entity_id, ...) | ||||
|        	local id = entity_id | ||||
|        	local record = world.entityIndex.sparse[id] | ||||
|        	if not record then | ||||
|       		return false | ||||
|        	end | ||||
| 
 | ||||
|        	local archetype = record.archetype | ||||
|        	if not archetype then | ||||
|     	   return false | ||||
|        	end | ||||
| 
 | ||||
|         local tr = archetype.records | ||||
| 
 | ||||
|         for i = 1, select("#", ...) do | ||||
|             if not tr[select(i, ...)] then | ||||
|                 return false | ||||
|             end | ||||
|         end | ||||
| 
 | ||||
|         return true | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| type Item = () -> (number, ...any) | ||||
| export type Query = typeof({ | ||||
|     __iter = function():  Item | ||||
|  | @ -678,8 +707,8 @@ export type Query = typeof({ | |||
|     end, | ||||
| }) & { | ||||
|     next: Item, | ||||
|     replace: (Query, ...any) -> (), | ||||
|     without: (Query) -> Query | ||||
|     without: (Query) -> Query, | ||||
|     replace: (Query, (...any) -> (...any)) -> () | ||||
| } | ||||
| 
 | ||||
| type CompatibleArchetype = { archetype: Archetype, indices: { number } } | ||||
|  | @ -688,135 +717,146 @@ local world_query: (World, ...i53) -> Query | |||
| do | ||||
| 
 | ||||
|     local noop: Item = function() | ||||
|     	return nil :: any | ||||
|    	    return nil :: any | ||||
|     end | ||||
| 
 | ||||
|     local EmptyQuery: Query = { | ||||
|     	__iter = function(): Item | ||||
|     		return noop | ||||
|     	end, | ||||
|     	next = noop :: Item, | ||||
|     	replace = noop :: (Query, ...any) -> (), | ||||
|     	without = function(self: Query, ...) | ||||
|     		return self | ||||
|     	end | ||||
|        	__iter = function(): Item | ||||
|             return noop | ||||
|        	end, | ||||
|        	next = noop :: Item, | ||||
|        	replace = noop :: (Query, ...any) -> (), | ||||
|        	without = function(self: Query, ...) | ||||
|             return self | ||||
|        	end | ||||
|     } | ||||
| 
 | ||||
|     setmetatable(EmptyQuery, EmptyQuery) | ||||
| 
 | ||||
|     local indices: { { number } } | ||||
|    	local compatibleArchetypes: { Archetype } | ||||
|    	local length | ||||
|    	local components: { number } | ||||
|    	local queryLength: number | ||||
|     local lastArchetype: number | ||||
|     local archetype: Archetype | ||||
|     local queryOutput: { any } | ||||
|     local queryLength: number | ||||
|     local entities: { number } | ||||
|     local i: number | ||||
| 
 | ||||
|    	local queryOutput: { any } | ||||
|     local compatible_archetypes: { Archetype } | ||||
|     local column_indices: { { number} } | ||||
|     local ids: { number } | ||||
| 
 | ||||
|    	local entities: {} | ||||
|    	local i: number | ||||
| 
 | ||||
|     local function world_query_next() | ||||
|     local function world_query_next(): any | ||||
|         local entityId = entities[i] | ||||
|         while entityId == nil do | ||||
|   		while entityId == nil do | ||||
|  			lastArchetype += 1 | ||||
|  			archetype = compatibleArchetypes[lastArchetype] | ||||
| 
 | ||||
|  			archetype = compatible_archetypes[lastArchetype] | ||||
|  			if not archetype then | ||||
|  			    return | ||||
|  			end | ||||
| 
 | ||||
|  			entities = archetype.entities | ||||
|                 return nil | ||||
|             end | ||||
|             entities = archetype.entities | ||||
|  			i = #entities | ||||
|             entityId = entities[i] | ||||
|         end | ||||
|  			entityId = entities[i] | ||||
|   		end | ||||
| 
 | ||||
|         local row = i | ||||
|         i-=1 | ||||
|   		local row = i | ||||
|   		i-=1 | ||||
| 
 | ||||
|         local columns = archetype.columns | ||||
|         local tr = indices[lastArchetype] | ||||
|   		local columns = archetype.columns | ||||
|   		local tr = column_indices[lastArchetype] | ||||
| 
 | ||||
|         if queryLength == 1 then | ||||
|             return entityId, columns[tr[1]][row] | ||||
|         elseif queryLength == 2 then | ||||
|             return entityId, columns[tr[1]][row], columns[tr[2]][row] | ||||
|         elseif queryLength == 3 then | ||||
|             return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row] | ||||
|         elseif queryLength == 4 then | ||||
|             return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row] | ||||
|         elseif queryLength == 5 then | ||||
|             return entityId,columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row], | ||||
|                 columns[tr[5]][row] | ||||
|         elseif queryLength == 6 then | ||||
|      			return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row], | ||||
|                     columns[tr[5]][row], | ||||
|      			    columns[tr[6]][row] | ||||
|         elseif queryLength == 7 then | ||||
|      			return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row], | ||||
|                     columns[tr[5]][row], | ||||
|      			    columns[tr[6]][row], | ||||
|          			columns[tr[7]][row] | ||||
|         elseif queryLength == 8 then | ||||
|      			return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row], | ||||
|                     columns[tr[5]][row], | ||||
|      			    columns[tr[6]][row], | ||||
|          			columns[tr[7]][row], | ||||
|                     columns[tr[8]][row] | ||||
|         end | ||||
|   		if queryLength == 1 then | ||||
|  			return entityId, columns[tr[1]][row] | ||||
|   		elseif queryLength == 2 then | ||||
|  			return entityId, columns[tr[1]][row], columns[tr[2]][row] | ||||
|   		elseif queryLength == 3 then | ||||
|  			return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row] | ||||
|   		elseif queryLength == 4 then | ||||
|  			return entityId, columns[tr[1]][row], columns[tr[2]][row], columns[tr[3]][row], columns[tr[4]][row] | ||||
|   		elseif queryLength == 5 then | ||||
|  			return entityId, | ||||
|     				columns[tr[1]][row], | ||||
|     				columns[tr[2]][row], | ||||
|     				columns[tr[3]][row], | ||||
|     				columns[tr[4]][row], | ||||
|     				columns[tr[5]][row] | ||||
|   		elseif queryLength == 6 then | ||||
|  			return entityId, | ||||
|     				columns[tr[1]][row], | ||||
|     				columns[tr[2]][row], | ||||
|     				columns[tr[3]][row], | ||||
|     				columns[tr[4]][row], | ||||
|     				columns[tr[5]][row], | ||||
|     				columns[tr[6]][row] | ||||
|   		elseif queryLength == 7 then | ||||
|  			return entityId, | ||||
|     				columns[tr[1]][row], | ||||
|     				columns[tr[2]][row], | ||||
|     				columns[tr[3]][row], | ||||
|     				columns[tr[4]][row], | ||||
|     				columns[tr[5]][row], | ||||
|     				columns[tr[6]][row], | ||||
|     				columns[tr[7]][row] | ||||
|   		elseif queryLength == 8 then | ||||
|  			return entityId, | ||||
|     				columns[tr[1]][row], | ||||
|     				columns[tr[2]][row], | ||||
|     				columns[tr[3]][row], | ||||
|     				columns[tr[4]][row], | ||||
|     				columns[tr[5]][row], | ||||
|     				columns[tr[6]][row], | ||||
|     				columns[tr[7]][row], | ||||
|     				columns[tr[8]][row] | ||||
|   		end | ||||
| 
 | ||||
|         for i in components do | ||||
|             queryOutput[i] = columns[tr[i]][row] | ||||
|         end | ||||
|   		for j in ids do | ||||
|  			queryOutput[j] = columns[tr[j]][row] | ||||
|   		end | ||||
| 
 | ||||
|         return entityId, unpack(queryOutput, 1, queryLength) | ||||
|   		return entityId, unpack(queryOutput, 1, queryLength) | ||||
|     end | ||||
| 
 | ||||
|     local function world_query_without(self, ...): Query | ||||
|     local function world_query_iter() | ||||
|         return world_query_next | ||||
|     end | ||||
| 
 | ||||
|     local function world_query_without(self, ...) | ||||
|         local withoutComponents = { ... } | ||||
|         for i = #compatibleArchetypes, 1, -1 do | ||||
|  			local archetype = compatibleArchetypes[i] | ||||
|   		for i = #compatible_archetypes, 1, -1 do | ||||
|  			local archetype = compatible_archetypes[i] | ||||
|  			local records = archetype.records | ||||
|  			local shouldRemove = false | ||||
| 
 | ||||
|  			for _, componentId in withoutComponents do | ||||
|  			    if records[componentId] then | ||||
|     				shouldRemove = true | ||||
|     				break | ||||
|  			    end | ||||
|                 if records[componentId] then | ||||
|    					shouldRemove = true | ||||
|    					break | ||||
|                 end | ||||
|  			end | ||||
| 
 | ||||
|  			if shouldRemove then | ||||
|  			    table.remove(compatibleArchetypes, i) | ||||
|                 table.remove(compatible_archetypes, i) | ||||
|                 table.remove(column_indices, i) | ||||
|  			end | ||||
|         end | ||||
|   		end | ||||
| 
 | ||||
|         if #compatibleArchetypes == 0 then | ||||
|             return EmptyQuery | ||||
|         end | ||||
| 
 | ||||
|         return self | ||||
|     end | ||||
| 
 | ||||
|     local function world_query_iter() | ||||
|         lastArchetype = 1 | ||||
|         archetype = compatibleArchetypes[1] | ||||
|         entities = archetype.entities | ||||
|         i = #entities | ||||
|         archetype = compatible_archetypes[lastArchetype] | ||||
| 
 | ||||
|         return world_query_next | ||||
|   		if not archetype then | ||||
|  			return EmptyQuery | ||||
|   		end | ||||
| 
 | ||||
|   		return self | ||||
|     end | ||||
| 
 | ||||
|     local function world_query_replace_values(row, columns, ...) | ||||
|     	for i, column in columns do | ||||
|     		column[row] = select(i, ...) | ||||
|     	end | ||||
|        	for i, column in columns do | ||||
|       		column[row] = select(i, ...) | ||||
|        	end | ||||
|     end | ||||
| 
 | ||||
|     local function world_query_replace(_, fn: any) | ||||
|         for i, archetype in compatibleArchetypes do | ||||
|       		local tr = indices[i] | ||||
|     local function world_query_replace(_, fn: (...any) -> (...any)) | ||||
|         for i, archetype in compatible_archetypes do | ||||
|       		local tr = column_indices[i] | ||||
|       		local columns = archetype.columns | ||||
| 
 | ||||
|       		for row in archetype.entities do | ||||
|  | @ -881,81 +921,88 @@ do | |||
|         return query | ||||
|     end | ||||
| 
 | ||||
|     function world_query(world: World, ...: number): Query | ||||
|         	-- breaking? | ||||
|         	if (...) == nil then | ||||
|         		error("Missing components") | ||||
|         	end | ||||
|     function world_query(world: World, ...: any): Query | ||||
|     	-- breaking? | ||||
|     	if (...) == nil then | ||||
|     		error("Missing components") | ||||
|     	end | ||||
| 
 | ||||
|             indices = {} | ||||
|             compatibleArchetypes = {} | ||||
|             length = 0 | ||||
|             components = { ... } | ||||
|     	local indices = {} | ||||
|     	local compatibleArchetypes = {} | ||||
|     	local length = 0 | ||||
| 
 | ||||
|         	local archetypes: { Archetype } = world.archetypes :: any | ||||
|         	local firstArchetypeMap: ArchetypeMap | ||||
|         	local componentIndex = world.componentIndex | ||||
|         local components = { ... } :: any | ||||
|     	local archetypes = world.archetypes | ||||
| 
 | ||||
|         	for _, componentId in components do | ||||
|         		local map: ArchetypeMap = componentIndex[componentId] :: any | ||||
|         		if not map then | ||||
|         			return EmptyQuery | ||||
|         		end | ||||
|     	local firstArchetypeMap: ArchetypeMap | ||||
|     	local componentIndex = world.componentIndex | ||||
| 
 | ||||
|         		if (firstArchetypeMap :: any) == nil or firstArchetypeMap.size > map.size then | ||||
|         			firstArchetypeMap = map | ||||
|         		end | ||||
|         	end | ||||
|     	for _, componentId in components do | ||||
|     		local map = componentIndex[componentId] | ||||
|     		if not map then | ||||
|     			return EmptyQuery | ||||
|     		end | ||||
| 
 | ||||
|         	for id in firstArchetypeMap.cache do | ||||
|         		local compatibleArchetype = archetypes[id] | ||||
|         		local archetypeRecords = compatibleArchetype.records | ||||
|     		if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then | ||||
|     			firstArchetypeMap = map | ||||
|     		end | ||||
|     	end | ||||
| 
 | ||||
|         		local records: { number } = {} | ||||
|         		local skip = false | ||||
|     	for id in firstArchetypeMap.cache do | ||||
|     		local compatibleArchetype = archetypes[id] | ||||
|     		local archetypeRecords = compatibleArchetype.records | ||||
| 
 | ||||
|         		for i, componentId in components do | ||||
|         			local index = archetypeRecords[componentId] | ||||
|         			if not index then | ||||
|         				skip = true | ||||
|         				break | ||||
|         			end | ||||
|         			-- index should be index.offset | ||||
|         			records[i] = index | ||||
|         		end | ||||
|     		local records = {} | ||||
|     		local skip = false | ||||
| 
 | ||||
|         		if skip then | ||||
|         			continue | ||||
|         		end | ||||
|     		for i, componentId in components do | ||||
|     			local index = archetypeRecords[componentId] | ||||
|     			if not index then | ||||
|     				skip = true | ||||
|     				break | ||||
|     			end | ||||
|     			-- index should be index.offset | ||||
|     			records[i] = index | ||||
|     		end | ||||
| 
 | ||||
|         		length += 1 | ||||
|         		compatibleArchetypes[length] = compatibleArchetype | ||||
|         		indices[length] = records | ||||
|         	end | ||||
|     		if skip then | ||||
|     			continue | ||||
|     		end | ||||
| 
 | ||||
|             lastArchetype = 1 | ||||
|            	archetype = compatibleArchetypes[lastArchetype] | ||||
|     		length += 1 | ||||
|     		compatibleArchetypes[length] = compatibleArchetype | ||||
|     		indices[length] = records | ||||
|     	end | ||||
| 
 | ||||
|            	if not archetype then | ||||
|           		return EmptyQuery | ||||
|            	end | ||||
|         compatible_archetypes = compatibleArchetypes | ||||
|         column_indices = indices | ||||
|         ids = components | ||||
| 
 | ||||
|             queryOutput = {} | ||||
|             queryLength = #components | ||||
|         lastArchetype = 1 | ||||
|        	archetype = compatible_archetypes[lastArchetype] | ||||
| 
 | ||||
|             entities =  archetype.entities | ||||
|             i = #entities | ||||
|        	if not archetype then | ||||
|       		return EmptyQuery | ||||
|        	end | ||||
| 
 | ||||
|             local it = { | ||||
|         		__iter = world_query_iter, | ||||
|         		next = world_query_next, | ||||
|         		without = world_query_without, | ||||
|                 with = world_query_with, | ||||
|         		replace = world_query_replace, | ||||
|         	} | ||||
|        	queryOutput = {} | ||||
|         queryLength = #ids | ||||
| 
 | ||||
|         	return setmetatable(it, it) :: any | ||||
|         end | ||||
|         entities = archetype.entities | ||||
|        	i = #entities | ||||
| 
 | ||||
|         local it = { | ||||
|             __iter = world_query_iter, | ||||
|             next = world_query_next, | ||||
|             with = world_query_with, | ||||
|             without = world_query_without, | ||||
|             replace = world_query_replace, | ||||
|         } :: any | ||||
| 
 | ||||
|         setmetatable(it, it) | ||||
| 
 | ||||
|        	return it | ||||
|     end | ||||
| end | ||||
| 
 | ||||
| type WorldIterator = (() -> (i53, { [unknown]: unknown? })) & (() -> ()) & (() -> i53) | ||||
|  | @ -998,11 +1045,13 @@ export type WorldShim = typeof(setmetatable( | |||
| 		--- Removes a component from the given entity | ||||
| 		remove: (WorldShim, id: Entity, component: Entity) -> (), | ||||
| 		--- Retrieves the value of up to 4 components. These values may be nil. | ||||
| 		get: (<A>(WorldShim, id: any, Entity<A>) -> A) | ||||
| 		get: (<A>(WorldShim, id: Entity, Entity<A>) -> A) | ||||
| 			& (<A, B>(WorldShim, id: Entity, Entity<A>, Entity<B>) -> (A, B)) | ||||
| 			& (<A, B, C>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>) -> (A, B, C)) | ||||
| 			& <A, B, C, D>(WorldShim, id: Entity, Entity<A>, Entity<B>, Entity<C>, Entity<D>) -> (A, B, C, D), | ||||
| 
 | ||||
| 		has: (WorldShim, Entity, ...Entity) -> boolean, | ||||
| 
 | ||||
| 		--- Searches the world for entities that match a given query | ||||
| 		query: (<A>(WorldShim, Entity<A>) -> QueryShim<A>) | ||||
| 			& (<A, B>(WorldShim, Entity<A>, Entity<B>) -> QueryShim<A, B>) | ||||
|  | @ -1104,6 +1153,7 @@ World.component = world_component | |||
| World.add = world_add | ||||
| World.set = world_set | ||||
| World.get = world_get | ||||
| World.has = world_has | ||||
| World.target = world_target | ||||
| World.parent = world_parent | ||||
| 
 | ||||
|  |  | |||
|  | @ -25,6 +25,24 @@ local N = 10 | |||
| type World = jecs.WorldShim | ||||
| 
 | ||||
| TEST("world", function() | ||||
|     do CASE "should allow remove a component that doesn't exist on entity" | ||||
| 		local world = jecs.World.new() | ||||
| 
 | ||||
| 		local Health = world:entity() | ||||
| 		local Poison = world:component() | ||||
| 
 | ||||
| 		local id = world:entity() | ||||
| 		do | ||||
| 	        world:remove(id, Poison) | ||||
| 			CHECK(true) -- Didn't error | ||||
| 		end | ||||
| 
 | ||||
| 		world:set(id, Health, 50) | ||||
| 		world:remove(id, Poison) | ||||
| 
 | ||||
| 		CHECK(world:get(id, Poison) == nil) | ||||
| 		CHECK(world:get(id, Health) == 50) | ||||
| 	end | ||||
| 	do CASE("should find every component id") | ||||
| 		local world = jecs.World.new() :: World | ||||
| 		local A = world:component() | ||||
|  | @ -60,10 +78,9 @@ TEST("world", function() | |||
| 		world:clear(e) | ||||
| 		CHECK(world:get(e, A) == nil) | ||||
| 		CHECK(world:get(e, B) == nil) | ||||
| 
 | ||||
| 	end | ||||
| 
 | ||||
| 	do CASE("iterator should not drain the query") | ||||
| 	do CASE("should drain query while iterating") | ||||
| 		local world = jecs.World.new() :: World | ||||
| 		local A = world:component() | ||||
| 		local B = world:component() | ||||
|  | @ -85,7 +102,8 @@ TEST("world", function() | |||
| 		for _ in q do | ||||
| 			j+=1 | ||||
| 		end | ||||
| 		CHECK(i	== j) | ||||
| 		CHECK(i == 2) | ||||
| 		CHECK(j == 0) | ||||
| 	end | ||||
| 
 | ||||
| 	do CASE("should be able to get next results") | ||||
|  | @ -224,20 +242,6 @@ TEST("world", function() | |||
| 		CHECK(world:get(id1, Health) == 50) | ||||
| 	end | ||||
| 
 | ||||
| 	do CASE("should allow remove that doesn't exist on entity") | ||||
| 		local world = jecs.World.new() | ||||
| 
 | ||||
| 		local Health = world:entity() | ||||
| 		local Poison = world:component() | ||||
| 
 | ||||
| 		local id = world:entity() | ||||
| 		world:set(id, Health, 50) | ||||
| 		world:remove(id, Poison) | ||||
| 
 | ||||
| 		CHECK(world:get(id, Poison) == nil) | ||||
| 		CHECK(world:get(id, Health) == 50) | ||||
| 	end | ||||
| 
 | ||||
| 	do CASE("should increment generation") | ||||
| 		local world = jecs.World.new() | ||||
| 		local e = world:entity() | ||||
|  | @ -530,6 +534,34 @@ TEST("world", function() | |||
| 
 | ||||
|         CHECK(withoutCount == 0) | ||||
|     end | ||||
| 
 | ||||
|     do CASE "should find Tag on entity" | ||||
|         local world = jecs.World.new() | ||||
| 
 | ||||
|         local Tag = world:component() | ||||
| 
 | ||||
|         local e = world:entity() | ||||
|         world:add(e, Tag) | ||||
| 
 | ||||
|         CHECK(world:has(e, Tag)) | ||||
|     end | ||||
| 
 | ||||
|     do CASE "should return false when missing one tag" | ||||
|            local world = jecs.World.new() | ||||
| 
 | ||||
|            local A = world:component() | ||||
|            local B = world:component() | ||||
|            local C = world:component() | ||||
|            local D = world:component() | ||||
| 
 | ||||
|            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 | ||||
| 
 | ||||
| end) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue