跳到主要内容

资源加载

.riv 文件既可以把资源字节(图片、字体、音频、脚本)in-band 嵌入文件,也可以通过 CDN UUID 引用资源,并交给 runtime 获取。实现 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 —— 回退到 in-band 字节(如果有)。

asset 会以具体类型传入:将它转换为具体子类后再填充。

资源类型Class填充方式
ImageImageAssetasset.renderImage(factory->decodeImage(bytes))
FontFontAssetasset.font(factory->decodeFont(bytes))
AudioAudioAssetasset.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
{
// 优先使用 in-band 字节。
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) {
// 假设该回调被分发到渲染线程;见下方关于 decoder 线程安全的警告。
rcp<RenderImage> ri =
factory->decodeImage({bytes.data(), bytes.size()});
keepAlive->renderImage(std::move(ri));
// 下一次 advanceAndApply 会使用新图片。
});
return true;
}
注意

并非每个 backend 都保证 decoder(Factory::decodeImageFactory::decodeFontFactory::decodeAudio)线程安全。如果要在线程外解码,请先解码为 CPU 侧表示,再回到渲染线程完成 GPU 上传;或者在 backend 支持时使用线程安全 Factory

内置 Loader

对于简单场景,runtime 提供了可扩展的相对路径 loader:

#include "rive/relative_local_asset_loader.hpp"

rcp<FileAssetLoader> loader =
make_rcp<RelativeLocalAssetLoader>("/path/to/assets");

它会按 uniqueFilename() 从磁盘目录加载文件,适合示例和工具链。

当 In-Band 字节已经覆盖全部资源时

如果 .riv 文件嵌入了所有资源,可以完全跳过 loader。对于这些资源,inBandBytes 会是非空,runtime 会使用你提供的 Factory 解码它们。