---
title: Systems and traversal
description: Write systems with EcsSystem, Each, and EachBlock, and opt traversals into partitioning.
sidebar:
  order: 5
---

## `[EcsSystem]`

`[EcsSystem]` marks a synchronous static `void` method. Supported parameters are scoped [named queries](/docs/guides/queries) and the capabilities listed in [Resources and local state](/docs/guides/resources), [Commands](/docs/guides/commands), and [Messages](/docs/guides/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`](/docs/reference/diagnostics)).

## `[Each]`

```csharp
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.

### Optimized builds

A Debug build calls the method once per entity, so a breakpoint in it is hit. An optimized build does not call the method. The generator copies its body into the loop over the entities, which removes the call whether or not the runtime would have inlined it. That includes NativeAOT, and a system that first ran while no entity matched.

- An exception thrown in the body does not name your method in its stack trace. It names a generated method, such as `__Archevolt_<Method>_Inlined`.
- The generator calls the method itself when a copy could behave differently. That is the case when the method contains a preprocessor directive, calls a method with a caller-information parameter such as `[CallerMemberName]`, or carries `[MethodImpl]` with `NoInlining`, `NoOptimization`, or `Synchronized`, or `[Conditional]`. It is also the case when the file declares a file-local type or an `extern alias`, or puts `using` directives on an outer namespace.

### SIMD kernels

In an optimized build, the generator compiles some `[Each]` methods into a SIMD loop over the component columns. It does so when it can write every float of the written component as a formula over the floats of the same entity, resource values, and constants. 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 the operations in this table.

| Kind | Allowed |
| --- | --- |
| Arithmetic | Float `+`, `-`, `*`, `/`, and negation. The `+`, `-`, and `*` operators of `Vector2`, `Vector3`, and `Vector4`. |
| Values | Constants, `static readonly` float and vector fields, locals, assignments to locals and to fields of the written component, fields and auto-properties of components and resources, positional record constructors, the constructors of `Vector2`, `Vector3`, and `Vector4`, and `with` expressions. |
| Branches | `if`, `else`, and `?:`, with conditions that compare floats using `<`, `<=`, `>`, `>=`, `==`, or `!=`, combined with `&&`, `\|\|`, and `!`. |
| Functions | `Abs`, `Sqrt`, `Min`, and `Max` of `MathF` or `float`, and the `float` overloads of `Math.Abs`, `Math.Min`, and `Math.Max`. |
| Calls | Non-generic static methods of the same project that follow these rules. They may take the written component by `ref`. |

Both of these methods run as kernels:

```csharp
[Each]
public static void Wrap(scoped ref Position position, scoped in Velocity velocity, scoped Res<FrameTime> time)
{
    float x = position.X + velocity.X * time.Value.DeltaSeconds;
    // Arena.Width is a const.
    if (x >= Arena.Width) x -= Arena.Width;
    position = position with { X = x };
}

[Each]
public static void Spin(scoped ref Rotation rotation, scoped Res<FrameTime> time)
    => rotation = rotation with { Angle = rotation.Angle + rotation.Speed * time.Value.DeltaSeconds };
```

A kernel computes both outcomes of a branch for every entity and keeps one. The result is the same as taking the branch, because a write made in one outcome reaches only the entities that take it.

These keep the loop over the entities:

- A loop, or a `return` before the end of the method.
- `%`, a conversion to `double` or `int`, or any other method of `Math` and `MathF`.
- A computed property, a static property such as `Vector3.Zero`, a recursive method, or a method from another assembly.
- A call to a local function, a generic method, a `[Conditional]` method, or a method with an `out` parameter or a `ref` return.
- A called method that writes through a `ref` parameter inside `?:` or on the right of `&&` or `||`, or that writes floats the same call also passes through `in`.
- A `[Conditional]` `[Each]` method.
- An `Entity` or `Commands` parameter.
- Components with different numbers of floats, such as a `float` written from a `Vector2`.
- A component of more than four floats whose floats get different formulas, where the CPU or the build has no 256-bit vectors. NativeAOT has none unless you raise its instruction set. A component of more than eight such floats always keeps the loop.

The kernel performs the same IEEE operations in the same order as the method, so it writes the same bits. The exception is the sign and payload bits of a NaN result, which .NET does not define for scalar code either.

## `[EachBlock]`

```csharp
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](/docs/guides/change-tracking) 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](/docs/guides/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`](/docs/reference/diagnostics).

## Descriptors

The generator exposes each method as `Owner.MethodName_Descriptor`. Runtime composition accepts these descriptors through `BoundSchedule.Bind`. See [Schedules](/docs/guides/schedules#runtime-composition).
