From fffa17d7e28f4a3147adf1f2ae2a73c4b0f7b945 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Wed, 12 Jun 2024 11:26:38 +0200 Subject: Add support for to-be-closed linda --- CHANGES | 1 + deep_test/deep_test.cpp | 3 +- docs/index.html | 9 ++-- src/compat.cpp | 2 +- src/deep.cpp | 3 +- src/deep.h | 2 +- src/linda.cpp | 115 ++++++++++++++++++++++++++++++++++++++++++------ src/lindafactory.cpp | 23 ++-------- 8 files changed, 116 insertions(+), 42 deletions(-) diff --git a/CHANGES b/CHANGES index 64794b1..f10f160 100644 --- a/CHANGES +++ b/CHANGES @@ -25,6 +25,7 @@ CHANGE 2: BGe 11-Jun-24 - providing "auto" as name when constructing a Linda cause Lanes to provide a name built from the source location of the construction. - specifying a group to lanes.linda() is mandatory when Lanes is configured with user Keepers. - linda:deep() result no longer contains the raw C pointer of the Linda object. + - Lindas have a __close metamethod that calls any provided suitable handler at Linda creation. - Lane generator settings: - error_trace_level added. Replaces the global verbose_errors setting. - name added. Can be used to set the name early (before the lane body calls set_debug_threadname()). diff --git a/deep_test/deep_test.cpp b/deep_test/deep_test.cpp index 178075d..2497742 100644 --- a/deep_test/deep_test.cpp +++ b/deep_test/deep_test.cpp @@ -131,7 +131,8 @@ int luaD_new_deep(lua_State* L) { int const nuv{ static_cast(luaL_optinteger(L, 1, 0)) }; lua_settop(L, 0); - return MyDeepFactory::Instance.pushDeepUserdata(DestState{ L }, nuv); + MyDeepFactory::Instance.pushDeepUserdata(DestState{ L }, nuv); + return 1; } // ################################################################################################# diff --git a/docs/index.html b/docs/index.html index 10bf832..be8324c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -70,7 +70,7 @@

- This document was revised on 15-May-24, and applies to version 4.0.0. + This document was revised on 12-Jun-24, and applies to version 4.0.0.

@@ -1196,13 +1196,16 @@

-	h = lanes.linda([opt_name, [opt_group]])
+	h = lanes.linda([name],[group],[close_handler])
 

+ Arguments to lanes.linda() can be provided in any order, as long as there is a single string, a single number, and a single callable value (all are optional).
Converting the Linda to a string will yield the provided name prefixed by "Linda: ".
If opt_name is omitted, it will evaluate to an hexadecimal number uniquely representing that Linda when the Linda is converted to a string. The value is the same as returned by linda:deep().
- If opt_name is "auto", Lanes will try to construct a name from the source location that called lanes.linda(). If that fails, the Linda name will be "<unresolved>". + If opt_name is "auto", Lanes will try to construct a name from the source location that called lanes.linda(). If that fails, the Linda name will be "<unresolved>".
+ If Lanes is configured with more than one Keeper state, group is mandatory.
+ If the Linda is to-be-closed (Lua 5.4+), and a close_handler is provided, it will be called with all the provided arguments. For older Lua versions, its presence will cause an error.

diff --git a/src/compat.cpp b/src/compat.cpp
index e1e7488..4076aa6 100644
--- a/src/compat.cpp
+++ b/src/compat.cpp
@@ -92,7 +92,7 @@ void* lua_newuserdatauv(lua_State* L_, size_t sz_, [[maybe_unused]] int nuvalue_
 // #################################################################################################
 
 // push on stack uservalue #n of full userdata at idx
-int lua_getiuservalue(lua_State* L_, int idx_, int n_)
+int lua_getiuservalue(lua_State* const L_, int const idx_, int const n_)
 {
     STACK_CHECK_START_REL(L_, 0);
     // full userdata can have only 1 uservalue before 5.4
diff --git a/src/deep.cpp b/src/deep.cpp
index b6a9a22..249c497 100644
--- a/src/deep.cpp
+++ b/src/deep.cpp
@@ -295,7 +295,7 @@ void DeepFactory::PushDeepProxy(DestState const L_, DeepPrelude* const prelude_,
  *
  * Returns: 'proxy' userdata for accessing the deep data via 'DeepFactory::toDeep()'
  */
-int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const
+void DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const
 {
     STACK_GROW(L_, 1);
     STACK_CHECK_START_REL(L_, 0);
@@ -322,7 +322,6 @@ int DeepFactory::pushDeepUserdata(DestState const L_, int const nuv_) const
 
     DeepFactory::PushDeepProxy(L_, _prelude, nuv_, LookupMode::LaneBody, L_);                      // proxy
     STACK_CHECK(L_, 1);
-    return 1;
 }
 
 // #################################################################################################
diff --git a/src/deep.h b/src/deep.h
index fb62276..bb86c20 100644
--- a/src/deep.h
+++ b/src/deep.h
@@ -68,7 +68,7 @@ class DeepFactory
     static void DeleteDeepObject(lua_State* L_, DeepPrelude* o_);
     [[nodiscard]] static DeepFactory* LookupFactory(lua_State* L_, int index_, LookupMode mode_);
     static void PushDeepProxy(DestState L_, DeepPrelude* o_, int nuv_, LookupMode mode_, lua_State* errL_);
-    [[nodiscard]] int pushDeepUserdata(DestState L_, int nuv_) const;
+    void pushDeepUserdata(DestState L_, int nuv_) const;
     [[nodiscard]] DeepPrelude* toDeep(lua_State* L_, int index_) const;
 };
 
diff --git a/src/linda.cpp b/src/linda.cpp
index 1bd5d16..91138c5 100644
--- a/src/linda.cpp
+++ b/src/linda.cpp
@@ -282,6 +282,33 @@ LUAG_FUNC(linda_cancel)
 
 // #################################################################################################
 
+#if LUA_VERSION_NUM >= 504
+// linda:__close(err|nil)
+static LUAG_FUNC(linda_close)
+{
+    [[maybe_unused]] Linda* const _linda{ ToLinda(L_, 1) };                                 // L_: linda err|nil
+
+    // do we have a uservalue? it contains a callback
+    switch (lua_getiuservalue(L_, 1, 1)) {
+    case LUA_TTABLE: // callable table
+    case LUA_TUSERDATA: // callable userdata
+    case LUA_TFUNCTION:                                                                            // L_: linda err|nil on_close()
+        lua_insert(L_, 1);                                                                         // L_: on_close() linda err|nil
+        lua_call(L_, lua_gettop(L_) - 1, 0);                                                       // L_:
+        return 0;
+
+    case LUA_TNONE:
+    case LUA_TNIL:
+        return 0;
+
+    default:
+        raise_luaL_error(L_, "Invalid __close handler");
+    }
+}
+#endif // LUA_VERSION_NUM >= 504
+
+// #################################################################################################
+
 /*
  * string = linda:__concat( a, b)
  *
@@ -811,6 +838,9 @@ LUAG_FUNC(linda_towatch)
 namespace {
     namespace local {
         static luaL_Reg const sLindaMT[] = {
+#if LUA_VERSION_NUM >= 504
+            { "__close", LG_linda_close },
+#endif // LUA_VERSION_NUM >= 504
             { "__concat", LG_linda_concat },
             { "__tostring", LG_linda_tostring },
 #if HAVE_DECODA_SUPPORT()
@@ -837,31 +867,88 @@ namespace {
 // #################################################################################################
 
 /*
- * ud = lanes.linda( [name[,group]])
+ * ud = lanes.linda( [name[,group[,close_handler]]])
  *
  * returns a linda object, or raises an error if creation failed
  */
 LUAG_FUNC(linda)
 {
+    static constexpr int kLastArg{ LUA_VERSION_NUM >= 504 ? 3 : 2};
     int const _top{ lua_gettop(L_) };
+    luaL_argcheck(L_, _top <= kLastArg, _top, "too many arguments");
+    int _closeHandlerIdx{};
+    int _nameIdx{};
     int _groupIdx{};
-    luaL_argcheck(L_, _top <= 2, _top, "too many arguments");
-    if (_top == 1) {
-        LuaType const _t{ luaG_type(L_, 1) };
-        int const _nameIdx{ (_t == LuaType::STRING) ? 1 : 0 };
-        _groupIdx = (_t == LuaType::NUMBER) ? 1 : 0;
-        luaL_argcheck(L_, _nameIdx || _groupIdx, 1, "wrong parameter (should be a string or a number)");
-    } else if (_top == 2) {
-        luaL_checktype(L_, 1, LUA_TSTRING);
-        luaL_checktype(L_, 2, LUA_TNUMBER);
-        _groupIdx = 2;
+    for (int const _i : std::ranges::iota_view{ 1, _top + 1 }) {
+        switch (luaG_type(L_, _i)) {
+#if LUA_VERSION_NUM >= 504 // to-be-closed support starts with Lua 5.4
+        case LuaType::FUNCTION:
+            luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler");
+            _closeHandlerIdx = _i;
+            break;
+
+        case LuaType::USERDATA:
+        case LuaType::TABLE:
+            luaL_argcheck(L_, _closeHandlerIdx == 0, _i, "More than one __close handler");
+            luaL_argcheck(L_, luaL_getmetafield(L_, _i, "__call") != 0, _i, "__close handler is not callable");
+            lua_pop(L_, 1); // luaL_getmetafield() pushed the field, we need to pop it
+            _closeHandlerIdx = _i;
+            break;
+#endif // LUA_VERSION_NUM >= 504
+
+        case LuaType::STRING:
+            luaL_argcheck(L_, _nameIdx == 0, _i, "More than one name");
+            _nameIdx = _i;
+            break;
+
+        case LuaType::NUMBER:
+            luaL_argcheck(L_, _groupIdx == 0, _i, "More than one group");
+            _groupIdx = _i;
+            break;
+
+        default:
+            luaL_argcheck(L_, false, _i, "Bad argument type (should be a string, a number, or a callable type)");
+        }
     }
+ 
     int const _nbKeepers{ Universe::Get(L_)->keepers.getNbKeepers() };
     if (!_groupIdx) {
-        luaL_argcheck(L_, _nbKeepers < 2, 0, "there are multiple keepers, you must specify a group");
+        luaL_argcheck(L_, _nbKeepers < 2, 0, "Group is mandatory in multiple Keeper scenarios");
     } else {
         int const _group{ static_cast(lua_tointeger(L_, _groupIdx)) };
-        luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "group out of range");
+        luaL_argcheck(L_, _group >= 0 && _group < _nbKeepers, _groupIdx, "Group out of range");
     }
-    return LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0);
+
+    // done with argument checking, let's proceed
+    if constexpr (LUA_VERSION_NUM >= 504) {
+        // make sure we have kMaxArgs arguments on the stack for processing, with name, group, and handler, in that order
+        lua_settop(L_, kLastArg);                                                                  // L_: a b c
+        // If either index is 0, lua_settop() adjusted the stack with a nil in slot kLastArg
+        lua_pushvalue(L_, _closeHandlerIdx ? _closeHandlerIdx : kLastArg);                         // L_: a b c close_handler
+        lua_pushvalue(L_, _groupIdx ? _groupIdx : kLastArg);                                       // L_: a b c close_handler group
+        lua_pushvalue(L_, _nameIdx ? _nameIdx : kLastArg);                                         // L_: a b c close_handler group name
+        lua_replace(L_, 1);                                                                        // L_: name b c close_handler group
+        lua_replace(L_, 2);                                                                        // L_: name group c close_handler
+        lua_replace(L_, 3);                                                                        // L_: name group close_handler
+
+        // if we have a __close handler, we need a uservalue slot to store it
+        int const _nuv{ _closeHandlerIdx ? 1 : 0 };
+        LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, _nuv);                            // L_: name group close_handler linda
+        if (_closeHandlerIdx != 0) {
+            lua_replace(L_, 2);                                                                    // L_: name linda close_handler
+            lua_setiuservalue(L_, 2, 1);                                                           // L_: name linda
+        }
+        // depending on whether we have a handler or not, the stack is not in the same state at this point
+        // just make sure we have our Linda at the top
+        LUA_ASSERT(L_, ToLinda(L_, -1));
+        return 1;
+    } else { // no to-be-closed support
+        // ensure we have name, group in that order on the stack
+        if (_nameIdx > _groupIdx) {
+            lua_insert(L_, 1);                                                                     // L_: name group
+        }
+        LindaFactory::Instance.pushDeepUserdata(DestState{ L_ }, 0);                               // L_: name group linda
+        return 1;
+    }
+
 }
diff --git a/src/lindafactory.cpp b/src/lindafactory.cpp
index 8622c12..863f16e 100644
--- a/src/lindafactory.cpp
+++ b/src/lindafactory.cpp
@@ -105,26 +105,9 @@ std::string_view LindaFactory::moduleName() const
 
 DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* const L_) const
 {
-    std::string_view _linda_name{};
-    LindaGroup _linda_group{ 0 };
-    // should have a string and/or a number of the stack as parameters (name and group)
-    switch (lua_gettop(L_)) {
-    default: // 0
-        break;
-
-    case 1: // 1 parameter, either a name or a group
-        if (luaG_type(L_, -1) == LuaType::STRING) {
-            _linda_name = luaG_tostring(L_, -1);
-        } else {
-            _linda_group = LindaGroup{ static_cast(lua_tointeger(L_, -1)) };
-        }
-        break;
-
-    case 2: // 2 parameters, a name and group, in that order
-        _linda_name = luaG_tostring(L_, -2);
-        _linda_group = LindaGroup{ static_cast(lua_tointeger(L_, -1)) };
-        break;
-    }
+    // we always expect name and group at the bottom of the stack (either can be nil). any extra stuff we ignore and keep unmodified
+    std::string_view _linda_name{ luaG_tostring(L_, 1) };
+    LindaGroup _linda_group{ static_cast(lua_tointeger(L_, 2)) };
 
     // store in the linda the location of the script that created it
     if (_linda_name == "auto") {
-- 
cgit v1.2.3-55-g6feb