blob: 82c2bcc8ffc18467524da9cc7050040a97458332 [file] [log] [blame] [edit]
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/rive_lua_libs.hpp"
#include "rive/viewmodel/viewmodel_property_color.hpp"
#include "rive/viewmodel/viewmodel_property_number.hpp"
#include "rive/viewmodel/viewmodel_property_string.hpp"
#include "rive/viewmodel/viewmodel_property_trigger.hpp"
#include "rive/viewmodel/viewmodel_property_list.hpp"
#include <math.h>
#include <stdio.h>
using namespace rive;
static void pushViewModelInstanceValue(lua_State* L,
ViewModelInstanceValue* propValue)
{
switch (propValue->coreType())
{
case ViewModelInstanceNumberBase::typeKey:
lua_newrive<ScriptedPropertyNumber>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceNumber>()));
break;
case ViewModelInstanceTriggerBase::typeKey:
lua_newrive<ScriptedPropertyTrigger>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceTrigger>()));
break;
case ViewModelInstanceListBase::typeKey:
lua_newrive<ScriptedPropertyList>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceList>()));
break;
case ViewModelInstanceColorBase::typeKey:
lua_newrive<ScriptedPropertyColor>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceColor>()));
break;
case ViewModelInstanceStringBase::typeKey:
lua_newrive<ScriptedPropertyString>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceString>()));
break;
default:
lua_pushnil(L);
break;
}
}
// int ScriptedPropertyViewModel::pushValueOfType(const char* name, int
// coreType)
// {
// auto vmi = m_instanceValue->referenceViewModelInstance();
// if (!vmi)
// {
// lua_pushnil(m_state);
// }
// else
// {
// auto propValue = vmi->propertyValue(name);
// if (propValue->coreType() != coreType)
// {
// lua_pushnil(m_state);
// }
// else
// {
// pushViewModelInstanceValue(m_state, propValue);
// }
// }
// return 1;
// }
ScriptedProperty::~ScriptedProperty()
{
m_instanceValue->removeDelegate(this);
clearListeners();
}
ScriptedPropertyViewModel::~ScriptedPropertyViewModel()
{
lua_unref(m_state, m_valueRef);
}
void ScriptedProperty::clearListeners()
{
for (auto itr = m_listeners.begin(); itr != m_listeners.end(); ++itr)
{
const ScriptedListener& listener = *itr;
lua_unref(m_state, listener.function);
if (listener.userdata)
{
lua_unref(m_state, listener.userdata);
}
}
m_listeners.clear();
}
ScriptedProperty::ScriptedProperty(lua_State* L,
rcp<ViewModelInstanceValue> value) :
m_state(L), m_instanceValue(std::move(value))
{
m_instanceValue->addDelegate(this);
}
void ScriptedProperty::valueChanged()
{
// This works because we don't actually call as we go (or we could
// invalidate listeners if a callback registers a new or removes a
// listener). Instead, we build up the call stack and then call for each
// callback on the stack.
for (auto itr = m_listeners.rbegin(); itr != m_listeners.rend(); itr++)
{
const ScriptedListener& listener = *itr;
lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.function);
if (listener.userdata)
{
lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.userdata);
}
else
{
// push nil so we have a constant number of args per call
lua_pushnil(m_state);
}
}
size_t calls = m_listeners.size();
for (size_t i = 0; i < calls; i++)
{
lua_pcall(m_state, 1, 0, 0);
}
}
int ScriptedProperty::addListener()
{
if (lua_isfunction(m_state, 2))
{
int callbackRef = lua_ref(m_state, 2);
m_listeners.push_back({callbackRef});
return 0;
}
else if (lua_isfunction(m_state, 3))
{
int userdataRef = lua_ref(m_state, 2);
int callbackRef = lua_ref(m_state, 3);
m_listeners.push_back({callbackRef, userdataRef});
return 0;
}
luaL_typeerrorL(m_state, 2, lua_typename(m_state, LUA_TFUNCTION));
return 0;
}
int ScriptedProperty::removeListener()
{
int checkIndex;
if (lua_isfunction(m_state, 2))
{
checkIndex = 2;
}
else if (lua_isfunction(m_state, 3))
{
checkIndex = 3;
}
else
{
luaL_typeerrorL(m_state, 2, lua_typename(m_state, LUA_TFUNCTION));
}
for (auto itr = m_listeners.begin(); itr != m_listeners.end();)
{
const ScriptedListener& listener = *itr;
lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.function);
if (lua_rawequal(m_state, -1, checkIndex))
{
lua_unref(m_state, listener.function);
if (listener.userdata)
{
lua_unref(m_state, listener.userdata);
}
itr = m_listeners.erase(itr);
}
else
{
++itr;
}
lua_pop(m_state, 1);
}
return 0;
}
ScriptedPropertyViewModel::ScriptedPropertyViewModel(
lua_State* L,
rcp<ViewModel> viewModel,
rcp<ViewModelInstanceViewModel> value) :
ScriptedProperty(L, std::move(value)), m_viewModel(std::move(viewModel))
{}
ScriptedViewModel::ScriptedViewModel(lua_State* L,
rcp<ViewModel> viewModel,
rcp<ViewModelInstance> viewModelInstance) :
m_state(L), m_viewModel(viewModel), m_viewModelInstance(viewModelInstance)
{}
ScriptedViewModel::~ScriptedViewModel()
{
for (auto itr : m_propertyRefs)
{
lua_unref(m_state, itr.second);
}
}
int ScriptedViewModel::pushValue(const char* name, int coreType)
{
auto itr = m_propertyRefs.find(name);
if (itr != m_propertyRefs.end())
{
lua_rawgeti(m_state, LUA_REGISTRYINDEX, itr->second);
return 1;
}
// To be fully typesafe at runtime we should check the property in the
// viewmodel and make sure the one in the value matches the same type or
// return a default based on the type of the viewmodel, but this incurs
// extra performance and since our type system at edit-time lets the user be
// intentional, for now we runtime error if the type is un-expected and we
// optimize for the best case.
auto vmi = m_viewModelInstance;
if (!vmi)
{
// Get type from viewmodel if we have one and user didn't request a type
// dynamically.
if (coreType != 0 || !m_viewModel)
{
lua_pushnil(m_state);
}
else
{
auto prop = m_viewModel->property(name);
switch (prop->coreType())
{
case ViewModelPropertyNumberBase::typeKey:
lua_newrive<ScriptedPropertyNumber>(m_state,
m_state,
nullptr);
break;
case ViewModelPropertyTriggerBase::typeKey:
lua_newrive<ScriptedPropertyTrigger>(m_state,
m_state,
nullptr);
break;
case ViewModelPropertyListBase::typeKey:
lua_newrive<ScriptedPropertyList>(m_state,
m_state,
nullptr);
break;
case ViewModelPropertyColorBase::typeKey:
lua_newrive<ScriptedPropertyColor>(m_state,
m_state,
nullptr);
case ViewModelPropertyStringBase::typeKey:
lua_newrive<ScriptedPropertyString>(m_state,
m_state,
nullptr);
}
}
}
else
{
auto propValue = vmi->propertyValue(name);
if (propValue == nullptr ||
(coreType != 0 && propValue->coreType() != coreType))
{
lua_pushnil(m_state);
}
else
{
pushViewModelInstanceValue(m_state, propValue);
}
}
m_propertyRefs[name] = lua_ref(m_state, -1);
return 1;
}
int ScriptedPropertyViewModel::pushValue()
{
// N.B. we'll need to invalidate m_valueRef if the viewmodel's value on the
// property changes.
if (m_valueRef != 0)
{
lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_valueRef);
return 1;
}
if (m_instanceValue)
{
lua_newrive<ScriptedViewModel>(
m_state,
m_state,
m_viewModel,
m_instanceValue->as<ViewModelInstanceViewModel>()
->referenceViewModelInstance());
}
else
{
// Push nil or push an empty model ref? For now empty/with def values.
lua_newrive<ScriptedViewModel>(m_state, m_state, m_viewModel, nullptr);
}
m_valueRef = lua_ref(m_state, -1);
return 1;
}
static int property_vm_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto vmProp = lua_torive<ScriptedPropertyViewModel>(L, 1);
assert(vmProp->state() == L);
switch (atom)
{
case (int)LuaAtoms::value:
return vmProp->pushValue();
default:
return 0;
}
}
static int vm_index(lua_State* L)
{
auto vm = lua_torive<ScriptedViewModel>(L, 1);
size_t namelen = 0;
const char* name = luaL_checklstring(L, 2, &namelen);
assert(vm->state() == L);
return vm->pushValue(name);
}
static int property_namecall_atom(lua_State* L,
ScriptedProperty* property,
int atom,
bool& error)
{
switch (atom)
{
case (int)LuaAtoms::addListener:
{
assert(property->state() == L);
return property->addListener();
}
case (int)LuaAtoms::removeListener:
{
assert(property->state() == L);
return property->removeListener();
}
case (int)LuaAtoms::fire:
{
auto value = property->instanceValue();
if (value != nullptr && value->is<ViewModelInstanceTrigger>())
{
value->as<ViewModelInstanceTrigger>()->trigger();
return 0;
}
}
}
error = true;
return 0;
}
static int vm_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
if (str != nullptr)
{
auto vm = lua_torive<ScriptedViewModel>(L, 1);
switch (atom)
{
case (int)LuaAtoms::getNumber:
{
size_t namelen = 0;
const char* name = luaL_checklstring(L, 2, &namelen);
assert(vm->state() == L);
return vm->pushValue(name,
ViewModelInstanceNumberBase::typeKey);
}
case (int)LuaAtoms::getTrigger:
{
size_t namelen = 0;
const char* name = luaL_checklstring(L, 2, &namelen);
assert(vm->state() == L);
return vm->pushValue(name,
ViewModelInstanceTriggerBase::typeKey);
}
default:
break;
}
}
luaL_error(L,
"%s is not a valid method of %s",
str,
ScriptedPropertyViewModel::luaName);
return 0;
}
static int property_vm_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
if (str != nullptr)
{
auto vm = lua_torive<ScriptedPropertyViewModel>(L, 1);
bool error = false;
int stackChange = property_namecall_atom(L, vm, atom, error);
if (!error)
{
return stackChange;
}
}
luaL_error(L,
"%s is not a valid method of %s",
str,
ScriptedPropertyViewModel::luaName);
return 0;
}
static int property_namecall(lua_State* L)
{
int atom;
const char* str = lua_namecallatom(L, &atom);
const char* name = "Property";
if (str != nullptr)
{
auto tag = lua_userdatatag(L, 1);
switch (tag)
{
case ScriptedPropertyNumber::luaTag:
name = ScriptedPropertyNumber::luaName;
break;
case ScriptedPropertyTrigger::luaTag:
name = ScriptedPropertyTrigger::luaName;
break;
case ScriptedPropertyColor::luaTag:
name = ScriptedPropertyColor::luaName;
break;
case ScriptedPropertyString::luaTag:
name = ScriptedPropertyString::luaName;
break;
default:
luaL_typeerror(L, 1, name);
break;
}
auto vm = (ScriptedProperty*)lua_touserdata(L, 1);
bool error = false;
int stackChange = property_namecall_atom(L, vm, atom, error);
if (!error)
{
return stackChange;
}
}
luaL_error(L, "%s is not a valid method of %s", str, name);
return 0;
}
ScriptedPropertyNumber::ScriptedPropertyNumber(
lua_State* L,
rcp<ViewModelInstanceNumber> value) :
ScriptedProperty(L, std::move(value))
{}
ScriptedPropertyTrigger::ScriptedPropertyTrigger(
lua_State* L,
rcp<ViewModelInstanceTrigger> value) :
ScriptedProperty(L, std::move(value))
{}
void ScriptedPropertyNumber::setValue(float value)
{
if (m_instanceValue)
{
m_instanceValue->as<ViewModelInstanceNumber>()->propertyValue(value);
}
}
int ScriptedPropertyNumber::pushValue()
{
if (m_instanceValue)
{
lua_pushnumber(
m_state,
m_instanceValue->as<ViewModelInstanceNumber>()->propertyValue());
}
else
{
lua_pushnumber(m_state, 0);
}
return 1;
}
ScriptedPropertyColor::ScriptedPropertyColor(
lua_State* L,
rcp<ViewModelInstanceColor> value) :
ScriptedProperty(L, std::move(value))
{}
void ScriptedPropertyColor::setValue(unsigned value)
{
if (m_instanceValue)
{
m_instanceValue->as<ViewModelInstanceColor>()->propertyValue(
(int)value);
}
}
int ScriptedPropertyColor::pushValue()
{
if (m_instanceValue)
{
lua_pushunsigned(m_state,
(unsigned)m_instanceValue->as<ViewModelInstanceColor>()
->propertyValue());
}
else
{
lua_pushunsigned(m_state, 0);
}
return 1;
}
ScriptedPropertyString::ScriptedPropertyString(
lua_State* L,
rcp<ViewModelInstanceString> value) :
ScriptedProperty(L, std::move(value))
{}
void ScriptedPropertyString::setValue(const std::string& value)
{
if (m_instanceValue)
{
m_instanceValue->as<ViewModelInstanceString>()->propertyValue(value);
}
}
int ScriptedPropertyString::pushValue()
{
if (m_instanceValue)
{
lua_pushstring(m_state,
m_instanceValue->as<ViewModelInstanceString>()
->propertyValue()
.c_str());
}
else
{
lua_pushstring(m_state, "");
}
return 1;
}
ScriptedPropertyList::ScriptedPropertyList(lua_State* L,
rcp<ViewModelInstanceList> value) :
ScriptedProperty(L, std::move(value))
{}
ScriptedPropertyList::~ScriptedPropertyList()
{
for (const auto& pair : m_propertyRefs)
{
lua_unref(m_state, pair.second);
}
}
void ScriptedPropertyList::valueChanged()
{
m_changed = true;
ScriptedProperty::valueChanged();
}
int ScriptedPropertyList::pushLength()
{
if (m_instanceValue)
{
lua_pushinteger(m_state,
(int)m_instanceValue->as<ViewModelInstanceList>()
->listItems()
.size());
}
else
{
lua_pushinteger(m_state, 0);
}
return 1;
}
int ScriptedPropertyList::pushValue(int index)
{
if (m_instanceValue)
{
auto items = m_instanceValue->as<ViewModelInstanceList>()->listItems();
if (m_changed)
{
std::unordered_map<ViewModelInstance*, int> refs;
// re-validate references.
for (auto& item : items)
{
auto vmi = item->viewModelInstance().get();
auto itr = m_propertyRefs.find(vmi);
if (itr != m_propertyRefs.end())
{
refs[vmi] = itr->second;
m_propertyRefs.erase(itr);
}
}
for (const auto& pair : m_propertyRefs)
{
lua_unref(m_state, pair.second);
}
m_propertyRefs = refs;
m_changed = false;
}
if (index >= items.size())
{
lua_pushnil(m_state);
}
else
{
auto listItem = items[index];
auto vmi = listItem->viewModelInstance();
auto itr = m_propertyRefs.find(vmi.get());
if (itr != m_propertyRefs.end())
{
lua_rawgeti(m_state, LUA_REGISTRYINDEX, itr->second);
}
else
{
lua_newrive<ScriptedViewModel>(m_state,
m_state,
ref_rcp(vmi->viewModel()),
vmi);
m_propertyRefs[vmi.get()] = lua_ref(m_state, -1);
}
}
}
else
{
lua_pushnil(m_state);
}
return 1;
}
static int property_list_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
auto propertyList = lua_torive<ScriptedPropertyList>(L, 1);
// if it's not an atom it should be an index into the array
if (!key)
{
int index = luaL_checkinteger(L, 2);
return propertyList->pushValue(index - 1);
}
switch (atom)
{
case (int)LuaAtoms::length:
assert(propertyList->state() == L);
return propertyList->pushLength();
default:
return 0;
}
}
static int property_number_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyNumber = lua_torive<ScriptedPropertyNumber>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
assert(propertyNumber->state() == L);
return propertyNumber->pushValue();
default:
return 0;
}
}
static int property_color_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyColor = lua_torive<ScriptedPropertyColor>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
assert(propertyColor->state() == L);
return propertyColor->pushValue();
default:
return 0;
}
}
static int property_number_newindex(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyNumber = lua_torive<ScriptedPropertyNumber>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
propertyNumber->setValue(float(luaL_checknumber(L, 3)));
default:
return 0;
}
return 0;
}
static int property_color_newindex(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyColor = lua_torive<ScriptedPropertyColor>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
propertyColor->setValue(luaL_checkunsigned(L, 3));
default:
return 0;
}
return 0;
}
static int property_string_index(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyString = lua_torive<ScriptedPropertyString>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
assert(propertyString->state() == L);
return propertyString->pushValue();
default:
return 0;
}
}
static int property_string_newindex(lua_State* L)
{
int atom;
const char* key = lua_tostringatom(L, 2, &atom);
if (!key)
{
luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING));
return 0;
}
auto propertyString = lua_torive<ScriptedPropertyString>(L, 1);
switch (atom)
{
case (int)LuaAtoms::value:
propertyString->setValue(luaL_checkstring(L, 3));
default:
return 0;
}
return 0;
}
int luaopen_rive_properties(lua_State* L)
{
{
lua_register_rive<ScriptedViewModel>(L);
lua_pushcfunction(L, vm_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, vm_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyViewModel>(L);
lua_pushcfunction(L, property_vm_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, property_vm_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyNumber>(L);
lua_pushcfunction(L, property_number_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, property_number_newindex, nullptr);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, property_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyColor>(L);
lua_pushcfunction(L, property_color_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, property_color_newindex, nullptr);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, property_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyString>(L);
lua_pushcfunction(L, property_string_index, nullptr);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, property_string_newindex, nullptr);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, property_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyTrigger>(L);
lua_pushcfunction(L, property_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
{
lua_register_rive<ScriptedPropertyList>(L);
lua_pushcfunction(L, property_namecall, nullptr);
lua_setfield(L, -2, "__namecall");
lua_pushcfunction(L, property_list_index, nullptr);
lua_setfield(L, -2, "__index");
lua_setreadonly(L, -1, true);
lua_pop(L, 1); // pop the metatable
}
return 7;
}
#endif