/* * 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 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 #define RMLDATAMODEL "RMLDATAMODEL" namespace Rml { namespace Lua { class LuaScalarDef; struct LuaDataModel { DataModelHandle handle; lua_State *dataL; LuaScalarDef *scalarDef; }; static void PushVariant(lua_State *L, const Rml::Variant &v) { switch (v.GetType()) { case Rml::Variant::Type::BOOL: lua_pushboolean(L, v.GetReference()); break; case Rml::Variant::Type::BYTE: lua_pushinteger(L, v.GetReference()); break; case Rml::Variant::Type::CHAR: { char s[1] = {v.GetReference() }; lua_pushlstring(L, s, 1); break; } case Rml::Variant::Type::FLOAT: lua_pushnumber(L, v.GetReference()); break; case Rml::Variant::Type::DOUBLE: lua_pushnumber(L, v.GetReference()); break; case Rml::Variant::Type::INT: lua_pushinteger(L, v.GetReference()); break; case Rml::Variant::Type::INT64: lua_pushinteger(L, v.GetReference()); break; case Rml::Variant::Type::STRING: { const Rml::String &s = v.GetReference(); lua_pushlstring(L, s.c_str(), s.length()); break; } case Rml::Variant::Type::NONE: case Rml::Variant::Type::VECTOR2: case Rml::Variant::Type::VECTOR3: case Rml::Variant::Type::VECTOR4: case Rml::Variant::Type::COLOURF: case Rml::Variant::Type::COLOURB: case Rml::Variant::Type::SCRIPTINTERFACE: case Rml::Variant::Type::TRANSFORMPTR: case Rml::Variant::Type::TRANSITIONLIST: case Rml::Variant::Type::ANIMATIONLIST: case Rml::Variant::Type::DECORATORSPTR: case Rml::Variant::Type::FONTEFFECTSPTR: case Rml::Variant::Type::VOIDPTR: default: // todo : support other types lua_pushnil(L); break; } } static void GetVariant(lua_State *L, int index, Variant &variant) { switch(lua_type(L, index)) { case LUA_TBOOLEAN: variant = (bool)lua_toboolean(L, index); break; case LUA_TNUMBER: if (lua_isinteger(L, index)) { variant = (int64_t)lua_tointeger(L, index); } else { variant = (double)lua_tonumber(L, index); } break; case LUA_TSTRING: variant = Rml::String(lua_tostring(L, index)); break; case LUA_TNIL: default: // todo: support other types variant = Variant(); break; } } class LuaScalarDef final : public VariableDefinition { public: LuaScalarDef (const struct LuaDataModel *model) : VariableDefinition(DataVariableType::Scalar), model(model) {} private: virtual bool Get(void* ptr, Variant& variant) { lua_State *L = model->dataL; if (!L) return false; int id = (intptr_t)ptr; GetVariant(L, id, variant); return true; } virtual bool Set(void* ptr, const Rml::Variant& variant) { int id = (intptr_t)ptr; lua_State *L = model->dataL; if (!L) return false; PushVariant(L, variant); lua_replace(L, id); return true; } const struct LuaDataModel *model; }; static int getId(lua_State *L, lua_State *dataL) { lua_pushvalue(dataL, 1); lua_xmove(dataL, L, 1); lua_pushvalue(L, 2); if (lua_rawget(L, -2) != LUA_TNUMBER) { luaL_error(L, "DataModel has no key : %s", lua_tostring(L, 2)); } int id = 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 == nullptr) luaL_error(L, "DataModel closed"); int id = getId(L, dataL); lua_xmove(L, dataL, 1); lua_replace(dataL, id); D->handle.DirtyVariable(lua_tostring(L, 2)); return 0; } // Construct a lua sub thread for LuaDataModel // stack 1 : { name(string) -> id(integer) } // stack 2- : values // For example : build from { str = "Hello", x = 0 } // 1: { str = 2 , x = 3 } // 2: "Hello" // 3: 0 static lua_State * InitDataModelFromTable(lua_State *L, int index, Rml::DataModelConstructor &ctor, class LuaScalarDef *def) { lua_State *dataL = lua_newthread(L); lua_newtable(dataL); intptr_t id = 2; lua_pushnil(L); while (lua_next(L, index) != 0) { if (!lua_checkstack(dataL, 4)) { luaL_error(L, "Memory Error"); } // 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); ctor.BindCustomDataVariable(key, Rml::DataVariable(def, (void *)id)); ++id; } return dataL; } bool OpenLuaDataModel(lua_State *L, Rml::Context *context, int name_index, int table_index) { Rml::String name = luaL_checkstring(L, name_index); luaL_checktype(L, table_index, LUA_TTABLE); Rml::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->handle = constructor.GetModelHandle(); D->scalarDef = new LuaScalarDef(D); D->dataL = InitDataModelFromTable(L, table_index, constructor, D->scalarDef); lua_setuservalue(L, -2); // set D->dataL into uservalue of D 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; delete D->scalarDef; D->scalarDef = nullptr; lua_pushnil(L); lua_setuservalue(L, -2); } } // namespace Lua } // namespace Rml