diff --git a/CHANGELOG.md b/CHANGELOG.md index d17f940..0278d05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # master +# v0.10.0 + +**API Changes** + +* Added LuaModule that can be used to load Lua source code to be shared in one or more Lua scripts + * LogicEngine::createLuaScriptFromSource and LogicEngine::createLuaScriptFromFile have new optional argument + to provide list of module dependencies and access alias for each of them + * Modules can use other modules recursively +* Reworked API for creating scripts (accepts an optional configuration object which can be used to refer to modules) + * Old API methods (createLuaScriptFromSource and createLuaScriptFromFile) are not available any more + * New API createLuaScript accepts Lua source code, and optionally configuration object and a name + * File handling is expected to be performed outside the Logic Engine + * Rationale: text file loading is not considered a core functionality of the logic engine + * Also removed corresponding LuaScript::getFilename() method + * Standard modules must be loaded explicitly via the LuaConfig object + * When loading older binary files, standard modules are implicitly loaded for compatibility + * New code should request standard modules explicitly and only where needed + +**Bugfixes** + +* Property::isLinked() is correctly exported in the shared library +* LogicEngine move constructor and assignment are correctly exported in the shared library + # v0.9.1 Summary: update Ramses to version 27.0.111 @@ -11,6 +34,7 @@ Summary: update Ramses to version 27.0.111 in your runtime or use the built-in ramses shipped with v0.9.1. Otherwise the scene may look wrong! + # v0.9.0 Summary: diff --git a/CMakeLists.txt b/CMakeLists.txt index 56a66c6..7f40f64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,8 @@ cmake_minimum_required(VERSION 3.13) #========================================================================== set(RLOGIC_VERSION_MAJOR 0) -set(RLOGIC_VERSION_MINOR 9) -set(RLOGIC_VERSION_PATCH 1) +set(RLOGIC_VERSION_MINOR 10) +set(RLOGIC_VERSION_PATCH 0) set(RLOGIC_VERSION ${RLOGIC_VERSION_MAJOR}.${RLOGIC_VERSION_MINOR}.${RLOGIC_VERSION_PATCH}) set(ramses-logic_VERSION "${RLOGIC_VERSION}" CACHE STRING "Ramses Logic version" FORCE) @@ -37,7 +37,6 @@ set(ramses-logic_RAMSES_TARGET "ramses-shared-lib-client-only" CACHE set(ramses-logic_FOLDER_PREFIX "" CACHE STRING "Set a custom prefix for target folders in Visual Studio. If not set, will be set based on project's relative path") set(ramses-logic_BUILD_RAMSES_RENDERER false CACHE BOOL "Set whether the ramses renderer will be built as part of this build. If this option is set to ON, ramses-logic_RAMSES_TARGET has to be set to a shared library target of ramses which contains a renderer (e.g ramses-shared-lib-android-egl-es-3-0)") -option(ramses-logic_BUILD_ANDROID "Enable/disable Android build" OFF) option(ramses-logic_ALLOW_RAMSES_BUILD "Set this to OFF to explicitly disable Ramses build, even if ramses-logic_RAMSES_TARGET is not a valid ramses target" ON) option(ramses-logic_FIND_RAMSES_FROM_SYSTEM "Set this to ON if you want to use an existing installation or package of Ramses (find_package)" OFF) set(ramses-logic_PLATFORM "" CACHE @@ -234,14 +233,6 @@ endif() include(cmake/addCheckerTargets.cmake) -# TODO implement public android build scripts -if(ramses-logic_BUILD_ANDROID) - if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/android") - message(FATAL_ERROR "Missing android build scripts. Please provide your own gradle scripts in android/") - endif() - add_subdirectory(android) -endif() - #========================================================================== # build and install documentation #========================================================================== diff --git a/README.md b/README.md index 1f1d247..be8016e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Prefer to learn by example? Have a look at our [self-contained example snippets] |Logic | Included Ramses version | Minimum required Ramses version | Binary file compatibility | |---------|-------------------------------|------------------------------------|------------------------------| +|0.10.0 | 27.0.111 | same as 0.6.0 | 0.9.x | |0.9.1 | 27.0.111 | same as 0.6.0 | 0.9.x | |0.9.0 | 27.0.110 | same as 0.6.0 | 0.9.x | |0.8.1 | 27.0.110 | same as 0.6.0 | 0.7.x or 0.8.x | diff --git a/benchmarks/compile_lua.cpp b/benchmarks/compile_lua.cpp index 55af3d6..139c942 100644 --- a/benchmarks/compile_lua.cpp +++ b/benchmarks/compile_lua.cpp @@ -9,13 +9,14 @@ #include "benchmark/benchmark.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "fmt/format.h" namespace rlogic { static void CompileLua(LogicEngine& logicEngine, std::string_view src) { - LuaScript* script = logicEngine.createLuaScriptFromSource(src); + LuaScript* script = logicEngine.createLuaScript(src); logicEngine.destroy(*script); } diff --git a/benchmarks/links.cpp b/benchmarks/links.cpp index e1c9fbf..c09b55f 100644 --- a/benchmarks/links.cpp +++ b/benchmarks/links.cpp @@ -9,6 +9,7 @@ #include "benchmark/benchmark.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "impl/LogicEngineImpl.h" #include "ramses-logic/Property.h" @@ -33,8 +34,8 @@ namespace rlogic end )", propertyCount); - LuaScript* srcScript = logicEngine.createLuaScriptFromSource(scriptSrc); - LuaScript* destScript = logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* srcScript = logicEngine.createLuaScript(scriptSrc); + LuaScript* destScript = logicEngine.createLuaScript(scriptSrc); const Property* from = srcScript->getOutputs()->getChild("src0"); Property* to = destScript->getInputs()->getChild("target0"); @@ -72,7 +73,7 @@ namespace rlogic std::vector scripts(scriptCount); for (int64_t i = 0; i < scriptCount; ++i) { - scripts[i] = logicEngine.createLuaScriptFromSource(scriptSrc); + scripts[i] = logicEngine.createLuaScript(scriptSrc); if (i >= 1) { diff --git a/benchmarks/property.cpp b/benchmarks/property.cpp index 65ff0d0..ee196aa 100644 --- a/benchmarks/property.cpp +++ b/benchmarks/property.cpp @@ -9,6 +9,7 @@ #include "benchmark/benchmark.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" #include "impl/LogicEngineImpl.h" @@ -32,7 +33,7 @@ namespace rlogic end )", propertyCount); - LuaScript* script = logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = logicEngine.createLuaScript(scriptSrc); Property* property = script->getInputs()->getChild("param0"); // Need different value, otherwise triggers internal caching (can't disable value check) int32_t increasingValue = 0; diff --git a/benchmarks/serialization.cpp b/benchmarks/serialization.cpp index 8a078ee..94c7af5 100644 --- a/benchmarks/serialization.cpp +++ b/benchmarks/serialization.cpp @@ -9,6 +9,7 @@ #include "benchmark/benchmark.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" #include "fmt/format.h" @@ -34,7 +35,7 @@ namespace rlogic std::vector scripts(scriptCount); for (int64_t i = 0; i < scriptCount; ++i) { - scripts[i] = logicEngine.createLuaScriptFromSource(scriptSrc); + scripts[i] = logicEngine.createLuaScript(scriptSrc); if (i >= 1) { diff --git a/benchmarks/update.cpp b/benchmarks/update.cpp index 5cf86ee..da38a61 100644 --- a/benchmarks/update.cpp +++ b/benchmarks/update.cpp @@ -9,6 +9,7 @@ #include "benchmark/benchmark.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" #include "impl/LogicEngineImpl.h" @@ -34,7 +35,7 @@ namespace rlogic end )", loopCount); - logicEngine.createLuaScriptFromSource(scriptSrc); + logicEngine.createLuaScript(scriptSrc); for (auto _ : state) // NOLINT(clang-analyzer-deadcode.DeadStores) False positive { @@ -83,7 +84,7 @@ namespace rlogic end )", loopCount); - logicEngine.createLuaScriptFromSource(scriptSrc); + logicEngine.createLuaScript(scriptSrc); for (auto _ : state) // NOLINT(clang-analyzer-deadcode.DeadStores) False positive { @@ -112,7 +113,7 @@ namespace rlogic end )", loopCount); - logicEngine.createLuaScriptFromSource(scriptSrc); + logicEngine.createLuaScript(scriptSrc); for (auto _ : state) // NOLINT(clang-analyzer-deadcode.DeadStores) False positive { @@ -161,7 +162,7 @@ namespace rlogic std::vector scripts(scriptCount); for (int64_t i = 0; i < scriptCount; ++i) { - scripts[i] = logicEngine.createLuaScriptFromSource(scriptSrc, fmt::format("script{}", i)); + scripts[i] = logicEngine.createLuaScript(scriptSrc, {}, fmt::format("script{}", i)); if (i >= 1) { diff --git a/ci/scripts/installation-check/shared-lib-check/ramses-logic-shared-lib-check.cpp b/ci/scripts/installation-check/shared-lib-check/ramses-logic-shared-lib-check.cpp index 9eb0f90..b86f393 100644 --- a/ci/scripts/installation-check/shared-lib-check/ramses-logic-shared-lib-check.cpp +++ b/ci/scripts/installation-check/shared-lib-check/ramses-logic-shared-lib-check.cpp @@ -17,7 +17,7 @@ int main() { std::cout << "Start ramses-logic-shared-lib-check\n"; rlogic::LogicEngine logicEngine; - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.int = INT OUT.float = FLOAT diff --git a/ci/scripts/installation-check/submodule-check/ramses-logic-submodule-check.cpp b/ci/scripts/installation-check/submodule-check/ramses-logic-submodule-check.cpp index 25fdf37..5851aac 100644 --- a/ci/scripts/installation-check/submodule-check/ramses-logic-submodule-check.cpp +++ b/ci/scripts/installation-check/submodule-check/ramses-logic-submodule-check.cpp @@ -17,7 +17,7 @@ int main() { std::cout << "Start ramses-logic-submodule-check\n"; rlogic::LogicEngine logicEngine; - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.int = INT OUT.float = FLOAT diff --git a/doc/sphinx/api.rst b/doc/sphinx/api.rst index 4306da6..ba9fffb 100644 --- a/doc/sphinx/api.rst +++ b/doc/sphinx/api.rst @@ -134,7 +134,7 @@ You can create scripts using the :class:`rlogic::LogicEngine` class like this: )" LogicEngine engine; - LuaScript* script = engine.createLuaScriptFromSource(source, "simple script"); + LuaScript* script = engine.createLuaScript(source, "simple script"); script->getInputs()->getChild("gear")->set(4); script->execute(); @@ -197,7 +197,7 @@ Here is a simple example how links are created: :linenos: LogicEngine logicEngine; - LuaScript* sourceScript = logicEngine.createLuaScriptFromSource(R"( + LuaScript* sourceScript = logicEngine.createLuaScript(R"( function interface() OUT.source = STRING end @@ -206,7 +206,7 @@ Here is a simple example how links are created: end )"); - LuaScript* destinationScript = logicEngine.createLuaScriptFromSource(R"( + LuaScript* destinationScript = logicEngine.createLuaScript(R"( function interface() IN.destination = STRING end @@ -409,7 +409,7 @@ you own one like this: :linenos: LogicEngine logicEngine; - LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = logicEngine.createLuaScript(R"( function interface() end function run() @@ -680,3 +680,4 @@ List of all examples examples/06_override_print examples/07_links examples/08_animation + examples/09_modules diff --git a/doc/sphinx/classes/EStandardModule.rst b/doc/sphinx/classes/EStandardModule.rst new file mode 100644 index 0000000..b655092 --- /dev/null +++ b/doc/sphinx/classes/EStandardModule.rst @@ -0,0 +1,18 @@ +.. + ------------------------------------------------------------------------- + Copyright (C) 2021 BMW AG + ------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + ------------------------------------------------------------------------- + +.. default-domain:: cpp +.. highlight:: cpp + +========================= +EStandardModule +========================= + +.. doxygenenum:: rlogic::EStandardModule + diff --git a/doc/sphinx/classes/LuaConfig.rst b/doc/sphinx/classes/LuaConfig.rst new file mode 100644 index 0000000..12f4ff6 --- /dev/null +++ b/doc/sphinx/classes/LuaConfig.rst @@ -0,0 +1,18 @@ +.. + ------------------------------------------------------------------------- + Copyright (C) 2021 BMW AG + ------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + ------------------------------------------------------------------------- + +.. default-domain:: cpp +.. highlight:: cpp + +========================= +LuaConfig +========================= + +.. doxygenclass:: rlogic::LuaConfig + :members: diff --git a/doc/sphinx/classes/LuaModule.rst b/doc/sphinx/classes/LuaModule.rst new file mode 100644 index 0000000..c18ba3b --- /dev/null +++ b/doc/sphinx/classes/LuaModule.rst @@ -0,0 +1,18 @@ +.. + ------------------------------------------------------------------------- + Copyright (C) 2021 BMW AG + ------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + ------------------------------------------------------------------------- + +.. default-domain:: cpp +.. highlight:: cpp + +========================= +LuaModule +========================= + +.. doxygenclass:: rlogic::LuaModule + :members: diff --git a/doc/sphinx/classes/generate_classes.py b/doc/sphinx/classes/generate_classes.py index 73487a7..51798bf 100644 --- a/doc/sphinx/classes/generate_classes.py +++ b/doc/sphinx/classes/generate_classes.py @@ -19,6 +19,7 @@ 'options': ' :members:', 'items': [ 'LogicEngine', + 'LuaModule', 'LuaScript', 'RamsesNodeBinding', 'RamsesAppearanceBinding', @@ -30,6 +31,7 @@ 'AnimationNode', 'Iterator', 'Collection', + 'LuaConfig', ], }, { @@ -76,6 +78,7 @@ 'namespace_prefix': 'rlogic::', 'options': '', 'items': [ + 'EStandardModule', 'EPropertyType', 'EInterpolationType', 'ELogMessageType', diff --git a/doc/sphinx/classes/index.rst b/doc/sphinx/classes/index.rst index 47a4073..cacd62a 100644 --- a/doc/sphinx/classes/index.rst +++ b/doc/sphinx/classes/index.rst @@ -18,6 +18,7 @@ Class Index LogicEngine + LuaModule LuaScript RamsesNodeBinding RamsesAppearanceBinding @@ -29,6 +30,7 @@ Class Index AnimationNode Iterator Collection + LuaConfig .. toctree:: @@ -70,6 +72,7 @@ Class Index :caption: Enums + EStandardModule EPropertyType EInterpolationType ELogMessageType diff --git a/doc/sphinx/examples/09_modules.rst b/doc/sphinx/examples/09_modules.rst new file mode 100644 index 0000000..5815d2e --- /dev/null +++ b/doc/sphinx/examples/09_modules.rst @@ -0,0 +1,19 @@ +.. + ------------------------------------------------------------------------- + Copyright (C) 2021 BMW AG + ------------------------------------------------------------------------- + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + ------------------------------------------------------------------------- + +.. default-domain:: cpp +.. highlight:: cpp + +================================ +Modules example +================================ + +.. literalinclude:: ../../../examples/09_modules/main.cpp + :start-after: #include + :end-before: return 0; diff --git a/doc/sphinx/lua_syntax.rst b/doc/sphinx/lua_syntax.rst index 17ab4f6..3947ea0 100644 --- a/doc/sphinx/lua_syntax.rst +++ b/doc/sphinx/lua_syntax.rst @@ -252,6 +252,10 @@ Other scripts which may be linked to the erroneous script will not be executed t Using Lua modules ================================================== +-------------------------------------------------- +Standard modules +-------------------------------------------------- + The ``Logic Engine`` restricts which Lua modules can be used to a subset of the standard modules of ``Lua 5.1``: @@ -270,6 +274,102 @@ Some of the standard modules are deliberately not supported: * Not supported on all platforms (e.g. Android forbids direct file access) * Stability/integration concerns (e.g. opening relative files in Lua makes the scripts non-relocatable) +-------------------------------------------------- +Custom modules +-------------------------------------------------- + +It is possible to create custom user modules (see :cpp:class:`rlogic::LuaModule` for the ``C++`` docs). +A custom module can contain any Lua source code which obeys the modern Lua module definition convention +(i.e. declare a table, fill it with data and functions, and return the table as a result of the module +script): + +.. code-block:: lua + :linenos: + :emphasize-lines: 1,5, 10,14 + + local coalaModule = {} + + coalaModule.coalaChief = "Alfred" + + coalaModule.coalaStruct = { + preferredFood = STRING, + weight = INT + } + + function coalaModule.bark() + print("Coalas don't bark...") + end + + return coalaModule + +The name of the module (line 1) is not of importance and won't be visible anywhere outside of +the module definition file. You can declare structs and other types you could otherwise use +in the interface() functions of scripts (line 5). You can declare functions and make them part +of the module by using the syntax on line 10. Make sure you return the module (line 14)! + +You can use modules in scripts as you would use a standard Lua module. The only exception +is that you can't import the module with the ``require`` keyword, but have to use a free +function ``modules()`` to declare the modules needed by the script: + +.. code-block:: lua + :linenos: + :emphasize-lines: 1,4,20,21 + + modules("coalas") + + function interface() + local s = coalas.coalaStruct + OUT.coalas = ARRAY(2, s) + end + + function run() + OUT.coalas = { + { + preferredFood = "bamboo", + weight = 5 + }, + { + preferredFood = "donuts", + weight = 12 + } + } + + print(coalas.chief .. " says:") + coalas.bark() + end + +The name ``coalas`` on line 1 is the name under which the module is mapped and available in the +script (e.g. on lines 4, 20-21). The name obeys the same rules as Lua labels - it can only contain digits, letters and the +underscore character, and it can't start with a digit. Also, the names used in the mapping must +be unique (otherwise the script won't be able to uniquely resolve which modules are supposed to +be used). + +It is also possible to use modules in other modules, like this: + +.. code-block:: lua + :linenos: + + modules("quaternions") + + local rotationHelper = {} + + function rotationHelper.matrixFromEuler(x, y, z) + local q = quaternions.createFromEuler(x, y, z) + return q.toMatrix() + end + + return rotationHelper + +In the example above, the ``rotationHelper`` module uses another module ``quaternions`` to provide +a new function which computes a rotation matrix using quaternions as an intermediate step. + +.. warning:: + Modules are supposed to read-only to prevent misuse and guarantee safe usage. However, this is not + implemented yet! This means that there is no mechanism (yet) which forbids modifying module data. We strongly advise against + modifying module data because a) it will produce errors in an upcoming release and b) it can introduce undefined behavior + when different module try to write the same data, or if one module writes and another one reads the + same data in undefined order. + ===================================== Additional Lua syntax specifics ===================================== diff --git a/examples/00_minimal/main.cpp b/examples/00_minimal/main.cpp index 6e6b488..27cd5b5 100644 --- a/examples/00_minimal/main.cpp +++ b/examples/00_minimal/main.cpp @@ -31,7 +31,7 @@ int main() * outputs of the script. * The run function contains the real code, which is executed during runtime */ - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.rotate_x = FLOAT OUT.rotation = VEC3F diff --git a/examples/01a_primitive_properties/main.cpp b/examples/01a_primitive_properties/main.cpp index 341e652..a1db790 100644 --- a/examples/01a_primitive_properties/main.cpp +++ b/examples/01a_primitive_properties/main.cpp @@ -7,7 +7,9 @@ // ------------------------------------------------------------------------- #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" + #include /** @@ -18,7 +20,7 @@ int main() rlogic::LogicEngine logicEngine; // Create a simple script which multiplies two numbers and stores the result in a string - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* multiplyScript = logicEngine.createLuaScript(R"( function interface() IN.param1 = INT IN.param2 = FLOAT @@ -29,14 +31,14 @@ int main() function run() OUT.result = "Calculated result is: " .. IN.param1 * IN.param2 end - )", "MultiplyScript"); + )"); /** * Query the inputs of the script. The inputs are * stored in a "Property" instance and can be used to * get the information about available inputs and outputs */ - rlogic::Property* inputs = script->getInputs(); + rlogic::Property* inputs = multiplyScript->getInputs(); for (size_t i = 0u; i < inputs->getChildCount(); ++i) { @@ -45,7 +47,7 @@ int main() } // We can do the same with the outputs - const rlogic::Property* outputs = script->getOutputs(); + const rlogic::Property* outputs = multiplyScript->getOutputs(); for (size_t i = 0u; i < outputs->getChildCount(); ++i) { @@ -72,7 +74,7 @@ int main() } // To delete the script call the destroy method on the LogicEngine instance - logicEngine.destroy(*script); + logicEngine.destroy(*multiplyScript); return 0; } diff --git a/examples/01b_struct_properties/main.cpp b/examples/01b_struct_properties/main.cpp index ca77a36..541cd63 100644 --- a/examples/01b_struct_properties/main.cpp +++ b/examples/01b_struct_properties/main.cpp @@ -7,6 +7,7 @@ // ------------------------------------------------------------------------- #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" #include @@ -22,7 +23,7 @@ int main() rlogic::LogicEngine logicEngine; // Create a script with inputs and outputs of the same type (consists of nested structs) - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.struct = { nested = { diff --git a/examples/01c_array_properties/main.cpp b/examples/01c_array_properties/main.cpp index d7ad270..52c6db6 100644 --- a/examples/01c_array_properties/main.cpp +++ b/examples/01c_array_properties/main.cpp @@ -7,6 +7,7 @@ // ------------------------------------------------------------------------- #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" #include @@ -21,7 +22,7 @@ int main() rlogic::LogicEngine logicEngine; // A script which demonstrates how to access vector and array properties - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.vec3f = VEC3F diff --git a/examples/02_errors_compile_time/main.cpp b/examples/02_errors_compile_time/main.cpp index 947448a..6192c07 100644 --- a/examples/02_errors_compile_time/main.cpp +++ b/examples/02_errors_compile_time/main.cpp @@ -22,16 +22,16 @@ int main() * Try to compile a script which has invalid Lua syntax * Giving a name to the script helps identify the source of the issue */ - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* faultyScript = logicEngine.createLuaScript(R"( function interface() this.does.not.compile end function run() end - )", "FaultyScript"); + )"); // script is nullptr because of the compilation error - if (nullptr == script) + if (nullptr == faultyScript) { /** * To get further information about the issue, fetch errors from LogicEngine diff --git a/examples/03_errors_runtime/main.cpp b/examples/03_errors_runtime/main.cpp index 1efba1a..33540cf 100644 --- a/examples/03_errors_runtime/main.cpp +++ b/examples/03_errors_runtime/main.cpp @@ -7,6 +7,8 @@ // ------------------------------------------------------------------------- #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" + #include #include @@ -22,7 +24,7 @@ int main() * valid syntax, but the type check of the Logic engine will fire a runtime * error for trying to assign a string to a VEC4F property */ - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* faultyScript = logicEngine.createLuaScript(R"( function interface() OUT.vec4f = VEC4F end @@ -30,10 +32,10 @@ int main() function run() OUT.vec4f = "this is not a table with 4 floats and will trigger a runtime error!" end - )", "FaultyScript"); + )"); // Script is successfully created, as it is syntactically correct - assert(script != nullptr); + assert(faultyScript != nullptr); /** * Update the logic engine including our script @@ -55,7 +57,7 @@ int main() std::cout << "Script '" << error.object->getName() << "' caused a runtime error:\n" << error.message << std::endl; } - logicEngine.destroy(*script); + logicEngine.destroy(*faultyScript); return 0; } diff --git a/examples/04_ramses_scene/main.cpp b/examples/04_ramses_scene/main.cpp index 4b536e3..7bdb096 100644 --- a/examples/04_ramses_scene/main.cpp +++ b/examples/04_ramses_scene/main.cpp @@ -75,7 +75,7 @@ int main(int argc, char* argv[]) * Create a simple script which takes the current time in milliseconds * and rotates around the Z axis slowly based on how much time is passed */ - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.time_msec = INT OUT.rotationZ = VEC3F diff --git a/examples/05_serialization/main.cpp b/examples/05_serialization/main.cpp index 1374566..cf99ff7 100644 --- a/examples/05_serialization/main.cpp +++ b/examples/05_serialization/main.cpp @@ -186,7 +186,7 @@ void CreateAndSaveContent(const std::string &ramsesSceneFile, const std::string& /** * Create a simple script which sets the rotation values of a node based on simulated time */ - rlogic::LuaScript* simpleScript = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* simpleScript = logicEngine.createLuaScript(R"( function interface() IN.time_msec = INT OUT.rotationZ = VEC3F @@ -196,7 +196,7 @@ void CreateAndSaveContent(const std::string &ramsesSceneFile, const std::string& -- Rotate around Z axis with 100 degrees per second OUT.rotationZ = {0, 0, IN.time_msec / 10} end - )", "simple rotation script"); + )", {}, "simple rotation script"); /** * Link the script output to the node binding input so that the value produced by the script is passed to the ramses node on update diff --git a/examples/06_override_print/main.cpp b/examples/06_override_print/main.cpp index eeaadb9..d4f5710 100644 --- a/examples/06_override_print/main.cpp +++ b/examples/06_override_print/main.cpp @@ -7,7 +7,9 @@ // ------------------------------------------------------------------------- #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/Property.h" + #include /** @@ -18,7 +20,7 @@ int main() rlogic::LogicEngine logicEngine; // Create a simple script which prints a debug message - rlogic::LuaScript* script = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( function interface() IN.debug_message = STRING end @@ -26,7 +28,7 @@ int main() function run() print(IN.debug_message) end - )", "MyScript"); + )"); // Override Lua's print() function so that we can get the result in C++ script->overrideLuaPrint( diff --git a/examples/07_links/main.cpp b/examples/07_links/main.cpp index 153a82b..0e1f0d0 100644 --- a/examples/07_links/main.cpp +++ b/examples/07_links/main.cpp @@ -41,8 +41,8 @@ int main() )"; // Create two scripts using the Lua source code from above - rlogic::LuaScript* script1 = logicEngine.createLuaScriptFromSource(scriptSrc, "script 1"); - rlogic::LuaScript* script2 = logicEngine.createLuaScriptFromSource(scriptSrc, "script 2"); + rlogic::LuaScript* script1 = logicEngine.createLuaScript(scriptSrc); + rlogic::LuaScript* script2 = logicEngine.createLuaScript(scriptSrc); // Assign the scripts their names so that we can see their execution order script1->getInputs()->getChild("script_name")->set("script 1"); diff --git a/examples/09_modules/main.cpp b/examples/09_modules/main.cpp new file mode 100644 index 0000000..c577d0a --- /dev/null +++ b/examples/09_modules/main.cpp @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" +#include "ramses-logic/LuaModule.h" +#include "ramses-logic/Property.h" + +#include +#include + +/** + * This example demonstrates how to create reusable modules and embed them to scripts + */ + +int main() +{ + rlogic::LogicEngine logicEngine; + + // Create a module which wraps Lua's print method and prints the name of the caller + rlogic::LuaModule* myPrint = logicEngine.createLuaModule(R"( + local myPrint = {} + + function myPrint.print(name) + print("Hello, " .. name .. "!") + end + + return myPrint + )"); + + // Create a LuaConfig object which we use to configure how the module + // shall be mapped to the script later (under the alias 'PrintModule') + rlogic::LuaConfig modulesConfig; + modulesConfig.addDependency("PrintModule", *myPrint); + + // Create a script which uses the custom print module. Notice that the script + // declares its dependency to a PrintModule via the modules() function + rlogic::LuaScript* script = logicEngine.createLuaScript(R"( + -- The script must declare the modules it depends on + -- The name here must match the alias provided in the LuaConfig above! + modules('PrintModule') + + function interface() + IN.name = STRING + end + + function run() + -- Calls the 'print' function packaged in the PrintModule + PrintModule.print(IN.name) + end + )", modulesConfig, "ScriptWithModule"); + + // Set the name input + script->getInputs()->getChild("name")->set("MrAnderson"); + + // Update the logic engine (will print 'Hello MrAnderson') + logicEngine.update(); + + // Modules have to be destroyed after all scripts that reference them have been destroyed + logicEngine.destroy(*script); + logicEngine.destroy(*myPrint); + + return 0; +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c738e1e..3a6d061 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -49,3 +49,4 @@ add_example(NAME 05_serialization FILES 05_serialization/main.cpp add_example(NAME 06_override_print FILES 06_override_print/main.cpp MAKE_TEST) add_example(NAME 07_links FILES 07_links/main.cpp MAKE_TEST) add_example(NAME 08_animation FILES 08_animation/main.cpp MAKE_TEST) +add_example(NAME 09_modules FILES 09_modules/main.cpp MAKE_TEST) diff --git a/include/ramses-logic/EStandardModule.h b/include/ramses-logic/EStandardModule.h new file mode 100644 index 0000000..7a6560a --- /dev/null +++ b/include/ramses-logic/EStandardModule.h @@ -0,0 +1,27 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#pragma once + +#include + +namespace rlogic +{ + /** + * Enum which represents the different Lua standard modules. Used in #rlogic::LuaConfig::addStandardModuleDependency + */ + enum class EStandardModule : int32_t + { + Base=0, //< Basic functionality mapped to global space, e.g. ipairs(), pcall(), error(), assert() + String, //< The String module mapped to the Lua environment as a table named 'string' + Table, //< The Table module mapped to the Lua environment as a table named 'table' + Math, //< The Math module mapped to the Lua environment as a table named 'math' + Debug, //< The Debug module mapped to the Lua environment as a table named 'debug' + All, //< Use this to load all standard modules + }; +} diff --git a/include/ramses-logic/LogicEngine.h b/include/ramses-logic/LogicEngine.h index ba0c291..c13c005 100644 --- a/include/ramses-logic/LogicEngine.h +++ b/include/ramses-logic/LogicEngine.h @@ -9,9 +9,9 @@ #pragma once #include "ramses-logic/APIExport.h" -#include "ramses-logic/LuaScript.h" #include "ramses-logic/Collection.h" #include "ramses-logic/ErrorData.h" +#include "ramses-logic/LuaConfig.h" #include "ramses-logic/EPropertyType.h" #include "ramses-logic/AnimationTypes.h" #include "ramses-logic/ERotationType.h" @@ -34,7 +34,10 @@ namespace rlogic::internal namespace rlogic { + class LogicNode; class LuaScript; + class LuaModule; + class Property; class RamsesNodeBinding; class RamsesAppearanceBinding; class RamsesCameraBinding; @@ -70,6 +73,13 @@ namespace rlogic */ [[nodiscard]] RLOGIC_API Collection scripts() const; + /** + * Returns an iterable #rlogic::Collection of all #rlogic::LuaModule instances created by this #LogicEngine. + * + * @return an iterable #rlogic::Collection with all #rlogic::LuaModule instances created by this #LogicEngine + */ + [[nodiscard]] RLOGIC_API Collection luaModules() const; + /** * Returns an iterable #rlogic::Collection of all #rlogic::RamsesNodeBinding instances created by this #LogicEngine. * @@ -115,6 +125,16 @@ namespace rlogic /// @copydoc findScript(std::string_view) const [[nodiscard]] RLOGIC_API LuaScript* findScript(std::string_view name); + /** + * Returns a pointer to the first occurrence of a #rlogic::LuaModule with a given \p name if such exists, and nullptr otherwise. + * + * @param name the name of the module to search for + * @return a pointer to the script, or nullptr if none was found + */ + [[nodiscard]] RLOGIC_API const LuaModule* findLuaModule(std::string_view name) const; + /// @copydoc findScript(std::string_view) const + [[nodiscard]] RLOGIC_API LuaModule* findLuaModule(std::string_view name); + /** * Returns a pointer to the first occurrence of a node binding with a given \p name if such exists, and nullptr otherwise. * @@ -166,32 +186,84 @@ namespace rlogic [[nodiscard]] RLOGIC_API AnimationNode* findAnimationNode(std::string_view name); /** - * Creates a new #rlogic::LuaScript from an existing Lua source file. Refer to the #rlogic::LuaScript class documentation - * for requirements that Lua scripts must fulfill in order to be added to the #LogicEngine. + * Creates a new Lua script from a source string. Refer to the #rlogic::LuaScript class + * for requirements which Lua scripts must fulfill in order to be added to the #LogicEngine. + * You can optionally provide Lua module dependencies via the \p config, they will be accessible + * under their configured alias name for use by the script. The provided module dependencies + * must exactly match the declared dependencies in source code (see #extractLuaDependencies). + * + * Attention! This method clears all previous errors! See also docs of #getErrors() + * + * @param source the Lua source code + * @param config configuration options, e.g. for module dependencies + * @param scriptName name to assign to the script once it's created + * @return a pointer to the created object or nullptr if + * something went wrong during creation. In that case, use #getErrors() to obtain errors. + * The script can be destroyed by calling the #destroy method + */ + RLOGIC_API LuaScript* createLuaScript( + std::string_view source, + const LuaConfig& config = {}, + std::string_view scriptName = ""); + + /** + * Creates a new #rlogic::LuaModule from Lua source code. + * LuaModules can be used to share code and data constants across scripts or + * other modules. See also #createLuaScript and #rlogic::LuaConfig for details. + * You can optionally provide Lua module dependencies via the \p config, they will be accessible + * under their configured alias name for use by the module. The provided module dependencies + * must exactly match the declared dependencies in source code (see #extractLuaDependencies). * * Attention! This method clears all previous errors! See also docs of #getErrors() * - * @param filename path to file from which to load the script source code - * @param scriptName name to assign to the script once it's created + * @param source module source code + * @param config configuration options, e.g. for module dependencies + * @param moduleName name to assign to the module once it's created * @return a pointer to the created object or nullptr if * something went wrong during creation. In that case, use #getErrors() to obtain errors. * The script can be destroyed by calling the #destroy method */ - RLOGIC_API LuaScript* createLuaScriptFromFile(std::string_view filename, std::string_view scriptName = ""); - - /** - * Creates a new Lua script from a source string. Refer to the #rlogic::LuaScript class - * for requirements which Lua scripts must fulfill in order to be added to the #LogicEngine. + RLOGIC_API LuaModule* createLuaModule( + std::string_view source, + const LuaConfig& config = {}, + std::string_view moduleName = ""); + + /** + * Extracts dependencies from a Lua script or module source code so that the corresponding + * modules can be provided when creating #rlogic::LuaScript or #rlogic::LuaModule. + * + * Any #rlogic::LuaScript or #rlogic::LuaModule which has a module dependency, + * i.e. it requires another #rlogic::LuaModule for it to work, must explicitly declare these + * dependencies directly in their source code by calling function 'modules' in global space + * and pass list of module names it depends on, for example: + * \code{.lua} + * modules("foo", "bar") + * function interface() + * OUT.x = foo.myType() + * end + * function run() + * OUT.x = bar.doSth() + * end + * \endcode + * The 'modules' function does not affect any other part of the source code in any way, + * it is used only for the purpose of explicit declaration and extraction of its dependencies. + * + * Please note that script runtime errors are ignored during extraction. In case a runtime + * error prevents the 'modules' function to be called, this method will still succeed + * but will not extract any modules, i.e. will not call \c callbackFunc. It is therefore + * highly recommended to put the modules declaration always at the beginning of every script + * before any other code so it will get executed even if there is runtime error later in the code. * * Attention! This method clears all previous errors! See also docs of #getErrors() * - * @param source the Lua source code - * @param scriptName name to assign to the script once it's created - * @return a pointer to the created object or nullptr if - * something went wrong during creation. In that case, use #getErrors() to obtain errors. - * The script can be destroyed by calling the #destroy method + * @param source source code of module or script to parse for dependencies + * @param callbackFunc function callback will be called for each dependency found + * @return \c true if extraction succeeded (also if no dependencies found) or \c false if + * something went wrong. In that case, use #getErrors() to obtain errors. */ - RLOGIC_API LuaScript* createLuaScriptFromSource(std::string_view source, std::string_view scriptName = ""); + RLOGIC_API bool extractLuaDependencies( + std::string_view source, + const std::function& callbackFunc); /** * Creates a new #rlogic::RamsesNodeBinding which can be used to set the properties of a Ramses Node object. @@ -356,10 +428,14 @@ namespace rlogic * Destroys an instance of an object created with #LogicEngine. * All objects created using #LogicEngine derive from a base class #rlogic::LogicObject * and can be destroyed using this method. + * * In case of a #rlogic::LogicNode and its derived classes, if any links are connected to this #rlogic::LogicNode, * they will be destroyed too. Note that after this call, the execution order of #rlogic::LogicNode may change! See the * docs of #link and #unlink for more information. - * In case of a #rlogic::DataArray, destroy will fail it is used in any #rlogic::AnimationNode's #rlogic::AnimationChannel. + * + * In case of a #rlogic::DataArray, destroy will fail if it is used in any #rlogic::AnimationNode's #rlogic::AnimationChannel. + * + * In case of a #rlogic::LuaModule, destroy will fail if it is used in any #rlogic::LuaScript. * * Attention! This method clears all previous errors! See also docs of #getErrors() * @@ -442,7 +518,7 @@ namespace rlogic * * @param other logic engine to move from */ - LogicEngine(LogicEngine&& other) noexcept; + RLOGIC_API LogicEngine(LogicEngine&& other) noexcept; /** * Assignment operator of LogicEngine is deleted because logic engines hold named resources and are not supposed to be copied @@ -456,7 +532,7 @@ namespace rlogic * * @param other logic engine to move from */ - LogicEngine& operator=(LogicEngine&& other) noexcept; + RLOGIC_API LogicEngine& operator=(LogicEngine&& other) noexcept; /** * Implementation detail of LogicEngine diff --git a/include/ramses-logic/LuaConfig.h b/include/ramses-logic/LuaConfig.h new file mode 100644 index 0000000..9f466ce --- /dev/null +++ b/include/ramses-logic/LuaConfig.h @@ -0,0 +1,97 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#pragma once + +#include "ramses-logic/APIExport.h" +#include "ramses-logic/EStandardModule.h" + +#include +#include + +namespace rlogic::internal +{ + class LuaConfigImpl; +} + +namespace rlogic +{ + class LuaModule; + + /** + * Holds configuration settings for Lua script and module creation. Can be default-constructed, moved + * and copied. + */ + class LuaConfig + { + public: + RLOGIC_API LuaConfig() noexcept; + + /** + * Adds a #rlogic::LuaModule as a dependency to be added when this config is used for script or module + * creation. The \p aliasName can be any valid Lua label which must obey following rules: + * - can't use the same label twice in the same #LuaConfig object + * - can't use standard module names (math, string etc.) + * + * The \p moduleInstance provided can be any module. You can't reference modules from + * different #rlogic::LogicEngine instances and the referenced modules must be from the same instance + * on which the config is used for script creation. + * + * @param aliasName the alias name under which the dependency will be mapped into the parent script/module + * @param moduleInstance the dependency module to map + * @return true if the dependency was added successfully, false otherwise + * In case of an error, check the logs. + */ + RLOGIC_API bool addDependency(std::string_view aliasName, const LuaModule& moduleInstance); + + /** + * Adds a standard module dependency. The module is mapped under a name as documented in #rlogic::EStandardModule. + * + * @param stdModule the standard module which will be mapped into the parent script/module + * @return true if the standard module was added successfully, false otherwise + * In case of an error, check the logs. + */ + RLOGIC_API bool addStandardModuleDependency(EStandardModule stdModule); + + /** + * Destructor of #LuaConfig + */ + RLOGIC_API ~LuaConfig() noexcept; + + /** + * Copy Constructor of #LuaConfig + * @param other the other #LuaConfig to copy from + */ + RLOGIC_API LuaConfig(const LuaConfig& other); + + /** + * Move Constructor of #LuaConfig + * @param other the other #LuaConfig to move from + */ + RLOGIC_API LuaConfig(LuaConfig&& other) noexcept; + + /** + * Assignment operator of #LuaConfig + * @param other the other #LuaConfig to copy from + * @return self + */ + RLOGIC_API LuaConfig& operator=(const LuaConfig& other); + + /** + * Move assignment operator of #LuaConfig + * @param other the other #LuaConfig to move from + * @return self + */ + RLOGIC_API LuaConfig& operator=(LuaConfig&& other) noexcept; + + /** + * Implementation detail of #LuaConfig + */ + std::unique_ptr m_impl; + }; +} diff --git a/include/ramses-logic/LuaModule.h b/include/ramses-logic/LuaModule.h new file mode 100644 index 0000000..1f4142c --- /dev/null +++ b/include/ramses-logic/LuaModule.h @@ -0,0 +1,98 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#pragma once + +#include "ramses-logic/LogicObject.h" + +namespace rlogic::internal +{ + class LuaModuleImpl; +} + +namespace rlogic +{ + /** + * #LuaModule represents Lua source code in form of a reusable Lua module + * which can be used in Lua scripts of one or more #rlogic::LuaScript instances. + * Lua modules are expected to follow these guidelines https://www.tutorialspoint.com/lua/lua_modules.htm + * and have some limitations: + * - the name of the module given when creating it is only used to differentiate between #LuaModule + * instances on Ramses logic API, not to access them from a script or to resolve a file on a filesystem + * - the module is not supposed to be resolved with the 'require' keyword in Lua scripts where used + * (due to security concerns) but must be provided explicitly when creating #rlogic::LuaScript instance + * #rlogic::LuaModule source code is loaded into its own Lua environment and is accessible in other #rlogic::LuaScript + * and/or #LuaModule instances in their own environments under the alias name given when creating those. + * + * #LuaModule can also be used to help provide property types for #rlogic::LuaScript interface declarations, + * for example a module with a 'struct' type: + * \code{.lua} + * local mytypes = {} + * function mytypes.mystruct() + * return { + * name = STRING, + * address = + * { + * street = STRING, + * number = INT + * } + * } + * end + * return mytypes + * \endcode + * And script using above module to define its interface: + * \code{.lua} + * function interface() + * IN.input_struct = mytypes.mystruct() + * OUT.output_struct = mytypes.mystruct() + * end + * \endcode + * + * The labels (INT, FLOAT, etc.) are reserved keywords and must not be overwritten for other purposes (e.g. name of variable or function). + */ + class LuaModule : public LogicObject + { + public: + /** + * Destructor of #LuaModule + */ + ~LuaModule() noexcept override; + + /** + * Copy Constructor of #LuaModule is deleted because #LuaModule is not supposed to be copied + */ + LuaModule(const LuaModule&) = delete; + + /** + * Move Constructor of #LuaModule is deleted because #LuaModule is not supposed to be moved + */ + LuaModule(LuaModule&&) = delete; + + /** + * Assignment operator of #LuaModule is deleted because #LuaModule is not supposed to be copied + */ + LuaModule& operator=(const LuaModule&) = delete; + + /** + * Move assignment operator of #LuaModule is deleted because #LuaModule is not supposed to be moved + */ + LuaModule& operator=(LuaModule&&) = delete; + + /** + * Implementation detail of #LuaModule + */ + internal::LuaModuleImpl& m_impl; + + /** + * Internal constructor of #LuaModule. Use #rlogic::LogicEngine::createLuaModule for user code. + * + * @param impl implementation details of the #LuaModule + */ + explicit LuaModule(std::unique_ptr impl) noexcept; + }; +} diff --git a/include/ramses-logic/LuaScript.h b/include/ramses-logic/LuaScript.h index 80025c0..7f308b4 100644 --- a/include/ramses-logic/LuaScript.h +++ b/include/ramses-logic/LuaScript.h @@ -50,6 +50,7 @@ namespace rlogic * - T obeys the same rules as TYPE, except T can not be an ARRAY itself * - T can be a struct, i.e. arrays of structs are supported * - Each property must have a name (string) - other types like number, bool etc. are not supported as keys + * - TYPE can also be defined in a module, see #rlogic::LuaModule for details * - the run() function only accesses the IN and OUT global symbols and the properties defined by it * * Violating any of these requirements will result in errors, which can be obtained by calling @@ -57,6 +58,8 @@ namespace rlogic * The LuaScript object encapsulates a Lua environment (see official Lua docs) which strips all * global table entries after the script is loaded to the Lua state, and leaves only the run() * function. + * Note that none of the TYPE labels should be used in user code (outside of run() at least) + * for other than interface definition purposes, see #rlogic::LuaModule for details. * * See also the full documentation at https://ramses-logic.readthedocs.io/en/latest/api.html for more details on Lua and * its interaction with C++. @@ -65,13 +68,6 @@ namespace rlogic { public: - /** - * Returns the filename provided when the script was created - * - * @return the filename of the script - */ - [[nodiscard]] RLOGIC_API std::string_view getFilename() const; - /** * Overrides the lua print function with a custom function. Each time "print" is used * inside a lua script, the function will be called. Because the lua "print" function allows diff --git a/include/ramses-logic/Property.h b/include/ramses-logic/Property.h index fbcf7e6..8309c6a 100644 --- a/include/ramses-logic/Property.h +++ b/include/ramses-logic/Property.h @@ -136,7 +136,7 @@ namespace rlogic * * @return true if the property is an input and is linked, false otherwise. */ - [[nodiscard]] bool isLinked() const; + [[nodiscard]] RLOGIC_API bool isLinked() const; /** * Constructor of Property. User is not supposed to call this - properties are created by other factory classes diff --git a/lib/flatbuffers/generated/ApiObjectsGen.h b/lib/flatbuffers/generated/ApiObjectsGen.h index b943e69..fe942a8 100644 --- a/lib/flatbuffers/generated/ApiObjectsGen.h +++ b/lib/flatbuffers/generated/ApiObjectsGen.h @@ -9,6 +9,7 @@ #include "AnimationNodeGen.h" #include "DataArrayGen.h" #include "LinkGen.h" +#include "LuaModuleGen.h" #include "LuaScriptGen.h" #include "PropertyGen.h" #include "RamsesAppearanceBindingGen.h" @@ -32,7 +33,8 @@ struct ApiObjects FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_CAMERABINDINGS = 10, VT_DATAARRAYS = 12, VT_ANIMATIONNODES = 14, - VT_LINKS = 16 + VT_LINKS = 16, + VT_LUAMODULES = 18 }; const flatbuffers::Vector> *luaScripts() const { return GetPointer> *>(VT_LUASCRIPTS); @@ -55,6 +57,9 @@ struct ApiObjects FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const flatbuffers::Vector> *links() const { return GetPointer> *>(VT_LINKS); } + const flatbuffers::Vector> *luaModules() const { + return GetPointer> *>(VT_LUAMODULES); + } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_LUASCRIPTS) && @@ -78,6 +83,9 @@ struct ApiObjects FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VerifyOffset(verifier, VT_LINKS) && verifier.VerifyVector(links()) && verifier.VerifyVectorOfTables(links()) && + VerifyOffset(verifier, VT_LUAMODULES) && + verifier.VerifyVector(luaModules()) && + verifier.VerifyVectorOfTables(luaModules()) && verifier.EndTable(); } }; @@ -107,6 +115,9 @@ struct ApiObjectsBuilder { void add_links(flatbuffers::Offset>> links) { fbb_.AddOffset(ApiObjects::VT_LINKS, links); } + void add_luaModules(flatbuffers::Offset>> luaModules) { + fbb_.AddOffset(ApiObjects::VT_LUAMODULES, luaModules); + } explicit ApiObjectsBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); @@ -127,8 +138,10 @@ inline flatbuffers::Offset CreateApiObjects( flatbuffers::Offset>> cameraBindings = 0, flatbuffers::Offset>> dataArrays = 0, flatbuffers::Offset>> animationNodes = 0, - flatbuffers::Offset>> links = 0) { + flatbuffers::Offset>> links = 0, + flatbuffers::Offset>> luaModules = 0) { ApiObjectsBuilder builder_(_fbb); + builder_.add_luaModules(luaModules); builder_.add_links(links); builder_.add_animationNodes(animationNodes); builder_.add_dataArrays(dataArrays); @@ -152,7 +165,8 @@ inline flatbuffers::Offset CreateApiObjectsDirect( const std::vector> *cameraBindings = nullptr, const std::vector> *dataArrays = nullptr, const std::vector> *animationNodes = nullptr, - const std::vector> *links = nullptr) { + const std::vector> *links = nullptr, + const std::vector> *luaModules = nullptr) { auto luaScripts__ = luaScripts ? _fbb.CreateVector>(*luaScripts) : 0; auto nodeBindings__ = nodeBindings ? _fbb.CreateVector>(*nodeBindings) : 0; auto appearanceBindings__ = appearanceBindings ? _fbb.CreateVector>(*appearanceBindings) : 0; @@ -160,6 +174,7 @@ inline flatbuffers::Offset CreateApiObjectsDirect( auto dataArrays__ = dataArrays ? _fbb.CreateVector>(*dataArrays) : 0; auto animationNodes__ = animationNodes ? _fbb.CreateVector>(*animationNodes) : 0; auto links__ = links ? _fbb.CreateVector>(*links) : 0; + auto luaModules__ = luaModules ? _fbb.CreateVector>(*luaModules) : 0; return rlogic_serialization::CreateApiObjects( _fbb, luaScripts__, @@ -168,7 +183,8 @@ inline flatbuffers::Offset CreateApiObjectsDirect( cameraBindings__, dataArrays__, animationNodes__, - links__); + links__, + luaModules__); } } // namespace rlogic_serialization diff --git a/lib/flatbuffers/generated/LogicEngineGen.h b/lib/flatbuffers/generated/LogicEngineGen.h index c47d724..2c92632 100644 --- a/lib/flatbuffers/generated/LogicEngineGen.h +++ b/lib/flatbuffers/generated/LogicEngineGen.h @@ -10,6 +10,7 @@ #include "ApiObjectsGen.h" #include "DataArrayGen.h" #include "LinkGen.h" +#include "LuaModuleGen.h" #include "LuaScriptGen.h" #include "PropertyGen.h" #include "RamsesAppearanceBindingGen.h" diff --git a/lib/flatbuffers/generated/LuaModuleGen.h b/lib/flatbuffers/generated/LuaModuleGen.h new file mode 100644 index 0000000..74f38d4 --- /dev/null +++ b/lib/flatbuffers/generated/LuaModuleGen.h @@ -0,0 +1,191 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_LUAMODULE_RLOGIC_SERIALIZATION_H_ +#define FLATBUFFERS_GENERATED_LUAMODULE_RLOGIC_SERIALIZATION_H_ + +#include "flatbuffers/flatbuffers.h" + +namespace rlogic_serialization { + +struct LuaModule; +struct LuaModuleBuilder; + +struct LuaModuleUsage; +struct LuaModuleUsageBuilder; + +struct LuaModule FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef LuaModuleBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_SOURCE = 6, + VT_DEPENDENCIES = 8, + VT_STANDARDMODULES = 10 + }; + const flatbuffers::String *name() const { + return GetPointer(VT_NAME); + } + const flatbuffers::String *source() const { + return GetPointer(VT_SOURCE); + } + const flatbuffers::Vector> *dependencies() const { + return GetPointer> *>(VT_DEPENDENCIES); + } + const flatbuffers::Vector *standardModules() const { + return GetPointer *>(VT_STANDARDMODULES); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_NAME) && + verifier.VerifyString(name()) && + VerifyOffset(verifier, VT_SOURCE) && + verifier.VerifyString(source()) && + VerifyOffset(verifier, VT_DEPENDENCIES) && + verifier.VerifyVector(dependencies()) && + verifier.VerifyVectorOfTables(dependencies()) && + VerifyOffset(verifier, VT_STANDARDMODULES) && + verifier.VerifyVector(standardModules()) && + verifier.EndTable(); + } +}; + +struct LuaModuleBuilder { + typedef LuaModule Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_name(flatbuffers::Offset name) { + fbb_.AddOffset(LuaModule::VT_NAME, name); + } + void add_source(flatbuffers::Offset source) { + fbb_.AddOffset(LuaModule::VT_SOURCE, source); + } + void add_dependencies(flatbuffers::Offset>> dependencies) { + fbb_.AddOffset(LuaModule::VT_DEPENDENCIES, dependencies); + } + void add_standardModules(flatbuffers::Offset> standardModules) { + fbb_.AddOffset(LuaModule::VT_STANDARDMODULES, standardModules); + } + explicit LuaModuleBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + LuaModuleBuilder &operator=(const LuaModuleBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateLuaModule( + flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset name = 0, + flatbuffers::Offset source = 0, + flatbuffers::Offset>> dependencies = 0, + flatbuffers::Offset> standardModules = 0) { + LuaModuleBuilder builder_(_fbb); + builder_.add_standardModules(standardModules); + builder_.add_dependencies(dependencies); + builder_.add_source(source); + builder_.add_name(name); + return builder_.Finish(); +} + +struct LuaModule::Traits { + using type = LuaModule; + static auto constexpr Create = CreateLuaModule; +}; + +inline flatbuffers::Offset CreateLuaModuleDirect( + flatbuffers::FlatBufferBuilder &_fbb, + const char *name = nullptr, + const char *source = nullptr, + const std::vector> *dependencies = nullptr, + const std::vector *standardModules = nullptr) { + auto name__ = name ? _fbb.CreateString(name) : 0; + auto source__ = source ? _fbb.CreateString(source) : 0; + auto dependencies__ = dependencies ? _fbb.CreateVector>(*dependencies) : 0; + auto standardModules__ = standardModules ? _fbb.CreateVector(*standardModules) : 0; + return rlogic_serialization::CreateLuaModule( + _fbb, + name__, + source__, + dependencies__, + standardModules__); +} + +struct LuaModuleUsage FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + typedef LuaModuleUsageBuilder Builder; + struct Traits; + enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE { + VT_NAME = 4, + VT_MODULE_ = 6 + }; + const flatbuffers::String *name() const { + return GetPointer(VT_NAME); + } + const rlogic_serialization::LuaModule *module_() const { + return GetPointer(VT_MODULE_); + } + bool Verify(flatbuffers::Verifier &verifier) const { + return VerifyTableStart(verifier) && + VerifyOffset(verifier, VT_NAME) && + verifier.VerifyString(name()) && + VerifyOffset(verifier, VT_MODULE_) && + verifier.VerifyTable(module_()) && + verifier.EndTable(); + } +}; + +struct LuaModuleUsageBuilder { + typedef LuaModuleUsage Table; + flatbuffers::FlatBufferBuilder &fbb_; + flatbuffers::uoffset_t start_; + void add_name(flatbuffers::Offset name) { + fbb_.AddOffset(LuaModuleUsage::VT_NAME, name); + } + void add_module_(flatbuffers::Offset module_) { + fbb_.AddOffset(LuaModuleUsage::VT_MODULE_, module_); + } + explicit LuaModuleUsageBuilder(flatbuffers::FlatBufferBuilder &_fbb) + : fbb_(_fbb) { + start_ = fbb_.StartTable(); + } + LuaModuleUsageBuilder &operator=(const LuaModuleUsageBuilder &); + flatbuffers::Offset Finish() { + const auto end = fbb_.EndTable(start_); + auto o = flatbuffers::Offset(end); + return o; + } +}; + +inline flatbuffers::Offset CreateLuaModuleUsage( + flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset name = 0, + flatbuffers::Offset module_ = 0) { + LuaModuleUsageBuilder builder_(_fbb); + builder_.add_module_(module_); + builder_.add_name(name); + return builder_.Finish(); +} + +struct LuaModuleUsage::Traits { + using type = LuaModuleUsage; + static auto constexpr Create = CreateLuaModuleUsage; +}; + +inline flatbuffers::Offset CreateLuaModuleUsageDirect( + flatbuffers::FlatBufferBuilder &_fbb, + const char *name = nullptr, + flatbuffers::Offset module_ = 0) { + auto name__ = name ? _fbb.CreateString(name) : 0; + return rlogic_serialization::CreateLuaModuleUsage( + _fbb, + name__, + module_); +} + +} // namespace rlogic_serialization + +#endif // FLATBUFFERS_GENERATED_LUAMODULE_RLOGIC_SERIALIZATION_H_ diff --git a/lib/flatbuffers/generated/LuaScriptGen.h b/lib/flatbuffers/generated/LuaScriptGen.h index f2f70e8..2899660 100644 --- a/lib/flatbuffers/generated/LuaScriptGen.h +++ b/lib/flatbuffers/generated/LuaScriptGen.h @@ -6,6 +6,7 @@ #include "flatbuffers/flatbuffers.h" +#include "LuaModuleGen.h" #include "PropertyGen.h" namespace rlogic_serialization { @@ -21,7 +22,9 @@ struct LuaScript FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { VT_FILENAME = 6, VT_LUASOURCECODE = 8, VT_ROOTINPUT = 10, - VT_ROOTOUTPUT = 12 + VT_ROOTOUTPUT = 12, + VT_DEPENDENCIES = 14, + VT_STANDARDMODULES = 16 }; const flatbuffers::String *name() const { return GetPointer(VT_NAME); @@ -38,6 +41,12 @@ struct LuaScript FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { const rlogic_serialization::Property *rootOutput() const { return GetPointer(VT_ROOTOUTPUT); } + const flatbuffers::Vector> *dependencies() const { + return GetPointer> *>(VT_DEPENDENCIES); + } + const flatbuffers::Vector *standardModules() const { + return GetPointer *>(VT_STANDARDMODULES); + } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && VerifyOffset(verifier, VT_NAME) && @@ -50,6 +59,11 @@ struct LuaScript FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { verifier.VerifyTable(rootInput()) && VerifyOffset(verifier, VT_ROOTOUTPUT) && verifier.VerifyTable(rootOutput()) && + VerifyOffset(verifier, VT_DEPENDENCIES) && + verifier.VerifyVector(dependencies()) && + verifier.VerifyVectorOfTables(dependencies()) && + VerifyOffset(verifier, VT_STANDARDMODULES) && + verifier.VerifyVector(standardModules()) && verifier.EndTable(); } }; @@ -73,6 +87,12 @@ struct LuaScriptBuilder { void add_rootOutput(flatbuffers::Offset rootOutput) { fbb_.AddOffset(LuaScript::VT_ROOTOUTPUT, rootOutput); } + void add_dependencies(flatbuffers::Offset>> dependencies) { + fbb_.AddOffset(LuaScript::VT_DEPENDENCIES, dependencies); + } + void add_standardModules(flatbuffers::Offset> standardModules) { + fbb_.AddOffset(LuaScript::VT_STANDARDMODULES, standardModules); + } explicit LuaScriptBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); @@ -91,8 +111,12 @@ inline flatbuffers::Offset CreateLuaScript( flatbuffers::Offset filename = 0, flatbuffers::Offset luaSourceCode = 0, flatbuffers::Offset rootInput = 0, - flatbuffers::Offset rootOutput = 0) { + flatbuffers::Offset rootOutput = 0, + flatbuffers::Offset>> dependencies = 0, + flatbuffers::Offset> standardModules = 0) { LuaScriptBuilder builder_(_fbb); + builder_.add_standardModules(standardModules); + builder_.add_dependencies(dependencies); builder_.add_rootOutput(rootOutput); builder_.add_rootInput(rootInput); builder_.add_luaSourceCode(luaSourceCode); @@ -112,17 +136,23 @@ inline flatbuffers::Offset CreateLuaScriptDirect( const char *filename = nullptr, const char *luaSourceCode = nullptr, flatbuffers::Offset rootInput = 0, - flatbuffers::Offset rootOutput = 0) { + flatbuffers::Offset rootOutput = 0, + const std::vector> *dependencies = nullptr, + const std::vector *standardModules = nullptr) { auto name__ = name ? _fbb.CreateString(name) : 0; auto filename__ = filename ? _fbb.CreateString(filename) : 0; auto luaSourceCode__ = luaSourceCode ? _fbb.CreateString(luaSourceCode) : 0; + auto dependencies__ = dependencies ? _fbb.CreateVector>(*dependencies) : 0; + auto standardModules__ = standardModules ? _fbb.CreateVector(*standardModules) : 0; return rlogic_serialization::CreateLuaScript( _fbb, name__, filename__, luaSourceCode__, rootInput, - rootOutput); + rootOutput, + dependencies__, + standardModules__); } } // namespace rlogic_serialization diff --git a/lib/flatbuffers/schemas/ApiObjects.fbs b/lib/flatbuffers/schemas/ApiObjects.fbs index d4528b1..7a51e05 100644 --- a/lib/flatbuffers/schemas/ApiObjects.fbs +++ b/lib/flatbuffers/schemas/ApiObjects.fbs @@ -6,6 +6,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. // ------------------------------------------------------------------------- +include "LuaModule.fbs"; include "LuaScript.fbs"; include "RamsesNodeBinding.fbs"; include "RamsesAppearanceBinding.fbs"; @@ -25,4 +26,5 @@ table ApiObjects dataArrays:[DataArray]; animationNodes:[AnimationNode]; links:[Link]; + luaModules:[LuaModule]; } diff --git a/lib/flatbuffers/schemas/LuaModule.fbs b/lib/flatbuffers/schemas/LuaModule.fbs new file mode 100644 index 0000000..b36b0ec --- /dev/null +++ b/lib/flatbuffers/schemas/LuaModule.fbs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +namespace rlogic_serialization; + +table LuaModule +{ + name:string; + source:string; + dependencies:[LuaModuleUsage]; + standardModules:[uint8]; +} + +table LuaModuleUsage +{ + name:string; + module:LuaModule; +} diff --git a/lib/flatbuffers/schemas/LuaScript.fbs b/lib/flatbuffers/schemas/LuaScript.fbs index a55e1cf..444d3b1 100644 --- a/lib/flatbuffers/schemas/LuaScript.fbs +++ b/lib/flatbuffers/schemas/LuaScript.fbs @@ -7,15 +7,18 @@ // ------------------------------------------------------------------------- include "Property.fbs"; +include "LuaModule.fbs"; namespace rlogic_serialization; table LuaScript { name:string; - filename:string; + filename:string; // TODO VersionBreak remove filename, it's obsolete luaSourceCode:string; // These are cached because they hold the property values rootInput:Property; rootOutput:Property; + dependencies:[LuaModuleUsage]; + standardModules:[uint8]; } diff --git a/lib/impl/LogicEngine.cpp b/lib/impl/LogicEngine.cpp index 993e349..b9d99db 100644 --- a/lib/impl/LogicEngine.cpp +++ b/lib/impl/LogicEngine.cpp @@ -8,6 +8,7 @@ #include "ramses-logic/LogicEngine.h" #include "ramses-logic/LuaScript.h" +#include "ramses-logic/LuaModule.h" #include "ramses-logic/RamsesNodeBinding.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/RamsesCameraBinding.h" @@ -15,6 +16,8 @@ #include "ramses-logic/AnimationNode.h" #include "impl/LogicEngineImpl.h" +#include "impl/LuaConfigImpl.h" +#include "internals/ApiObjects.h" #include @@ -31,14 +34,14 @@ namespace rlogic LogicEngine& LogicEngine::operator=(LogicEngine&& other) noexcept = default; - LuaScript* LogicEngine::createLuaScriptFromFile(std::string_view filename, std::string_view scriptName) + Collection LogicEngine::scripts() const { - return m_impl->createLuaScriptFromFile(filename, scriptName); + return Collection(m_impl->getApiObjects().getScripts()); } - Collection LogicEngine::scripts() const + Collection LogicEngine::luaModules() const { - return Collection(m_impl->getApiObjects().getScripts()); + return Collection(m_impl->getApiObjects().getLuaModules()); } Collection LogicEngine::ramsesNodeBindings() const @@ -93,6 +96,15 @@ namespace rlogic return findObject(m_impl->getApiObjects().getScripts(), name); } + const LuaModule* LogicEngine::findLuaModule(std::string_view name) const + { + return findObject(m_impl->getApiObjects().getLuaModules(), name); + } + LuaModule* LogicEngine::findLuaModule(std::string_view name) + { + return findObject(m_impl->getApiObjects().getLuaModules(), name); + } + const RamsesNodeBinding* LogicEngine::findNodeBinding(std::string_view name) const { return findObject(m_impl->getApiObjects().getNodeBindings(), name); @@ -138,9 +150,19 @@ namespace rlogic return findObject(m_impl->getApiObjects().getAnimationNodes(), name); } - LuaScript* LogicEngine::createLuaScriptFromSource(std::string_view source, std::string_view scriptName) + LuaScript* LogicEngine::createLuaScript(std::string_view source, const LuaConfig& config, std::string_view scriptName) + { + return m_impl->createLuaScript(source, *config.m_impl, scriptName); + } + + LuaModule* LogicEngine::createLuaModule(std::string_view source, const LuaConfig& config, std::string_view moduleName) + { + return m_impl->createLuaModule(source, *config.m_impl, moduleName); + } + + bool LogicEngine::extractLuaDependencies(std::string_view source, const std::function& callbackFunc) { - return m_impl->createLuaScriptFromSource(source, scriptName); + return m_impl->extractLuaDependencies(source, callbackFunc); } RamsesNodeBinding* LogicEngine::createRamsesNodeBinding(ramses::Node& ramsesNode, ERotationType rotationType /* = ERotationType::Euler_XYZ*/, std::string_view name) diff --git a/lib/impl/LogicEngineImpl.cpp b/lib/impl/LogicEngineImpl.cpp index 139c655..3555f05 100644 --- a/lib/impl/LogicEngineImpl.cpp +++ b/lib/impl/LogicEngineImpl.cpp @@ -7,13 +7,16 @@ // ------------------------------------------------------------------------- #include "impl/LogicEngineImpl.h" -#include "impl/LuaScriptImpl.h" - +#include "impl/LogicNodeImpl.h" #include "impl/LoggerImpl.h" +#include "impl/LuaModuleImpl.h" +#include "impl/LuaConfigImpl.h" + #include "internals/FileUtils.h" #include "internals/TypeUtils.h" #include "internals/FileFormatVersions.h" #include "internals/RamsesObjectResolver.h" +#include "internals/ApiObjects.h" // TODO Violin remove these header dependencies #include "ramses-logic/RamsesNodeBinding.h" @@ -21,6 +24,8 @@ #include "ramses-logic/RamsesCameraBinding.h" #include "ramses-logic/AnimationNode.h" #include "ramses-logic/DataArray.h" +#include "ramses-logic/LuaScript.h" +#include "ramses-logic/LuaModule.h" #include "ramses-logic-build-config.h" #include "generated/LogicEngineGen.h" @@ -36,59 +41,53 @@ namespace rlogic::internal { LogicEngineImpl::LogicEngineImpl() - : m_luaState(std::make_unique()) + : m_apiObjects(std::make_unique()) { } LogicEngineImpl::~LogicEngineImpl() noexcept = default; - LuaScript* LogicEngineImpl::createLuaScriptFromFile(std::string_view filename, std::string_view scriptName) + LuaScript* LogicEngineImpl::createLuaScript(std::string_view source, const LuaConfigImpl& config, std::string_view scriptName) { m_errors.clear(); + return m_apiObjects->createLuaScript(source, config, scriptName, m_errors); + } - std::string filenameStr{ filename }; - std::ifstream iStream; - iStream.open(filenameStr, std::ios_base::in); - if (iStream.fail()) - { - m_errors.add("Failed opening file " + filenameStr + "!", nullptr); - return nullptr; - } - - std::string source(std::istreambuf_iterator(iStream), std::istreambuf_iterator{}); - std::optional compiledScript = LuaCompilationUtils::Compile(*m_luaState, std::move(source), scriptName, std::move(filenameStr), m_errors); - if (!compiledScript) - return nullptr; - - return m_apiObjects.createLuaScript(std::move(*compiledScript), scriptName); + LuaModule* LogicEngineImpl::createLuaModule(std::string_view source, const LuaConfigImpl& config, std::string_view moduleName) + { + m_errors.clear(); + return m_apiObjects->createLuaModule(source, config, moduleName, m_errors); } - LuaScript* LogicEngineImpl::createLuaScriptFromSource(std::string_view source, std::string_view scriptName) + bool LogicEngineImpl::extractLuaDependencies(std::string_view source, const std::function& callbackFunc) { m_errors.clear(); - std::optional compiledScript = LuaCompilationUtils::Compile(*m_luaState, std::string{ source }, scriptName, "", m_errors); - if (!compiledScript) - return nullptr; + const std::optional> extractedDependencies = LuaCompilationUtils::ExtractModuleDependencies(source, m_errors); + if (!extractedDependencies) + return false; + + for (const auto& dep : *extractedDependencies) + callbackFunc(dep); - return m_apiObjects.createLuaScript(std::move(*compiledScript), scriptName); + return true; } RamsesNodeBinding* LogicEngineImpl::createRamsesNodeBinding(ramses::Node& ramsesNode, ERotationType rotationType, std::string_view name) { m_errors.clear(); - return m_apiObjects.createRamsesNodeBinding(ramsesNode, rotationType, name); + return m_apiObjects->createRamsesNodeBinding(ramsesNode, rotationType, name); } RamsesAppearanceBinding* LogicEngineImpl::createRamsesAppearanceBinding(ramses::Appearance& ramsesAppearance, std::string_view name) { m_errors.clear(); - return m_apiObjects.createRamsesAppearanceBinding(ramsesAppearance, name); + return m_apiObjects->createRamsesAppearanceBinding(ramsesAppearance, name); } RamsesCameraBinding* LogicEngineImpl::createRamsesCameraBinding(ramses::Camera& ramsesCamera, std::string_view name) { m_errors.clear(); - return m_apiObjects.createRamsesCameraBinding(ramsesCamera, name); + return m_apiObjects->createRamsesCameraBinding(ramsesCamera, name); } template @@ -102,7 +101,7 @@ namespace rlogic::internal return nullptr; } - return m_apiObjects.createDataArray(data, name); + return m_apiObjects->createDataArray(data, name); } rlogic::AnimationNode* LogicEngineImpl::createAnimationNode(const AnimationChannels& channels, std::string_view name) @@ -110,7 +109,7 @@ namespace rlogic::internal m_errors.clear(); auto containsDataArray = [this](const DataArray* da) { - const auto& dataArrays = m_apiObjects.getDataArrays(); + const auto& dataArrays = m_apiObjects->getDataArrays(); const auto it = std::find_if(dataArrays.cbegin(), dataArrays.cend(), [da](const auto& d) { return d.get() == da; }); return it != dataArrays.cend(); @@ -194,18 +193,18 @@ namespace rlogic::internal } } - return m_apiObjects.createAnimationNode(channels, name); + return m_apiObjects->createAnimationNode(channels, name); } bool LogicEngineImpl::destroy(LogicObject& object) { m_errors.clear(); - return m_apiObjects.destroy(object, m_errors); + return m_apiObjects->destroy(object, m_errors); } bool LogicEngineImpl::isLinked(const LogicNode& logicNode) const { - return m_apiObjects.getLogicNodeDependencies().isLinked(logicNode.m_impl); + return m_apiObjects->getLogicNodeDependencies().isLinked(logicNode.m_impl); } void LogicEngineImpl::updateLinksRecursive(Property& inputProperty) @@ -222,7 +221,7 @@ namespace rlogic::internal } else { - const PropertyImpl* output = m_apiObjects.getLogicNodeDependencies().getLinkedOutput(*child.m_impl); + const PropertyImpl* output = m_apiObjects->getLogicNodeDependencies().getLinkedOutput(*child.m_impl); if (nullptr != output) { child.m_impl->setValue(output->getValue(), true); @@ -236,7 +235,7 @@ namespace rlogic::internal m_errors.clear(); LOG_DEBUG("Begin update"); - const std::optional sortedNodes = m_apiObjects.getLogicNodeDependencies().getTopologicallySortedNodes(); + const std::optional sortedNodes = m_apiObjects->getLogicNodeDependencies().getTopologicallySortedNodes(); if (!sortedNodes) { m_errors.add("Failed to sort logic nodes based on links between their properties. Create a loop-free link graph before calling update()!", nullptr); @@ -263,7 +262,7 @@ namespace rlogic::internal const std::optional potentialError = node.update(); if (potentialError) { - m_errors.add(potentialError->message, m_apiObjects.getApiObject(node)); + m_errors.add(potentialError->message, m_apiObjects->getApiObject(node)); return false; } } @@ -377,8 +376,7 @@ namespace rlogic::internal RamsesObjectResolver ramsesResolver(m_errors, scene); - // TODO Violin also use fresh Lua environment, so that we don't pollute current one when loading failed - std::optional deserializedObjects = ApiObjects::Deserialize(*m_luaState, *logicEngine->apiObjects(), ramsesResolver, dataSourceDescription, m_errors); + std::unique_ptr deserializedObjects = ApiObjects::Deserialize(*logicEngine->apiObjects(), ramsesResolver, dataSourceDescription, m_errors); if (!deserializedObjects) { @@ -386,7 +384,7 @@ namespace rlogic::internal } // No errors -> move data into member - m_apiObjects = std::move(*deserializedObjects); + m_apiObjects = std::move(deserializedObjects); return true; } @@ -395,20 +393,20 @@ namespace rlogic::internal { m_errors.clear(); - if (!m_apiObjects.checkBindingsReferToSameRamsesScene(m_errors)) + if (!m_apiObjects->checkBindingsReferToSameRamsesScene(m_errors)) { m_errors.add("Can't save a logic engine to file while it has references to more than one Ramses scene!", nullptr); return false; } // Refuse save() if logic graph has loops - if (!m_apiObjects.getLogicNodeDependencies().getTopologicallySortedNodes()) + if (!m_apiObjects->getLogicNodeDependencies().getTopologicallySortedNodes()) { m_errors.add("Failed to sort logic nodes based on links between their properties. Create a loop-free link graph before calling saveToFile()!", nullptr); return false; } - if (m_apiObjects.bindingsDirty()) + if (m_apiObjects->bindingsDirty()) { LOG_WARN("Saving logic engine content with manually updated binding values without calling update() will result in those values being lost!"); } @@ -434,7 +432,7 @@ namespace rlogic::internal const auto logicEngine = rlogic_serialization::CreateLogicEngine(builder, ramsesVersionOffset, ramsesLogicVersionOffset, - ApiObjects::Serialize(m_apiObjects, builder)); + ApiObjects::Serialize(*m_apiObjects, builder)); builder.Finish(logicEngine); if (!FileUtils::SaveBinary(std::string(filename), builder.GetBufferPointer(), builder.GetSize())) @@ -450,24 +448,19 @@ namespace rlogic::internal { m_errors.clear(); - return m_apiObjects.getLogicNodeDependencies().link(*sourceProperty.m_impl, *targetProperty.m_impl, m_errors); + return m_apiObjects->getLogicNodeDependencies().link(*sourceProperty.m_impl, *targetProperty.m_impl, m_errors); } bool LogicEngineImpl::unlink(const Property& sourceProperty, const Property& targetProperty) { m_errors.clear(); - return m_apiObjects.getLogicNodeDependencies().unlink(*sourceProperty.m_impl, *targetProperty.m_impl, m_errors); + return m_apiObjects->getLogicNodeDependencies().unlink(*sourceProperty.m_impl, *targetProperty.m_impl, m_errors); } ApiObjects& LogicEngineImpl::getApiObjects() { - return m_apiObjects; - } - - const ApiObjects& LogicEngineImpl::getApiObjects() const - { - return m_apiObjects; + return *m_apiObjects; } template DataArray* LogicEngineImpl::createDataArray(const std::vector&, std::string_view name); diff --git a/lib/impl/LogicEngineImpl.h b/lib/impl/LogicEngineImpl.h index d6dbd8d..dfb1528 100644 --- a/lib/impl/LogicEngineImpl.h +++ b/lib/impl/LogicEngineImpl.h @@ -8,16 +8,14 @@ #pragma once -#include "internals/SolState.h" - #include "internals/LogicNodeDependencies.h" #include "internals/ErrorReporting.h" -#include "internals/ApiObjects.h" +#include "ramses-logic/ERotationType.h" #include "ramses-logic/AnimationTypes.h" #include "ramses-framework-api/RamsesFrameworkTypes.h" -#include +#include #include #include #include @@ -39,6 +37,7 @@ namespace rlogic class DataArray; class AnimationNode; class LuaScript; + class LuaModule; class LogicNode; class Property; } @@ -50,8 +49,10 @@ namespace rlogic_serialization namespace rlogic::internal { + class LuaConfigImpl; class LogicNodeImpl; class RamsesBindingImpl; + class ApiObjects; class LogicEngineImpl { @@ -68,8 +69,9 @@ namespace rlogic::internal LogicEngineImpl& operator=(const LogicEngineImpl& other) = delete; // Public API - LuaScript* createLuaScriptFromFile(std::string_view filename, std::string_view scriptName); - LuaScript* createLuaScriptFromSource(std::string_view source, std::string_view scriptName); + LuaScript* createLuaScript(std::string_view source, const LuaConfigImpl& config, std::string_view scriptName); + LuaModule* createLuaModule(std::string_view source, const LuaConfigImpl& config, std::string_view moduleName); + bool extractLuaDependencies(std::string_view source, const std::function& callbackFunc); RamsesNodeBinding* createRamsesNodeBinding(ramses::Node& ramsesNode, ERotationType rotationType, std::string_view name); RamsesAppearanceBinding* createRamsesAppearanceBinding(ramses::Appearance& ramsesAppearance, std::string_view name); RamsesCameraBinding* createRamsesCameraBinding(ramses::Camera& ramsesCamera, std::string_view name); @@ -79,8 +81,8 @@ namespace rlogic::internal bool destroy(LogicObject& object); - bool update(bool disableDirtyTracking = false); - const std::vector& getErrors() const; + bool update(bool disableDirtyTracking = false); + [[nodiscard]] const std::vector& getErrors() const; bool loadFromFile(std::string_view filename, ramses::Scene* scene, bool enableMemoryVerification); bool loadFromBuffer(const void* rawBuffer, size_t bufferSize, ramses::Scene* scene, bool enableMemoryVerification); @@ -92,11 +94,9 @@ namespace rlogic::internal [[nodiscard]] bool isLinked(const LogicNode& logicNode) const; [[nodiscard]] ApiObjects& getApiObjects(); - [[nodiscard]] const ApiObjects& getApiObjects() const; private: - std::unique_ptr m_luaState; - ApiObjects m_apiObjects; + std::unique_ptr m_apiObjects; ErrorReporting m_errors; void updateLinksRecursive(Property& inputProperty); diff --git a/lib/impl/LuaCompilationUtils.cpp b/lib/impl/LuaCompilationUtils.cpp deleted file mode 100644 index 34d177c..0000000 --- a/lib/impl/LuaCompilationUtils.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// ------------------------------------------------------------------------- -// Copyright (C) 2020 BMW AG -// ------------------------------------------------------------------------- -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. -// ------------------------------------------------------------------------- - -#include "impl/LuaCompilationUtils.h" - -#include "ramses-logic/Property.h" -#include "impl/PropertyImpl.h" -#include "internals/SolState.h" -#include "internals/ErrorReporting.h" -#include "internals/PropertyTypeExtractor.h" -#include "internals/EPropertySemantics.h" -#include "fmt/format.h" - -namespace rlogic::internal -{ - std::optional LuaCompilationUtils::Compile(SolState& solState, std::string source, std::string_view scriptName, std::string filename, ErrorReporting& errorReporting) - { - const std::string chunkname = BuildChunkName(scriptName, filename); - sol::load_result load_result = solState.loadScript(source, chunkname); - - if (!load_result.valid()) - { - sol::error error = load_result; - errorReporting.add(fmt::format("[{}] Error while loading script. Lua stack trace:\n{}", chunkname, error.what()), nullptr); - return std::nullopt; - } - - sol::protected_function mainFunction = load_result; - - sol::environment env = solState.createEnvironment(); - env.set_on(mainFunction); - - sol::protected_function_result main_result = mainFunction(); - if (!main_result.valid()) - { - sol::error error = main_result; - errorReporting.add(error.what(), nullptr); - return std::nullopt; - } - - sol::protected_function intf = env["interface"]; - - if (!intf.valid()) - { - errorReporting.add(fmt::format("[{}] No 'interface' function defined!", chunkname), nullptr); - return std::nullopt; - } - - sol::protected_function run = env["run"]; - - if (!run.valid()) - { - errorReporting.add(fmt::format("[{}] No 'run' function defined!", chunkname), nullptr); - return std::nullopt; - } - - PropertyTypeExtractor inputsExtractor("IN", EPropertyType::Struct); - PropertyTypeExtractor outputsExtractor("OUT", EPropertyType::Struct); - - sol::environment& interfaceEnvironment = solState.getInterfaceExtractionEnvironment(); - - interfaceEnvironment["IN"] = std::ref(inputsExtractor); - interfaceEnvironment["OUT"] = std::ref(outputsExtractor); - - interfaceEnvironment.set_on(intf); - sol::protected_function_result intfResult = intf(); - - interfaceEnvironment["IN"] = sol::lua_nil; - interfaceEnvironment["OUT"] = sol::lua_nil; - - if (!intfResult.valid()) - { - sol::error error = intfResult; - errorReporting.add(fmt::format("[{}] Error while loading script. Lua stack trace:\n{}", chunkname, error.what()), nullptr); - return std::nullopt; - } - - return LuaCompiledScript{ - std::move(source), - std::move(filename), - solState, - std::move(load_result), - std::make_unique(std::make_unique(inputsExtractor.getExtractedTypeData(), EPropertySemantics::ScriptInput)), - std::make_unique(std::make_unique(outputsExtractor.getExtractedTypeData(), EPropertySemantics::ScriptOutput)) - }; - } - - std::string LuaCompilationUtils::BuildChunkName(std::string_view scriptName, std::string_view fileName) - { - std::string chunkname; - if (scriptName.empty()) - { - chunkname = fileName.empty() ? "unknown" : fileName; - } - else - { - if (fileName.empty()) - { - chunkname = scriptName; - } - else - { - chunkname = fileName; - chunkname.append(":"); - chunkname.append(scriptName); - } - } - return chunkname; - } -} diff --git a/lib/impl/LuaConfig.cpp b/lib/impl/LuaConfig.cpp new file mode 100644 index 0000000..d7042c5 --- /dev/null +++ b/lib/impl/LuaConfig.cpp @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "ramses-logic/LuaConfig.h" + +#include "impl/LuaConfigImpl.h" + +namespace rlogic +{ + LuaConfig::LuaConfig() noexcept + : m_impl(std::make_unique()) + { + } + + LuaConfig::~LuaConfig() noexcept = default; + + LuaConfig& LuaConfig::operator=(const LuaConfig& other) + { + m_impl = std::make_unique(*other.m_impl); + return *this; + } + + LuaConfig::LuaConfig(const LuaConfig& other) + { + *this = other; + } + + LuaConfig::LuaConfig(LuaConfig&&) noexcept = default; + LuaConfig& LuaConfig::operator=(LuaConfig&&) noexcept = default; + + bool LuaConfig::addDependency(std::string_view aliasName, const LuaModule& moduleInstance) + { + return m_impl->addDependency(aliasName, moduleInstance); + } + + bool LuaConfig::addStandardModuleDependency(EStandardModule stdModule) + { + return m_impl->addStandardModuleDependency(stdModule); + } +} diff --git a/lib/impl/LuaConfigImpl.cpp b/lib/impl/LuaConfigImpl.cpp new file mode 100644 index 0000000..e8b1766 --- /dev/null +++ b/lib/impl/LuaConfigImpl.cpp @@ -0,0 +1,77 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "impl/LuaConfigImpl.h" +#include "impl/LoggerImpl.h" + +#include "internals/LuaCompilationUtils.h" +#include "internals/SolState.h" + +namespace rlogic::internal +{ + bool LuaConfigImpl::addDependency(std::string_view aliasName, const LuaModule& moduleInstance) + { + if (!LuaCompilationUtils::CheckModuleName(aliasName)) + { + LOG_ERROR("Failed to add dependency '{}'! The alias name should be a valid Lua label.", aliasName); + return false; + } + + if (SolState::IsReservedModuleName(aliasName)) + { + LOG_ERROR("Failed to add dependency '{}'! The alias collides with a standard library name!", aliasName); + return false; + } + + std::string aliasNameStr {aliasName}; + + if (m_modulesMapping.cend() != m_modulesMapping.find(aliasNameStr)) + { + LOG_ERROR("Module dependencies must be uniquely aliased! Alias '{}' is already used!", aliasName); + return false; + } + + m_modulesMapping.emplace(std::move(aliasNameStr), &moduleInstance); + return true; + } + + const ModuleMapping& LuaConfigImpl::getModuleMapping() const + { + return m_modulesMapping; + } + + bool LuaConfigImpl::addStandardModuleDependency(EStandardModule stdModule) + { + if (std::find(m_stdModules.cbegin(), m_stdModules.cend(), stdModule) != m_stdModules.cend()) + { + LOG_ERROR("Standard module {} already added, can't add twice!", stdModule); + return false; + } + + if (stdModule == EStandardModule::All) + { + for (EStandardModule m = EStandardModule::Base; m != EStandardModule::All;) + { + m_stdModules.push_back(m); + m = static_cast(static_cast(m) + 1); + } + } + else + { + m_stdModules.push_back(stdModule); + } + + return true; + } + + const StandardModules& LuaConfigImpl::getStandardModules() const + { + return m_stdModules; + } + +} diff --git a/lib/impl/LuaConfigImpl.h b/lib/impl/LuaConfigImpl.h new file mode 100644 index 0000000..7e72b35 --- /dev/null +++ b/lib/impl/LuaConfigImpl.h @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#pragma once + +#include "ramses-logic/EStandardModule.h" + +#include +#include +#include +#include + +namespace rlogic +{ + class LuaModule; +} + +namespace rlogic::internal +{ + using ModuleMapping = std::unordered_map; + using StandardModules = std::vector; + + class LuaConfigImpl + { + public: + bool addDependency(std::string_view aliasName, const LuaModule& moduleInstance); + bool addStandardModuleDependency(EStandardModule stdModule); + + [[nodiscard]] const ModuleMapping& getModuleMapping() const; + [[nodiscard]] const StandardModules& getStandardModules() const; + + private: + ModuleMapping m_modulesMapping; + StandardModules m_stdModules; + }; +} diff --git a/lib/impl/LuaModule.cpp b/lib/impl/LuaModule.cpp new file mode 100644 index 0000000..9e48572 --- /dev/null +++ b/lib/impl/LuaModule.cpp @@ -0,0 +1,22 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "ramses-logic/LuaModule.h" +#include "impl/LuaModuleImpl.h" + +namespace rlogic +{ + LuaModule::LuaModule(std::unique_ptr impl) noexcept + : LogicObject(std::move(impl)) + /* NOLINTNEXTLINE(cppcoreguidelines-pro-type-static-cast-downcast) */ + , m_impl{ static_cast(*LogicObject::m_impl) } + { + } + + LuaModule::~LuaModule() noexcept = default; +} diff --git a/lib/impl/LuaModuleImpl.cpp b/lib/impl/LuaModuleImpl.cpp new file mode 100644 index 0000000..ac13757 --- /dev/null +++ b/lib/impl/LuaModuleImpl.cpp @@ -0,0 +1,135 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "impl/LuaModuleImpl.h" + +#include "ramses-logic/LuaModule.h" + +#include "internals/LuaCompilationUtils.h" +#include "generated/LuaModuleGen.h" +#include "flatbuffers/flatbuffers.h" +#include "internals/SolState.h" +#include "internals/ErrorReporting.h" +#include "internals/DeserializationMap.h" +#include "internals/SerializationMap.h" +#include "internals/FileFormatVersions.h" +#include + +namespace rlogic::internal +{ + LuaModuleImpl::LuaModuleImpl(LuaCompiledModule module, std::string_view name) + : LogicObjectImpl(name) + , m_sourceCode{ std::move(module.source.sourceCode) } + , m_module{ std::move(module.moduleTable) } + , m_dependencies{std::move(module.source.userModules)} + , m_stdModules {std::move(module.source.stdModules)} + { + assert(m_module != sol::nil); + } + + std::string_view LuaModuleImpl::getSourceCode() const + { + return m_sourceCode; + } + + const sol::table& LuaModuleImpl::getModule() const + { + return m_module; + } + + flatbuffers::Offset LuaModuleImpl::Serialize(const LuaModuleImpl& module, flatbuffers::FlatBufferBuilder& builder, SerializationMap& serializationMap) + { + std::vector> modulesFB; + modulesFB.reserve(module.m_dependencies.size()); + for (const auto& dependency : module.m_dependencies) + { + modulesFB.push_back( + rlogic_serialization::CreateLuaModuleUsage(builder, + builder.CreateString(dependency.first), + serializationMap.resolveLuaModuleOffset(*dependency.second))); + } + + std::vector stdModules; + stdModules.reserve(module.m_stdModules.size()); + for (const EStandardModule stdModule : module.m_stdModules) + { + stdModules.push_back(static_cast(stdModule)); + } + + return rlogic_serialization::CreateLuaModule(builder, + builder.CreateString(module.getName()), + builder.CreateString(module.getSourceCode()), + builder.CreateVector(modulesFB), + builder.CreateVector(stdModules) + ); + } + + std::unique_ptr LuaModuleImpl::Deserialize( + SolState& solState, + const rlogic_serialization::LuaModule& module, + ErrorReporting& errorReporting, + DeserializationMap& deserializationMap) + { + if (!module.name()) + { + errorReporting.add("Fatal error during loading of LuaModule from serialized data: missing name!", nullptr); + return nullptr; + } + + if (!module.source()) + { + errorReporting.add("Fatal error during loading of LuaModule from serialized data: missing source code!", nullptr); + return nullptr; + } + + if (!module.dependencies()) + { + errorReporting.add("Fatal error during loading of LuaModule from serialized data: missing dependencies!", nullptr); + return nullptr; + } + + const std::string_view name = module.name()->string_view(); + std::string source = module.source()->str(); + + StandardModules stdModules; + stdModules.reserve(module.standardModules()->size()); + for (const uint8_t stdModule : *module.standardModules()) + { + stdModules.push_back(static_cast(stdModule)); + } + + ModuleMapping modulesUsed; + modulesUsed.reserve(module.dependencies()->size()); + for (const auto* mod : *module.dependencies()) + { + if (!mod->name() || !mod->module_()) + { + errorReporting.add(fmt::format("Fatal error during loading of LuaModule '{}' module data: missing name or module!", name), nullptr); + return nullptr; + } + const LuaModule& moduleUsed = deserializationMap.resolveLuaModule(*mod->module_()); + modulesUsed.emplace(mod->name()->str(), &moduleUsed); + } + + std::optional compiledModule = LuaCompilationUtils::CompileModule(solState, modulesUsed, stdModules, source, name, errorReporting); + if (!compiledModule) + { + errorReporting.add(fmt::format("Fatal error during loading of LuaModule '{}' from serialized data: failed parsing Lua module source code.", name), nullptr); + return nullptr; + } + + return std::make_unique( + std::move(*compiledModule), + name); + } + + const ModuleMapping& LuaModuleImpl::getDependencies() const + { + return m_dependencies; + } +} diff --git a/lib/impl/LuaModuleImpl.h b/lib/impl/LuaModuleImpl.h new file mode 100644 index 0000000..9c58294 --- /dev/null +++ b/lib/impl/LuaModuleImpl.h @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#pragma once + +#include "impl/LogicObjectImpl.h" +#include "internals/LuaCompilationUtils.h" +#include "internals/SolWrapper.h" +#include + +namespace rlogic_serialization +{ + struct LuaModule; +} + +namespace flatbuffers +{ + template struct Offset; + class FlatBufferBuilder; +} + +namespace rlogic +{ + class LuaModule; +} + +namespace rlogic::internal +{ + class ErrorReporting; + class SolState; + class DeserializationMap; + class SerializationMap; + + class LuaModuleImpl : public LogicObjectImpl + { + public: + LuaModuleImpl(LuaCompiledModule module, std::string_view name); + + [[nodiscard]] std::string_view getSourceCode() const; + [[nodiscard]] const sol::table& getModule() const; + [[nodiscard]] const ModuleMapping& getDependencies() const; + + [[nodiscard]] static flatbuffers::Offset Serialize( + const LuaModuleImpl& module, + flatbuffers::FlatBufferBuilder& builder, + SerializationMap& serializationMap); + + [[nodiscard]] static std::unique_ptr Deserialize( + SolState& solState, + const rlogic_serialization::LuaModule& module, + ErrorReporting& errorReporting, + DeserializationMap& deserializationMap); + + private: + std::string m_sourceCode; + sol::table m_module; + ModuleMapping m_dependencies; + StandardModules m_stdModules; + }; +} diff --git a/lib/impl/LuaScript.cpp b/lib/impl/LuaScript.cpp index b245200..3f6a754 100644 --- a/lib/impl/LuaScript.cpp +++ b/lib/impl/LuaScript.cpp @@ -22,11 +22,6 @@ namespace rlogic LuaScript::~LuaScript() noexcept = default; - std::string_view LuaScript::getFilename() const - { - return m_script.getFilename(); - } - void LuaScript::overrideLuaPrint(LuaPrintFunction luaPrintFunction) { m_script.overrideLuaPrint(std::move(luaPrintFunction)); diff --git a/lib/impl/LuaScriptImpl.cpp b/lib/impl/LuaScriptImpl.cpp index 197d466..f433b63 100644 --- a/lib/impl/LuaScriptImpl.cpp +++ b/lib/impl/LuaScriptImpl.cpp @@ -8,12 +8,15 @@ #include "impl/LuaScriptImpl.h" +#include "ramses-logic/LuaModule.h" #include "internals/SolState.h" #include "impl/PropertyImpl.h" +#include "impl/LuaModuleImpl.h" #include "internals/WrappedLuaProperty.h" #include "internals/SolHelper.h" #include "internals/ErrorReporting.h" +#include "internals/FileFormatVersions.h" #include "generated/LuaScriptGen.h" @@ -23,12 +26,13 @@ namespace rlogic::internal { LuaScriptImpl::LuaScriptImpl(LuaCompiledScript compiledScript, std::string_view name) : LogicNodeImpl(name) - , m_filename(std::move(compiledScript.fileName)) - , m_source(std::move(compiledScript.sourceCode)) + , m_source(std::move(compiledScript.source.sourceCode)) , m_luaPrintFunction(&LuaScriptImpl::DefaultLuaPrintFunction) , m_wrappedRootInput(*compiledScript.rootInput->m_impl) , m_wrappedRootOutput(*compiledScript.rootOutput->m_impl) , m_solFunction(std::move(compiledScript.mainFunction)) + , m_modules(std::move(compiledScript.source.userModules)) + , m_stdModules(std::move(compiledScript.source.stdModules)) { setRootProperties(std::move(compiledScript.rootInput), std::move(compiledScript.rootOutput)); @@ -45,12 +49,37 @@ namespace rlogic::internal // TODO Violin investigate options to save byte code, instead of plain text, e.g.: //sol::bytecode scriptCode = m_solFunction.dump(); + flatbuffers::Offset>> modulesOffset = 0; + if (!luaScript.m_modules.empty()) + { + std::vector> modulesFB; + modulesFB.reserve(luaScript.m_modules.size()); + for (const auto& module : luaScript.m_modules) + { + modulesFB.push_back( + rlogic_serialization::CreateLuaModuleUsage(builder, + builder.CreateString(module.first), + serializationMap.resolveLuaModuleOffset(*module.second))); + } + + modulesOffset = builder.CreateVector(modulesFB); + } + + std::vector stdModules; + stdModules.reserve(luaScript.m_stdModules.size()); + for (const EStandardModule stdModule : luaScript.m_stdModules) + { + stdModules.push_back(static_cast(stdModule)); + } + auto script = rlogic_serialization::CreateLuaScript(builder, builder.CreateString(luaScript.getName()), - builder.CreateString(luaScript.getFilename()), + 0, builder.CreateString(luaScript.m_source), PropertyImpl::Serialize(*luaScript.getInputs()->m_impl, builder, serializationMap), - PropertyImpl::Serialize(*luaScript.getOutputs()->m_impl, builder, serializationMap) + PropertyImpl::Serialize(*luaScript.getOutputs()->m_impl, builder, serializationMap), + modulesOffset, + builder.CreateVector(stdModules) ); builder.Finish(script); @@ -70,21 +99,15 @@ namespace rlogic::internal return nullptr; } - if (!luaScript.filename()) - { - errorReporting.add("Fatal error during loading of LuaScript from serialized data: missing filename!", nullptr); - return nullptr; - } + static_assert(g_FileFormatVersion == 2, "Remove filename from schema - search for 'VersionBreak' in schema files"); const std::string_view name = luaScript.name()->string_view(); - std::string filename = luaScript.filename()->str(); if (!luaScript.luaSourceCode()) { errorReporting.add("Fatal error during loading of LuaScript from serialized data: missing Lua source code!", nullptr); return nullptr; } - std::string sourceCode = luaScript.luaSourceCode()->str(); if (!luaScript.rootInput()) @@ -132,8 +155,41 @@ namespace rlogic::internal return nullptr; } + StandardModules stdModules; + if (luaScript.standardModules()) + { + stdModules.reserve(luaScript.standardModules()->size()); + for (const uint8_t stdModule : *luaScript.standardModules()) + { + stdModules.push_back(static_cast(stdModule)); + } + } + else + { + static_assert(g_FileFormatVersion == 2, "Remove this with next file version break; also consider making stdModules required"); + stdModules = { EStandardModule::Base, EStandardModule::String, EStandardModule::Table, EStandardModule::Math, EStandardModule::Debug }; + } + + static_assert(g_FileFormatVersion == 2, "Consider making modules mandatory with next file version break"); + ModuleMapping userModules; + if (luaScript.dependencies()) + { + userModules.reserve(luaScript.dependencies()->size()); + for (const auto* module : *luaScript.dependencies()) + { + if (!module->name() || !module->module_()) + { + errorReporting.add(fmt::format("Fatal error during loading of LuaScript '{}' module data: missing name or module!", name), nullptr); + return nullptr; + } + const LuaModule& moduleUsed = deserializationMap.resolveLuaModule(*module->module_()); + userModules.emplace(module->name()->str(), &moduleUsed); + } + } + sol::protected_function mainFunction = load_result; - sol::environment env = solState.createEnvironment(); + sol::environment env = solState.createEnvironment(stdModules, userModules, EEnvironmentType::Runtime); + env.set_on(mainFunction); sol::protected_function_result main_result = mainFunction(); @@ -146,18 +202,17 @@ namespace rlogic::internal return std::make_unique( LuaCompiledScript{ - std::move(sourceCode), - std::move(filename), - solState, + LuaSource{ + std::move(sourceCode), + solState, + std::move(stdModules), + std::move(userModules) + }, sol::protected_function(std::move(load_result)), std::make_unique(std::move(rootInput)), std::make_unique(std::move(rootOutput)) - }, name); - } - - std::string_view LuaScriptImpl::getFilename() const - { - return m_filename; + }, + name); } std::optional LuaScriptImpl::update() @@ -201,4 +256,8 @@ namespace rlogic::internal m_luaPrintFunction = std::move(luaPrintFunction); } + const ModuleMapping& LuaScriptImpl::getModules() const + { + return m_modules; + } } diff --git a/lib/impl/LuaScriptImpl.h b/lib/impl/LuaScriptImpl.h index b1028d8..49c6497 100644 --- a/lib/impl/LuaScriptImpl.h +++ b/lib/impl/LuaScriptImpl.h @@ -9,8 +9,9 @@ #pragma once #include "impl/LogicNodeImpl.h" -#include "impl/LuaCompilationUtils.h" +#include "impl/LuaConfigImpl.h" +#include "internals/LuaCompilationUtils.h" #include "internals/SerializationMap.h" #include "internals/DeserializationMap.h" #include "internals/WrappedLuaProperty.h" @@ -35,6 +36,11 @@ namespace rlogic_serialization struct LuaScript; } +namespace rlogic +{ + class LuaModule; +} + namespace rlogic::internal { class SolState; @@ -58,20 +64,21 @@ namespace rlogic::internal ErrorReporting& errorReporting, DeserializationMap& deserializationMap); - [[nodiscard]] std::string_view getFilename() const; - std::optional update() override; + [[nodiscard]] const ModuleMapping& getModules() const; + void luaPrint(sol::variadic_args args); void overrideLuaPrint(LuaPrintFunction luaPrintFunction); private: - std::string m_filename; - std::string m_source; - LuaPrintFunction m_luaPrintFunction; - WrappedLuaProperty m_wrappedRootInput; - WrappedLuaProperty m_wrappedRootOutput; - sol::protected_function m_solFunction; + std::string m_source; + LuaPrintFunction m_luaPrintFunction; + WrappedLuaProperty m_wrappedRootInput; + WrappedLuaProperty m_wrappedRootOutput; + sol::protected_function m_solFunction; + ModuleMapping m_modules; + StandardModules m_stdModules; static void DefaultLuaPrintFunction(std::string_view scriptName, std::string_view message); }; diff --git a/lib/internals/ApiObjects.cpp b/lib/internals/ApiObjects.cpp index 66e5a16..15eaf99 100644 --- a/lib/internals/ApiObjects.cpp +++ b/lib/internals/ApiObjects.cpp @@ -13,6 +13,7 @@ #include "ramses-logic-build-config.h" #include "ramses-logic/LuaScript.h" +#include "ramses-logic/LuaModule.h" #include "ramses-logic/RamsesNodeBinding.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/RamsesCameraBinding.h" @@ -21,6 +22,7 @@ #include "impl/PropertyImpl.h" #include "impl/LuaScriptImpl.h" +#include "impl/LuaModuleImpl.h" #include "impl/RamsesNodeBindingImpl.h" #include "impl/RamsesAppearanceBindingImpl.h" #include "impl/RamsesCameraBindingImpl.h" @@ -48,14 +50,73 @@ namespace rlogic::internal ApiObjects::ApiObjects() = default; ApiObjects::~ApiObjects() noexcept = default; - LuaScript* ApiObjects::createLuaScript(LuaCompiledScript compiledScript, std::string_view name) + bool ApiObjects::checkLuaModules(const ModuleMapping& moduleMapping, ErrorReporting& errorReporting) { - m_scripts.emplace_back(std::make_unique(std::make_unique(std::move(compiledScript), name))); + for (const auto& module : moduleMapping) + { + if (getLuaModules().cend() == std::find_if(getLuaModules().cbegin(), getLuaModules().cend(), + [&module](const auto& m) { return m.get() == module.second; })) + { + errorReporting.add(fmt::format("Failed to map Lua module '{}'! It was created on a different instance of LogicEngine.", module.first), module.second); + return false; + } + } + + return true; + } + + LuaScript* ApiObjects::createLuaScript( + std::string_view source, + const LuaConfigImpl& config, + std::string_view scriptName, + ErrorReporting& errorReporting) + { + const ModuleMapping& modules = config.getModuleMapping(); + if (!checkLuaModules(modules, errorReporting)) + return nullptr; + + std::optional compiledScript = LuaCompilationUtils::CompileScript( + *m_solState, + modules, + config.getStandardModules(), + std::string{ source }, + scriptName, + errorReporting); + + if (!compiledScript) + return nullptr; + + m_scripts.emplace_back(std::make_unique(std::make_unique(std::move(*compiledScript), scriptName))); LuaScript* script = m_scripts.back().get(); registerLogicNode(*script); return script; } + LuaModule* ApiObjects::createLuaModule( + std::string_view source, + const LuaConfigImpl& config, + std::string_view moduleName, + ErrorReporting& errorReporting) + { + const ModuleMapping& modules = config.getModuleMapping(); + if (!checkLuaModules(modules, errorReporting)) + return nullptr; + + std::optional compiledModule = LuaCompilationUtils::CompileModule( + *m_solState, + modules, + config.getStandardModules(), + std::string{source}, + moduleName, + errorReporting); + + if (!compiledModule) + return nullptr; + + m_luaModules.emplace_back(std::make_unique(std::make_unique(std::move(*compiledModule), moduleName))); + return m_luaModules.back().get(); + } + RamsesNodeBinding* ApiObjects::createRamsesNodeBinding(ramses::Node& ramsesNode, ERotationType rotationType, std::string_view name) { m_ramsesNodeBindings.emplace_back(std::make_unique(std::make_unique(ramsesNode, rotationType, name))); @@ -121,6 +182,10 @@ namespace rlogic::internal if (luaScript) return destroyInternal(*luaScript, errorReporting); + auto luaModule = dynamic_cast(&object); + if (luaModule) + return destroyInternal(*luaModule, errorReporting); + auto ramsesNodeBinding = dynamic_cast(&object); if (ramsesNodeBinding) return destroyInternal(*ramsesNodeBinding, errorReporting); @@ -195,6 +260,32 @@ namespace rlogic::internal return true; } + bool ApiObjects::destroyInternal(LuaModule& luaModule, ErrorReporting& errorReporting) + { + const auto it = std::find_if(m_luaModules.cbegin(), m_luaModules.cend(), [&](const auto& m) { + return m.get() == &luaModule; + }); + if (it == m_luaModules.cend()) + { + errorReporting.add("Can't find Lua module in logic engine!", &luaModule); + return false; + } + for (const auto& script : m_scripts) + { + for (const auto& moduleInUse : script->m_script.getModules()) + { + if (moduleInUse.second == &luaModule) + { + errorReporting.add(fmt::format("Failed to destroy LuaModule '{}', it is used in LuaScript '{}'", luaModule.getName(), script->getName()), &luaModule); + return false; + } + } + } + + m_luaModules.erase(it); + return true; + } + bool ApiObjects::destroyInternal(RamsesNodeBinding& ramsesNodeBinding, ErrorReporting& errorReporting) { auto nodeIter = find_if(m_ramsesNodeBindings.begin(), m_ramsesNodeBindings.end(), [&](const std::unique_ptr& nodeBinding) @@ -337,6 +428,16 @@ namespace rlogic::internal return m_scripts; } + LuaModulesContainer& ApiObjects::getLuaModules() + { + return m_luaModules; + } + + const LuaModulesContainer& ApiObjects::getLuaModules() const + { + return m_luaModules; + } + NodeBindingsContainer& ApiObjects::getNodeBindings() { return m_ramsesNodeBindings; @@ -413,6 +514,20 @@ namespace rlogic::internal { SerializationMap serializationMap; + flatbuffers::Offset>> luaModulesOffset = 0; + if (!apiObjects.m_luaModules.empty()) + { + std::vector> luaModules; + luaModules.reserve(apiObjects.m_luaModules.size()); + for (const auto& luaModule : apiObjects.m_luaModules) + { + luaModules.push_back(LuaModuleImpl::Serialize(luaModule->m_impl, builder, serializationMap)); + serializationMap.storeLuaModule(*luaModule, luaModules.back()); + } + + luaModulesOffset = builder.CreateVector(luaModules); + } + std::vector> luascripts; luascripts.reserve(apiObjects.m_scripts.size()); std::transform(apiObjects.m_scripts.begin(), apiObjects.m_scripts.end(), std::back_inserter(luascripts), @@ -480,7 +595,8 @@ namespace rlogic::internal builder.CreateVector(ramsescamerabindings), builder.CreateVector(dataArrays), builder.CreateVector(animationNodes), - builder.CreateVector(links) + builder.CreateVector(links), + luaModulesOffset ); builder.Finish(logicEngine); @@ -488,83 +604,97 @@ namespace rlogic::internal return logicEngine; } - std::optional ApiObjects::Deserialize( - SolState& solState, + std::unique_ptr ApiObjects::Deserialize( const rlogic_serialization::ApiObjects& apiObjects, const IRamsesObjectResolver& ramsesResolver, const std::string& dataSourceDescription, ErrorReporting& errorReporting) { // Collect data here, only return if no error occurred - ApiObjects deserialized; + auto deserialized = std::make_unique(); // Collect deserialized object mappings to resolve dependencies DeserializationMap deserializationMap; if (!apiObjects.luaScripts()) { - errorReporting.add("Fatal error during loading from serialized data: missing scripts container!", nullptr); - return std::nullopt; + errorReporting.add("Fatal error during loading from serialized data: missing Lua scripts container!", nullptr); + return nullptr; } if (!apiObjects.nodeBindings()) { errorReporting.add("Fatal error during loading from serialized data: missing node bindings container!", nullptr); - return std::nullopt; + return nullptr; } if (!apiObjects.appearanceBindings()) { errorReporting.add("Fatal error during loading from serialized data: missing appearance bindings container!", nullptr); - return std::nullopt; + return nullptr; } if (!apiObjects.cameraBindings()) { errorReporting.add("Fatal error during loading from serialized data: missing camera bindings container!", nullptr); - return std::nullopt; + return nullptr; } if (!apiObjects.links()) { errorReporting.add("Fatal error during loading from serialized data: missing links container!", nullptr); - return std::nullopt; + return nullptr; } if (!apiObjects.dataArrays()) { errorReporting.add("Fatal error during loading from serialized data: missing data arrays container!", nullptr); - return std::nullopt; + return nullptr; } if (!apiObjects.animationNodes()) { errorReporting.add("Fatal error during loading from serialized data: missing animation nodes container!", nullptr); - return std::nullopt; + return nullptr; + } + + if (apiObjects.luaModules()) + { + const auto& luaModules = *apiObjects.luaModules(); + deserialized->m_luaModules.reserve(luaModules.size()); + for (const auto* module : luaModules) + { + std::unique_ptr deserializedModule = LuaModuleImpl::Deserialize(*deserialized->m_solState, *module, errorReporting, deserializationMap); + if (!deserializedModule) + return nullptr; + + deserialized->m_luaModules.push_back(std::make_unique(std::move(deserializedModule))); + deserializationMap.storeLuaModule(*module, *deserialized->m_luaModules.back()); + } } const auto& luascripts = *apiObjects.luaScripts(); - deserialized.m_scripts.reserve(luascripts.size()); + deserialized->m_scripts.reserve(luascripts.size()); for (const auto* script : luascripts) { // TODO Violin find ways to unit-test this case - also for other container types // Ideas: see if verifier catches it; or: disable flatbuffer's internal asserts if possible assert (script); - std::unique_ptr deserializedScript = LuaScriptImpl::Deserialize(solState, *script, errorReporting, deserializationMap); + std::unique_ptr deserializedScript = LuaScriptImpl::Deserialize(*deserialized->m_solState, *script, errorReporting, deserializationMap); if (deserializedScript) { - deserialized.m_scripts.emplace_back(std::make_unique(std::move(deserializedScript))); - deserialized.registerLogicNode(*deserialized.m_scripts.back()); + deserialized->m_scripts.emplace_back(std::make_unique(std::move(deserializedScript))); + deserialized->registerLogicNode(*deserialized->m_scripts.back()); } else { - return std::nullopt; + return nullptr; } } const auto& ramsesNodeBindings = *apiObjects.nodeBindings(); - deserialized.m_ramsesNodeBindings.reserve(ramsesNodeBindings.size()); + deserialized->m_ramsesNodeBindings.reserve(ramsesNodeBindings.size()); for (const auto* binding : ramsesNodeBindings) { assert (binding); @@ -572,17 +702,17 @@ namespace rlogic::internal if (deserializedBinding) { - deserialized.m_ramsesNodeBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); - deserialized.registerLogicNode(*deserialized.m_ramsesNodeBindings.back()); + deserialized->m_ramsesNodeBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); + deserialized->registerLogicNode(*deserialized->m_ramsesNodeBindings.back()); } else { - return std::nullopt; + return nullptr; } } const auto& ramsesAppearanceBindings = *apiObjects.appearanceBindings(); - deserialized.m_ramsesAppearanceBindings.reserve(ramsesAppearanceBindings.size()); + deserialized->m_ramsesAppearanceBindings.reserve(ramsesAppearanceBindings.size()); for (const auto* binding : ramsesAppearanceBindings) { assert(binding); @@ -590,17 +720,17 @@ namespace rlogic::internal if (deserializedBinding) { - deserialized.m_ramsesAppearanceBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); - deserialized.registerLogicNode(*deserialized.m_ramsesAppearanceBindings.back()); + deserialized->m_ramsesAppearanceBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); + deserialized->registerLogicNode(*deserialized->m_ramsesAppearanceBindings.back()); } else { - return std::nullopt; + return nullptr; } } const auto& ramsesCameraBindings = *apiObjects.cameraBindings(); - deserialized.m_ramsesCameraBindings.reserve(ramsesCameraBindings.size()); + deserialized->m_ramsesCameraBindings.reserve(ramsesCameraBindings.size()); for (const auto* binding : ramsesCameraBindings) { assert(binding); @@ -608,40 +738,40 @@ namespace rlogic::internal if (deserializedBinding) { - deserialized.m_ramsesCameraBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); - deserialized.registerLogicNode(*deserialized.m_ramsesCameraBindings.back()); + deserialized->m_ramsesCameraBindings.emplace_back(std::make_unique(std::move(deserializedBinding))); + deserialized->registerLogicNode(*deserialized->m_ramsesCameraBindings.back()); } else { - return std::nullopt; + return nullptr; } } const auto& dataArrays = *apiObjects.dataArrays(); - deserialized.m_dataArrays.reserve(dataArrays.size()); + deserialized->m_dataArrays.reserve(dataArrays.size()); for (const auto* fbData : dataArrays) { assert(fbData); auto deserializedDataArray = DataArrayImpl::Deserialize(*fbData, errorReporting); if (!deserializedDataArray) - return std::nullopt; + return nullptr; - deserialized.m_dataArrays.push_back(std::make_unique(std::move(deserializedDataArray))); - deserializationMap.storeDataArray(*fbData, *deserialized.m_dataArrays.back()); + deserialized->m_dataArrays.push_back(std::make_unique(std::move(deserializedDataArray))); + deserializationMap.storeDataArray(*fbData, *deserialized->m_dataArrays.back()); } // animation nodes must go after data arrays because they need to resolve references const auto& animNodes = *apiObjects.animationNodes(); - deserialized.m_animationNodes.reserve(animNodes.size()); + deserialized->m_animationNodes.reserve(animNodes.size()); for (const auto* fbData : animNodes) { assert(fbData); auto deserializedAnimNode = AnimationNodeImpl::Deserialize(*fbData, errorReporting, deserializationMap); if (!deserializedAnimNode) - return std::nullopt; + return nullptr; - deserialized.m_animationNodes.push_back(std::make_unique(std::move(deserializedAnimNode))); - deserialized.registerLogicNode(*deserialized.m_animationNodes.back()); + deserialized->m_animationNodes.push_back(std::make_unique(std::move(deserializedAnimNode))); + deserialized->registerLogicNode(*deserialized->m_animationNodes.back()); } // links must go last due to dependency on deserialized properties @@ -654,19 +784,19 @@ namespace rlogic::internal if (!rLink->sourceProperty()) { errorReporting.add("Fatal error during loading from serialized data: missing link source property!", nullptr); - return std::nullopt; + return nullptr; } if (!rLink->targetProperty()) { errorReporting.add("Fatal error during loading from serialized data: missing link target property!", nullptr); - return std::nullopt; + return nullptr; } const rlogic_serialization::Property* sourceProp = rLink->sourceProperty(); const rlogic_serialization::Property* targetProp = rLink->targetProperty(); - const bool success = deserialized.m_logicNodeDependencies.link( + const bool success = deserialized->m_logicNodeDependencies.link( deserializationMap.resolvePropertyImpl(*sourceProp), deserializationMap.resolvePropertyImpl(*targetProp), errorReporting); @@ -681,12 +811,11 @@ namespace rlogic::internal sourceProp->name()->string_view(), targetProp->name()->string_view() ), nullptr); - return std::nullopt; + return nullptr; } } - // This syntax is compatible with GCC7 (c++11 converts automatically) - return std::make_optional(std::move(deserialized)); + return deserialized; } bool ApiObjects::isDirty() const diff --git a/lib/internals/ApiObjects.h b/lib/internals/ApiObjects.h index 588bc23..ea3852f 100644 --- a/lib/internals/ApiObjects.h +++ b/lib/internals/ApiObjects.h @@ -8,12 +8,14 @@ #pragma once -#include "LogicNodeDependencies.h" - #include "ramses-logic/AnimationTypes.h" #include "ramses-logic/ERotationType.h" -#include "impl/LuaCompilationUtils.h" +#include "impl/LuaConfigImpl.h" + +#include "internals/LuaCompilationUtils.h" +#include "internals/SolState.h" +#include "internals/LogicNodeDependencies.h" #include #include @@ -32,6 +34,7 @@ namespace rlogic class LogicObject; class LogicNode; class LuaScript; + class LuaModule; class RamsesNodeBinding; class RamsesAppearanceBinding; class RamsesCameraBinding; @@ -56,6 +59,7 @@ namespace rlogic::internal class IRamsesObjectResolver; using ScriptsContainer = std::vector>; + using LuaModulesContainer = std::vector>; using NodeBindingsContainer = std::vector>; using AppearanceBindingsContainer = std::vector>; using CameraBindingsContainer = std::vector>; @@ -65,27 +69,37 @@ namespace rlogic::internal class ApiObjects { public: - // Move-able and non-copyable + // Not move-able and non-copyable ApiObjects(); ~ApiObjects() noexcept; - // TODO Violin try to find a way to make the class move-able without exceptions (and also the whole LogicEngine) - // Currently not possible because MSVC2017 compiler forces copy on move: https://stackoverflow.com/questions/47604029/move-constructors-of-stl-containers-in-msvc-2017-are-not-marked-as-noexcept - ApiObjects(ApiObjects&& other) = default; - ApiObjects& operator=(ApiObjects&& other) = default; + // Not move-able because of the dependency between sol objects and their parent sol state + // Moving those would require a custom move assignment operator which keeps both sol states alive + // until the objects have been moved, and only then also moves the sol state - we don't need this + // complexity because we never move-assign ApiObjects + ApiObjects(ApiObjects&& other) noexcept = delete; + ApiObjects& operator=(ApiObjects&& other) noexcept = delete; ApiObjects(const ApiObjects& other) = delete; ApiObjects& operator=(const ApiObjects& other) = delete; // Serialization/Deserialization static flatbuffers::Offset Serialize(const ApiObjects& apiObjects, flatbuffers::FlatBufferBuilder& builder); - static std::optional Deserialize( - SolState& solState, + static std::unique_ptr Deserialize( const rlogic_serialization::ApiObjects& apiObjects, const IRamsesObjectResolver& ramsesResolver, const std::string& dataSourceDescription, ErrorReporting& errorReporting); // Create/destroy API objects - LuaScript* createLuaScript(LuaCompiledScript compiledScript, std::string_view name); + LuaScript* createLuaScript( + std::string_view source, + const LuaConfigImpl& config, + std::string_view scriptName, + ErrorReporting& errorReporting); + LuaModule* createLuaModule( + std::string_view source, + const LuaConfigImpl& config, + std::string_view moduleName, + ErrorReporting& errorReporting); RamsesNodeBinding* createRamsesNodeBinding(ramses::Node& ramsesNode, ERotationType rotationType, std::string_view name); RamsesAppearanceBinding* createRamsesAppearanceBinding(ramses::Appearance& ramsesAppearance, std::string_view name); RamsesCameraBinding* createRamsesCameraBinding(ramses::Camera& ramsesCamera, std::string_view name); @@ -100,6 +114,8 @@ namespace rlogic::internal // Getters [[nodiscard]] ScriptsContainer& getScripts(); [[nodiscard]] const ScriptsContainer& getScripts() const; + [[nodiscard]] LuaModulesContainer& getLuaModules(); + [[nodiscard]] const LuaModulesContainer& getLuaModules() const; [[nodiscard]] NodeBindingsContainer& getNodeBindings(); [[nodiscard]] const NodeBindingsContainer& getNodeBindings() const; [[nodiscard]] AppearanceBindingsContainer& getAppearanceBindings(); @@ -128,22 +144,29 @@ namespace rlogic::internal void registerLogicNode(LogicNode& logicNode); void unregisterLogicNode(LogicNode& logicNode); + bool checkLuaModules( + const ModuleMapping& moduleMapping, + ErrorReporting& errorReporting); + // Type-specific destruction logic [[nodiscard]] bool destroyInternal(RamsesNodeBinding& ramsesNodeBinding, ErrorReporting& errorReporting); [[nodiscard]] bool destroyInternal(LuaScript& luaScript, ErrorReporting& errorReporting); + [[nodiscard]] bool destroyInternal(LuaModule& luaModule, ErrorReporting& errorReporting); [[nodiscard]] bool destroyInternal(RamsesAppearanceBinding& ramsesAppearanceBinding, ErrorReporting& errorReporting); [[nodiscard]] bool destroyInternal(RamsesCameraBinding& ramsesCameraBinding, ErrorReporting& errorReporting); [[nodiscard]] bool destroyInternal(AnimationNode& node, ErrorReporting& errorReporting); [[nodiscard]] bool destroyInternal(DataArray& dataArray, ErrorReporting& errorReporting); + std::unique_ptr m_solState {std::make_unique()}; + ScriptsContainer m_scripts; + LuaModulesContainer m_luaModules; NodeBindingsContainer m_ramsesNodeBindings; AppearanceBindingsContainer m_ramsesAppearanceBindings; CameraBindingsContainer m_ramsesCameraBindings; DataArrayContainer m_dataArrays; AnimationNodesContainer m_animationNodes; LogicNodeDependencies m_logicNodeDependencies; - std::unordered_map m_reverseImplMapping; }; } diff --git a/lib/internals/DeserializationMap.h b/lib/internals/DeserializationMap.h index bdb28d2..bb98cbb 100644 --- a/lib/internals/DeserializationMap.h +++ b/lib/internals/DeserializationMap.h @@ -14,11 +14,13 @@ namespace rlogic_serialization { struct Property; struct DataArray; + struct LuaModule; } namespace rlogic { class DataArray; + class LuaModule; } namespace rlogic::internal @@ -55,9 +57,23 @@ namespace rlogic::internal return *it->second; } + void storeLuaModule(const rlogic_serialization::LuaModule& flatbufferObject, const LuaModule& luaModule) + { + assert(m_luaModules.count(&flatbufferObject) == 0 && "one time store only"); + m_luaModules.insert({ &flatbufferObject, &luaModule }); + } + + const LuaModule& resolveLuaModule(const rlogic_serialization::LuaModule& flatbufferObject) const + { + const auto it = m_luaModules.find(&flatbufferObject); + assert(it != m_luaModules.cend()); + return *it->second; + } + private: std::unordered_map m_properties; std::unordered_map m_dataArrays; + std::unordered_map m_luaModules; }; } diff --git a/lib/internals/ErrorReporting.cpp b/lib/internals/ErrorReporting.cpp index b59f14f..d925c22 100644 --- a/lib/internals/ErrorReporting.cpp +++ b/lib/internals/ErrorReporting.cpp @@ -12,7 +12,7 @@ namespace rlogic::internal { - void ErrorReporting::add(std::string errorMessage, LogicObject* logicObject) + void ErrorReporting::add(std::string errorMessage, const LogicObject* logicObject) { if (logicObject) { diff --git a/lib/internals/ErrorReporting.h b/lib/internals/ErrorReporting.h index 504270d..7c97c07 100644 --- a/lib/internals/ErrorReporting.h +++ b/lib/internals/ErrorReporting.h @@ -19,7 +19,7 @@ namespace rlogic::internal public: void clear(); - void add(std::string errorMessage, LogicObject* logicObject); + void add(std::string errorMessage, const LogicObject* logicObject); [[nodiscard]] const std::vector& getErrors() const; diff --git a/lib/internals/FileFormatVersions.h b/lib/internals/FileFormatVersions.h index e77e1e3..a0c3183 100644 --- a/lib/internals/FileFormatVersions.h +++ b/lib/internals/FileFormatVersions.h @@ -12,6 +12,6 @@ namespace rlogic::internal { - // Always bump this with any file schema changes + // Always bump this with breaking file schema changes constexpr uint32_t g_FileFormatVersion = 2; } diff --git a/lib/internals/LuaCompilationUtils.cpp b/lib/internals/LuaCompilationUtils.cpp new file mode 100644 index 0000000..e76caf1 --- /dev/null +++ b/lib/internals/LuaCompilationUtils.cpp @@ -0,0 +1,278 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2020 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "internals/LuaCompilationUtils.h" + +#include "ramses-logic/Property.h" +#include "ramses-logic/LuaModule.h" +#include "impl/PropertyImpl.h" +#include "impl/LuaModuleImpl.h" +#include "impl/LoggerImpl.h" +#include "internals/SolState.h" +#include "internals/ErrorReporting.h" +#include "internals/PropertyTypeExtractor.h" +#include "internals/EPropertySemantics.h" +#include "fmt/format.h" + +namespace rlogic::internal +{ + std::optional LuaCompilationUtils::CompileScript( + SolState& solState, + const ModuleMapping& userModules, + const StandardModules& stdModules, + std::string source, + std::string_view name, + ErrorReporting& errorReporting) + { + const std::string chunkname = BuildChunkName(name); + + sol::load_result load_result = solState.loadScript(source, chunkname); + if (!load_result.valid()) + { + sol::error error = load_result; + errorReporting.add(fmt::format("[{}] Error while loading script. Lua stack trace:\n{}", chunkname, error.what()), nullptr); + return std::nullopt; + } + + if (!CrossCheckDeclaredAndProvidedModules(source, userModules, chunkname, errorReporting)) + return std::nullopt; + + // TODO Violin use separate environment for script loading, don't reuse it as a runtime environment + sol::environment env = solState.createEnvironment(stdModules, userModules, EEnvironmentType::Runtime); + + sol::protected_function mainFunction = load_result; + env.set_on(mainFunction); + + sol::protected_function_result main_result = mainFunction(); + if (!main_result.valid()) + { + sol::error error = main_result; + errorReporting.add(error.what(), nullptr); + return std::nullopt; + } + + sol::protected_function intf = env["interface"]; + + if (!intf.valid()) + { + errorReporting.add(fmt::format("[{}] No 'interface' function defined!", chunkname), nullptr); + return std::nullopt; + } + + sol::protected_function run = env["run"]; + + if (!run.valid()) + { + errorReporting.add(fmt::format("[{}] No 'run' function defined!", chunkname), nullptr); + return std::nullopt; + } + + PropertyTypeExtractor inputsExtractor("IN", EPropertyType::Struct); + PropertyTypeExtractor outputsExtractor("OUT", EPropertyType::Struct); + + sol::environment interfaceEnvironment = solState.createEnvironment(stdModules, userModules, EEnvironmentType::Interface); + + interfaceEnvironment["IN"] = std::ref(inputsExtractor); + interfaceEnvironment["OUT"] = std::ref(outputsExtractor); + + interfaceEnvironment.set_on(intf); + sol::protected_function_result intfResult = intf(); + + interfaceEnvironment["IN"] = sol::lua_nil; + interfaceEnvironment["OUT"] = sol::lua_nil; + for (const auto& module : userModules) + interfaceEnvironment[module.first] = sol::lua_nil; + + if (!intfResult.valid()) + { + sol::error error = intfResult; + errorReporting.add(fmt::format("[{}] Error while loading script. Lua stack trace:\n{}", chunkname, error.what()), nullptr); + return std::nullopt; + } + + return LuaCompiledScript{ + LuaSource{ + std::move(source), + solState, + stdModules, + userModules + }, + std::move(load_result), + std::make_unique(std::make_unique(inputsExtractor.getExtractedTypeData(), EPropertySemantics::ScriptInput)), + std::make_unique(std::make_unique(outputsExtractor.getExtractedTypeData(), EPropertySemantics::ScriptOutput)) + }; + } + + std::optional LuaCompilationUtils::CompileModule( + SolState& solState, + const ModuleMapping& userModules, + const StandardModules& stdModules, + std::string source, + std::string_view name, + ErrorReporting& errorReporting) + { + const std::string chunkname = BuildChunkName(name); + + sol::load_result load_result = solState.loadScript(source, chunkname); + if (!load_result.valid()) + { + sol::error error = load_result; + errorReporting.add(fmt::format("[{}] Error while loading module. Lua stack trace:\n{}", chunkname, error.what()), nullptr); + return std::nullopt; + } + + if (!CrossCheckDeclaredAndProvidedModules(source, userModules, chunkname, errorReporting)) + return std::nullopt; + + sol::environment env = solState.createEnvironment(stdModules, userModules, EEnvironmentType::Module); + + sol::protected_function mainFunction = load_result; + env.set_on(mainFunction); + + sol::protected_function_result main_result = mainFunction(); + if (!main_result.valid()) + { + sol::error error = main_result; + errorReporting.add(error.what(), nullptr); + return std::nullopt; + } + + sol::object resultObj = main_result; + // TODO Violin check and test for abuse: yield, more than one result + if (!resultObj.is()) + { + errorReporting.add(fmt::format("[{}] Error while loading module. Module script must return a table!", chunkname), nullptr); + return std::nullopt; + } + + sol::table moduleTable = resultObj; + + return LuaCompiledModule{ + LuaSource{ + std::move(source), + solState, + stdModules, + userModules + }, + std::move(moduleTable) + }; + } + + std::string LuaCompilationUtils::BuildChunkName(std::string_view scriptName) + { + return std::string(scriptName.empty() ? "unknown" : scriptName); + } + + bool LuaCompilationUtils::CheckModuleName(std::string_view name) + { + if (name.empty()) + return false; + + // Check if any non-alpha-numeric char is contained + const bool onlyAlphanumChars = std::all_of(name.cbegin(), name.cend(), [](char c) { return c == '_' || (0 != std::isalnum(c)); }); + if (!onlyAlphanumChars) + return false; + + // First letter is a digit -> error + if (0 != std::isdigit(name[0])) + return false; + + return true; + } + + bool LuaCompilationUtils::CrossCheckDeclaredAndProvidedModules(std::string_view source, const ModuleMapping& modules, std::string_view chunkname, ErrorReporting& errorReporting) + { + std::optional> declaredModules = LuaCompilationUtils::ExtractModuleDependencies(source, errorReporting); + if (!declaredModules) // failed extraction + return false; + if (modules.empty() && declaredModules->empty()) // early out if no modules + return true; + + std::vector providedModules; + providedModules.reserve(modules.size()); + for (const auto& m : modules) + providedModules.push_back(m.first); + std::sort(declaredModules->begin(), declaredModules->end()); + std::sort(providedModules.begin(), providedModules.end()); + if (providedModules != declaredModules) + { + std::string errMsg = fmt::format("[{}] Error while loading script/module. Module dependencies declared in source code do not match those provided by LuaConfig.\n", chunkname); + errMsg += fmt::format(" Module dependencies declared in source code: {}\n", fmt::join(*declaredModules, ", ")); + errMsg += fmt::format(" Module dependencies provided on create API: {}", fmt::join(providedModules, ", ")); + errorReporting.add(errMsg, nullptr); + return false; + } + + return true; + } + + std::optional> LuaCompilationUtils::ExtractModuleDependencies(std::string_view source, ErrorReporting& errorReporting) + { + sol::state tempLuaState; + + std::vector extractedModules; + bool success = true; + int timesCalled = 0; + tempLuaState.set_function("modules", [&](sol::variadic_args va) { + ++timesCalled; + int argIdx = 0; + for (const auto& v : va) + { + if (v.is()) + { + extractedModules.push_back(v.as()); + } + else + { + const auto argTypeName = sol::type_name(v.lua_state(), v.get_type()); + errorReporting.add( + fmt::format(R"(Error while extracting module dependencies: argument {} is of type '{}', string must be provided: ex. 'modules("moduleA", "moduleB")')", + argIdx, argTypeName), nullptr); + success = false; + } + ++argIdx; + } + }); + + const sol::load_result load_result = tempLuaState.load(source, "temp"); + if (!load_result.valid()) + { + sol::error error = load_result; + errorReporting.add(fmt::format("Error while extracting module dependencies:\n{}", error.what()), nullptr); + return std::nullopt; + } + + sol::protected_function scriptFunc = load_result; + const sol::protected_function_result scriptFuncResult = scriptFunc(); + if (!scriptFuncResult.valid()) + { + const sol::error error = scriptFuncResult; + LOG_DEBUG(fmt::format("Lua runtime error while extracting module dependencies, this is ignored for the actual extraction but might affect its result:\n{}", error.what()), nullptr); + } + + if (!success) + return std::nullopt; + + if (timesCalled > 1) + { + errorReporting.add("Error while extracting module dependencies: 'modules' function was executed more than once", nullptr); + return std::nullopt; + } + + auto sortedDependencies = extractedModules; + std::sort(sortedDependencies.begin(), sortedDependencies.end()); + const auto duplicateIt = std::adjacent_find(sortedDependencies.begin(), sortedDependencies.end()); + if (duplicateIt != sortedDependencies.end()) + { + errorReporting.add(fmt::format("Error while extracting module dependencies: '{}' appears more than once in dependency list", *duplicateIt), nullptr); + return std::nullopt; + } + + return extractedModules; + } +} diff --git a/lib/impl/LuaCompilationUtils.h b/lib/internals/LuaCompilationUtils.h similarity index 52% rename from lib/impl/LuaCompilationUtils.h rename to lib/internals/LuaCompilationUtils.h index ca9b87f..778362b 100644 --- a/lib/impl/LuaCompilationUtils.h +++ b/lib/internals/LuaCompilationUtils.h @@ -8,7 +8,9 @@ #pragma once +#include "impl/LuaConfigImpl.h" #include "internals/SolWrapper.h" + #include #include #include @@ -16,6 +18,7 @@ namespace rlogic { class Property; + class LuaModule; } namespace rlogic::internal @@ -23,14 +26,19 @@ namespace rlogic::internal class SolState; class ErrorReporting; - struct LuaCompiledScript + struct LuaSource { // Metadata std::string sourceCode; - std::string fileName; - - // Which Lua/sol environment holds the compiled function std::reference_wrapper solState; + StandardModules stdModules; + ModuleMapping userModules; + }; + + struct LuaCompiledScript + { + LuaSource source; + // The main function (holding interface() and run() functions) sol::protected_function mainFunction; @@ -39,17 +47,43 @@ namespace rlogic::internal std::unique_ptr rootOutput; }; + struct LuaCompiledModule + { + LuaSource source; + sol::table moduleTable; + }; + class LuaCompilationUtils { public: - [[nodiscard]] static std::optional Compile( + [[nodiscard]] static std::optional CompileScript( + SolState& solState, + const ModuleMapping& userModules, + const StandardModules& stdModules, + std::string source, + std::string_view name, + ErrorReporting& errorReporting); + + [[nodiscard]] static std::optional CompileModule( SolState& solState, + const ModuleMapping& userModules, + const StandardModules& stdModules, std::string source, - std::string_view scriptName, - std::string filename, + std::string_view name, + ErrorReporting& errorReporting); + + [[nodiscard]] static bool CheckModuleName(std::string_view name); + + [[nodiscard]] static std::optional> ExtractModuleDependencies( + std::string_view source, ErrorReporting& errorReporting); private: - [[nodiscard]] static std::string BuildChunkName(std::string_view scriptName, std::string_view fileName); + [[nodiscard]] static std::string BuildChunkName(std::string_view scriptName); + [[nodiscard]] static bool CrossCheckDeclaredAndProvidedModules( + std::string_view source, + const ModuleMapping& modules, + std::string_view chunkname, + ErrorReporting& errorReporting); }; } diff --git a/lib/internals/PropertyTypeExtractor.cpp b/lib/internals/PropertyTypeExtractor.cpp index cc46841..c174355 100644 --- a/lib/internals/PropertyTypeExtractor.cpp +++ b/lib/internals/PropertyTypeExtractor.cpp @@ -149,7 +149,7 @@ namespace rlogic::internal return sol::object(state, sol::in_place_type, ArrayTypeInfo{arraySize, *arrayType}); } - void PropertyTypeExtractor::RegisterTypesToEnvironment(sol::environment& environment) + void PropertyTypeExtractor::RegisterTypes(sol::environment& environment) { environment.new_usertype("ArrayTypeInfo"); environment.new_usertype("LuaScriptPropertyExtractor", diff --git a/lib/internals/PropertyTypeExtractor.h b/lib/internals/PropertyTypeExtractor.h index 3a46cab..3d815a2 100644 --- a/lib/internals/PropertyTypeExtractor.h +++ b/lib/internals/PropertyTypeExtractor.h @@ -20,7 +20,7 @@ namespace rlogic::internal PropertyTypeExtractor(std::string rootName, EPropertyType rootType); // Obtain collected type info - HierarchicalTypeData getExtractedTypeData() const; + [[nodiscard]] HierarchicalTypeData getExtractedTypeData() const; // Lua overloads std::reference_wrapper index(const sol::object& propertyName); @@ -28,7 +28,7 @@ namespace rlogic::internal static sol::object CreateArray(sol::this_state state, const sol::object& size, std::optional arrayType); // Register symbols for type extraction to sol environment - static void RegisterTypesToEnvironment(sol::environment& environment); + static void RegisterTypes(sol::environment& environment); private: TypeData m_typeData; diff --git a/lib/internals/SerializationMap.h b/lib/internals/SerializationMap.h index 51ca6e8..3052fea 100644 --- a/lib/internals/SerializationMap.h +++ b/lib/internals/SerializationMap.h @@ -16,11 +16,13 @@ namespace rlogic_serialization { struct Property; struct DataArray; + struct LuaModule; } namespace rlogic { class DataArray; + class LuaModule; } namespace rlogic::internal @@ -46,20 +48,34 @@ namespace rlogic::internal void storeDataArray(const DataArray& dataArray, flatbuffers::Offset offset) { - assert(m_dataArrayIndices.count(&dataArray) == 0 && "one time store only"); - m_dataArrayIndices.insert({ &dataArray, offset }); + assert(m_dataArrays.count(&dataArray) == 0 && "one time store only"); + m_dataArrays.insert({ &dataArray, offset }); } flatbuffers::Offset resolveDataArrayOffset(const DataArray& dataArray) const { - const auto it = m_dataArrayIndices.find(&dataArray); - assert(it != m_dataArrayIndices.cend()); + const auto it = m_dataArrays.find(&dataArray); + assert(it != m_dataArrays.cend()); + return it->second; + } + + void storeLuaModule(const LuaModule& luaModule, flatbuffers::Offset offset) + { + assert(m_luaModules.count(&luaModule) == 0 && "one time store only"); + m_luaModules.insert({ &luaModule, offset }); + } + + flatbuffers::Offset resolveLuaModuleOffset(const LuaModule& luaModule) const + { + const auto it = m_luaModules.find(&luaModule); + assert(it != m_luaModules.cend()); return it->second; } private: std::unordered_map> m_properties; - std::unordered_map> m_dataArrayIndices; + std::unordered_map> m_dataArrays; + std::unordered_map> m_luaModules; }; } diff --git a/lib/internals/SolHelper.h b/lib/internals/SolHelper.h index 122f90b..9912b2b 100644 --- a/lib/internals/SolHelper.h +++ b/lib/internals/SolHelper.h @@ -9,6 +9,7 @@ #pragma once #include "fmt/format.h" +#include "ramses-logic/EStandardModule.h" #include "internals/SolWrapper.h" #include diff --git a/lib/internals/SolState.cpp b/lib/internals/SolState.cpp index 5249954..af904e8 100644 --- a/lib/internals/SolState.cpp +++ b/lib/internals/SolState.cpp @@ -8,6 +8,9 @@ #include "internals/SolState.h" +#include "ramses-logic/LuaModule.h" +#include "impl/LuaModuleImpl.h" + #include "internals/PropertyTypeExtractor.h" #include "internals/WrappedLuaProperty.h" @@ -28,31 +31,7 @@ namespace rlogic::internal SolState::SolState() { - m_solState.open_libraries(sol::lib::base, sol::lib::string, sol::lib::math, sol::lib::table, sol::lib::debug); - m_solState.set_exception_handler(&solExceptionHandler); - - m_interfaceExtractionEnvironment = sol::environment(m_solState, sol::create, m_solState.globals()); - PropertyTypeExtractor::RegisterTypesToEnvironment(m_interfaceExtractionEnvironment); - - // TODO Violin only register wrappers to runtime environments, not in the global environment - WrappedLuaProperty::RegisterTypes(m_solState); - } - - sol::load_result SolState::loadScript(std::string_view source, std::string_view scriptName) - { - return m_solState.load(source, std::string(scriptName)); - } - - sol::environment SolState::createEnvironment() - { - // No 'fallback' symbol table, the environment has no access to the - // global symbols of the default environment - sol::environment newEnv(m_solState, sol::create); - - // Set itself as a global variable registry - newEnv["_G"] = newEnv; - - const std::vector safeBaseSymbols = { + m_safeBaselibSymbols = { "assert", "error", "ipairs", @@ -76,31 +55,118 @@ namespace rlogic::internal "getmetatable", }; - for (const auto& name : safeBaseSymbols) + // TODO Violin try to load standard modules on demand + for (auto solLib : SolLibs) + { + m_solState.open_libraries(solLib); + } + + m_solState.set_exception_handler(&solExceptionHandler); + + // TODO Violin only register wrappers to runtime environments, not in the global environment + WrappedLuaProperty::RegisterTypes(m_solState); + } + + sol::load_result SolState::loadScript(std::string_view source, std::string_view scriptName) + { + return m_solState.load(source, std::string(scriptName)); + } + + sol::environment SolState::createEnvironment(const StandardModules& stdModules, const ModuleMapping& userModules, EEnvironmentType type) + { + sol::environment newEnv(m_solState, sol::create); + + // Set itself as a global variable registry + newEnv["_G"] = newEnv; + + mapStandardModules(stdModules, newEnv); + + for (const auto& module : userModules) { - newEnv[name] = m_solState[name]; + assert(!SolState::IsReservedModuleName(module.first)); + newEnv[module.first] = module.second->m_impl.getModule(); } - // TODO Violin when we implement modules, this list should be taken from the explicit lists of base + custom modules - const std::vector baseLibs = { "string", "math", "table", "debug" }; + // Allow access to interface types in all environments, except runtime + if (type != EEnvironmentType::Runtime) + { + PropertyTypeExtractor::RegisterTypes(newEnv); + } - for (const auto& name : baseLibs) + // if using 'modules' call to declare dependencies - resolve it to noop + auto dummyFunc = [](const std::string& /*unused*/) {}; + newEnv["modules"] = dummyFunc; + + return newEnv; + } + + void SolState::mapStandardModules(const StandardModules& stdModules, sol::environment& env) + { + // Copy base libraries' tables into environment to sandbox them, so that scripts cannot affect each other by modifying them + for (const auto& stdModule : stdModules) { - const sol::table& moduleAsTable = m_solState[name]; - sol::table copy(m_solState, sol::create); - for (const auto& pair : moduleAsTable) + // The base module needs special handling because it's not in a named table + if (stdModule == EStandardModule::Base) { - // first is the name of a function in module, second is the function - copy[pair.first] = pair.second; + + for (const auto& name : m_safeBaselibSymbols) + { + env[name] = m_solState[name]; + } + + } + else + { + std::string_view moduleTableName = *GetStdModuleName(stdModule); + const sol::table& moduleAsTable = m_solState[moduleTableName]; + copyTableIntoEnvironment(moduleAsTable, moduleTableName, env); } - newEnv[name] = std::move(copy); } + } - return newEnv; + void SolState::copyTableIntoEnvironment(const sol::table& table, std::string_view name, sol::environment& env) + { + sol::table copy(m_solState, sol::create); + for (const auto& pair : table) + { + // first is the name of a function in module, second is the function + copy[pair.first] = pair.second; + } + env[name] = std::move(copy); + } + + std::optional SolState::GetStdModuleName(rlogic::EStandardModule m) + { + switch (m) + { + case EStandardModule::Base: + // Standard module has no name because it's not loaded in a table but global space + return std::nullopt; + case EStandardModule::String: + return "string"; + case EStandardModule::Table: + return "table"; + case EStandardModule::Math: + return "math"; + case EStandardModule::Debug: + return "debug"; + case EStandardModule::All: + return std::nullopt; + } + return std::nullopt; } - sol::environment& SolState::getInterfaceExtractionEnvironment() + bool SolState::IsReservedModuleName(std::string_view name) { - return m_interfaceExtractionEnvironment; + for (auto m : StdModules) + { + std::optional potentialName = GetStdModuleName(m); + if (potentialName && *potentialName == name) + { + return true; + } + } + + return false; } } diff --git a/lib/internals/SolState.h b/lib/internals/SolState.h index 737fab8..f52d182 100644 --- a/lib/internals/SolState.h +++ b/lib/internals/SolState.h @@ -8,12 +8,38 @@ #pragma once +#include "impl/LuaConfigImpl.h" #include "internals/SolWrapper.h" #include +#include namespace rlogic::internal { + enum class EEnvironmentType + { + Interface, + Runtime, + Module + }; + + constexpr std::array StdModules = { + rlogic::EStandardModule::Base, + rlogic::EStandardModule::String, + rlogic::EStandardModule::Table, + rlogic::EStandardModule::Math, + rlogic::EStandardModule::Debug + }; + constexpr std::array SolLibs = { + sol::lib::base, + sol::lib::string, + sol::lib::table, + sol::lib::math, + sol::lib::debug + }; + + static_assert(StdModules.size() == SolLibs.size()); + class SolState { public: @@ -21,17 +47,26 @@ namespace rlogic::internal // Move-able (noexcept); Not copy-able ~SolState() noexcept = default; + // Not move-able because of the dependency of sol environments to the + // underlying sol state. Enabling move would require to write a custom + // move code which first moves the dependent objects, then the sol state + // (in inverse order of creation) SolState(SolState&& other) noexcept = delete; SolState& operator=(SolState&& other) noexcept = delete; SolState(const SolState& other) = delete; SolState& operator=(const SolState& other) = delete; sol::load_result loadScript(std::string_view source, std::string_view scriptName); - sol::environment& getInterfaceExtractionEnvironment(); - sol::environment createEnvironment(); + sol::environment createEnvironment(const StandardModules& stdModules, const ModuleMapping& userModules, EEnvironmentType type); + void copyTableIntoEnvironment(const sol::table& table, std::string_view name, sol::environment& env); + [[nodiscard]] static bool IsReservedModuleName(std::string_view name); private: sol::state m_solState; - sol::environment m_interfaceExtractionEnvironment; + // Cached to avoid unnecessary heap allocations + std::vector m_safeBaselibSymbols; + + void mapStandardModules(const StandardModules& stdModules, sol::environment& env); + [[nodiscard]] static std::optional GetStdModuleName(rlogic::EStandardModule m); }; } diff --git a/unittests/AnimationNodeTest.cpp b/unittests/AnimationNodeTest.cpp index a824097..f60f36e 100644 --- a/unittests/AnimationNodeTest.cpp +++ b/unittests/AnimationNodeTest.cpp @@ -7,6 +7,8 @@ // ------------------------------------------------------------------------- #include "gtest/gtest.h" +#include "WithTempDirectory.h" + #include "ramses-logic/LogicEngine.h" #include "ramses-logic/DataArray.h" #include "ramses-logic/AnimationNode.h" @@ -291,6 +293,8 @@ namespace rlogic::internal TEST_F(AnAnimationNode, CanBeSerializedAndDeserialized) { + WithTempDirectory tempDir; + { LogicEngine otherEngine; @@ -379,6 +383,8 @@ namespace rlogic::internal TEST_F(AnAnimationNode, WillSerializeAnimationInputStatesButNotProgress) { + WithTempDirectory tempDir; + { LogicEngine otherEngine; diff --git a/unittests/ApiObjectsTest.cpp b/unittests/ApiObjectsTest.cpp index 8f10fe4..afa7521 100644 --- a/unittests/ApiObjectsTest.cpp +++ b/unittests/ApiObjectsTest.cpp @@ -21,6 +21,7 @@ #include "impl/RamsesCameraBindingImpl.h" #include "ramses-logic/LuaScript.h" +#include "ramses-logic/LuaModule.h" #include "ramses-logic/RamsesNodeBinding.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/RamsesCameraBinding.h" @@ -38,7 +39,6 @@ namespace rlogic::internal class AnApiObjects : public ::testing::Test { protected: - SolState m_state; ErrorReporting m_errorReporting; ApiObjects m_apiObjects; flatbuffers::FlatBufferBuilder m_flatBufferBuilder; @@ -60,14 +60,12 @@ namespace rlogic::internal LuaScript* createScript() { - return createScript(m_apiObjects, m_state, m_valid_empty_script); + return createScript(m_apiObjects, m_valid_empty_script); } - LuaScript* createScript(ApiObjects& apiObjects, SolState& solState, std::string_view source) + LuaScript* createScript(ApiObjects& apiObjects, std::string_view source) { - auto compiledScript = LuaCompilationUtils::Compile(solState, std::string{ source }, "script", "", m_errorReporting); - EXPECT_TRUE(compiledScript); - auto script = apiObjects.createLuaScript(std::move(*compiledScript), "script"); + auto script = apiObjects.createLuaScript(source, {}, "script", m_errorReporting); EXPECT_NE(nullptr, script); return script; } @@ -92,7 +90,7 @@ namespace rlogic::internal TEST_F(AnApiObjects, ProducesErrorsWhenDestroyingScriptFromAnotherClassInstance) { ApiObjects otherInstance; - LuaScript* script = createScript(otherInstance, m_state, m_valid_empty_script); + LuaScript* script = createScript(otherInstance, m_valid_empty_script); ASSERT_FALSE(m_apiObjects.destroy(*script, m_errorReporting)); EXPECT_EQ(m_errorReporting.getErrors().size(), 1u); EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Can't find script in logic engine!"); @@ -102,6 +100,21 @@ namespace rlogic::internal EXPECT_EQ(script, otherInstance.getApiObject(script->m_impl)); } + TEST_F(AnApiObjects, CreatesLuaModule) + { + const std::string_view moduleSrc = R"( + local mymath = {} + return mymath + )"; + + auto module = m_apiObjects.createLuaModule(moduleSrc, {}, "module", m_errorReporting); + EXPECT_NE(nullptr, module); + + EXPECT_TRUE(m_errorReporting.getErrors().empty()); + ASSERT_EQ(1u, m_apiObjects.getLuaModules().size()); + EXPECT_EQ(module, m_apiObjects.getLuaModules().front().get()); + } + TEST_F(AnApiObjects, CreatesRamsesNodeBindingWithoutErrors) { RamsesNodeBinding* ramsesNodeBinding = m_apiObjects.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); @@ -232,7 +245,7 @@ namespace rlogic::internal EXPECT_EQ(m_errorReporting.getErrors()[0].object, dataArray3); m_errorReporting.clear(); - EXPECT_FALSE(m_apiObjects.destroy(*dataArray4, m_errorReporting)); + EXPECT_FALSE(m_apiObjects.destroy(*dataArray4, m_errorReporting)); EXPECT_EQ(m_errorReporting.getErrors().size(), 1u); EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Failed to destroy data array 'data4', it is used in animation node 'animNode' channel 'channel2'"); EXPECT_EQ(m_errorReporting.getErrors()[0].object, dataArray4); @@ -299,25 +312,6 @@ namespace rlogic::internal EXPECT_TRUE(m_apiObjects.getAnimationNodes().empty()); } - TEST_F(AnApiObjects, CanBeMovedWithoutChangingObjectAddresses) - { - const LuaScript* script = createScript(); - RamsesNodeBinding* ramsesNodeBinding = m_apiObjects.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); - RamsesAppearanceBinding* appBinding = m_apiObjects.createRamsesAppearanceBinding(*m_appearance, "AppearanceBinding"); - RamsesCameraBinding* camBinding = m_apiObjects.createRamsesCameraBinding(*m_camera, "CameraBinding"); - - ApiObjects movedObjects(std::move(m_apiObjects)); - EXPECT_EQ(script, movedObjects.getScripts()[0].get()); - EXPECT_EQ(ramsesNodeBinding, movedObjects.getNodeBindings()[0].get()); - EXPECT_EQ(appBinding, movedObjects.getAppearanceBindings()[0].get()); - EXPECT_EQ(camBinding, movedObjects.getCameraBindings()[0].get()); - - EXPECT_EQ(script, movedObjects.getApiObject(script->m_impl)); - EXPECT_EQ(ramsesNodeBinding, movedObjects.getApiObject(ramsesNodeBinding->m_impl)); - EXPECT_EQ(appBinding, movedObjects.getApiObject(appBinding->m_impl)); - EXPECT_EQ(camBinding, movedObjects.getApiObject(camBinding->m_impl)); - } - TEST_F(AnApiObjects, ProvidesEmptyCollections_WhenNothingWasCreated) { ScriptsContainer& scripts = m_apiObjects.getScripts(); @@ -504,9 +498,8 @@ namespace rlogic::internal // Create test flatbuffer with only a script flatbuffers::FlatBufferBuilder builder; { - SolState tempState; ApiObjects toSerialize; - createScript(toSerialize, tempState, m_valid_empty_script); + createScript(toSerialize, m_valid_empty_script); ApiObjects::Serialize(toSerialize, builder); } @@ -517,6 +510,9 @@ namespace rlogic::internal const rlogic_serialization::LuaScript& serializedScript = *serialized.luaScripts()->Get(0); EXPECT_EQ(std::string(m_valid_empty_script), serializedScript.luaSourceCode()->str()); EXPECT_EQ("script", serializedScript.name()->str()); + + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "test", m_errorReporting); + EXPECT_TRUE(deserialized); } TEST_F(AnApiObjects_Serialization, CreatesFlatbufferContainers_ForBindings) @@ -524,7 +520,6 @@ namespace rlogic::internal // Create test flatbuffer with only a node binding flatbuffers::FlatBufferBuilder builder; { - SolState tempState; ApiObjects toSerialize; toSerialize.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "node"); toSerialize.createRamsesAppearanceBinding(*m_appearance, "appearance"); @@ -555,7 +550,6 @@ namespace rlogic::internal // Create test flatbuffer with a link between script and binding flatbuffers::FlatBufferBuilder builder; { - SolState tempState; ApiObjects toSerialize; const std::string_view scriptWithOutput = R"( @@ -569,7 +563,7 @@ namespace rlogic::internal end )"; - const LuaScript* script = createScript(toSerialize, tempState, scriptWithOutput); + const LuaScript* script = createScript(toSerialize, scriptWithOutput); RamsesNodeBinding* nodeBinding = toSerialize.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); ASSERT_TRUE(toSerialize.getLogicNodeDependencies().link( *script->getOutputs()->getChild("nested")->getChild("rotation")->m_impl, @@ -599,9 +593,8 @@ namespace rlogic::internal // Create dummy data and serialize flatbuffers::FlatBufferBuilder builder; { - SolState tempState; ApiObjects toSerialize; - createScript(toSerialize, tempState, m_valid_empty_script); + createScript(toSerialize, m_valid_empty_script); toSerialize.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "node"); toSerialize.createRamsesAppearanceBinding(*m_appearance, "appearance"); toSerialize.createRamsesCameraBinding(*m_camera, "camera"); @@ -614,7 +607,7 @@ namespace rlogic::internal EXPECT_CALL(m_resolverMock, findRamsesNodeInScene(::testing::Eq("node"), m_node->getSceneObjectId())).WillOnce(::testing::Return(m_node)); EXPECT_CALL(m_resolverMock, findRamsesAppearanceInScene(::testing::Eq("appearance"), m_appearance->getSceneObjectId())).WillOnce(::testing::Return(m_appearance)); EXPECT_CALL(m_resolverMock, findRamsesCameraInScene(::testing::Eq("camera"), m_camera->getSceneObjectId())).WillOnce(::testing::Return(m_camera)); - std::optional apiObjectsOptional = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "", m_errorReporting); + std::unique_ptr apiObjectsOptional = ApiObjects::Deserialize(serialized, m_resolverMock, "", m_errorReporting); ASSERT_TRUE(apiObjectsOptional); @@ -644,7 +637,6 @@ namespace rlogic::internal // Create dummy data and serialize flatbuffers::FlatBufferBuilder builder; { - SolState tempState; ApiObjects toSerialize; const std::string_view scriptForLinks = R"( @@ -659,8 +651,8 @@ namespace rlogic::internal end )"; - auto script1 = createScript(toSerialize, tempState, scriptForLinks); - auto script2 = createScript(toSerialize, tempState, scriptForLinks); + auto script1 = createScript(toSerialize, scriptForLinks); + auto script2 = createScript(toSerialize, scriptForLinks); ASSERT_TRUE(toSerialize.getLogicNodeDependencies().link( *script1->getOutputs()->getChild("nested")->getChild("integer")->m_impl, *script2->getInputs()->getChild("integer")->m_impl, @@ -671,7 +663,7 @@ namespace rlogic::internal auto& serialized = *flatbuffers::GetRoot(builder.GetBufferPointer()); - std::optional apiObjectsOptional = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "", m_errorReporting); + std::unique_ptr apiObjectsOptional = ApiObjects::Deserialize(serialized, m_resolverMock, "", m_errorReporting); ASSERT_TRUE(apiObjectsOptional); @@ -693,6 +685,32 @@ namespace rlogic::internal EXPECT_EQ(linkMap.begin()->first, script2->getInputs()->getChild("integer")->m_impl.get()); } + // TODO VersionBreak enable test with next breaking change + //TEST_F(AnApiObjects_Serialization, ErrorWhenLuaModulesContainerMissing) + //{ + // { + // auto apiObjects = rlogic_serialization::CreateApiObjects( + // m_flatBufferBuilder, + // 0, // no modules container + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}), + // m_flatBufferBuilder.CreateVector(std::vector>{}) + // ); + // m_flatBufferBuilder.Finish(apiObjects); + // } + // + // const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); + // std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); + // + // EXPECT_FALSE(deserialized); + // ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); + // EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading from serialized data: missing Lua scripts and/or modules container!"); + //} + TEST_F(AnApiObjects_Serialization, ErrorWhenScriptsContainerMissing) { { @@ -710,11 +728,11 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); - EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading from serialized data: missing scripts container!"); + EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading from serialized data: missing Lua scripts container!"); } TEST_F(AnApiObjects_Serialization, ErrorWhenNodeBindingsContainerMissing) @@ -734,7 +752,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -758,7 +776,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -782,7 +800,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -806,7 +824,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -830,7 +848,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -854,7 +872,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); @@ -878,7 +896,7 @@ namespace rlogic::internal } const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::optional deserialized = ApiObjects::Deserialize(m_state, serialized, m_resolverMock, "unit test", m_errorReporting); + std::unique_ptr deserialized = ApiObjects::Deserialize(serialized, m_resolverMock, "unit test", m_errorReporting); EXPECT_FALSE(deserialized); ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); diff --git a/unittests/DataArrayTest.cpp b/unittests/DataArrayTest.cpp index ae65619..5808d71 100644 --- a/unittests/DataArrayTest.cpp +++ b/unittests/DataArrayTest.cpp @@ -7,6 +7,8 @@ // ------------------------------------------------------------------------- #include "gtest/gtest.h" +#include "WithTempDirectory.h" + #include "ramses-logic/LogicEngine.h" #include "ramses-logic/DataArray.h" #include "impl/DataArrayImpl.h" @@ -108,6 +110,8 @@ namespace rlogic::internal TYPED_TEST(ADataArray, CanBeSerializedAndDeserialized) { + WithTempDirectory tempDir; + const auto data1 = SomeDataVector(); const auto data2 = SomeDataVector(); const auto data3 = SomeDataVector(); diff --git a/unittests/LogicEngineTest_Animations.cpp b/unittests/LogicEngineTest_Animations.cpp index 65945ff..c59486c 100644 --- a/unittests/LogicEngineTest_Animations.cpp +++ b/unittests/LogicEngineTest_Animations.cpp @@ -8,6 +8,7 @@ #include "gtest/gtest.h" #include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" #include "ramses-logic/DataArray.h" #include "ramses-logic/AnimationNode.h" #include "ramses-logic/Property.h" @@ -102,12 +103,12 @@ namespace rlogic::internal end )"; - const auto scriptMain = m_logicEngine.createLuaScriptFromSource(scriptMainSrc, "scriptMain"); - const auto scriptDelayPlayAnim2 = m_logicEngine.createLuaScriptFromSource(scriptDelayPlaySrc, "scriptDelayPlayAnim2"); - const auto scriptDelayPlayAnim3 = m_logicEngine.createLuaScriptFromSource(scriptDelayPlaySrc, "scriptDelayPlayAnim3"); - const auto scriptScalarToVec1 = m_logicEngine.createLuaScriptFromSource(scriptScalarToVecSrc, "scriptScalarToVec1"); - const auto scriptScalarToVec2 = m_logicEngine.createLuaScriptFromSource(scriptScalarToVecSrc, "scriptScalarToVec2"); - const auto scriptScalarToVec3 = m_logicEngine.createLuaScriptFromSource(scriptScalarToVecSrc, "scriptScalarToVec3"); + const auto scriptMain = m_logicEngine.createLuaScript(scriptMainSrc); + const auto scriptDelayPlayAnim2 = m_logicEngine.createLuaScript(scriptDelayPlaySrc); + const auto scriptDelayPlayAnim3 = m_logicEngine.createLuaScript(scriptDelayPlaySrc); + const auto scriptScalarToVec1 = m_logicEngine.createLuaScript(scriptScalarToVecSrc); + const auto scriptScalarToVec2 = m_logicEngine.createLuaScript(scriptScalarToVecSrc); + const auto scriptScalarToVec3 = m_logicEngine.createLuaScript(scriptScalarToVecSrc); const auto* nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node); diff --git a/unittests/LogicEngineTest_Base.h b/unittests/LogicEngineTest_Base.h index 6ec8f0c..88c296e 100644 --- a/unittests/LogicEngineTest_Base.h +++ b/unittests/LogicEngineTest_Base.h @@ -28,6 +28,29 @@ namespace rlogic { class ALogicEngine : public ::testing::Test { + public: + static LuaConfig CreateDeps(const std::vector>& dependencies) + { + LuaConfig config; + for (const auto& [alias, module] : dependencies) + { + config.addDependency(alias, *module); + } + + return config; + } + + static LuaConfig WithStdModules(std::initializer_list modules) + { + LuaConfig config; + for (auto m : modules) + { + config.addStandardModuleDependency(m); + } + return config; + } + + protected: LogicEngine m_logicEngine; RamsesTestSetup m_ramses; @@ -46,6 +69,14 @@ namespace rlogic const std::string_view m_invalid_empty_script = R"( )"; + const std::string_view m_moduleSourceCode = R"( + local mymath = {} + function mymath.add(a,b) + print(a+b) + end + return mymath + )"; + void recreate(bool skipAppearance = false) { const ramses::sceneId_t sceneId = m_scene->getSceneId(); @@ -63,6 +94,5 @@ namespace rlogic m_appearance = &RamsesTestSetup::CreateTrivialTestAppearance(*m_scene); } } - }; } diff --git a/unittests/LogicEngineTest_DependencyExtraction.cpp b/unittests/LogicEngineTest_DependencyExtraction.cpp new file mode 100644 index 0000000..eb469b0 --- /dev/null +++ b/unittests/LogicEngineTest_DependencyExtraction.cpp @@ -0,0 +1,186 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "LogicEngineTest_Base.h" + +namespace rlogic +{ + class ALogicEngine_DependencyExtraction : public ALogicEngine + { + protected: + void extractAndExpect(std::string_view src, const std::vector& expected) + { + std::vector extractedDependencies; + std::function callbackFunc = [&extractedDependencies](const std::string& dep) { + extractedDependencies.push_back(dep); + }; + + ASSERT_TRUE(m_logicEngine.extractLuaDependencies(src, callbackFunc)); + EXPECT_EQ(expected, extractedDependencies); + EXPECT_TRUE(m_logicEngine.getErrors().empty()); + } + + void extractAndExpectError(std::string_view src, std::string_view errorMsgSubstr) + { + std::function callbackFunc = [](const std::string& /*unused*/) {}; + EXPECT_FALSE(m_logicEngine.extractLuaDependencies(src, callbackFunc)); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr(errorMsgSubstr)); + } + }; + + TEST_F(ALogicEngine_DependencyExtraction, extractsModules) + { + constexpr std::string_view src = R"( + modules("foo", "bar") + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpect(src, { "foo", "bar" }); + } + + TEST_F(ALogicEngine_DependencyExtraction, extractsEmptyModulesIfModulesNotSpecifiedInSource) + { + constexpr std::string_view src = R"( + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpect(src, {}); + } + + TEST_F(ALogicEngine_DependencyExtraction, extractsEmptyModulesIfModulesSpecifiedInScriptButEmpty) + { + constexpr std::string_view src = R"( + modules() + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpect(src, {}); + } + + TEST_F(ALogicEngine_DependencyExtraction, failsExtractModulesIfWrongArguments) + { + constexpr std::string_view src = R"( + modules("foo", 5, "bar") + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpectError(src, R"(Error while extracting module dependencies: argument 1 is of type 'number', string must be provided: ex. 'modules("moduleA", "moduleB")')"); + } + + TEST_F(ALogicEngine_DependencyExtraction, failsExtractModulesIfWrongSyntax) + { + constexpr std::string_view src = R"( + modules("foo", + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpectError(src, "'(' expected near"); + } + + TEST_F(ALogicEngine_DependencyExtraction, extractsModulesEvenIfUnresolvedLabelUsedInGlobalSpace) + { + constexpr std::string_view src = R"( + modules("foo", "bar") + + foo.bla(bar.bla) -- ILLEGAL + + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + extractAndExpect(src, { "foo", "bar" }); + } + + TEST_F(ALogicEngine_DependencyExtraction, extractsEmptyModulesIfUnresolvedLabelUsedBeforeModulesDeclaration) + { + constexpr std::string_view src = R"( + foo.bla(bar.bla) -- ILLEGAL + + modules("foo", "bar") -- modules should be declared before code + + function interface() + bla.bla = INT + end + function run() + bla.bla = bla + end + )"; + + // will not fail but will not extract modules + extractAndExpect(src, {}); + } + + TEST_F(ALogicEngine_DependencyExtraction, failsExtractModulesIfDupliciteModulesUsed) + { + extractAndExpectError(R"(modules("foo", "foo"))", "Error while extracting module dependencies: 'foo' appears more than once in dependency list"); + extractAndExpectError(R"(modules("foo", "bar", "foo"))", "Error while extracting module dependencies: 'foo' appears more than once in dependency list"); + extractAndExpectError(R"(modules("bar", "foo", "duck", "foo", "pigeon"))", "Error while extracting module dependencies: 'foo' appears more than once in dependency list"); + } + + TEST_F(ALogicEngine_DependencyExtraction, failsExtractModulesIfModulesMoreThanOnceInExecutedCode) + { + constexpr std::string_view src = R"( + modules("foo", "bar") + function interface() + bla.bla = INT + end + modules("foo2", "bar2") + function run() + bla.bla = bla + end + modules("foo3", "bar3") + )"; + + extractAndExpectError(src, "Error while extracting module dependencies: 'modules' function was executed more than once"); + } + + TEST_F(ALogicEngine_DependencyExtraction, extractsModulesFromModuleUsingAnotherModule) + { + constexpr std::string_view src = R"( + modules("foo") + local mymath = {} + function mymath.doSth(x) + return foo.compute(x) + end + mymath.pi = foo.sqrt(2) + return mymath + )"; + + extractAndExpect(src, { "foo" }); + } +} diff --git a/unittests/LogicEngineTest_Dirtiness.cpp b/unittests/LogicEngineTest_Dirtiness.cpp index f42fedf..5208140 100644 --- a/unittests/LogicEngineTest_Dirtiness.cpp +++ b/unittests/LogicEngineTest_Dirtiness.cpp @@ -13,6 +13,7 @@ #include "LogicEngineTest_Base.h" #include "impl/LogicEngineImpl.h" +#include "internals/ApiObjects.h" #include "RamsesTestUtils.h" @@ -55,7 +56,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, DirtyAfterCreatingScript) { - m_logicEngine.createLuaScriptFromSource(m_valid_empty_script); + m_logicEngine.createLuaScript(m_valid_empty_script); EXPECT_TRUE(m_apiObjects.isDirty()); } @@ -79,7 +80,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, NotDirty_AfterCreatingObjectsAndCallingUpdate) { - m_logicEngine.createLuaScriptFromSource(m_valid_empty_script); + m_logicEngine.createLuaScript(m_valid_empty_script); m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); m_logicEngine.createRamsesAppearanceBinding(*m_appearance, ""); m_logicEngine.createRamsesCameraBinding(*m_camera, ""); @@ -89,7 +90,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, Dirty_AfterSettingScriptInput) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_minimal_script); + LuaScript* script = m_logicEngine.createLuaScript(m_minimal_script); m_logicEngine.update(); script->getInputs()->getChild("data")->set(5); @@ -101,7 +102,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, Dirty_AfterSettingNestedScriptInput) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_nested_properties_script); + LuaScript* script = m_logicEngine.createLuaScript(m_nested_properties_script); m_logicEngine.update(); script->getInputs()->getChild("data")->getChild("nested")->set(5); @@ -143,8 +144,8 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, Dirty_WhenAddingLink) { - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(m_minimal_script); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_minimal_script); + LuaScript* script1 = m_logicEngine.createLuaScript(m_minimal_script); + LuaScript* script2 = m_logicEngine.createLuaScript(m_minimal_script); m_logicEngine.update(); m_logicEngine.link(*script1->getOutputs()->getChild("data"), *script2->getInputs()->getChild("data")); @@ -155,8 +156,8 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, NotDirty_WhenRemovingLink) { - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(m_minimal_script); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_minimal_script); + LuaScript* script1 = m_logicEngine.createLuaScript(m_minimal_script); + LuaScript* script2 = m_logicEngine.createLuaScript(m_minimal_script); m_logicEngine.link(*script1->getOutputs()->getChild("data"), *script2->getInputs()->getChild("data")); m_logicEngine.update(); @@ -170,8 +171,8 @@ namespace rlogic::internal TEST_F(ALogicEngine_Dirtiness, NotDirty_WhenRemovingNestedLink) { - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(m_nested_properties_script); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_nested_properties_script); + LuaScript* script1 = m_logicEngine.createLuaScript(m_nested_properties_script); + LuaScript* script2 = m_logicEngine.createLuaScript(m_nested_properties_script); m_logicEngine.link(*script1->getOutputs()->getChild("data")->getChild("nested"), *script2->getInputs()->getChild("data")->getChild("nested")); m_logicEngine.update(); @@ -186,8 +187,8 @@ namespace rlogic::internal // Removing link does not mark things dirty, but setting value does TEST_F(ALogicEngine_Dirtiness, Dirty_WhenRemovingLink_AndSettingValueByCallingSetAfterwards) { - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(m_nested_properties_script); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_nested_properties_script); + LuaScript* script1 = m_logicEngine.createLuaScript(m_nested_properties_script); + LuaScript* script2 = m_logicEngine.createLuaScript(m_nested_properties_script); m_logicEngine.update(); m_logicEngine.link(*script1->getOutputs()->getChild("data")->getChild("nested"), *script2->getInputs()->getChild("data")->getChild("nested")); @@ -209,7 +210,7 @@ namespace rlogic::internal end )"; - m_logicEngine.createLuaScriptFromSource(scriptWithError); + m_logicEngine.createLuaScript(scriptWithError); EXPECT_FALSE(m_logicEngine.update()); EXPECT_TRUE(m_apiObjects.isDirty()); @@ -233,8 +234,8 @@ namespace rlogic::internal end )"; - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(scriptWithFixableError); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_minimal_script); + LuaScript* script1 = m_logicEngine.createLuaScript(scriptWithFixableError); + LuaScript* script2 = m_logicEngine.createLuaScript(m_minimal_script); // No error -> have normal run -> nothing is dirty script1->getInputs()->getChild("triggerError")->set(false); @@ -277,7 +278,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_BindingDirtiness, NotDirtyAfterCreatingScript) { - m_logicEngine.createLuaScriptFromSource(m_valid_empty_script); + m_logicEngine.createLuaScript(m_valid_empty_script); EXPECT_FALSE(m_apiObjects.bindingsDirty()); } @@ -334,7 +335,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_BindingDirtiness, Dirty_WhenAddingLink) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_bindningDataScript); + LuaScript* script = m_logicEngine.createLuaScript(m_bindningDataScript); RamsesNodeBinding* binding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); m_logicEngine.update(); @@ -348,7 +349,7 @@ namespace rlogic::internal TEST_F(ALogicEngine_BindingDirtiness, NotDirty_WhenRemovingLink) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_bindningDataScript); + LuaScript* script = m_logicEngine.createLuaScript(m_bindningDataScript); RamsesNodeBinding* binding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); m_logicEngine.link(*script->getOutputs()->getChild("vec3f"), *binding->getInputs()->getChild("rotation")); m_logicEngine.update(); @@ -365,7 +366,7 @@ namespace rlogic::internal // executed when adding link, even if the link was just "re-added" TEST_F(ALogicEngine_BindingDirtiness, Dirty_WhenReAddingLink) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_bindningDataScript); + LuaScript* script = m_logicEngine.createLuaScript(m_bindningDataScript); RamsesNodeBinding* binding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); ASSERT_TRUE(m_logicEngine.link(*script->getOutputs()->getChild("vec3f"), *binding->getInputs()->getChild("rotation"))); m_logicEngine.update(); diff --git a/unittests/LogicEngineTest_ErrorHandling.cpp b/unittests/LogicEngineTest_ErrorHandling.cpp index 9b534c5..220db9d 100644 --- a/unittests/LogicEngineTest_ErrorHandling.cpp +++ b/unittests/LogicEngineTest_ErrorHandling.cpp @@ -30,25 +30,25 @@ namespace rlogic TEST_F(ALogicEngine_ErrorHandling, ClearsErrorsOnCreateNewLuaScript) { - auto script = m_logicEngine.createLuaScriptFromFile("somefile.txt"); + auto script = m_logicEngine.createLuaScript("somefile.txt"); ASSERT_EQ(nullptr, script); EXPECT_FALSE(m_logicEngine.getErrors().empty()); - script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script); + script = m_logicEngine.createLuaScript(m_valid_empty_script); ASSERT_NE(nullptr, script); EXPECT_TRUE(m_logicEngine.getErrors().empty()); } TEST_F(ALogicEngine_ErrorHandling, ReturnsOnFirstError) { - auto script = m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + auto script = m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(nullptr, script); EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); } TEST_F(ALogicEngine_ErrorHandling, ClearsErrorsOnUpdate) { - auto script = m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + auto script = m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(nullptr, script); EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -73,17 +73,17 @@ namespace rlogic { WithTempDirectory tempFolder; - m_logicEngine.createLuaScriptFromSource(m_valid_empty_script); + m_logicEngine.createLuaScript(m_valid_empty_script); // Generate error, so that we can test it's cleared by saveToFile() - m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_TRUE(m_logicEngine.saveToFile("logic.bin")); EXPECT_EQ(m_logicEngine.getErrors().size(), 0u); // Generate error, so that we can test it's cleared by loadFromFile() - m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_TRUE(m_logicEngine.loadFromFile("logic.bin")); @@ -92,18 +92,18 @@ namespace rlogic TEST_F(ALogicEngine_ErrorHandling, ClearsErrorsOnLinkAndUnlink) { - LuaScript* script1 = m_logicEngine.createLuaScriptFromSource(m_linkable_script); - LuaScript* script2 = m_logicEngine.createLuaScriptFromSource(m_linkable_script); + LuaScript* script1 = m_logicEngine.createLuaScript(m_linkable_script); + LuaScript* script2 = m_logicEngine.createLuaScript(m_linkable_script); // Generate error, so that we can test it's cleared by link() - m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_TRUE(m_logicEngine.link(*script1->getOutputs()->getChild("output"), *script2->getInputs()->getChild("input"))); EXPECT_EQ(m_logicEngine.getErrors().size(), 0u); // Generate error, so that we can test it's cleared by unlink() - m_logicEngine.createLuaScriptFromSource(m_invalid_empty_script); + m_logicEngine.createLuaScript(m_invalid_empty_script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_TRUE(m_logicEngine.unlink(*script1->getOutputs()->getChild("output"), *script2->getInputs()->getChild("input"))); diff --git a/unittests/LogicEngineTest_Factory.cpp b/unittests/LogicEngineTest_Factory.cpp index 135343a..bc502ad 100644 --- a/unittests/LogicEngineTest_Factory.cpp +++ b/unittests/LogicEngineTest_Factory.cpp @@ -11,10 +11,12 @@ #include "ramses-logic/RamsesNodeBinding.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/RamsesCameraBinding.h" +#include "ramses-logic/LuaModule.h" #include "impl/LogicNodeImpl.h" #include "WithTempDirectory.h" + #include namespace rlogic @@ -28,66 +30,154 @@ namespace rlogic TEST_F(ALogicEngine_Factory, ProducesErrorWhenCreatingEmptyScript) { - const LuaScript* script = m_logicEngine.createLuaScriptFromSource("", ""); + const LuaScript* script = m_logicEngine.createLuaScript(""); ASSERT_EQ(nullptr, script); EXPECT_FALSE(m_logicEngine.getErrors().empty()); } TEST_F(ALogicEngine_Factory, CreatesScriptFromValidLuaWithoutErrors) { - const LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, ""); + const LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script); ASSERT_TRUE(nullptr != script); EXPECT_TRUE(m_logicEngine.getErrors().empty()); } TEST_F(ALogicEngine_Factory, DestroysScriptWithoutErrors) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, ""); + LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script); ASSERT_TRUE(script); ASSERT_TRUE(m_logicEngine.destroy(*script)); } - TEST_F(ALogicEngine_Factory, FailsToCreateScriptFromFile_WhenFileDoesNotExist) + TEST_F(ALogicEngine_Factory, ProducesErrorsWhenDestroyingScriptFromAnotherEngineInstance) { - auto script = m_logicEngine.createLuaScriptFromFile("somefile.txt"); - ASSERT_EQ(nullptr, script); - EXPECT_FALSE(m_logicEngine.getErrors().empty()); + LogicEngine otherLogicEngine; + auto script = otherLogicEngine.createLuaScript(m_valid_empty_script); + ASSERT_TRUE(script); + ASSERT_FALSE(m_logicEngine.destroy(*script)); + EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); + EXPECT_EQ(m_logicEngine.getErrors()[0].message, "Can't find script in logic engine!"); } - TEST_F(ALogicEngine_Factory, FailsToLoadsScriptFromEmptyFile) + TEST_F(ALogicEngine_Factory, CreatesLuaModule) { - std::ofstream ofs; - ofs.open("empty.lua", std::ofstream::out); - ofs.close(); + const auto module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule"); + ASSERT_NE(nullptr, module); + EXPECT_TRUE(m_logicEngine.getErrors().empty()); - auto script = m_logicEngine.createLuaScriptFromFile("empty.lua"); - ASSERT_EQ(nullptr, script); - EXPECT_FALSE(m_logicEngine.getErrors().empty()); + EXPECT_EQ(module, m_logicEngine.findLuaModule("mymodule")); + ASSERT_EQ(1u, m_logicEngine.luaModules().size()); + EXPECT_EQ(module, *m_logicEngine.luaModules().cbegin()); + + const auto& constLogicEngine = m_logicEngine; + EXPECT_EQ(module, constLogicEngine.findLuaModule("mymodule")); } - TEST_F(ALogicEngine_Factory, LoadsScriptFromValidLuaFileWithoutErrors) + TEST_F(ALogicEngine_Factory, AllowsCreatingLuaModuleWithEmptyName) { - std::ofstream ofs; - ofs.open("valid.lua", std::ofstream::out); - ofs << m_valid_empty_script; - ofs.close(); + EXPECT_NE(nullptr, m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "")); + EXPECT_TRUE(m_logicEngine.getErrors().empty()); + } - auto script = m_logicEngine.createLuaScriptFromFile("valid.lua"); - ASSERT_TRUE(nullptr != script); + TEST_F(ALogicEngine_Factory, AllowsCreatingLuaModuleWithNameContainingNonAlphanumericChars) + { + EXPECT_NE(nullptr, m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "!@#$")); EXPECT_TRUE(m_logicEngine.getErrors().empty()); - // TODO Violin fix this test! - //EXPECT_EQ("what name do we want here?", script->getName()); - EXPECT_EQ("valid.lua", script->getFilename()); } - TEST_F(ALogicEngine_Factory, ProducesErrorsWhenDestroyingScriptFromAnotherEngineInstance) + TEST_F(ALogicEngine_Factory, AllowsCreatingLuaModuleWithDupliciteNameEvenIfSourceDiffers) { - LogicEngine otherLogicEngine; - auto script = otherLogicEngine.createLuaScriptFromSource(m_valid_empty_script); - ASSERT_TRUE(script); - ASSERT_FALSE(m_logicEngine.destroy(*script)); - EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); - EXPECT_EQ(m_logicEngine.getErrors()[0].message, "Can't find script in logic engine!"); + ASSERT_TRUE(m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule")); + // same name and same source is OK + EXPECT_TRUE(m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule")); + + // same name and different source is also OK + EXPECT_TRUE(m_logicEngine.createLuaModule("return {}", {}, "mymodule")); + } + + TEST_F(ALogicEngine_Factory, CanDestroyLuaModule) + { + LuaModule* module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule"); + ASSERT_NE(nullptr, module); + EXPECT_TRUE(m_logicEngine.destroy(*module)); + EXPECT_TRUE(m_logicEngine.getErrors().empty()); + EXPECT_FALSE(m_logicEngine.findLuaModule("mymodule")); + } + + TEST_F(ALogicEngine_Factory, FailsToDestroyLuaModuleIfFromOtherLogicInstance) + { + LogicEngine otherLogic; + LuaModule* module = otherLogic.createLuaModule(m_moduleSourceCode); + ASSERT_NE(nullptr, module); + + EXPECT_FALSE(m_logicEngine.destroy(*module)); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_EQ(m_logicEngine.getErrors().front().message, "Can't find Lua module in logic engine!"); + } + + TEST_F(ALogicEngine_Factory, FailsToDestroyLuaModuleIfUsedInLuaScript) + { + LuaModule* module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule"); + ASSERT_NE(nullptr, module); + + constexpr std::string_view valid_empty_script = R"( + modules("mymodule") + function interface() + end + function run() + end + )"; + EXPECT_TRUE(m_logicEngine.createLuaScript(valid_empty_script, CreateDeps({ { "mymodule", module } }), "script")); + + EXPECT_FALSE(m_logicEngine.destroy(*module)); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_EQ(m_logicEngine.getErrors().front().message, "Failed to destroy LuaModule 'mymodule', it is used in LuaScript 'script'"); + } + + TEST_F(ALogicEngine_Factory, CanDestroyModuleAfterItIsNotUsedAnymore) + { + LuaModule* module = m_logicEngine.createLuaModule(m_moduleSourceCode); + ASSERT_NE(nullptr, module); + + constexpr std::string_view valid_empty_script = R"( + modules("mymodule") + function interface() + end + function run() + end + )"; + auto script = m_logicEngine.createLuaScript(valid_empty_script, CreateDeps({ { "mymodule", module } })); + ASSERT_NE(nullptr, script); + EXPECT_FALSE(m_logicEngine.destroy(*module)); + + EXPECT_TRUE(m_logicEngine.destroy(*script)); + EXPECT_TRUE(m_logicEngine.destroy(*module)); + } + + TEST_F(ALogicEngine_Factory, ProducesErrorWhenCreatingLuaScriptUsingModuleFromAnotherLogicInstance) + { + LogicEngine other; + const auto module = other.createLuaModule(m_moduleSourceCode); + ASSERT_NE(nullptr, module); + + EXPECT_EQ(nullptr, m_logicEngine.createLuaScript(m_valid_empty_script, CreateDeps({ { "name", module } }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_EQ(m_logicEngine.getErrors().front().message, + "Failed to map Lua module 'name'! It was created on a different instance of LogicEngine."); + } + + TEST_F(ALogicEngine_Factory, ProducesErrorWhenCreatingLuaModuleUsingModuleFromAnotherLogicInstance) + { + LogicEngine other; + const auto module = other.createLuaModule(m_moduleSourceCode); + ASSERT_NE(nullptr, module); + + LuaConfig config; + config.addDependency("name", *module); + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(m_valid_empty_script, config)); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_EQ(m_logicEngine.getErrors().front().message, + "Failed to map Lua module 'name'! It was created on a different instance of LogicEngine."); } TEST_F(ALogicEngine_Factory, ProducesErrorsWhenDestroyingRamsesNodeBindingFromAnotherEngineInstance) @@ -130,7 +220,7 @@ namespace rlogic TEST_F(ALogicEngine_Factory, RenamesObjectsAfterCreation) { - auto script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, ""); + auto script = m_logicEngine.createLuaScript(m_valid_empty_script); auto ramsesNodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); auto ramsesAppearanceBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "AppearanceBinding"); auto ramsesCameraBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "CameraBinding"); @@ -175,7 +265,7 @@ namespace rlogic TEST_F(ALogicEngine_Factory, CanBeMoved) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, "Script"); + LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script, {}, "Script"); RamsesNodeBinding* ramsesNodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); RamsesAppearanceBinding* appBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "AppearanceBinding"); RamsesCameraBinding* camBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "CameraBinding"); diff --git a/unittests/LogicEngineTest_Linking.cpp b/unittests/LogicEngineTest_Linking.cpp index e7a64b4..561906d 100644 --- a/unittests/LogicEngineTest_Linking.cpp +++ b/unittests/LogicEngineTest_Linking.cpp @@ -20,6 +20,7 @@ #include "ramses-client-api/PerspectiveCamera.h" #include "ramses-logic/LuaScript.h" +#include "ramses-logic/EStandardModule.h" #include "ramses-logic/Property.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/RamsesNodeBinding.h" @@ -29,6 +30,7 @@ #include "impl/RamsesNodeBindingImpl.h" #include "impl/LogicNodeImpl.h" #include "impl/PropertyImpl.h" +#include "internals/ApiObjects.h" #include "fmt/format.h" #include @@ -39,8 +41,8 @@ namespace rlogic { protected: ALogicEngine_Linking() - : m_sourceScript(*m_logicEngine.createLuaScriptFromSource(m_minimalLinkScript, "SourceScript")) - , m_targetScript(*m_logicEngine.createLuaScriptFromSource(m_minimalLinkScript, "TargetScript")) + : m_sourceScript(*m_logicEngine.createLuaScript(m_minimalLinkScript, {}, "SourceScript")) + , m_targetScript(*m_logicEngine.createLuaScript(m_minimalLinkScript, {}, "TargetScript")) , m_sourceProperty(*m_sourceScript.getOutputs()->getChild("source")) , m_targetProperty(*m_targetScript.getInputs()->getChild("target")) { @@ -102,8 +104,8 @@ namespace rlogic end )", std::get<1>(errorCase), std::get<0>(errorCase)); - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); const auto sourceProperty = sourceScript->getOutputs()->getChild("outParam"); const auto targetProperty = targetScript->getInputs()->getChild("inParam"); @@ -155,8 +157,8 @@ namespace rlogic TEST_F(ALogicEngine_Linking, ProducesErrorIfOutputIsLinkedToOutput) { - auto sourceScript = m_logicEngine.createLuaScriptFromSource(m_linkScriptMultipleTypes); - auto targetScript = m_logicEngine.createLuaScriptFromSource(m_linkScriptMultipleTypes); + auto sourceScript = m_logicEngine.createLuaScript(m_linkScriptMultipleTypes); + auto targetScript = m_logicEngine.createLuaScript(m_linkScriptMultipleTypes); const auto sourceOuput = sourceScript->getOutputs()->getChild("source_INT"); const auto targetOutput = targetScript->getOutputs()->getChild("source_INT"); @@ -239,8 +241,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); const auto outputs = sourceScript->getOutputs(); const auto inputs = targetScript->getInputs(); @@ -266,7 +268,7 @@ namespace rlogic TEST_F(ALogicEngine_Linking, ProducesErrorOnNextUpdateIfLinkCycleWasCreated) { - LuaScript& loopScript = *m_logicEngine.createLuaScriptFromSource(m_minimalLinkScript); + LuaScript& loopScript = *m_logicEngine.createLuaScript(m_minimalLinkScript); const Property* sourceInput = m_sourceScript.getInputs()->getChild("target"); const Property* sourceOutput = m_sourceScript.getOutputs()->getChild("source"); const Property* targetInput = m_targetScript.getInputs()->getChild("target"); @@ -302,9 +304,9 @@ namespace rlogic end )"; - auto script1 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script1"); - auto script2 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script2"); - auto script3 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script3"); + auto script1 = m_logicEngine.createLuaScript(scriptSource); + auto script2 = m_logicEngine.createLuaScript(scriptSource); + auto script3 = m_logicEngine.createLuaScript(scriptSource); auto script1Input2 = script1->getInputs()->getChild("inString2"); auto script2Input1 = script2->getInputs()->getChild("inString1"); @@ -346,8 +348,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); const auto outputs = sourceScript->getOutputs(); const auto inputs = targetScript->getInputs(); @@ -377,8 +379,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); auto arrayTarget = targetScript->getInputs()->getChild("array"); auto arraySource = sourceScript->getOutputs()->getChild("array"); @@ -401,8 +403,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); const auto sourceProperty = sourceScript->getOutputs()->getChild("intSource"); const auto targetProperty1 = targetScript->getInputs()->getChild("intTarget1"); @@ -450,7 +452,7 @@ namespace rlogic TEST_F(ALogicEngine_Linking, ProducesNoErrorsIfMultipleLinksFromSameSourceAreUnlinked) { - auto targetScript2 = m_logicEngine.createLuaScriptFromSource(m_minimalLinkScript); + auto targetScript2 = m_logicEngine.createLuaScript(m_minimalLinkScript); const auto targetProperty2 = targetScript2->getInputs()->getChild("target"); @@ -479,8 +481,8 @@ namespace rlogic TEST_F(ALogicEngine_Linking, PropagatesOutputsToInputsIfLinked) { - auto sourceScript = m_logicEngine.createLuaScriptFromSource(m_linkScriptMultipleTypes, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(m_linkScriptMultipleTypes, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(m_linkScriptMultipleTypes); + auto targetScript = m_logicEngine.createLuaScript(m_linkScriptMultipleTypes); auto output = sourceScript->getOutputs()->getChild("source_INT"); auto input = targetScript->getInputs()->getChild("target_INT"); @@ -516,8 +518,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptArrayOfStructs, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptArrayOfStructs, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptArrayOfStructs); + auto targetScript = m_logicEngine.createLuaScript(scriptArrayOfStructs); auto output = sourceScript->getOutputs()->getChild("data")->getChild(1)->getChild("one"); auto input = targetScript->getInputs()->getChild("data")->getChild(1)->getChild("two"); @@ -551,8 +553,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptArrayOfStructs, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptArrayOfStructs, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptArrayOfStructs); + auto targetScript = m_logicEngine.createLuaScript(scriptArrayOfStructs); auto output = sourceScript->getOutputs()->getChild("data")->getChild("one")->getChild(1); auto input = targetScript->getInputs()->getChild("data")->getChild("two")->getChild(1); @@ -579,8 +581,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource); auto output = sourceScript->getOutputs()->getChild("intSource"); auto input = targetScript->getInputs()->getChild("intTarget"); @@ -619,8 +621,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource1, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource2, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource1); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource2); auto output = sourceScript->getOutputs()->getChild("intSource"); auto input1 = targetScript->getInputs()->getChild("intTarget1"); @@ -656,7 +658,7 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource, "SourceScript"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); auto targetBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "TargetBinding"); auto sourceInput = sourceScript->getInputs()->getChild("floatInput"); @@ -687,7 +689,7 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource, "SourceScript"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource); auto targetBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "TargetBinding"); auto sourceInput = sourceScript->getInputs()->getChild("floatInput"); @@ -722,8 +724,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource1, "source"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource2, "target"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource1); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource2); auto sourceOutput = sourceScript->getOutputs()->getChild("output"); auto targetInput = targetScript->getInputs()->getChild("input"); @@ -758,8 +760,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource1, "source"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource2, "target"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource1); + auto targetScript = m_logicEngine.createLuaScript(luaScriptSource2); auto sourceOutput = sourceScript->getOutputs()->getChild("output"); auto targetInput = targetScript->getInputs()->getChild("input"); @@ -795,8 +797,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(luaScriptSource, "SourceScript"); - auto targetScript = otherLogicEngine.createLuaScriptFromSource(luaScriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(luaScriptSource, {}, "SourceScript"); + auto targetScript = otherLogicEngine.createLuaScript(luaScriptSource, {}, "TargetScript"); const auto sourceOutput = sourceScript->getOutputs()->getChild("floatOutput"); const auto targetInput = targetScript->getInputs()->getChild("floatInput"); @@ -840,9 +842,9 @@ namespace rlogic end )"; - auto scriptA = m_logicEngine.createLuaScriptFromSource(sourceScript, "ScriptA"); - auto scriptB = m_logicEngine.createLuaScriptFromSource(sourceScript, "ScriptB"); - auto scriptC = m_logicEngine.createLuaScriptFromSource(targetScript, "ScriptC"); + auto scriptA = m_logicEngine.createLuaScript(sourceScript); + auto scriptB = m_logicEngine.createLuaScript(sourceScript); + auto scriptC = m_logicEngine.createLuaScript(targetScript); auto inputA = scriptA->getInputs()->getChild("floatInput"); auto outputA = scriptA->getOutputs()->getChild("floatOutput"); @@ -878,9 +880,9 @@ namespace rlogic end )"; - auto scriptA = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptA"); - auto scriptB = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptB"); - auto scriptC = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptC"); + auto scriptA = m_logicEngine.createLuaScript(scriptSource); + auto scriptB = m_logicEngine.createLuaScript(scriptSource); + auto scriptC = m_logicEngine.createLuaScript(scriptSource); auto inputA = scriptA->getInputs()->getChild("floatInput"); auto outputA = scriptA->getOutputs()->getChild("floatOutput"); @@ -924,9 +926,9 @@ namespace rlogic end )"; - auto scriptA = m_logicEngine.createLuaScriptFromSource(sourceScript, "ScriptA"); - auto scriptB = m_logicEngine.createLuaScriptFromSource(targetScript, "ScriptB"); - auto scriptC = m_logicEngine.createLuaScriptFromSource(targetScript, "ScriptC"); + auto scriptA = m_logicEngine.createLuaScript(sourceScript); + auto scriptB = m_logicEngine.createLuaScript(targetScript); + auto scriptC = m_logicEngine.createLuaScript(targetScript); auto inputA = scriptA->getInputs()->getChild("floatInput"); auto outputA = scriptA->getOutputs()->getChild("floatOutput"); @@ -967,9 +969,9 @@ namespace rlogic end )"; - auto scriptA = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptA"); - auto scriptB = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptB"); - auto scriptC = m_logicEngine.createLuaScriptFromSource(scriptSource, "ScriptC"); + auto scriptA = m_logicEngine.createLuaScript(scriptSource); + auto scriptB = m_logicEngine.createLuaScript(scriptSource); + auto scriptC = m_logicEngine.createLuaScript(scriptSource); auto inputA = scriptA->getInputs()->getChild("floatInput"); auto outputA = scriptA->getOutputs()->getChild("floatOutput"); @@ -1022,8 +1024,8 @@ namespace rlogic )"; // Create scripts in reversed order to make it more likely that order will be wrong unless ordered by dependencies - auto scriptB = m_logicEngine.createLuaScriptFromSource(srcScriptB, "ScriptB"); - auto scriptA = m_logicEngine.createLuaScriptFromSource(srcScriptA, "ScriptA"); + auto scriptB = m_logicEngine.createLuaScript(srcScriptB); + auto scriptA = m_logicEngine.createLuaScript(srcScriptA); auto scriptAOutput = scriptA->getOutputs()->getChild("output"); auto scriptAnested_str1 = scriptA->getOutputs()->getChild("nested")->getChild("str1"); @@ -1058,7 +1060,7 @@ namespace rlogic end )"; - auto script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + auto script = m_logicEngine.createLuaScript(scriptSrc); // TODO Violin add appearance binding here too, once test PR #305 is merged auto nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); @@ -1107,9 +1109,9 @@ namespace rlogic end )"; - auto scriptA = m_logicEngine.createLuaScriptFromSource(sourceScript, "ScriptA"); - auto scriptB = m_logicEngine.createLuaScriptFromSource(sourceScript, "ScriptB"); - auto scriptC = m_logicEngine.createLuaScriptFromSource(targetScript, "ScriptC"); + auto scriptA = m_logicEngine.createLuaScript(sourceScript); + auto scriptB = m_logicEngine.createLuaScript(sourceScript); + auto scriptC = m_logicEngine.createLuaScript(targetScript); auto scriptAInput = scriptA->getInputs()->getChild("floatInput"); auto scriptAOutput = scriptA->getOutputs()->getChild("floatOutput"); @@ -1152,7 +1154,7 @@ namespace rlogic { RamsesNodeBinding& nodeBinding = *m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); - auto* outScript = m_logicEngine.createLuaScriptFromSource(R"( + auto* outScript = m_logicEngine.createLuaScript(R"( function interface() OUT.out_vec3f = VEC3F end @@ -1184,7 +1186,7 @@ namespace rlogic { RamsesNodeBinding& nodeBinding = *m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, ""); - auto* outScript = m_logicEngine.createLuaScriptFromSource(R"( + auto* outScript = m_logicEngine.createLuaScript(R"( function interface() OUT.translation = VEC3F OUT.visibility = BOOL @@ -1245,6 +1247,8 @@ namespace rlogic */ LogicEngine tmpLogicEngine; + LuaConfig config; + config.addStandardModuleDependency(EStandardModule::Base); const auto srcScriptAB = R"( function interface() IN.input = STRING @@ -1266,9 +1270,9 @@ namespace rlogic )"; // Create them in reversed order to make sure they are ordered wrongly if not ordered explicitly - auto scriptC = tmpLogicEngine.createLuaScriptFromSource(srcScriptCsrc, "ScriptC"); - auto scriptB = tmpLogicEngine.createLuaScriptFromSource(srcScriptAB, "ScriptB"); - auto scriptA = tmpLogicEngine.createLuaScriptFromSource(srcScriptAB, "ScriptA"); + auto scriptC = tmpLogicEngine.createLuaScript(srcScriptCsrc, config, "ScriptC"); + auto scriptB = tmpLogicEngine.createLuaScript(srcScriptAB, config, "ScriptB"); + auto scriptA = tmpLogicEngine.createLuaScript(srcScriptAB, config, "ScriptA"); auto scriptAInput = scriptA->getInputs()->getChild("input"); auto scriptAOutput = scriptA->getOutputs()->getChild("output"); @@ -1375,8 +1379,8 @@ namespace rlogic )"; // Create scripts in reversed order to make it more likely that order will be wrong unless ordered by dependencies - auto scriptB = tmpLogicEngine.createLuaScriptFromSource(srcScriptB, "ScriptB"); - auto scriptA = tmpLogicEngine.createLuaScriptFromSource(srcScriptA, "ScriptA"); + auto scriptB = tmpLogicEngine.createLuaScript(srcScriptB, {}, "ScriptB"); + auto scriptA = tmpLogicEngine.createLuaScript(srcScriptA, {}, "ScriptA"); auto scriptAOutput = scriptA->getOutputs()->getChild("output"); auto scriptAnested_str1 = scriptA->getOutputs()->getChild("nested")->getChild("str1"); @@ -1553,7 +1557,7 @@ namespace rlogic end )"; - auto script = tmpLogicEngine.createLuaScriptFromSource(scriptSrc, "Script"); + auto script = tmpLogicEngine.createLuaScript(scriptSrc); auto nodeBinding1 = tmpLogicEngine.createRamsesNodeBinding(*ramsesNode1, ERotationType::Euler_XYZ, "NodeBinding1"); auto nodeBinding2 = tmpLogicEngine.createRamsesNodeBinding(*ramsesNode2, ERotationType::Euler_XYZ, "NodeBinding2"); @@ -1660,7 +1664,7 @@ namespace rlogic end )"; - auto script = tmpLogicEngine.createLuaScriptFromSource(scriptSrc, "Script"); + auto script = tmpLogicEngine.createLuaScript(scriptSrc); auto appBinding1 = tmpLogicEngine.createRamsesAppearanceBinding(appearance1, "AppBinding1"); auto appBinding2 = tmpLogicEngine.createRamsesAppearanceBinding(appearance2, "AppBinding2"); @@ -1745,7 +1749,7 @@ namespace rlogic end )"; - auto script = tmpLogicEngine.createLuaScriptFromSource(scriptSrc, "Script"); + auto script = tmpLogicEngine.createLuaScript(scriptSrc); auto cameraBinding1 = tmpLogicEngine.createRamsesCameraBinding(*camera1, "CameraBinding1"); auto cameraBinding2 = tmpLogicEngine.createRamsesCameraBinding(*camera2, "CameraBinding2"); @@ -1822,8 +1826,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto middleScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "MiddleScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource); + auto middleScript = m_logicEngine.createLuaScript(scriptSource); auto targetBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); auto sourceOutputBool = sourceScript->getOutputs()->getChild("output")->getChild("outBool"); @@ -1862,7 +1866,7 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource); auto targetBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "RamsesBinding"); m_logicEngine.update(); @@ -1894,8 +1898,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource); + auto targetScript = m_logicEngine.createLuaScript(scriptSource); m_logicEngine.update(); @@ -1922,7 +1926,7 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource); auto targetBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "RamsesBinding"); auto output = sourceScript->getOutputs()->getChild("output"); @@ -1981,8 +1985,8 @@ namespace rlogic TEST_F(ALogicEngine_Linking_Confidence, CanDestroyLinkedScriptsWithComplexTypes_WithLinkStillActive_DestroySourceFirst) { - LuaScript& sourceScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); - LuaScript& targetScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); + LuaScript& sourceScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); + LuaScript& targetScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); const Property& sourceVec(*sourceScript.getOutputs()->getChild("struct")->getChild("nested")->getChild("vec3f")); Property& targetVec(*targetScript.getInputs()->getChild("struct")->getChild("nested")->getChild("vec3f")); @@ -1998,8 +2002,8 @@ namespace rlogic TEST_F(ALogicEngine_Linking_Confidence, CanDestroyLinkedScriptsWithComplexTypes_WithLinkStillActive_DestroyTargetFirst) { - LuaScript& sourceScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); - LuaScript& targetScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); + LuaScript& sourceScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); + LuaScript& targetScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); const Property& sourceVec(*sourceScript.getOutputs()->getChild("struct")->getChild("nested")->getChild("vec3f")); Property& targetVec(*targetScript.getInputs()->getChild("struct")->getChild("nested")->getChild("vec3f")); @@ -2016,8 +2020,8 @@ namespace rlogic TEST_F(ALogicEngine_Linking_Confidence, CanDestroyLinkedScriptsWithComplexTypes_WithLinkStillActive_ArrayOfStructs) { - LuaScript& sourceScript(*m_logicEngine.createLuaScriptFromSource(m_scriptArrayOfStructs)); - LuaScript& targetScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); + LuaScript& sourceScript(*m_logicEngine.createLuaScript(m_scriptArrayOfStructs)); + LuaScript& targetScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); const Property& sourceVec(*sourceScript.getOutputs()->getChild("array")->getChild(0)->getChild("vec3f")); Property& targetVec(*targetScript.getInputs()->getChild("struct")->getChild("nested")->getChild("vec3f")); @@ -2034,7 +2038,7 @@ namespace rlogic TEST_F(ALogicEngine_Linking_Confidence, CanDestroyLinkedBinding_WithNestedLinkStillActive) { - LuaScript& sourceScript(*m_logicEngine.createLuaScriptFromSource(m_scriptNestedStructs)); + LuaScript& sourceScript(*m_logicEngine.createLuaScript(m_scriptNestedStructs)); auto targetBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); auto translationProperty = targetBinding->getInputs()->getChild("translation"); diff --git a/unittests/LogicEngineTest_Lookup.cpp b/unittests/LogicEngineTest_Lookup.cpp index 7f384ce..d35e184 100644 --- a/unittests/LogicEngineTest_Lookup.cpp +++ b/unittests/LogicEngineTest_Lookup.cpp @@ -16,7 +16,7 @@ namespace rlogic TEST_F(ALogicEngine_Lookup, FindsObjectsByTheirName) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, "script"); + LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script, {}, "script"); RamsesNodeBinding* nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "nodebinding"); RamsesAppearanceBinding* appearanceBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "appbinding"); RamsesCameraBinding* cameraBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "camerabinding"); @@ -33,7 +33,7 @@ namespace rlogic TEST_F(ALogicEngine_Lookup, FindsObjectsByTheirName_Const) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, "script"); + LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script, {}, "script"); RamsesNodeBinding* nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "nodebinding"); RamsesAppearanceBinding* appearanceBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "appbinding"); RamsesCameraBinding* cameraBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "camerabinding"); @@ -57,7 +57,7 @@ namespace rlogic TEST_F(ALogicEngine_Lookup, FindsObjectsAfterRenaming_ByNewNameOnly) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, "script"); + LuaScript* script = m_logicEngine.createLuaScript(m_valid_empty_script, {}, "script"); RamsesNodeBinding* nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "nodebinding"); RamsesAppearanceBinding* appearanceBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "appbinding"); RamsesCameraBinding* cameraBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "camerabinding"); @@ -91,7 +91,7 @@ namespace rlogic TEST_F(ALogicEngine_Lookup, FindsObjectByNameOnlyIfTypeMatches) { - m_logicEngine.createLuaScriptFromSource(m_valid_empty_script, "script"); + m_logicEngine.createLuaScript(m_valid_empty_script, {}, "script"); m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "nodebinding"); m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "appbinding"); m_logicEngine.createRamsesCameraBinding(*m_camera, "camerabinding"); diff --git a/unittests/LogicEngineTest_Serialization.cpp b/unittests/LogicEngineTest_Serialization.cpp index d57072c..376c118 100644 --- a/unittests/LogicEngineTest_Serialization.cpp +++ b/unittests/LogicEngineTest_Serialization.cpp @@ -31,6 +31,7 @@ #include "impl/LogicNodeImpl.h" #include "impl/LogicEngineImpl.h" #include "impl/DataArrayImpl.h" +#include "internals/ApiObjects.h" #include "internals/FileUtils.h" #include "internals/FileFormatVersions.h" #include "LogTestUtils.h" @@ -49,13 +50,13 @@ namespace rlogic::internal static std::vector CreateTestBuffer() { LogicEngine logicEngineForSaving; - logicEngineForSaving.createLuaScriptFromSource(R"( + logicEngineForSaving.createLuaScript(R"( function interface() IN.param = INT end function run() end - )", "luascript"); + )", {}, "luascript"); logicEngineForSaving.saveToFile("tempfile.bin"); @@ -254,13 +255,13 @@ namespace rlogic::internal { { LogicEngine logicEngine; - logicEngine.createLuaScriptFromSource(R"( + logicEngine.createLuaScript(R"( function interface() IN.param = INT end function run() end - )", "luascript"); + )", {}, "luascript"); logicEngine.saveToFile("LogicEngine.bin"); } @@ -364,13 +365,13 @@ namespace rlogic::internal { { LogicEngine logicEngine; - logicEngine.createLuaScriptFromSource(R"( + logicEngine.createLuaScript(R"( function interface() IN.param = INT end function run() end - )", "luascript"); + )", {}, "luascript"); logicEngine.createRamsesAppearanceBinding(*m_appearance, "appearancebinding"); logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "nodebinding"); @@ -442,25 +443,25 @@ namespace rlogic::internal { { LogicEngine logicEngine; - logicEngine.createLuaScriptFromSource(R"( + logicEngine.createLuaScript(R"( function interface() IN.param = INT end function run() end - )", "luascript"); + )", {}, "luascript"); logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "binding"); logicEngine.saveToFile("LogicEngine.bin"); } { - m_logicEngine.createLuaScriptFromSource(R"( + m_logicEngine.createLuaScript(R"( function interface() IN.param2 = FLOAT end function run() end - )", "luascript2"); + )", {}, "luascript2"); m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "binding2"); EXPECT_TRUE(m_logicEngine.loadFromFile("LogicEngine.bin", m_scene)); @@ -492,9 +493,9 @@ namespace rlogic::internal )"; LogicEngine logicEngine; - auto sourceScript = logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto targetScript = logicEngine.createLuaScriptFromSource(scriptSource, "TargetScript"); - logicEngine.createLuaScriptFromSource(scriptSource, "NotLinkedScript"); + auto sourceScript = logicEngine.createLuaScript(scriptSource, {}, "SourceScript"); + auto targetScript = logicEngine.createLuaScript(scriptSource, {}, "TargetScript"); + logicEngine.createLuaScript(scriptSource, {}, "NotLinkedScript"); auto output = sourceScript->getOutputs()->getChild("output"); auto input = targetScript->getInputs()->getChild("input"); @@ -539,8 +540,8 @@ namespace rlogic::internal end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource, {}, "SourceScript"); + auto targetScript = m_logicEngine.createLuaScript(scriptSource, {}, "TargetScript"); // Save logic engine state without links to file m_logicEngine.saveToFile("LogicEngine.bin"); @@ -575,6 +576,57 @@ namespace rlogic::internal EXPECT_EQ(2u, (*internalNodeDependencies.getTopologicallySortedNodes()).size()); } + TEST_F(ALogicEngine_Serialization, PreviouslyCreatedModulesAreDeletedInSolStateAfterDeserialization) + { + { + LogicEngine logicEngineForSaving; + const std::string_view moduleSrc = R"( + local mymath = {} + mymath.PI=3.1415 + return mymath + )"; + + std::string_view script = R"( + modules("mymath") + function interface() + OUT.pi = FLOAT + end + function run() + OUT.pi = mymath.PI + end + )"; + + LuaModule* mymath = logicEngineForSaving.createLuaModule(moduleSrc, {}, "mymath"); + LuaConfig config; + config.addDependency("mymath", *mymath); + logicEngineForSaving.createLuaScript(script, config, "script"); + + logicEngineForSaving.saveToFile("LogicEngine.bin"); + } + + // Create a module with name colliding with the one from file - it should be deleted + const std::string_view moduleToBeWipedSrc = R"( + local mymath = {} + mymath.PI=4 + return mymath + )"; + + // This module will be overwritten when loading the file below. The logic engine should not + // keep any leftovers from modules or scripts when loading from file - all content should be + // taken from the file! + LuaModule* moduleToBeWiped = m_logicEngine.createLuaModule(moduleToBeWipedSrc, {}, "mymath"); + EXPECT_NE(nullptr, moduleToBeWiped); + + EXPECT_TRUE(m_logicEngine.loadFromFile("LogicEngine.bin")); + + m_logicEngine.update(); + + LuaScript* script = m_logicEngine.findScript("script"); + + // This is the PI from the loaded module, not from 'moduleToBeWiped' + EXPECT_FLOAT_EQ(3.1415f, *script->getOutputs()->getChild("pi")->get()); + } + class ALogicEngine_Serialization_Compatibility : public ALogicEngine { protected: @@ -607,6 +659,7 @@ namespace rlogic::internal } flatbuffers::FlatBufferBuilder m_fbBuilder; + WithTempDirectory m_tempDir; }; TEST_F(ALogicEngine_Serialization_Compatibility, ProducesErrorIfDeserilizedFromFileReferencingIncompatibleRamsesVersion) @@ -664,61 +717,71 @@ namespace rlogic::internal // Then copy the resulting testLogic.bin and testScene.bin to unittests/res folder { // Load and update works - RamsesTestSetup m_ramses; - LogicEngine m_logicEngine; - ramses::Scene* m_scene = &m_ramses.loadSceneFromFile("res/unittests/testScene.bin"); - ASSERT_NE(nullptr, m_scene); - ASSERT_TRUE(m_logicEngine.loadFromFile("res/unittests/testLogic.bin", m_scene)); + RamsesTestSetup ramses; + LogicEngine logicEngine; + ramses::Scene* scene = &ramses.loadSceneFromFile("res/unittests/testScene.bin"); + ASSERT_NE(nullptr, scene); + ASSERT_TRUE(logicEngine.loadFromFile("res/unittests/testLogic.bin", scene)); // Contains objects and their input/outputs - ASSERT_NE(nullptr, m_logicEngine.findScript("script1")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script1")->getInputs()->getChild("floatInput")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script1")->getOutputs()->getChild("floatOutput")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script1")->getOutputs()->getChild("nodeTranslation")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getInputs()->getChild("floatInput")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getInputs()->getChild("floatInput")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("offsetX")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("offsetY")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("width")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("height")); - EXPECT_NE(nullptr, m_logicEngine.findScript("script2")->getOutputs()->getChild("floatUniform")); - ASSERT_NE(nullptr, m_logicEngine.findAnimationNode("animNode")); - EXPECT_NE(nullptr, m_logicEngine.findAnimationNode("animNode")->getOutputs()->getChild("channel")); - - EXPECT_NE(nullptr, m_logicEngine.findNodeBinding("nodebinding")); - EXPECT_NE(nullptr, m_logicEngine.findCameraBinding("camerabinding")); - EXPECT_NE(nullptr, m_logicEngine.findAppearanceBinding("appearancebinding")); - EXPECT_NE(nullptr, m_logicEngine.findDataArray("dataarray")); + + // When breaking file version, re-enable this check and re-export asset from assetProducer + // Consider making modules non-optional again + static_assert(g_FileFormatVersion == 2, "Consider making modules mandatory with next file version break"); + //ASSERT_NE(nullptr, logicEngine.findLuaModule("nestedModuleMath")); + //ASSERT_NE(nullptr, logicEngine.findLuaModule("moduleMath")); + //ASSERT_NE(nullptr, logicEngine.findLuaModule("moduleTypes")); + ASSERT_NE(nullptr, logicEngine.findScript("script1")); + EXPECT_NE(nullptr, logicEngine.findScript("script1")->getInputs()->getChild("floatInput")); + EXPECT_NE(nullptr, logicEngine.findScript("script1")->getOutputs()->getChild("floatOutput")); + EXPECT_NE(nullptr, logicEngine.findScript("script1")->getOutputs()->getChild("nodeTranslation")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getInputs()->getChild("floatInput")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getInputs()->getChild("floatInput")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("offsetX")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("offsetY")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("width")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getOutputs()->getChild("cameraViewport")->getChild("height")); + EXPECT_NE(nullptr, logicEngine.findScript("script2")->getOutputs()->getChild("floatUniform")); + ASSERT_NE(nullptr, logicEngine.findAnimationNode("animNode")); + EXPECT_NE(nullptr, logicEngine.findAnimationNode("animNode")->getOutputs()->getChild("channel")); + + EXPECT_NE(nullptr, logicEngine.findNodeBinding("nodebinding")); + EXPECT_NE(nullptr, logicEngine.findCameraBinding("camerabinding")); + EXPECT_NE(nullptr, logicEngine.findAppearanceBinding("appearancebinding")); + EXPECT_NE(nullptr, logicEngine.findDataArray("dataarray")); // Can set new value and update() - m_logicEngine.findScript("script1")->getInputs()->getChild("floatInput")->set(42.5f); - EXPECT_TRUE(m_logicEngine.update()); + logicEngine.findScript("script1")->getInputs()->getChild("floatInput")->set(42.5f); + EXPECT_TRUE(logicEngine.update()); // Values on Ramses are updated according to expectations vec3f translation; - auto m_node = ramses::RamsesUtils::TryConvert(*m_scene->findObjectByName("test node")); - auto m_camera = ramses::RamsesUtils::TryConvert(*m_scene->findObjectByName("test camera")); - m_node->getTranslation(translation[0], translation[1], translation[2]); + auto node = ramses::RamsesUtils::TryConvert(*scene->findObjectByName("test node")); + auto camera = ramses::RamsesUtils::TryConvert(*scene->findObjectByName("test camera")); + node->getTranslation(translation[0], translation[1], translation[2]); EXPECT_THAT(translation, ::testing::ElementsAre(42.5f, 2.f, 3.f)); - EXPECT_EQ(m_camera->getViewportX(), 45); - EXPECT_EQ(m_camera->getViewportY(), 47); - EXPECT_EQ(m_camera->getViewportWidth(), 143u); - EXPECT_EQ(m_camera->getViewportHeight(), 243u); + EXPECT_EQ(camera->getViewportX(), 45); + EXPECT_EQ(camera->getViewportY(), 47); + EXPECT_EQ(camera->getViewportWidth(), 143u); + EXPECT_EQ(camera->getViewportHeight(), 243u); // Animation node is linked and can be animated - const auto animNode = m_logicEngine.findAnimationNode("animNode"); - EXPECT_TRUE(m_logicEngine.isLinked(*animNode)); + const auto animNode = logicEngine.findAnimationNode("animNode"); + EXPECT_TRUE(logicEngine.isLinked(*animNode)); animNode->getInputs()->getChild("play")->set(true); animNode->getInputs()->getChild("timeDelta")->set(1.5f); - EXPECT_TRUE(m_logicEngine.update()); + EXPECT_TRUE(logicEngine.update()); ramses::UniformInput uniform; - auto m_appearance = ramses::RamsesUtils::TryConvert(*m_scene->findObjectByName("test appearance")); - m_appearance->getEffect().getUniformInput(1, uniform); + auto appearance = ramses::RamsesUtils::TryConvert(*scene->findObjectByName("test appearance")); + appearance->getEffect().getUniformInput(1, uniform); float floatValue = 0.f; - m_appearance->getInputValueFloat(uniform, floatValue); + appearance->getInputValueFloat(uniform, floatValue); EXPECT_FLOAT_EQ(1.5f, floatValue); + + static_assert(g_FileFormatVersion == 2); // When breaking file version, re-enable this check + //EXPECT_EQ(957, *logicEngine.findScript("script2")->getOutputs()->getChild("nestedModulesResult")->get()); } } } diff --git a/unittests/LogicEngineTest_Update.cpp b/unittests/LogicEngineTest_Update.cpp index c3361cb..7b05bb3 100644 --- a/unittests/LogicEngineTest_Update.cpp +++ b/unittests/LogicEngineTest_Update.cpp @@ -38,7 +38,7 @@ namespace rlogic TEST_F(ALogicEngine_Update, UpdatesRamsesNodeBindingValuesOnUpdate) { - auto luaScript = m_logicEngine.createLuaScriptFromSource(R"( + auto luaScript = m_logicEngine.createLuaScript(R"( function interface() IN.param = BOOL OUT.param = BOOL @@ -46,7 +46,7 @@ namespace rlogic function run() OUT.param = IN.param end - )", "Script"); + )"); auto ramsesNodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); @@ -68,7 +68,7 @@ namespace rlogic RamsesTestSetup testSetup; ramses::Scene* scene = testSetup.createScene(); - auto luaScript = m_logicEngine.createLuaScriptFromSource(R"( + auto luaScript = m_logicEngine.createLuaScript(R"( function interface() IN.param = INT OUT.param = INT @@ -76,7 +76,7 @@ namespace rlogic function run() OUT.param = IN.param end - )", "Script"); + )"); auto ramsesCameraBinding = m_logicEngine.createRamsesCameraBinding(*scene->createPerspectiveCamera(), "CameraBinding"); @@ -147,8 +147,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "Source"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "Target"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource, WithStdModules({EStandardModule::Base})); + auto targetScript = m_logicEngine.createLuaScript(scriptSource, WithStdModules({EStandardModule::Base})); auto output = sourceScript->getOutputs()->getChild("param"); auto input = targetScript->getInputs()->getChild("param"); @@ -181,7 +181,7 @@ namespace rlogic end )"; - auto script = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script"); + auto script = m_logicEngine.createLuaScript(scriptSource); auto nodeBinding = m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); auto appearanceBinding = m_logicEngine.createRamsesAppearanceBinding(*m_appearance, "AppearanceBinding"); auto cameraBinding = m_logicEngine.createRamsesCameraBinding(*m_camera, "CameraBinding"); @@ -277,8 +277,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource, {}, "SourceScript"); + auto targetScript = m_logicEngine.createLuaScript(scriptSource, {}, "TargetScript"); auto sourceInput = sourceScript->getInputs()->getChild("inFloat"); auto sourceOutput = sourceScript->getOutputs()->getChild("outFloat"); @@ -357,7 +357,7 @@ namespace rlogic std::array s = {}; for (size_t i = 0; i < s.size(); ++i) { - s[i] = m_logicEngine.createLuaScriptFromSource(scriptSource, fmt::format("Script{}", i)); + s[i] = m_logicEngine.createLuaScript(scriptSource, {}, fmt::format("Script{}", i)); } auto in1S0 = s[0]->getInputs()->getChild("in1"); @@ -450,8 +450,8 @@ namespace rlogic end )"; - auto sourceScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "SourceScript"); - auto targetScript = m_logicEngine.createLuaScriptFromSource(scriptSource, "TargetScript"); + auto sourceScript = m_logicEngine.createLuaScript(scriptSource, {}, "SourceScript"); + auto targetScript = m_logicEngine.createLuaScript(scriptSource, {}, "TargetScript"); auto sourceInput = sourceScript->getInputs()->getChild("inFloat"); auto sourceOutput = sourceScript->getOutputs()->getChild("outFloat"); diff --git a/unittests/LuaConfigTest.cpp b/unittests/LuaConfigTest.cpp new file mode 100644 index 0000000..8e59aa6 --- /dev/null +++ b/unittests/LuaConfigTest.cpp @@ -0,0 +1,183 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "LogTestUtils.h" + +#include "ramses-logic/LuaConfig.h" +#include "ramses-logic/LogicEngine.h" +#include "impl/LuaConfigImpl.h" + +#include "fmt/format.h" + +namespace rlogic::internal +{ + class ALuaConfig : public ::testing::Test + { + protected: + LogicEngine m_logicEngine; + LuaModule* m_module{ m_logicEngine.createLuaModule(R"( + local mymath = {} + function mymath.add(a,b) + print(a+b) + end + return mymath + )") }; + + std::string m_errorMessage; + ScopedLogContextLevel m_scopedLogs{ELogMessageType::Error, [&](ELogMessageType msgType, std::string_view message) { + m_errorMessage = message; + if (msgType != ELogMessageType::Error) + { + ASSERT_TRUE(false) << "Should be error!"; + } + }}; + }; + + TEST_F(ALuaConfig, IsCreated) + { + LuaConfig config; + EXPECT_TRUE(config.m_impl->getModuleMapping().empty()); + } + + TEST_F(ALuaConfig, IsCopied) + { + LuaConfig config; + config.addDependency("mod1", *m_module); + config.addDependency("mod2", *m_module); + config.addStandardModuleDependency(EStandardModule::Debug); + + LuaConfig configCopy(config); + EXPECT_EQ(config.m_impl->getModuleMapping(), configCopy.m_impl->getModuleMapping()); + EXPECT_EQ(config.m_impl->getStandardModules(), configCopy.m_impl->getStandardModules()); + } + + TEST_F(ALuaConfig, IsCopyAssigned) + { + LuaConfig config; + config.addDependency("mod1", *m_module); + config.addDependency("mod2", *m_module); + config.addStandardModuleDependency(EStandardModule::Debug); + + // Copy assignment + LuaConfig configCopy; + configCopy = config; + EXPECT_EQ(config.m_impl->getModuleMapping(), configCopy.m_impl->getModuleMapping()); + EXPECT_EQ(config.m_impl->getStandardModules(), configCopy.m_impl->getStandardModules()); + } + + TEST_F(ALuaConfig, IsMoved) + { + LuaConfig config; + config.addDependency("mod1", *m_module); + config.addStandardModuleDependency(EStandardModule::Debug); + + LuaConfig movedConfig(std::move(config)); + + EXPECT_EQ(movedConfig.m_impl->getModuleMapping().at("mod1"), m_module); + EXPECT_THAT(movedConfig.m_impl->getStandardModules(), ::testing::ElementsAre(EStandardModule::Debug)); + } + + TEST_F(ALuaConfig, IsMoveAssigned) + { + LuaConfig config; + config.addDependency("mod1", *m_module); + config.addStandardModuleDependency(EStandardModule::Debug); + + LuaConfig movedAssigned; + movedAssigned = std::move(config); + + EXPECT_EQ(movedAssigned.m_impl->getModuleMapping().at("mod1"), m_module); + EXPECT_THAT(movedAssigned.m_impl->getStandardModules(), ::testing::ElementsAre(EStandardModule::Debug)); + } + + TEST_F(ALuaConfig, ProducesErrorWhenCreatingLuaScriptUsingModuleUnderInvalidName) + { + std::vector invalidLabels = { + "", + "!invalid", + "invalid%", + "3invalid", + "42", + "\n", + }; + + LuaConfig config; + for (const auto& invalidLabel : invalidLabels) + { + EXPECT_FALSE(config.addDependency(invalidLabel, *m_module)); + + EXPECT_EQ(fmt::format("Failed to add dependency '{}'! The alias name should be a valid Lua label.", invalidLabel), m_errorMessage); + } + } + + TEST_F(ALuaConfig, ProducesErrorWhenUsingTheSameLabelTwice) + { + LuaConfig config; + + ASSERT_TRUE(config.addDependency("module", *m_module)); + EXPECT_FALSE(config.addDependency("module", *m_module)); + + EXPECT_EQ("Module dependencies must be uniquely aliased! Alias 'module' is already used!", m_errorMessage); + } + + TEST_F(ALuaConfig, ProducesErrorWhenUsingStandardModuleAsAliasName) + { + LuaConfig config; + + EXPECT_FALSE(config.addDependency("math", *m_module)); + EXPECT_EQ("Failed to add dependency 'math'! The alias collides with a standard library name!", m_errorMessage); + EXPECT_FALSE(config.addDependency("string", *m_module)); + EXPECT_EQ("Failed to add dependency 'string'! The alias collides with a standard library name!", m_errorMessage); + EXPECT_FALSE(config.addDependency("debug", *m_module)); + EXPECT_EQ("Failed to add dependency 'debug'! The alias collides with a standard library name!", m_errorMessage); + EXPECT_FALSE(config.addDependency("table", *m_module)); + EXPECT_EQ("Failed to add dependency 'table'! The alias collides with a standard library name!", m_errorMessage); + } + + class ALuaConfig_StdModules : public ::testing::Test + { + }; + + TEST_F(ALuaConfig_StdModules, AddsStandardModuleDependencies) + { + LuaConfig config; + + EXPECT_TRUE(config.m_impl->getStandardModules().empty()); + + ASSERT_TRUE(config.addStandardModuleDependency(EStandardModule::Base)); + EXPECT_THAT(config.m_impl->getStandardModules(), ::testing::ElementsAre(EStandardModule::Base)); + ASSERT_TRUE(config.addStandardModuleDependency(EStandardModule::String)); + EXPECT_THAT(config.m_impl->getStandardModules(), ::testing::ElementsAre(EStandardModule::Base, EStandardModule::String)); + } + + TEST_F(ALuaConfig_StdModules, CantAddTheSameStandardModuleTwice) + { + LuaConfig config; + + EXPECT_TRUE(config.m_impl->getStandardModules().empty()); + + ASSERT_TRUE(config.addStandardModuleDependency(EStandardModule::Base)); + EXPECT_FALSE(config.addStandardModuleDependency(EStandardModule::Base)); + } + + TEST_F(ALuaConfig_StdModules, AddsAllModulesAtOnce) + { + LuaConfig config; + + EXPECT_TRUE(config.addStandardModuleDependency(EStandardModule::All)); + + EXPECT_THAT(config.m_impl->getStandardModules(), ::testing::ElementsAre( + EStandardModule::Base, + EStandardModule::String, + EStandardModule::Table, + EStandardModule::Math, + EStandardModule::Debug)); + } +} diff --git a/unittests/LuaModuleTest.cpp b/unittests/LuaModuleTest.cpp new file mode 100644 index 0000000..77906f4 --- /dev/null +++ b/unittests/LuaModuleTest.cpp @@ -0,0 +1,335 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "WithTempDirectory.h" + +#include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaScript.h" +#include "ramses-logic/Property.h" +#include "ramses-logic/LuaModule.h" +#include "impl/LuaModuleImpl.h" +#include + +namespace rlogic::internal +{ + class ALuaModule : public ::testing::Test + { + protected: + const std::string_view m_moduleSourceCode = R"( + local mymath = {} + function mymath.add(a,b) + print(a+b) + end + return mymath + )"; + + LuaConfig createDeps(const std::vector>& dependencies) + { + LuaConfig config; + for (const auto& [alias, moduleSrc] : dependencies) + { + LuaModule* mod = m_logicEngine.createLuaModule(moduleSrc); + config.addDependency(alias, *mod); + } + + return config; + } + + LogicEngine m_logicEngine; + }; + + TEST_F(ALuaModule, IsCreated) + { + const auto module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymodule"); + ASSERT_NE(nullptr, module); + EXPECT_EQ("mymodule", module->getName()); + } + + TEST_F(ALuaModule, ChangesName) + { + const auto module = m_logicEngine.createLuaModule(m_moduleSourceCode); + ASSERT_NE(nullptr, module); + + module->setName("mm"); + EXPECT_EQ("mm", module->getName()); + EXPECT_EQ(module, this->m_logicEngine.findLuaModule("mm")); + EXPECT_TRUE(this->m_logicEngine.getErrors().empty()); + } + + TEST_F(ALuaModule, FailsCreationIfSourceInvalid) + { + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule("!", {})); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Error while loading module")); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("unexpected symbol near '!'")); + + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(R"( + local mymath = {} + function mymath.add(a,b) + print(a+b) + return mymath + )")); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Error while loading module")); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("expected (to close 'function'")); + + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(R"( + local mymath = {} + function mymath.add(a,b) + print(a+b + end + return mymath + )")); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Error while loading module")); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("expected (to close '('")); + } + + TEST_F(ALuaModule, CanBeSerialized) + { + WithTempDirectory tempDir; + + { + LogicEngine logic; + logic.createLuaModule(m_moduleSourceCode, {}, "mymodule"); + EXPECT_TRUE(logic.saveToFile("module.tmp")); + } + + EXPECT_TRUE(m_logicEngine.loadFromFile("module.tmp")); + const auto module = m_logicEngine.findLuaModule("mymodule"); + ASSERT_NE(nullptr, module); + EXPECT_EQ("mymodule", module->getName()); + EXPECT_EQ(m_moduleSourceCode, module->m_impl.getSourceCode()); + } + + class ALuaModuleWithDependency : public ALuaModule + { + protected: + std::string_view m_mathSrc = R"( + local mymath = {} + function mymath.add(a,b) + return a+b + end + return mymath + )"; + + std::string_view m_quadsSrc = R"( + modules("mymath") + local quads = {} + function quads.create(a,b) + return {math.sin(a), math.cos(b), mymath.add(a, b)} + end + return quads + )"; + }; + + TEST_F(ALuaModuleWithDependency, IsCreated) + { + const auto quadsMod = m_logicEngine.createLuaModule(m_quadsSrc, createDeps({{"mymath", m_mathSrc}}), "quadsMod"); + ASSERT_NE(nullptr, quadsMod); + EXPECT_EQ("quadsMod", quadsMod->getName()); + } + + TEST_F(ALuaModuleWithDependency, HasTwoDependencies) + { + std::string_view mathSubSrc = R"( + local mymath = {} + function mymath.sub(a,b) + return a-b + end + return mymath + )"; + + std::string_view mathCombinedSrc = R"( + modules("_mathAdd", "_mathSub") + + local mymath = {} + mymath.add=_mathAdd.add + mymath.sub=_mathSub.sub + return mymath + )"; + const auto mathCombined = m_logicEngine.createLuaModule(mathCombinedSrc, createDeps({ {"_mathAdd", m_mathSrc}, {"_mathSub", mathSubSrc} })); + + LuaConfig config; + config.addDependency("_math", *mathCombined); + const auto script = m_logicEngine.createLuaScript(R"( + modules("_math") + function interface() + OUT.added = INT + OUT.subbed = INT + end + + function run() + OUT.added = _math.add(1,2) + OUT.subbed = _math.sub(15,5) + end + )", config); + ASSERT_NE(nullptr, script); + + m_logicEngine.update(); + EXPECT_EQ(3, *script->getOutputs()->getChild("added")->get()); + EXPECT_EQ(10, *script->getOutputs()->getChild("subbed")->get()); + } + + TEST_F(ALuaModuleWithDependency, UsesSameModuleUnderMultipleNames) + { + std::string_view mathCombinedSrc = R"( + modules("math1", "math2") + local math = {} + math.add1=math1.add + math.add2=math2.add + return math + )"; + const auto mathCombined = m_logicEngine.createLuaModule(mathCombinedSrc, createDeps({ {"math1", m_mathSrc}, {"math2", m_mathSrc} })); + + LuaConfig config; + config.addDependency("mathAll", *mathCombined); + const auto script = m_logicEngine.createLuaScript(R"( + modules("mathAll") + + function interface() + OUT.result = INT + end + + function run() + OUT.result = mathAll.add1(1,2) + mathAll.add2(100,10) + end + )", config); + ASSERT_NE(nullptr, script); + + m_logicEngine.update(); + EXPECT_EQ(113, *script->getOutputs()->getChild("result")->get()); + } + + TEST_F(ALuaModuleWithDependency, TwoModulesDependOnTheSameModule) + { + const auto config = createDeps({ {"mymath", m_mathSrc} }); + + const auto mathUser1 = m_logicEngine.createLuaModule(R"( + modules("mymath") + local mathUser1 = {} + function mathUser1.add(a, b) + return mymath.add(a + 1, b + 1) + end + return mathUser1 + )", config); + + const auto mathUser2 = m_logicEngine.createLuaModule(R"( + modules("mymath") + local mathUser2 = {} + function mathUser2.add(a, b) + return mymath.add(a + 10, b + 10) + end + return mathUser2 + )", config); + + LuaConfig scriptConfig; + scriptConfig.addDependency("math1", *mathUser1); + scriptConfig.addDependency("math2", *mathUser2); + const auto script = m_logicEngine.createLuaScript(R"( + modules("math1", "math2") + function interface() + OUT.result1 = INT + OUT.result2 = INT + end + + function run() + OUT.result1 = math1.add(1,2) + OUT.result2 = math2.add(1,2) + end + )", scriptConfig); + ASSERT_NE(nullptr, script); + + m_logicEngine.update(); + EXPECT_EQ(5, *script->getOutputs()->getChild("result1")->get()); + EXPECT_EQ(23, *script->getOutputs()->getChild("result2")->get()); + } + + TEST_F(ALuaModuleWithDependency, CanBeSerialized) + { + WithTempDirectory tmpDir; + { + LogicEngine logic; + LuaConfig config; + config.addDependency("mymath", *logic.createLuaModule(m_mathSrc, {}, "mathMod")); + logic.createLuaModule(m_quadsSrc, config, "quadsMod"); + EXPECT_TRUE(logic.saveToFile("dep_modules.tmp")); + } + + EXPECT_TRUE(m_logicEngine.loadFromFile("dep_modules.tmp")); + + const LuaModule* mathMod = m_logicEngine.findLuaModule("mathMod"); + LuaModule* quadsMod = m_logicEngine.findLuaModule("quadsMod"); + ASSERT_NE(mathMod, nullptr); + ASSERT_NE(quadsMod, nullptr); + + EXPECT_THAT(quadsMod->m_impl.getDependencies(), ::testing::ElementsAre(std::pair({"mymath", mathMod}))); + } + + TEST_F(ALuaModuleWithDependency, UpdatesWithoutIssues_WhenModulesWithRuntimeErrors_AreNeverCalled) + { + std::string_view errors = R"( + local mymath = {} + function mymath.add(a,b) + error("this fails always") + return a+b + end + return mymath + )"; + const auto quadsMod = m_logicEngine.createLuaModule(m_quadsSrc, createDeps({ {"mymath", errors} })); + ASSERT_NE(nullptr, quadsMod); + + // This works fine, because neither the the quads module nor the math modules are ever called + EXPECT_TRUE(m_logicEngine.update()); + } + + class ALuaModuleDependencyMatch : public ALuaModuleWithDependency + { + }; + + TEST_F(ALuaModuleDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_NotProvidedButDeclared) + { + constexpr std::string_view mathExt = R"( + modules("dep1", "dep2") + local mymathExt = {} + mymathExt.pi = 3.14 + return mymathExt + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(mathExt, createDeps({ {"dep2", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Module dependencies declared in source code: dep1, dep2\n Module dependencies provided on create API: dep2")); + } + + TEST_F(ALuaModuleDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_ProvidedButNotDeclared) + { + constexpr std::string_view mathExt = R"( + modules("dep1", "dep2") + local mymathExt = {} + mymathExt.pi = 3.14 + return mymathExt + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(mathExt, createDeps({ {"dep1", m_moduleSourceCode}, {"dep2", m_moduleSourceCode}, {"dep3", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Module dependencies declared in source code: dep1, dep2\n Module dependencies provided on create API: dep1, dep2, dep3")); + } + + TEST_F(ALuaModuleDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_ExractionError) + { + constexpr std::string_view mathExt = R"( + modules("dep1", "dep1") -- duplicate dependency + local mymathExt = {} + mymathExt.pi = 3.14 + return mymathExt + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaModule(mathExt, createDeps({ {"dep1", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Error while extracting module dependencies: 'dep1' appears more than once in dependency list")); + } +} diff --git a/unittests/LuaScriptTest_Debug.cpp b/unittests/LuaScriptTest_Debug.cpp index f2809eb..4618672 100644 --- a/unittests/LuaScriptTest_Debug.cpp +++ b/unittests/LuaScriptTest_Debug.cpp @@ -42,7 +42,7 @@ namespace rlogic TEST_F(ALuaScript_Debug, ProducesErrorWithFullStackTrace_WhenErrorsInInterface) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_scriptWithInterfaceError, "errorscript"); + LuaScript* script = m_logicEngine.createLuaScript(m_scriptWithInterfaceError, {}, "errorscript"); ASSERT_EQ(nullptr, script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -58,7 +58,7 @@ namespace rlogic TEST_F(ALuaScript_Debug, ProducesErrorWithFullStackTrace_WhenRuntimeErrors) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_scriptWithRuntimeError, "errorscript"); + LuaScript* script = m_logicEngine.createLuaScript(m_scriptWithRuntimeError, {}, "errorscript"); ASSERT_NE(nullptr, script); m_logicEngine.update(); @@ -71,25 +71,10 @@ namespace rlogic EXPECT_EQ(script, m_logicEngine.getErrors()[0].object); } - TEST_F(ALuaScript_Debug, ErrorMessageContainsFilenameAndScriptnameWithSemicolonWhenBothAvailable) - { - WithTempDirectory tempFolder; - - std::ofstream ofs; - ofs.open("script.lua", std::ofstream::out); - ofs << m_scriptWithInterfaceError; - ofs.close(); - - auto script = m_logicEngine.createLuaScriptFromFile("script.lua", "errorscript"); - EXPECT_EQ(nullptr, script); - ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); - EXPECT_THAT(m_logicEngine.getErrors()[0].message, ::testing::HasSubstr("\t[string \"script.lua:errorscript\"]:3: in function <[string \"script.lua:errorscript\"]:2>")); - } - TEST_F(ALuaScript_Debug, ErrorStackTraceContainsScriptName_WhenScriptWasNotLoadedFromFile) { // Script loaded from string, not file - LuaScript* script = m_logicEngine.createLuaScriptFromSource(m_scriptWithInterfaceError, "errorscript"); + LuaScript* script = m_logicEngine.createLuaScript(m_scriptWithInterfaceError, {}, "errorscript"); // Error message contains script name in the stack (file not known) EXPECT_EQ(nullptr, script); @@ -105,13 +90,13 @@ namespace rlogic // Logic engine always overrides the print function internally - test that it doesn't cause crashes TEST_F(ALuaScript_Debug, DefaultOverrideOfLuaPrintFunctionDoesNotCrash) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() end function run() print("Nice message", "Another message") end - )", "PrintingScript"); + )"); ASSERT_NE(nullptr, script); @@ -122,13 +107,13 @@ namespace rlogic { std::vector messages; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() end function run() print("Nice message", "Another message") end - )", "PrintingScript"); + )", {}, "PrintingScript"); ASSERT_NE(nullptr, script); @@ -150,13 +135,13 @@ namespace rlogic { std::vector messages; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() end function run() print(42) end - )", "PrintingScript"); + )"); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); diff --git a/unittests/LuaScriptTest_Interface.cpp b/unittests/LuaScriptTest_Interface.cpp index a57664e..28997d6 100644 --- a/unittests/LuaScriptTest_Interface.cpp +++ b/unittests/LuaScriptTest_Interface.cpp @@ -25,7 +25,7 @@ namespace rlogic // Not testable, because assignment to userdata can't be catched. It's just a replacement of the current value TEST_F(ALuaScript_Interface, DISABLED_GeneratesErrorWhenOverwritingInputsInInterfaceFunction) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN = {} end @@ -42,7 +42,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, GlobalSymbolsNotAvailable) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( globalVar = "not visible" function interface() @@ -53,7 +53,7 @@ namespace rlogic function run() end - )", "noGlobalSymbols"); + )", WithStdModules({EStandardModule::Base})); ASSERT_EQ(nullptr, script); EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -62,14 +62,14 @@ namespace rlogic TEST_F(ALuaScript_Interface, ProducesErrorsIfARuntimeErrorOccursInInterface) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() error("emits error") end function run() end - )", "errorInInterface"); + )", WithStdModules({EStandardModule::Base}), "errorInInterface"); ASSERT_EQ(nullptr, script); EXPECT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -83,7 +83,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, ReturnsItsTopLevelOutputsByIndex_OrderedLexicographically) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithOutputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithOutputs); auto outputs = script->getOutputs(); @@ -114,7 +114,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, ReturnsNestedOutputsByIndex_OrderedLexicographically_whenDeclaredOneByOne) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.struct = {} OUT.struct.field3 = {} @@ -160,7 +160,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, CanDeclarePropertiesProgramatically) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.root = {} local lastStruct = OUT.root @@ -172,7 +172,7 @@ namespace rlogic function run() end - )"); + )", WithStdModules({ EStandardModule::Base })); ASSERT_NE(nullptr, script); @@ -199,7 +199,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, MarksInputsAsInput) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); auto inputs = script->getInputs(); const auto inputCount = inputs->getChildCount(); for (size_t i = 0; i < inputCount; ++i) @@ -210,7 +210,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, MarksOutputsAsOutput) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithOutputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithOutputs); auto outputs = script->getOutputs(); const auto outputCount = outputs->getChildCount(); for (size_t i = 0; i < outputCount; ++i) @@ -221,7 +221,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, AssignsDefaultValuesToItsInputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); auto inputs = script->getInputs(); auto speedInt32 = inputs->getChild("speed"); @@ -249,7 +249,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, AssignsDefaultValuesToItsOutputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithOutputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithOutputs); auto outputs = script->getOutputs(); auto speedInt32 = outputs->getChild("speed"); @@ -294,7 +294,7 @@ namespace rlogic TEST_F(ALuaScript_Interface, AssignsDefaultValuesToArrays) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.array_int = ARRAY(3, INT) IN.array_float = ARRAY(3, FLOAT) diff --git a/unittests/LuaScriptTest_Lifecycle.cpp b/unittests/LuaScriptTest_Lifecycle.cpp index d697c12..9a5ba50 100644 --- a/unittests/LuaScriptTest_Lifecycle.cpp +++ b/unittests/LuaScriptTest_Lifecycle.cpp @@ -24,54 +24,22 @@ namespace rlogic::internal WithTempDirectory tempFolder; }; - TEST_F(ALuaScript_Lifecycle, HasEmptyFilenameWhenCreatedFromSource) - { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScript); - ASSERT_NE(nullptr, script); - EXPECT_EQ("", script->getFilename()); - } - TEST_F(ALuaScript_Lifecycle, ProducesNoErrorsWhenCreatedFromMinimalScript) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScript); + auto* script = m_logicEngine.createLuaScript(m_minimalScript); ASSERT_NE(nullptr, script); EXPECT_TRUE(m_logicEngine.getErrors().empty()); } TEST_F(ALuaScript_Lifecycle, ProvidesNameAsPassedDuringCreation) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScript, "script name"); + auto* script = m_logicEngine.createLuaScript(m_minimalScript, {}, "script name"); EXPECT_EQ("script name", script->getName()); - EXPECT_EQ("", script->getFilename()); - } - - TEST_F(ALuaScript_Lifecycle, ProducesErrorWhenLoadedFileWithRuntimeErrorsInTheInterfaceFunction) - { - std::ofstream ofs; - ofs.open("script.lua", std::ofstream::out); - ofs << R"( - function interface() - error("This will cause errors when creating the script") - end - function run() - end - )"; - ofs.close(); - - const auto script = m_logicEngine.createLuaScriptFromFile("script.lua"); - EXPECT_EQ(nullptr, script); - ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); - EXPECT_EQ(m_logicEngine.getErrors()[0].message, - "[script.lua] Error while loading script. Lua stack trace:\n" - "[string \"script.lua\"]:3: This will cause errors when creating the script\n" - "stack traceback:\n" - "\t[C]: in function 'error'\n" - "\t[string \"script.lua\"]:3: in function <[string \"script.lua\"]:2>"); } TEST_F(ALuaScript_Lifecycle, KeepsGlobalScopeSymbolsDuringRunMethod) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( -- 'Local' symbols in the global space are global too local global1 = "global1" global2 = "global2" @@ -107,14 +75,14 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() IN.param = INT end function run() end - )", "MyScript"); + )"); ASSERT_NE(nullptr, script); EXPECT_TRUE(tempLogicEngine.saveToFile("script.bin")); @@ -124,8 +92,6 @@ namespace rlogic::internal const LuaScript* loadedScript = *m_logicEngine.scripts().begin(); ASSERT_NE(nullptr, loadedScript); - EXPECT_EQ("MyScript", loadedScript->getName()); - EXPECT_EQ("", loadedScript->getFilename()); auto inputs = loadedScript->getInputs(); auto outputs = loadedScript->getOutputs(); @@ -147,14 +113,14 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() IN.array = ARRAY(2, FLOAT) end function run() end - )", "MyScript"); + )", {}, "MyScript"); ASSERT_NE(nullptr, script); @@ -192,7 +158,7 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() IN.nested = @@ -202,7 +168,7 @@ namespace rlogic::internal end function run() end - )", "MyScript"); + )", {}, "MyScript"); script->getInputs()->getChild("nested")->getChild("array")->getChild(0)->set({1.1f, 1.2f, 1.3f}); EXPECT_TRUE(tempLogicEngine.saveToFile("arrays.bin")); @@ -235,7 +201,7 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() IN.int_param = INT @@ -250,7 +216,7 @@ namespace rlogic::internal function run() OUT.float_param = 47.11 end - )", "MyScript"); + )"); ASSERT_NE(nullptr, script); EXPECT_TRUE(tempLogicEngine.saveToFile("nested_array.bin")); @@ -260,8 +226,6 @@ namespace rlogic::internal const LuaScript* loadedScript = *m_logicEngine.scripts().begin(); ASSERT_NE(nullptr, loadedScript); - EXPECT_EQ("MyScript", loadedScript->getName()); - EXPECT_EQ("", loadedScript->getFilename()); auto inputs = loadedScript->getInputs(); auto outputs = loadedScript->getOutputs(); @@ -305,7 +269,7 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() local structDecl = { @@ -322,7 +286,7 @@ namespace rlogic::internal function run() OUT.arrayOfStructs = IN.arrayOfStructs end - )", "MyScript"); + )"); ASSERT_NE(nullptr, script); script->getInputs()->getChild("arrayOfStructs")->getChild(1)->getChild("nested_struct")->getChild("nested_array")->getChild(0)->set(42.f); @@ -389,7 +353,7 @@ namespace rlogic::internal { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource(scriptSrc, "MyScript"); + auto script = tempLogicEngine.createLuaScript(scriptSrc, {}, "MyScript"); ASSERT_NE(nullptr, script); EXPECT_TRUE(tempLogicEngine.saveToFile("arrays.bin")); @@ -440,14 +404,14 @@ namespace rlogic::internal { { LogicEngine tempLogicEngine; - auto script = tempLogicEngine.createLuaScriptFromSource( + auto script = tempLogicEngine.createLuaScript( R"( function interface() IN.data = INT end function run() end - )", "MyScript"); + )"); ASSERT_NE(nullptr, script); script->getInputs()->getChild("data")->set(42); diff --git a/unittests/LuaScriptTest_Modules.cpp b/unittests/LuaScriptTest_Modules.cpp new file mode 100644 index 0000000..39a4c13 --- /dev/null +++ b/unittests/LuaScriptTest_Modules.cpp @@ -0,0 +1,989 @@ +// ------------------------------------------------------------------------- +// Copyright (C) 2021 BMW AG +// ------------------------------------------------------------------------- +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +// ------------------------------------------------------------------------- + +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "WithTempDirectory.h" + +#include "ramses-logic/LogicEngine.h" +#include "ramses-logic/LuaModule.h" +#include "ramses-logic/LuaScript.h" +#include "ramses-logic/Property.h" +#include "impl/LuaScriptImpl.h" +#include + +using namespace testing; + +namespace rlogic::internal +{ + class ALuaScriptWithModule : public ::testing::Test + { + protected: + const std::string_view m_moduleSourceCode = R"( + local mymath = {} + function mymath.add(a,b) + return a+b + end + mymath.PI=3.1415 + return mymath + )"; + const std::string_view m_moduleSourceCode2 = R"( + local myothermath = {} + function myothermath.sub(a,b) + return a-b + end + function myothermath.colorType() + return { + red = INT, + blue = INT, + green = INT + } + end + myothermath.color = { + red = 255, + green = 128, + blue = 72 + } + return myothermath + )"; + + LuaConfig createDeps(const std::vector>& dependencies) + { + LuaConfig config; + for (const auto& [alias, moduleSrc] : dependencies) + { + LuaModule* mod = m_logicEngine.createLuaModule(moduleSrc); + config.addDependency(alias, *mod); + } + + return config; + } + + static LuaConfig WithStdMath() + { + LuaConfig config; + config.addStandardModuleDependency(EStandardModule::Math); + return config; + } + + LogicEngine m_logicEngine; + }; + + TEST_F(ALuaScriptWithModule, CanBeCreated) + { + LuaConfig config; + LuaModule* mod = m_logicEngine.createLuaModule(m_moduleSourceCode); + config.addDependency("mymath", *mod); + + const auto script = m_logicEngine.createLuaScript(R"( + modules("mymath") + + function interface() + OUT.v = INT + OUT.pi = FLOAT + end + + function run() + OUT.v = mymath.add(1,2) + OUT.pi = mymath.PI + end + )", config); + ASSERT_NE(nullptr, script); + EXPECT_THAT(script->m_script.getModules(), ElementsAre(Pair("mymath", mod))); + + m_logicEngine.update(); + EXPECT_EQ(3, *script->getOutputs()->getChild("v")->get()); + EXPECT_FLOAT_EQ(3.1415f, *script->getOutputs()->getChild("pi")->get()); + } + + TEST_F(ALuaScriptWithModule, UsesModuleUnderDifferentName) + { + const auto script = m_logicEngine.createLuaScript(R"( + modules("mymodule") + + function interface() + OUT.v = INT + OUT.pi = FLOAT + end + + function run() + OUT.v = mymodule.add(1,2) + OUT.pi = mymodule.PI + end + )", createDeps({ { "mymodule", m_moduleSourceCode } })); + ASSERT_NE(nullptr, script); + + m_logicEngine.update(); + EXPECT_EQ(3, *script->getOutputs()->getChild("v")->get()); + EXPECT_FLOAT_EQ(3.1415f, *script->getOutputs()->getChild("pi")->get()); + } + + TEST_F(ALuaScriptWithModule, MultipleModules) + { + const auto script = m_logicEngine.createLuaScript(R"( + modules("mymath", "mymath2") + + function interface() + OUT.v = INT + end + + function run() + OUT.v = mymath.add(1,2) + mymath2.sub(20,10) + end + )", createDeps({ { "mymath", m_moduleSourceCode }, { "mymath2", m_moduleSourceCode2 } })); + + m_logicEngine.update(); + EXPECT_EQ(13, *script->getOutputs()->getChild("v")->get()); + } + + TEST_F(ALuaScriptWithModule, UsesSameModuleUnderMultipleNames) + { + const auto module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymathmodule"); + ASSERT_NE(nullptr, module); + + LuaConfig config; + config.addDependency("mymath", *module); + config.addDependency("mymath2", *module); + + const auto script = m_logicEngine.createLuaScript(R"( + modules("mymath", "mymath2") + + function interface() + OUT.v = INT + end + + function run() + OUT.v = mymath.add(1,2) + mymath2.add(20,10) + end + )", config); + ASSERT_NE(nullptr, script); + + m_logicEngine.update(); + EXPECT_EQ(33, *script->getOutputs()->getChild("v")->get()); + } + + TEST_F(ALuaScriptWithModule, TwoScriptsUseSameModule) + { + const auto module = m_logicEngine.createLuaModule(m_moduleSourceCode, {}, "mymathmodule"); + ASSERT_NE(nullptr, module); + + LuaConfig config1; + config1.addDependency("mymath", *module); + + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + + function interface() + OUT.v = INT + end + + function run() + OUT.v = mymath.add(1,2) + end + )", config1); + ASSERT_NE(nullptr, script1); + + LuaConfig config2; + config2.addDependency("mymathother", *module); + + const auto script2 = m_logicEngine.createLuaScript(R"( + modules("mymathother") + + function interface() + OUT.v = INT + end + + function run() + OUT.v = mymathother.add(10,20) + end + )", config2); + ASSERT_NE(nullptr, script1); + + m_logicEngine.update(); + EXPECT_EQ(3, *script1->getOutputs()->getChild("v")->get()); + EXPECT_EQ(30, *script2->getOutputs()->getChild("v")->get()); + } + + TEST_F(ALuaScriptWithModule, ErrorIfModuleDoesNotReturnTable) + { + std::vector errorCases = { + "return nil", + "return IN", + "return OUT", + "return 5", + "return \"TheModule\"", + "return false", + "return true", + "return print", + }; + + for (const auto& moduleSrc : errorCases) + { + LuaModule* luaModule = m_logicEngine.createLuaModule(moduleSrc, {}, "mod"); + EXPECT_EQ(nullptr, luaModule); + + EXPECT_FALSE(m_logicEngine.getErrors().empty()); + EXPECT_EQ("[mod] Error while loading module. Module script must return a table!", m_logicEngine.getErrors()[0].message); + } + + } + + TEST_F(ALuaScriptWithModule, CanUseTableDataAndItsTypeDefinitionFromModule) + { + const auto script = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + OUT.color = mymath.colorType() + end + function run() + OUT.color = mymath.color + end + )", createDeps({ { "mymath", m_moduleSourceCode2 } })); + ASSERT_TRUE(script); + + m_logicEngine.update(); + const Property* colorOutput = script->getOutputs()->getChild("color"); + ASSERT_TRUE(colorOutput && colorOutput->getChild("red") && colorOutput->getChild("green") && colorOutput->getChild("blue")); + EXPECT_EQ(255, *colorOutput->getChild("red")->get()); + EXPECT_EQ(128, *colorOutput->getChild("green")->get()); + EXPECT_EQ(72, *colorOutput->getChild("blue")->get()); + } + + TEST_F(ALuaScriptWithModule, UsesModuleThatDependsOnAnotherModule) + { + const std::string_view wrappedModuleSrc = R"( + modules("mymath") + local wrapped = {} + function wrapped.add(a,b) + return mymath.add(a, b) + 5 + end + return wrapped + )"; + + const auto wrapped = m_logicEngine.createLuaModule(wrappedModuleSrc, createDeps({ { "mymath", m_moduleSourceCode } })); + + LuaConfig config; + config.addDependency("wrapped", *wrapped); + + const auto script = m_logicEngine.createLuaScript(R"( + modules("wrapped") + function interface() + OUT.result = INT + end + function run() + OUT.result = wrapped.add(10, 20) + end + )", config); + + EXPECT_TRUE(m_logicEngine.update()); + const Property* result = script->getOutputs()->getChild("result"); + ASSERT_TRUE(result); + EXPECT_EQ(35, *result->get()); + } + + TEST_F(ALuaScriptWithModule, SecondLevelDependenciesAreHidden) + { + const std::string_view wrappedModuleSrc = R"( + modules("mymath") + local wrapped = {} + function wrapped.add(a,b) + return a + b + 100 + end + wrapped.PI=42 + return wrapped + )"; + + const auto wrapped = m_logicEngine.createLuaModule(wrappedModuleSrc, createDeps({{"mymath", m_moduleSourceCode}})); + + LuaConfig config; + config.addDependency("wrapped", *wrapped); + + const auto script = m_logicEngine.createLuaScript(R"( + modules("wrapped") + function interface() + OUT.add = INT + OUT.PI = FLOAT + end + function run() + -- This tests that the indirect dependency is correctly hidden + if mymath ~= nil then + error("If this error happens, mymath module is not properly wrapped!") + end + OUT.add = wrapped.add(10, 20) + OUT.PI = wrapped.PI + end + )", config); + + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(130, *script->getOutputs()->getChild("add")->get()); + EXPECT_FLOAT_EQ(42.f, *script->getOutputs()->getChild("PI")->get()); + } + + TEST_F(ALuaScriptWithModule, ReloadsModuleUsingTheSameNameCausesItToBeRecompiled) + { + const std::string_view moduleSource = R"( + local mymath = {} + mymath.pi=3.1415 + return mymath + )"; + + const std::string_view moduleSource_Modified = R"( + local mymath = {} + mymath.pi=4 + return mymath + )"; + + const std::string_view scriptSrc = R"( + modules("module") + function interface() + OUT.pi = FLOAT + end + function run() + OUT.pi = module.pi + end + )"; + + auto module = m_logicEngine.createLuaModule(moduleSource, {}, "module"); + + LuaConfig config; + config.addDependency("module", *module); + auto script = m_logicEngine.createLuaScript(scriptSrc, config); + + ASSERT_TRUE(m_logicEngine.update()); + const Property* colorOutput = script->getOutputs()->getChild("pi"); + EXPECT_FLOAT_EQ(3.1415f, *colorOutput->get()); + + ASSERT_TRUE(m_logicEngine.destroy(*script)); + ASSERT_TRUE(m_logicEngine.destroy(*module)); + module = m_logicEngine.createLuaModule(moduleSource_Modified, {}, "module"); + + config = {}; + config.addDependency("module", *module); + script = m_logicEngine.createLuaScript(scriptSrc, config); + + ASSERT_TRUE(m_logicEngine.update()); + colorOutput = script->getOutputs()->getChild("pi"); + EXPECT_FLOAT_EQ(4.0f, *colorOutput->get()); + } + + TEST_F(ALuaScriptWithModule, CanBeSerialized) + { + WithTempDirectory tempDir; + + { + LogicEngine logic; + // 2 scripts, one module used by first script, other module used by both scripts + const auto module1 = logic.createLuaModule(m_moduleSourceCode, {}, "mymodule1"); + const auto module2 = logic.createLuaModule(m_moduleSourceCode2, {}, "mymodule2"); + ASSERT_TRUE(module1 && module2); + + LuaConfig config1; + config1.addDependency("mymath", *module1); + config1.addDependency("mymathother", *module2); + + LuaConfig config2; + config2.addDependency("mymath", *module2); + + logic.createLuaScript(R"( + modules("mymath", "mymathother") + function interface() + OUT.v = INT + OUT.color = mymathother.colorType() + end + function run() + OUT.v = mymath.add(1,2) + mymathother.sub(60,30) + OUT.color = mymathother.color + end + )", config1, "script1"); + logic.createLuaScript(R"( + modules("mymath") + function interface() + OUT.v = INT + end + function run() + OUT.v = mymath.sub(90,60) + end + )", config2, "script2"); + + EXPECT_TRUE(logic.saveToFile("scriptmodules.tmp")); + } + + EXPECT_TRUE(m_logicEngine.loadFromFile("scriptmodules.tmp")); + m_logicEngine.update(); + + const auto module1 = m_logicEngine.findLuaModule("mymodule1"); + const auto module2 = m_logicEngine.findLuaModule("mymodule2"); + const auto script1 = m_logicEngine.findScript("script1"); + const auto script2 = m_logicEngine.findScript("script2"); + ASSERT_TRUE(module1 && module2 && script1 && script2); + EXPECT_THAT(script1->m_script.getModules(), UnorderedElementsAre(Pair("mymath", module1), Pair("mymathother", module2))); + EXPECT_THAT(script2->m_script.getModules(), UnorderedElementsAre(Pair("mymath", module2))); + + m_logicEngine.update(); + EXPECT_EQ(33, *script1->getOutputs()->getChild("v")->get()); + const Property* colorOutput = script1->getOutputs()->getChild("color"); + ASSERT_TRUE(colorOutput && colorOutput->getChild("red") && colorOutput->getChild("green") && colorOutput->getChild("blue")); + EXPECT_EQ(255, *colorOutput->getChild("red")->get()); + EXPECT_EQ(128, *colorOutput->getChild("green")->get()); + EXPECT_EQ(72, *colorOutput->getChild("blue")->get()); + + EXPECT_EQ(30, *script2->getOutputs()->getChild("v")->get()); + } + + TEST_F(ALuaScriptWithModule, UsesStructPropertyInInterfaceDefinedInModule) + { + const std::string_view moduleDefiningInterfaceType = R"( + local mytypes = {} + function mytypes.mystruct() + return { + name = STRING, + address = + { + street = STRING, + number = INT + } + } + end + return mytypes + )"; + + const auto script = m_logicEngine.createLuaScript(R"( + modules("mytypes") + function interface() + IN.struct = mytypes.mystruct() + OUT.struct = mytypes.mystruct() + end + + function run() + OUT.struct = IN.struct + end + )", createDeps({ { "mytypes", moduleDefiningInterfaceType } })); + ASSERT_NE(nullptr, script); + + for (auto rootProp : std::initializer_list{ script->getInputs(), script->getOutputs() }) + { + ASSERT_EQ(1u, rootProp->getChildCount()); + const auto structChild = rootProp->getChild(0); + + EXPECT_EQ("struct", structChild->getName()); + EXPECT_EQ(EPropertyType::Struct, structChild->getType()); + ASSERT_EQ(2u, structChild->getChildCount()); + const auto name = structChild->getChild("name"); + EXPECT_EQ(EPropertyType::String, name->getType()); + const auto address = structChild->getChild("address"); + ASSERT_EQ(2u, address->getChildCount()); + EXPECT_EQ(EPropertyType::Struct, address->getType()); + const auto addressStr = address->getChild("street"); + const auto addressNr = address->getChild("number"); + EXPECT_EQ(EPropertyType::String, addressStr->getType()); + EXPECT_EQ(EPropertyType::Int32, addressNr->getType()); + } + EXPECT_TRUE(m_logicEngine.update()); + } + + TEST_F(ALuaScriptWithModule, UsesStructPropertyInInterfaceDefinedInModule_UseInArray) + { + const std::string_view moduleDefiningInterfaceType = R"( + local mytypes = {} + function mytypes.mystruct() + return { + name = STRING, + address = + { + street = STRING, + number = INT + } + } + end + return mytypes + )"; + + const auto script = m_logicEngine.createLuaScript(R"( + modules("mytypes") + function interface() + IN.array_of_structs = ARRAY(2, mytypes.mystruct()) + OUT.array_of_structs = ARRAY(2, mytypes.mystruct()) + end + + function run() + OUT.array_of_structs = IN.array_of_structs + end + )", createDeps({ { "mytypes", moduleDefiningInterfaceType } })); + ASSERT_NE(nullptr, script); + + for (auto rootProp : std::initializer_list{ script->getInputs(), script->getOutputs() }) + { + ASSERT_EQ(1u, rootProp->getChildCount()); + const auto arrayOfStructs = rootProp->getChild(0); + + EXPECT_EQ("array_of_structs", arrayOfStructs->getName()); + EXPECT_EQ(EPropertyType::Array, arrayOfStructs->getType()); + ASSERT_EQ(2u, arrayOfStructs->getChildCount()); + + for (size_t i = 0; i < 2; ++i) + { + const auto structChild = arrayOfStructs->getChild(i); + EXPECT_EQ(EPropertyType::Struct, structChild->getType()); + EXPECT_EQ("", structChild->getName()); + ASSERT_EQ(2u, structChild->getChildCount()); + const auto name = structChild->getChild("name"); + EXPECT_EQ(EPropertyType::String, name->getType()); + const auto address = structChild->getChild("address"); + ASSERT_EQ(2u, address->getChildCount()); + EXPECT_EQ(EPropertyType::Struct, address->getType()); + const auto addressStr = address->getChild("street"); + const auto addressNr = address->getChild("number"); + EXPECT_EQ(EPropertyType::String, addressStr->getType()); + EXPECT_EQ(EPropertyType::Int32, addressNr->getType()); + } + } + EXPECT_TRUE(m_logicEngine.update()); + } + + TEST_F(ALuaScriptWithModule, ScriptOverwritingBaseLibraryWontAffectOtherScriptUsingIt) + { + const auto script1 = m_logicEngine.createLuaScript(R"( + function interface() + IN.v = FLOAT + OUT.v = INT + end + function run() + OUT.v = math.floor(IN.v) + math.floor = nil + end + )", WithStdMath()); + ASSERT_NE(nullptr, script1); + + const auto script2 = m_logicEngine.createLuaScript(R"( + function interface() + IN.v = FLOAT + OUT.v = INT + end + function run() + OUT.v = math.floor(IN.v + 1.0) + end + )", WithStdMath()); + ASSERT_NE(nullptr, script2); + + // first update runs fine + script1->getInputs()->getChild("v")->set(1.2f); + script2->getInputs()->getChild("v")->set(1.3f); + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(1, *script1->getOutputs()->getChild("v")->get()); + EXPECT_EQ(2, *script2->getOutputs()->getChild("v")->get()); + + // force update of script2 again, after math.floor was set nil in script1 + // script2 is NOT affected + script2->getInputs()->getChild("v")->set(2.3f); + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(3, *script2->getOutputs()->getChild("v")->get()); + + // script1 broke itself by setting its dependency to nil and fails to update + script1->getInputs()->getChild("v")->set(2.2f); + EXPECT_FALSE(m_logicEngine.update()); + } + + TEST_F(ALuaScriptWithModule, ScriptOverwritingBaseLibraryViaModuleWontAffectOtherScriptUsingIt) + { + const std::string_view maliciousModuleSrc = R"( + local mymath = {} + function mymath.breakFloor(v) + ret = math.floor(v) + math.floor = nil + return ret + end + return mymath + )"; + + const auto maliciousModule = m_logicEngine.createLuaModule(maliciousModuleSrc, WithStdMath()); + + LuaConfig withMaliciousModule; + withMaliciousModule.addDependency("mymath", *maliciousModule); + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + IN.v = FLOAT + OUT.v = INT + end + function run() + OUT.v = mymath.breakFloor(IN.v) + end + )", withMaliciousModule); + ASSERT_NE(nullptr, script1); + + const auto script2 = m_logicEngine.createLuaScript(R"( + function interface() + IN.v = FLOAT + OUT.v = INT + end + function run() + OUT.v = math.floor(IN.v + 1.0) + end + )", WithStdMath()); + ASSERT_NE(nullptr, script2); + + // first update runs fine + script1->getInputs()->getChild("v")->set(1.2f); + script2->getInputs()->getChild("v")->set(1.3f); + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(1, *script1->getOutputs()->getChild("v")->get()); + EXPECT_EQ(2, *script2->getOutputs()->getChild("v")->get()); + + // force update of script2 again, after math.floor was set nil in script1 via module + // script2 is NOT affected + script2->getInputs()->getChild("v")->set(2.3f); + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(3, *script2->getOutputs()->getChild("v")->get()); + + // module broke itself by setting its math dependency to nil and script1 using it fails to update + script1->getInputs()->getChild("v")->set(2.2f); + EXPECT_FALSE(m_logicEngine.update()); + } + + class ALuaScriptDependencyMatch : public ALuaScriptWithModule + { + }; + + TEST_F(ALuaScriptDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_NotProvidedButDeclared) + { + constexpr std::string_view src = R"( + modules("dep1", "dep2") + function interface() + end + function run() + end + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaScript(src, createDeps({ {"dep2", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Module dependencies declared in source code: dep1, dep2\n Module dependencies provided on create API: dep2")); + } + + TEST_F(ALuaScriptDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_ProvidedButNotDeclared) + { + constexpr std::string_view src = R"( + modules("dep1", "dep2") + function interface() + end + function run() + end + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaScript(src, createDeps({ {"dep1", m_moduleSourceCode}, {"dep2", m_moduleSourceCode}, {"dep3", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Module dependencies declared in source code: dep1, dep2\n Module dependencies provided on create API: dep1, dep2, dep3")); + } + + TEST_F(ALuaScriptDependencyMatch, FailsToBeCreatedIfDeclaredDependencyDoesNotMatchProvidedDependency_ExractionError) + { + constexpr std::string_view src = R"( + modules("dep1", "dep1") -- duplicate dependency + function interface() + end + function run() + end + )"; + EXPECT_EQ(nullptr, m_logicEngine.createLuaScript(src, createDeps({ {"dep1", m_moduleSourceCode} }))); + ASSERT_EQ(1u, m_logicEngine.getErrors().size()); + EXPECT_THAT(m_logicEngine.getErrors().front().message, ::testing::HasSubstr("Error while extracting module dependencies: 'dep1' appears more than once in dependency list")); + } + + // Tests specifically modules isolation + class ALuaScriptWithModule_Isolation : public ALuaScriptWithModule + { + protected: + }; + + // This test reflects behavior which will be fixed in a next release (adapt the tests after the fix) + TEST_F(ALuaScriptWithModule_Isolation, ScriptOverwritingModuleFunctionAffectsOtherScriptUsingIt_InRunFunction) + { + const std::string_view mymathModuleSrc = R"( + local mymath = {} + function mymath.floor1(v) + return math.floor(v) + end + function mymath.floor2(v) + return math.floor(v) + 100 + end + return mymath + )"; + const auto mymathModule = m_logicEngine.createLuaModule(mymathModuleSrc, WithStdMath(), "mymath"); + ASSERT_NE(nullptr, mymathModule); + + LuaConfig config; + config.addDependency("mymath", *mymathModule); + + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + end + function run() + mymath.floor1 = mymath.floor2 + end + )", config); + ASSERT_NE(nullptr, script1); + + // This will overwrite the module function floor1 with floor2 + EXPECT_TRUE(m_logicEngine.update()); + + const auto script2 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + OUT.floor1 = INT + OUT.floor2 = INT + end + function run() + OUT.floor1 = mymath.floor1(1.5) + OUT.floor2 = mymath.floor2(1.5) + end + )", config); + ASSERT_NE(nullptr, script2); + + EXPECT_TRUE(m_logicEngine.update()); + + // Both floor1 and floor2 have the modified code + EXPECT_EQ(101, *script2->getOutputs()->getChild("floor1")->get()); + EXPECT_EQ(101, *script2->getOutputs()->getChild("floor2")->get()); + } + + // This test reflects behavior which will be fixed in a next release (adapt the tests after the fix) + TEST_F(ALuaScriptWithModule_Isolation, ScriptOverwritingModuleFunctionAffectsOtherScriptUsingIt_InInterfaceFunction) + { + const std::string_view mymathModuleSrc = R"( + local mymath = {} + function mymath.floor1(v) + return math.floor(v) + end + function mymath.floor2(v) + return math.floor(v) + 100 + end + return mymath + )"; + const auto mymathModule = m_logicEngine.createLuaModule(mymathModuleSrc, WithStdMath()); + ASSERT_NE(nullptr, mymathModule); + + LuaConfig config; + config.addDependency("mymath", *mymathModule); + + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + mymath.floor1 = mymath.floor2 + end + function run() + end + )", config); + ASSERT_NE(nullptr, script1); + + // This will overwrite the module function floor1 with floor2 + EXPECT_TRUE(m_logicEngine.update()); + + const auto script2 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + OUT.floor1 = INT + OUT.floor2 = INT + end + function run() + OUT.floor1 = mymath.floor1(1.5) + OUT.floor2 = mymath.floor2(1.5) + end + )", config); + ASSERT_NE(nullptr, script2); + + EXPECT_TRUE(m_logicEngine.update()); + + // Both floor1 and floor2 have the modified code + EXPECT_EQ(101, *script2->getOutputs()->getChild("floor1")->get()); + EXPECT_EQ(101, *script2->getOutputs()->getChild("floor2")->get()); + } + + // This test reflects behavior which will be fixed in a next release (adapt the tests after the fix) + TEST_F(ALuaScriptWithModule_Isolation, ScriptOverwritingModuleDataAffectsOtherScriptUsingIt_InRunFunction) + { + const std::string_view mymathModuleSrc = R"( + local mymath = {} + mymath.data = 1 + return mymath + )"; + const auto mymathModule = m_logicEngine.createLuaModule(mymathModuleSrc); + ASSERT_NE(nullptr, mymathModule); + + LuaConfig config; + config.addDependency("mymath", *mymathModule); + + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + end + function run() + mymath.data = 42 + end + )", config); + ASSERT_NE(nullptr, script1); + + // This will overwrite the module function floor1 with floor2 + EXPECT_TRUE(m_logicEngine.update()); + + const auto script2 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + OUT.data = INT + end + function run() + OUT.data = mymath.data + end + )", config); + ASSERT_NE(nullptr, script2); + + EXPECT_TRUE(m_logicEngine.update()); + + // data in script2 has modified value from script1 + EXPECT_EQ(42, *script2->getOutputs()->getChild("data")->get()); + } + + // This test reflects behavior which will be fixed in a next release (adapt the tests after the fix) + TEST_F(ALuaScriptWithModule_Isolation, ScriptOverwritingModuleDataAffectsOtherScriptUsingIt_InInterfaceFunction) + { + const std::string_view mymathModuleSrc = R"( + local mymath = {} + mymath.data = 1 + return mymath + )"; + const auto mymathModule = m_logicEngine.createLuaModule(mymathModuleSrc); + ASSERT_NE(nullptr, mymathModule); + + LuaConfig config; + config.addDependency("mymath", *mymathModule); + + const auto script1 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + mymath.data = 42 + end + function run() + end + )", config); + ASSERT_NE(nullptr, script1); + + // This will overwrite the module function floor1 with floor2 + EXPECT_TRUE(m_logicEngine.update()); + + const auto script2 = m_logicEngine.createLuaScript(R"( + modules("mymath") + function interface() + OUT.data = INT + end + function run() + OUT.data = mymath.data + end + )", config); + ASSERT_NE(nullptr, script2); + + EXPECT_TRUE(m_logicEngine.update()); + + // data in script2 has modified value from script1 + EXPECT_EQ(42, *script2->getOutputs()->getChild("data")->get()); + } + + // This is something we want to forbid and catch as error. When we do, rewrite this test + TEST_F(ALuaScriptWithModule_Isolation, ModuleOfDataCanBeModifiedByScript) + { + const std::string_view moduleSrc = R"( + local mod = {} + mod.value = 1 + function mod.getValue() + return mod.value + end + return mod + )"; + + const std::string_view scriptSrc = R"( + modules("mappedMod") + function interface() + OUT.fromScript = INT + OUT.fromModule = INT + end + + function run() + mappedMod.value = 5 + OUT.fromScript = mappedMod.value + OUT.fromModule = mappedMod.getValue() + end + )"; + + const auto script = m_logicEngine.createLuaScript(scriptSrc, createDeps({ { "mappedMod", moduleSrc } })); + + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(5, *script->getOutputs()->getChild("fromScript")->get()); + EXPECT_EQ(5, *script->getOutputs()->getChild("fromModule")->get()); + } + + TEST_F(ALuaScriptWithModule_Isolation, ModuleCanModifyOutsideDataWhenExplicitlyPassedAsArgument) + { + const std::string_view moduleSrc = R"( + local mod = {} + mod.value = 1 + function mod.modifyModule(theModule) + theModule.value = 42 + end + return mod + )"; + + const std::string_view scriptSrc = R"( + modules("mappedMod") + function interface() + OUT.result = INT + end + + function run() + -- Will modify the module because it's passed as argument by the + -- script to the module + mappedMod.modifyModule(mappedMod) + OUT.result = mappedMod.value + end + )"; + + const auto script = m_logicEngine.createLuaScript(scriptSrc, createDeps({ { "mappedMod", moduleSrc } })); + + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(42, *script->getOutputs()->getChild("result")->get()); + } + + // This test reflects behavior which will be fixed in a next release (adapt the tests after the fix) + TEST_F(ALuaScriptWithModule_Isolation, DataIsNotIsolatedBetweenModuleAndScript_WhenNested) + { + const std::string_view moduleSrc = R"( + local mod = {} + mod.people = {joe = {age = 20}} + function mod.getJoeAge() + return mod.people.joe.age + end + return mod + )"; + + const std::string_view scriptSrc = R"( + modules("mappedMod") + function interface() + OUT.resultBeforeMod = INT + OUT.resultAfterMod = INT + end + + function run() + OUT.resultBeforeMod = mappedMod.getJoeAge() + -- This will modify the module's copy of joe + mappedMod.people.joe.age = 42 + OUT.resultAfterMod = mappedMod.getJoeAge() + end + )"; + + const auto script = m_logicEngine.createLuaScript(scriptSrc, createDeps({ { "mappedMod", moduleSrc } })); + + EXPECT_TRUE(m_logicEngine.update()); + EXPECT_EQ(20, *script->getOutputs()->getChild("resultBeforeMod")->get()); + EXPECT_EQ(42, *script->getOutputs()->getChild("resultAfterMod")->get()); + } +} diff --git a/unittests/LuaScriptTest_Runtime.cpp b/unittests/LuaScriptTest_Runtime.cpp index 9e205b5..9212522 100644 --- a/unittests/LuaScriptTest_Runtime.cpp +++ b/unittests/LuaScriptTest_Runtime.cpp @@ -37,7 +37,7 @@ namespace rlogic // Not testable, because assignment to userdata can't be catched. It's just a replacement of the current value TEST_F(ALuaScript_Runtime, DISABLED_GeneratesErrorWhenOverwritingInputsInRunFunction) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() end @@ -54,7 +54,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ReportsErrorWhenAssigningVectorComponentsIndividually) { - m_logicEngine.createLuaScriptFromSource(R"( + m_logicEngine.createLuaScript(R"( function interface() OUT.vec3f = VEC3F end @@ -72,7 +72,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorIfUndefinedInputIsUsedInRun) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() end function run() @@ -88,7 +88,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorIfUndefinedOutputIsUsedInRun) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() end function run() @@ -104,13 +104,13 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ReportsSourceNodeOnRuntimeError) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() end function run() error("this causes an error") end - )"); + )", WithStdModules({ EStandardModule::Base })); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -121,7 +121,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenTryingToWriteInputValues) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.value = FLOAT end @@ -157,7 +157,7 @@ namespace rlogic for (const auto& invalidStatement : invalidStatements) { - auto script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, invalidStatement)); + auto script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, invalidStatement)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -192,7 +192,7 @@ namespace rlogic for (const auto& invalidStatement : invalidStatements) { - auto script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, invalidStatement)); + auto script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, invalidStatement)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -220,12 +220,13 @@ namespace rlogic for (const auto& singleCase : allErrorCases) { - auto script = m_logicEngine.createLuaScriptFromSource("function interface()\n" - "end\n" - "function run()\n" + - singleCase.errorCode + - "\n" - "end\n"); + auto script = m_logicEngine.createLuaScript( + "function interface()\n" + "end\n" + "function run()\n" + + singleCase.errorCode + + "\n" + "end\n"); ASSERT_NE(nullptr, script); m_logicEngine.update(); @@ -238,7 +239,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, SetsValueOfTopLevelInputSuccessfully_WhenTemplateMatchesDeclaredInputType) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); auto inputs = script->getInputs(); auto speedInt32 = inputs->getChild("speed"); @@ -283,7 +284,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProvidesCalculatedValueAfterExecution) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.a = INT @@ -313,7 +314,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ReadsDataFromVec234Inputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.vec2f = VEC2F IN.vec3f = VEC3F @@ -354,7 +355,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, WritesValuesToVectorTypeOutputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.vec2f = VEC2F OUT.vec3f = VEC3F @@ -432,7 +433,7 @@ namespace rlogic scriptSource += aCase; scriptSource += "\nend\n"; - auto* script = m_logicEngine.createLuaScriptFromSource(scriptSource); + auto* script = m_logicEngine.createLuaScript(scriptSource); ASSERT_NE(nullptr, script); EXPECT_TRUE(m_logicEngine.update()); @@ -444,7 +445,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, PermitsAssigningOfVector_FromTable_WithKeyValuePairs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.vec2f = VEC2F OUT.vec3i = VEC3I @@ -468,7 +469,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, UsesNestedInputsToProduceResult) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.data = { a = INT, @@ -500,7 +501,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, StoresDataToNestedOutputs_AsWholeStruct) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.data = INT OUT.struct = { @@ -533,7 +534,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, StoresDataToNestedOutputs_Individually) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.data = INT OUT.data = { @@ -564,7 +565,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNestedProperties_Underspecified) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.data = { field1 = INT, @@ -592,7 +593,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNestedProperties_Overspecified) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.data = { field1 = INT, @@ -622,7 +623,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNestedProperties_WhenFieldHasWrongType) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.data = { field1 = INT, @@ -653,7 +654,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNestedProperties_WhenNestedSubStructDoesNotMatch) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.data = { field1 = INT, @@ -705,7 +706,7 @@ namespace rlogic end )"; - auto* script = m_logicEngine.createLuaScriptFromSource(scriptWithArrays); + auto* script = m_logicEngine.createLuaScript(scriptWithArrays); auto inputs = script->getInputs(); auto in_array_int = inputs->getChild("array_int"); @@ -759,7 +760,7 @@ namespace rlogic for (const auto& invalidStatement : invalidStatements) { - auto script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, invalidStatement)); + auto script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, invalidStatement)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -801,7 +802,7 @@ namespace rlogic for (const auto& singleCase : allErrorCases) { - auto script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, singleCase.errorCode)); + auto script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, singleCase.errorCode)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -814,7 +815,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AssignArrayValuesFromLuaTable) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.int_array = ARRAY(2, INT) OUT.float_array = ARRAY(2, FLOAT) @@ -850,7 +851,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AssignArrayValuesFromLuaTable_WithExplicitKeys) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.int_array = ARRAY(3, INT) end @@ -872,7 +873,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningArrayWithFewerElementsThanRequired_UsingExplicitIndices) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.int_array = ARRAY(3, INT) end @@ -891,7 +892,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningArrayFromLuaTableWithCorrectSizeButWrongIndices) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.int_array = ARRAY(3, INT) end @@ -944,7 +945,7 @@ namespace rlogic for (const auto& singleCase : allErrorCases) { - auto script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, singleCase.errorCode)); + auto script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, singleCase.errorCode)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -957,7 +958,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AssignsValuesArraysInVariousLuaSyntaxStyles) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() IN.array = ARRAY(3, VEC2I) OUT.array = ARRAY(3, VEC2I) @@ -982,7 +983,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AssignsValuesArraysInVariousLuaSyntaxStyles_InNestedStruct) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() IN.struct = { array1 = ARRAY(1, VEC2F), @@ -1040,7 +1041,7 @@ namespace rlogic for (const auto& aCase : allCases) { - auto* script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, aCase)); + auto* script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, aCase)); ASSERT_NE(nullptr, script); EXPECT_TRUE(m_logicEngine.update()); @@ -1077,7 +1078,7 @@ namespace rlogic for (const auto& aCase : allCases) { - auto* script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, aCase.errorCode)); + auto* script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, aCase.errorCode)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -1112,7 +1113,7 @@ namespace rlogic for (const auto& aCase : allCases) { - auto* script = m_logicEngine.createLuaScriptFromSource(fmt::format(scriptTemplate, aCase.errorCode)); + auto* script = m_logicEngine.createLuaScript(fmt::format(scriptTemplate, aCase.errorCode)); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -1125,7 +1126,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenImplicitlyRoundingNumbers) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.float = FLOAT OUT.int = INT @@ -1154,7 +1155,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNilToIntOutputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.int = INT end @@ -1171,7 +1172,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningBoolToIntOutputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.int = INT end @@ -1188,7 +1189,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningBoolToStringOutputs) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.str = STRING end @@ -1206,7 +1207,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorWhenAssigningNumberToStringOutputs) { - m_logicEngine.createLuaScriptFromSource(R"( + m_logicEngine.createLuaScript(R"( function interface() OUT.str = STRING end @@ -1222,7 +1223,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, SupportsMultipleLevelsOfNestedInputs_confidenceTest) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.rabbit = { color = { @@ -1277,7 +1278,7 @@ namespace rlogic for (const auto& singleCase : allCases) { - auto script = m_logicEngine.createLuaScriptFromSource( + auto script = m_logicEngine.createLuaScript( "function interface()\n" "end\n" "function run()\n" + @@ -1305,7 +1306,7 @@ namespace rlogic for (const auto& singleCase : allCases) { - auto script = m_logicEngine.createLuaScriptFromSource( + auto script = m_logicEngine.createLuaScript( "function interface()\n" "end\n" "function run()\n" + @@ -1336,7 +1337,7 @@ namespace rlogic end )"; - auto* script = m_logicEngine.createLuaScriptFromSource(scriptWithArrays); + auto* script = m_logicEngine.createLuaScript(scriptWithArrays); auto inputs = script->getInputs(); auto IN_array = inputs->getChild("array_structs"); @@ -1356,7 +1357,7 @@ namespace rlogic // I think this is not catchable, because it's just a normal function call TEST_F(ALuaScript_Runtime, DISABLED_ForbidsCallingInterfaceFunctionInsideTheRunFunction) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( do_the_shuffle = false function interface() @@ -1385,7 +1386,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AbortsAfterFirstRuntimeError) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() IN.float = FLOAT OUT.float = FLOAT @@ -1404,7 +1405,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, AssignOutputsFromInputsInDifferentWays_ConfidenceTest) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() IN.assignmentType = STRING @@ -1574,7 +1575,7 @@ namespace rlogic // Therefore it is not catchable in c++ TEST_F(ALuaScript_Runtime, DISABLED_ForbidsOverwritingRunFunctionInsideTheRunFunction) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() OUT.str = STRING end @@ -1597,7 +1598,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorIfInvalidOutPropertyIsAccessed) { - auto scriptWithInvalidOutParam = m_logicEngine.createLuaScriptFromSource(R"( + auto scriptWithInvalidOutParam = m_logicEngine.createLuaScript(R"( function interface() end function run() @@ -1612,7 +1613,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorIfInvalidNestedOutPropertyIsAccessed) { - auto scriptWithInvalidStructAccess = m_logicEngine.createLuaScriptFromSource(R"( + auto scriptWithInvalidStructAccess = m_logicEngine.createLuaScript(R"( function interface() end function run() @@ -1627,7 +1628,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesErrorIfValidestedButInvalidOutPropertyIsAccessed) { - auto scriptWithValidStructButInvalidField = m_logicEngine.createLuaScriptFromSource(R"( + auto scriptWithValidStructButInvalidField = m_logicEngine.createLuaScript(R"( function interface() OUT.struct = { param = INT @@ -1645,7 +1646,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, CanAssignInputDirectlyToOutput) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() IN.param_struct = { param1 = FLOAT, @@ -1692,7 +1693,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, ProducesNoErrorIfOutputIsSetInFunction) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.param = INT OUT.struct1 = { @@ -1744,7 +1745,7 @@ namespace rlogic TEST_F(ALuaScript_Runtime, DoesNotSetOutputIfOutputParamIsPassedToFunction) { - auto script = m_logicEngine.createLuaScriptFromSource(R"( + auto script = m_logicEngine.createLuaScript(R"( function interface() OUT.param = INT end @@ -1796,9 +1797,9 @@ namespace rlogic color = vec4(1.0, 0.0, 0.0, 1.0); })"; - auto script1 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script1"); - auto script2 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script2"); - auto script3 = m_logicEngine.createLuaScriptFromSource(scriptSource, "Script3"); + auto script1 = m_logicEngine.createLuaScript(scriptSource); + auto script2 = m_logicEngine.createLuaScript(scriptSource); + auto script3 = m_logicEngine.createLuaScript(scriptSource); auto script1FloatInput = script1->getInputs()->getChild("inFloat"); auto script1FloatOutput = script1->getOutputs()->getChild("outFloat"); @@ -1896,7 +1897,7 @@ namespace rlogic } - TEST_F(ALuaScript_Runtime, IncludesStandardLibraries) + TEST_F(ALuaScript_Runtime, IncludesStandardLibraries_WhenConfiguredWithThem) { const std::string_view scriptSrc = R"( function debug_func(arg) @@ -1921,7 +1922,7 @@ namespace rlogic OUT.language_of_debug_func = debuginfo.what end )"; - auto script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + auto script = m_logicEngine.createLuaScript(scriptSrc, WithStdModules({EStandardModule::Base, EStandardModule::String, EStandardModule::Table, EStandardModule::Debug, EStandardModule::Math})); ASSERT_NE(nullptr, script); m_logicEngine.update(); diff --git a/unittests/LuaScriptTest_Serialization.cpp b/unittests/LuaScriptTest_Serialization.cpp index abd6940..d43f207 100644 --- a/unittests/LuaScriptTest_Serialization.cpp +++ b/unittests/LuaScriptTest_Serialization.cpp @@ -23,9 +23,11 @@ namespace rlogic::internal class ALuaScript_Serialization : public ::testing::Test { protected: - std::unique_ptr createTestScript(std::string_view source, std::string_view scriptName = "", std::string_view filename = "") + std::unique_ptr createTestScript(std::string_view source, std::string_view scriptName = "") { - return std::make_unique(*LuaCompilationUtils::Compile(m_solState, std::string{ source }, scriptName, std::string{ filename }, m_errorReporting), scriptName); + return std::make_unique( + *LuaCompilationUtils::CompileScript(m_solState, {}, {}, std::string{ source }, scriptName, m_errorReporting), + scriptName); } std::string_view m_minimalScript = R"( @@ -49,7 +51,7 @@ namespace rlogic::internal { // Serialize { - std::unique_ptr script = createTestScript(m_minimalScript, "name", "filename"); + std::unique_ptr script = createTestScript(m_minimalScript, "name"); (void)LuaScriptImpl::Serialize(*script, m_flatBufferBuilder, m_serializationMap); } @@ -80,27 +82,10 @@ namespace rlogic::internal } } - TEST_F(ALuaScript_Serialization, RemembersFilename) - { - { - std::unique_ptr script = createTestScript(m_minimalScript, "", "filename"); - (void)LuaScriptImpl::Serialize(*script, m_flatBufferBuilder, m_serializationMap); - } - - const auto& serializedScript = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - ASSERT_TRUE(serializedScript.filename()); - EXPECT_EQ(serializedScript.filename()->string_view(), "filename"); - - { - std::unique_ptr deserializedScript = LuaScriptImpl::Deserialize(m_solState, serializedScript, m_errorReporting, m_deserializationMap); - EXPECT_EQ(deserializedScript->getFilename(), "filename"); - } - } - TEST_F(ALuaScript_Serialization, SerializesLuaSourceCode) { { - std::unique_ptr script = createTestScript(m_minimalScript, "", ""); + std::unique_ptr script = createTestScript(m_minimalScript, ""); (void)LuaScriptImpl::Serialize(*script, m_flatBufferBuilder, m_serializationMap); } @@ -127,25 +112,6 @@ namespace rlogic::internal EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading of LuaScript from serialized data: missing name!"); } - TEST_F(ALuaScript_Serialization, ProducesErrorWhenFilenameMissing) - { - { - auto script = rlogic_serialization::CreateLuaScript( - m_flatBufferBuilder, - m_flatBufferBuilder.CreateString("name"), - 0 // no filename - ); - m_flatBufferBuilder.Finish(script); - } - - const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); - std::unique_ptr deserialized = LuaScriptImpl::Deserialize(m_solState, serialized, m_errorReporting, m_deserializationMap); - - EXPECT_FALSE(deserialized); - ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); - EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading of LuaScript from serialized data: missing filename!"); - } - TEST_F(ALuaScript_Serialization, ProducesErrorWhenLuaSourceCodeMissing) { { @@ -166,6 +132,28 @@ namespace rlogic::internal EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading of LuaScript from serialized data: missing Lua source code!"); } + // TODO VersionBreak enable test with next breaking change + //TEST_F(ALuaScript_Serialization, ProducesErrorWhenModulesMissing) + //{ + // { + // auto script = rlogic_serialization::CreateLuaScript( + // m_flatBufferBuilder, + // m_flatBufferBuilder.CreateString("name"), + // m_flatBufferBuilder.CreateString("some/file.lua"), + // m_flatBufferBuilder.CreateString("lua source code"), + // m_testUtils.serializeTestProperty("IN"), + // m_testUtils.serializeTestProperty("OUT"), + // 0 // no modules + // ); + // m_flatBufferBuilder.Finish(script); + // } + // + // const auto& serialized = *flatbuffers::GetRoot(m_flatBufferBuilder.GetBufferPointer()); + // EXPECT_EQ(nullptr, LuaScriptImpl::Deserialize(m_solState, serialized, m_errorReporting, m_deserializationMap)); + // ASSERT_EQ(m_errorReporting.getErrors().size(), 1u); + // EXPECT_EQ(m_errorReporting.getErrors()[0].message, "Fatal error during loading of LuaScript from serialized data: missing Lua modules usage!"); + //} + TEST_F(ALuaScript_Serialization, ProducesErrorWhenRootInputMissing) { { diff --git a/unittests/LuaScriptTest_Syntax.cpp b/unittests/LuaScriptTest_Syntax.cpp index 9a492a8..16e1919 100644 --- a/unittests/LuaScriptTest_Syntax.cpp +++ b/unittests/LuaScriptTest_Syntax.cpp @@ -22,10 +22,10 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfNoInterfaceIsPresent) { - LuaScript* scriptNoInterface = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptNoInterface = m_logicEngine.createLuaScript(R"( function run() end - )", "scriptNoInterface"); + )", {}, "scriptNoInterface"); ASSERT_EQ(nullptr, scriptNoInterface); ASSERT_EQ(1u, m_logicEngine.getErrors().size()); @@ -34,10 +34,10 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfNoRunIsPresent) { - LuaScript* scriptNoRun = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptNoRun = m_logicEngine.createLuaScript(R"( function interface() end - )", "scriptNoRun"); + )", {}, "scriptNoRun"); ASSERT_EQ(nullptr, scriptNoRun); EXPECT_EQ(1u, m_logicEngine.getErrors().size()); @@ -46,7 +46,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CannotBeCreatedFromSyntacticallyIncorrectScript) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource("this.is.not.valid.lua.code", "badSyntaxScript"); + LuaScript* script = m_logicEngine.createLuaScript("this.is.not.valid.lua.code", {}, "badSyntaxScript"); ASSERT_EQ(nullptr, script); ASSERT_EQ(1u, m_logicEngine.getErrors().size()); EXPECT_THAT(m_logicEngine.getErrors()[0].message, ::testing::HasSubstr("[string \"badSyntaxScript\"]:1: '' expected near 'not'")); @@ -54,7 +54,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, PropagatesErrorsEmittedInLua_FromGlobalScope) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( error("Expect this error!") function interface() @@ -62,7 +62,7 @@ namespace rlogic function run() end - )", "scriptWithErrorInGlobalCode"); + )", WithStdModules({EStandardModule::Base}), "scriptWithErrorInGlobalCode"); EXPECT_EQ(nullptr, script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_THAT(m_logicEngine.getErrors()[0].message, ::testing::HasSubstr( @@ -73,14 +73,14 @@ namespace rlogic TEST_F(ALuaScript_Syntax, PropagatesErrorsEmittedInLua_DuringInterfaceDeclaration) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() error("Expect this error!") end function run() end - )", "scriptWithErrorInInterface"); + )", WithStdModules({EStandardModule::Base}), "scriptWithErrorInInterface"); EXPECT_EQ(nullptr, script); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); EXPECT_THAT(m_logicEngine.getErrors()[0].message, ::testing::HasSubstr( @@ -91,14 +91,14 @@ namespace rlogic TEST_F(ALuaScript_Syntax, PropagatesErrorsEmittedInLua_DuringRun) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() end function run() error("Expect this error!") end - )", "scriptWithErrorInRun"); + )", WithStdModules({EStandardModule::Base}), "scriptWithErrorInRun"); ASSERT_NE(nullptr, script); @@ -114,7 +114,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorWhenIndexingVectorPropertiesOutOfRange) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.vec2f = VEC2F IN.vec3f = VEC3F @@ -131,7 +131,7 @@ namespace rlogic function run() local message = "Value of " .. IN.propertyName .. "[" .. tostring(IN.index) .. "]" .. " is " .. IN[IN.propertyName][IN.index] end - )", "scriptOOR"); + )", WithStdModules({EStandardModule::Base}), "scriptOOR"); Property* inputs = script->getInputs(); inputs->getChild("vec2f")->set({1.1f, 1.2f}); @@ -194,7 +194,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CanUseLuaSyntaxForComputingArraySize) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.array = ARRAY(3, INT) OUT.array_size = INT @@ -211,7 +211,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CanUseLuaSyntaxForComputingComplexArraySize) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.array = ARRAY(3, { @@ -233,7 +233,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CanUseLuaSyntaxForComputingStructSize) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.struct = { data1 = VEC3F, @@ -254,7 +254,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CanUseLuaSyntaxForComputingVec234Size) { - m_logicEngine.createLuaScriptFromSource(R"( + m_logicEngine.createLuaScript(R"( function interface() IN.vec2f = VEC2F IN.vec3f = VEC3F @@ -279,7 +279,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, CanUseLuaSyntaxForComputingSizeOfStrings) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.string = STRING OUT.string_size = INT @@ -297,7 +297,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, RaisesErrorWhenTryingToGetSizeOfNonArrayTypes) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.notArray = INT end @@ -305,7 +305,7 @@ namespace rlogic function run() local size = #IN.notArray end - )", "invalidArraySizeAccess"); + )", {}, "invalidArraySizeAccess"); ASSERT_FALSE(m_logicEngine.update()); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -316,7 +316,7 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProdocesErrorWhenIndexingVectorWithNonIntegerIndices) { - LuaScript* script = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* script = m_logicEngine.createLuaScript(R"( function interface() IN.vec = VEC4I @@ -338,7 +338,7 @@ namespace rlogic error("Test problem - check error cases below") end end - )", "invalidIndexingScript"); + )", {}, "invalidIndexingScript"); Property* inputs = script->getInputs(); Property* errorType = inputs->getChild("errorType"); @@ -423,7 +423,7 @@ namespace rlogic scriptSource += errorCase.errorCode; scriptSource += "\nend\n"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSource, "mismatchedVecSizes"); + LuaScript* script = m_logicEngine.createLuaScript(scriptSource, {}, "mismatchedVecSizes"); ASSERT_NE(nullptr, script); EXPECT_FALSE(m_logicEngine.update()); @@ -439,12 +439,12 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfRunFunctionDoesNotEndCorrectly) { - LuaScript* scriptWithWrongEndInRun = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptWithWrongEndInRun = m_logicEngine.createLuaScript(R"( function interface() end function run() ENDE - )", "missingEndInScript"); + )", {}, "missingEndInScript"); ASSERT_EQ(nullptr, scriptWithWrongEndInRun); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -453,12 +453,12 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfInterfaceFunctionDoesNotEndCorrectly) { - LuaScript* scriptWithWrongEndInInterface = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptWithWrongEndInInterface = m_logicEngine.createLuaScript(R"( function interface() ENDE function run() end - )", "missingEndInScript"); + )", {}, "missingEndInScript"); ASSERT_EQ(nullptr, scriptWithWrongEndInInterface); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -467,11 +467,11 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfInterfaceFunctionDoesNotEndAtAll) { - LuaScript* scriptWithNoEndInInterface = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptWithNoEndInInterface = m_logicEngine.createLuaScript(R"( function interface() function run() end - )", "endlessInterface"); + )", {}, "endlessInterface"); ASSERT_EQ(nullptr, scriptWithNoEndInInterface); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); @@ -480,11 +480,11 @@ namespace rlogic TEST_F(ALuaScript_Syntax, ProducesErrorIfRunFunctionDoesNotEndAtAll) { - LuaScript* scriptWithNoEndInRun = m_logicEngine.createLuaScriptFromSource(R"( + LuaScript* scriptWithNoEndInRun = m_logicEngine.createLuaScript(R"( function interface() end function run() - )", "endlessRun"); + )", {}, "endlessRun"); ASSERT_EQ(nullptr, scriptWithNoEndInRun); ASSERT_EQ(m_logicEngine.getErrors().size(), 1u); diff --git a/unittests/LuaScriptTest_Types.cpp b/unittests/LuaScriptTest_Types.cpp index 1361214..be983d0 100644 --- a/unittests/LuaScriptTest_Types.cpp +++ b/unittests/LuaScriptTest_Types.cpp @@ -19,7 +19,7 @@ namespace rlogic TEST_F(ALuaScript_Types, FailsToSetValueOfTopLevelInput_WhenTemplateDoesNotMatchDeclaredInputType) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); auto inputs = script->getInputs(); auto speedInt32 = inputs->getChild("speed"); @@ -47,7 +47,7 @@ namespace rlogic TEST_F(ALuaScript_Types, FailsToSetArrayDirectly) { - auto* script = m_logicEngine.createLuaScriptFromSource(R"( + auto* script = m_logicEngine.createLuaScript(R"( function interface() IN.array_int = ARRAY(2, INT) end @@ -65,7 +65,7 @@ namespace rlogic TEST_F(ALuaScript_Types, ProvidesIndexBasedAndNameBasedAccessToInputProperties) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); ASSERT_NE(nullptr, script); auto enabled_byIndex = script->getInputs()->getChild(0); EXPECT_NE(nullptr, enabled_byIndex); @@ -77,7 +77,7 @@ namespace rlogic TEST_F(ALuaScript_Types, ProvidesIndexBasedAndNameBasedAccessToOutputProperties) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithOutputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithOutputs); ASSERT_NE(nullptr, script); auto enabled_byIndex = script->getOutputs()->getChild(0); EXPECT_NE(nullptr, enabled_byIndex); @@ -89,7 +89,7 @@ namespace rlogic TEST_F(ALuaScript_Types, AssignsNameAndTypeToGlobalInputsStruct) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScript); + auto* script = m_logicEngine.createLuaScript(m_minimalScript); auto inputs = script->getInputs(); @@ -100,7 +100,7 @@ namespace rlogic TEST_F(ALuaScript_Types, AssignsNameAndTypeToGlobalOutputsStruct) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScript); + auto* script = m_logicEngine.createLuaScript(m_minimalScript); auto outputs = script->getOutputs(); ASSERT_EQ(0u, outputs->getChildCount()); @@ -110,7 +110,7 @@ namespace rlogic TEST_F(ALuaScript_Types, ReturnsItsTopLevelInputsByIndex_IndexEqualsLexicographicOrder) { - auto* script = m_logicEngine.createLuaScriptFromSource(m_minimalScriptWithInputs); + auto* script = m_logicEngine.createLuaScript(m_minimalScriptWithInputs); auto inputs = script->getInputs(); @@ -153,7 +153,7 @@ namespace rlogic end )"; - auto* script = m_logicEngine.createLuaScriptFromSource(scriptWithArrays); + auto* script = m_logicEngine.createLuaScript(scriptWithArrays); std::initializer_list rootProperties = {script->getInputs(), script->getOutputs()}; @@ -211,7 +211,7 @@ namespace rlogic end )"; - auto* script = m_logicEngine.createLuaScriptFromSource(scriptWithArrays); + auto* script = m_logicEngine.createLuaScript(scriptWithArrays); std::initializer_list rootProperties = { script->getInputs(), script->getOutputs() }; diff --git a/unittests/PropertyTypeExtractorTest.cpp b/unittests/PropertyTypeExtractorTest.cpp index 1219959..9c265eb 100644 --- a/unittests/PropertyTypeExtractorTest.cpp +++ b/unittests/PropertyTypeExtractorTest.cpp @@ -17,7 +17,7 @@ namespace rlogic::internal sol::state sol; sol::environment env(sol, sol::create, sol.globals()); - PropertyTypeExtractor::RegisterTypesToEnvironment(env); + PropertyTypeExtractor::RegisterTypes(env); // Environment now has the type symbols (data types, declaration functions) EXPECT_TRUE(env["INT"].valid()); @@ -38,7 +38,7 @@ namespace rlogic::internal APropertyTypeExtractor() : m_env(m_sol, sol::create, m_sol.globals()) { - PropertyTypeExtractor::RegisterTypesToEnvironment(m_env); + PropertyTypeExtractor::RegisterTypes(m_env); } HierarchicalTypeData extractTypeInfo(std::string_view source) diff --git a/unittests/RamsesAppearanceBindingTest.cpp b/unittests/RamsesAppearanceBindingTest.cpp index 10ff82d..a8cbcf5 100644 --- a/unittests/RamsesAppearanceBindingTest.cpp +++ b/unittests/RamsesAppearanceBindingTest.cpp @@ -653,7 +653,7 @@ namespace rlogic::internal OUT.float = 42.42 end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); ASSERT_TRUE(m_logicEngine.link(*script->getOutputs()->getChild("float"), *appearanceBinding.getInputs()->getChild("floatUniform1"))); EXPECT_TRUE(m_logicEngine.update()); @@ -873,7 +873,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); auto& appearanceBinding = *m_logicEngine.createRamsesAppearanceBinding(appearance, "AppearanceBinding"); script->getInputs()->getChild("float")->set(42.42f); diff --git a/unittests/RamsesCameraBindingTest.cpp b/unittests/RamsesCameraBindingTest.cpp index ad765b5..bbe044f 100644 --- a/unittests/RamsesCameraBindingTest.cpp +++ b/unittests/RamsesCameraBindingTest.cpp @@ -637,7 +637,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); auto* cameraBinding = m_logicEngine.createRamsesCameraBinding(m_perspectiveCam, ""); @@ -684,7 +684,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); auto* cameraBinding = m_logicEngine.createRamsesCameraBinding(m_orthoCam, ""); @@ -1213,7 +1213,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); RamsesCameraBinding& cameraBinding = *m_logicEngine.createRamsesCameraBinding(m_perspectiveCam, "CameraBinding"); @@ -1366,7 +1366,7 @@ namespace rlogic::internal // Create camera and preset values m_perspectiveCam.setViewport(11, 12, 13u, 14u); - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); RamsesCameraBinding& cameraBinding = *m_logicEngine.createRamsesCameraBinding(m_perspectiveCam, "CameraBinding"); // set other values to artificially check that the binding won't override them m_perspectiveCam.setViewport(9, 8, 1u, 2u); diff --git a/unittests/RamsesNodeBindingTest.cpp b/unittests/RamsesNodeBindingTest.cpp index e7bd0b4..fa56ef6 100644 --- a/unittests/RamsesNodeBindingTest.cpp +++ b/unittests/RamsesNodeBindingTest.cpp @@ -290,7 +290,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); RamsesNodeBinding& nodeBinding = *m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); @@ -990,7 +990,7 @@ namespace rlogic::internal end )"; - LuaScript* script = tempEngineForSaving.createLuaScriptFromSource(scriptSrc); + LuaScript* script = tempEngineForSaving.createLuaScript(scriptSrc); RamsesNodeBinding& nodeBinding = *tempEngineForSaving.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); @@ -1100,7 +1100,7 @@ namespace rlogic::internal end )"; - LuaScript* script = m_logicEngine.createLuaScriptFromSource(scriptSrc); + LuaScript* script = m_logicEngine.createLuaScript(scriptSrc); RamsesNodeBinding& nodeBinding = *m_logicEngine.createRamsesNodeBinding(*m_node, ERotationType::Euler_XYZ, "NodeBinding"); // Adding and removing link does not set anything in ramses diff --git a/unittests/SerializationTestUtils.h b/unittests/SerializationTestUtils.h index 2cbabb3..81996b4 100644 --- a/unittests/SerializationTestUtils.h +++ b/unittests/SerializationTestUtils.h @@ -74,7 +74,8 @@ namespace rlogic::internal m_builder.CreateString("some/file"), m_builder.CreateString("print('test lua code')"), serializeTestProperty("IN"), - serializeTestProperty("OUT") + serializeTestProperty("OUT"), + m_builder.CreateVector(std::vector>{}) ); } diff --git a/unittests/SolStateTest.cpp b/unittests/SolStateTest.cpp index dde7456..88b0ba1 100644 --- a/unittests/SolStateTest.cpp +++ b/unittests/SolStateTest.cpp @@ -10,6 +10,8 @@ #include "internals/SolState.h" #include "internals/SolWrapper.h" +#include "internals/LuaCompilationUtils.h" +#include "internals/ErrorReporting.h" namespace rlogic::internal { @@ -47,30 +49,91 @@ namespace rlogic::internal EXPECT_THAT(error.what(), ::testing::HasSubstr("'' expected near 'not'")); } - TEST_F(ASolState, CreatesNewEnvironment) + TEST_F(ASolState, CreatesNewRuntimeEnvironment) { - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); EXPECT_TRUE(env.valid()); } - TEST_F(ASolState, NewEnvironment_InheritsGlobals) + TEST_F(ASolState, CreatesNewInterfaceEnvironment) { - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Interface); + EXPECT_TRUE(env.valid()); + } + + TEST_F(ASolState, CreatesNewModuleEnvironment) + { + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Module); + EXPECT_TRUE(env.valid()); + } + + TEST_F(ASolState, NewEnvironment_HidesGlobalStandardModules) + { + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env.valid()); // Libs - EXPECT_TRUE(env["print"].valid()); - EXPECT_TRUE(env["math"].valid()); + EXPECT_FALSE(env["print"].valid()); + EXPECT_FALSE(env["math"].valid()); // Should be only available during interface, not in the global scope defined symbols EXPECT_FALSE(env["INT"].valid()); EXPECT_FALSE(env["STRING"].valid()); } + TEST_F(ASolState, NewEnvironment_ExposesRequestedGlobalStandardModules) + { + sol::environment env = m_solState.createEnvironment({EStandardModule::Math}, {}, EEnvironmentType::Runtime); + ASSERT_TRUE(env.valid()); + + EXPECT_TRUE(env["math"].valid()); + + EXPECT_FALSE(env["print"].valid()); + EXPECT_FALSE(env["debug"].valid()); + EXPECT_FALSE(env["string"].valid()); + EXPECT_FALSE(env["table"].valid()); + EXPECT_FALSE(env["error"].valid()); + EXPECT_FALSE(env["INT"].valid()); + EXPECT_FALSE(env["STRING"].valid()); + } + + TEST_F(ASolState, NewEnvironment_ExposesRequestedGlobalStandardModules_TwoModules) + { + sol::environment env = m_solState.createEnvironment({ EStandardModule::String, EStandardModule::Table }, {}, EEnvironmentType::Runtime); + ASSERT_TRUE(env.valid()); + + EXPECT_TRUE(env["string"].valid()); + EXPECT_TRUE(env["table"].valid()); + + EXPECT_FALSE(env["math"].valid()); + EXPECT_FALSE(env["print"].valid()); + EXPECT_FALSE(env["debug"].valid()); + EXPECT_FALSE(env["error"].valid()); + EXPECT_FALSE(env["INT"].valid()); + EXPECT_FALSE(env["STRING"].valid()); + } + + TEST_F(ASolState, NewEnvironment_ExposesRequestedGlobalStandardModules_BaseLib) + { + sol::environment env = m_solState.createEnvironment({ EStandardModule::Base }, {}, EEnvironmentType::Runtime); + ASSERT_TRUE(env.valid()); + + EXPECT_TRUE(env["error"].valid()); + EXPECT_TRUE(env["tostring"].valid()); + EXPECT_TRUE(env["print"].valid()); + + EXPECT_FALSE(env["table"].valid()); + EXPECT_FALSE(env["math"].valid()); + EXPECT_FALSE(env["debug"].valid()); + EXPECT_FALSE(env["string"].valid()); + EXPECT_FALSE(env["INT"].valid()); + EXPECT_FALSE(env["STRING"].valid()); + } + // Those are created at a later point of the script lifecycle TEST_F(ASolState, NewEnvironment_HasNo_IN_OUT_globalsYet) { - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env.valid()); EXPECT_FALSE(env["IN"].valid()); @@ -79,7 +142,7 @@ namespace rlogic::internal TEST_F(ASolState, NewEnvironment_HasNoFunctionsExpectedByUserScript) { - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env.valid()); EXPECT_FALSE(env["interface"].valid()); @@ -88,8 +151,8 @@ namespace rlogic::internal TEST_F(ASolState, NewEnvironment_TwoEnvironmentsShareNoData) { - sol::environment env1 = m_solState.createEnvironment(); - sol::environment env2 = m_solState.createEnvironment(); + sol::environment env1 = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); + sol::environment env2 = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env1.valid()); ASSERT_TRUE(env2.valid()); @@ -118,7 +181,7 @@ namespace rlogic::internal sol::function func = loadedScript(); // Apply fresh environment to func - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env.valid()); env.set_on(func); @@ -142,7 +205,7 @@ namespace rlogic::internal sol::protected_function loadedScript = m_solState.loadScript(script, "test script"); // Apply a fresh environments to loaded script _before_ executing it - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); env.set_on(loadedScript); sol::function func = loadedScript(); @@ -166,7 +229,7 @@ namespace rlogic::internal std::string dataStatus = script(); EXPECT_EQ(dataStatus, "no data"); - sol::environment env = m_solState.createEnvironment(); + sol::environment env = m_solState.createEnvironment({}, {}, EEnvironmentType::Runtime); ASSERT_TRUE(env.valid()); env["data"] = "a lot of data!"; diff --git a/unittests/testAssetProducer/main.cpp b/unittests/testAssetProducer/main.cpp index 0fc93ec..317aaa4 100644 --- a/unittests/testAssetProducer/main.cpp +++ b/unittests/testAssetProducer/main.cpp @@ -8,12 +8,14 @@ #include "ramses-logic/LogicEngine.h" #include "ramses-logic/Property.h" +#include "ramses-logic/LuaModule.h" #include "ramses-logic/LuaScript.h" #include "ramses-logic/RamsesNodeBinding.h" #include "ramses-logic/RamsesCameraBinding.h" #include "ramses-logic/RamsesAppearanceBinding.h" #include "ramses-logic/DataArray.h" #include "ramses-logic/AnimationNode.h" +#include "ramses-logic/EStandardModule.h" #include "ramses-client.h" #include "ramses-utils.h" @@ -57,7 +59,7 @@ int main(int argc, char* argv[]) scene->flush(); rlogic::LogicEngine logicEngine; - rlogic::LuaScript* script1 = logicEngine.createLuaScriptFromSource(R"( + rlogic::LuaScript* script1 = logicEngine.createLuaScript(R"( function interface() IN.intInput = INT IN.vec2iInput = VEC2I @@ -83,30 +85,65 @@ int main(int argc, char* argv[]) OUT.floatOutput = IN.floatInput OUT.nodeTranslation = {IN.floatInput, 2, 3} end - )", "script1"); - rlogic::LuaScript* script2 = logicEngine.createLuaScriptFromSource(R"( + )", {}, "script1"); + + const auto luaNestedModuleMath = logicEngine.createLuaModule(R"( + local mymath = {} + function mymath.sub(a,b) + return a - b + end + return mymath + )"); + + rlogic::LuaConfig config; + config.addDependency("nestedMath", *luaNestedModuleMath); + + const auto luaModuleMath = logicEngine.createLuaModule(R"( + local mymath = {} + mymath.sub=nestedMath.sub + function mymath.add(a,b) + return a + b + end + return mymath + )", config, "moduleMath"); + + const auto luaModuleTypes = logicEngine.createLuaModule(R"( + local mytypes = {} + function mytypes.camViewport() + return { + offsetX = INT, + offsetY = INT, + width = INT, + height = INT + } + end + return mytypes + )", {}, "moduleTypes"); + + config = {}; + config.addDependency("modulemath", *luaModuleMath); + config.addDependency("moduletypes", *luaModuleTypes); + config.addStandardModuleDependency(rlogic::EStandardModule::Math); + + rlogic::LuaScript* script2 = logicEngine.createLuaScript(R"( function interface() IN.floatInput = FLOAT - - OUT.cameraViewport = { - offsetX = INT, - offsetY = INT, - width = INT, - height = INT - } + OUT.cameraViewport = moduletypes.camViewport() OUT.floatUniform = FLOAT + OUT.nestedModulesResult = INT end function run() OUT.floatUniform = IN.floatInput + 5.0 local roundedFloat = math.ceil(IN.floatInput) OUT.cameraViewport = { - offsetX = 2 + roundedFloat, - offsetY = 4 + roundedFloat, - width = 100 + roundedFloat, - height = 200 + roundedFloat + offsetX = modulemath.add(2, roundedFloat), + offsetY = modulemath.add(4, roundedFloat), + width = modulemath.add(100, roundedFloat), + height = modulemath.add(200, roundedFloat) } + OUT.nestedModulesResult = modulemath.sub(1000, roundedFloat) end - )", "script2"); + )", config, "script2"); ramses::Node* node = { scene->createNode("test node") }; ramses::OrthographicCamera* camera = { scene->createOrthographicCamera("test camera") }; @@ -127,7 +164,12 @@ int main(int argc, char* argv[]) logicEngine.link(*script2->getOutputs()->getChild("floatUniform"), *appBinding->getInputs()->getChild("floatUniform")); logicEngine.link(*animNode->getOutputs()->getChild("channel"), *appBinding->getInputs()->getChild("animatedFloatUniform")); - logicEngine.update(); + bool success = logicEngine.update(); + + if(!success) + { + return 1; + } logicEngine.saveToFile("testLogic.bin"); scene->saveToFile("testScene.bin", false);