From f1d6a8faf987984ebd0ce83116b1e6d31d2341ba Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Tue, 17 Aug 2021 10:07:42 +0200
Subject: [PATCH] SCRIPT: Register functions and jobs when registering a
 module.

Only local functions and jobs are registered, the other will be
registered with their parent script.
---
 rsc/lua/module.lua                            |  3 +
 src/Lib/Script/CRTPLuaScriptObject.hh         | 14 ++++
 .../FunctionDeclaration.cc                    | 28 ++++++-
 .../CRTPLuaScriptObject/JobDeclaration.cc     | 28 ++++++-
 .../CRTPLuaScriptObject/ModuleDeclaration.cc  |  9 ++-
 src/Lib/Script/LuaContext.cc                  | 77 +++++++++++++++++--
 src/Lib/Script/LuaContext.hh                  | 39 ++++++++--
 7 files changed, 181 insertions(+), 17 deletions(-)

diff --git a/rsc/lua/module.lua b/rsc/lua/module.lua
index 52c28751..8975ff8e 100644
--- a/rsc/lua/module.lua
+++ b/rsc/lua/module.lua
@@ -113,6 +113,9 @@ function DeclareModule(tbl)
     if tbl.functions then module:setFunctions(tbl.functions) end
 
     ___tryImportOptions(module, tbl)
+    -- Register jobs and functions in runtime now that they are added to the module!
+    for _, job in ipairs(tbl.jobs)      do job:pushToRuntime() end
+    for _, fun in ipairs(tbl.functions) do fun:pushToRuntime() end
 
     -- Register the module!
     module:pushToRuntime()
diff --git a/src/Lib/Script/CRTPLuaScriptObject.hh b/src/Lib/Script/CRTPLuaScriptObject.hh
index c0a145f4..989f0140 100644
--- a/src/Lib/Script/CRTPLuaScriptObject.hh
+++ b/src/Lib/Script/CRTPLuaScriptObject.hh
@@ -183,6 +183,8 @@ script_class (JobDeclaration) {
     static int setOptions(lua_State *) noexcept;
     static int setImportFromScript(lua_State *) noexcept;
 
+    static int pushToRuntime(lua_State *const) noexcept;
+
     static int TOSTRING(lua_State *const) noexcept;
 
     method_list metaMethods = { luaRegDefaultGC,
@@ -190,12 +192,17 @@ script_class (JobDeclaration) {
     method_list methods     = { LUA_DECL_METHOD(JobDeclaration, setName),
                             LUA_DECL_METHOD(JobDeclaration, setOptions),
                             LUA_DECL_METHOD(JobDeclaration, setImportFromScript),
+                            LUA_DECL_METHOD(JobDeclaration, pushToRuntime),
                             LUA_DECL_CREATE(JobDeclaration) };
 
+    bool isImported{ false };
+
 public:
     std::string name{};
     std::string parentScript{};
     std::vector<OptionDeclaration *> options{};
+
+    bool isLocalToModule() const noexcept;
 };
 
 // Function declaration
@@ -205,17 +212,24 @@ script_class (FunctionDeclaration) {
     static int setName(lua_State *) noexcept;
     static int setImportFromScript(lua_State *) noexcept;
 
+    static int pushToRuntime(lua_State *const) noexcept;
+
     static int TOSTRING(lua_State *const) noexcept;
 
     method_list metaMethods = { luaRegDefaultGC,
                                 LUA_DECL_META_METHOD(FunctionDeclaration, "__tostring", TOSTRING) };
     method_list methods     = { LUA_DECL_METHOD(FunctionDeclaration, setName),
                             LUA_DECL_METHOD(FunctionDeclaration, setImportFromScript),
+                            LUA_DECL_METHOD(FunctionDeclaration, pushToRuntime),
                             LUA_DECL_CREATE(FunctionDeclaration) };
 
+    bool isImported{ false };
+
 public:
     std::string name{};
     std::string parentScript{};
+
+    bool isLocalToModule() const noexcept;
 };
 
 // Module declaration
diff --git a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
index 4612f031..70f2dac4 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/FunctionDeclaration.cc
@@ -27,6 +27,32 @@ FunctionDeclaration::setName(lua_State *L) noexcept
 int
 FunctionDeclaration::setImportFromScript(lua_State *L) noexcept
 {
-    FunctionDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
+    auto *const fun   = FunctionDeclaration::CHECK(L, 1);
+    fun->parentScript = CHECK_STRING_VIEW(L, 2);
+    fun->isImported   = true;
     LUA_RETURN_NOTHING(L);
 }
+
+int
+FunctionDeclaration::pushToRuntime(lua_State *const L) noexcept
+{
+    FunctionDeclaration *const self = CHECK(L, 1);
+    auto *const context             = LuaContext::getContext(L);
+
+    lua_settop(L, 1);
+
+    if (self->isImported)
+        err(context) << "The function \"" << self->parentScript << "::" << self->name
+                     << "\" is an imported function\n";
+
+    else if (context->registerDeclaration(self) == LuaContext::Code::Error)
+        context->setFailed("Failed to register function " + self->name);
+
+    LUA_RETURN_NOTHING(L);
+}
+
+bool
+FunctionDeclaration::isLocalToModule() const noexcept
+{
+    return parentScript.size() == 0;
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
index 52e0d9be..5c16cfe0 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/JobDeclaration.cc
@@ -58,6 +58,32 @@ JobDeclaration::setOptions(lua_State *L) noexcept
 int
 JobDeclaration::setImportFromScript(lua_State *L) noexcept
 {
-    JobDeclaration::CHECK(L, 1)->parentScript = CHECK_STRING_VIEW(L, 2);
+    auto *const job   = JobDeclaration::CHECK(L, 1);
+    job->parentScript = CHECK_STRING_VIEW(L, 2);
+    job->isImported   = true;
     LUA_RETURN_NOTHING(L);
 }
+
+int
+JobDeclaration::pushToRuntime(lua_State *const L) noexcept
+{
+    JobDeclaration *const self = CHECK(L, 1);
+    auto *const context        = LuaContext::getContext(L);
+
+    lua_settop(L, 1);
+
+    if (self->isImported)
+        err(context) << "The job \"" << self->parentScript << "::" << self->name
+                     << "\" is an imported job\n";
+
+    else if (context->registerDeclaration(self) == LuaContext::Code::Error)
+        context->setFailed("Failed to register job " + self->name);
+
+    LUA_RETURN_NOTHING(L);
+}
+
+bool
+JobDeclaration::isLocalToModule() const noexcept
+{
+    return parentScript.size() == 0;
+}
diff --git a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
index 6d7b048f..82910f87 100644
--- a/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
+++ b/src/Lib/Script/CRTPLuaScriptObject/ModuleDeclaration.cc
@@ -148,6 +148,8 @@ ModuleDeclaration::setFunctions(lua_State *const L) noexcept
     auto *const self = ModuleDeclaration::CHECK(L, 1);
     IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         FunctionDeclaration *const fun = FunctionDeclaration::CHECK(L, -1);
+        if (fun->isLocalToModule())
+            fun->parentScript = self->moduleName;
         self->moduleFunctions.emplace_back(fun);
         self->addUserValue(L, 1); // Tie the function to the module
         return true;              // The value was popped from the stack
@@ -161,6 +163,8 @@ ModuleDeclaration::setJobs(lua_State *const L) noexcept
     auto *const self = ModuleDeclaration::CHECK(L, 1);
     IterateOverArray(L, 2, [self, L]() noexcept -> bool {
         JobDeclaration *const job = JobDeclaration::CHECK(L, -1);
+        if (job->isLocalToModule())
+            job->parentScript = self->moduleName;
         self->moduleJobs.emplace_back(job);
         self->addUserValue(L, 1); // Tie the job to the module
         return true;              // The value was popped from the stack
@@ -174,12 +178,13 @@ ModuleDeclaration::pushToRuntime(lua_State *const L) noexcept
     ModuleDeclaration *const self = CHECK(L, 1);
     auto *const context           = LuaContext::getContext(L);
 
-    Utils::uniqAndSort<std::string>(self->importNames);
+    self->importNames.emplace_back(self->moduleName);   // You can use localy defined things
+    Utils::uniqAndSort<std::string>(self->importNames); // Better for all std algorithms
     if (self->validateModule(L)) {
         err(context) << "Register module \"" << self->moduleName << "\" in the runtime!\n";
         lua_settop(L, 1);
 
-        if (context->registerModuleDeclaration(self) == LuaContext::Code::Error)
+        if (context->registerDeclaration(self) == LuaContext::Code::Error)
             context->setFailed("Failed to register module " + self->moduleName);
     }
 
diff --git a/src/Lib/Script/LuaContext.cc b/src/Lib/Script/LuaContext.cc
index 3c204739..9ca957e8 100644
--- a/src/Lib/Script/LuaContext.cc
+++ b/src/Lib/Script/LuaContext.cc
@@ -142,15 +142,51 @@ LuaContext::getState() noexcept
     return L;
 }
 
+bool
+LuaContext::moduleExists(string_view mod) const noexcept
+{
+    return modules.count(mod) >= 1;
+}
+
+LuaContext::Code
+LuaContext::registerDeclaration(const FunctionDeclaration *const fun) noexcept
+{
+    FunctionDeclaration::CHECK(L, -1);
+    return registerToMapTuple(fun->parentScript, fun->name, &functions);
+}
+
+LuaContext::Code
+LuaContext::registerDeclaration(const JobDeclaration *const job) noexcept
+{
+    JobDeclaration::CHECK(L, -1);
+    return registerToMapTuple(job->parentScript, job->name, &jobs);
+}
+
 LuaContext::Code
-LuaContext::registerModuleDeclaration(const ModuleDeclaration *const mod) noexcept
+LuaContext::registerToMapTuple(
+    string_view module, string_view name,
+    std::map<std::tuple<string_view, string_view>, const int> *const map) noexcept
 {
-    if (modules.count(mod->moduleName) >= 1) {
+    if (map->count({ module, name }) >= 1)
+        return Error;
+
+    streamErr << "Register \"" << module << "::" << name << "\"\n";
+
+    // WARN: The object needs to be already checked!
+    const int r = luaL_ref(L, LUA_REGISTRYINDEX);
+    map->emplace(std::tuple{ module, name }, r);
+    return Success;
+}
+
+LuaContext::Code
+LuaContext::registerDeclaration(const ModuleDeclaration *const mod) noexcept
+{
+    if (moduleExists(mod->moduleName)) {
         streamErr << "The module \"" << mod->moduleName << "\" is already registered!";
         return Code::Error;
     }
 
-    streamErr << "Try to register module \"" << mod->moduleName << "\"\n";
+    streamErr << "Register module \"" << mod->moduleName << "\"\n";
 
     ModuleDeclaration::CHECK(L, -1);
     const int r = luaL_ref(L, LUA_REGISTRYINDEX);
@@ -162,7 +198,7 @@ LuaContext::registerModuleDeclaration(const ModuleDeclaration *const mod) noexce
 const ModuleDeclaration *
 LuaContext::getModule(const std::string_view name) const noexcept
 {
-    if (modules.count(name) >= 1) {
+    if (moduleExists(name)) {
         const int r = modules.at(name);
         streamErr << "Try to get module \"" << name << "\" with register key " << r << "\n";
         lua_rawgeti(L, LUA_REGISTRYINDEX, r);
@@ -173,10 +209,41 @@ LuaContext::getModule(const std::string_view name) const noexcept
     }
 }
 
+LuaContext::Code
+LuaContext::unregisterFunctionDeclaration(const std::string_view module,
+                                          const std::string_view name) noexcept
+{
+    return unregisterFromMapTuple(module, name, &functions);
+}
+
+LuaContext::Code
+LuaContext::unregisterJobDeclaration(const std::string_view module,
+                                     const std::string_view name) noexcept
+{
+    return unregisterFromMapTuple(module, name, &jobs);
+}
+
+LuaContext::Code
+LuaContext::unregisterFromMapTuple(
+    string_view module, string_view name,
+    std::map<std::tuple<string_view, string_view>, const int> *const map) noexcept
+{
+    if (!moduleExists(module))
+        return Error;
+
+    if (map->count({ module, name }) >= 1) {
+        const int r = map->at({ module, name });
+        luaL_unref(L, LUA_REGISTRYINDEX, r);
+        return Success;
+    } else {
+        return Error;
+    }
+}
+
 LuaContext::Code
 LuaContext::unregisterModuleDeclaration(const std::string_view name) noexcept
 {
-    if (modules.count(name) >= 1) {
+    if (moduleExists(name)) {
         const int r = modules.at(name);
         luaL_unref(L, LUA_REGISTRYINDEX, r);
         return Code::Success;
diff --git a/src/Lib/Script/LuaContext.hh b/src/Lib/Script/LuaContext.hh
index faf5808c..88362f64 100644
--- a/src/Lib/Script/LuaContext.hh
+++ b/src/Lib/Script/LuaContext.hh
@@ -16,27 +16,37 @@ class ScriptDocument;
 namespace Vivy::Script
 {
 class ModuleDeclaration;
+class FunctionDeclaration;
+class JobDeclaration;
 }
 
 namespace Vivy::Script
 {
 // New Lua script instance
 class LuaContext final {
+    VIVY_UNMOVABLE_OBJECT(LuaContext)
+
+    using string_view = const std::string_view;
+
     lua_State *L{ nullptr };
+
     std::string failureString{};
     std::string currentFile{};
     std::string lastLoadedModule{};
-    std::map<const std::string_view, const int> modules                           = {};
+
+    std::map<string_view, const int> modules                            = {};
+    std::map<std::tuple<string_view, string_view>, const int> functions = {};
+    std::map<std::tuple<string_view, string_view>, const int> jobs      = {};
+
     static inline std::map<const lua_State *const, LuaContext *const> contextList = {};
 
     ScriptStore::LoggerType &streamOut;
     ScriptStore::LoggerType &streamErr;
 
 public:
-    enum class Code {
-        Success,
-        Error,
-    };
+    enum class Code { Success, Error };
+    static constexpr inline Code Success = Code::Success;
+    static constexpr inline Code Error   = Code::Error;
 
     LuaContext(ScriptStore::LoggerType &out, ScriptStore::LoggerType &err) noexcept;
     ~LuaContext() noexcept;
@@ -52,9 +62,14 @@ public:
     operator lua_State *() noexcept { return L; }
     static LuaContext *getContext(const lua_State *const) noexcept;
 
-    // WARN: Need the ModuleDeclaration to be on top of the stack!
-    Code registerModuleDeclaration(const ModuleDeclaration *const) noexcept;
-    Code unregisterModuleDeclaration(const std::string_view) noexcept;
+    // WARN: Need the ModuleDeclaration, the FunctionDeclaration and the
+    // JobDeclaration to be on top of the stack!
+    Code registerDeclaration(const ModuleDeclaration *const) noexcept;
+    Code registerDeclaration(const FunctionDeclaration *const) noexcept;
+    Code registerDeclaration(const JobDeclaration *const) noexcept;
+    Code unregisterModuleDeclaration(string_view) noexcept;
+    Code unregisterFunctionDeclaration(string_view, string_view) noexcept;
+    Code unregisterJobDeclaration(string_view, string_view) noexcept;
 
     // Returns the module or `nullptr` if not found.
     const ModuleDeclaration *getModule(const std::string_view) const noexcept;
@@ -78,6 +93,14 @@ private:
     Code loadFile(const char *) noexcept;
     void loadPackagedFile(const QString &) noexcept;
 
+    Code unregisterFromMapTuple(
+        const std::string_view module, const std::string_view name,
+        std::map<std::tuple<string_view, string_view>, const int> *const mapPtr) noexcept;
+    Code registerToMapTuple(
+        const std::string_view module, const std::string_view name,
+        std::map<std::tuple<string_view, string_view>, const int> *const mapPtr) noexcept;
+    bool moduleExists(string_view) const noexcept;
+
     // Swap the env, needed before executing a user script!
     void swapEnv() noexcept;
 };
-- 
GitLab