Skip to content
Archevolt
Esc
↑↓navigate↵open⌘Jpreview
On this page

Systems and traversal

Write systems with EcsSystem, Each, and EachBlock, and opt traversals into partitioning.

[EcsSystem]

[EcsSystem] marks a synchronous static void method. Supported parameters are scoped named queries and the capabilities listed in Resources and local state, Commands, and Messages.

Private methods are callable through generated invokers inside their partial owner. Overloads, generic methods, async methods, iterators, and ref returns are rejected (diagnostic AV005).

[Each]

public static partial class MotionKernel
{
    [Each(Partitioned = true), Without<Frozen>]
    public static void Move(scoped ref Position position, scoped in Velocity velocity)
    {
        position = position with { X = position.X + velocity.X };
    }
}

[Each] calls the method once per matching entity.

  • Use scoped ref T for writes and scoped in T for reads.
  • An optional Entity parameter supplies identity.
  • Membership and change-filter attributes apply to the method.
  • Components passed by value are rejected.

SIMD kernels

In an optimized build, the generator compiles some [Each] methods into a SIMD loop over the component columns instead of a call per entity. It does so when each float of the written component gets the same formula, over the same float of the other components. The method must:

  • Write one component, and read only components with the same number of floats.
  • Take only component refs and Res<T> parameters, with no Added<T>, Changed<T>, or Optional<T> term.
  • Use only float +, -, *, /, and negation, the +, -, and * operators of Vector2, Vector3, and Vector4, constants, locals, fields and auto-properties of components and resources, positional record constructors, and with expressions.

position = position with { Value = position.Value + velocity.Value * time.Value.DeltaSeconds } qualifies. A method call, a branch, a conversion to double, or a formula that mixes X into Y keeps the call per entity.

The kernel performs the same IEEE operations in the same order as the method, so it writes the same bits. The exception is which NaN payload results when two NaN operands meet, which .NET does not define for scalar code either. Debug builds always call the method, so a breakpoint in it is hit. An optimized build does not call a qualifying method.

[EachBlock]

public static partial class MotionKernel
{
    [EachBlock(Partitioned = true), Without<Frozen>]
    public static void MoveBlock(scoped Span<Position> positions, scoped ReadOnlySpan<Velocity> velocities)
    {
        for (int i = 0; i < positions.Length; i++)
            positions[i] = positions[i] with { X = positions[i].X + velocities[i].X };
    }
}

[EachBlock] calls the method once per block of matching entities and passes each component as a span over that block. A block is a contiguous run of entities from one archetype. Use it when you want to write the row loop yourself, for example with Vector256.

  • Use scoped Span<T> for writes and scoped ReadOnlySpan<T> for reads. Every span in one call has the same length.
  • An optional scoped EntityColumn parameter supplies identity. entities[i] returns the Entity for row i.
  • With<T> and Without<T> apply to the method.
  • Added<T> and Changed<T> select whole blocks, not rows. The method receives a block when its archetype has an addition or change since the system last ran, and every row of that block comes with it, including rows that did not change. Use [Each] or a named query when the body must see only the changed rows.
  • A writable span of a tracked component stamps every row in the block as changed.

Partitioning

[Each] and [EachBlock] execute one traversal by default. Partitioned = true opts into splitting the matched rows into contiguous ranges, one per worker in the world’s execution budget, which matters once parallel execution is enabled. A partitioned [EachBlock] method can receive several blocks from one archetype.

Partitioned methods accept component refs or spans, Entity or EntityColumn, and readonly unmanaged resources, including optional resources without owner-thread affinity. Locals, commands, messages, managed resources, mutable resources, and named queries are excluded. An unsafe partition request is reported as AV010.

Descriptors

The generator exposes each method as Owner.MethodName_Descriptor. Runtime composition accepts these descriptors through BoundSchedule.Bind. See Schedules.

Was this page helpful?