blob: b1d17bbf910d22471709943d2cbaeb9a68e9c23a [file] [edit]
#if defined(WITH_RIVE_SCRIPTING) && defined(WITH_RIVE_SCRIPTNET)
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/artboard.hpp"
#include "rive/bindable_artboard.hpp"
#include "rive/file.hpp"
#include "rive/scripted/decoded_file.hpp"
#include "rive/viewmodel/viewmodel_instance.hpp"
#include "lua.h"
#include "lualib.h"
using namespace rive;
ScriptedRiveFile::ScriptedRiveFile() = default;
ScriptedRiveFile::~ScriptedRiveFile() = default;
ScriptedBindableArtboard::ScriptedBindableArtboard() = default;
ScriptedBindableArtboard::~ScriptedBindableArtboard() = default;
int ScriptedBindableArtboard::pushData(lua_State* L)
{
if (viewModel == nullptr)
{
lua_pushnil(L);
return 1;
}
if (m_dataCache.push(L, viewModel))
{
return 1;
}
lua_newrive<ScriptedViewModel>(
L,
L,
ref_rcp(viewModel->viewModel()),
viewModel);
m_dataCache.store(L, viewModel);
return 1;
}
namespace
{
// ── RiveFile ───────────────────────────────────────────────────────────────
int riveFileIndex(lua_State* L)
{
auto* self = lua_torive<ScriptedRiveFile>(L, 1);
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (key == nullptr)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
switch (atom)
{
case (int)LuaAtoms::artboardNames:
{
size_t count = self->file->artboardCount();
lua_createtable(L, (int)count, 0);
for (size_t i = 0; i < count; i++)
{
const std::string& name = self->file->artboard(i)->name();
lua_pushlstring(L, name.data(), name.size());
lua_rawseti(L, -2, (int)i + 1);
}
return 1;
}
default:
break;
}
luaL_error(
L,
"'%s' is not a valid index of %s",
key,
ScriptedRiveFile::luaName);
return 0;
}
int riveFileNamecall(lua_State* L)
{
int atom;
const char* name = lua_namecallatom(L, &atom);
auto* self = lua_torive<ScriptedRiveFile>(L, 1);
if (name != nullptr && atom == (int)LuaAtoms::bindableArtboard)
{
const char* artboardName = lua_isnoneornil(L, 2) ? nullptr
: luaL_checkstring(L, 2);
auto* bindable = lua_newrive<ScriptedBindableArtboard>(L);
DecodedBindable decoded = decodedBindable(*self->file, artboardName);
if (decoded.artboard == nullptr)
{
lua_pop(L, 1);
lua_pushnil(L);
return 1;
}
bindable->artboard = std::move(decoded.artboard);
bindable->viewModel = std::move(decoded.viewModel);
return 1;
}
luaL_error(
L,
"%s is not a valid method of %s",
name != nullptr ? name : "?",
ScriptedRiveFile::luaName);
return 0;
}
// ── BindableArtboard ───────────────────────────────────────────────────────
int bindableArtboardIndex(lua_State* L)
{
auto* self = lua_torive<ScriptedBindableArtboard>(L, 1);
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (key == nullptr)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
switch (atom)
{
case (int)LuaAtoms::name:
{
const std::string& name = self->artboard->artboard()->name();
lua_pushlstring(L, name.data(), name.size());
return 1;
}
case (int)LuaAtoms::data:
return self->pushData(L);
default:
break;
}
luaL_error(
L,
"'%s' is not a valid index of %s",
key,
ScriptedBindableArtboard::luaName);
return 0;
}
// Wrappers are minted per read, so equality is by the artboard they carry.
int bindableArtboardEq(lua_State* L)
{
auto* lhs = lua_torive<ScriptedBindableArtboard>(L, 1);
auto* rhs = lua_torive<ScriptedBindableArtboard>(L, 2);
lua_pushboolean(L, lhs->artboard == rhs->artboard);
return 1;
}
} // namespace
// ── context:decodeFile(data) ───────────────────────────────────────────────
// Decodes on the calling thread: import calls into the Factory and runs the
// file's module bodies, neither of which is safe on a worker. The returned
// Promise is already settled, which keeps the signature open to an
// off-thread decode later.
int context_decodeFile_impl(lua_State* L)
{
// Luau errors longjmp past C++ destructors, so every check that can raise
// runs before anything is decoded.
size_t length = 0;
const void* data = nullptr;
if (lua_isbuffer(L, 2))
{
data = lua_tobuffer(L, 2, &length);
}
else
{
luaL_typeerror(L, 2, "buffer");
return 0;
}
if (data == nullptr || length == 0)
{
luaL_error(L, "decodeFile: empty buffer");
return 0;
}
ScriptingContext* context = ScriptingContext::from(L);
Factory* factory = context != nullptr ? context->factory() : nullptr;
if (factory == nullptr)
{
luaL_error(
L,
"decodeFile: this Lua state has no factory to decode "
"with");
return 0;
}
auto* promise = lua_newrive<ScriptedPromise>(L, lua_mainthread(L));
int promiseIdx = lua_gettop(L);
// Allocated before the import so no allocation can fail while the decoded
// file is held only by a C++ local.
auto* riveFile = lua_newrive<ScriptedRiveFile>(L);
int fileIdx = lua_gettop(L);
ImportResult result = ImportResult::malformed;
riveFile->file = importDecodedFile(
Span<const uint8_t>(static_cast<const uint8_t*>(data), length),
factory,
&result);
if (riveFile->file != nullptr)
{
promise->resolve(L, fileIdx);
}
else
{
lua_pushstring(
L,
result == ImportResult::unsupportedVersion
? "unsupportedVersion: not a Rive file this "
"runtime can read"
: "malformed: not a Rive file");
promise->reject(L, lua_gettop(L));
lua_pop(L, 1);
}
lua_pushvalue(L, promiseIdx);
return 1;
}
int luaopen_rive_file(lua_State* L)
{
{
lua_register_rive<ScriptedRiveFile>(L);
lua_pushcfunction(L, riveFileIndex, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, riveFileNamecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedBindableArtboard>(L);
lua_pushcfunction(L, bindableArtboardIndex, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, bindableArtboardEq, nullptr);
lua_setfield(L, -2, "__eq");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
return 0;
}
#endif