A .riv file can either embed asset bytes (images, fonts, audio, scripts) in-band
or reference them by CDN UUID and let the runtime fetch them. Implement
FileAssetLoader to control the second path — resolving from disk, the
network, or your own asset pipeline.
For async (HTTP, decoder thread, etc), return true from loadContentswithout calling renderImage / font / audioSource, then populate the
asset later from any thread:
bool loadContents(FileAsset& asset, Span<const uint8_t>, Factory* factory) override { auto* image = dynamic_cast<ImageAsset*>(&asset); if (!image) return false; rcp<ImageAsset> keepAlive = ref_rcp(image); fetchAsync(image->cdnUuidStr(), [keepAlive, factory](std::vector<uint8_t> bytes) { // Assumes this callback is dispatched to the render thread — see the // warning below about decoder thread-safety. rcp<RenderImage> ri = factory->decodeImage({bytes.data(), bytes.size()}); keepAlive->renderImage(std::move(ri)); // The next advanceAndApply will pick up the new image. }); return true;}
Decoders (Factory::decodeImage, Factory::decodeFont,
Factory::decodeAudio) are not guaranteed thread-safe for every
backend. If you decode off-thread, decode into a CPU-side representation
and finalize the GPU upload back on the render thread, or use a
thread-safe Factory if your backend supports it.
If your .riv file embeds all of its assets, you can skip the loader
entirely. inBandBytes will be non-empty for those assets and the runtime
will decode them with the Factory you provided.