diff --git a/apps/components_tests/lua/testutilpackage.cpp b/apps/components_tests/lua/testutilpackage.cpp index a61c0e0306..47041985f6 100644 --- a/apps/components_tests/lua/testutilpackage.cpp +++ b/apps/components_tests/lua/testutilpackage.cpp @@ -9,22 +9,33 @@ namespace { using namespace testing; + struct LuaUtilPackageTest : Test + { + LuaUtil::LuaState mLuaState{ nullptr, nullptr }; + + LuaUtilPackageTest() + { + mLuaState.addInternalLibSearchPath( + std::filesystem::path{ OPENMW_PROJECT_SOURCE_DIR } / "components" / "lua"); + sol::state_view sol = mLuaState.unsafeState(); + sol["util"] = LuaUtil::initUtilPackage(sol); + } + }; + template - T get(sol::state& lua, const std::string& luaCode) + T get(sol::state_view& lua, const std::string& luaCode) { return lua.safe_script("return " + luaCode).get(); } - std::string getAsString(sol::state& lua, std::string luaCode) + std::string getAsString(sol::state_view& lua, std::string luaCode) { return LuaUtil::toString(lua.safe_script("return " + luaCode)); } - TEST(LuaUtilPackageTest, Vector2) + TEST_F(LuaUtilPackageTest, Vector2) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua.safe_script("v = util.vector2(3, 4)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 3); EXPECT_FLOAT_EQ(get(lua, "v.y"), 4); @@ -55,11 +66,9 @@ namespace EXPECT_TRUE(get(lua, "swizzle['01'] == util.vector2(0, 1) and swizzle['0y'] == util.vector2(0, 2)")); } - TEST(LuaUtilPackageTest, Vector3) + TEST_F(LuaUtilPackageTest, Vector3) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua.safe_script("v = util.vector3(5, 12, 13)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); @@ -94,11 +103,9 @@ namespace get(lua, "swizzle['001'] == util.vector3(0, 0, 1) and swizzle['0yx'] == util.vector3(0, 2, 1)")); } - TEST(LuaUtilPackageTest, Vector4) + TEST_F(LuaUtilPackageTest, Vector4) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua.safe_script("v = util.vector4(5, 12, 13, 15)"); EXPECT_FLOAT_EQ(get(lua, "v.x"), 5); EXPECT_FLOAT_EQ(get(lua, "v.y"), 12); @@ -136,11 +143,9 @@ namespace lua, "swizzle['0001'] == util.vector4(0, 0, 0, 1) and swizzle['0yx1'] == util.vector4(0, 2, 1, 1)")); } - TEST(LuaUtilPackageTest, Color) + TEST_F(LuaUtilPackageTest, Color) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua.safe_script("brown = util.color.rgba(0.75, 0.25, 0, 1)"); EXPECT_EQ(get(lua, "tostring(brown)"), "(0.75, 0.25, 0, 1)"); lua.safe_script("blue = util.color.rgb(0, 1, 0, 1)"); @@ -155,11 +160,9 @@ namespace EXPECT_TRUE(get(lua, "red:asRgb() == util.vector3(1, 0, 0)")); } - TEST(LuaUtilPackageTest, Transform) + TEST_F(LuaUtilPackageTest, Transform) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua["T"] = lua["util"]["transform"]; lua["v"] = lua["util"]["vector3"]; EXPECT_ERROR(lua.safe_script("T.identity = nil"), "attempt to index"); @@ -191,11 +194,9 @@ namespace EXPECT_LT(get(lua, "(rz_move_rx:inverse() * v(0, 1, 2) - v(1, 2, 3)):length()"), 1e-6); } - TEST(LuaUtilPackageTest, UtilityFunctions) + TEST_F(LuaUtilPackageTest, UtilityFunctions) { - sol::state lua; - lua.open_libraries(sol::lib::base, sol::lib::math, sol::lib::string); - lua["util"] = LuaUtil::initUtilPackage(lua); + sol::state_view lua = mLuaState.unsafeState(); lua.safe_script("v = util.vector2(1, 0):rotate(math.rad(120))"); EXPECT_FLOAT_EQ(get(lua, "v.x"), -0.5f); EXPECT_FLOAT_EQ(get(lua, "v.y"), 0.86602539f); @@ -203,6 +204,10 @@ namespace EXPECT_FLOAT_EQ(get(lua, "util.clamp(0.1, 0, 1.5)"), 0.1f); EXPECT_FLOAT_EQ(get(lua, "util.clamp(-0.1, 0, 1.5)"), 0); EXPECT_FLOAT_EQ(get(lua, "util.clamp(2.1, 0, 1.5)"), 1.5f); + EXPECT_FLOAT_EQ(get(lua, "util.round(2.1)"), 2.0f); + EXPECT_FLOAT_EQ(get(lua, "util.round(-2.1)"), -2.0f); + EXPECT_FLOAT_EQ(get(lua, "util.remap(5, 0, 10, 0, 100)"), 50.0f); + EXPECT_FLOAT_EQ(get(lua, "util.remap(-5, 0, 10, 0, 100)"), -50.0f); lua.safe_script("t = util.makeReadOnly({x = 1})"); EXPECT_FLOAT_EQ(get(lua, "t.x"), 1); EXPECT_ERROR(lua.safe_script("t.y = 2"), "userdata value"); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index aa6c8763bb..69c7ba462e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -61,6 +61,7 @@ add_component_dir (lua luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 shapes/box inputactions yamlloader scripttracker luastateptr ) +copy_resource_file("lua/util.lua" "${OPENMW_RESOURCES_ROOT}" "resources/lua_libs/util.lua") add_component_dir (l10n messagebundles manager diff --git a/components/lua/util.lua b/components/lua/util.lua new file mode 100644 index 0000000000..f37a2e52cc --- /dev/null +++ b/components/lua/util.lua @@ -0,0 +1,21 @@ + +local M = {} + +function M.remap(value, min, max, newMin, newMax) + return newMin + (value - min) * (newMax - newMin) / (max - min) +end + +function M.round(value) + return value >= 0 and math.floor(value + 0.5) or math.ceil(value - 0.5) +end + +function M.clamp(value, low, high) + return value < low and low or (value > high and high or value) +end + +function M.normalizeAngle(angle) + local fullTurns = angle / (2 * math.pi) + 0.5 + return (fullTurns - math.floor(fullTurns) - 0.5) * (2 * math.pi) +end + +return M diff --git a/components/lua/utilpackage.cpp b/components/lua/utilpackage.cpp index 85492ccf06..2b706e1cb8 100644 --- a/components/lua/utilpackage.cpp +++ b/components/lua/utilpackage.cpp @@ -352,16 +352,14 @@ namespace LuaUtil return std::make_tuple(angles.z(), angles.y(), angles.x()); }; + sol::function luaUtilLoader = lua["loadInternalLib"]("util"); + sol::table utils = luaUtilLoader(); + for (const auto& [key, value] : utils) + util[key.as()] = value; + // Utility functions - util["clamp"] = [](double value, double from, double to) { return std::clamp(value, from, to); }; - // NOTE: `util["clamp"] = std::clamp` causes error 'AddressSanitizer: stack-use-after-scope' - util["normalizeAngle"] = &Misc::normalizeAngle; util["makeReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/false); }; util["makeStrictReadOnly"] = [](const sol::table& tbl) { return makeReadOnly(tbl, /*strictIndex=*/true); }; - util["remap"] = [](double value, double min, double max, double newMin, double newMax) { - return newMin + (value - min) * (newMax - newMin) / (max - min); - }; - util["round"] = [](double value) { return round(value); }; if (lua["bit32"] != sol::nil) {