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
QueryBuilderand reuse the query handle (Identifier<EntityQuery>) across frames. - Access query data in three common ways:
ForEachfor 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.