Skip to content

06 — Partitions

Template partitions split entities into separate groups for cache-friendly, targeted iteration.

Source: com.trecs.core/Samples~/Tutorials/06_Partitions/

What it does

Balls bounce under gravity. When a ball's energy drops below a threshold, it moves to a "Resting" partition and turns gray. After a rest timer expires, it launches back into the air and returns to "Active".

Schema

Tags

public static class BallTags
{
    public struct Ball : ITag { }
    public struct Active : ITag { }
}

Template with partitions

public partial class BallEntity
    : ITemplate,
        IExtends<CommonTemplates.RenderableGameObject>,
        ITagged<BallTags.Ball>,
        IPartitionedBy<BallTags.Active>
{
    Position Position;
    Velocity Velocity;
    RestTimer RestTimer;
    PrefabId PrefabId = new(PartitionsPrefabs.Ball);
}

IPartitionedBy<T> declares a presence/absence partition dimension. Two partitions are emitted: balls with the Active tag, and balls without. The absent case has no companion tag — query it with Without =.

Systems

PhysicsSystem — active balls only

Processes only balls in the Active partition:

[ForEachEntity(typeof(BallTags.Ball), typeof(BallTags.Active))]
void Execute(in ActiveBall ball)
{
    var vel = ball.Velocity;
    vel.y += Gravity * World.DeltaTime;
    ball.Position += vel * World.DeltaTime;
    ball.Velocity = vel;

    // Transition to the absent (idle) partition when energy is low AND on the floor
    if (
        math.lengthsq(ball.Velocity) < RestThreshold * RestThreshold
        && ball.Position.y <= FloorY + 0.01f
    )
    {
        ball.Velocity = float3.zero;
        ball.RestTimer = 2f + World.Rng.Next() * 3f; // rest 2-5 seconds
        ball.UnsetTag<BallTags.Active>(World);
    }
}

partial struct ActiveBall : IAspect, IWrite<Position, Velocity, RestTimer> { }

WakeUpSystem — resting balls only

[ExecuteAfter(typeof(PhysicsSystem))]
public partial class WakeUpSystem : ISystem
{
    [ForEachEntity(typeof(BallTags.Ball), Without = typeof(BallTags.Active))]
    void Execute(in RestingBall ball)
    {
        ball.RestTimer -= World.DeltaTime;

        if (ball.RestTimer <= 0)
        {
            float angle = World.Rng.Next() * 2f * math.PI;
            ball.Velocity = new float3(math.cos(angle) * 2f, LaunchSpeed, math.sin(angle) * 2f);
            ball.SetTag<BallTags.Active>(World);
        }
    }

    partial struct RestingBall : IAspect, IWrite<Velocity, RestTimer> { }
}

BallPresenter — different rendering per partition

Two [ForEachEntity] methods, different tag filters:

[ExecuteIn(SystemPhase.Presentation)]
public partial class BallPresenter : ISystem
{
    [ForEachEntity(typeof(BallTags.Ball), typeof(BallTags.Active))]
    void RenderActive(in ActiveBallView ball)
    {
        // Yellow/red color
    }

    [ForEachEntity(typeof(BallTags.Ball), Without = typeof(BallTags.Active))]
    void RenderResting(in RestingBallView ball)
    {
        // Gray color
    }

    public void Execute()
    {
        RenderActive();
        RenderResting();
    }
}

Concepts introduced

  • IPartitionedBy<T> — declares a presence/absence partition dimension on a template. See Templates and Groups, GroupIndex & TagSets.
  • SetTag<T>() / UnsetTag<T>() — transition entities between partitions by toggling the tag. See Structural Changes.
  • Without = typeof(T) — query the absent partition.
  • Partition-filtered iteration — systems iterate only entities in a specific partition. See Queries & Iteration.
  • Partition separation — Active and Resting balls live in separate contiguous arrays for cache-friendly iteration.
  • Multiple [ForEachEntity] methods — different queries in one system, called from an explicit Execute(). See Systems.
  • For dynamic, overlapping membership, see Sets.