/*
* This source file is part of RmlUi, the HTML/CSS Interface Middleware
*
* For the latest information, see http://github.com/mikke89/RmlUi
*
* Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
* Copyright (c) 2019-2023 The RmlUi Team, and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "LuaDataModel.h"
#include
#include
#include
#include
#define RMLDATAMODEL "RMLDATAMODEL"
namespace Rml {
namespace Lua {
namespace luabind {
#if LUA_VERSION_NUM < 503
static void lua_reverse(lua_State* L, int a, int b)
{
for (; a < b; ++a, --b)
{
lua_pushvalue(L, a);
lua_pushvalue(L, b);
lua_replace(L, a);
lua_replace(L, b);
}
}
void lua_rotate(lua_State* L, int idx, int n)
{
int n_elems = 0;
idx = lua_absindex(L, idx);
n_elems = lua_gettop(L) - idx + 1;
if (n < 0)
{
n += n_elems;
}
if (n > 0 && n < n_elems)
{
luaL_checkstack(L, 2, "not enough stack slots available");
n = n_elems - n;
lua_reverse(L, idx, idx + n - 1);
lua_reverse(L, idx + n, idx + n_elems - 1);
lua_reverse(L, idx, idx + n_elems - 1);
}
}
#endif
using call_t = Rml::Function;
inline int errhandler(lua_State* L)
{
const char* msg = lua_tostring(L, 1);
if (msg == NULL)
{
if (luaL_callmeta(L, 1, "__tostring") && lua_type(L, -1) == LUA_TSTRING)
return 1;
else
msg = lua_pushfstring(L, "(error object is a %s value)", luaL_typename(L, 1));
}
luaL_traceback(L, L, msg, 1);
return 1;
}
inline void errfunc(const char* msg)
{
Log::Message(Log::LT_WARNING, "%s", msg);
}
inline int function_call(lua_State* L)
{
call_t& f = *(call_t*)lua_touserdata(L, 1);
f();
return 0;
}
inline bool invoke(lua_State* L, call_t f, int argn = 0)
{
if (!lua_checkstack(L, 3))
{
errfunc("stack overflow");
lua_pop(L, argn);
return false;
}
lua_pushcfunction(L, errhandler);
lua_pushcfunction(L, function_call);
lua_pushlightuserdata(L, &f);
lua_rotate(L, -argn - 3, 3);
if (lua_pcall(L, 1 + argn, 0, lua_gettop(L) - argn - 2) != LUA_OK)
{
errfunc(lua_tostring(L, -1));
lua_pop(L, 2);
return false;
}
lua_pop(L, 1);
return true;
}
} // namespace luabind
class LuaScalarDef;
class LuaTableDef;
struct LuaDataModel {
DataModelConstructor constructor;
DataModelHandle handle;
lua_State* dataL;
LuaScalarDef* scalarDef;
LuaTableDef* tableDef;
int top;
};
class LuaTableDef : public VariableDefinition {
public:
LuaTableDef(const struct LuaDataModel* model);
bool Get(void* ptr, Variant& variant) override;
bool Set(void* ptr, const Variant& variant) override;
int Size(void* ptr) override;
DataVariable Child(void* ptr, const DataAddressEntry& address) override;
protected:
const struct LuaDataModel* model;
};
class LuaScalarDef final : public LuaTableDef {
public:
LuaScalarDef(const struct LuaDataModel* model);
DataVariable Child(void* ptr, const DataAddressEntry& address) override;
};
LuaTableDef::LuaTableDef(const struct LuaDataModel* model) : VariableDefinition(DataVariableType::Scalar), model(model) {}
bool LuaTableDef::Get(void* ptr, Variant& variant)
{
lua_State* L = model->dataL;
if (!L)
return false;
int id = (int)(intptr_t)ptr;
GetVariant(L, id, &variant);
return true;
}
bool LuaTableDef::Set(void* ptr, const Variant& variant)
{
int id = (int)(intptr_t)ptr;
lua_State* L = model->dataL;
if (!L)
return false;
PushVariant(L, &variant);
lua_replace(L, id);
return true;
}
static int lLuaTableDefSize(lua_State* L)
{
lua_pushinteger(L, luaL_len(L, 1));
return 1;
}
static int lLuaTableDefChild(lua_State* L)
{
lua_gettable(L, 1);
return 1;
}
int LuaTableDef::Size(void* ptr)
{
lua_State* L = model->dataL;
if (!L)
return 0;
int id = (int)(intptr_t)ptr;
if (lua_type(L, id) != LUA_TTABLE)
{
return 0;
}
if (!lua_checkstack(L, 4))
{
return 0;
}
lua_pushcfunction(L, lLuaTableDefSize);
lua_pushvalue(L, id);
if (LUA_OK != lua_pcall(L, 1, 1, 0))
{
lua_pop(L, 1);
return 0;
}
int size = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
return size;
}
DataVariable LuaTableDef::Child(void* ptr, const DataAddressEntry& address)
{
lua_State* L = model->dataL;
if (!L)
return DataVariable{};
int id = (int)(intptr_t)ptr;
if (lua_type(L, id) != LUA_TTABLE)
{
return DataVariable{};
}
if (!lua_checkstack(L, 4))
{
return DataVariable{};
}
lua_pushcfunction(L, lLuaTableDefChild);
lua_pushvalue(L, id);
if (address.index == -1)
{
lua_pushlstring(L, address.name.data(), address.name.size());
}
else
{
lua_pushinteger(L, (lua_Integer)address.index + 1);
}
if (LUA_OK != lua_pcall(L, 2, 1, 0))
{
lua_pop(L, 1);
return DataVariable{};
}
return DataVariable(model->tableDef, (void*)(intptr_t)lua_gettop(L));
}
LuaScalarDef::LuaScalarDef(const struct LuaDataModel* model) : LuaTableDef(model) {}
DataVariable LuaScalarDef::Child(void* ptr, const DataAddressEntry& address)
{
lua_State* L = model->dataL;
if (!L)
return DataVariable{};
lua_settop(L, model->top);
return LuaTableDef::Child(ptr, address);
}
static void BindVariable(struct LuaDataModel* D, lua_State* L)
{
lua_State* dataL = D->dataL;
if (!lua_checkstack(dataL, 4))
{
luaL_error(L, "Memory Error");
}
int id = lua_gettop(dataL) + 1;
D->top = id;
// L top : key value
lua_xmove(L, dataL, 1); // move value to dataL with index(id)
lua_pushvalue(L, -1); // dup key
lua_xmove(L, dataL, 1);
lua_pushinteger(dataL, id);
lua_rawset(dataL, 1);
const char* key = lua_tostring(L, -1);
if (lua_type(dataL, D->top) == LUA_TFUNCTION)
{
D->constructor.BindEventCallback(key, [=](DataModelHandle, Event& event, const VariantList& varlist) {
lua_pushvalue(dataL, id);
lua_xmove(dataL, L, 1);
luabind::invoke(
L,
[&]() {
LuaType::push(L, &event, false);
for (auto const& variant : varlist)
{
PushVariant(L, &variant);
}
lua_call(L, (int)varlist.size() + 1, 0);
},
1);
});
}
else
{
D->constructor.BindCustomDataVariable(key, DataVariable(D->scalarDef, (void*)(intptr_t)id));
}
}
static int getId(lua_State* L, lua_State* dataL)
{
lua_pushvalue(dataL, 1);
lua_xmove(dataL, L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (lua_type(L, -1) != LUA_TNUMBER)
{
luaL_error(L, "DataModel has no key : %s", lua_tostring(L, 2));
}
int id = (int)lua_tointeger(L, -1);
lua_pop(L, 2);
return id;
}
static int lDataModelGet(lua_State* L)
{
struct LuaDataModel* D = (struct LuaDataModel*)lua_touserdata(L, 1);
lua_State* dataL = D->dataL;
if (dataL == nullptr)
luaL_error(L, "DataModel closed");
int id = getId(L, dataL);
lua_pushvalue(dataL, id);
lua_xmove(dataL, L, 1);
return 1;
}
static int lDataModelSet(lua_State* L)
{
struct LuaDataModel* D = (struct LuaDataModel*)lua_touserdata(L, 1);
lua_State* dataL = D->dataL;
if (dataL == NULL)
luaL_error(L, "DataModel released");
lua_settop(dataL, D->top);
lua_pushvalue(L, 2);
lua_xmove(L, dataL, 1);
lua_rawget(dataL, 1);
if (lua_type(dataL, -1) == LUA_TNUMBER)
{
int id = (int)lua_tointeger(dataL, -1);
lua_pop(dataL, 1);
lua_xmove(L, dataL, 1);
lua_replace(dataL, id);
D->handle.DirtyVariable(lua_tostring(L, 2));
return 0;
}
lua_pop(dataL, 1);
BindVariable(D, L);
return 0;
}
bool OpenLuaDataModel(lua_State* L, Context* context, int name_index, int table_index)
{
String name = luaL_checkstring(L, name_index);
luaL_checktype(L, table_index, LUA_TTABLE);
DataModelConstructor constructor = context->CreateDataModel(name);
if (!constructor)
{
constructor = context->GetDataModel(name);
if (!constructor)
{
return false;
}
}
struct LuaDataModel* D = (struct LuaDataModel*)lua_newuserdata(L, sizeof(*D));
D->dataL = nullptr;
D->scalarDef = nullptr;
D->tableDef = nullptr;
D->constructor = constructor;
D->handle = constructor.GetModelHandle();
D->scalarDef = new LuaScalarDef(D);
D->tableDef = new LuaTableDef(D);
D->dataL = lua_newthread(L);
D->top = 1;
lua_newtable(D->dataL);
lua_pushnil(L);
while (lua_next(L, table_index) != 0)
{
BindVariable(D, L);
}
lua_setuservalue(L, -2);
if (luaL_newmetatable(L, RMLDATAMODEL))
{
luaL_Reg l[] = {
{"__index", lDataModelGet},
{"__newindex", lDataModelSet},
{nullptr, nullptr},
};
luaL_setfuncs(L, l, 0);
}
lua_setmetatable(L, -2);
return true;
}
// If you create all the Data Models from lua, you can store these LuaDataModel objects in a table,
// and call CloseLuaDataModel for each after Context released.
// We don't put it in __gc, becuase LuaDataModel can be free before DataModel if you are not careful.
// scalarDef will free by CloseLuaDataModel, but DataModel need it.
void CloseLuaDataModel(lua_State* L)
{
luaL_checkudata(L, -1, RMLDATAMODEL);
struct LuaDataModel* D = (struct LuaDataModel*)lua_touserdata(L, -1);
D->dataL = nullptr;
D->top = 0;
delete D->scalarDef;
D->scalarDef = nullptr;
delete D->tableDef;
D->tableDef = nullptr;
lua_pushnil(L);
lua_setuservalue(L, -2);
}
} // namespace Lua
} // namespace Rml