> 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/jobs.md).

# Jobs

Parallel execution unit

A job is the **unit of parallel work in Theia**. It represents a single, self-contained piece of work that **runs on a worker thread**, independently of any other job in the same batch. Most of the time jobs are an implementation detail, queries and `ParallelSystems` create and dispatch them internally, but understanding the model helps when reasoning about parallel execution and is useful when implementing custom parallel work.

#### Why Classes, Not Structs

`Job` is an abstract class, and all derivatives are classes. Since jobs are stored in a `ConcurrentQueue` and dispatched polymorphically through the abstract `Job` base, **they need to be heap-allocated** **references**.&#x20;

If they were structs, every enqueue would box them, and the virtual `Execute` dispatch would still require a reference anyway. Using classes means the reference is stored directly: no boxing, no unboxing, and the virtual dispatch is a single *vtable* lookup. `JobPool<TJob>` then recycles those heap allocations across batches, **so the cost is paid once at warmup rather than every frame**.

#### Job Scheduler

`JobScheduler` is a process-wide scheduler. On startup it launches `ProcessorCount - 1` worker threads, **each** running a loop that **waits for available work and drains the shared queue** when signaled. **The submitting thread participates in draining alongside the workers**.

Two fast paths skip the scheduler entirely and run jobs inline on the calling thread:

* **Single job:** scheduling overhead would dominate the work itself;
* **No worker threads:** single-core hosts have no workers to dispatch to.

`CountdownEvent` instances are pooled and reused across batches, avoiding allocation on every `JobScheduler.Run` call.

`JobScheduler.Run` is the only entry point. It takes a span of jobs, dispatches the batch, and blocks until every job has signaled completion:

```csharp
JobScheduler.Run(jobs.AsSpan(0, jobCount));
```

#### Job Pool

Each `Job` subtype has **its own static pool managed** by `JobPool<TJob>`. The pool **pre-allocates a small batch** on first access and hands instances out via `Rent`; callers return them with `Return` when finished. **Rent and return are thread-safe**. The pool does not reset returned jobs, therefore, callers are responsible for re-initializing rented instances before invoking `Execute`.

Since `JobPool<TJob>` is generic, a job type that is itself generic gets an independent pool per closed instantiation. This is the core foundation that makes the query and parallel system job infrastructure work without contention.

#### Implementing a Custom Job

For parallel work that doesn't fit the query or system model, derive from `Job` and override `Execute`:

```csharp
public sealed class PathfindingJob : Job
{
    public Entity Entity;
    public World World;

    public override void Execute()
    {
        /* Run pathfinding for Entity on a worker thread */
    }
}
```

Rent instances from `JobPool`, initialize them, dispatch via `JobScheduler.Run`, then return them. **The calling thread blocks until all jobs in the batch have finished**:

```csharp
PathfindingJob jobA = JobPool<PathfindingJob>.Rent();
PathfindingJob jobB = JobPool<PathfindingJob>.Rent();

jobA.Entity = entityA;
jobA.World = world;
jobB.Entity = entityB;
jobB.World = world;

JobScheduler.Run(stackalloc Job[2] { jobA, jobB });

/* All paths are resolved before reaching this point */

JobPool<PathfindingJob>.Return(jobA);
JobPool<PathfindingJob>.Return(jobB);
```

#### How Queries and Systems Use Jobs

Parallel query iteration, `ForEachParallel` and `ForEachEntityParallel`, dispatches **one job per non-empty archetype chunk**. **Each chunk** **job receives its own copy of the callback** and runs independently on a worker thread.

`ParallelSystems` **wraps each child system's full lifecycle** **in a** `SystemJob` and dispatches them as a batch through `JobScheduler`.

Since `JobScheduler` is a shared queue that all worker threads drain, a `ParallelSystems` node running systems that themselves call `ForEachParallel` works naturally: the system jobs and the chunk jobs they produce all enter the same queue. **Worker threads don't distinguish between job types**; they grab the next available item, execute it, and keep going until the batch is complete.
