Open Asterune
Systems and scheduling

Systems and scheduling

Schedule ECS callbacks by phase, dependency, interval, and execution group.

A System instance stores Luau source that returns System.define(options, run). The scheduler calls run(dt) during the selected phase. dt is the accumulated time since that System last ran, in seconds.

Systems under ServerScriptService run on the server. Systems under PlayerScripts run on that client. Put client templates under StarterPlayerScripts. Systems outside these containers do not run.

Parameters

OptionDefaultBehavior
phase"PostSimulation"Scheduling phase for a serial System.
after{}System instances or named sets that must run before this System.
before{}System instances or named sets that must run after this System.
sets{}Names of scheduling sets containing this System.
parallelfalseRuns at the parallel barrier instead of a serial phase. Cannot be combined with phase.
interval0Minimum elapsed seconds between runs. Zero runs on every eligible phase. Must be finite and nonnegative.

Unknown option names and invalid option types cause errors.

Phases

PhaseExecution point
PreAnimationBefore animation evaluation.
PreSimulationBefore the parallel barrier and physics simulation.
FixedPreSimulationBefore each fixed physics tick.
FixedPostSimulationAfter each fixed physics tick.
PostSimulationAfter physics simulation.
PreRenderBefore rendering. Available only on the client.

A parallel System runs after PreSimulation and before physics. Set parallel = true and omit phase. The System must belong to a parallel ExecutionGroup.

Fixed systems use RunService.FixedTickRate. Several fixed ticks can run during one rendered frame. Use the supplied dt for time-dependent calculations.

Usage

Place this source in a System under ServerScriptService. The example assumes a ComponentDefinition named Durability in ReplicatedStorage.

Luau
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Durability = require(ReplicatedStorage:WaitForChild("Durability"))
local query = Query.new(Durability)

return System.define({ phase = "PostSimulation" }, function(dt)
    for instance, durability in query do
        durability.Current = math.max(0, durability.Current - dt)
    end
end)

Construct the query outside the callback so its cached storage can be reused. Use after, before, and sets when Systems require a defined order. RunService:GetSchedule() returns scheduling, execution-time, component-access, and conflict information.

Parallel execution

An ExecutionGroup with ExecutionMode = Enum.ExecutionMode.Parallel provides a separate Luau execution context. A callback can enter the parallel phase with task.desynchronize() and resume serial execution with task.synchronize(). Signal callbacks registered with ConnectParallel also require a parallel group.

The member tables identify access restrictions:

Thread safetyParallel access
UnsafeAccess is rejected.
ReadParallelReads and supported read calls are allowed; property writes are rejected.
WriteParallelReads and writes are allowed.
SerialOnlyAccess requires serial execution.

ComponentHandle field writes are recorded during the parallel phase and applied at the serial barrier. Structural changes, including adding or removing components, require serial execution. Use ExecutionGroup messages or SharedTable to exchange data between group contexts. Module and ordinary table state is not a substitute for shared storage across contexts.

Limitations

A System's source must return a SystemDefinition. Returning nil, a table, or another value prevents registration. A server System cannot select PreRender. Scheduling dependencies must be compatible with their phases and must not form a cycle. Check GetSchedule when a dependency cannot be scheduled.

A nonzero interval accumulates elapsed time. It does not guarantee a callback at an exact wall-clock instant. Callback failures are recorded in the schedule report and reported as script errors.

System · System global · SystemDefinition · ExecutionGroup · RunService · SharedTable · Component definitions