mirror of
				https://github.com/Reuh/ubiquitousse.git
				synced 2025-10-27 17:19:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			639 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			639 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| --- ECS (entity compenent system) library
 | |
| --
 | |
| -- Entity Component System library, inspired by the excellent tiny-ecs. Main differences include:
 | |
| --
 | |
| -- * ability to nest systems;
 | |
| -- * instanciation of systems for each world (no shared state);
 | |
| -- * adding and removing entities is done instantaneously
 | |
| -- * ability to add and remove components from entities after they were added to the world.
 | |
| --
 | |
| -- No mandatory dependency.
 | |
| -- Optional dependency: `ubiquitousse.scene`, to allow quick creation of ECS-based scenes.
 | |
| --
 | |
| -- The module returns a table that contains several functions, `world` or `scene` are starting points
 | |
| -- to create your world.
 | |
| --
 | |
| -- @module ecs
 | |
| -- @usage TODO
 | |
| local loaded, scene = pcall(require, (...):match("^(.-)ecs").."scene")
 | |
| if not loaded then scene = nil end
 | |
| 
 | |
| let ecs
 | |
| 
 | |
| -- TODO: Implement a skip list for faster search.
 | |
| -- better control over system order: process, draw, methods? (for lag reasons and dependencies)
 | |
| -- more generic events?
 | |
| 
 | |
| -- TODO: :reorder (like refresh but update order)
 | |
| 
 | |
| -- TODO: clarify documentation on entity tables and instanciated system table
 | |
| 
 | |
| --- Entity table.
 | |
| -- TODO
 | |
| -- @section Entity
 | |
| 
 | |
| --- Entity system table.
 | |
| -- @doc entity
 | |
| 
 | |
| --- @table entity
 | |
| -- @field .. d
 | |
| 
 | |
| --- Recursively remove subsystems from a system.
 | |
| let recDestroySystems = (system)
 | |
| 	for i=#system.systems, 1, -1 do
 | |
| 		let s = system.systems[i]
 | |
| 		recDestroySystems(s)
 | |
| 		s:onDestroy()
 | |
| 		system.systems[i] = nil
 | |
| 		if s.name then
 | |
| 			system.world.s[s.name] = nil
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| --- Recursively call :clear and :onRemoveFromWorld to a list of systems in a world.
 | |
| let recCallOnRemoveFromWorld = (world, systems)
 | |
| 	for _, s in ipairs(systems) do
 | |
| 		s:clear()
 | |
| 		recCallOnRemoveFromWorld(world, s.systems)
 | |
| 		s:onRemoveFromWorld(world)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| --- Iterate through the next entity, based on state s: { previousLinkedListItem }
 | |
| let nextEntity = (s)
 | |
| 	if s[1] then
 | |
| 		let var = s[1][1]
 | |
| 		s[1] = s[1][2]
 | |
| 		return var
 | |
| 	else
 | |
| 		return nil
 | |
| 	end
 | |
| end
 | |
| 
 | |
| --- Recursively copy content of a into b if it isn't already present. No cycle detection.
 | |
| let copy = (a, b)
 | |
| 	for k, v in pairs(a) do
 | |
| 		if type(v) == "table" then
 | |
| 			if b[k] == nil then
 | |
| 				b[k] = {}
 | |
| 				copy(v, b[k])
 | |
| 			elseif b[k] == "table" then
 | |
| 				copy(v, b[k])
 | |
| 			end
 | |
| 		elseif b[k] == nil then
 | |
| 			b[k] = v
 | |
| 		end
 | |
| 	end
 | |
| end
 | |
| 
 | |
| --- System fields and methods.
 | |
| --
 | |
| -- When they are added to a world, a new, per-world self table is created and used for every method call (which we call "instancied system").
 | |
| -- Instancied systems can be retrieved in `system.s` or `system.systems`.
 | |
| --
 | |
| -- The "world" is just the top-level system, behaving in exactly the same way as other systems.
 | |
| --
 | |
| -- Every field defined below is optional and can be accessed or redefined at any time, unless written otherwise. Though you would typically set them
 | |
| -- when creating your system.
 | |
| -- @type System
 | |
| let system_mt = {
 | |
| 	--- Modifiable fields
 | |
| 	-- @doc modifiable
 | |
| 
 | |
| 	--- Name of the system.
 | |
| 	-- Used to create a field with the system's name in `world.s` and into each entity (the "entity's system table") that's in this system.
 | |
| 	-- If not set, the entity will not have a system table.
 | |
| 	--
 | |
| 	-- Do not change after system instanciation.
 | |
| 	-- @ftype string
 | |
| 	-- @ftype nil if no name
 | |
| 	name = nil,
 | |
| 
 | |
| 	--- List of subsystems.
 | |
| 	-- On a instancied system, this is a list of the same subsystems, but instancied for this world.
 | |
| 	--
 | |
| 	-- Do not change after system instanciation.
 | |
| 	-- @ftype table
 | |
| 	-- @ftype nil if no subsystem
 | |
| 	systems = nil,
 | |
| 
 | |
| 	--- If not false, the system will only update every interval seconds.
 | |
| 	-- `false` by default.
 | |
| 	-- @ftype number interval of time between each update
 | |
| 	-- @ftype false to disable
 | |
| 	interval = false,
 | |
| 	--- The system and its susbsystems will only update if this is true.
 | |
| 	-- `true` by default.
 | |
| 	-- @ftype boolean
 | |
| 	active = true,
 | |
| 	--- The system and its subsystems will only draw if this is true.
 | |
| 	-- `true` by default.
 | |
| 	-- @ftype boolean
 | |
| 	visible = true,
 | |
| 
 | |
| 	--- Defaults value to put into the entities's system table when they are added. Will recursively fill missing values.
 | |
| 	--
 | |
| 	-- When an entity is added to a system, a `.entity` field is always set in the system table, referring to the full entity table.
 | |
| 	--
 | |
| 	-- Changing this will not affect entities already in the system.
 | |
| 	-- @ftype table
 | |
| 	-- @ftype nil if no default
 | |
| 	default = nil,
 | |
| 	--- Defaults methods to assign to the entities's system table when they are added.
 | |
| 	--
 | |
| 	-- When calling the methods with `entity.systemName:method(...)`, the method will actually receive the
 | |
| 	-- arguments method(system, `system table, ...)`. Methamethods are accepted. New methods can be
 | |
| 	-- created anytime.
 | |
| 	-- @ftype table
 | |
| 	-- @ftype nil if no methods
 | |
| 	methods = nil,
 | |
| 
 | |
| 	--- Callbacks.
 | |
| 	--
 | |
| 	-- Functions that are called when something happens in the system.
 | |
| 	-- Redefine them to change system behaviour.
 | |
| 	-- @doc callbacks
 | |
| 
 | |
| 	--- Called when checking if an entity should be added to this system.
 | |
| 	-- Returns true if the entity should be added to this system (and therefore its subsystems).
 | |
| 	--
 | |
| 	-- If this is a string or a table, it will be converted to a filter function on instanciation using ecs.any.
 | |
| 	--
 | |
| 	-- If this true, will accept every entity; if false, reject every entity.
 | |
| 	--
 | |
| 	-- Will only test entities when they are added; changing this after system creation will not affect entities already in the system.
 | |
| 	--
 | |
| 	-- By default, rejects everything.
 | |
| 	-- @callback
 | |
| 	-- @tparam table e entity table to check
 | |
| 	-- @treturn boolean true if entity should be added
 | |
| 	filter = :(e) return false end,
 | |
| 	--- Called when adding an entity to this system determining its order.
 | |
| 	-- Returns true if e1 <= e2. e1 and e2 are two entities.
 | |
| 	--
 | |
| 	-- Used to place the entity in the sorted entity list when it is added; changing this after system creation
 | |
| 	-- will not change the order of entities already in the system.
 | |
| 	--
 | |
| 	-- By default, entities are in the same order they were inserted.
 | |
| 	-- @callback
 | |
| 	-- @tparam table e1 entity table to check for inferiority
 | |
| 	-- @tparam table e2 entity table to check for superiority
 | |
| 	-- @treturn boolean true if e1 <= e2
 | |
| 	compare = :(e1, e2) return true end,
 | |
| 
 | |
| 	--- Called when adding an entity to the system.
 | |
| 	-- @callback
 | |
| 	-- @tparam table s the entity's system table
 | |
| 	onAdd = :(s) end,
 | |
| 	--- Called when removing an entity from the system.
 | |
| 	-- @callback
 | |
| 	-- @tparam table s the entity's system table
 | |
| 	onRemove = :(s) end,
 | |
| 	--- Called when the system is instancied, before any call to `System:onAddToWorld` (including other systems in the world).
 | |
| 	-- @callback
 | |
| 	onInstance = :() end,
 | |
| 	--- Called when the system is added to a world.
 | |
| 	-- @callback
 | |
| 	-- @tparam System world world system
 | |
| 	onAddToWorld = :(world) end,
 | |
| 	--- Called when the system is removed from a world (i.e., the world is destroyed).
 | |
| 	-- @callback
 | |
| 	-- @tparam System world world system
 | |
| 	onRemoveFromWorld = :(world) end,
 | |
| 	--- Called when the world is destroyed, after every call to `System:onRemoveFromWorld` (including other systems in the world).
 | |
| 	-- @callback
 | |
| 	onDestroy = :() end,
 | |
| 	--- Called when updating the system.
 | |
| 	-- @callback
 | |
| 	-- @number dt delta-time since last update
 | |
| 	onUpdate = :(dt) end,
 | |
| 	--- Called when drawing the system.
 | |
| 	-- @callback
 | |
| 	onDraw = :() end,
 | |
| 	--- Called when updating the system, for every entity the system contains. Called after `System:onUpdate` was called on the system.
 | |
| 	-- @callback
 | |
| 	-- @tparam table s the entity's system table
 | |
| 	-- @number dt delta-time since last update
 | |
| 	process = :(s, dt) end,
 | |
| 	--- Called when drawing the system, for every entity the system contains. Called after `System:onDraw` was called on the system.
 | |
| 	-- @callback
 | |
| 	-- @tparam table s the entity's system table
 | |
| 	render = :(s) end,
 | |
| 
 | |
| 	--- Read-only fields
 | |
| 	-- @doc ro
 | |
| 
 | |
| 	--- The world the system belongs to.
 | |
| 	-- @ftype System world
 | |
| 	-- @ro
 | |
| 	world = nil,
 | |
| 	--- Number of entities in the system.
 | |
| 	-- @ftype integer
 | |
| 	-- @ro
 | |
| 	entityCount = 0,
 | |
| 	--- Map of all named systems in the world (not only subsystems). Same for every system from the same world.
 | |
| 	-- @ftype table {[system.name]=instanciedSystem, ...}
 | |
| 	-- @ro
 | |
| 	s = nil,
 | |
| 
 | |
| 	--- Private fields ---
 | |
| 
 | |
| 	--- First element of the linked list of entities: { entity, next_element }.
 | |
| 	-- @local
 | |
| 	_first = nil,
 | |
| 	--- Associative map of entities in the system and their previous linked list element (or true if first element).
 | |
| 	-- This make the list effectively a doubly linked list, but with easy access to the previous element using this map (and therefore O(1) deletion).
 | |
| 	-- @local
 | |
| 	_previous = nil,
 | |
| 	--- Amount of time waited since last update (if interval is set).
 | |
| 	-- @local
 | |
| 	_waited = 0,
 | |
| 	--- Metatable of entities' system table.
 | |
| 	-- Contains the methods defined in the methods field, wrapped to be called with the correct arguments, as well as a
 | |
| 	-- __index field (if not redefined in methods).
 | |
| 	-- @local
 | |
| 	_methods_mt = nil,
 | |
| 
 | |
| 	--- Methods.
 | |
| 	--
 | |
| 	-- Methods available on instancied system table.
 | |
| 	-- @doc smethods
 | |
| 
 | |
| 	--- Add entities to the system and its subsystems.
 | |
| 	--
 | |
| 	-- Will skip entities that are already in the system.
 | |
| 	--
 | |
| 	-- Entities are added to subsystems after they were succesfully added to their parent system.
 | |
| 	--
 | |
| 	-- If this is called on a subsystem instead of the world, be warned that this will bypass all the parent's systems filters.
 | |
| 	--
 | |
| 	-- Since `System:remove` will not search for entities in systems where they should have been filtered out, the added entities will not be removed
 | |
| 	-- when calling :remove on a parent system or the world. The entity can be removed by calling `System:remove` on the system `System:add` was called on.
 | |
| 	--
 | |
| 	-- Complexity: O(1) per unordered system, O(entityCount) per ordered system.
 | |
| 	-- @tparam table e entity to add
 | |
| 	-- @tparam table... ... other entities to add
 | |
| 	-- @treturn e,... the function arguments
 | |
| 	add = :(e, ...)
 | |
| 		if e ~= nil and not @_previous[e] and @filter(e) then
 | |
| 			-- setup entity
 | |
| 			if @name then
 | |
| 				if not e[@name] e[@name] = {}
 | |
| 				if @default copy(@default, e[@name])
 | |
| 				if @methods setmetatable(e[@name], @_methods_mt)
 | |
| 				e[@name].entity = e
 | |
| 			end
 | |
| 			-- add to linked list
 | |
| 			if @_first == nil then
 | |
| 				@_first = { e, nil }
 | |
| 				@_previous[e] = true
 | |
| 			elseif @compare(e, @_first[1]) then
 | |
| 				let nxt = @_first
 | |
| 				@_first = { e, nxt }
 | |
| 				@_previous[e] = true
 | |
| 				@_previous[nxt[1]] = @_first
 | |
| 			else
 | |
| 				let entity = @_first
 | |
| 				while entity[2] ~= nil do
 | |
| 					if @compare(e, entity[2][1]) then
 | |
| 						let nxt = entity[2]
 | |
| 						entity[2] = { e, nxt }
 | |
| 						@_previous[e] = entity
 | |
| 						@_previous[nxt[1]] = entity[2]
 | |
| 						break
 | |
| 					end
 | |
| 					entity = entity[2]
 | |
| 				end
 | |
| 				if entity[2] == nil then
 | |
| 					entity[2] = { e, nil }
 | |
| 					@_previous[e] = entity
 | |
| 				end
 | |
| 			end
 | |
| 			-- notify addition
 | |
| 			@entityCount += 1
 | |
| 			@onAdd(e[@name])
 | |
| 			-- add to subsystems
 | |
| 			for _, s in ipairs(@systems) do
 | |
| 				s:add(e)
 | |
| 			end
 | |
| 		end
 | |
| 		if ... then
 | |
| 			return e, @add(...)
 | |
| 		else
 | |
| 			return e
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Refresh an entity's systems.
 | |
| 	--
 | |
| 	-- Behave similarly to `System:add`, but if the entity is already in the system, instead of skipping it, it
 | |
| 	-- will check for new and removed components and add and remove from (sub)systems accordingly.
 | |
| 	--
 | |
| 	-- Complexity: O(1) per system + add/remove complexity.
 | |
| 	-- @tparam table e entity to refresh
 | |
| 	-- @tparam table... ... other entities to refresh
 | |
| 	-- @treturn e,... the function arguments
 | |
| 	refresh = :(e, ...)
 | |
| 		if e ~= nil then
 | |
| 			if not @_previous[e] then
 | |
| 				@add(e)
 | |
| 			elseif @_previous[e] then
 | |
| 				if not @filter(e) then
 | |
| 					@remove(e)
 | |
| 				else
 | |
| 					for _, s in ipairs(@systems) do
 | |
| 						s:refresh(e)
 | |
| 					end
 | |
| 				end
 | |
| 			end
 | |
| 		end
 | |
| 		if ... then
 | |
| 			return e, @refresh(...)
 | |
| 		else
 | |
| 			return e
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Remove entities to the system and its subsystems.
 | |
| 	--
 | |
| 	-- Will skip entities that are not in the system.
 | |
| 	--
 | |
| 	-- Entities are removed from subsystems before they are removed from their parent system.
 | |
| 	--
 | |
| 	-- If you intend to call this on a subsystem instead of the world, please read the warning in `System:add`.
 | |
| 	--
 | |
| 	-- Returns all removed entities.
 | |
| 	--
 | |
| 	-- Complexity: O(1) per system.
 | |
| 	-- @tparam table e entity to remove
 | |
| 	-- @tparam table... ... other entities to remove
 | |
| 	-- @treturn e,... the function arguments
 | |
| 	remove = :(e, ...)
 | |
| 		if e ~= nil then
 | |
| 			if @_previous[e] then
 | |
| 				-- remove from subsystems
 | |
| 				for _, s in ipairs(@systems) do
 | |
| 					s:remove(e)
 | |
| 				end
 | |
| 			end
 | |
| 			if @_previous[e] then -- recheck in case it was removed already from a subsystem onRemove callback
 | |
| 				-- remove from linked list
 | |
| 				let prev = @_previous[e]
 | |
| 				if prev == true then
 | |
| 					@_first = @_first[2]
 | |
| 					if @_first then
 | |
| 						@_previous[@_first[1]] = true
 | |
| 					end
 | |
| 				else
 | |
| 					prev[2] = prev[2][2]
 | |
| 					if prev[2] then
 | |
| 						@_previous[prev[2][1]] = prev
 | |
| 					end
 | |
| 				end
 | |
| 				-- notify removal
 | |
| 				@_previous[e] = nil
 | |
| 				@entityCount -= 1
 | |
| 				@onRemove(e[@name])
 | |
| 			end
 | |
| 		end
 | |
| 		if ... then
 | |
| 			return e, @remove(...)
 | |
| 		else
 | |
| 			return e
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Returns true if every entity is in the system.
 | |
| 	--
 | |
| 	-- Complexity: O(1).
 | |
| 	-- @tparam table e entity that may be in the system
 | |
| 	-- @tparam table... ... other entities that may be in the system
 | |
| 	-- @treturn boolean true if every entity is in the system
 | |
| 	has = :(e, ...)
 | |
| 		let has = e == nil or not not @_previous[e]
 | |
| 		if ... then
 | |
| 			return has and @has(...)
 | |
| 		else
 | |
| 			return has
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Returns an iterator that iterate through the entties in this system.
 | |
| 	-- @treturn iterator iterator over the entities in this system, in order
 | |
| 	iter = :()
 | |
| 		return nextEntity, { @_first }
 | |
| 	end,
 | |
| 	--- Remove every entity from the system and its subsystems.
 | |
| 	clear = :()
 | |
| 		for e in @iter() do
 | |
| 			@remove(e)
 | |
| 		end
 | |
| 		for _, s in ipairs(@systems) do
 | |
| 			s:clear()
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Try to update the system and its subsystems. Should be called on every game update.
 | |
| 	--
 | |
| 	-- Subsystems are updated after their parent system.
 | |
| 	-- @number dt delta-time since last update
 | |
| 	update = :(dt)
 | |
| 		if @active then
 | |
| 			if @interval then
 | |
| 				@_waited += dt
 | |
| 				if @_waited < @interval then
 | |
| 					return
 | |
| 				end
 | |
| 			end
 | |
| 			@onUpdate(dt)
 | |
| 			if @process ~= system_mt.process then
 | |
| 				for e in @iter() do
 | |
| 					@process(e[@name], dt)
 | |
| 				end
 | |
| 			end
 | |
| 			for _, s in ipairs(@systems) do
 | |
| 				s:update(dt)
 | |
| 			end
 | |
| 			if @interval then
 | |
| 				@_waited -= @interval
 | |
| 			end
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Try to draw the system and its subsystems. Should be called on every game draw.
 | |
| 	--
 | |
| 	-- Subsystems are drawn after their parent system.
 | |
| 	draw = :()
 | |
| 		if @visible then
 | |
| 			@onDraw()
 | |
| 			if @render ~= system_mt.render then
 | |
| 				for e in @iter() do
 | |
| 					@render(e[@name])
 | |
| 				end
 | |
| 			end
 | |
| 			for _, s in ipairs(@systems) do
 | |
| 				s:draw()
 | |
| 			end
 | |
| 		end
 | |
| 	end,
 | |
| 	--- Remove all the entities and subsystems in this system.
 | |
| 	destroy = :()
 | |
| 		recCallOnRemoveFromWorld(@world, { @ })
 | |
| 		recDestroySystems({ systems = { @ } })
 | |
| 	end
 | |
| }
 | |
| 
 | |
| --- Self descriptive
 | |
| let alwaysTrue = () return true end
 | |
| let alwaysFalse = () return true end
 | |
| 
 | |
| --- Recursively instanciate a list of systems for a world:
 | |
| -- * create their self table with instance fields set
 | |
| -- * create a field with their name in world.s (if name defined)
 | |
| let recInstanciateSystems = (world, systems)
 | |
| 	let t = {}
 | |
| 	for _, s in ipairs(systems) do
 | |
| 		let system
 | |
| 		-- setup method table
 | |
| 		let methods_mt = {}
 | |
| 		if s.methods then
 | |
| 			methods_mt.__index = methods_mt
 | |
| 			for k, v in pairs(s.methods) do
 | |
| 				methods_mt[k] = :(...)
 | |
| 					return v(system, @, ...)
 | |
| 				end
 | |
| 			end
 | |
| 			setmetatable(s.methods, {
 | |
| 				__newindex = :(k, v)
 | |
| 					rawset(@, k, v)
 | |
| 					methods_mt[k] = :(...)
 | |
| 						return v(system, @, ...)
 | |
| 					end
 | |
| 				end
 | |
| 			})
 | |
| 		end
 | |
| 		-- instanciate system
 | |
| 		system = setmetatable({
 | |
| 			systems = recInstanciateSystems(world, s.systems or {}),
 | |
| 			world = world,
 | |
| 			s = world.s,
 | |
| 			_previous = {},
 | |
| 			_methods_mt = methods_mt
 | |
| 		}, {
 | |
| 			__index = :(k)
 | |
| 				if s[k] ~= nil then
 | |
| 					return s[k]
 | |
| 				else
 | |
| 					return system_mt[k]
 | |
| 				end
 | |
| 			end
 | |
| 		})
 | |
| 		if type(s.filter) == "string" then
 | |
| 			system.filter = (_, e) return e[s.filter] ~= nil end
 | |
| 		elseif type(s.filter) == "table" then
 | |
| 			system.filter = ecs.any(unpack(s.filter))
 | |
| 		elseif type(s.filter) == "boolean" then
 | |
| 			if s.filter then
 | |
| 				system.filter = alwaysTrue
 | |
| 			else
 | |
| 				system.filter = alwaysFalse
 | |
| 			end
 | |
| 		end
 | |
| 		-- add system
 | |
| 		table.insert(t, system)
 | |
| 		if s.name then
 | |
| 			world.s[s.name] = system
 | |
| 		end
 | |
| 		system:onInstance()
 | |
| 	end
 | |
| 	return t
 | |
| end
 | |
| --- Recursively call :onAddToWorld to a list of systems in a world.
 | |
| let recCallOnAddToWorld = (world, systems)
 | |
| 	for _, s in ipairs(systems) do
 | |
| 		recCallOnAddToWorld(world, s.systems)
 | |
| 		s:onAddToWorld(world)
 | |
| 	end
 | |
| end
 | |
| 
 | |
| --- ECS module.
 | |
| -- @section end
 | |
| ecs = {
 | |
| 	--- Create and returns a world system based on a list of systems.
 | |
| 	-- The systems will be instancied for this world.
 | |
| 	-- @tparam table,... ... list of (uninstancied) system tables
 | |
| 	-- @treturn System the world system
 | |
| 	world = (...)
 | |
| 		let world = setmetatable({
 | |
| 			filter = ecs.all(),
 | |
| 			s = {},
 | |
| 			_previous = {}
 | |
| 		}, { __index = system_mt })
 | |
| 		world.world = world
 | |
| 		world.systems = recInstanciateSystems(world, {...})
 | |
| 		recCallOnAddToWorld(world, world.systems)
 | |
| 		return world
 | |
| 	end,
 | |
| 
 | |
| 	--- Returns a filter that returns true if, for every argument, a field with the same name exists in the entity.
 | |
| 	-- @tparam string,... ... list of field names that must be in entity
 | |
| 	-- @treturn function(e) that returns true if e has all the fields
 | |
| 	all = (...)
 | |
| 		if ... then
 | |
| 			let l = {...}
 | |
| 			return function(s, e)
 | |
| 				for _, k in ipairs(l) do
 | |
| 					if e[k] == nil then
 | |
| 						return false
 | |
| 					end
 | |
| 				end
 | |
| 				return true
 | |
| 			end
 | |
| 		else
 | |
| 			return alwaysTrue
 | |
| 		end
 | |
| 	end,
 | |
| 
 | |
| 	--- Returns a filter that returns true if one of the arguments if the name of a field in the entity.
 | |
| 	-- @tparam string,... ... list of field names that may be in entity
 | |
| 	-- @treturn function(e) that returns true if e has at leats one of the fields
 | |
| 	any = (...)
 | |
| 		if ... then
 | |
| 			let l = {...}
 | |
| 			return function(s, e)
 | |
| 				for _, k in ipairs(l) do
 | |
| 					if e[k] ~= nil then
 | |
| 						return true
 | |
| 					end
 | |
| 				end
 | |
| 				return false
 | |
| 			end
 | |
| 		else
 | |
| 			return alwaysFalse
 | |
| 		end
 | |
| 	end,
 | |
| 
 | |
| 	--- If `uqt.scene` is available, returns a new scene that will consist of a ECS world with the specified systems and entities.
 | |
| 	-- @require ubiquitousse.scene
 | |
| 	-- @string name the name of the new scene
 | |
| 	-- @tparam[opt={}] table systems list of systems to add to the world
 | |
| 	-- @tparam[opt={}] table entities list of entities to add to the world
 | |
| 	-- @treturn scene the new scene
 | |
| 	scene = (name, systems={}, entities={})
 | |
| 		assert(scene, "ubiquitousse.scene unavailable")
 | |
| 		let s = scene.new(name)
 | |
| 		let w
 | |
| 
 | |
| 		function s:enter()
 | |
| 			w = ecs.world(unpack(systems))
 | |
| 			w:add(unpack(entities))
 | |
| 		end
 | |
| 		function s:exit()
 | |
| 			w:destroy()
 | |
| 		end
 | |
| 		function s:update(dt)
 | |
| 			w:update(dt)
 | |
| 		end
 | |
| 		function s:draw()
 | |
| 			w:draw()
 | |
| 		end
 | |
| 
 | |
| 		return s
 | |
| 	end
 | |
| }
 | |
| 
 | |
| return ecs
 |