跳到主要内容

资源加载

.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 已带有类型:将其转换为具体子类以填充内容。

资源类型如何填充
图片ImageAssetasset.renderImage(factory->decodeImage(bytes))
字体FontAssetasset.font(factory->decodeFont(bytes))
音频AudioAssetasset.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::decodeImageFactory::decodeFontFactory::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 对它们进行解码。