mirror of
https://github.com/OpenMW/openmw.git
synced 2025-03-01 15:09:43 +00:00
Merge branch 'fix_lua_48' into 'openmw-48'
Merge !2661, !2687, !2733, !2770, !2774 to openmw-48 (fixes #7128) See merge request OpenMW/openmw!2778
This commit is contained in:
commit
2f6a809d18
25 changed files with 554 additions and 387 deletions
|
@ -60,7 +60,7 @@ add_openmw_dir (mwscript
|
||||||
|
|
||||||
add_openmw_dir (mwlua
|
add_openmw_dir (mwlua
|
||||||
luamanagerimp object worldview userdataserializer eventqueue
|
luamanagerimp object worldview userdataserializer eventqueue
|
||||||
luabindings localscripts playerscripts objectbindings cellbindings asyncbindings
|
luabindings localscripts playerscripts objectbindings cellbindings
|
||||||
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings
|
camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings
|
||||||
types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair
|
types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
#include "luabindings.hpp"
|
|
||||||
|
|
||||||
#include "luamanagerimp.hpp"
|
|
||||||
|
|
||||||
namespace sol
|
|
||||||
{
|
|
||||||
template <>
|
|
||||||
struct is_automagical<MWLua::AsyncPackageId> : std::false_type {};
|
|
||||||
|
|
||||||
template <>
|
|
||||||
struct is_automagical<LuaUtil::Callback> : std::false_type {};
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace MWLua
|
|
||||||
{
|
|
||||||
|
|
||||||
struct TimerCallback
|
|
||||||
{
|
|
||||||
AsyncPackageId mAsyncId;
|
|
||||||
std::string mName;
|
|
||||||
};
|
|
||||||
|
|
||||||
sol::function getAsyncPackageInitializer(const Context& context)
|
|
||||||
{
|
|
||||||
using TimerType = LuaUtil::ScriptsContainer::TimerType;
|
|
||||||
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
|
||||||
api["registerTimerCallback"]
|
|
||||||
= [](const AsyncPackageId& asyncId, std::string_view name, sol::main_protected_function callback) {
|
|
||||||
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
|
|
||||||
return TimerCallback{ asyncId, std::string(name) };
|
|
||||||
};
|
|
||||||
api["newSimulationTimer"] = [world = context.mWorldView](const AsyncPackageId&, double delay,
|
|
||||||
const TimerCallback& callback, sol::main_object callbackArg) {
|
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::SIMULATION_TIME,
|
|
||||||
world->getSimulationTime() + delay, callback.mAsyncId.mScriptId, callback.mName,
|
|
||||||
std::move(callbackArg));
|
|
||||||
};
|
|
||||||
api["newGameTimer"] = [world = context.mWorldView](const AsyncPackageId&, double delay,
|
|
||||||
const TimerCallback& callback, sol::main_object callbackArg) {
|
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::GAME_TIME, world->getGameTime() + delay,
|
|
||||||
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
|
||||||
};
|
|
||||||
api["newUnsavableSimulationTimer"] = [world = context.mWorldView](const AsyncPackageId& asyncId, double delay,
|
|
||||||
sol::main_protected_function callback) {
|
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
|
||||||
TimerType::SIMULATION_TIME, world->getSimulationTime() + delay, asyncId.mScriptId, std::move(callback));
|
|
||||||
};
|
|
||||||
api["newUnsavableGameTimer"] = [world = context.mWorldView](const AsyncPackageId& asyncId, double delay,
|
|
||||||
sol::main_protected_function callback) {
|
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
|
||||||
TimerType::GAME_TIME, world->getGameTime() + delay, asyncId.mScriptId, std::move(callback));
|
|
||||||
};
|
|
||||||
api["callback"] = [](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> LuaUtil::Callback {
|
|
||||||
return LuaUtil::Callback{ std::move(fn), asyncId.mHiddenData };
|
|
||||||
};
|
|
||||||
|
|
||||||
sol::usertype<LuaUtil::Callback> callbackType = context.mLua->sol().new_usertype<LuaUtil::Callback>("Callback");
|
|
||||||
callbackType[sol::meta_function::call] =
|
|
||||||
[](const LuaUtil::Callback& callback, sol::variadic_args va) { return callback.call(sol::as_args(va)); };
|
|
||||||
|
|
||||||
auto initializer = [](sol::table hiddenData)
|
|
||||||
{
|
|
||||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
|
|
||||||
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
|
|
||||||
};
|
|
||||||
return sol::make_object(context.mLua->sol(), initializer);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -38,15 +38,6 @@ namespace MWLua
|
||||||
void initCellBindingsForLocalScripts(const Context&);
|
void initCellBindingsForLocalScripts(const Context&);
|
||||||
void initCellBindingsForGlobalScripts(const Context&);
|
void initCellBindingsForGlobalScripts(const Context&);
|
||||||
|
|
||||||
// Implemented in asyncbindings.cpp
|
|
||||||
struct AsyncPackageId
|
|
||||||
{
|
|
||||||
LuaUtil::ScriptsContainer* mContainer;
|
|
||||||
int mScriptId;
|
|
||||||
sol::table mHiddenData;
|
|
||||||
};
|
|
||||||
sol::function getAsyncPackageInitializer(const Context&);
|
|
||||||
|
|
||||||
// Implemented in camerabindings.cpp
|
// Implemented in camerabindings.cpp
|
||||||
sol::table initCameraPackage(const Context&);
|
sol::table initCameraPackage(const Context&);
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <components/settings/settings.hpp>
|
#include <components/settings/settings.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/asyncpackage.hpp>
|
||||||
#include <components/lua/utilpackage.hpp>
|
#include <components/lua/utilpackage.hpp>
|
||||||
|
|
||||||
#include <components/lua_ui/util.hpp>
|
#include <components/lua_ui/util.hpp>
|
||||||
|
@ -86,7 +87,10 @@ namespace MWLua
|
||||||
LocalScripts::initializeSelfPackage(localContext);
|
LocalScripts::initializeSelfPackage(localContext);
|
||||||
LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
|
LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
|
||||||
|
|
||||||
mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context));
|
mLua.addCommonPackage("openmw.async",
|
||||||
|
LuaUtil::getAsyncPackageInitializer(
|
||||||
|
mLua.sol(), [this] { return mWorldView.getSimulationTime(); },
|
||||||
|
[this] { return mWorldView.getGameTime(); }));
|
||||||
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
|
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
|
||||||
mLua.addCommonPackage("openmw.core", initCorePackage(context));
|
mLua.addCommonPackage("openmw.core", initCorePackage(context));
|
||||||
mLua.addCommonPackage("openmw.types", initTypesPackage(context));
|
mLua.addCommonPackage("openmw.types", initTypesPackage(context));
|
||||||
|
|
|
@ -109,9 +109,9 @@ namespace MWLua
|
||||||
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
|
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
api["asyncCastRenderingRay"] = [context](const LuaUtil::Callback& callback, const osg::Vec3f& from,
|
api["asyncCastRenderingRay"] = [context](
|
||||||
const osg::Vec3f& to) {
|
const sol::table& callback, const osg::Vec3f& from, const osg::Vec3f& to) {
|
||||||
context.mLuaManager->addAction([context, callback, from, to] {
|
context.mLuaManager->addAction([context, callback = LuaUtil::Callback::fromLua(callback), from, to] {
|
||||||
MWPhysics::RayCastingResult res;
|
MWPhysics::RayCastingResult res;
|
||||||
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
|
MWBase::Environment::get().getWorld()->castRenderingRay(res, from, to, false, false);
|
||||||
context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res));
|
context.mLuaManager->queueCallback(callback, sol::main_object(context.mLua->sol(), sol::in_place, res));
|
||||||
|
|
|
@ -89,69 +89,6 @@ namespace MWLua
|
||||||
|
|
||||||
sol::table initUserInterfacePackage(const Context& context)
|
sol::table initUserInterfacePackage(const Context& context)
|
||||||
{
|
{
|
||||||
auto uiContent = context.mLua->sol().new_usertype<LuaUi::Content>("UiContent");
|
|
||||||
uiContent[sol::meta_function::length] = [](const LuaUi::Content& content)
|
|
||||||
{
|
|
||||||
return content.size();
|
|
||||||
};
|
|
||||||
uiContent[sol::meta_function::index] = sol::overload(
|
|
||||||
[](const LuaUi::Content& content, size_t index)
|
|
||||||
{
|
|
||||||
return content.at(fromLuaIndex(index));
|
|
||||||
},
|
|
||||||
[](const LuaUi::Content& content, std::string_view name)
|
|
||||||
{
|
|
||||||
return content.at(name);
|
|
||||||
});
|
|
||||||
uiContent[sol::meta_function::new_index] = sol::overload(
|
|
||||||
[](LuaUi::Content& content, size_t index, const sol::table& table)
|
|
||||||
{
|
|
||||||
content.assign(fromLuaIndex(index), table);
|
|
||||||
},
|
|
||||||
[](LuaUi::Content& content, size_t index, sol::nil_t nil)
|
|
||||||
{
|
|
||||||
content.remove(fromLuaIndex(index));
|
|
||||||
},
|
|
||||||
[](LuaUi::Content& content, std::string_view name, const sol::table& table)
|
|
||||||
{
|
|
||||||
content.assign(name, table);
|
|
||||||
},
|
|
||||||
[](LuaUi::Content& content, std::string_view name, sol::nil_t nil)
|
|
||||||
{
|
|
||||||
content.remove(name);
|
|
||||||
});
|
|
||||||
uiContent["insert"] = [](LuaUi::Content& content, size_t index, const sol::table& table)
|
|
||||||
{
|
|
||||||
content.insert(fromLuaIndex(index), table);
|
|
||||||
};
|
|
||||||
uiContent["add"] = [](LuaUi::Content& content, const sol::table& table)
|
|
||||||
{
|
|
||||||
content.insert(content.size(), table);
|
|
||||||
};
|
|
||||||
uiContent["indexOf"] = [](LuaUi::Content& content, const sol::table& table) -> sol::optional<size_t>
|
|
||||||
{
|
|
||||||
size_t index = content.indexOf(table);
|
|
||||||
if (index < content.size())
|
|
||||||
return toLuaIndex(index);
|
|
||||||
else
|
|
||||||
return sol::nullopt;
|
|
||||||
};
|
|
||||||
{
|
|
||||||
auto pairs = [](LuaUi::Content& content)
|
|
||||||
{
|
|
||||||
auto next = [](LuaUi::Content& content, size_t i) -> sol::optional<std::tuple<size_t, sol::table>>
|
|
||||||
{
|
|
||||||
if (i < content.size())
|
|
||||||
return std::make_tuple(i + 1, content.at(i));
|
|
||||||
else
|
|
||||||
return sol::nullopt;
|
|
||||||
};
|
|
||||||
return std::make_tuple(next, content, 0);
|
|
||||||
};
|
|
||||||
uiContent[sol::meta_function::ipairs] = pairs;
|
|
||||||
uiContent[sol::meta_function::pairs] = pairs;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
|
auto element = context.mLua->sol().new_usertype<LuaUi::Element>("Element");
|
||||||
element["layout"] = sol::property(
|
element["layout"] = sol::property(
|
||||||
[](LuaUi::Element& element)
|
[](LuaUi::Element& element)
|
||||||
|
@ -210,12 +147,8 @@ namespace MWLua
|
||||||
luaManager->addAction([wm, obj=obj.as<LObject>()]{ wm->setConsoleSelectedObject(obj.ptr()); });
|
luaManager->addAction([wm, obj=obj.as<LObject>()]{ wm->setConsoleSelectedObject(obj.ptr()); });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
api["content"] = [](const sol::table& table)
|
api["content"] = LuaUi::loadContentConstructor(context.mLua);
|
||||||
{
|
api["create"] = [context](const sol::table& layout) {
|
||||||
return LuaUi::Content(table);
|
|
||||||
};
|
|
||||||
api["create"] = [context](const sol::table& layout)
|
|
||||||
{
|
|
||||||
auto element = LuaUi::Element::make(layout);
|
auto element = LuaUi::Element::make(layout);
|
||||||
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::CREATE, element, context.mLua));
|
context.mLuaManager->addAction(std::make_unique<UiAction>(UiAction::CREATE, element, context.mLua));
|
||||||
return element;
|
return element;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include "gmock/gmock.h"
|
#include "gmock/gmock.h"
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include <components/lua/asyncpackage.hpp>
|
||||||
#include <components/lua/luastate.hpp>
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/lua/scriptscontainer.hpp>
|
|
||||||
|
|
||||||
#include "../testing_util.hpp"
|
#include "../testing_util.hpp"
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <components/esm/luascripts.hpp>
|
#include <components/esm/luascripts.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/asyncpackage.hpp>
|
||||||
#include <components/lua/luastate.hpp>
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/lua/scriptscontainer.hpp>
|
#include <components/lua/scriptscontainer.hpp>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
#include <boost/filesystem/operations.hpp>
|
#include <boost/filesystem/operations.hpp>
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
#include <components/lua/scriptscontainer.hpp>
|
#include <components/lua/asyncpackage.hpp>
|
||||||
#include <components/lua/storage.hpp>
|
#include <components/lua/storage.hpp>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
|
@ -24,17 +24,16 @@ namespace
|
||||||
LuaUtil::LuaStorage storage(mLua);
|
LuaUtil::LuaStorage storage(mLua);
|
||||||
|
|
||||||
std::vector<std::string> callbackCalls;
|
std::vector<std::string> callbackCalls;
|
||||||
LuaUtil::Callback callback{
|
sol::table callbackHiddenData(mLua, sol::create);
|
||||||
sol::make_object(mLua, [&](const std::string& section, const sol::optional<std::string>& key)
|
callbackHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptsContainer::ScriptId{};
|
||||||
{
|
sol::table callback(mLua, sol::create);
|
||||||
if (key)
|
callback[1] = [&](const std::string& section, const sol::optional<std::string>& key) {
|
||||||
callbackCalls.push_back(section + "_" + *key);
|
if (key)
|
||||||
else
|
callbackCalls.push_back(section + "_" + *key);
|
||||||
callbackCalls.push_back(section + "_*");
|
else
|
||||||
}),
|
callbackCalls.push_back(section + "_*");
|
||||||
sol::table(mLua, sol::create)
|
|
||||||
};
|
};
|
||||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = "fakeId";
|
callback[2] = LuaUtil::AsyncPackageId{ nullptr, 0, callbackHiddenData };
|
||||||
|
|
||||||
mLua["mutable"] = storage.getMutableSection("test");
|
mLua["mutable"] = storage.getMutableSection("test");
|
||||||
mLua["ro"] = storage.getReadOnlySection("test");
|
mLua["ro"] = storage.getReadOnlySection("test");
|
||||||
|
|
|
@ -1,62 +1,105 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/luastate.hpp>
|
||||||
#include <components/lua_ui/content.hpp>
|
#include <components/lua_ui/content.hpp>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
using namespace testing;
|
using namespace testing;
|
||||||
|
|
||||||
sol::state state;
|
struct LuaUiContentTest : Test
|
||||||
|
|
||||||
sol::table makeTable()
|
|
||||||
{
|
{
|
||||||
return sol::table(state, sol::create);
|
LuaUtil::LuaState mLuaState{ nullptr, nullptr };
|
||||||
|
sol::protected_function mNew;
|
||||||
|
LuaUiContentTest()
|
||||||
|
{
|
||||||
|
mLuaState.addInternalLibSearchPath("resources/lua_libs");
|
||||||
|
mNew = LuaUi::loadContentConstructor(&mLuaState);
|
||||||
|
}
|
||||||
|
|
||||||
|
LuaUi::ContentView makeContent(sol::table source)
|
||||||
|
{
|
||||||
|
auto result = mNew.call(source);
|
||||||
|
if (result.get_type() != sol::type::table)
|
||||||
|
throw std::logic_error("Expected table");
|
||||||
|
return LuaUi::ContentView(result.get<sol::table>());
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::table makeTable() { return sol::table(mLuaState.sol(), sol::create); }
|
||||||
|
|
||||||
|
sol::table makeTable(std::string name)
|
||||||
|
{
|
||||||
|
auto result = makeTable();
|
||||||
|
result["name"] = name;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(LuaUiContentTest, ProtectedMetatable)
|
||||||
|
{
|
||||||
|
mLuaState.sol()["makeContent"] = mNew;
|
||||||
|
mLuaState.sol()["M"] = makeContent(makeTable()).getMetatable();
|
||||||
|
std::string testScript = R"(
|
||||||
|
assert(not pcall(function() setmetatable(makeContent{}, {}) end), 'Metatable is not protected')
|
||||||
|
assert(getmetatable(makeContent{}) == false, 'Metatable is not protected')
|
||||||
|
)";
|
||||||
|
EXPECT_NO_THROW(mLuaState.sol().safe_script(testScript));
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::table makeTable(std::string name)
|
TEST_F(LuaUiContentTest, Create)
|
||||||
{
|
|
||||||
auto result = makeTable();
|
|
||||||
result["name"] = name;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(LuaUiContentTest, Create)
|
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
LuaUi::Content content(table);
|
LuaUi::ContentView content = makeContent(table);
|
||||||
EXPECT_EQ(content.size(), 3);
|
EXPECT_EQ(content.size(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LuaUiContentTest, CreateWithHole)
|
TEST_F(LuaUiContentTest, Insert)
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table[4] = makeTable();
|
table.add(makeTable());
|
||||||
EXPECT_ANY_THROW(LuaUi::Content content(table));
|
LuaUi::ContentView content = makeContent(table);
|
||||||
|
content.insert(2, makeTable("inserted"));
|
||||||
|
EXPECT_EQ(content.size(), 4);
|
||||||
|
auto inserted = content.at("inserted");
|
||||||
|
auto index = content.indexOf(inserted);
|
||||||
|
EXPECT_TRUE(index.has_value());
|
||||||
|
EXPECT_EQ(index.value(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LuaUiContentTest, WrongType)
|
TEST_F(LuaUiContentTest, MakeHole)
|
||||||
|
{
|
||||||
|
auto table = makeTable();
|
||||||
|
table.add(makeTable());
|
||||||
|
table.add(makeTable());
|
||||||
|
LuaUi::ContentView content = makeContent(table);
|
||||||
|
sol::table t = makeTable();
|
||||||
|
EXPECT_ANY_THROW(content.assign(3, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LuaUiContentTest, WrongType)
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add("a");
|
table.add("a");
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
EXPECT_ANY_THROW(LuaUi::Content content(table));
|
EXPECT_ANY_THROW(makeContent(table));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LuaUiContentTest, NameAccess)
|
TEST_F(LuaUiContentTest, NameAccess)
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable("a"));
|
table.add(makeTable("a"));
|
||||||
LuaUi::Content content(table);
|
LuaUi::ContentView content = makeContent(table);
|
||||||
EXPECT_NO_THROW(content.at("a"));
|
EXPECT_NO_THROW(content.at("a"));
|
||||||
content.remove("a");
|
content.remove("a");
|
||||||
|
EXPECT_EQ(content.size(), 1);
|
||||||
content.assign(content.size(), makeTable("b"));
|
content.assign(content.size(), makeTable("b"));
|
||||||
content.assign("b", makeTable());
|
content.assign("b", makeTable());
|
||||||
EXPECT_ANY_THROW(content.at("b"));
|
EXPECT_ANY_THROW(content.at("b"));
|
||||||
|
@ -67,31 +110,35 @@ namespace
|
||||||
EXPECT_ANY_THROW(content.at("c"));
|
EXPECT_ANY_THROW(content.at("c"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LuaUiContentTest, IndexOf)
|
TEST_F(LuaUiContentTest, IndexOf)
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
table.add(makeTable());
|
table.add(makeTable());
|
||||||
LuaUi::Content content(table);
|
LuaUi::ContentView content = makeContent(table);
|
||||||
auto child = makeTable();
|
auto child = makeTable();
|
||||||
content.assign(2, child);
|
content.assign(2, child);
|
||||||
EXPECT_EQ(content.indexOf(child), 2);
|
EXPECT_EQ(content.indexOf(child).value(), 2);
|
||||||
EXPECT_EQ(content.indexOf(makeTable()), content.size());
|
EXPECT_TRUE(!content.indexOf(makeTable()).has_value());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(LuaUiContentTest, BoundsChecks)
|
TEST_F(LuaUiContentTest, BoundsChecks)
|
||||||
{
|
{
|
||||||
auto table = makeTable();
|
auto table = makeTable();
|
||||||
LuaUi::Content content(table);
|
LuaUi::ContentView content = makeContent(table);
|
||||||
EXPECT_ANY_THROW(content.at(0));
|
EXPECT_ANY_THROW(content.at(0));
|
||||||
|
EXPECT_EQ(content.size(), 0);
|
||||||
content.assign(content.size(), makeTable());
|
content.assign(content.size(), makeTable());
|
||||||
|
EXPECT_EQ(content.size(), 1);
|
||||||
content.assign(content.size(), makeTable());
|
content.assign(content.size(), makeTable());
|
||||||
|
EXPECT_EQ(content.size(), 2);
|
||||||
content.assign(content.size(), makeTable());
|
content.assign(content.size(), makeTable());
|
||||||
|
EXPECT_EQ(content.size(), 3);
|
||||||
EXPECT_ANY_THROW(content.at(3));
|
EXPECT_ANY_THROW(content.at(3));
|
||||||
EXPECT_ANY_THROW(content.remove(3));
|
EXPECT_ANY_THROW(content.remove(3));
|
||||||
EXPECT_NO_THROW(content.remove(1));
|
content.remove(2);
|
||||||
EXPECT_NO_THROW(content.at(1));
|
|
||||||
EXPECT_EQ(content.size(), 2);
|
EXPECT_EQ(content.size(), 2);
|
||||||
|
EXPECT_ANY_THROW(content.at(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
project (Components)
|
project (Components)
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
set(BUNDLE_RESOURCES_DIR "${APP_BUNDLE_DIR}/Contents/Resources")
|
||||||
|
set(OPENMW_RESOURCES_ROOT ${BUNDLE_RESOURCES_DIR})
|
||||||
|
endif(APPLE)
|
||||||
|
|
||||||
# Version file
|
# Version file
|
||||||
set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in")
|
set (VERSION_IN_FILE "${OpenMW_SOURCE_DIR}/files/version.in")
|
||||||
set (VERSION_FILE_PATH_BASE "${OpenMW_BINARY_DIR}")
|
set (VERSION_FILE_PATH_BASE "${OpenMW_BINARY_DIR}")
|
||||||
|
@ -29,7 +34,7 @@ endif (GIT_CHECKOUT)
|
||||||
# source files
|
# source files
|
||||||
|
|
||||||
add_component_dir (lua
|
add_component_dir (lua
|
||||||
luastate scriptscontainer utilpackage serialization configuration l10n storage
|
luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (l10n
|
add_component_dir (l10n
|
||||||
|
@ -271,6 +276,7 @@ add_component_dir (lua_ui
|
||||||
properties widget element util layers content alignment resources
|
properties widget element util layers content alignment resources
|
||||||
adapter text textedit window image container flex
|
adapter text textedit window image container flex
|
||||||
)
|
)
|
||||||
|
copy_resource_file("lua_ui/content.lua" "${OPENMW_RESOURCES_ROOT}" "resources/lua_libs/content.lua")
|
||||||
|
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
|
93
components/lua/asyncpackage.cpp
Normal file
93
components/lua/asyncpackage.cpp
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#include "asyncpackage.hpp"
|
||||||
|
|
||||||
|
namespace sol
|
||||||
|
{
|
||||||
|
template <>
|
||||||
|
struct is_automagical<LuaUtil::AsyncPackageId> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct is_automagical<LuaUtil::Callback> : std::false_type
|
||||||
|
{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace LuaUtil
|
||||||
|
{
|
||||||
|
|
||||||
|
struct TimerCallback
|
||||||
|
{
|
||||||
|
AsyncPackageId mAsyncId;
|
||||||
|
std::string mName;
|
||||||
|
};
|
||||||
|
|
||||||
|
Callback Callback::fromLua(const sol::table& t)
|
||||||
|
{
|
||||||
|
return Callback{ t.raw_get<sol::main_protected_function>(1), t.raw_get<AsyncPackageId>(2).mHiddenData };
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Callback::isLuaCallback(const sol::object& t)
|
||||||
|
{
|
||||||
|
if (!t.is<sol::table>())
|
||||||
|
return false;
|
||||||
|
sol::object meta = sol::table(t)[sol::metatable_key];
|
||||||
|
if (!meta.is<sol::table>())
|
||||||
|
return false;
|
||||||
|
return sol::table(meta).raw_get_or<bool, std::string_view, bool>("isCallback", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::function getAsyncPackageInitializer(
|
||||||
|
lua_State* L, std::function<double()> simulationTimeFn, std::function<double()> gameTimeFn)
|
||||||
|
{
|
||||||
|
sol::state_view lua(L);
|
||||||
|
using TimerType = ScriptsContainer::TimerType;
|
||||||
|
sol::usertype<AsyncPackageId> api = lua.new_usertype<AsyncPackageId>("AsyncPackage");
|
||||||
|
api["registerTimerCallback"]
|
||||||
|
= [](const AsyncPackageId& asyncId, std::string_view name, sol::main_protected_function callback) {
|
||||||
|
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
|
||||||
|
return TimerCallback{ asyncId, std::string(name) };
|
||||||
|
};
|
||||||
|
api["newSimulationTimer"] = [simulationTimeFn](const AsyncPackageId&, double delay,
|
||||||
|
const TimerCallback& callback, sol::main_object callbackArg) {
|
||||||
|
callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::SIMULATION_TIME, simulationTimeFn() + delay,
|
||||||
|
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||||
|
};
|
||||||
|
api["newGameTimer"] = [gameTimeFn](const AsyncPackageId&, double delay, const TimerCallback& callback,
|
||||||
|
sol::main_object callbackArg) {
|
||||||
|
callback.mAsyncId.mContainer->setupSerializableTimer(TimerType::GAME_TIME, gameTimeFn() + delay,
|
||||||
|
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||||
|
};
|
||||||
|
api["newUnsavableSimulationTimer"]
|
||||||
|
= [simulationTimeFn](const AsyncPackageId& asyncId, double delay, sol::main_protected_function callback) {
|
||||||
|
asyncId.mContainer->setupUnsavableTimer(
|
||||||
|
TimerType::SIMULATION_TIME, simulationTimeFn() + delay, asyncId.mScriptId, std::move(callback));
|
||||||
|
};
|
||||||
|
api["newUnsavableGameTimer"]
|
||||||
|
= [gameTimeFn](const AsyncPackageId& asyncId, double delay, sol::main_protected_function callback) {
|
||||||
|
asyncId.mContainer->setupUnsavableTimer(
|
||||||
|
TimerType::GAME_TIME, gameTimeFn() + delay, asyncId.mScriptId, std::move(callback));
|
||||||
|
};
|
||||||
|
|
||||||
|
sol::table callbackMeta = sol::table::create(L);
|
||||||
|
callbackMeta[sol::meta_function::call] = [](const sol::table& callback, sol::variadic_args va) {
|
||||||
|
return Callback::fromLua(callback).call(sol::as_args(va));
|
||||||
|
};
|
||||||
|
callbackMeta[sol::meta_function::to_string] = [] { return "Callback"; };
|
||||||
|
callbackMeta[sol::meta_function::metatable] = false;
|
||||||
|
callbackMeta["isCallback"] = true;
|
||||||
|
api["callback"] = [callbackMeta](const AsyncPackageId& asyncId, sol::main_protected_function fn) -> sol::table {
|
||||||
|
sol::table c = sol::table::create(fn.lua_state(), 2);
|
||||||
|
c.raw_set(1, std::move(fn), 2, asyncId);
|
||||||
|
c[sol::metatable_key] = callbackMeta;
|
||||||
|
return c;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto initializer = [](sol::table hiddenData) {
|
||||||
|
ScriptsContainer::ScriptId id = hiddenData[ScriptsContainer::sScriptIdKey];
|
||||||
|
return AsyncPackageId{ id.mContainer, id.mIndex, hiddenData };
|
||||||
|
};
|
||||||
|
return sol::make_object(lua, initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
56
components/lua/asyncpackage.hpp
Normal file
56
components/lua/asyncpackage.hpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef COMPONENTS_LUA_ASYNCPACKAGE_H
|
||||||
|
#define COMPONENTS_LUA_ASYNCPACKAGE_H
|
||||||
|
|
||||||
|
#include "scriptscontainer.hpp"
|
||||||
|
|
||||||
|
namespace LuaUtil
|
||||||
|
{
|
||||||
|
struct AsyncPackageId
|
||||||
|
{
|
||||||
|
ScriptsContainer* mContainer;
|
||||||
|
int mScriptId;
|
||||||
|
sol::table mHiddenData;
|
||||||
|
};
|
||||||
|
sol::function getAsyncPackageInitializer(
|
||||||
|
lua_State* L, std::function<double()> simulationTimeFn, std::function<double()> gameTimeFn);
|
||||||
|
|
||||||
|
// Wrapper for a Lua function.
|
||||||
|
// Holds information about the script the function belongs to.
|
||||||
|
// Needed to prevent callback calls if the script was removed.
|
||||||
|
struct Callback
|
||||||
|
{
|
||||||
|
sol::main_protected_function mFunc;
|
||||||
|
sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer
|
||||||
|
|
||||||
|
static bool isLuaCallback(const sol::object&);
|
||||||
|
static Callback fromLua(const sol::table&);
|
||||||
|
|
||||||
|
bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; }
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
sol::object call(Args&&... args) const
|
||||||
|
{
|
||||||
|
if (isValid())
|
||||||
|
return LuaUtil::call(mFunc, std::forward<Args>(args)...);
|
||||||
|
else
|
||||||
|
Log(Debug::Debug) << "Ignored callback to the removed script "
|
||||||
|
<< mHiddenData.get<std::string>(ScriptsContainer::sScriptDebugNameKey);
|
||||||
|
return sol::nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void tryCall(Args&&... args) const
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this->call(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
catch (std::exception& e)
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Error in callback: " << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // COMPONENTS_LUA_ASYNCPACKAGE_H
|
|
@ -60,6 +60,13 @@ namespace LuaUtil
|
||||||
|
|
||||||
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
|
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
|
||||||
|
|
||||||
|
mLua["setEnvironment"]
|
||||||
|
= [](const sol::environment& env, const sol::function& fn) { sol::set_environment(env, fn); };
|
||||||
|
mLua["loadFromVFS"] = [this](std::string_view packageName) {
|
||||||
|
return loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
|
||||||
|
};
|
||||||
|
mLua["loadInternalLib"] = [this](std::string_view packageName) { return loadInternalLib(packageName); };
|
||||||
|
|
||||||
// Some fixes for compatibility between different Lua versions
|
// Some fixes for compatibility between different Lua versions
|
||||||
if (mLua["unpack"] == sol::nil)
|
if (mLua["unpack"] == sol::nil)
|
||||||
mLua["unpack"] = mLua["table"]["unpack"];
|
mLua["unpack"] = mLua["table"]["unpack"];
|
||||||
|
@ -85,6 +92,19 @@ namespace LuaUtil
|
||||||
end
|
end
|
||||||
printGen = function(name) return function(...) return printToLog(name, ...) end end
|
printGen = function(name) return function(...) return printToLog(name, ...) end end
|
||||||
|
|
||||||
|
function requireGen(env, loaded, loadFn)
|
||||||
|
return function(packageName)
|
||||||
|
local p = loaded[packageName]
|
||||||
|
if p == nil then
|
||||||
|
local loader = loadFn(packageName)
|
||||||
|
setEnvironment(env, loader)
|
||||||
|
p = loader(packageName)
|
||||||
|
loaded[packageName] = p
|
||||||
|
end
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
function createStrictIndexFn(tbl)
|
function createStrictIndexFn(tbl)
|
||||||
return function(_, key)
|
return function(_, key)
|
||||||
local res = tbl[key]
|
local res = tbl[key]
|
||||||
|
@ -207,17 +227,7 @@ namespace LuaUtil
|
||||||
loaded[key] = maybeRunLoader(value);
|
loaded[key] = maybeRunLoader(value);
|
||||||
for (const auto& [key, value] : packages)
|
for (const auto& [key, value] : packages)
|
||||||
loaded[key] = maybeRunLoader(value);
|
loaded[key] = maybeRunLoader(value);
|
||||||
env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable
|
env["require"] = mLua["requireGen"](env, loaded, mLua["loadFromVFS"]);
|
||||||
{
|
|
||||||
sol::object package = loaded[packageName];
|
|
||||||
if (package != sol::nil)
|
|
||||||
return package;
|
|
||||||
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
|
|
||||||
sol::set_environment(env, packageLoader);
|
|
||||||
package = call(packageLoader, packageName);
|
|
||||||
loaded[packageName] = package;
|
|
||||||
return package;
|
|
||||||
};
|
|
||||||
|
|
||||||
sol::set_environment(env, script);
|
sol::set_environment(env, script);
|
||||||
return call(script);
|
return call(script);
|
||||||
|
@ -229,15 +239,7 @@ namespace LuaUtil
|
||||||
sol::table loaded(mLua, sol::create);
|
sol::table loaded(mLua, sol::create);
|
||||||
for (const std::string& s : safePackages)
|
for (const std::string& s : safePackages)
|
||||||
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
|
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
|
||||||
env["require"] = [this, loaded, env](const std::string& module) mutable
|
env["require"] = mLua["requireGen"](env, loaded, mLua["loadInternalLib"]);
|
||||||
{
|
|
||||||
if (loaded[module] != sol::nil)
|
|
||||||
return loaded[module];
|
|
||||||
sol::protected_function initializer = loadInternalLib(module);
|
|
||||||
sol::set_environment(env, initializer);
|
|
||||||
loaded[module] = call(initializer, module);
|
|
||||||
return loaded[module];
|
|
||||||
};
|
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -246,38 +246,6 @@ namespace LuaUtil
|
||||||
int64_t mTemporaryCallbackCounter = 0;
|
int64_t mTemporaryCallbackCounter = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Wrapper for a Lua function.
|
|
||||||
// Holds information about the script the function belongs to.
|
|
||||||
// Needed to prevent callback calls if the script was removed.
|
|
||||||
struct Callback
|
|
||||||
{
|
|
||||||
sol::main_protected_function mFunc;
|
|
||||||
sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer
|
|
||||||
|
|
||||||
bool isValid() const { return mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil; }
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
sol::object call(Args&&... args) const
|
|
||||||
{
|
|
||||||
if (isValid())
|
|
||||||
return LuaUtil::call(mFunc, std::forward<Args>(args)...);
|
|
||||||
else
|
|
||||||
Log(Debug::Debug) << "Ignored callback to the removed script "
|
|
||||||
<< mHiddenData.get<std::string>(ScriptsContainer::sScriptDebugNameKey);
|
|
||||||
return sol::nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
|
||||||
void tryCall(Args&&... args) const
|
|
||||||
{
|
|
||||||
try { this->call(std::forward<Args>(args)...); }
|
|
||||||
catch (std::exception& e)
|
|
||||||
{
|
|
||||||
Log(Debug::Error) << "Error in callback: " << e.what();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H
|
#endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H
|
||||||
|
|
|
@ -108,8 +108,7 @@ namespace LuaUtil
|
||||||
return section.mSection->get(key).getCopy(s);
|
return section.mSection->get(key).getCopy(s);
|
||||||
};
|
};
|
||||||
sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); };
|
sview["asTable"] = [](const SectionView& section) { return section.mSection->asTable(); };
|
||||||
sview["subscribe"] = [](const SectionView& section, const Callback& callback)
|
sview["subscribe"] = [](const SectionView& section, const sol::table& callback) {
|
||||||
{
|
|
||||||
std::vector<Callback>& callbacks = section.mSection->mCallbacks;
|
std::vector<Callback>& callbacks = section.mSection->mCallbacks;
|
||||||
if (!callbacks.empty() && callbacks.size() == callbacks.capacity())
|
if (!callbacks.empty() && callbacks.size() == callbacks.capacity())
|
||||||
{
|
{
|
||||||
|
@ -117,7 +116,7 @@ namespace LuaUtil
|
||||||
[&](const Callback& c) { return !c.isValid(); }),
|
[&](const Callback& c) { return !c.isValid(); }),
|
||||||
callbacks.end());
|
callbacks.end());
|
||||||
}
|
}
|
||||||
callbacks.push_back(callback);
|
callbacks.push_back(Callback::fromLua(callback));
|
||||||
};
|
};
|
||||||
sview["reset"] = [](const SectionView& section, const sol::optional<sol::table>& newValues)
|
sview["reset"] = [](const SectionView& section, const sol::optional<sol::table>& newValues)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
#include <boost/filesystem/path.hpp>
|
#include <boost/filesystem/path.hpp>
|
||||||
|
|
||||||
#include "scriptscontainer.hpp"
|
#include "asyncpackage.hpp"
|
||||||
#include "serialization.hpp"
|
#include "serialization.hpp"
|
||||||
|
|
||||||
namespace LuaUtil
|
namespace LuaUtil
|
||||||
|
|
|
@ -2,104 +2,21 @@
|
||||||
|
|
||||||
namespace LuaUi
|
namespace LuaUi
|
||||||
{
|
{
|
||||||
Content::Content(const sol::table& table)
|
sol::protected_function loadContentConstructor(LuaUtil::LuaState* state)
|
||||||
{
|
{
|
||||||
size_t size = table.size();
|
sol::function loader = state->loadInternalLib("content");
|
||||||
for (size_t index = 0; index < size; ++index)
|
sol::set_environment(state->newInternalLibEnvironment(), loader);
|
||||||
{
|
sol::table metatable = loader().get<sol::table>();
|
||||||
sol::object value = table.get<sol::object>(index + 1);
|
if (metatable["new"].get_type() != sol::type::function)
|
||||||
if (value.is<sol::table>())
|
throw std::logic_error("Expected function");
|
||||||
assign(index, value.as<sol::table>());
|
return metatable["new"].get<sol::protected_function>();
|
||||||
else
|
|
||||||
throw std::logic_error("UI Content children must all be tables.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Content::assign(size_t index, const sol::table& table)
|
bool isValidContent(const sol::object& object)
|
||||||
{
|
{
|
||||||
if (mOrdered.size() < index)
|
if (object.get_type() != sol::type::table)
|
||||||
throw std::logic_error("Can't have gaps in UI Content.");
|
return false;
|
||||||
if (index == mOrdered.size())
|
sol::table table = object;
|
||||||
mOrdered.push_back(table);
|
return table.traverse_get<sol::optional<bool>>(sol::metatable_key, "__Content").value_or(false);
|
||||||
else
|
|
||||||
{
|
|
||||||
sol::optional<std::string> oldName = mOrdered[index]["name"];
|
|
||||||
if (oldName.has_value())
|
|
||||||
mNamed.erase(oldName.value());
|
|
||||||
mOrdered[index] = table;
|
|
||||||
}
|
|
||||||
sol::optional<std::string> name = table["name"];
|
|
||||||
if (name.has_value())
|
|
||||||
mNamed[name.value()] = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Content::assign(std::string_view name, const sol::table& table)
|
|
||||||
{
|
|
||||||
auto it = mNamed.find(name);
|
|
||||||
if (it != mNamed.end())
|
|
||||||
assign(it->second, table);
|
|
||||||
else
|
|
||||||
throw std::logic_error(std::string("Can't find a UI Content child with name ") += name);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Content::insert(size_t index, const sol::table& table)
|
|
||||||
{
|
|
||||||
if (mOrdered.size() < index)
|
|
||||||
throw std::logic_error("Can't have gaps in UI Content.");
|
|
||||||
mOrdered.insert(mOrdered.begin() + index, table);
|
|
||||||
for (size_t i = index; i < mOrdered.size(); ++i)
|
|
||||||
{
|
|
||||||
sol::optional<std::string> name = mOrdered[i]["name"];
|
|
||||||
if (name.has_value())
|
|
||||||
mNamed[name.value()] = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sol::table Content::at(size_t index) const
|
|
||||||
{
|
|
||||||
if (index > size())
|
|
||||||
throw std::logic_error("Invalid UI Content index.");
|
|
||||||
return mOrdered.at(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
sol::table Content::at(std::string_view name) const
|
|
||||||
{
|
|
||||||
auto it = mNamed.find(name);
|
|
||||||
if (it == mNamed.end())
|
|
||||||
throw std::logic_error("Invalid UI Content name.");
|
|
||||||
return mOrdered.at(it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Content::remove(size_t index)
|
|
||||||
{
|
|
||||||
sol::table table = at(index);
|
|
||||||
sol::optional<std::string> name = table["name"];
|
|
||||||
if (name.has_value())
|
|
||||||
{
|
|
||||||
auto it = mNamed.find(name.value());
|
|
||||||
if (it != mNamed.end())
|
|
||||||
mNamed.erase(it);
|
|
||||||
}
|
|
||||||
mOrdered.erase(mOrdered.begin() + index);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Content::remove(std::string_view name)
|
|
||||||
{
|
|
||||||
auto it = mNamed.find(name);
|
|
||||||
if (it == mNamed.end())
|
|
||||||
throw std::logic_error("Invalid UI Content name.");
|
|
||||||
size_t index = it->second;
|
|
||||||
remove(index);
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t Content::indexOf(const sol::table& table)
|
|
||||||
{
|
|
||||||
auto it = std::find(mOrdered.begin(), mOrdered.end(), table);
|
|
||||||
if (it == mOrdered.end())
|
|
||||||
return size();
|
|
||||||
else
|
|
||||||
return it - mOrdered.begin();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,36 +6,105 @@
|
||||||
|
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
|
#include <components/lua/luastate.hpp>
|
||||||
|
|
||||||
namespace LuaUi
|
namespace LuaUi
|
||||||
{
|
{
|
||||||
class Content
|
sol::protected_function loadContentConstructor(LuaUtil::LuaState* state);
|
||||||
|
|
||||||
|
bool isValidContent(const sol::object& object);
|
||||||
|
|
||||||
|
class ContentView
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using iterator = std::vector<sol::table>::iterator;
|
// accepts only Lua tables returned by ui.content
|
||||||
|
explicit ContentView(sol::table table)
|
||||||
|
: mTable(std::move(table))
|
||||||
|
{
|
||||||
|
if (!isValidContent(mTable))
|
||||||
|
throw std::domain_error("Expected a Content table");
|
||||||
|
}
|
||||||
|
|
||||||
Content() {}
|
size_t size() const { return mTable.size(); }
|
||||||
|
|
||||||
// expects a Lua array - a table with keys from 1 to n without any nil values in between
|
void assign(std::string_view name, const sol::table& table)
|
||||||
// any other keys are ignored
|
{
|
||||||
explicit Content(const sol::table&);
|
if (indexOf(name).has_value())
|
||||||
|
mTable[name] = table;
|
||||||
|
else
|
||||||
|
throw std::domain_error("Invalid Content key");
|
||||||
|
}
|
||||||
|
void assign(size_t index, const sol::table& table)
|
||||||
|
{
|
||||||
|
if (index <= size())
|
||||||
|
mTable[toLua(index)] = table;
|
||||||
|
else
|
||||||
|
throw std::range_error("Invalid Content index");
|
||||||
|
}
|
||||||
|
void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); }
|
||||||
|
|
||||||
size_t size() const { return mOrdered.size(); }
|
sol::table at(size_t index) const
|
||||||
|
{
|
||||||
|
if (index < size())
|
||||||
|
return mTable.get<sol::table>(toLua(index));
|
||||||
|
else
|
||||||
|
throw std::range_error("Invalid Content index");
|
||||||
|
}
|
||||||
|
sol::table at(std::string_view name) const
|
||||||
|
{
|
||||||
|
if (indexOf(name).has_value())
|
||||||
|
return mTable.get<sol::table>(name);
|
||||||
|
else
|
||||||
|
throw std::range_error("Invalid Content key");
|
||||||
|
}
|
||||||
|
void remove(size_t index)
|
||||||
|
{
|
||||||
|
if (index < size())
|
||||||
|
// for some reason mTable[key] = value doesn't call __newindex
|
||||||
|
getMetatable()[sol::meta_function::new_index].get<sol::protected_function>()(
|
||||||
|
mTable, toLua(index), sol::nil);
|
||||||
|
else
|
||||||
|
throw std::range_error("Invalid Content index");
|
||||||
|
}
|
||||||
|
void remove(std::string_view name)
|
||||||
|
{
|
||||||
|
auto index = indexOf(name);
|
||||||
|
if (index.has_value())
|
||||||
|
remove(index.value());
|
||||||
|
else
|
||||||
|
throw std::domain_error("Invalid Content key");
|
||||||
|
}
|
||||||
|
std::optional<size_t> indexOf(std::string_view name) const
|
||||||
|
{
|
||||||
|
sol::object result = callMethod("indexOf", name);
|
||||||
|
if (result.is<size_t>())
|
||||||
|
return fromLua(result.as<size_t>());
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional<size_t> indexOf(const sol::table& table) const
|
||||||
|
{
|
||||||
|
sol::object result = callMethod("indexOf", table);
|
||||||
|
if (result.is<size_t>())
|
||||||
|
return fromLua(result.as<size_t>());
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
void assign(std::string_view name, const sol::table& table);
|
sol::table getMetatable() const { return mTable[sol::metatable_key].get<sol::table>(); }
|
||||||
void assign(size_t index, const sol::table& table);
|
|
||||||
void insert(size_t index, const sol::table& table);
|
|
||||||
|
|
||||||
sol::table at(size_t index) const;
|
private:
|
||||||
sol::table at(std::string_view name) const;
|
sol::table mTable;
|
||||||
size_t remove(size_t index);
|
|
||||||
size_t remove(std::string_view name);
|
|
||||||
size_t indexOf(const sol::table& table);
|
|
||||||
|
|
||||||
private:
|
template <typename... Arg>
|
||||||
std::map<std::string, size_t, std::less<>> mNamed;
|
sol::object callMethod(std::string_view name, Arg&&... arg) const
|
||||||
std::vector<sol::table> mOrdered;
|
{
|
||||||
|
return mTable.get<sol::protected_function>(name)(mTable, arg...);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline size_t toLua(size_t index) { return index + 1; }
|
||||||
|
static inline size_t fromLua(size_t index) { return index - 1; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // COMPONENTS_LUAUI_CONTENT
|
#endif // COMPONENTS_LUAUI_CONTENT
|
||||||
|
|
140
components/lua_ui/content.lua
Normal file
140
components/lua_ui/content.lua
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
local M = {}
|
||||||
|
M.__Content = true
|
||||||
|
M.new = function(source)
|
||||||
|
local result = {}
|
||||||
|
result.__nameIndex = {}
|
||||||
|
for i, v in ipairs(source) do
|
||||||
|
if type(v) ~= 'table' then
|
||||||
|
error('Content can only contain tables')
|
||||||
|
end
|
||||||
|
result[i] = v
|
||||||
|
if type(v.name) == 'string' then
|
||||||
|
result.__nameIndex[v.name] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return setmetatable(result, M)
|
||||||
|
end
|
||||||
|
local function validateIndex(self, index)
|
||||||
|
if type(index) ~= 'number' then
|
||||||
|
error('Unexpected Content key: ' .. tostring(index))
|
||||||
|
end
|
||||||
|
if index < 1 or (#self + 1) < index then
|
||||||
|
error('Invalid Content index: ' .. tostring(index))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function getIndexFromKey(self, key)
|
||||||
|
local index = key
|
||||||
|
if type(key) == 'string' then
|
||||||
|
index = self.__nameIndex[key]
|
||||||
|
if not index then
|
||||||
|
error('Unexpected content key:' .. key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
validateIndex(self, index)
|
||||||
|
return index
|
||||||
|
end
|
||||||
|
|
||||||
|
local methods = {
|
||||||
|
insert = function(self, index, value)
|
||||||
|
validateIndex(self, index)
|
||||||
|
if type(value) ~= 'table' then
|
||||||
|
error('Content can only contain tables')
|
||||||
|
end
|
||||||
|
for i = #self, index, -1 do
|
||||||
|
rawset(self, i + 1, rawget(self, i))
|
||||||
|
local name = rawget(self, i + 1)
|
||||||
|
if name then
|
||||||
|
self.__nameIndex[name] = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rawset(self, index, value)
|
||||||
|
if value.name then
|
||||||
|
self.__nameIndex[value.name] = index
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
indexOf = function(self, value)
|
||||||
|
if type(value) == 'string' then
|
||||||
|
return self.__nameIndex[value]
|
||||||
|
elseif type(value) == 'table' then
|
||||||
|
for i = 1, #self do
|
||||||
|
if rawget(self, i) == value then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
|
end,
|
||||||
|
add = function(self, value)
|
||||||
|
self:insert(#self + 1, value)
|
||||||
|
return #self
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
M.__index = function(self, key)
|
||||||
|
if methods[key] then return methods[key] end
|
||||||
|
local index = getIndexFromKey(self, key)
|
||||||
|
return rawget(self, index)
|
||||||
|
end
|
||||||
|
local function nameAt(self, index)
|
||||||
|
local v = rawget(self, index)
|
||||||
|
return v and type(v.name) == 'string' and v.name
|
||||||
|
end
|
||||||
|
|
||||||
|
local function remove(self, index)
|
||||||
|
local oldName = nameAt(self, index)
|
||||||
|
if oldName then
|
||||||
|
self.__nameIndex[oldName] = nil
|
||||||
|
end
|
||||||
|
if index > #self then
|
||||||
|
error('Invalid Content index:' .. tostring(index))
|
||||||
|
end
|
||||||
|
for i = index, #self - 1 do
|
||||||
|
local v = rawget(self, i + 1)
|
||||||
|
rawset(self, i, v)
|
||||||
|
if type(v.name) == 'string' then
|
||||||
|
self.__nameIndex[v.name] = i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rawset(self, #self, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function assign(self, index, value)
|
||||||
|
local oldName = nameAt(self, index)
|
||||||
|
if oldName then
|
||||||
|
self.__nameIndex[oldName] = nil
|
||||||
|
end
|
||||||
|
rawset(self, index, value)
|
||||||
|
if value.name then
|
||||||
|
self.__nameIndex[value.name] = index
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.__newindex = function(self, key, value)
|
||||||
|
local index = getIndexFromKey(self, key)
|
||||||
|
if value == nil then
|
||||||
|
remove(self, index)
|
||||||
|
elseif type(value) == 'table' then
|
||||||
|
assign(self, index, value)
|
||||||
|
else
|
||||||
|
error('Content can only contain tables')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
M.__tostring = function(self)
|
||||||
|
return ('UiContent{%d layouts}'):format(#self)
|
||||||
|
end
|
||||||
|
local function next(self, index)
|
||||||
|
local v = rawget(self, index)
|
||||||
|
if v then
|
||||||
|
return index + 1, v
|
||||||
|
else
|
||||||
|
return nil, nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M.__pairs = function(self)
|
||||||
|
return next, self, 1
|
||||||
|
end
|
||||||
|
M.__ipairs = M.__pairs
|
||||||
|
M.__metatable = false
|
||||||
|
|
||||||
|
return M
|
|
@ -62,9 +62,7 @@ namespace LuaUi
|
||||||
destroyWidget(w);
|
destroyWidget(w);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (!contentObj.is<Content>())
|
ContentView content(contentObj.as<sol::table>());
|
||||||
throw std::logic_error("Layout content field must be a openmw.ui.content");
|
|
||||||
Content content = contentObj.as<Content>();
|
|
||||||
result.resize(content.size());
|
result.resize(content.size());
|
||||||
size_t minSize = std::min(children.size(), content.size());
|
size_t minSize = std::min(children.size(), content.size());
|
||||||
for (size_t i = 0; i < minSize; i++)
|
for (size_t i = 0; i < minSize; i++)
|
||||||
|
@ -106,13 +104,12 @@ namespace LuaUi
|
||||||
if (!eventsObj.is<sol::table>())
|
if (!eventsObj.is<sol::table>())
|
||||||
throw std::logic_error("The \"events\" layout field must be a table of callbacks");
|
throw std::logic_error("The \"events\" layout field must be a table of callbacks");
|
||||||
auto events = eventsObj.as<sol::table>();
|
auto events = eventsObj.as<sol::table>();
|
||||||
events.for_each([ext](const sol::object& name, const sol::object& callback)
|
events.for_each([ext](const sol::object& name, const sol::object& callback) {
|
||||||
{
|
if (name.is<std::string>() && LuaUtil::Callback::isLuaCallback(callback))
|
||||||
if (name.is<std::string>() && callback.is<LuaUtil::Callback>())
|
ext->setCallback(name.as<std::string>(), LuaUtil::Callback::fromLua(callback));
|
||||||
ext->setCallback(name.as<std::string>(), callback.as<LuaUtil::Callback>());
|
|
||||||
else if (!name.is<std::string>())
|
else if (!name.is<std::string>())
|
||||||
Log(Debug::Warning) << "UI event key must be a string";
|
Log(Debug::Warning) << "UI event key must be a string";
|
||||||
else if (!callback.is<LuaUtil::Callback>())
|
else
|
||||||
Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
|
Log(Debug::Warning) << "UI event handler for key \"" << name.as<std::string>()
|
||||||
<< "\" must be an openmw.async.callback";
|
<< "\" must be an openmw.async.callback";
|
||||||
});
|
});
|
||||||
|
@ -193,8 +190,8 @@ namespace LuaUi
|
||||||
assert(!mRoot);
|
assert(!mRoot);
|
||||||
if (!mRoot)
|
if (!mRoot)
|
||||||
{
|
{
|
||||||
mRoot = createWidget(mLayout, 0);
|
mRoot = createWidget(layout(), 0);
|
||||||
mLayer = setLayer(mRoot, mLayout);
|
mLayer = setLayer(mRoot, layout());
|
||||||
updateAttachment();
|
updateAttachment();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -203,16 +200,16 @@ namespace LuaUi
|
||||||
{
|
{
|
||||||
if (mRoot && mUpdate)
|
if (mRoot && mUpdate)
|
||||||
{
|
{
|
||||||
if (mRoot->widget()->getTypeName() != widgetType(mLayout))
|
if (mRoot->widget()->getTypeName() != widgetType(layout()))
|
||||||
{
|
{
|
||||||
destroyWidget(mRoot);
|
destroyWidget(mRoot);
|
||||||
mRoot = createWidget(mLayout, 0);
|
mRoot = createWidget(layout(), 0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
updateWidget(mRoot, mLayout, 0);
|
updateWidget(mRoot, layout(), 0);
|
||||||
}
|
}
|
||||||
mLayer = setLayer(mRoot, mLayout);
|
mLayer = setLayer(mRoot, layout());
|
||||||
updateAttachment();
|
updateAttachment();
|
||||||
}
|
}
|
||||||
mUpdate = false;
|
mUpdate = false;
|
||||||
|
@ -220,9 +217,11 @@ namespace LuaUi
|
||||||
|
|
||||||
void Element::destroy()
|
void Element::destroy()
|
||||||
{
|
{
|
||||||
if (mRoot)
|
if (!mRoot)
|
||||||
destroyWidget(mRoot);
|
return;
|
||||||
|
destroyWidget(mRoot);
|
||||||
mRoot = nullptr;
|
mRoot = nullptr;
|
||||||
|
mLayout = sol::make_object(mLayout.lua_state(), sol::nil);
|
||||||
sAllElements.erase(this);
|
sAllElements.erase(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ namespace LuaUi
|
||||||
|
|
||||||
WidgetExtension* mRoot;
|
WidgetExtension* mRoot;
|
||||||
WidgetExtension* mAttachedTo;
|
WidgetExtension* mAttachedTo;
|
||||||
sol::table mLayout;
|
sol::object mLayout;
|
||||||
std::string mLayer;
|
std::string mLayer;
|
||||||
bool mUpdate;
|
bool mUpdate;
|
||||||
bool mDestroy;
|
bool mDestroy;
|
||||||
|
@ -34,10 +34,11 @@ namespace LuaUi
|
||||||
void attachToWidget(WidgetExtension* w);
|
void attachToWidget(WidgetExtension* w);
|
||||||
void detachFromWidget();
|
void detachFromWidget();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Element(sol::table layout);
|
Element(sol::table layout);
|
||||||
static std::map<Element*, std::shared_ptr<Element>> sAllElements;
|
sol::table layout() { return mLayout.as<sol::table>(); }
|
||||||
void updateAttachment();
|
static std::map<Element*, std::shared_ptr<Element>> sAllElements;
|
||||||
|
void updateAttachment();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include <MyGUI_Widget.h>
|
#include <MyGUI_Widget.h>
|
||||||
#include <sol/sol.hpp>
|
#include <sol/sol.hpp>
|
||||||
|
|
||||||
#include <components/lua/scriptscontainer.hpp>
|
#include <components/lua/asyncpackage.hpp>
|
||||||
|
|
||||||
#include "properties.hpp"
|
#include "properties.hpp"
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
if (NOT DEFINED OPENMW_RESOURCES_ROOT)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(LUA_API_FILES
|
set(LUA_API_FILES
|
||||||
README.md
|
README.md
|
||||||
coroutine.doclua
|
coroutine.doclua
|
||||||
|
@ -18,5 +22,5 @@ set(LUA_API_FILES
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach (f ${LUA_API_FILES})
|
foreach (f ${LUA_API_FILES})
|
||||||
copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OpenMW_BINARY_DIR}" "resources/lua_api/${f}")
|
copy_resource_file("${CMAKE_CURRENT_SOURCE_DIR}/${f}" "${OPENMW_RESOURCES_ROOT}" "resources/lua_api/${f}")
|
||||||
endforeach (f)
|
endforeach (f)
|
||||||
|
|
|
@ -172,6 +172,12 @@
|
||||||
-- for i = 1, #content do
|
-- for i = 1, #content do
|
||||||
-- print('widget',content[i].name,'at',i)
|
-- print('widget',content[i].name,'at',i)
|
||||||
-- end
|
-- end
|
||||||
|
-- @usage
|
||||||
|
-- -- Note: layout names can collide with method names. Because of that you can't use a layout name such as "insert":
|
||||||
|
-- local content = ui.content {
|
||||||
|
-- { name = 'insert '}
|
||||||
|
-- }
|
||||||
|
-- content.insert.content = ui.content {} -- fails here, content.insert is a function!
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Puts the layout at given index by shifting all the elements after it
|
-- Puts the layout at given index by shifting all the elements after it
|
||||||
|
@ -210,6 +216,7 @@
|
||||||
|
|
||||||
---
|
---
|
||||||
-- Access or replace the element's layout
|
-- Access or replace the element's layout
|
||||||
|
-- Note: Is reset to `nil` on `destroy`
|
||||||
-- @field [parent=#Element] #Layout layout
|
-- @field [parent=#Element] #Layout layout
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
Loading…
Reference in a new issue