| #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 |