name: memory-safety description: Native container lifetime, allocator selection, and GC-avoidance rules for DOTS runtime code. Covers NativeArray/NativeList/NativeHashMap disposal patterns, Allocator.Temp vs TempJob vs Persistent guidance, and managed-allocation detection. Replaces the memory-checker subagent. use-when: | Load for unity-dots-dev when task involves NativeContainers, custom allocators, native memory disposal, GC pressure investigation, or diagnosing AtomicSafetyHandle / InvalidOperationException on containers. do-not-use-when: | Do not load for Unity classic MonoBehaviour tasks. Do not load for tester, verifier, or data-tool roles. Not needed if the task touches no native memory or containers. platforms: [claude-code, codex, copilot, cursor, windsurf] task-categories: [ecs, memory, native-containers, performance, dots] metadata: source: https://docs.unity3d.com/Packages/com.unity.collections@2.4 version: 2.4.4 tier: 1
Memory Safety
This is a skill pack, not an agent.
Allocator Decision Tree
Lifetime ≤ this frame, this scope → Allocator.Temp
Lifetime ≤ this job → Allocator.TempJob (dispose in job)
Lifetime spans frames (system state) → Allocator.Persistent (Dispose in OnDestroy)
Job allocates output for main thread → Allocator.TempJob
Asset/blob (read-only, long-lived) → BlobAssetReference<T> (no allocator)
Rule: every Allocator.Persistent allocation must have a matching Dispose()
in OnDestroy (for ISystem use state.Dispose-bound NativeArray/Lookup
fields). Otherwise the Job Safety System will leak across domain reloads.
Native Container Rules
- Never store a
NativeArrayallocated insideOnCreatewithoutDisposingit inOnDestroy. - Never pass a
NativeArrayto a job and reuse it on the main thread the same frame unless the job is.Complete()d first (= sync point — requires approval). - Never resize a
NativeListfrom a parallel job. UseNativeList<T>.ParallelWriter. - Don't share writable native containers across two parallel jobs without
dependency chaining. The Safety System will flag it as
InvalidOperationExceptionin editor; in player builds it silently corrupts. NativeHashMap<K,V>is not parallel-safe for writes. UseNativeParallelHashMap<K,V>.ParallelWriterfor writes from multiple threads.
Managed Allocation Hotspots
These are silent GC bombs inside OnUpdate / hot paths. Audit before signaling
verifier:
| Anti-pattern | Replace with |
|---|---|
string.Format(...) / $"..." |
FixedString*N*Bytes or skip the log |
new List<T>() |
NativeList<T>(Allocator.Temp) |
LINQ (.Where, .Select, .ToList) |
manual loop or IJobEntity |
Enum.GetValues(typeof(...)) |
a cached static readonly array outside hot path |
Boxing (e.g. object o = someStruct;) |
keep the type concrete |
Dictionary<K,V>.Add in hot path |
NativeParallelHashMap<K,V> |
Debug.Log("…" + obj) |
guard with #if UNITY_EDITOR if needed at all |
Blob Assets
Use BlobAssetReference<T> for read-only, long-lived data (terrain LUTs,
ability defs, item tables):
- Build with
BlobBuilderonce, in a baker or[CreateAsset]flow. - Pass by
BlobAssetReference<T>field on a component. - Never mutate after build — blobs are immutable.
Pre-Verifier Checklist
- Every new
Allocator.Persistenthas a pairedDispose()inOnDestroy - No
newof managed type inOnUpdate - No string interpolation in hot path
- No LINQ in hot path
- No
NativeHashMapwritten from a parallel job (use parallel variant) -
NativeArrayreturned from a job to the main thread is.Complete()d with explicit approval, OR consumed by a follow-up job via dependency
Profiler Evidence (recommended)
If complexity ≥ medium and triage flagged this as a hot system: take a
GC Alloc sample before and after via profiler_get_runtime_memory and include
the delta in verification_bundle.logs_to_inspect.