View Source Ecspanse.System (ECSpanse v0.10.0)
The system implements the logic and behaviors of the application
by manipulating the state of the components.
The systems are defined by invoking use Ecspanse.System
in their module definition.
The system modules must implement the
Ecspanse.System.WithoutEventSubscriptions.run/1
or
Ecspanse.System.WithEventSubscriptions.run/2
callbacks,
depending if the system subscribes to certain events or not.
The return value of the run
function is ignored.
The Ecspanse systems run either synchronously or asynchronously,
as scheduled in the Ecspanse.setup/1
callback.
Systems are the sole mechanism through which the state of components can be altered. Running commands outside of a system is not allowed.
Resources can be created, updated, and deleted only by systems that are executed synchronously.
There are some special systems that are created automatically by the framework:
Ecspanse.System.CreateStartupResources
- startup system that creates the default framework resources, states, and custom resources inserted at startup.Ecspanse.System.Debug
- used by thedebug/0
function.Ecspanse.System.Timer
- tracks and updates all components using theEcspanse.Template.Component.Timer
template.Ecspanse.System.TrackFPS
- tracks and updates theEcspanse.Resource.FPS
resource.
Info
The
Ecspanse.Query
andEcspanse.Command
functions are imported by default for all modules thatuse Ecspanse.System
Options
:lock_components
- a list of component modules that will be locked for the duration of the system execution.:event_subscriptions
- a list of event modules that the system subscribes to.
Component locking
Component locking is required only for async systems to avoid race conditions.
For async systems, any components that are to be modified, created, or deleted,
must be locked in the lock_components
option. Otherwise, the operation will fail.
Wherever it makes sense, it is recommended to lock also components that are queried but not modified,
as they could be modified by other systems.
Not all async systems run concurrently. The systems are grouped in batches, based on the components they lock.
Event subscriptions
The event subscriptions enables a system to execute solely in response to certain specified events.
The Ecspanse.System.WithEventSubscriptions.run/2
callback is triggered
for every occurrence of an event type to which the system has subscribed.
These callbacks execute concurrently to enhance performance.
However, they are grouped based on their batch keys (see Ecspanse.event/2
options)
as a safeguard against potential race conditions.
Examples
defmodule Demo.Systems.Move do
@moduledoc "An async system locking components, that subscribes to an event"
use Ecspanse.System,
lock_components: [Demo.Components.Position],
event_subscriptions: [Demo.Events.Move]
def run(%Demo.Events.Move{entity_id: entity_id, direction: direction}, frame) do
# move logic
end
end
defmodule Demo.Systems.SpawnEnemy do
@moduledoc "A sync system that does not need to lock components, and it is not subscribed to any events"
use Ecspanse.System
def run(frame) do
# spawn logic
end
end
Summary
Functions
Utility function. Gives the current process Ecspanse.System
abilities to execute commands.
Allows running async code inside a system.
Types
@type system_queue() ::
:startup_systems
| :frame_start_systems
| :batch_systems
| :frame_end_systems
| :shutdown_systems
@type t() :: %Ecspanse.System{ execution: :sync | :async, module: module(), queue: system_queue(), run_after: [system_module :: module()], run_conditions: [{module(), atom()}] }
Functions
@spec debug(opts :: keyword()) :: :ok
Utility function. Gives the current process Ecspanse.System
abilities to execute commands.
This is a powerful tool for testing and debugging, as the promoted process can change the components and resources state without having to be scheduled like a regular system.
See Ecspanse.TestServer
for more details.
This function is intended for use only in testing and development environments.
@spec execute_async(Enumerable.t(), (term() -> term()), keyword()) :: :ok
Allows running async code inside a system.
Because commands can run only from inside a system,
running commands in a Task, for example, is not possible.
The execute_async/3
is a wrapper around Elixir.Task.async_stream/3
and is built exactly for this purpose. It allows running commands in parallel.
The result of the processing is ignored. So the function is suitable for cases when the result is not important. For example, updating components for a list of entities.
Info
This function is already imported for all modules that
use Ecspanse.System
Options
:concurrent
- the number of concurrent tasks to run. Defaults to the number of schedulers online. SeeElixir.Task.async_stream/5
options for more details.
use with care
While the locked components ensure that no other system is modifying the same components at the same time, the
execute_async/3
does not offer any such guarantees inside the same system.For example, the same component can be modified concurrently, leading to race conditions and inconsistent state.
Examples
Ecspanse.System.execute_async(
enemy_entities,
fn enemy_entity ->
# update the enemy components
end,
concurrent: length(enemy_entities) + 1
)