> For the complete documentation index, see [llms.txt](https://wise-shepherd-games.gitbook.io/theia-ecs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wise-shepherd-games.gitbook.io/theia-ecs/execution/iteration-safety.md).

# Iteration Safety

Prevents structural changes from corrupting in-progress iteration

Theia enforces a strict contract: **structural changes cannot be applied while the world is being read**. This page explains how that contract works, what it covers, and how to work within it.

#### The Queries-Executing Counter

Every query iteration, `ForEach`, `ForEachEntity`, `ForEachParallel` and `ForEachEntityParallel`, increments the world's queries-executing counter on entry and decrements it on exit. The counter is thread-safe via `Interlocked`, so multiple parallel queries incrementing and decrementing concurrently is safe.

Any structural change attempted while the counter is non-zero throws immediately:

```csharp
/* Returns true if any query is currently iterating */
world.AreThereAnyQueriesBeingExecuted();
```

The following operations are structural changes and throw if any query is executing:

* Creating assemblages;
* Creating queries;
* Creating or ghoulifying entities;
* Adding or removing components;
* Flushing deferred commands (`FlushDeferred` throws if the counter is non-zero).

{% hint style="info" %}
**Adding or removing relations is not a structural change from the world-query perspective.**

It only throws when the specific relation's own update lock is held via `QueryRelation` or `QueryEvaluatedRelation`.
{% endhint %}

Serialization and deserialization also throw if called while queries are executing or a flush is in progress; see [Serialization](/theia-ecs/serialization.md).

#### The Relation Interlock

`QueryRelation` and `QueryEvaluatedRelation` have their own **per-relation update lock**, **independent of the world-query counter**. Structural changes to a relation, such as adding or removing links and relations, **throw if that relation's iteration lock is currently held**. The same principle applies: use deferred commands to stage relation changes during iteration.

#### The Deferred Path

**Inside a query** callback, structural changes **always go through the deferred path**. The world-query counter is non-zero for the duration of the iteration, so the immediate API throws. Stage the change and flush after iteration completes; see [Deferred Commands](/theia-ecs/entities-and-composition/deferred-commands.md) for the full API.

#### Queries Must Not Throw

If a query callback throws an unhandled exception, the counter is never decremented, therefore, **the world is left in a permanently broken state** where every subsequent structural change throws. Beyond the counter consequence, **a game should never throw during normal simulation execution**. **Exceptions** in a hot loop that runs every tick **are a design smell**. Keep callbacks clean, validate inputs before iteration, and handle error cases without throwing.

#### Ref Access and Concurrent Mutations

`Get<T>` and `Set<T>` are not structural changes and are safe during single-threaded iteration. However, anything that returns a `ref` or mutates through a `ref` directly (*e.g.*: component references, relation payloads or span elements) produces undefined behavior if another thread is accessing the same memory concurrently. **Concurrent ref access to the same data from multiple jobs is the caller's responsibility to avoid**.

#### When to Check `AreThereAnyQueriesBeingExecuted` and `IsFlushingDeferred`

Entity lifecycle and component events cannot fire during query iteration; structural changes are blocked, so there is no path for them to trigger. In a well-structured simulation, structural changes **happen only on the main thread between ticks**, **and these checks are never needed**.

These methods exist for call sites **where the world's state cannot be guaranteed at the time of the call**; **parallel jobs and relation event handlers are the canonical cases**. A structural change inside a parallel job or a relation event handler may need to decide at runtime whether the world allows the immediate path or requires deferring. Checking `AreThereAnyQueriesBeingExecuted()` or `IsFlushingDeferred()` makes that decision explicit.
