资源加载
.riv 文件既可以内嵌资源字节(图片、字体、音频、脚本),也可以通过 CDN UUID 引用它们,并让运行时去获取这些资源。实现 FileAssetLoader 可以控制第二种路径——从磁盘、网络或你自己的资源管线中解析资源。
FileAssetLoader 接口
#include "rive/file_asset_loader.hpp"
#include "rive/assets/file_asset.hpp"
class FileAssetLoader : public RefCnt<FileAssetLoader> {
public:
virtual bool loadContents(FileAsset& asset,
Span<const uint8_t> inBandBytes,
Factory* factory) = 0;
};
loadContents 会在 File::import 期间为每个资源调用一次。你需要返回:
true—— 你已处理该资源。你要么已经同步填充了asset,要么启动了异步工作,并会在之后填充它。false—— 回退到内嵌字节(如果有)。
传入的 asset 已带有类型:将其转换为具体子类以填充内容。
| 资源类型 | 类 | 如何填充 |
|---|---|---|
| 图片 | ImageAsset | asset.renderImage(factory->decodeImage(bytes)) |
| 字体 | FontAsset | asset.font(factory->decodeFont(bytes)) |
| 音频 | AudioAsset | asset.audioSource(factory->decodeAudio(bytes)) |
同步示例:按文件名加载
#include "rive/file_asset_loader.hpp"
#include "rive/assets/image_asset.hpp"
#include "rive/assets/font_asset.hpp"
#include "rive/assets/audio_asset.hpp"
#include <filesystem>
#include <fstream>
#include <iterator>
#include <vector>
class DiskAssetLoader : public rive::FileAssetLoader {
public:
explicit DiskAssetLoader(std::filesystem::path root)
: m_root(std::move(root)) {}
bool loadContents(rive::FileAsset& asset,
rive::Span<const uint8_t> inBandBytes,
rive::Factory* factory) override
{
// Prefer in-band bytes when present.
if (inBandBytes.size() > 0) return false;
auto path = m_root / asset.uniqueFilename();
std::ifstream f(path, std::ios::binary);
if (!f) return false;
std::vector<uint8_t> bytes((std::istreambuf_iterator<char>(f)), {});
rive::Span<const uint8_t> span{bytes.data(), bytes.size()};
if (auto* img = dynamic_cast<rive::ImageAsset*>(&asset)) {
img->renderImage(factory->decodeImage(span));
return true;
}
if (auto* fnt = dynamic_cast<rive::FontAsset*>(&asset)) {
fnt->font(factory->decodeFont(span));
return true;
}
if (auto* aud = dynamic_cast<rive::AudioAsset*>(&asset)) {
aud->audioSource(factory->decodeAudio(span));
return true;
}
return false;
}
private:
std::filesystem::path m_root;
};
asset.uniqueFilename() 是编辑器分配的带扩展名文件名;如果你按 CDN UUID 建立索引,则使用 asset.cdnUuidStr()。
接入方式
rcp<FileAssetLoader> loader = make_rcp<DiskAssetLoader>("assets/");
ImportResult result;
rcp<File> file = File::import(bytes, factory, &result, loader);
该 loader 是引用计数对象——File 会在文件的整个生命周期内保持它存活,因此异步加载可以在 import 返回之后完成。
异步加载
对于异步场景(HTTP、解码线程等),从 loadContents 返回 true,但不要调用 renderImage / font / audioSource,然后稍后从任意线程填充该资源:
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;
}
对于每个后端,解码器(Factory::decodeImage、Factory::decodeFont、
Factory::decodeAudio)并不保证线程安全。如果你在非主线程解码,
请解码为 CPU 侧表示,并回到渲染线程完成 GPU 上传;或者在后端支持的情况下,
使用线程安全的 Factory。
内置 Loader
对于简单场景,运行时提供了一个可以扩展的相对路径 loader:
#include "rive/relative_local_asset_loader.hpp"
rcp<FileAssetLoader> loader =
make_rcp<RelativeLocalAssetLoader>("/path/to/assets");
它会从磁盘目录中按 uniqueFilename() 加载文件——这对于示例和工具非常方便。
当内嵌字节已经覆盖所有资源时
如果你的 .riv 文件嵌入了所有资源,则可以完全跳过 loader。对于这些资源,inBandBytes 将为非空,运行时会使用你提供的 Factory 对它们进行解码。