Blob assets
Blob assets are pieces of binary data that are optimized for streaming. Blob is short for Binary Large Object. By writing your data into a blob asset, you are storing it in a format that can be loaded efficiently and referenced from a component stored on an entity. Like struct components, blob assets must not contain any managed data: You cannot use regular arrays, strings, or any other managed object in a blob asset. Blob assets should only contain read-only data that does not change at runtime: they can be accessed from multiple threads at once and (unlike native containers) have no safety checks against concurrent writes.
In order to quickly load blob assets, it is necessary that their data is relocatable: The meaning of the data in the blob asset must not change when you copy the whole blob asset to another memory address. This implies that blob assets may not contain absolute references to itself, which precludes the use of internal pointers. Any information you would usually store via a pointer must instead be referenced via an offset relative to the memory address of the blob asset itself. This mostly applies to storing strings and arrays. The details of this indirection using offsets instead of absolute pointers impacts interaction with blob assets in two ways:
- Blob assets must be created using a BlobBuilder. This type takes care of computing the relative offsets for you.
- Blob assets must always be accessed and passed by reference using the
ref
keyword or using BlobAssetReference. This is necessary to ensure that any relative offsets within the blob asset still resolve to the right absolute address. The issue is again relocation: Blob assets can be relocated as a whole in memory, but accessing them by value instead of by reference does not in general guarantee that the whole blob asset is copied.
Note
You will get a compiler error should you try to use a blob asset containing internal pointers by value.
Creating blob assets
Creating a blob asset always involves at least four steps:
- Create a BlobBuilder. This needs to allocate some memory internally.
- Construct the root of the blob asset using BlobBuilder.ConstructRoot
- Fill the structure with your data.
- Create a BlobAssetReference using BlobBuilder.CreateBlobAssetReference. This copies the blob asset to the final location.
- Dispose the blob builder allocated in step 1.
For example, here we are storing a struct with only primitive members as a blob asset:
struct MarketData
{
public float PriceOranges;
public float PriceApples;
}
BlobAssetReference<MarketData> CreateMarketData()
{
// Create a new builder that will use temporary memory to construct the blob asset
var builder = new BlobBuilder(Allocator.Temp);
// Construct the root object for the blob asset. Notice the use of `ref`.
ref MarketData marketData = ref builder.ConstructRoot<MarketData>();
// Now fill the constructed root with the data:
// Apples compare to Oranges in the universally accepted ratio of 2 : 1 .
marketData.PriceApples = 2f;
marketData.PriceOranges = 4f;
// Now copy the data from the builder into its final place, which will
// use the persistent allocator
var result = builder.CreateBlobAssetReference<MarketData>(Allocator.Persistent);
// Make sure to dispose the builder itself so all internal memory is disposed.
builder.Dispose();
return result;
}
The role of the blob builder is to construct the data stored in the blob asset, make sure that all internal references are stored as offsets, and finally copy the finished blob asset into a single allocation referenced by the returned BlobAssetReference<T>
.
Using BlobArray
Arrays within blob assets need special treatment because they are implemented using relative offsets internally. This is implemented using the BlobArray type. Here is how to allocate an array of blob data and fill it:
struct Hobby
{
public float Excitement;
public int NumOrangesRequired;
}
struct HobbyPool
{
public BlobArray<Hobby> Hobbies;
}
BlobAssetReference<HobbyPool> CreateHobbyPool()
{
var builder = new BlobBuilder(Allocator.Temp);
ref HobbyPool hobbyPool = ref builder.ConstructRoot<HobbyPool>();
// Allocate enough room for two hobbies in the pool. Use the returned BlobBuilderArray
// to fill in the data.
const int numHobbies = 2;
BlobBuilderArray<Hobby> arrayBuilder = builder.Allocate(
ref hobbyPool.Hobbies,
numHobbies
);
// Initialize the hobbies.
// An exciting hobby that consumes a lot of oranges.
arrayBuilder[0] = new Hobby
{
Excitement = 1,
NumOrangesRequired = 7
};
// A less exciting hobby that conserves oranges.
arrayBuilder[1] = new Hobby
{
Excitement = 0.2f,
NumOrangesRequired = 2
};
var result = builder.CreateBlobAssetReference<HobbyPool>(Allocator.Persistent);
builder.Dispose();
return result;
}
Using BlobString
Strings have the same problems as arrays and have custom support using BlobString. They are similarly allocated using the BlobBuilder
API.
struct CharacterSetup
{
public float Loveliness;
public BlobString Name;
}
BlobAssetReference<CharacterSetup> CreateCharacterSetup(string name)
{
var builder = new BlobBuilder(Allocator.Temp);
ref CharacterSetup character = ref builder.ConstructRoot<CharacterSetup>();
character.Loveliness = 9001; // it's just a very lovely character
// Create a new BlobString and set it to the given name.
builder.AllocateString(ref character.Name, name);
var result = builder.CreateBlobAssetReference<CharacterSetup>(Allocator.Persistent);
builder.Dispose();
return result;
}
Using BlobPtr
Should you need to manually set an internal pointer, you can use the BlobPtr
struct FriendList
{
public BlobPtr<BlobString> BestFriend;
public BlobArray<BlobString> Friends;
}
BlobAssetReference<FriendList> CreateFriendList()
{
var builder = new BlobBuilder(Allocator.Temp);
ref FriendList friendList = ref builder.ConstructRoot<FriendList>();
const int numFriends = 3;
var arrayBuilder = builder.Allocate(ref friendList.Friends, numFriends);
builder.AllocateString(ref arrayBuilder[0], "Alice");
builder.AllocateString(ref arrayBuilder[1], "Bob");
builder.AllocateString(ref arrayBuilder[2], "Joachim");
// Set the best friend pointer to point to the second array element.
builder.SetPointer(ref friendList.BestFriend, ref arrayBuilder[2]);
var result = builder.CreateBlobAssetReference<FriendList>(Allocator.Persistent);
builder.Dispose();
return result;
}
Accessing blob assets on a component
Once you have obtained a BlobAssetReference<T>
to a blob asset, you can store this reference on component and access it. Note that all parts of a blob asset that contain internal pointers must be accessed by reference.
struct Hobbies : IComponentData
{
public BlobAssetReference<HobbyPool> Blob;
}
float GetExcitingHobby(ref Hobbies component, int numOranges)
{
// Get a reference to the pool of available hobbies. Note that it needs to be passed by
// reference, because otherwise the internal reference in the BlobArray would be invalid.
ref HobbyPool pool = ref component.Blob.Value;
// Find the most exciting hobby we can participate in with our current number of oranges.
float mostExcitingHobby = 0;
for (int i = 0; i < pool.Hobbies.Length; i++)
{
// This is safe to use without a reference, because the Hobby struct does not
// contain internal references.
var hobby = pool.Hobbies[i];
if (hobby.NumOrangesRequired > numOranges)
continue;
if (hobby.Excitement >= mostExcitingHobby)
mostExcitingHobby = hobby.Excitement;
}
return mostExcitingHobby;
}
When do I need to dispose a blob asset reference?
All blob asset that were allocated at runtime using BlobBuilder.CreateBlobAssetReference need to be manually disposed. This is different for blob assets that were loaded as part of an entity scene loaded from disk: All of these blob assets are reference counted and automatically released once no component references them anymore. They must not be manually disposed.
Debugging blob asset contents
Blob assets implement internal references using relative offsets. This means that copying a BlobString
struct (or any other type with these internal references) will copy the relative offset contained, but not what it is pointing to. The result of this is an unusable BlobString
that will represents an essentially random string of characters. While this is easy to avoid in your own code, debugging utilities will often do exactly that. Therefore the contents of a BlobString
cannot be shown correctly in a debugger.
However, there is support for displaying the values of a BlobAssetReference<T>
and all of its contents. If you want to look up the contents of a BlobString
, navigate to the containing BlobAssetReference<T>
and start debugging from there.