Skip to content

Bulk Query Operations / CRUD ​

Neofox: hyper POWER IN NUMBERS!

Query lets you Add or Remove components (and even Despawn!) in bulk – just like an individual Entity, but for thousands at once!

Quick Reference ​

MethodDescription
Query.Add<C>(value)Add a component to all matched Entities
Query.Remove<C>()Remove a component from all matched Entities
Query.Remove<C>(match)Remove all matching components (Wildcards allowed) from all matched Entities
Query.Despawn()Despawn all matched Entities
Query.Truncate(n, mode)Reduce matched Entities to a specific count
Query.Batch()Begin a batch for multiple structural changes

Usage Examples ​

cs
// Query for all entities with Health that also took Damage
var whoTookDamage = world.Query<Health, Damage>().Stream();

// Do something for each Entity in Query
whoTookDamage.For((in entity, ref health, ref damage) => 
{
    health.hp -= damage.amount;
    if (health.hp < 0) entity.Add<Exploding>();
});

// 👇 All damage values were accounted for! Bulk remove the values.
whoTookDamage.Remove<Damage>();
cs
// Query for all Entities that are about to Explode (and have a Position)
var whosExplodingWhere = world.Query<Position>().Has<Exploding>().Stream();

// Do something for each Entity in Query
whosExplodingWhere.For((ref position) => Game.SpawnExplosion(position));

// 👇 Despawn all Entities matched by Query
whosExplodingWhere.Despawn();
cs
var loadedGuns = world.Query()
    .Has<Gun>()
    .Not<Loaded>().Not<Cooldown>().Not<RequestProjectileSpawn>()
    .Stream();

// ⚠️ Multiple bulk operations may require batching, because
// entities wouldn't necessarily match our query after each change
// and thus "escape" before subsequent operations would be applied.
loadedGuns.Batch()
    .Add<Cooldown>(2.0f)  // for our cooldown system
    .Add<RequestProjectileSpawn>()  // for another system
    .Remove<Loaded>()
    .Submit();

Adding & Removing Components ​

MethodBehavior
Query.Add<C>(value)Adds component to each Entity. Throws if Query already matches that Component.
Query.Remove<C>()Removes component from each Entity. Throws if Query doesn't match that Component.

Neofox: think WHY THE EXCEPTIONS?

These safety checks prevent silent no-ops. If you're adding a component the Query already matches, either ALL entities already have it, or the query is empty. Same logic for removal.

Object Links and Entity Relations can also be added/removed in bulk – via a Batch, which offers the same Add/Remove surface as an individual Entity:

cs
// Add an object link to all matched entities
query.Batch().Add(Link.With(sharedTexture)).Submit();

// Add a relation (with backing data) to all matched entities
query.Batch().Add(new Attacking(damage), targetEntity).Submit();

// Remove a relation from all matched entities
query.Batch().Remove<Attacking>(targetEntity).Submit();

// Wildcards work too: remove ALL Attacking relations, whatever their target
query.Batch().Remove<Attacking>(Entity.Any).Submit();

Batch Operations ​

Structural changes often cause Entities to no longer match the Query. Use Query.Batch() to queue multiple changes:

cs
var batch = query.Batch()
    .Add<Tag>()
    .Add<Health>(100)
    .Remove<Invulnerable>();

batch.Submit();  // Execute all at once

Neofox: nom_verified BATCH LIFECYCLE

Once you Submit() a Batch, the World takes ownership. You only need to Dispose() the Batch if you decide not to submit it.

Handling Conflicts ​

Batch operations can encounter semantic conflicts when adding/removing components:

ConflictExample
Add<T> conflictSome or all Entities already have that component
Remove<T> conflictNot every Entity has that component

Pass conflict resolution strategies to Query.Batch(AddConflict, RemoveConflict):

AddConflict Options ​

OptionBehavior
Strict (default)Throw if adding a component not explicitly excluded from query
PreserveKeep existing values, add only where not present
ReplaceOverwrite existing values, add where not present

RemoveConflict Options ​

OptionBehavior
Strict (default)Throw unless component is expressly included in Query
AllowSkip Archetypes where component isn't present (idempotent, near-zero cost)
cs
// Example: Replace health values on all entities, even those that already have Health
query.Batch(AddConflict.Replace)
    .Add(new Health(100))
    .Submit();