blob: 589865dccd0f36db62c8697c88429cd7e910438f [file]
#if defined(WITH_RIVE_SCRIPTING_WASM) && defined(__EMSCRIPTEN__)
#include "rive/wasm/browser_scripting_vm.hpp"
#include <emscripten/em_js.h>
using namespace rive;
// The page-side slot table (globalThis.__riveScriptInstances) is owned by
// the JS module runner, which instantiates each script module against the
// import object web/rive_module_imports.mjs builds.
// clang-format off
EM_JS(uint32_t, riveWebInstantiate, (const uint8_t* bytes, uint32_t size), {
// TODO(web-backend): WebAssembly.instantiate against the generated
// import object, mint a slot in globalThis.__riveScriptInstances.
return 0;
});
EM_JS(uint32_t, riveWebCallModule,
(uint32_t slot, const char* name, uint32_t argc, uint32_t* argv), {
const table = globalThis.__riveScriptInstances;
const instance = table && table.get(slot);
const fn = instance && instance.exports[UTF8ToString(name)];
if (!fn)
{
return 0;
}
const args = [];
for (let i = 0; i < argc; i++)
{
args.push(HEAPU32[(argv >> 2) + i]);
}
// TODO(web-backend): trap handling — catch, report, return 0.
return fn.apply(null, args) >>> 0;
});
EM_JS(void, riveWebReadMemory,
(uint32_t slot, uint32_t appAddr, uint8_t* dst, uint32_t size), {
const table = globalThis.__riveScriptInstances;
const instance = table && table.get(slot);
if (!instance)
{
return;
}
HEAPU8.set(
new Uint8Array(instance.exports.memory.buffer, appAddr, size), dst);
});
EM_JS(void, riveWebWriteMemory,
(uint32_t slot, uint32_t appAddr, const uint8_t* src, uint32_t size), {
const table = globalThis.__riveScriptInstances;
const instance = table && table.get(slot);
if (!instance)
{
return;
}
new Uint8Array(instance.exports.memory.buffer, appAddr, size)
.set(HEAPU8.subarray(src, src + size));
});
EM_JS(void, riveWebReleaseInstance, (uint32_t slot), {
const table = globalThis.__riveScriptInstances;
if (table)
{
table.delete(slot);
}
});
// clang-format on
std::unique_ptr<BrowserScriptingVM> BrowserScriptingVM::make(
Span<const uint8_t> module,
Factory* factory,
std::string& outError)
{
std::unique_ptr<BrowserScriptingVM> vm(new BrowserScriptingVM());
vm->m_factory = factory;
if (!vm->boot(module))
{
outError = vm->m_lastError;
return nullptr;
}
return vm;
}
bool BrowserScriptingVM::boot(Span<const uint8_t> module)
{
m_instanceSlot = riveWebInstantiate(module.data(), (uint32_t)module.size());
if (m_instanceSlot == 0)
{
m_lastError = "browser module instantiation failed";
return false;
}
// TODO(web-backend): mirror WasmScriptingVM::init's boot sequence
// (__wasm_call_ctors, host_newstate into m_L, the host_install_* set,
// host_register_embedded, host_seal_env) through callModule.
m_lastError = "browser backend boot not implemented";
return false;
}
BrowserScriptingVM::~BrowserScriptingVM()
{
if (m_instanceSlot != 0)
{
riveWebReleaseInstance(m_instanceSlot);
}
}
bool BrowserScriptingVM::valid() const
{
return m_instanceSlot != 0 && m_L != 0;
}
void* BrowserScriptingVM::resolveModulePtr(uint32_t appAddr, uint32_t size)
{
// TODO(web-backend): bounds-check against the instance's memory size
// and track dirty ranges so clean reads skip the write-back.
m_staged.emplace_back();
StagedRange& range = m_staged.back();
range.appAddr = appAddr;
range.bytes.resize(size);
riveWebReadMemory(m_instanceSlot, appAddr, range.bytes.data(), size);
return range.bytes.data();
}
uint32_t BrowserScriptingVM::callModule(const char* name,
uint32_t argc,
uint32_t* argv)
{
// Writes through staged pointers must land before the module runs.
for (StagedRange& range : m_staged)
{
riveWebWriteMemory(m_instanceSlot,
range.appAddr,
range.bytes.data(),
(uint32_t)range.bytes.size());
}
m_staged.clear();
return riveWebCallModule(m_instanceSlot, name, argc, argv);
}
void BrowserScriptingVM::raiseModuleError(const char* message)
{
// TODO(web-backend): abort the in-flight module call; for now the error
// is only recorded.
m_lastError = message;
}
#endif