Threading & Parallelism Query #003 2026-03-20 Namespace: Verse + RimWorld 15+ source files analyzed

Which Parts of the Code Are Safe to Run in Parallel?

"Which parts of the code are safe to run in parallel?"

Answer confidence:
90% — All threading patterns verified in decompiled source
MP-Critical
Do not parallelize game simulation code. RimWorld's game loop (DoSingleTick()) is fundamentally single-threaded with a fixed, deterministic order of 22 steps. The global Rand state (seed + iterations) has no locks, no atomics, no thread-local isolation — any parallel Rand call will cause a desync.

Only loading, pathfinding (Burst Jobs), and data preparation are safe to run in parallel — because they never touch Rand or shared game state.

What Ludeon Already Parallelizes

RimWorld 1.6 uses 5 distinct parallelism mechanisms. Each is carefully isolated from the game simulation loop.

MechanismWhere UsedWhen
Unity Burst Jobs BURST PathFinderJob (A* search) Every frame, scheduled & completed async
GenThreading.ParallelFor/Each DefDatabase, XmlCrossRef, ThingFilter Loading only (startup)
Parallel.ForEach PathFinderMapData, DefOfHelper, ShortHashGiver, PlayDataLoader, SaveFileList Loading + per-tick data gathering
PLINQ (.AsParallel()) GenTypes (type caching), DirectXmlLoader Loading only (one-time)
Explicit Thread DirectXmlLoader (2 threads), AudioClipLoader Loading only

1. Pathfinding — The Big One SAFE

PathFinderJob — Burst-Compiled A* BURST

In RimWorld 1.6, pathfinding was moved to Unity Burst Jobs. The PathFinderJob struct implements IJob with [BurstCompile] and runs A* on NativeArrays (unmanaged memory). Multiple path requests can be batched and scheduled in parallel.

Why it's safe: Burst jobs operate on copied data (NativeArrays). They cannot access managed C# objects, static fields, or the Rand state. The grid data is gathered beforehand on the main thread.

Verse/PathFinderJob.cs Verse/PathFinder.cs
Main Thread: GatherData() Schedule PathFinderJobs Worker Threads: A* on NativeArrays Main Thread: Complete & read results

PathFinderMapData — Parallel Data Sources

The 13 data sources (Cost, Area, Connectivity, Water, Fence, Building, Faction, Fog, Danger, Darkness, Perceptual...) are updated via Parallel.ForEach(sources, ...) — each source computes independently on its own NativeArray slice.

Why it's safe: Each IPathFinderDataSource writes to its own array. No shared mutable state. No Rand calls. Incremental updates (cellDeltas) are collected on the main thread first.

Verse/PathFinderMapData.cs:218-235

2. Loading & Startup SAFE

All parallelism during loading happens before the game simulation starts. No ticks are running, no Rand is active.

SystemMechanismFile
XML Asset Loading 2 explicit Threads + ConcurrentBag Verse/DirectXmlLoader.cs
XML Cross-References GenThreading.ParallelForEach Verse/DirectXmlCrossRefLoader.cs:475
DefDatabase Population Parallel.ForEach per Def subclass Verse/PlayDataLoader.cs:110
DefOf Binding Parallel.ForEach RimWorld/DefOfHelper.cs:20
ShortHash Assignment Parallel.ForEach Verse/ShortHashGiver.cs:16
Type System Caching AllTypes.AsParallel() Verse/GenTypes.cs:163-210
Static Constructors Parallel.ForEach Verse/StaticConstructorOnStartupUtility.cs:35
ThingFilter Init GenThreading.ParallelFor + lock Verse/ThingFilter.cs:557-558
DefDatabase Actions GenThreading.ParallelForEach Verse/DefDatabase.cs:150
Audio Loading Background Thread + lock RuntimeAudioClipLoader/Manager.cs
Save File Metadata Parallel.ForEach RimWorld/Dialog_SaveFileList.cs:26

3. Fleck Drawing SAFE

FleckParallelizationInfo — Parallel Draw Batching

Fleck (particle) drawing is batched across threads using ManualResetEvent for synchronization. Each batch operates on a slice of the fleck array with its own DrawBatch. Pure rendering — no game state mutation, no Rand.

FleckParallelizationInfo.cs Verse/FleckSystemBase.cs

4. Immutable Stat Caching SAFE

StatWorker — ConcurrentDictionary for Immutable Stats

ConcurrentDictionary<Thing, float> immutableStatCache is used for stat values that never change. Thread-safe by design. The mutable stat cache is a regular Dictionary (single-threaded only).

RimWorld/StatWorker.cs

What Is NOT Safe to Parallelize

The Single-Threaded Core
The entire game simulation — everything inside DoSingleTick() — is single-threaded and must stay that way. 22 steps execute in a fixed order. Each step can read/write any game state freely because nothing else runs concurrently.

Cannot Parallelize — Rand-Dependent

Cannot Parallelize — Shared Mutable State

GenThreading — RimWorld's Custom Parallelization

Ludeon built their own parallel framework instead of using System.Threading.Tasks.Parallel everywhere. It uses ThreadPool.QueueUserWorkItem with AutoResetEvent synchronization.

SliceWork() N batches (N = ProcessorCount) ThreadPool.QueueUserWorkItem per batch Interlocked.Add + AutoResetEvent Main thread waits until all done
MethodSignatureUsed For
ParallelForEach<T> (List<T>, Action<T>, maxDegree) DefDatabase, XmlCrossRef
ParallelFor (from, to, Action<int>, maxDegree) ThingFilter initialization

Verse/GenThreading.cs:1-145

Thread Safety Patterns in the Codebase

PatternWhere UsedPurpose
lock (object) Log, ThingFilter, AudioLoader Critical section protection
ConcurrentDictionary StatWorker (immutable cache) Thread-safe read/write cache
ConcurrentBag DirectXmlLoader Thread-safe collection for XML files
Interlocked.Add/Read GenThreading, Log, DeepProfiler Atomic counter operations
volatile DeepProfiler.enabled Thread-safe flag visibility
ManualResetEvent FleckParallelizationInfo Draw batch completion signal
AutoResetEvent GenThreading, ParallelDeflateOutputStream Task completion synchronization
Thread-local storage DeepProfiler (per-thread instances) Avoid contention on profiler stacks
Rand.ValueAsync(seed) Available but rarely used Stateless RNG — safe from any thread

The Rand Problem MP-CRITICAL

Why Most Game Code Cannot Be Parallelized

Rand is a global static with two fields: seed (uint) and iterations (uint). Every Rand.Value call increments iterations by 1. There is no lock, no atomic operation, no thread-local state.

If two threads call Rand.Value concurrently, the iterations counter gets corrupted. Even if you could prevent corruption, the call order would be non-deterministic — breaking savegame reproducibility.

Rand is called in: Pawn.Tick, Thing.PostMake, Kill(), DamageInfo, MTBEventOccurs, HediffSet, Storyteller, equalizeCells.Shuffle, combat verbs, social interactions, need calculations — virtually everywhere.

Safe Alternative: Rand.ValueAsync(seed)
Rand.ValueAsync(int seed) calls MurmurHash.GetInt(seed, 0) directly. No global state touched. Safe from any thread. But almost nothing in the codebase uses it.

Verse/Rand.cs:98-101

Safety Classification

CategorySafetyExamplesReason
Burst Jobs SAFE PathFinderJob NativeArrays, no managed access
Data Source Gathering SAFE PathFinderMapData sources Each source writes own array
Loading / Startup SAFE XML, Defs, Types, Audio No simulation running
Fleck Rendering SAFE FleckSystemBase draw batches Pure rendering, no game state
Immutable Caches SAFE StatWorker immutable cache ConcurrentDictionary, read-heavy
Pure Hash Functions SAFE MurmurHash, Rand.ValueAsync No state, pure function
Read-Only Queries CAUTION Reachability, Region traversal Safe if no rebuild during query
Map Grid Reads CAUTION ThingGrid.ThingsAt, CellIndices Safe if no spawn/despawn during read
Tick Simulation UNSAFE DoSingleTick, all 22 steps Global Rand + shared mutable state
Pawn Operations UNSAFE Tick, Kill, Spawn, Job dispatch Rand + 20 trackers + Map registration
Thing Lifecycle UNSAFE PostMake, SpawnSetup, DeSpawn Rand + 15 Map subsystem registrations

Implications for Modders

If You Want to Add Parallelism

Safe to parallelize:


Never parallelize:

Burst Jobs: Not Patchable!
PathFinderJob is Burst-compiled native code. Harmony cannot patch it. To modify pathfinding costs, patch PathFinderMapData.ParameterizeGridJob or PathFinderCostTuning.For(Pawn) instead — these are managed C# and run on the main thread.

Verse/PathFinderCostTuning.cs

Related Systems

These systems are directly related to parallelism decisions:

RNG / Rand — MP-Critical Global static state, no thread safety — the #1 barrier to parallelism
→ Read more
Game Loop — DoSingleTick Fixed 22-step order, deterministic, single-threaded — the backbone
→ Read more
Pathfinding — Burst Jobs The only simulation-adjacent system that is truly parallel
Verse/PathFinderJob.cs
Thing Hierarchy — Spawn/DeSpawn 15+ Map subsystem registrations — cannot run concurrently
→ Read more
Pawn — 20+ Trackers Each tracker may call Rand, modify shared state, or trigger events
→ Read more
Region System — Rebuild Pipeline Region rebuild mutates RegionGrid + triggers Room recalculation
→ Read more

Analysis Coverage

22%
1,895 of ~8,650 source files analyzed
Based on decompiled C# source (ILSpy)

The threading analysis is comprehensive because parallelism patterns are concentrated in a few key files. All Parallel.For, GenThreading, AsParallel, Thread, lock, volatile, ConcurrentDictionary, and Burst usages have been found via full-text search.

NamespaceFilesAnalyzed
Verse1,747326
Verse.AI~278118
Verse.AI.Group~12919
RimWorld5,9131,114
RimWorld.QuestGen381306
RimWorld.Planet~20012

Not Yet Covered

These files were not deeply analyzed