1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-30 03:32:36 +00:00

Add components/lua/luastate and components/lua/utilpackage

This commit is contained in:
Petr Mikheev 2021-01-12 23:17:48 +01:00
parent b432a1a1c1
commit 4b068b27ca
9 changed files with 701 additions and 1 deletions

View File

@ -15,6 +15,9 @@ if (GTEST_FOUND AND GMOCK_FOUND)
esm/test_fixed_string.cpp
esm/variant.cpp
lua/test_lua.cpp
lua/test_utilpackage.cpp
misc/test_stringops.cpp
misc/test_endianness.cpp
@ -39,7 +42,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES})
target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components)
target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components ${LUA_LIBRARIES})
# Fix for not visible pthreads functions for linker with glibc 2.15
if (UNIX AND NOT APPLE)
target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT})

View File

@ -0,0 +1,167 @@
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <components/lua/luastate.hpp>
#include "testing_util.hpp"
namespace
{
using namespace testing;
TestFile counterFile(R"X(
x = 42
return {
get = function() return x end,
inc = function(v) x = x + v end
}
)X");
TestFile invalidScriptFile("Invalid script");
TestFile testsFile(R"X(
return {
-- should work
sin = function(x) return math.sin(x) end,
requireMathSin = function(x) return require('math').sin(x) end,
useCounter = function()
local counter = require('aaa.counter')
counter.inc(1)
return counter.get()
end,
callRawset = function()
t = {a = 1, b = 2}
rawset(t, 'b', 3)
return t.b
end,
print = print,
-- should throw an error
incorrectRequire = function() require('counter') end,
modifySystemLib = function() math.sin = 5 end,
rawsetSystemLib = function() rawset(math, 'sin', 5) end,
callLoadstring = function() loadstring('print(1)') end,
setSqr = function() require('sqrlib').sqr = math.sin end,
setOmwName = function() require('openmw').name = 'abc' end,
-- should work if API is registered
sqr = function(x) return require('sqrlib').sqr(x) end,
apiName = function() return require('test.api').name end
}
)X");
struct LuaStateTest : Test
{
std::unique_ptr<VFS::Manager> mVFS = createTestVFS({
{"aaa/counter.lua", &counterFile},
{"bbb/tests.lua", &testsFile},
{"invalid.lua", &invalidScriptFile}
});
LuaUtil::LuaState mLua{mVFS.get()};
};
TEST_F(LuaStateTest, Sandbox)
{
sol::table script1 = mLua.runInNewSandbox("aaa/counter.lua");
EXPECT_EQ(LuaUtil::call(script1["get"]).get<int>(), 42);
LuaUtil::call(script1["inc"], 3);
EXPECT_EQ(LuaUtil::call(script1["get"]).get<int>(), 45);
sol::table script2 = mLua.runInNewSandbox("aaa/counter.lua");
EXPECT_EQ(LuaUtil::call(script2["get"]).get<int>(), 42);
LuaUtil::call(script2["inc"], 1);
EXPECT_EQ(LuaUtil::call(script2["get"]).get<int>(), 43);
EXPECT_EQ(LuaUtil::call(script1["get"]).get<int>(), 45);
}
TEST_F(LuaStateTest, ErrorHandling)
{
EXPECT_ERROR(mLua.runInNewSandbox("invalid.lua"), "[string \"invalid.lua\"]:1:");
}
TEST_F(LuaStateTest, CustomRequire)
{
sol::table script = mLua.runInNewSandbox("bbb/tests.lua");
EXPECT_FLOAT_EQ(LuaUtil::call(script["sin"], 1).get<float>(),
-LuaUtil::call(script["requireMathSin"], -1).get<float>());
EXPECT_EQ(LuaUtil::call(script["useCounter"]).get<int>(), 43);
EXPECT_EQ(LuaUtil::call(script["useCounter"]).get<int>(), 44);
{
sol::table script2 = mLua.runInNewSandbox("bbb/tests.lua");
EXPECT_EQ(LuaUtil::call(script2["useCounter"]).get<int>(), 43);
}
EXPECT_EQ(LuaUtil::call(script["useCounter"]).get<int>(), 45);
EXPECT_ERROR(LuaUtil::call(script["incorrectRequire"]), "Resource 'counter.lua' not found");
}
TEST_F(LuaStateTest, ReadOnly)
{
sol::table script = mLua.runInNewSandbox("bbb/tests.lua");
// rawset itself is allowed
EXPECT_EQ(LuaUtil::call(script["callRawset"]).get<int>(), 3);
// but read-only object can not be modified even with rawset
EXPECT_ERROR(LuaUtil::call(script["rawsetSystemLib"]), "bad argument #1 to 'rawset' (table expected, got userdata)");
EXPECT_ERROR(LuaUtil::call(script["modifySystemLib"]), "a userdata value");
EXPECT_EQ(mLua.getMutableFromReadOnly(mLua.makeReadOnly(script)), script);
}
TEST_F(LuaStateTest, Print)
{
{
sol::table script = mLua.runInNewSandbox("bbb/tests.lua");
testing::internal::CaptureStdout();
LuaUtil::call(script["print"], 1, 2, 3);
std::string output = testing::internal::GetCapturedStdout();
EXPECT_EQ(output, "[bbb/tests.lua]:\t1\t2\t3\n");
}
{
sol::table script = mLua.runInNewSandbox("bbb/tests.lua", "prefix");
testing::internal::CaptureStdout();
LuaUtil::call(script["print"]); // print with no arguments
std::string output = testing::internal::GetCapturedStdout();
EXPECT_EQ(output, "prefix[bbb/tests.lua]:\n");
}
}
TEST_F(LuaStateTest, UnsafeFunction)
{
sol::table script = mLua.runInNewSandbox("bbb/tests.lua");
EXPECT_ERROR(LuaUtil::call(script["callLoadstring"]), "a nil value");
}
TEST_F(LuaStateTest, ProvideAPI)
{
LuaUtil::LuaState lua(mVFS.get());
sol::table api1 = lua.makeReadOnly(lua.sol().create_table_with("name", "api1"));
sol::table api2 = lua.makeReadOnly(lua.sol().create_table_with("name", "api2"));
sol::table script1 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api1}});
lua.addCommonPackage(
"sqrlib", lua.sol().create_table_with("sqr", [](int x) { return x * x; }));
sol::table script2 = lua.runInNewSandbox("bbb/tests.lua", "", {{"test.api", api2}});
EXPECT_ERROR(LuaUtil::call(script1["sqr"], 3), "Resource 'sqrlib.lua' not found");
EXPECT_EQ(LuaUtil::call(script2["sqr"], 3).get<int>(), 9);
EXPECT_EQ(LuaUtil::call(script1["apiName"]).get<std::string>(), "api1");
EXPECT_EQ(LuaUtil::call(script2["apiName"]).get<std::string>(), "api2");
}
TEST_F(LuaStateTest, GetLuaVersion)
{
EXPECT_THAT(LuaUtil::getLuaVersion(), HasSubstr("Lua"));
}
}

View File

@ -0,0 +1,80 @@
#include "gmock/gmock.h"
#include <gtest/gtest.h>
#include <components/lua/utilpackage.hpp>
#include "testing_util.hpp"
namespace
{
using namespace testing;
TEST(LuaUtilPackageTest, Vector2)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
lua.safe_script("v = util.vector2(3, 4)");
EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get<float>(), 3);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 4);
EXPECT_EQ(lua.safe_script("return tostring(v)").get<std::string>(), "(3, 4)");
EXPECT_FLOAT_EQ(lua.safe_script("return v:length()").get<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return v:length2()").get<float>(), 25);
EXPECT_FALSE(lua.safe_script("return util.vector2(1, 2) == util.vector2(1, 3)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) + util.vector2(2, 5) == util.vector2(3, 7)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) - util.vector2(2, 5) == -util.vector2(1, 3)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) == util.vector2(2, 4) / 2").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector2(1, 2) * 2 == util.vector2(2, 4)").get<bool>());
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2) * v").get<float>(), 17);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector2(3, 2):dot(v)").get<float>(), 17);
EXPECT_ERROR(lua.safe_script("v2, len = v.normalize()"), "value is not a valid userdata"); // checks that it doesn't segfault
lua.safe_script("v2, len = v:normalize()");
EXPECT_FLOAT_EQ(lua.safe_script("return len").get<float>(), 5);
EXPECT_TRUE(lua.safe_script("return v2 == util.vector2(3/5, 4/5)").get<bool>());
lua.safe_script("_, len = util.vector2(0, 0):normalize()");
EXPECT_FLOAT_EQ(lua.safe_script("return len").get<float>(), 0);
}
TEST(LuaUtilPackageTest, Vector3)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
lua.safe_script("v = util.vector3(5, 12, 13)");
EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 12);
EXPECT_FLOAT_EQ(lua.safe_script("return v.z").get<float>(), 13);
EXPECT_EQ(lua.safe_script("return tostring(v)").get<std::string>(), "(5, 12, 13)");
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length()").get<float>(), 5);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(4, 0, 3):length2()").get<float>(), 25);
EXPECT_FALSE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(1, 3, 2)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) + util.vector3(2, 5, 1) == util.vector3(3, 7, 4)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) - util.vector3(2, 5, 1) == -util.vector3(1, 3, -2)").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) == util.vector3(2, 4, 6) / 2").get<bool>());
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 2, 3) * 2 == util.vector3(2, 4, 6)").get<bool>());
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1) * v").get<float>(), 5*3 + 12*2 + 13*1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.vector3(3, 2, 1):dot(v)").get<float>(), 5*3 + 12*2 + 13*1);
EXPECT_TRUE(lua.safe_script("return util.vector3(1, 0, 0) ^ util.vector3(0, 1, 0) == util.vector3(0, 0, 1)").get<bool>());
EXPECT_ERROR(lua.safe_script("v2, len = util.vector3(3, 4, 0).normalize()"), "value is not a valid userdata");
lua.safe_script("v2, len = util.vector3(3, 4, 0):normalize()");
EXPECT_FLOAT_EQ(lua.safe_script("return len").get<float>(), 5);
EXPECT_TRUE(lua.safe_script("return v2 == util.vector3(3/5, 4/5, 0)").get<bool>());
lua.safe_script("_, len = util.vector3(0, 0, 0):normalize()");
EXPECT_FLOAT_EQ(lua.safe_script("return len").get<float>(), 0);
}
TEST(LuaUtilPackageTest, UtilityFunctions)
{
sol::state lua;
lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string);
lua["util"] = LuaUtil::initUtilPackage(lua);
lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))");
EXPECT_FLOAT_EQ(lua.safe_script("return v.x").get<float>(), -0.5);
EXPECT_FLOAT_EQ(lua.safe_script("return v.y").get<float>(), 0.86602539);
EXPECT_FLOAT_EQ(lua.safe_script("return util.normalizeAngle(math.pi * 10 + 0.1)").get<float>(), 0.1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(0.1, 0, 1.5)").get<float>(), 0.1);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(-0.1, 0, 1.5)").get<float>(), 0);
EXPECT_FLOAT_EQ(lua.safe_script("return util.clamp(2.1, 0, 1.5)").get<float>(), 1.5);
}
}

View File

@ -0,0 +1,59 @@
#ifndef LUA_TESTING_UTIL_H
#define LUA_TESTING_UTIL_H
#include <sstream>
#include <components/vfs/archive.hpp>
#include <components/vfs/manager.hpp>
namespace
{
class TestFile : public VFS::File
{
public:
explicit TestFile(std::string content) : mContent(std::move(content)) {}
Files::IStreamPtr open() override
{
return std::make_shared<std::stringstream>(mContent, std::ios_base::in);
}
private:
const std::string mContent;
};
struct TestData : public VFS::Archive
{
std::map<std::string, VFS::File*> mFiles;
TestData(std::map<std::string, VFS::File*> files) : mFiles(std::move(files)) {}
void listResources(std::map<std::string, VFS::File*>& out, char (*normalize_function) (char)) override
{
out = mFiles;
}
bool contains(const std::string& file, char (*normalize_function) (char)) const override
{
return mFiles.count(file) != 0;
}
std::string getDescription() const override { return "TestData"; }
};
inline std::unique_ptr<VFS::Manager> createTestVFS(std::map<std::string, VFS::File*> files)
{
auto vfs = std::make_unique<VFS::Manager>(true);
vfs->addArchive(new TestData(std::move(files)));
vfs->buildIndex();
return vfs;
}
#define EXPECT_ERROR(X, ERR_SUBSTR) try { X; FAIL() << "Expected error"; } \
catch (std::exception& e) { EXPECT_THAT(e.what(), HasSubstr(ERR_SUBSTR)); }
}
#endif // LUA_TESTING_UTIL_H

View File

@ -28,6 +28,10 @@ endif (GIT_CHECKOUT)
# source files
add_component_dir (lua
luastate utilpackage
)
add_component_dir (settings
settings parser
)

169
components/lua/luastate.cpp Normal file
View File

@ -0,0 +1,169 @@
#include "luastate.hpp"
#ifndef NO_LUAJIT
#include <luajit.h>
#endif // NO_LUAJIT
#include <components/debug/debuglog.hpp>
namespace LuaUtil
{
static std::string packageNameToPath(std::string_view packageName)
{
std::string res(packageName);
for (size_t i = 0; i < res.size(); ++i)
if (res[i] == '.')
res[i] = '/';
res.append(".lua");
return res;
}
static const std::string safeFunctions[] = {
"assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring",
"type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"};
static const std::string safePackages[] = {"coroutine", "math", "string", "table"};
LuaState::LuaState(const VFS::Manager* vfs) : mVFS(vfs)
{
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::string, sol::lib::table);
mLua["math"]["randomseed"](static_cast<unsigned>(time(NULL)));
mLua["math"]["randomseed"] = sol::nil;
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
mLua.script(R"(printToLog = function(name, ...)
local msg = name
for _, v in ipairs({...}) do
msg = msg .. '\t' .. tostring(v)
end
return writeToLog(msg)
end)");
mLua.script("printGen = function(name) return function(...) return printToLog(name, ...) end end");
// Some fixes for compatibility between different Lua versions
if (mLua["unpack"] == sol::nil)
mLua["unpack"] = mLua["table"]["unpack"];
else if (mLua["table"]["unpack"] == sol::nil)
mLua["table"]["unpack"] = mLua["unpack"];
mSandboxEnv = sol::table(mLua, sol::create);
mSandboxEnv["_VERSION"] = mLua["_VERSION"];
for (const std::string& s : safeFunctions)
{
if (mLua[s] == sol::nil) throw std::logic_error("Lua function not found: " + s);
mSandboxEnv[s] = mLua[s];
}
for (const std::string& s : safePackages)
{
if (mLua[s] == sol::nil) throw std::logic_error("Lua package not found: " + s);
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]);
}
}
LuaState::~LuaState()
{
// Should be cleaned before destructing mLua.
mCommonPackages.clear();
mSandboxEnv = sol::nil;
}
sol::table LuaState::makeReadOnly(sol::table table)
{
if (table.is<sol::userdata>())
return table; // it is already userdata, no sense to wrap it again
table[sol::meta_function::index] = table;
sol::stack::push(mLua, std::move(table));
lua_newuserdata(mLua, 0);
lua_pushvalue(mLua, -2);
lua_setmetatable(mLua, -2);
return sol::stack::pop<sol::table>(mLua);
}
sol::table LuaState::getMutableFromReadOnly(const sol::userdata& ro)
{
sol::stack::push(mLua, ro);
lua_getmetatable(mLua, -1);
sol::table res = sol::stack::pop<sol::table>(mLua);
lua_pop(mLua, 1);
return res;
}
void LuaState::addCommonPackage(const std::string& packageName, const sol::object& package)
{
if (package.is<sol::function>())
mCommonPackages[packageName] = package;
else
mCommonPackages[packageName] = makeReadOnly(package);
}
sol::protected_function_result LuaState::runInNewSandbox(
const std::string& path, const std::string& namePrefix,
const std::map<std::string, sol::object>& packages, const sol::object& hiddenData)
{
sol::protected_function script = loadScript(path);
sol::environment env(mLua, sol::create, mSandboxEnv);
std::string envName = namePrefix + "[" + path + "]:";
env["print"] = mLua["printGen"](envName);
sol::table loaded(mLua, sol::create);
for (const auto& [key, value] : mCommonPackages)
loaded[key] = value;
for (const auto& [key, value] : packages)
loaded[key] = value;
env["require"] = [this, env, loaded, hiddenData](std::string_view packageName)
{
sol::table packages = loaded;
sol::object package = packages[packageName];
if (package == sol::nil)
{
sol::protected_function packageLoader = loadScript(packageNameToPath(packageName));
sol::set_environment(env, packageLoader);
package = throwIfError(packageLoader());
if (!package.is<sol::table>())
throw std::runtime_error("Lua package must return a table.");
packages[packageName] = package;
}
else if (package.is<sol::function>())
package = packages[packageName] = call(package.as<sol::protected_function>(), hiddenData);
return package;
};
sol::set_environment(env, script);
return call(script);
}
sol::protected_function_result LuaState::throwIfError(sol::protected_function_result&& res)
{
if (!res.valid() && static_cast<int>(res.get_type()) == LUA_TSTRING)
throw std::runtime_error("Lua error: " + res.get<std::string>());
else
return std::move(res);
}
sol::protected_function LuaState::loadScript(const std::string& path)
{
auto iter = mCompiledScripts.find(path);
if (iter != mCompiledScripts.end())
return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary);
std::string fileContent(std::istreambuf_iterator<char>(*mVFS->get(path)), {});
sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text);
if (!res.valid())
throw std::runtime_error("Lua error: " + res.get<std::string>());
mCompiledScripts[path] = res.get<sol::function>().dump();
return res;
}
std::string getLuaVersion()
{
#ifdef NO_LUAJIT
return LUA_RELEASE;
#else
return LUA_RELEASE " (" LUAJIT_VERSION ")";
#endif
}
}

107
components/lua/luastate.hpp Normal file
View File

@ -0,0 +1,107 @@
#ifndef COMPONENTS_LUA_LUASTATE_H
#define COMPONENTS_LUA_LUASTATE_H
#include <map>
#include <sol/sol.hpp>
#include <components/vfs/manager.hpp>
namespace LuaUtil
{
std::string getLuaVersion();
// Holds Lua state.
// Provides additional features:
// - Load scripts from the virtual filesystem;
// - Caching of loaded scripts;
// - Disable unsafe Lua functions;
// - Run every instance of every script in a separate sandbox;
// - Forbid any interactions between sandboxes except than via provided API;
// - Access to common read-only resources from different sandboxes;
// - Replace standard `require` with a safe version that allows to search
// Lua libraries (only source, no dll's) in the virtual filesystem;
// - Make `print` to add the script name to the every message and
// write to Log rather than directly to stdout;
class LuaState
{
public:
explicit LuaState(const VFS::Manager* vfs);
~LuaState();
// Returns underlying sol::state.
sol::state& sol() { return mLua; }
// A shortcut to create a new Lua table.
sol::table newTable() { return sol::table(mLua, sol::create); }
// Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata.
// Needed to forbid any changes in common resources that can accessed from different sandboxes.
sol::table makeReadOnly(sol::table);
sol::table getMutableFromReadOnly(const sol::userdata&);
// Registers a package that will be available from every sandbox via `require(name)`.
// The package can be either a sol::table with an API or a sol::function. If it is a function,
// it will be evaluated (once per sandbox) the first time when requested. If the package
// is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains).
void addCommonPackage(const std::string& packageName, const sol::object& package);
// Creates a new sandbox, runs a script, and returns the result
// (the result is expected to be an interface of the script).
// Args:
// path: path to the script in the virtual filesystem;
// namePrefix: sandbox name will be "<namePrefix>[<filePath>]". Sandbox name
// will be added to every `print` output.
// packages: additional packages that should be available from the sandbox via `require`. Each package
// should be either a sol::table or a sol::function. If it is a function, it will be evaluated
// (once per sandbox) with the argument 'hiddenData' the first time when requested.
sol::protected_function_result runInNewSandbox(const std::string& path,
const std::string& namePrefix = "",
const std::map<std::string, sol::object>& packages = {},
const sol::object& hiddenData = sol::nil);
void dropScriptCache() { mCompiledScripts.clear(); }
private:
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
template <typename... Args>
friend sol::protected_function_result call(sol::protected_function fn, Args&&... args);
sol::protected_function loadScript(const std::string& path);
sol::state mLua;
sol::table mSandboxEnv;
std::map<std::string, sol::bytecode> mCompiledScripts;
std::map<std::string, sol::object> mCommonPackages;
const VFS::Manager* mVFS;
};
// Should be used for every call of every Lua function.
// It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078
template <typename... Args>
sol::protected_function_result call(sol::protected_function fn, Args&&... args)
{
try
{
return LuaState::throwIfError(fn(std::forward<Args>(args)...));
}
catch (std::exception&) { throw; }
catch (...) { throw std::runtime_error("Unknown error"); }
}
// getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist.
template <class... Str>
sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str)
{
if (!table.is<sol::table>())
return sol::nil;
if constexpr (sizeof...(str) == 0)
return table.as<sol::table>()[first];
else
return getFieldOrNil(table.as<sol::table>()[first], str...);
}
}
#endif // COMPONENTS_LUA_LUASTATE_H

View File

@ -0,0 +1,98 @@
#include "utilpackage.hpp"
#include <algorithm>
#include <sstream>
#include <osg/Vec3f>
#include <components/misc/mathutil.hpp>
namespace sol
{
template <>
struct is_automagical<osg::Vec2f> : std::false_type {};
template <>
struct is_automagical<osg::Vec3f> : std::false_type {};
}
namespace LuaUtil
{
sol::table initUtilPackage(sol::state& lua)
{
sol::table util(lua, sol::create);
// TODO: Add bindings for osg::Matrix
// Lua bindings for osg::Vec2f
util["vector2"] = [](float x, float y) { return osg::Vec2f(x, y); };
sol::usertype<osg::Vec2f> vec2Type = lua.new_usertype<osg::Vec2f>("Vec2");
vec2Type["x"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.x(); } );
vec2Type["y"] = sol::readonly_property([](const osg::Vec2f& v) -> float { return v.y(); } );
vec2Type[sol::meta_function::to_string] = [](const osg::Vec2f& v) {
std::stringstream ss;
ss << "(" << v.x() << ", " << v.y() << ")";
return ss.str();
};
vec2Type[sol::meta_function::unary_minus] = [](const osg::Vec2f& a) { return -a; };
vec2Type[sol::meta_function::addition] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a + b; };
vec2Type[sol::meta_function::subtraction] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a - b; };
vec2Type[sol::meta_function::equal_to] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a == b; };
vec2Type[sol::meta_function::multiplication] = sol::overload(
[](const osg::Vec2f& a, float c) { return a * c; },
[](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; });
vec2Type[sol::meta_function::division] = [](const osg::Vec2f& a, float c) { return a / c; };
vec2Type["dot"] = [](const osg::Vec2f& a, const osg::Vec2f& b) { return a * b; };
vec2Type["length"] = &osg::Vec2f::length;
vec2Type["length2"] = &osg::Vec2f::length2;
vec2Type["normalize"] = [](const osg::Vec2f& v) {
float len = v.length();
if (len == 0)
return std::make_tuple(osg::Vec2f(), 0.f);
else
return std::make_tuple(v * (1.f / len), len);
};
vec2Type["rotate"] = &Misc::rotateVec2f;
// Lua bindings for osg::Vec3f
util["vector3"] = [](float x, float y, float z) { return osg::Vec3f(x, y, z); };
sol::usertype<osg::Vec3f> vec3Type = lua.new_usertype<osg::Vec3f>("Vec3");
vec3Type["x"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.x(); } );
vec3Type["y"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.y(); } );
vec3Type["z"] = sol::readonly_property([](const osg::Vec3f& v) -> float { return v.z(); } );
vec3Type[sol::meta_function::to_string] = [](const osg::Vec3f& v) {
std::stringstream ss;
ss << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")";
return ss.str();
};
vec3Type[sol::meta_function::unary_minus] = [](const osg::Vec3f& a) { return -a; };
vec3Type[sol::meta_function::addition] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a + b; };
vec3Type[sol::meta_function::subtraction] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a - b; };
vec3Type[sol::meta_function::equal_to] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a == b; };
vec3Type[sol::meta_function::multiplication] = sol::overload(
[](const osg::Vec3f& a, float c) { return a * c; },
[](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; });
vec3Type[sol::meta_function::division] = [](const osg::Vec3f& a, float c) { return a / c; };
vec3Type[sol::meta_function::involution] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; };
vec3Type["dot"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a * b; };
vec3Type["cross"] = [](const osg::Vec3f& a, const osg::Vec3f& b) { return a ^ b; };
vec3Type["length"] = &osg::Vec3f::length;
vec3Type["length2"] = &osg::Vec3f::length2;
vec3Type["normalize"] = [](const osg::Vec3f& v) {
float len = v.length();
if (len == 0)
return std::make_tuple(osg::Vec3f(), 0.f);
else
return std::make_tuple(v * (1.f / len), len);
};
// Utility functions
util["clamp"] = [](float value, float from, float to) { return std::clamp(value, from, to); };
// NOTE: `util["clamp"] = std::clamp<float>` causes error 'AddressSanitizer: stack-use-after-scope'
util["normalizeAngle"] = &Misc::normalizeAngle;
return util;
}
}

View File

@ -0,0 +1,13 @@
#ifndef COMPONENTS_LUA_UTILPACKAGE_H
#define COMPONENTS_LUA_UTILPACKAGE_H
#include <sol/sol.hpp>
namespace LuaUtil
{
sol::table initUtilPackage(sol::state&);
}
#endif // COMPONENTS_LUA_UTILPACKAGE_H