DOTS//CORE local · not synced

m05 · Baking · reading · 10 min

Prefabs and blob assets — what gets shared instead of copied

The copy problem

Everything so far bakes authored data into entities: a value on a MonoBehaviour becomes a value in a component, one copy per entity. For small per-entity values that’s exactly right — that’s what chunks are for.

But two kinds of data break it. Data that many entities must share identically (a 4 KB pathfinding grid every agent reads), and data that describes a thing to be created later (the enemy prefab a spawner instantiates). Copying either into every entity is wrong, and each is wrong for its own reason. Baking handles them with two different mechanisms, and the mechanisms are worth deriving rather than memorizing.

Prefabs: the entity that exists but doesn’t run

Start with the spawner. A spawner needs to create enemies at runtime. Module 1 says instantiation copies an existing entity’s archetype and component values into a new entity — so instantiating requires a source entity that already has the right shape and data.

Where does that source come from? It can’t be constructed at runtime; that would mean writing in code the archetype and values a designer authored, duplicating the authoring work in C# and guaranteeing drift. It should be baked, like everything else authored.

But a baked source entity would then exist in the world — and every query matching its archetype would pick it up. Your enemy prefab would be rendered, moved by the movement system, and damaged by the damage system, sitting at the origin as a ghost enemy nobody placed.

Both requirements have to hold at once: be a fully baked entity with the correct archetype and values, and be invisible to ordinary queries. That is precisely what a Prefab tag component does. It’s a zero-size tag (Module 1), and the query system excludes Prefab-tagged entities by default. The entity is real, instantiable, and unmatched.

Notice you just derived a piece of API from constraints, which is the test §0 sets. Given “instantiation needs a source entity” plus “sources must not participate in gameplay,” a default-excluded tag is the obvious answer — and it costs zero bytes because tags carry no data.

In a baker, this is GetEntity(prefabGameObject, TransformUsageFlags.Dynamic) stored in a component, so the spawner holds an Entity handle to its source — one of the references the last lesson said gets remapped on load. The pieces interlock.

Blobs: why big shared data can’t be a component

Now the pathfinding grid. Suppose 10,000 agents each need to read the same 4 KB navigation data.

Putting it in a component is catastrophic, and Module 1 gives the exact price. A chunk is 16 KB. A component of 4 KB means three entities per chunk — before any other component. Your iteration loses every property that made it fast: no density, no prefetch benefit, cache lines full of a grid the loop reads one field of. And you’ve stored 10,000 identical copies of the same 4 KB, which is 40 MB to represent 4 KB of information.

cache line · 64 B fetched 4 B useful · 6% of the bandwidth you paid for a chunk holding a large per-entity payload: capacity collapses to a handful of entities, and the loop pays a cache miss per entity for data that was identical across all of them

So it must be stored once, outside the chunk, with entities referencing it. Which raises the question that determines the mechanism: why not just a pointer?

Three reasons, each fatal on its own. A managed C# reference can’t live in an IComponentData — components are unmanaged, and Burst-compiled jobs can’t touch managed objects at all (Module 3). A raw pointer can’t be serialized — the address is meaningless in the next process, so a baked scene containing pointers is garbage on load. And a pointer gives the safety system nothing to reason about; jobs sharing it get no read/write analysis (Module 4).

A blob asset is the construct that satisfies all three. It is immutable, unmanaged, position-independent data with a BlobAssetReference<T> handle that is Burst-compatible, job-safe, and — critically — serializable, because internal links inside a blob are stored as relative offsets rather than absolute addresses. A blob doesn’t care where in memory it lands; every reference inside it is “so many bytes from here.” That single design choice is what lets baked data contain structured, linked information at all.

Immutability isn’t a limitation bolted on; it’s what makes the sharing safe. If a blob could be written, 10,000 entities referencing it across parallel jobs would be a data race by construction, and the safety system’s whole argument (Module 4) collapses. Read-only shared data has no write to overlap, so every reader parallelizes freely — the [ReadOnly] argument, applied to an asset instead of a container.

The chunk cost of all this: BlobAssetReference<T> is a handle, so the component holding it is a few bytes. Density preserved, data shared once, Burst-safe, serializable.

The decision, stated as a rule

You now have the criteria, so the rule is derivable rather than memorized:

Copy it into the component when the value is small and genuinely per-entity — position, health, speed. Chunk density is the point, and small unique values are what chunks are for.

Put it in a blob when the data is large, immutable, and shared. The threshold isn’t a magic byte count; it’s the moment per-entity copies start costing chunk capacity that no system’s access pattern justifies. Mesh collision data, navigation grids, authored curves, lookup tables.

Make it a prefab when the authored thing is a template for entities not yet created, rather than data an existing entity holds.

What this buys you

You can now explain why a prefab must be a real entity hidden from queries by a tag, why large shared data must leave the chunk, and why the thing it leaves into has to be immutable, unmanaged and offset-addressed rather than a pointer. That completes the baking picture: values copied, templates tagged, shared data blobbed, references remapped. Next, the lab — you will measure the chunk-density cost of a baking decision directly.

m05.l05