diff --git a/include/openspace/scripting/lualibrary.h b/include/openspace/scripting/lualibrary.h index 698da1ca6a..235a6735d8 100644 --- a/include/openspace/scripting/lualibrary.h +++ b/include/openspace/scripting/lualibrary.h @@ -33,9 +33,9 @@ namespace openspace::scripting { /** -* This structure represents a Lua library, itself consisting of a unique #name and -* an arbitrary number of #functions -*/ + * This structure represents a Lua library, itself consisting of a unique #name and + * an arbitrary number of #functions + */ struct LuaLibrary { /** * This structure represents a Lua function with its #name, #function pointer @@ -49,7 +49,7 @@ struct LuaLibrary { lua_CFunction function; /// A vector of light userdata to be passed into the function std::vector userdata; - /// A text describing the arugments to this function + /// A text describing the arguments to this function std::string argumentText; /// A help text describing what the function does/ std::string helpText; @@ -58,6 +58,8 @@ struct LuaLibrary { std::string name; /// The list of all C-based callback functions for this library std::vector functions; + /// A list of all libraries that are children for this library + std::vector subLibraries; /// A list of script files that are executed for each Lua state std::vector scripts = std::vector(); @@ -77,6 +79,9 @@ struct LuaLibrary { /// Comparison function that compares two LuaLibrary%s name bool operator<(const LuaLibrary& rhs) const; + + /// Merges the contents of a second LuaLibrary into this LuaLibrary + void merge(LuaLibrary rhs); }; } // namespace openspace::scripting diff --git a/src/scripting/lualibrary.cpp b/src/scripting/lualibrary.cpp index 245c40b42c..edd6929671 100644 --- a/src/scripting/lualibrary.cpp +++ b/src/scripting/lualibrary.cpp @@ -24,10 +24,60 @@ #include +#include + namespace openspace::scripting { bool LuaLibrary::operator<(const LuaLibrary& rhs) const { return name < rhs.name; } +void LuaLibrary::merge(LuaLibrary rhs) { + for (const LuaLibrary::Function& fun : rhs.functions) { + const auto itf = std::find_if( + functions.begin(), + functions.end(), + [&fun](const LuaLibrary::Function& function) { + return fun.name == function.name; + } + ); + if (itf != functions.end()) { + // the function with the desired name is already present, but we don't + // want to overwrite it + LERRORC( + "LuaLibrary", + fmt::format( + "Lua function '{}' in library '{}' has been defined twice", + fun.name, rhs.name + ) + ); + return; + } + else { + functions.push_back(fun); + } + } + + for (LuaLibrary s : rhs.subLibraries) { + if (s.name.empty()) { + LERRORC("LuaLibrary", "Sublibraries must have a non-empty name"); + } + + auto it = std::find_if( + subLibraries.begin(), subLibraries.end(), + [&s](const LuaLibrary& lib) { return lib.name == s.name; } + ); + if (it == subLibraries.end()) { + subLibraries.push_back(std::move(s)); + } + else { + it->merge(std::move(s)); + } + } + + for (const std::filesystem::path& script : rhs.scripts) { + scripts.push_back(script); + } +} + } // namespace openspace::scripting diff --git a/src/scripting/scriptengine.cpp b/src/scripting/scriptengine.cpp index dc917b8de8..5acf21d172 100644 --- a/src/scripting/scriptengine.cpp +++ b/src/scripting/scriptengine.cpp @@ -43,6 +43,80 @@ namespace { constexpr const char* _loggerCat = "ScriptEngine"; constexpr const int TableOffset = -3; // top-first argument-second argument + + std::vector luaFunctions(const openspace::scripting::LuaLibrary& library, + std::string prefix) + { + using namespace openspace::scripting; + + std::vector result; + + std::string total = prefix; + if (!library.name.empty()) { + total += library.name + "."; + } + + for (const LuaLibrary::Function& function : library.functions) { + result.push_back(total + function.name); + } + + for (const LuaLibrary& sl : library.subLibraries) { + std::vector r = luaFunctions(sl, total); + result.insert(result.end(), r.begin(), r.end()); + } + + for (const LuaLibrary::Documentation& doc : library.documentations) { + result.push_back(total + doc.name); + } + + return result; + } + + void toJson(const openspace::scripting::LuaLibrary& library, std::stringstream& json) + { + constexpr const char* replStr = R"("{}": "{}", )"; + constexpr const char* replStr2 = R"("{}": "{}")"; + + using namespace openspace; + using namespace openspace::scripting; + + json << "{"; + json << fmt::format(replStr, "library", library.name); + json << "\"functions\": ["; + + for (const LuaLibrary::Function& f : library.functions) { + json << "{"; + json << fmt::format(replStr, "name", f.name); + json << fmt::format(replStr, "arguments", escapedJson(f.argumentText)); + json << fmt::format(replStr2, "help", escapedJson(f.helpText)); + json << "}"; + if (&f != &library.functions.back() || !library.documentations.empty()) { + json << ","; + } + } + + for (const LuaLibrary::Documentation& doc : library.documentations) { + json << "{"; + json << fmt::format(replStr, "name", doc.name); + json << fmt::format(replStr, "arguments", escapedJson(doc.parameter)); + json << fmt::format(replStr2, "help", escapedJson(doc.description)); + json << "}"; + if (&doc != &library.documentations.back()) { + json << ","; + } + } + + json << "],"; + + json << "\"subLibraries\": ["; + for (const LuaLibrary& sl : library.subLibraries) { + toJson(sl, json); + if (&sl != &library.subLibraries.back()) { + json << ","; + } + } + json << "]}"; + } } // namespace namespace openspace::scripting { @@ -99,8 +173,7 @@ ghoul::lua::LuaState* ScriptEngine::luaState() { void ScriptEngine::addLibrary(LuaLibrary library) { ZoneScoped - auto sortFunc = [](const LuaLibrary::Function& lhs, const LuaLibrary::Function& rhs) - { + auto sortFunc = [](const LuaLibrary::Function& lhs, const LuaLibrary::Function& rhs) { return lhs.name < rhs.name; }; @@ -111,50 +184,18 @@ void ScriptEngine::addLibrary(LuaLibrary library) { [&library](const LuaLibrary& lib) { return lib.name == library.name; } ); - if (it == _registeredLibraries.end()) { - // If not, we can add it after we sorted it - std::sort(library.functions.begin(), library.functions.end(), sortFunc); - _registeredLibraries.push_back(std::move(library)); - std::sort(_registeredLibraries.begin(), _registeredLibraries.end()); - } - else { - // otherwise, we merge the libraries - - LuaLibrary merged = *it; - for (const LuaLibrary::Function& fun : library.functions) { - const auto itf = std::find_if( - merged.functions.begin(), - merged.functions.end(), - [&fun](const LuaLibrary::Function& function) { - return fun.name == function.name; - } - ); - if (itf != merged.functions.end()) { - // the function with the desired name is already present, but we don't - // want to overwrite it - LERROR(fmt::format( - "Lua function '{}' in library '{}' has been defined twice", - fun.name, - library.name - )); - return; - } - else { - merged.functions.push_back(fun); - } - } - - for (const std::filesystem::path& script : library.scripts) { - merged.scripts.push_back(script); - } - + if (it != _registeredLibraries.end()) { + // if we found the library, we want to merge them + LuaLibrary cpy = *it; + cpy.merge(std::move(library)); _registeredLibraries.erase(it); - - // Sort the merged library before inserting it - std::sort(merged.functions.begin(), merged.functions.end(), sortFunc); - _registeredLibraries.push_back(std::move(merged)); - std::sort(_registeredLibraries.begin(), _registeredLibraries.end()); + library = cpy; } + + // If not, we can add it after we sorted it + std::sort(library.functions.begin(), library.functions.end(), sortFunc); + _registeredLibraries.push_back(std::move(library)); + std::sort(_registeredLibraries.begin(), _registeredLibraries.end()); } bool ScriptEngine::hasLibrary(const std::string& name) { @@ -314,6 +355,22 @@ void ScriptEngine::addLibraryFunctions(lua_State* state, LuaLibrary& library, lua_settable(state, TableOffset); } + for (LuaLibrary& sl : library.subLibraries) { + ghoul::lua::push(state, sl.name); + lua_newtable(state); + lua_settable(state, TableOffset); + + // Retrieve the table + ghoul::lua::push(state, sl.name); + lua_gettable(state, -2); + + // Add the library functions into the table + addLibraryFunctions(state, sl, Replace::No); + + // Pop the table + lua_pop(state, 1); + } + for (const std::filesystem::path& script : library.scripts) { // First we run the script to set its values in the current state ghoul::lua::runScriptFile(state, script.string()); @@ -567,27 +624,10 @@ std::vector ScriptEngine::allLuaFunctions() const { ZoneScoped std::vector result; - for (const LuaLibrary& library : _registeredLibraries) { - for (const LuaLibrary::Function& function : library.functions) { - std::string total = "openspace."; - if (!library.name.empty()) { - total += library.name + "."; - } - total += function.name; - result.push_back(std::move(total)); - } - - for (const LuaLibrary::Documentation& doc : library.documentations) { - std::string total = "openspace."; - if (!library.name.empty()) { - total += library.name + "."; - } - total += doc.name; - result.push_back(std::move(total)); - } + std::vector r = luaFunctions(library, "openspace."); + result.insert(result.end(), r.begin(), r.end()); } - return result; } @@ -600,42 +640,12 @@ std::string ScriptEngine::generateJson() const { bool first = true; for (const LuaLibrary& l : _registeredLibraries) { - constexpr const char* replStr = R"("{}": "{}", )"; - constexpr const char* replStr2 = R"("{}": "{}")"; - if (!first) { json << ","; } first = false; - json << "{"; - json << fmt::format(replStr, "library", l.name); - json << "\"functions\": ["; - - for (const LuaLibrary::Function& f : l.functions) { - json << "{"; - json << fmt::format(replStr, "name", f.name); - json << fmt::format(replStr, "arguments", escapedJson(f.argumentText)); - json << fmt::format(replStr2, "help", escapedJson(f.helpText)); - json << "}"; - if (&f != &l.functions.back() || !l.documentations.empty()) { - json << ","; - } - } - - for (const LuaLibrary::Documentation& doc : l.documentations) { - json << "{"; - json << fmt::format(replStr, "name", doc.name); - json << fmt::format(replStr, "arguments", escapedJson(doc.parameter)); - json << fmt::format(replStr2, "help", escapedJson(doc.description)); - json << "}"; - if (&doc != &l.documentations.back()) { - json << ","; - } - } - - json << "]}"; - + toJson(l, json); } json << "]";