mirror of
				https://github.com/Ukendio/jecs.git
				synced 2025-11-04 02:49:18 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1664 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			1664 lines
		
	
	
	
		
			41 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
--!optimize 2
 | 
						|
--!native
 | 
						|
--!strict
 | 
						|
 | 
						|
local None = {}
 | 
						|
 | 
						|
local function merge(one, two)
 | 
						|
	local new = table.clone(one)
 | 
						|
 | 
						|
	for key, value in two do
 | 
						|
		if value == None then
 | 
						|
			new[key] = nil
 | 
						|
		else
 | 
						|
			new[key] = value
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	return new
 | 
						|
end
 | 
						|
 | 
						|
-- https://github.com/freddylist/llama/blob/master/src/List/toSet.lua
 | 
						|
local function toSet(list)
 | 
						|
	local set = {}
 | 
						|
 | 
						|
	for _, v in ipairs(list) do
 | 
						|
		set[v] = true
 | 
						|
	end
 | 
						|
 | 
						|
	return set
 | 
						|
end
 | 
						|
 | 
						|
-- https://github.com/freddylist/llama/blob/master/src/Dictionary/values.lua
 | 
						|
local function values(dictionary)
 | 
						|
	local valuesList = {}
 | 
						|
 | 
						|
	local index = 1
 | 
						|
 | 
						|
	for _, value in pairs(dictionary) do
 | 
						|
		valuesList[index] = value
 | 
						|
		index = index + 1
 | 
						|
	end
 | 
						|
 | 
						|
	return valuesList
 | 
						|
end
 | 
						|
 | 
						|
local valueIds = {}
 | 
						|
local nextValueId = 0
 | 
						|
local compatibilityCache = {}
 | 
						|
local archetypeCache = {}
 | 
						|
 | 
						|
local function getValueId(value)
 | 
						|
	local valueId = valueIds[value]
 | 
						|
	if valueId == nil then
 | 
						|
		valueIds[value] = nextValueId
 | 
						|
		valueId = nextValueId
 | 
						|
		nextValueId += 1
 | 
						|
	end
 | 
						|
 | 
						|
	return valueId
 | 
						|
end
 | 
						|
 | 
						|
function archetypeOf(...)
 | 
						|
	local length = select("#", ...)
 | 
						|
 | 
						|
	local currentNode = archetypeCache
 | 
						|
 | 
						|
	for i = 1, length do
 | 
						|
		local nextNode = currentNode[select(i, ...)]
 | 
						|
 | 
						|
		if not nextNode then
 | 
						|
			nextNode = {}
 | 
						|
			currentNode[select(i, ...)] = nextNode
 | 
						|
		end
 | 
						|
 | 
						|
		currentNode = nextNode
 | 
						|
	end
 | 
						|
 | 
						|
	if currentNode._archetype then
 | 
						|
		return currentNode._archetype
 | 
						|
	end
 | 
						|
 | 
						|
	local list = table.create(length)
 | 
						|
 | 
						|
	for i = 1, length do
 | 
						|
		list[i] = getValueId(select(i, ...))
 | 
						|
	end
 | 
						|
 | 
						|
	table.sort(list)
 | 
						|
 | 
						|
	local archetype = table.concat(list, "_")
 | 
						|
 | 
						|
	currentNode._archetype = archetype
 | 
						|
 | 
						|
	return archetype
 | 
						|
end
 | 
						|
 | 
						|
local stack = {}
 | 
						|
 | 
						|
local function newStackFrame(node)
 | 
						|
	return {
 | 
						|
		node = node,
 | 
						|
		accessedKeys = {},
 | 
						|
	}
 | 
						|
end
 | 
						|
 | 
						|
local function cleanup()
 | 
						|
	local currentFrame = stack[#stack]
 | 
						|
 | 
						|
	for baseKey, state in pairs(currentFrame.node.system) do
 | 
						|
		for key, value in pairs(state.storage) do
 | 
						|
			if not currentFrame.accessedKeys[baseKey] or not currentFrame.accessedKeys[baseKey][key] then
 | 
						|
				local cleanupCallback = state.cleanupCallback
 | 
						|
 | 
						|
				if cleanupCallback then
 | 
						|
					local shouldAbortCleanup = cleanupCallback(value)
 | 
						|
 | 
						|
					if shouldAbortCleanup then
 | 
						|
						continue
 | 
						|
					end
 | 
						|
				end
 | 
						|
 | 
						|
				state.storage[key] = nil
 | 
						|
			end
 | 
						|
		end
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
local function start(node, fn)
 | 
						|
	table.insert(stack, newStackFrame(node))
 | 
						|
	fn()
 | 
						|
	cleanup()
 | 
						|
	table.remove(stack, #stack)
 | 
						|
end
 | 
						|
 | 
						|
local function withinTopoContext()
 | 
						|
	return #stack ~= 0
 | 
						|
end
 | 
						|
 | 
						|
local function useFrameState()
 | 
						|
	return stack[#stack].node.frame
 | 
						|
end
 | 
						|
 | 
						|
local function useCurrentSystem()
 | 
						|
	if #stack == 0 then
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	return stack[#stack].node.currentSystem
 | 
						|
end
 | 
						|
 | 
						|
 | 
						|
--[=[
 | 
						|
	@within Matter
 | 
						|
 | 
						|
	:::tip
 | 
						|
	**Don't use this function directly in your systems.**
 | 
						|
 | 
						|
	This function is used for implementing your own topologically-aware functions. It should not be used in your
 | 
						|
	systems directly. You should use this function to implement your own utilities, similar to `useEvent` and
 | 
						|
	`useThrottle`.
 | 
						|
	:::
 | 
						|
 | 
						|
	`useHookState` does one thing: it returns a table. An empty, pristine table. Here's the cool thing though:
 | 
						|
	it always returns the *same* table, based on the script and line where *your function* (the function calling
 | 
						|
	`useHookState`) was called.
 | 
						|
 | 
						|
	### Uniqueness
 | 
						|
 | 
						|
	If your function is called multiple times from the same line, perhaps within a loop, the default behavior of
 | 
						|
	`useHookState` is to uniquely identify these by call count, and will return a unique table for each call.
 | 
						|
 | 
						|
	However, you can override this behavior: you can choose to key by any other value. This means that in addition to
 | 
						|
	script and line number, the storage will also only return the same table if the unique value (otherwise known as the
 | 
						|
	"discriminator") is the same.
 | 
						|
 | 
						|
	### Cleaning up
 | 
						|
	As a second optional parameter, you can pass a function that is automatically invoked when your storage is about
 | 
						|
	to be cleaned up. This happens when your function (and by extension, `useHookState`) ceases to be called again
 | 
						|
	next frame (keyed by script, line number, and discriminator).
 | 
						|
 | 
						|
	Your cleanup callback is passed the storage table that's about to be cleaned up. You can then perform cleanup work,
 | 
						|
	like disconnecting events.
 | 
						|
 | 
						|
	*Or*, you could return `true`, and abort cleaning up altogether. If you abort cleanup, your storage will stick
 | 
						|
	around another frame (even if your function wasn't called again). This can be used when you know that the user will
 | 
						|
	(or might) eventually call your function again, even if they didn't this frame. (For example, caching a value for
 | 
						|
	a number of seconds).
 | 
						|
 | 
						|
	If cleanup is aborted, your cleanup function will continue to be called every frame, until you don't abort cleanup,
 | 
						|
	or the user actually calls your function again.
 | 
						|
 | 
						|
	### Example: useThrottle
 | 
						|
 | 
						|
	This is the entire implementation of the built-in `useThrottle` function:
 | 
						|
 | 
						|
	```lua
 | 
						|
	local function cleanup(storage)
 | 
						|
		return os.clock() < storage.expiry
 | 
						|
	end
 | 
						|
 | 
						|
	local function useThrottle(seconds, discriminator)
 | 
						|
		local storage = useHookState(discriminator, cleanup)
 | 
						|
 | 
						|
		if storage.time == nil or os.clock() - storage.time >= seconds then
 | 
						|
			storage.time = os.clock()
 | 
						|
			storage.expiry = os.clock() + seconds
 | 
						|
			return true
 | 
						|
		end
 | 
						|
 | 
						|
		return false
 | 
						|
	end
 | 
						|
	```
 | 
						|
 | 
						|
	A lot of talk for something so simple, right?
 | 
						|
 | 
						|
	@param discriminator? any -- A unique value to additionally key by
 | 
						|
	@param cleanupCallback (storage: {}) -> boolean? -- A function to run when the storage for this hook is cleaned up
 | 
						|
]=]
 | 
						|
local function useHookState(discriminator, cleanupCallback): {}
 | 
						|
	local file, line = debug.info(3, "sl")
 | 
						|
	local fn = debug.info(2, "f")
 | 
						|
 | 
						|
	local baseKey = string.format("%s:%s:%d", tostring(fn), file, line)
 | 
						|
 | 
						|
	local currentFrame = stack[#stack]
 | 
						|
 | 
						|
	if currentFrame == nil then
 | 
						|
		error("Attempt to access topologically-aware storage outside of a Loop-system context.", 3)
 | 
						|
	end
 | 
						|
 | 
						|
	if not currentFrame.accessedKeys[baseKey] then
 | 
						|
		currentFrame.accessedKeys[baseKey] = {}
 | 
						|
	end
 | 
						|
 | 
						|
	local accessedKeys = currentFrame.accessedKeys[baseKey]
 | 
						|
 | 
						|
	local key = #accessedKeys
 | 
						|
 | 
						|
	if discriminator ~= nil then
 | 
						|
		if type(discriminator) == "number" then
 | 
						|
			discriminator = tostring(discriminator)
 | 
						|
		end
 | 
						|
 | 
						|
		key = discriminator
 | 
						|
	end
 | 
						|
 | 
						|
	accessedKeys[key] = true
 | 
						|
 | 
						|
	if not currentFrame.node.system[baseKey] then
 | 
						|
		currentFrame.node.system[baseKey] = {
 | 
						|
			storage = {},
 | 
						|
			cleanupCallback = cleanupCallback,
 | 
						|
		}
 | 
						|
	end
 | 
						|
 | 
						|
	local storage = currentFrame.node.system[baseKey].storage
 | 
						|
 | 
						|
	if not storage[key] then
 | 
						|
		storage[key] = {}
 | 
						|
	end
 | 
						|
 | 
						|
	return storage[key]
 | 
						|
end
 | 
						|
 | 
						|
local topoRuntime = {
 | 
						|
	start = start,
 | 
						|
	useHookState = useHookState,
 | 
						|
	useFrameState = useFrameState,
 | 
						|
	useCurrentSystem = useCurrentSystem,
 | 
						|
	withinTopoContext = withinTopoContext,
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
--[=[
 | 
						|
	@class Component
 | 
						|
 | 
						|
	A component is a named piece of data that exists on an entity.
 | 
						|
	Components are created and removed in the [World](/api/World).
 | 
						|
 | 
						|
	In the docs, the terms "Component" and "ComponentInstance" are used:
 | 
						|
	- **"Component"** refers to the base class of a specific type of component you've created.
 | 
						|
		This is what [`Matter.component`](/api/Matter#component) returns.
 | 
						|
	- **"Component Instance"** refers to an actual piece of data that can exist on an entity.
 | 
						|
		The metatable of a component instance table is its respective Component table.
 | 
						|
 | 
						|
	Component instances are *plain-old data*: they do not contain behaviors or methods.
 | 
						|
 | 
						|
	Since component instances are immutable, one helper function exists on all component instances, `patch`,
 | 
						|
	which allows reusing data from an existing component instance to make up for the ergonomic loss of mutations.
 | 
						|
]=]
 | 
						|
 | 
						|
--[=[
 | 
						|
	@within Component
 | 
						|
	@type ComponentInstance {}
 | 
						|
 | 
						|
	The `ComponentInstance` type refers to an actual piece of data that can exist on an entity.
 | 
						|
	The metatable of the component instance table is set to its particular Component table.
 | 
						|
 | 
						|
	A component instance can be created by calling the Component table:
 | 
						|
 | 
						|
	```lua
 | 
						|
	-- Component:
 | 
						|
	local MyComponent = Matter.component("My component")
 | 
						|
 | 
						|
	-- component instance:
 | 
						|
	local myComponentInstance = MyComponent({
 | 
						|
		some = "data"
 | 
						|
	})
 | 
						|
 | 
						|
	print(getmetatable(myComponentInstance) == MyComponent) --> true
 | 
						|
	```
 | 
						|
]=]
 | 
						|
 | 
						|
-- This is a special value we set inside the component's metatable that will allow us to detect when
 | 
						|
-- a Component is accidentally inserted as a Component Instance.
 | 
						|
-- It should not be accessible through indexing into a component instance directly.
 | 
						|
local DIAGNOSTIC_COMPONENT_MARKER = {}
 | 
						|
 | 
						|
local function newComponent(name, defaultData)
 | 
						|
	name = name or debug.info(2, "s") .. "@" .. debug.info(2, "l")
 | 
						|
 | 
						|
	assert(
 | 
						|
		defaultData == nil or type(defaultData) == "table",
 | 
						|
		"if component default data is specified, it must be a table"
 | 
						|
	)
 | 
						|
 | 
						|
	local component = {}
 | 
						|
	component.__index = component
 | 
						|
 | 
						|
	function component.new(data)
 | 
						|
		data = data or {}
 | 
						|
 | 
						|
		if defaultData then
 | 
						|
			data = merge(defaultData, data)
 | 
						|
		end
 | 
						|
 | 
						|
		return table.freeze(setmetatable(data, component))
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
	@within Component
 | 
						|
 | 
						|
	```lua
 | 
						|
	for id, target in world:query(Target) do
 | 
						|
		if shouldChangeTarget(target) then
 | 
						|
			world:insert(id, target:patch({ -- modify the existing component
 | 
						|
				currentTarget = getNewTarget()
 | 
						|
			}))
 | 
						|
		end
 | 
						|
	end
 | 
						|
	```
 | 
						|
 | 
						|
	A utility function used to immutably modify an existing component instance. Key/value pairs from the passed table
 | 
						|
	will override those of the existing component instance.
 | 
						|
 | 
						|
	As all components are immutable and frozen, it is not possible to modify the existing component directly.
 | 
						|
 | 
						|
	You can use the `Matter.None` constant to remove a value from the component instance:
 | 
						|
 | 
						|
	```lua
 | 
						|
	target:patch({
 | 
						|
		currentTarget = Matter.None -- sets currentTarget to nil
 | 
						|
	})
 | 
						|
	```
 | 
						|
 | 
						|
	@param partialNewData {} -- The table to be merged with the existing component data.
 | 
						|
	@return ComponentInstance -- A copy of the component instance with values from `partialNewData` overriding existing values.
 | 
						|
	]=]
 | 
						|
	function component:patch(partialNewData)
 | 
						|
		local patch = getmetatable(self).new(merge(self, partialNewData))
 | 
						|
		return patch
 | 
						|
	end
 | 
						|
 | 
						|
	setmetatable(component, {
 | 
						|
		__call = function(_, ...)
 | 
						|
			return component.new(...)
 | 
						|
		end,
 | 
						|
		__tostring = function()
 | 
						|
			return name
 | 
						|
		end,
 | 
						|
		[DIAGNOSTIC_COMPONENT_MARKER] = true,
 | 
						|
	})
 | 
						|
 | 
						|
	return component
 | 
						|
end
 | 
						|
 | 
						|
local function assertValidType(value, position)
 | 
						|
	if typeof(value) ~= "table" then
 | 
						|
		error(string.format("Component #%d is invalid: not a table", position), 3)
 | 
						|
	end
 | 
						|
 | 
						|
	local metatable = getmetatable(value)
 | 
						|
 | 
						|
	if metatable == nil then
 | 
						|
		error(string.format("Component #%d is invalid: has no metatable", position), 3)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
local function assertValidComponent(value, position)
 | 
						|
	assertValidType(value, position)
 | 
						|
 | 
						|
	local metatable = getmetatable(value)
 | 
						|
 | 
						|
	if getmetatable(metatable) ~= nil and getmetatable(metatable)[DIAGNOSTIC_COMPONENT_MARKER] then
 | 
						|
		error(
 | 
						|
			string.format(
 | 
						|
				"Component #%d is invalid: Component Instance %s was passed instead of the Component itself!",
 | 
						|
				position,
 | 
						|
				tostring(metatable)
 | 
						|
			),
 | 
						|
			3
 | 
						|
		)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
local function assertValidComponentInstance(value, position)
 | 
						|
	assertValidType(value, position)
 | 
						|
 | 
						|
	if getmetatable(value)[DIAGNOSTIC_COMPONENT_MARKER] ~= nil then
 | 
						|
		error(
 | 
						|
			string.format(
 | 
						|
				"Component #%d is invalid: passed a Component instead of a Component instance; "
 | 
						|
					.. "did you forget to call it as a function?",
 | 
						|
				position
 | 
						|
			),
 | 
						|
			3
 | 
						|
		)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
local ERROR_NO_ENTITY = "Entity doesn't exist, use world:contains to check if needed"
 | 
						|
local ERROR_DUPLICATE_ENTITY =
 | 
						|
	"The world already contains an entity with ID %d. Use World:replace instead if this is intentional."
 | 
						|
local ERROR_NO_COMPONENTS = "Missing components"
 | 
						|
 | 
						|
type i53 = number
 | 
						|
type i24 = number
 | 
						|
 | 
						|
type Component = { [any]: any }
 | 
						|
type ComponentInstance = Component
 | 
						|
 | 
						|
type Ty = { i53 }
 | 
						|
type ArchetypeId = number
 | 
						|
 | 
						|
type Column = { any }
 | 
						|
 | 
						|
type Archetype = {
 | 
						|
	-- Unique identifier of this archetype
 | 
						|
	id: number,
 | 
						|
	edges: {
 | 
						|
		[i24]: {
 | 
						|
			add: Archetype,
 | 
						|
			remove: Archetype,
 | 
						|
		},
 | 
						|
	},
 | 
						|
	types: Ty,
 | 
						|
	type: string | number,
 | 
						|
	entities: { number },
 | 
						|
	columns: { Column },
 | 
						|
	records: {},
 | 
						|
}
 | 
						|
 | 
						|
type Record = {
 | 
						|
	archetype: Archetype,
 | 
						|
	row: number,
 | 
						|
}
 | 
						|
 | 
						|
type EntityIndex = { [i24]: Record }
 | 
						|
type ComponentIndex = { [i24]: ArchetypeMap }
 | 
						|
 | 
						|
type ArchetypeRecord = number
 | 
						|
type ArchetypeMap = { sparse: { [ArchetypeId]: ArchetypeRecord }, size: number }
 | 
						|
type Archetypes = { [ArchetypeId]: Archetype }
 | 
						|
 | 
						|
local function transitionArchetype(
 | 
						|
	entityIndex: EntityIndex,
 | 
						|
	to: Archetype,
 | 
						|
	destinationRow: i24,
 | 
						|
	from: Archetype,
 | 
						|
	sourceRow: i24
 | 
						|
)
 | 
						|
	-- local columns = sourceArchetype.columns
 | 
						|
	-- local sourceEntities = sourceArchetype.entities
 | 
						|
	-- local destinationEntities = destinationArchetype.entities
 | 
						|
	-- local destinationColumns = destinationArchetype.columns
 | 
						|
 | 
						|
	local columns = from.columns
 | 
						|
	local sourceEntities = from.entities
 | 
						|
	local destinationEntities = to.entities
 | 
						|
	local destinationColumns = to.columns
 | 
						|
	local tr = to.records
 | 
						|
	local types = from.types
 | 
						|
 | 
						|
	for componentId, column in columns do
 | 
						|
		local targetColumn = destinationColumns[tr[types[componentId]]]
 | 
						|
		if targetColumn then
 | 
						|
			targetColumn[destinationRow] = column[sourceRow]
 | 
						|
		end
 | 
						|
 | 
						|
		if sourceRow ~= #column then
 | 
						|
			column[sourceRow] = column[#column]
 | 
						|
			column[#column] = nil
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	destinationEntities[destinationRow] = sourceEntities[sourceRow]
 | 
						|
	entityIndex[sourceEntities[sourceRow]].row = destinationRow
 | 
						|
 | 
						|
	local movedAway = #sourceEntities
 | 
						|
	if sourceRow ~= movedAway then
 | 
						|
		sourceEntities[sourceRow] = sourceEntities[movedAway]
 | 
						|
		entityIndex[sourceEntities[movedAway]].row = sourceRow
 | 
						|
	end
 | 
						|
 | 
						|
	sourceEntities[movedAway] = nil
 | 
						|
end
 | 
						|
 | 
						|
local function archetypeAppend(entity: i53, archetype: Archetype): i24
 | 
						|
	local entities = archetype.entities
 | 
						|
	table.insert(entities, entity)
 | 
						|
	return #entities
 | 
						|
end
 | 
						|
 | 
						|
local function newEntity(entityId: i53, record: Record, archetype: Archetype)
 | 
						|
	local row = archetypeAppend(entityId, archetype)
 | 
						|
	record.archetype = archetype
 | 
						|
	record.row = row
 | 
						|
	return record
 | 
						|
end
 | 
						|
 | 
						|
local function moveEntity(entityIndex, entityId: i53, record: Record, to: Archetype)
 | 
						|
	local sourceRow = record.row
 | 
						|
	local from = record.archetype
 | 
						|
	local destinationRow = archetypeAppend(entityId, to)
 | 
						|
	transitionArchetype(entityIndex, to, destinationRow, from, sourceRow)
 | 
						|
	record.archetype = to
 | 
						|
	record.row = destinationRow
 | 
						|
end
 | 
						|
 | 
						|
local function hash(arr): string | number
 | 
						|
	return table.concat(arr, "_")
 | 
						|
end
 | 
						|
 | 
						|
local function createArchetypeRecords(componentIndex: ComponentIndex, to: Archetype)
 | 
						|
	local destinationCount = #to.types
 | 
						|
	local destinationIds = to.types
 | 
						|
 | 
						|
	for i = 1, destinationCount do
 | 
						|
		local destinationId = destinationIds[i]
 | 
						|
 | 
						|
		if not componentIndex[destinationId] then
 | 
						|
			componentIndex[destinationId] = { sparse = {}, size = 0 }
 | 
						|
		end
 | 
						|
		componentIndex[destinationId].sparse[to.id] = i
 | 
						|
		to.records[destinationId] = i
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
local function archetypeOf(world: World, types: { i24 }, prev: Archetype?): Archetype
 | 
						|
	local ty = hash(types)
 | 
						|
 | 
						|
	world.nextArchetypeId = (world.nextArchetypeId :: number) + 1
 | 
						|
	local id = world.nextArchetypeId
 | 
						|
 | 
						|
	local columns = {} :: { any }
 | 
						|
 | 
						|
	for _ in types do
 | 
						|
		table.insert(columns, {})
 | 
						|
	end
 | 
						|
 | 
						|
	local archetype = {
 | 
						|
		id = id,
 | 
						|
		types = types,
 | 
						|
		type = ty,
 | 
						|
		columns = columns,
 | 
						|
		entities = {},
 | 
						|
		edges = {},
 | 
						|
		records = {},
 | 
						|
	}
 | 
						|
 | 
						|
	world.archetypeIndex[ty] = archetype
 | 
						|
	world.archetypes[id] = archetype
 | 
						|
 | 
						|
	if #types > 0 then
 | 
						|
		createArchetypeRecords(world.componentIndex, archetype, prev)
 | 
						|
	end
 | 
						|
 | 
						|
	return archetype
 | 
						|
end
 | 
						|
 | 
						|
local World = {}
 | 
						|
World.__index = World
 | 
						|
 | 
						|
function World.new()
 | 
						|
	local self = setmetatable({
 | 
						|
		entityIndex = {},
 | 
						|
		componentIndex = {},
 | 
						|
		archetypes = {},
 | 
						|
		archetypeIndex = {},
 | 
						|
		nextId = 0,
 | 
						|
		nextArchetypeId = 0,
 | 
						|
		_size = 0,
 | 
						|
		_changedStorage = {},
 | 
						|
	}, World)
 | 
						|
 | 
						|
	self.ROOT_ARCHETYPE = archetypeOf(self, {}, nil)
 | 
						|
	return self
 | 
						|
end
 | 
						|
 | 
						|
type World = typeof(World.new())
 | 
						|
 | 
						|
local function ensureArchetype(world: World, types, prev)
 | 
						|
	if #types < 1 then
 | 
						|
		return world.ROOT_ARCHETYPE
 | 
						|
	end
 | 
						|
 | 
						|
	local ty = hash(types)
 | 
						|
	local archetype = world.archetypeIndex[ty]
 | 
						|
	if archetype then
 | 
						|
		return archetype
 | 
						|
	end
 | 
						|
 | 
						|
	return archetypeOf(world, types, prev)
 | 
						|
end
 | 
						|
 | 
						|
local function findInsert(types: { i53 }, toAdd: i53)
 | 
						|
	local count = #types
 | 
						|
	for i = 1, count do
 | 
						|
		local id = types[i]
 | 
						|
		if id == toAdd then
 | 
						|
			return -1
 | 
						|
		end
 | 
						|
		if id > toAdd then
 | 
						|
			return i
 | 
						|
		end
 | 
						|
	end
 | 
						|
	return count + 1
 | 
						|
end
 | 
						|
 | 
						|
local function findArchetypeWith(world: World, node: Archetype, componentId: i53)
 | 
						|
	local types = node.types
 | 
						|
	local at = findInsert(types, componentId)
 | 
						|
	if at == -1 then
 | 
						|
		return node
 | 
						|
	end
 | 
						|
 | 
						|
	local destinationType = table.clone(node.types)
 | 
						|
	table.insert(destinationType, at, componentId)
 | 
						|
	return ensureArchetype(world, destinationType, node)
 | 
						|
end
 | 
						|
 | 
						|
local function ensureEdge(archetype: Archetype, componentId: i53)
 | 
						|
	if not archetype.edges[componentId] then
 | 
						|
		archetype.edges[componentId] = {} :: any
 | 
						|
	end
 | 
						|
	return archetype.edges[componentId]
 | 
						|
end
 | 
						|
 | 
						|
local function archetypeTraverseAdd(world: World, componentId: i53, archetype: Archetype?): Archetype
 | 
						|
	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
 | 
						|
	local edge = ensureEdge(from, componentId)
 | 
						|
 | 
						|
	if not edge.add then
 | 
						|
		edge.add = findArchetypeWith(world, from, componentId)
 | 
						|
	end
 | 
						|
 | 
						|
	return edge.add
 | 
						|
end
 | 
						|
 | 
						|
local function componentAdd(world: World, entityId: i53, componentInstance)
 | 
						|
	local componentId = #getmetatable(componentInstance)
 | 
						|
 | 
						|
	local record = world:ensureRecord(entityId)
 | 
						|
	local sourceArchetype = record.archetype
 | 
						|
	local destinationArchetype = archetypeTraverseAdd(world, componentId, sourceArchetype)
 | 
						|
 | 
						|
	if sourceArchetype == destinationArchetype then
 | 
						|
		local archetypeRecord = destinationArchetype.records[componentId]
 | 
						|
		destinationArchetype.columns[archetypeRecord][record.row] = componentInstance
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	if sourceArchetype then
 | 
						|
		moveEntity(world.entityIndex, entityId, record, destinationArchetype)
 | 
						|
	else
 | 
						|
		-- if it has any components, then it wont be the root archetype
 | 
						|
		if #destinationArchetype.types > 0 then
 | 
						|
			newEntity(entityId, record, destinationArchetype)
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	local archetypeRecord = destinationArchetype.records[componentId]
 | 
						|
	destinationArchetype.columns[archetypeRecord][record.row] = componentInstance
 | 
						|
end
 | 
						|
 | 
						|
function World.ensureRecord(world: World, entityId: i53)
 | 
						|
	local entityIndex = world.entityIndex
 | 
						|
	local id = entityId
 | 
						|
	if not entityIndex[id] then
 | 
						|
		entityIndex[id] = {} :: Record
 | 
						|
	end
 | 
						|
	return entityIndex[id]
 | 
						|
end
 | 
						|
 | 
						|
local function archetypeTraverseRemove(world: World, componentId: i53, archetype: Archetype?): Archetype
 | 
						|
	local from = (archetype or world.ROOT_ARCHETYPE) :: Archetype
 | 
						|
	local edge = ensureEdge(from, componentId)
 | 
						|
 | 
						|
	if not edge.remove then
 | 
						|
		local to = table.clone(from.types)
 | 
						|
		table.remove(to, table.find(to, componentId))
 | 
						|
		edge.remove = ensureArchetype(world, to, from)
 | 
						|
	end
 | 
						|
 | 
						|
	return edge.remove
 | 
						|
end
 | 
						|
 | 
						|
local function get(componentIndex: ComponentIndex, record: Record, componentId: i24): ComponentInstance?
 | 
						|
	local archetype = record.archetype
 | 
						|
    if not archetype then 
 | 
						|
        return
 | 
						|
    end
 | 
						|
	local map = componentIndex[componentId]
 | 
						|
	if map == nil then
 | 
						|
		return nil
 | 
						|
	end
 | 
						|
 | 
						|
	local archetypeRecord = map.sparse[archetype.id]
 | 
						|
	if not archetypeRecord then
 | 
						|
		return nil
 | 
						|
	end
 | 
						|
 | 
						|
	return archetype.columns[archetypeRecord][record.row]
 | 
						|
end
 | 
						|
 | 
						|
local function componentRemove(world: World, entityId: i53, component: Component)
 | 
						|
	local componentId = #component
 | 
						|
	local record = world:ensureRecord(entityId)
 | 
						|
	local sourceArchetype = record.archetype
 | 
						|
	local destinationArchetype = archetypeTraverseRemove(world, componentId, sourceArchetype)
 | 
						|
 | 
						|
	-- TODO:
 | 
						|
	-- There is a better way to get the component for returning
 | 
						|
	local componentInstance = get(world.componentIndex, record, componentId)
 | 
						|
	if sourceArchetype and not (sourceArchetype == destinationArchetype) then
 | 
						|
		moveEntity(world.entityIndex, entityId, record, destinationArchetype)
 | 
						|
	end
 | 
						|
 | 
						|
	return componentInstance
 | 
						|
end
 | 
						|
 | 
						|
--[=[
 | 
						|
	Removes a component (or set of components) from an existing entity.
 | 
						|
 | 
						|
	```lua
 | 
						|
	local removedA, removedB = world:remove(entityId, ComponentA, ComponentB)
 | 
						|
	```
 | 
						|
 | 
						|
	@param entityId number -- The entity ID
 | 
						|
	@param ... Component -- The components to remove
 | 
						|
	@return ...ComponentInstance -- Returns the component instance values that were removed in the order they were passed.
 | 
						|
]=]
 | 
						|
function World.remove(world: World, entityId: i53, ...)
 | 
						|
	if not world:contains(entityId) then
 | 
						|
		error(ERROR_NO_ENTITY, 2)
 | 
						|
	end
 | 
						|
 | 
						|
	local length = select("#", ...)
 | 
						|
	local removed = {}
 | 
						|
	for i = 1, length do
 | 
						|
		table.insert(removed, componentRemove(world, entityId, select(i, ...)))
 | 
						|
	end
 | 
						|
 | 
						|
	return unpack(removed, 1, length)
 | 
						|
end
 | 
						|
 | 
						|
function World.get(
 | 
						|
	world: World,
 | 
						|
	entityId: i53,
 | 
						|
	a: Component,
 | 
						|
	b: Component?,
 | 
						|
	c: Component?,
 | 
						|
	d: Component?,
 | 
						|
	e: Component?
 | 
						|
): any
 | 
						|
	local componentIndex = world.componentIndex
 | 
						|
	local record = world.entityIndex[entityId]
 | 
						|
	if not record then
 | 
						|
		return nil
 | 
						|
	end
 | 
						|
 | 
						|
	local va = get(componentIndex, record, #a)
 | 
						|
 | 
						|
	if b == nil then
 | 
						|
		return va
 | 
						|
	elseif c == nil then
 | 
						|
		return va, get(componentIndex, record, #b)
 | 
						|
	elseif d == nil then
 | 
						|
		return va, get(componentIndex, record, #b), get(componentIndex, record, #c)
 | 
						|
	elseif e == nil then
 | 
						|
		return va, get(componentIndex, record, #b), get(componentIndex, record, #c), get(componentIndex, record, #d)
 | 
						|
	else
 | 
						|
		error("args exceeded")
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function World.insert(world: World, entityId: i53, ...)
 | 
						|
	if not world:contains(entityId) then
 | 
						|
		error(ERROR_NO_ENTITY, 2)
 | 
						|
	end
 | 
						|
 | 
						|
	for i = 1, select("#", ...) do
 | 
						|
		local newComponent = select(i, ...)
 | 
						|
		assertValidComponentInstance(newComponent, i)
 | 
						|
 | 
						|
		local metatable = getmetatable(newComponent)
 | 
						|
		local oldComponent = world:get(entityId, metatable)
 | 
						|
		componentAdd(world, entityId, newComponent)
 | 
						|
 | 
						|
		world:_trackChanged(metatable, entityId, oldComponent, newComponent)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function World.replace(world: World, entityId: i53, ...: ComponentInstance)
 | 
						|
	error("Replace is unimplemented")
 | 
						|
 | 
						|
	if not world:contains(entityId) then
 | 
						|
		error(ERROR_NO_ENTITY, 2)
 | 
						|
	end
 | 
						|
 | 
						|
	--moveEntity(entityId, record, world.ROOT_ARCHETYPE)
 | 
						|
	for i = 1, select("#", ...) do
 | 
						|
		local newComponent = select(i, ...)
 | 
						|
		assertValidComponentInstance(newComponent, i)
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function World.entity(world: World)
 | 
						|
	world.nextId += 1
 | 
						|
	return world.nextId
 | 
						|
end
 | 
						|
 | 
						|
function World:__iter()
 | 
						|
	return error("NOT IMPLEMENTED YET")
 | 
						|
end
 | 
						|
 | 
						|
function World._trackChanged(world: World, metatable, id, old, new)
 | 
						|
	if not world._changedStorage[metatable] then
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	if old == new then
 | 
						|
		return
 | 
						|
	end
 | 
						|
 | 
						|
	local record = table.freeze({
 | 
						|
		old = old,
 | 
						|
		new = new,
 | 
						|
	})
 | 
						|
 | 
						|
	for _, storage in ipairs(world._changedStorage[metatable]) do
 | 
						|
		-- If this entity has changed since the last time this system read it,
 | 
						|
		-- we ensure that the "old" value is whatever the system saw it as last, instead of the
 | 
						|
		-- "old" value we have here.
 | 
						|
		if storage[id] then
 | 
						|
			storage[id] = table.freeze({ old = storage[id].old, new = new })
 | 
						|
		else
 | 
						|
			storage[id] = record
 | 
						|
		end
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
--[=[
 | 
						|
	Spawns a new entity in the world with a specific entity ID and given components.
 | 
						|
 | 
						|
	The next ID generated from [World:spawn] will be increased as needed to never collide with a manually specified ID.
 | 
						|
 | 
						|
	@param entityId number -- The entity ID to spawn with
 | 
						|
	@param ... ComponentInstance -- The component values to spawn the entity with.
 | 
						|
	@return number -- The same entity ID that was passed in
 | 
						|
]=]
 | 
						|
function World.spawnAt(world: World, entityId: i53, ...: ComponentInstance)
 | 
						|
	if world:contains(entityId) then
 | 
						|
		error(string.format(ERROR_DUPLICATE_ENTITY, entityId), 2)
 | 
						|
	end
 | 
						|
 | 
						|
	if entityId >= world.nextId then
 | 
						|
		world.nextId = entityId + 1
 | 
						|
	end
 | 
						|
 | 
						|
	world._size += 1
 | 
						|
	world:ensureRecord(entityId)
 | 
						|
 | 
						|
	local components = {}
 | 
						|
	for i = 1, select("#", ...) do
 | 
						|
		local component = select(i, ...)
 | 
						|
		assertValidComponentInstance(component, i)
 | 
						|
 | 
						|
		local metatable = getmetatable(component)
 | 
						|
		if components[metatable] then
 | 
						|
			error(("Duplicate component type at index %d"):format(i), 2)
 | 
						|
		end
 | 
						|
 | 
						|
		world:_trackChanged(metatable, entityId, nil, component)
 | 
						|
 | 
						|
		components[metatable] = component
 | 
						|
		componentAdd(world, entityId, component)
 | 
						|
	end
 | 
						|
 | 
						|
	return entityId
 | 
						|
end
 | 
						|
 | 
						|
--[=[
 | 
						|
	Spawns a new entity in the world with the given components.
 | 
						|
 | 
						|
	@param ... ComponentInstance -- The component values to spawn the entity with.
 | 
						|
	@return number -- The new entity ID.
 | 
						|
]=]
 | 
						|
function World.spawn(world: World, ...: ComponentInstance)
 | 
						|
	return world:spawnAt(world.nextId, ...)
 | 
						|
end
 | 
						|
 | 
						|
function World.despawn(world: World, entityId: i53)
 | 
						|
	local entityIndex = world.entityIndex
 | 
						|
	local record = entityIndex[entityId]
 | 
						|
	moveEntity(entityIndex, entityId, record, world.ROOT_ARCHETYPE)
 | 
						|
	world.ROOT_ARCHETYPE.entities[record.row] = nil
 | 
						|
	entityIndex[entityId] = nil
 | 
						|
	world._size -= 1
 | 
						|
end
 | 
						|
 | 
						|
function World.clear(world: World)
 | 
						|
	world.entityIndex = {}
 | 
						|
	world.componentIndex = {}
 | 
						|
	world.archetypes = {}
 | 
						|
	world.archetypeIndex = {}
 | 
						|
	world._size = 0
 | 
						|
	world.ROOT_ARCHETYPE = archetypeOf(world, {}, nil)
 | 
						|
end
 | 
						|
 | 
						|
function World.size(world: World)
 | 
						|
	return world._size
 | 
						|
end
 | 
						|
 | 
						|
function World.contains(world: World, entityId: i53)
 | 
						|
	return world.entityIndex[entityId] ~= nil
 | 
						|
end
 | 
						|
 | 
						|
local function noop(): any
 | 
						|
	return function() end
 | 
						|
end
 | 
						|
 | 
						|
local emptyQueryResult = setmetatable({
 | 
						|
	next = function() end,
 | 
						|
	snapshot = function()
 | 
						|
		return {}
 | 
						|
	end,
 | 
						|
	without = function(self)
 | 
						|
		return self
 | 
						|
	end,
 | 
						|
	view = function()
 | 
						|
		return {
 | 
						|
			get = function() end,
 | 
						|
			contains = function() end,
 | 
						|
		}
 | 
						|
	end,
 | 
						|
}, {
 | 
						|
	__iter = noop,
 | 
						|
	__call = noop,
 | 
						|
})
 | 
						|
 | 
						|
local function queryResult(compatibleArchetypes, components: { number }, queryLength, ...): any
 | 
						|
	local a: any, b: any, c: any, d: any, e: any = ...
 | 
						|
	local lastArchetype, archetype = next(compatibleArchetypes)
 | 
						|
	if not lastArchetype then
 | 
						|
		return emptyQueryResult
 | 
						|
	end
 | 
						|
 | 
						|
	local lastRow
 | 
						|
	local queryOutput = {}
 | 
						|
	local function iterate()
 | 
						|
		local row = next(archetype.entities, lastRow)
 | 
						|
		while row == nil do
 | 
						|
			lastArchetype, archetype = next(compatibleArchetypes, lastArchetype)
 | 
						|
			if lastArchetype == nil then
 | 
						|
				return
 | 
						|
			end
 | 
						|
			row = next(archetype.entities, row)
 | 
						|
		end
 | 
						|
 | 
						|
		lastRow = row
 | 
						|
 | 
						|
		local columns = archetype.columns
 | 
						|
		local entityId = archetype.entities[row :: number]
 | 
						|
		local archetypeRecords = archetype.records
 | 
						|
 | 
						|
		if queryLength == 1 then
 | 
						|
			return entityId, columns[archetypeRecords[a]][row]
 | 
						|
		elseif queryLength == 2 then
 | 
						|
			return entityId, columns[archetypeRecords[a]][row], columns[archetypeRecords[b]][row]
 | 
						|
		elseif queryLength == 3 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row]
 | 
						|
		elseif queryLength == 4 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row],
 | 
						|
				columns[archetypeRecords[d]][row]
 | 
						|
		elseif queryLength == 5 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row],
 | 
						|
				columns[archetypeRecords[d]][row],
 | 
						|
				columns[archetypeRecords[e]][row]
 | 
						|
		end
 | 
						|
 | 
						|
		for i, componentId in components do
 | 
						|
			queryOutput[i] = columns[archetypeRecords[componentId]][row]
 | 
						|
		end
 | 
						|
 | 
						|
		return entityId, unpack(queryOutput, 1, queryLength)
 | 
						|
	end
 | 
						|
	--[=[
 | 
						|
		@class QueryResult
 | 
						|
 | 
						|
		A result from the [`World:query`](/api/World#query) function.
 | 
						|
 | 
						|
		Calling the table or the `next` method allows iteration over the results. Once all results have been returned, the
 | 
						|
		QueryResult is exhausted and is no longer useful.
 | 
						|
 | 
						|
		```lua
 | 
						|
		for id, enemy, charge, model in world:query(Enemy, Charge, Model) do
 | 
						|
			-- Do something
 | 
						|
		end
 | 
						|
		```
 | 
						|
	]=]
 | 
						|
	local QueryResult = {}
 | 
						|
	QueryResult.__index = QueryResult
 | 
						|
 | 
						|
	-- TODO:
 | 
						|
	-- remove in matter 1.0
 | 
						|
	function QueryResult:__call()
 | 
						|
		return iterate()
 | 
						|
	end
 | 
						|
 | 
						|
	function QueryResult:__iter()
 | 
						|
		return function()
 | 
						|
			return iterate()
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
		Returns an iterator that will skip any entities that also have the given components.
 | 
						|
 | 
						|
		@param ... Component -- The component types to filter against.
 | 
						|
		@return () -> (id, ...ComponentInstance) -- Iterator of entity ID followed by the requested component values
 | 
						|
 | 
						|
		```lua
 | 
						|
		for id in world:query(Target):without(Model) do
 | 
						|
			-- Do something
 | 
						|
		end
 | 
						|
		```
 | 
						|
	]=]
 | 
						|
	function QueryResult:without(...)
 | 
						|
		local components = { ... }
 | 
						|
		for i, component in components do
 | 
						|
			components[i] = #component
 | 
						|
		end
 | 
						|
 | 
						|
		local compatibleArchetypes = compatibleArchetypes
 | 
						|
		for i = #compatibleArchetypes, 1, -1 do
 | 
						|
			local archetype = compatibleArchetypes[i]
 | 
						|
			local shouldRemove = false
 | 
						|
			for _, componentId in components do
 | 
						|
				if archetype.records[componentId] then
 | 
						|
					shouldRemove = true
 | 
						|
					break
 | 
						|
				end
 | 
						|
			end
 | 
						|
 | 
						|
			if shouldRemove then
 | 
						|
				table.remove(compatibleArchetypes, i)
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		lastArchetype, archetype = next(compatibleArchetypes)
 | 
						|
		if not lastArchetype then
 | 
						|
			return emptyQueryResult
 | 
						|
		end
 | 
						|
 | 
						|
		return self
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
		Returns the next set of values from the query result. Once all results have been returned, the
 | 
						|
		QueryResult is exhausted and is no longer useful.
 | 
						|
 | 
						|
		:::info
 | 
						|
		This function is equivalent to calling the QueryResult as a function. When used in a for loop, this is implicitly
 | 
						|
		done by the language itself.
 | 
						|
		:::
 | 
						|
 | 
						|
		```lua
 | 
						|
		-- Using world:query in this position will make Lua invoke the table as a function. This is conventional.
 | 
						|
		for id, enemy, charge, model in world:query(Enemy, Charge, Model) do
 | 
						|
			-- Do something
 | 
						|
		end
 | 
						|
		```
 | 
						|
 | 
						|
		If you wanted to iterate over the QueryResult without a for loop, it's recommended that you call `next` directly
 | 
						|
		instead of calling the QueryResult as a function.
 | 
						|
		```lua
 | 
						|
		local id, enemy, charge, model = world:query(Enemy, Charge, Model):next()
 | 
						|
		local id, enemy, charge, model = world:query(Enemy, Charge, Model)() -- Possible, but unconventional
 | 
						|
		```
 | 
						|
 | 
						|
		@return id -- Entity ID
 | 
						|
		@return ...ComponentInstance -- The requested component values
 | 
						|
	]=]
 | 
						|
	function QueryResult:next()
 | 
						|
		return iterate()
 | 
						|
	end
 | 
						|
 | 
						|
	local function drain()
 | 
						|
		local entry = table.pack(iterate())
 | 
						|
		return if entry.n > 0 then entry else nil
 | 
						|
	end
 | 
						|
 | 
						|
	local Snapshot = {
 | 
						|
		__iter = function(self): any
 | 
						|
			local i = 0
 | 
						|
			return function()
 | 
						|
				i += 1
 | 
						|
 | 
						|
				local data = self[i] :: any
 | 
						|
 | 
						|
				if data then
 | 
						|
					return unpack(data, 1, data.n)
 | 
						|
				end
 | 
						|
 | 
						|
				return
 | 
						|
			end
 | 
						|
		end,
 | 
						|
	}
 | 
						|
 | 
						|
	function QueryResult:snapshot()
 | 
						|
		local list = setmetatable({}, Snapshot) :: any
 | 
						|
		for entry in drain do
 | 
						|
			table.insert(list, entry)
 | 
						|
		end
 | 
						|
 | 
						|
		return list
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
		Creates a View of the query and does all of the iterator tasks at once at an amortized cost.
 | 
						|
		This is used for many repeated random access to an entity. If you only need to iterate, just use a query.
 | 
						|
 | 
						|
		```lua
 | 
						|
		local inflicting = world:query(Damage, Hitting, Player):view()
 | 
						|
		for _, source in world:query(DamagedBy) do
 | 
						|
			local damage = inflicting:get(source.from)
 | 
						|
		end
 | 
						|
 | 
						|
		for _ in world:query(Damage):view() do end -- You can still iterate views if you want!
 | 
						|
		```
 | 
						|
		
 | 
						|
		@return View See [View](/api/View) docs.
 | 
						|
	]=]
 | 
						|
	function QueryResult:view()
 | 
						|
		local fetches = {}
 | 
						|
		local list = {} :: any
 | 
						|
 | 
						|
		local View = {}
 | 
						|
		View.__index = View
 | 
						|
 | 
						|
		function View:__iter()
 | 
						|
			local current = list.head
 | 
						|
			return function()
 | 
						|
				if not current then
 | 
						|
					return
 | 
						|
				end
 | 
						|
				local entity = current.entity
 | 
						|
				local fetch = fetches[entity]
 | 
						|
				current = current.next
 | 
						|
 | 
						|
				return entity, unpack(fetch, 1, fetch.n)
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		--[=[
 | 
						|
			@within View
 | 
						|
				Retrieve the query results to corresponding `entity`
 | 
						|
			@param entity number - the entity ID
 | 
						|
			@return ...ComponentInstance
 | 
						|
		]=]
 | 
						|
		function View:get(entity)
 | 
						|
			if not self:contains(entity) then
 | 
						|
				return
 | 
						|
			end
 | 
						|
 | 
						|
			local fetch = fetches[entity]
 | 
						|
			local queryLength = fetch.n
 | 
						|
 | 
						|
			if queryLength == 1 then
 | 
						|
				return fetch[1]
 | 
						|
			elseif queryLength == 2 then
 | 
						|
				return fetch[1], fetch[2]
 | 
						|
			elseif queryLength == 3 then
 | 
						|
				return fetch[1], fetch[2], fetch[3]
 | 
						|
			elseif queryLength == 4 then
 | 
						|
				return fetch[1], fetch[2], fetch[3], fetch[4]
 | 
						|
			elseif queryLength == 5 then
 | 
						|
				return fetch[1], fetch[2], fetch[3], fetch[4], fetch[5]
 | 
						|
			end
 | 
						|
 | 
						|
			return unpack(fetch, 1, fetch.n)
 | 
						|
		end
 | 
						|
 | 
						|
		--[=[
 | 
						|
			@within View
 | 
						|
			Equivalent to `world:contains()`	
 | 
						|
			@param entity number - the entity ID
 | 
						|
			@return boolean 
 | 
						|
		]=]
 | 
						|
		function View:contains(entity)
 | 
						|
			return fetches[entity] ~= nil
 | 
						|
		end
 | 
						|
 | 
						|
		for entry in drain do
 | 
						|
			local entityId = entry[1]
 | 
						|
			local fetch = table.pack(select(2, unpack(entry)))
 | 
						|
			local node = { entity = entityId, next = nil }
 | 
						|
			fetches[entityId] = fetch
 | 
						|
 | 
						|
			if not list.head then
 | 
						|
				list.head = node
 | 
						|
			else
 | 
						|
				local current = list.head
 | 
						|
				while current.next do
 | 
						|
					current = current.next
 | 
						|
				end
 | 
						|
				current.next = node
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		return setmetatable({}, View)
 | 
						|
	end
 | 
						|
 | 
						|
	return setmetatable({}, QueryResult)
 | 
						|
end
 | 
						|
 | 
						|
--[=[
 | 
						|
	Performs a query against the entities in this World. Returns a [QueryResult](/api/QueryResult), which iterates over
 | 
						|
	the results of the query.
 | 
						|
 | 
						|
	Order of iteration is not guaranteed.
 | 
						|
 | 
						|
	```lua
 | 
						|
	for id, enemy, charge, model in world:query(Enemy, Charge, Model) do
 | 
						|
		-- Do something
 | 
						|
	end
 | 
						|
 | 
						|
	for id in world:query(Target):without(Model) do
 | 
						|
		-- Again, with feeling
 | 
						|
	end
 | 
						|
	```
 | 
						|
 | 
						|
	@param ... Component -- The component types to query. Only entities with *all* of these components will be returned.
 | 
						|
	@return QueryResult -- See [QueryResult](/api/QueryResult) docs.
 | 
						|
]=]function World.query(world: World, ...: Component): any
 | 
						|
	local compatibleArchetypes = {}
 | 
						|
	local components = { ... }
 | 
						|
	local archetypes = world.archetypes
 | 
						|
	local queryLength = select("#", ...)
 | 
						|
	local a: any, b: any, c: any, d: any, e: any = ...
 | 
						|
 | 
						|
	if queryLength == 0 then
 | 
						|
		-- TODO:
 | 
						|
		-- return noop query
 | 
						|
		warn("TODO noop query")
 | 
						|
	end
 | 
						|
 | 
						|
	if queryLength == 1 then
 | 
						|
		a = #a
 | 
						|
		components = { a }
 | 
						|
		-- local archetypesMap = world.componentIndex[a]
 | 
						|
		-- components = { a }
 | 
						|
		-- local function single()
 | 
						|
		-- 	local id = next(archetypesMap)
 | 
						|
		-- 	local archetype = archetypes[id :: number]
 | 
						|
		-- 	local lastRow
 | 
						|
 | 
						|
		-- 	return function(): any
 | 
						|
		-- 		local row, entity = next(archetype.entities, lastRow)
 | 
						|
		-- 		while row == nil do
 | 
						|
		-- 			id = next(archetypesMap, id)
 | 
						|
		-- 			if id == nil then
 | 
						|
		-- 				return
 | 
						|
		-- 			end
 | 
						|
		-- 			archetype = archetypes[id]
 | 
						|
		-- 			row = next(archetype.entities, row)
 | 
						|
		-- 		end
 | 
						|
		-- 		lastRow = row
 | 
						|
 | 
						|
		-- 		return entity, archetype.columns[archetype.records[a]]
 | 
						|
		-- 	end
 | 
						|
		-- end
 | 
						|
		-- return single()
 | 
						|
	elseif queryLength == 2 then
 | 
						|
		--print("iter double")
 | 
						|
		a = #a
 | 
						|
		b = #b
 | 
						|
		components = { a, b }
 | 
						|
 | 
						|
		-- --print(a, b, world.componentIndex)
 | 
						|
		-- --[[local archetypesMap = world.componentIndex[a]
 | 
						|
		-- for id in archetypesMap do
 | 
						|
		-- 	local archetype = archetypes[id]
 | 
						|
		-- 	if archetype.records[b] then
 | 
						|
		-- 		table.insert(compatibleArchetypes, archetype)
 | 
						|
		-- 	end
 | 
						|
		-- end
 | 
						|
 | 
						|
		-- local function double(): () -> (number, any, any)
 | 
						|
		-- 	local lastArchetype, archetype = next(compatibleArchetypes)
 | 
						|
		-- 	local lastRow
 | 
						|
 | 
						|
		-- 	return function()
 | 
						|
		-- 		local row = next(archetype.entities, lastRow)
 | 
						|
		-- 		while row == nil do
 | 
						|
		-- 			lastArchetype, archetype = next(compatibleArchetypes, lastArchetype)
 | 
						|
		-- 			if lastArchetype == nil then
 | 
						|
		-- 				return
 | 
						|
		-- 			end
 | 
						|
 | 
						|
		-- 			row = next(archetype.entities, row)
 | 
						|
		-- 		end
 | 
						|
		-- 		lastRow = row
 | 
						|
 | 
						|
		-- 		local entity = archetype.entities[row :: number]
 | 
						|
		-- 		local columns = archetype.columns
 | 
						|
		-- 		local archetypeRecords = archetype.records
 | 
						|
		-- 		return entity, columns[archetypeRecords[a]], columns[archetypeRecords[b]]
 | 
						|
		-- 	end
 | 
						|
		-- end
 | 
						|
		-- return double()
 | 
						|
	elseif queryLength == 3 then
 | 
						|
		a = #a
 | 
						|
		b = #b
 | 
						|
		c = #c
 | 
						|
		components = { a, b, c }
 | 
						|
	elseif queryLength == 4 then
 | 
						|
		a = #a
 | 
						|
		b = #b
 | 
						|
		c = #c
 | 
						|
		d = #d
 | 
						|
 | 
						|
		components = { a, b, c, d }
 | 
						|
	elseif queryLength == 5 then
 | 
						|
		a = #a
 | 
						|
		b = #b
 | 
						|
		c = #c
 | 
						|
		d = #d
 | 
						|
		e = #e
 | 
						|
 | 
						|
		components = { a, b, c, d, e }
 | 
						|
	else
 | 
						|
		for i, component in components do
 | 
						|
			components[i] = (#component) :: any
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	local firstArchetypeMap
 | 
						|
	local componentIndex = world.componentIndex
 | 
						|
	for _, componentId in (components :: any) :: { number } do
 | 
						|
		local map = componentIndex[componentId]
 | 
						|
		if not map then
 | 
						|
			-- TODO:
 | 
						|
			-- see what upstream does in this case
 | 
						|
			-- currently replicating jecs
 | 
						|
			error(tostring(componentId) .. " has not been added to an entity")
 | 
						|
		end
 | 
						|
 | 
						|
		if firstArchetypeMap == nil or map.size < firstArchetypeMap.size then
 | 
						|
			firstArchetypeMap = map
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	for id in firstArchetypeMap.sparse do
 | 
						|
		local archetype = archetypes[id]
 | 
						|
		local archetypeRecords = archetype.records
 | 
						|
		local matched = true
 | 
						|
		for _, componentId in components do
 | 
						|
			if not archetypeRecords[componentId] then
 | 
						|
				matched = false
 | 
						|
				break
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		if matched then
 | 
						|
			table.insert(compatibleArchetypes, archetype)
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	-- Only want to include archetype selection?
 | 
						|
 | 
						|
	local lastArchetype, archetype = next(compatibleArchetypes)
 | 
						|
	if not lastArchetype then
 | 
						|
		return noop()
 | 
						|
	end
 | 
						|
 | 
						|
	local lastRow
 | 
						|
	local queryOutput = {}
 | 
						|
	local function iterate()
 | 
						|
		local row = next(archetype.entities, lastRow)
 | 
						|
		while row == nil do
 | 
						|
			lastArchetype, archetype = next(compatibleArchetypes, lastArchetype)
 | 
						|
			if lastArchetype == nil then
 | 
						|
				return
 | 
						|
			end
 | 
						|
			row = next(archetype.entities, row)
 | 
						|
		end
 | 
						|
 | 
						|
		lastRow = row
 | 
						|
 | 
						|
		local columns = archetype.columns
 | 
						|
		local entityId = archetype.entities[row :: number]
 | 
						|
		local archetypeRecords = archetype.records
 | 
						|
 | 
						|
		if queryLength == 1 then
 | 
						|
			return entityId, columns[archetypeRecords[a]][row]
 | 
						|
		elseif queryLength == 2 then
 | 
						|
			return entityId, columns[archetypeRecords[a]][row], columns[archetypeRecords[b]][row]
 | 
						|
		elseif queryLength == 3 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row]
 | 
						|
		elseif queryLength == 4 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row],
 | 
						|
				columns[archetypeRecords[d]][row]
 | 
						|
		elseif queryLength == 5 then
 | 
						|
			return entityId,
 | 
						|
				columns[archetypeRecords[a]][row],
 | 
						|
				columns[archetypeRecords[b]][row],
 | 
						|
				columns[archetypeRecords[c]][row],
 | 
						|
				columns[archetypeRecords[d]][row],
 | 
						|
				columns[archetypeRecords[e]][row]
 | 
						|
		end
 | 
						|
 | 
						|
		for i, componentId in components do
 | 
						|
			queryOutput[i] = columns[archetypeRecords[componentId]][row]
 | 
						|
		end
 | 
						|
 | 
						|
		return entityId, unpack(queryOutput, 1, queryLength)
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
	@class QueryResult
 | 
						|
 | 
						|
	A result from the [`World:query`](/api/World#query) function.
 | 
						|
 | 
						|
	Calling the table or the `next` method allows iteration over the results. Once all results have been returned, the
 | 
						|
	QueryResult is exhausted and is no longer useful.
 | 
						|
 | 
						|
	```lua
 | 
						|
	for id, enemy, charge, model in world:query(Enemy, Charge, Model) do
 | 
						|
		-- Do something
 | 
						|
	end
 | 
						|
	```
 | 
						|
	]=]
 | 
						|
	local QueryResult = {}
 | 
						|
	QueryResult.__index = QueryResult
 | 
						|
 | 
						|
	function QueryResult:__call()
 | 
						|
		return iterate()
 | 
						|
	end
 | 
						|
 | 
						|
	function QueryResult:__iter()
 | 
						|
		return function()
 | 
						|
			return iterate()
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
	Returns the next set of values from the query result. Once all results have been returned, the
 | 
						|
	QueryResult is exhausted and is no longer useful.
 | 
						|
 | 
						|
	:::info
 | 
						|
	This function is equivalent to calling the QueryResult as a function. When used in a for loop, this is implicitly
 | 
						|
	done by the language itself.
 | 
						|
	:::
 | 
						|
 | 
						|
	```lua
 | 
						|
	-- Using world:query in this position will make Lua invoke the table as a function. This is conventional.
 | 
						|
	for id, enemy, charge, model in world:query(Enemy, Charge, Model) do
 | 
						|
		-- Do something
 | 
						|
	end
 | 
						|
	```
 | 
						|
 | 
						|
	If you wanted to iterate over the QueryResult without a for loop, it's recommended that you call `next` directly
 | 
						|
	instead of calling the QueryResult as a function.
 | 
						|
	```lua
 | 
						|
	local id, enemy, charge, model = world:query(Enemy, Charge, Model):next()
 | 
						|
	local id, enemy, charge, model = world:query(Enemy, Charge, Model)() -- Possible, but unconventional
 | 
						|
	```
 | 
						|
 | 
						|
	@return id -- Entity ID
 | 
						|
	@return ...ComponentInstance -- The requested component values
 | 
						|
	]=]
 | 
						|
	function QueryResult:next()
 | 
						|
		return iterate()
 | 
						|
	end
 | 
						|
 | 
						|
	local Snapshot = {
 | 
						|
		__iter = function(self): any
 | 
						|
			local i = 0
 | 
						|
			return function()
 | 
						|
				i += 1
 | 
						|
 | 
						|
				local data = self[i]
 | 
						|
 | 
						|
				if data then
 | 
						|
					return unpack(data, 1, data.n)
 | 
						|
				end
 | 
						|
 | 
						|
				return
 | 
						|
			end
 | 
						|
		end,
 | 
						|
	}
 | 
						|
 | 
						|
	function QueryResult:snapshot()
 | 
						|
		local list = setmetatable({}, Snapshot) :: any
 | 
						|
 | 
						|
		local function iter()
 | 
						|
			--local entry = table.pack(iterate())
 | 
						|
			--return if #entry == 0 then nil else entry
 | 
						|
			return "x"
 | 
						|
		end
 | 
						|
 | 
						|
		for data in iter :: any do
 | 
						|
			if data[1] then
 | 
						|
				table.insert(list, data)
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		return list
 | 
						|
	end
 | 
						|
 | 
						|
	--[=[
 | 
						|
	Returns an iterator that will skip any entities that also have the given components.
 | 
						|
 | 
						|
	@param ... Component -- The component types to filter against.
 | 
						|
	@return () -> (id, ...ComponentInstance) -- Iterator of entity ID followed by the requested component values
 | 
						|
 | 
						|
	```lua
 | 
						|
	for id in world:query(Target):without(Model) do
 | 
						|
		-- Do something
 | 
						|
	end
 | 
						|
	```
 | 
						|
	]=]
 | 
						|
	function QueryResult:without(...)
 | 
						|
		local components = { ... }
 | 
						|
		for i, component in components do
 | 
						|
			components[i] = #component
 | 
						|
		end
 | 
						|
 | 
						|
		local compatibleArchetypes = compatibleArchetypes
 | 
						|
		for i = #compatibleArchetypes, 1, -1 do
 | 
						|
			local archetype = compatibleArchetypes[i]
 | 
						|
			local shouldRemove = false
 | 
						|
			for _, componentId in components do
 | 
						|
				if archetype.records[componentId] then
 | 
						|
					shouldRemove = true
 | 
						|
					break
 | 
						|
				end
 | 
						|
			end
 | 
						|
 | 
						|
			if shouldRemove then
 | 
						|
				table.remove(compatibleArchetypes, i)
 | 
						|
			end
 | 
						|
		end
 | 
						|
 | 
						|
		lastArchetype, archetype = next(compatibleArchetypes)
 | 
						|
		if not lastArchetype then
 | 
						|
			return noop()
 | 
						|
		end
 | 
						|
 | 
						|
		return self
 | 
						|
	end
 | 
						|
 | 
						|
	return setmetatable({}, QueryResult)
 | 
						|
end
 | 
						|
 | 
						|
local function cleanupQueryChanged(hookState)
 | 
						|
	local world = hookState.world
 | 
						|
	local componentToTrack = hookState.componentToTrack
 | 
						|
 | 
						|
	for index, object in world._changedStorage[componentToTrack] do
 | 
						|
		if object == hookState.storage then
 | 
						|
			table.remove(world._changedStorage[componentToTrack], index)
 | 
						|
			break
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	if next(world._changedStorage[componentToTrack]) == nil then
 | 
						|
		world._changedStorage[componentToTrack] = nil
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
function World.queryChanged(world: World, componentToTrack, ...: nil)
 | 
						|
	if ... then
 | 
						|
		error("World:queryChanged does not take any additional parameters", 2)
 | 
						|
	end
 | 
						|
 | 
						|
	local hookState = topoRuntime.useHookState(componentToTrack, cleanupQueryChanged) :: any
 | 
						|
	if hookState.storage then
 | 
						|
		return function(): any
 | 
						|
			local entityId, record = next(hookState.storage)
 | 
						|
 | 
						|
			if entityId then
 | 
						|
				hookState.storage[entityId] = nil
 | 
						|
 | 
						|
				return entityId, record
 | 
						|
			end
 | 
						|
			return
 | 
						|
		end
 | 
						|
	end
 | 
						|
 | 
						|
	if not world._changedStorage[componentToTrack] then
 | 
						|
		world._changedStorage[componentToTrack] = {}
 | 
						|
	end
 | 
						|
 | 
						|
	local storage = {}
 | 
						|
	hookState.storage = storage
 | 
						|
	hookState.world = world
 | 
						|
	hookState.componentToTrack = componentToTrack
 | 
						|
 | 
						|
	table.insert(world._changedStorage[componentToTrack], storage)
 | 
						|
 | 
						|
	local queryResult = world:query(componentToTrack)
 | 
						|
 | 
						|
	return function(): any
 | 
						|
		local entityId, component = queryResult:next()
 | 
						|
 | 
						|
		if entityId then
 | 
						|
			return entityId, table.freeze({ new = component })
 | 
						|
		end
 | 
						|
		return
 | 
						|
	end
 | 
						|
end
 | 
						|
 | 
						|
return {
 | 
						|
    World = World,
 | 
						|
    component = newComponent
 | 
						|
}
 |