Table of Contents

Entity Query Usage

This page describes practical EntityQuery usage patterns from the runtime ECS API. For full API signatures, see the generated API pages under doc/api/.

Overview

EntityQuery is the main data access surface for ECS iteration in Ghost.Entities.

  • Build a query once using QueryBuilder and reuse the query handle (Identifier<EntityQuery>) across frames.
  • Access query data in three common ways:
    • ForEach for simple lambda-style iteration.
    • GetEntityComponentIterator<T...>() when you need entity IDs with component refs.
    • GetChunkIterator() for cache-friendly chunk-level traversal.
  • Schedule jobs (IJobChunk / IJobEntity<...>) for parallel work.

Minimal Setup

using Misaki.HighPerformance.Jobs;
using Misaki.HighPerformance.LowLevel.Buffer;

var scheduler = new JobScheduler(4);
var world = World.Create(scheduler);

using var scope = AllocationManager.CreateStackScope();
using var set = new ComponentSet(scope.AllocationHandle, ComponentTypeID<Transform>.Value);

Span<Entity> entities = stackalloc Entity[1000];
world.EntityManager.CreateEntities(entities, set);

var queryID = new QueryBuilder().WithAllRW<Transform>().Build(world);
ref var query = ref world.ComponentManager.GetEntityQueryReference(queryID);

Pattern 1: ForEach for Simple Iteration

Use ForEach<T...> when you want concise in-place updates or debug output.

query.ForEach<Transform>((entity, ref Transform transform) =>
{
    transform.position += new float3(1, 0, 0);
});

Why this pattern:

  • Fast to write and easy to read.
  • Good for gameplay logic and quick data transforms.

Pattern 2: Entity + Component Iterator

Use GetEntityComponentIterator<T...>() when you need both identity and mutable component data.

foreach (var (entity, transform) in query.GetEntityComponentIterator<Transform>())
{
    transform.Get().position += new float3(0, 1, 0);
}

Why this pattern:

  • Explicit access to the entity in each iteration.
  • Works well when you need side lookups keyed by entity.

Pattern 3: Chunk Iterator for Throughput

Use GetChunkIterator() for the most cache-friendly, low-overhead traversal.

foreach (var chunk in query.GetChunkIterator())
{
    var transforms = chunk.GetComponentDataRW<Transform>();
    var entitiesInChunk = chunk.GetEntities();

    for (var i = 0; i < chunk.EntityCount; i++)
    {
        transforms[i].position += new float3(0, 0, 1);
    }
}

Why this pattern:

  • Minimizes per-entity overhead.
  • Best fit for heavy simulation and broad data transforms.

Parallel Job Scheduling

EntityQuery supports chunk and entity parallel scheduling.

IJobChunk example:

internal struct MoveChunkJob : IJobChunk
{
    public void Execute(ChunkView view, ref readonly JobExecutionContext ctx)
    {
        var transforms = view.GetComponentDataRW<Transform>();
        for (var i = 0; i < view.EntityCount; i++)
        {
            transforms[i].position += new float3(5, 5, 5);
        }
    }
}

var handle = query.ScheduleChunkParallel(new MoveChunkJob(), batchSize: 1, JobHandle.Invalid);
scheduler.Wait(handle);

Notes:

  • Ensure all dependencies are represented in the input JobHandle.
  • Wait or chain handles before reading modified data on the main thread.
  • Prefer chunk jobs for contiguous memory access patterns.

Lifecycle and Cleanup

world.EntityManager.DestroyEntities(entities);
world.Dispose();
scheduler.Dispose();
JobScheduler.ReleaseTempAllocator();

If your test or app creates worlds/schedulers manually, always clean them up in deterministic order.