From f4b0527c7d11b3a95d44b880cbdd4aae2d58ca8d Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 15 Apr 2024 14:39:05 +0200 Subject: C++ migration: deep userdata API rework. bye bye idfunc, hello DeepFactory --- deep_test/deep_test.cpp | 66 +++++----- deep_test/deep_test.vcxproj | 14 +++ docs/index.html | 39 +++--- src/deep.cpp | 149 +++++++++------------- src/deep.h | 62 +++++++--- src/keeper.cpp | 2 + src/keeper.h | 2 +- src/lanes.cpp | 6 +- src/linda.cpp | 292 +++++++++++++++++++++----------------------- 9 files changed, 318 insertions(+), 314 deletions(-) diff --git a/deep_test/deep_test.cpp b/deep_test/deep_test.cpp index 3467939..b11445b 100644 --- a/deep_test/deep_test.cpp +++ b/deep_test/deep_test.cpp @@ -5,6 +5,21 @@ #include #include +class MyDeepFactory : public DeepFactory +{ + private: + + DeepPrelude* newDeepObjectInternal(lua_State* L) const override; + void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const override; + void createMetatable(lua_State* L) const override + { + luaL_getmetatable(L, "deep"); + } + char const* moduleName() const override { return "deep_test"; } +}; + +static MyDeepFactory g_MyDeepFactory; + // ################################################################################################ // a lanes-deep userdata. needs DeepPrelude and luaG_newdeepuserdata from Lanes code. @@ -15,44 +30,25 @@ struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a De // ################################################################################################ -[[nodiscard]] static void* deep_test_id(lua_State* L, DeepOp op_) +DeepPrelude* MyDeepFactory::newDeepObjectInternal(lua_State* L) const { - switch( op_) - { - case DeepOp::New: - { - MyDeepUserdata* deep_test = new MyDeepUserdata; - return deep_test; - } - - case DeepOp::Delete: - { - MyDeepUserdata* deep_test = static_cast(lua_touserdata( L, 1)); - delete deep_test; - return nullptr; - } - - case DeepOp::Metatable: - { - luaL_getmetatable( L, "deep"); // mt - return nullptr; - } + MyDeepUserdata* deep_test = new MyDeepUserdata{ g_MyDeepFactory }; + return deep_test; +} - case DeepOp::Module: - return (void*)"deep_test"; +// ################################################################################################ - default: - { - return nullptr; - } - } +void MyDeepFactory::deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const +{ + MyDeepUserdata* deep_test = static_cast(o_); + delete deep_test; } // ################################################################################################ [[nodiscard]] static int deep_set(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + MyDeepUserdata* const self{ static_cast(g_MyDeepFactory.toDeep(L, 1)) }; lua_Integer i = lua_tointeger( L, 2); self->val = i; return 0; @@ -60,10 +56,9 @@ struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a De // ################################################################################################ -// won't actually do anything as deep userdata don't have uservalue slots [[nodiscard]] static int deep_setuv(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + MyDeepUserdata* const self{ static_cast(g_MyDeepFactory.toDeep(L, 1)) }; int uv = (int) luaL_optinteger(L, 2, 1); lua_settop( L, 3); lua_pushboolean( L, lua_setiuservalue( L, 1, uv) != 0); @@ -75,7 +70,7 @@ struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a De // won't actually do anything as deep userdata don't have uservalue slots [[nodiscard]] static int deep_getuv(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + MyDeepUserdata* const self{ static_cast(g_MyDeepFactory.toDeep(L, 1)) }; int uv = (int) luaL_optinteger(L, 2, 1); lua_getiuservalue( L, 1, uv); return 1; @@ -85,7 +80,7 @@ struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a De [[nodiscard]] static int deep_tostring(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + MyDeepUserdata* const self{ static_cast(g_MyDeepFactory.toDeep(L, 1)) }; lua_pushfstring(L, "%p:deep(%d)", lua_topointer(L, 1), self->val); return 1; } @@ -94,7 +89,7 @@ struct MyDeepUserdata : public DeepPrelude // Deep userdata MUST start with a De [[nodiscard]] static int deep_gc(lua_State* L) { - MyDeepUserdata* self = static_cast(luaG_todeep(L, deep_test_id, 1)); + MyDeepUserdata* const self{ static_cast(g_MyDeepFactory.toDeep(L, 1)) }; return 0; } @@ -115,9 +110,8 @@ static luaL_Reg const deep_mt[] = int luaD_new_deep( lua_State* L) { int const nuv{ static_cast(luaL_optinteger(L, 1, 0)) }; - // no additional parameter to luaG_newdeepuserdata! lua_settop(L, 0); - return luaG_newdeepuserdata(Dest{ L }, deep_test_id, nuv); + return g_MyDeepFactory.pushDeepUserdata(Dest{ L }, nuv); } // ################################################################################################ diff --git a/deep_test/deep_test.vcxproj b/deep_test/deep_test.vcxproj index 5cd3c55..ddfad5d 100644 --- a/deep_test/deep_test.vcxproj +++ b/deep_test/deep_test.vcxproj @@ -388,6 +388,7 @@ $(SolutionDir)..\Lua53\include;$(SolutionDir)Lanes;%(AdditionalIncludeDirectories) $(IntDir)$(TargetName).pdb stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua53\bin\$(Platform)\Debug\ @@ -407,6 +408,7 @@ $(SolutionDir)..\Lua51\include;$(SolutionDir)Lanes;%(AdditionalIncludeDirectories) $(IntDir)$(TargetName).pdb stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua51\bin\$(Platform)\Debug\ @@ -426,6 +428,7 @@ $(SolutionDir)..\Lua51\include;$(SolutionDir)Lanes;%(AdditionalIncludeDirectories) $(IntDir)$(TargetName).pdb stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua52\bin\$(Platform)\Debug\ @@ -446,6 +449,7 @@ $(IntDir)$(TargetName).pdb WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\LuaJIT-2.1.0-beta3\bin\$(Platform)\ @@ -466,6 +470,7 @@ $(IntDir)$(TargetName).pdb WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\LuaJIT-2.0.5\bin\$(Platform)\ @@ -485,6 +490,7 @@ $(SolutionDir)..\Lua54\include;$(SolutionDir)Lanes;%(AdditionalIncludeDirectories) $(IntDir)$(TargetName).pdb stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\framework\ @@ -504,6 +510,7 @@ $(SolutionDir)..\MoonJIT\src;$(SolutionDir)Lanes;%(AdditionalIncludeDirectories) $(IntDir)$(TargetName).pdb stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\MoonJIT\bin\$(Platform)\ @@ -524,6 +531,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua53\bin\$(Platform)\Debug\ @@ -544,6 +552,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua51\bin\$(Platform)\Debug\ @@ -564,6 +573,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\Lua52\bin\$(Platform)\Debug\ @@ -584,6 +594,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\LuaJIT-2.1.0-beta3\bin\$(Platform)\ @@ -604,6 +615,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\LuaJIT-2.0.5\bin\$(Platform)\ @@ -624,6 +636,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\framework\ @@ -644,6 +657,7 @@ $(IntDir)$(TargetName).pdb _WINDLL;_DEBUG;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) stdcpp20 + ProgramDatabase xcopy /R /F /Y /I "$(TargetPath)" $(SolutionDir)..\MoonJIT\bin\$(Platform)\ diff --git a/docs/index.html b/docs/index.html index 3e535a6..67eccd5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1683,34 +1683,45 @@ int luaD_new_clonable(lua_State* L)
  1. - Provide an identity function for your userdata, in C. This function is used for creation and deletion of your deep userdata (the shared resource), and for making metatables for the state-specific proxies for accessing it. The prototype is -
    	void* idfunc(lua_State* L, DeepOp op_);
    - op_ can be one of: + Provide a factory for your userdata. This object is used for creation and deletion of your deep userdata (the shared resource), and for making metatables for the state-specific proxies for accessing it. The prototype is +
    +class MyDeepFactory : public DeepFactory
    +{
    +	private:
    +
    +	DeepPrelude* newDeepObjectInternal(lua_State* L) const override;
    +	void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const override;
    +	void createMetatable(lua_State* L) const override;
    +	char const* moduleName() const override;
    +};
    +
    +static MyDeepFactory g_MyDeepFactory;
    +
      -
    • DeepOp::New: requests the creation of a new object, whose pointer is returned. Said object must derive from DeepPrelude.
    • -
    • DeepOp::Delete: receives this same pointer on the stack as a light userdata, and should cleanup the object.
    • -
    • DeepOp::Metatable: should build a metatable for the object. Don't cache the metatable yourself, Lanes takes care of it (DeepOp::Metatable should only be invoked once per state). Just push the metatable on the stack.
    • -
    • DeepOp::Module: requests the name of the module that exports the idfunc, to be returned. It is necessary so that Lanes can require it in any lane state that receives a userdata. This is to prevent crashes in situations where the module could be unloaded while the idfunc pointer is still held.
    • +
    • newDeepObjectInternal: requests the creation of a new object, whose pointer is returned. Said object must derive from DeepPrelude.
    • +
    • deleteDeepObjectInternal: should cleanup the object.
    • +
    • createMetatable: should build a metatable for the object. Don't cache the metatable yourself, Lanes takes care of it (createMetatable should only be invoked once per state). Just push the metatable on the stack.
    • +
    • moduleName: requests the name of the module that exports the factory, to be returned. It is necessary so that Lanes can require it in any lane state that receives a userdata. This is to prevent crashes in situations where the module could be unloaded while the factory pointer is still held.
    - Take a look at linda_id in lanes.cpp or deep_test_id in deep_test.cpp. + Take a look at LindaFactory in linda.cpp or MyDeepFactory in deep_test.cpp.
  2. Include "deep.h" and either link against Lanes or statically compile compat.cpp deep.cpp tools.cpp universe.cpp into your module if you want to avoid a runtime dependency for users that will use your module without Lanes. -
  3. Instanciate your userdata using luaG_newdeepuserdata(), instead of the regular lua_newuserdata(). Given an idfunc, it sets up the support structures and returns a state-specific proxy userdata for accessing your data. This proxy can also be copied over to other lanes.
  4. -
  5. Accessing the deep userdata from your C code, use luaG_todeep() instead of the regular lua_touserdata().
  6. +
  7. Instanciate your userdata using yourFactoryObject.pushDeepUserdata()(), instead of the regular lua_newuserdata(). Given a factory, it sets up the support structures and returns a state-specific proxy userdata for accessing your data. This proxy can also be copied over to other lanes.
  8. +
  9. Accessing the deep userdata from your C code, use yourFactoryObject.toDeep() instead of the regular lua_touserdata().

- Deep userdata management will take care of tying to __gc methods, and doing reference counting to see how many proxies are still there for accessing the data. Once there are none, the data will be freed through a call to the idfunc you provided. + Deep userdata management will take care of tying to __gc methods, and doing reference counting to see how many proxies are still there for accessing the data. Once there are none, the data will be freed through a call to the factory you provided.

- Deep userdata in transit inside keeper states (sent in a linda but not yet consumed) don't call idfunc(DeepOp::Delete) and aren't considered by reference counting. The rationale is the following: + Deep userdata in transit inside keeper states (sent in a linda but not yet consumed) don't call deleteDeepObjectInternal and aren't considered by reference counting. The rationale is the following:
If some non-keeper state holds a deep userdata for some deep object, then even if the keeper collects its own deep userdata, it shouldn't be cleaned up since the refcount is not 0.
- OTOH, if a keeper state holds the last deep userdata for some deep object, then no lane can do actual work with it. Deep userdata's idfunc() is never called from a keeper state. + OTOH, if a keeper state holds the last deep userdata for some deep object, then no lane can do actual work with it. Deep userdata's factory() interface is never accessed from a keeper state.
- Therefore, Lanes can just call idfunc(DeepOp::Delete) when the last non-keeper-held deep userdata is collected, as long as it doesn't do the same in a keeper state after that, since any remaining deep userdata in keeper states now hold stale pointers. + Therefore, Lanes can just call deleteDeepObjectInternal when the last non-keeper-held deep userdata is collected, as long as it doesn't do the same in a keeper state after that, since any remaining deep userdata in keeper states now hold stale pointers.

diff --git a/src/deep.cpp b/src/deep.cpp index d0b8123..780c86e 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -47,11 +47,11 @@ THE SOFTWARE. /*---=== Deep userdata ===---*/ /* -* 'registry[REGKEY]' is a two-way lookup table for 'idfunc's and those type's +* 'registry[REGKEY]' is a two-way lookup table for 'factory's and those type's * metatables: * -* metatable -> idfunc -* idfunc -> metatable +* metatable -> factory +* factory -> metatable */ // crc64/we of string "DEEP_LOOKUP_KEY" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey DEEP_LOOKUP_KEY{ 0x9fb9b4f3f633d83dull }; @@ -84,8 +84,8 @@ static void set_deep_lookup(lua_State* L) // ################################################################################################ /* -* Pops the key (metatable or idfunc) off the stack, and replaces with the -* deep lookup value (idfunc/metatable/nil). +* Pops the key (metatable or factory) off the stack, and replaces with the +* deep lookup value (factory/metatable/nil). */ static void get_deep_lookup(lua_State* L) { @@ -104,21 +104,21 @@ static void get_deep_lookup(lua_State* L) // ################################################################################################ /* -* Return the registered ID function for 'index' (deep userdata proxy), +* Return the registered factory for 'index' (deep userdata proxy), * or nullptr if 'index' is not a deep userdata proxy. */ -[[nodiscard]] static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_) +[[nodiscard]] static inline DeepFactory* get_factory(lua_State* L, int index, LookupMode mode_) { // when looking inside a keeper, we are 100% sure the object is a deep userdata if (mode_ == LookupMode::FromKeeper) { - DeepPrelude** const proxy{ lua_tofulluserdata(L, index) }; - // we can (and must) cast and fetch the internally stored idfunc - return (*proxy)->idfunc; + DeepPrelude* const proxy{ *lua_tofulluserdata(L, index) }; + // we can (and must) cast and fetch the internally stored factory + return &proxy->m_factory; } else { - // essentially we are making sure that the metatable of the object we want to copy is stored in our metatable/idfunc database + // essentially we are making sure that the metatable of the object we want to copy is stored in our metatable/factory database // it is the only way to ensure that the userdata is indeed a deep userdata! // of course, we could just trust the caller, but we won't STACK_GROW( L, 1); @@ -129,10 +129,10 @@ static void get_deep_lookup(lua_State* L) return nullptr; // no metatable: can't be a deep userdata object! } - // replace metatable with the idfunc pointer, if it is actually a deep userdata - get_deep_lookup( L); // deep ... idfunc|nil + // replace metatable with the factory pointer, if it is actually a deep userdata + get_deep_lookup( L); // deep ... factory|nil - luaG_IdFunction const ret{ *lua_tolightuserdata(L, -1) }; // nullptr if not a userdata + DeepFactory* const ret{ lua_tolightuserdata(L, -1) }; // nullptr if not a userdata lua_pop( L, 1); STACK_CHECK( L, 0); return ret; @@ -141,14 +141,10 @@ static void get_deep_lookup(lua_State* L) // ################################################################################################ -void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) +void DeepFactory::DeleteDeepObject(lua_State* L, DeepPrelude* o_) { - ASSERT_L(prelude_->idfunc); STACK_CHECK_START_REL(L, 0); - // Call 'idfunc( "delete", deep_ptr )' to make deep cleanup - lua_pushlightuserdata( L, prelude_); - prelude_->idfunc( L, DeepOp::Delete); - lua_pop(L, 1); + o_->m_factory.deleteDeepObjectInternal(L, o_); STACK_CHECK(L, 0); } @@ -162,8 +158,8 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) */ [[nodiscard]] static int deep_userdata_gc(lua_State* L) { - DeepPrelude** const proxy{ lua_tofulluserdata(L, 1) }; - DeepPrelude* p = *proxy; + DeepPrelude* const* const proxy{ lua_tofulluserdata(L, 1) }; + DeepPrelude* const p{ *proxy }; // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded // in that case, we are not multithreaded and locking isn't necessary anyway @@ -178,17 +174,9 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) lua_insert( L, -2); // __gc self lua_call( L, 1, 0); // } - // 'idfunc' expects a clean stack to work on - lua_settop( L, 0); - free_deep_prelude( L, p); - - // top was set to 0, then userdata was pushed. "delete" might want to pop the userdata (we don't care), but should not push anything! - if ( lua_gettop( L) > 1) - { - return luaL_error( L, "Bad idfunc(DeepOp::Delete): should not push anything"); - } + // we don't really know what remains on the stack at that point (depending on us finding a __gc or not), but we don't care + DeepFactory::DeleteDeepObject(L, p); } - *proxy = nullptr; // make sure we don't use it any more, just in case return 0; } @@ -196,14 +184,14 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) /* * Push a proxy userdata on the stack. - * returns nullptr if ok, else some error string related to bad idfunc behavior or module require problem + * returns nullptr if ok, else some error string related to bad factory behavior or module require problem * (error cannot happen with mode_ == LookupMode::ToKeeper) * - * Initializes necessary structures if it's the first time 'idfunc' is being + * Initializes necessary structures if it's the first time 'factory' is being * used in this Lua state (metatable, registring it). Otherwise, increments the * reference count. */ -char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_) +char const* DeepFactory::PushDeepProxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_) { // Check if a proxy already exists push_registry_subtable_mode( L, DEEP_PROXY_CACHE_KEY, "v"); // DPC @@ -228,24 +216,25 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m *proxy = prelude; prelude->m_refcount.fetch_add(1, std::memory_order_relaxed); // one more proxy pointing to this deep data - // Get/create metatable for 'idfunc' (in this state) - lua_pushlightuserdata( L, std::bit_cast(prelude->idfunc)); // DPC proxy idfunc + // Get/create metatable for 'factory' (in this state) + DeepFactory& factory = prelude->m_factory; + lua_pushlightuserdata( L, std::bit_cast(&factory)); // DPC proxy factory get_deep_lookup( L); // DPC proxy metatable? if( lua_isnil( L, -1)) // // No metatable yet. { - char const* modname; int oldtop = lua_gettop( L); // DPC proxy nil lua_pop( L, 1); // DPC proxy // 1 - make one and register it if (mode_ != LookupMode::ToKeeper) { - (void) prelude->idfunc( L, DeepOp::Metatable); // DPC proxy metatable - if( lua_gettop( L) - oldtop != 0 || !lua_istable( L, -1)) + factory.createMetatable(L); // DPC proxy metatable + if (lua_gettop(L) - oldtop != 0 || !lua_istable(L, -1)) { + // factory didn't push exactly 1 value, or the value it pushed is not a table: ERROR! lua_settop( L, oldtop); // DPC proxy X lua_pop( L, 3); // - return "Bad idfunc(eOP_metatable): unexpected pushed value"; + return "Bad DeepFactory::createMetatable overload: unexpected pushed value"; } // if the metatable contains a __gc, we will call it from our own lua_getfield( L, -1, "__gc"); // DPC proxy metatable __gc @@ -271,22 +260,11 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m // Memorize for later rounds lua_pushvalue( L, -1); // DPC proxy metatable metatable - lua_pushlightuserdata( L, std::bit_cast(prelude->idfunc)); // DPC proxy metatable metatable idfunc + lua_pushlightuserdata(L, std::bit_cast(&factory)); // DPC proxy metatable metatable factory set_deep_lookup( L); // DPC proxy metatable - // 2 - cause the target state to require the module that exported the idfunc - // this is needed because we must make sure the shared library is still loaded as long as we hold a pointer on the idfunc - { - int oldtop_module = lua_gettop( L); - modname = (char const*) prelude->idfunc( L, DeepOp::Module); // DPC proxy metatable - // make sure the function pushed nothing on the stack! - if( lua_gettop( L) - oldtop_module != 0) - { - lua_pop( L, 3); // - return "Bad idfunc(eOP_module): should not push anything"; - } - } - if (nullptr != modname) // we actually got a module name + // 2 - cause the target state to require the module that exported the factory + if (char const* const modname{ factory.moduleName() }; modname) // we actually got a module name { // L.registry._LOADED exists without having registered the 'package' library. lua_getglobal( L, "require"); // DPC proxy metatable require() @@ -309,7 +287,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m if( require_result != LUA_OK) { // failed, return the error message - lua_pushfstring( L, "error while requiring '%s' identified by idfunc(eOP_module): ", modname); + lua_pushfstring( L, "error while requiring '%s' identified by DeepFactory::moduleName: ", modname); lua_insert( L, -2); // DPC proxy metatable prefix error lua_concat( L, 2); // DPC proxy metatable error return lua_tostring( L, -1); @@ -323,7 +301,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m else // no L.registry._LOADED; can this ever happen? { lua_pop( L, 6); // - return "unexpected error while requiring a module identified by idfunc(eOP_module)"; + return "unexpected error while requiring a module identified by DeepFactory::moduleName"; } } else // a module name, but no require() function :-( @@ -334,7 +312,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m } } STACK_CHECK(L, 2); // DPC proxy metatable - ASSERT_L(lua_type(L, -2) == LUA_TUSERDATA); + ASSERT_L(lua_type_as_enum(L, -2) == LuaType::USERDATA); ASSERT_L(lua_istable( L, -1)); lua_setmetatable( L, -2); // DPC proxy @@ -343,7 +321,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m lua_pushvalue( L, -2); // DPC proxy deep proxy lua_rawset( L, -4); // DPC proxy lua_remove( L, -2); // proxy - ASSERT_L(lua_type(L, -1) == LUA_TUSERDATA); + ASSERT_L(lua_type_as_enum(L, -1) == LuaType::USERDATA); STACK_CHECK(L, 0); return nullptr; } @@ -353,56 +331,47 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m /* * Create a deep userdata * -* proxy_ud= deep_userdata( idfunc [, ...] ) -* -* Creates a deep userdata entry of the type defined by 'idfunc'. -* Parameters found on the stack are left as is passed on to the 'idfunc' "new" invocation. -* -* 'idfunc' must fulfill the following features: +* proxy_ud= deep_userdata( [...] ) * -* lightuserdata = idfunc( DeepOp::New [, ...] ) -- creates a new deep data instance -* void = idfunc( DeepOp::Delete, lightuserdata ) -- releases a deep data instance -* tbl = idfunc( DeepOp::Metatable ) -- gives metatable for userdata proxies +* Creates a deep userdata entry of the type defined by the factory. +* Parameters found on the stack are left as is and passed on to DeepFactory::newDeepObjectInternal. * -* Reference counting and true userdata proxying are taken care of for the -* actual data type. +* Reference counting and true userdata proxying are taken care of for the actual data type. * * Types using the deep userdata system (and only those!) can be passed between * separate Lua states via 'luaG_inter_move()'. * -* Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()' +* Returns: 'proxy' userdata for accessing the deep data via 'DeepFactory::toDeep()' */ -int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_) +int DeepFactory::pushDeepUserdata(Dest L, int nuv_) const { STACK_GROW( L, 1); STACK_CHECK_START_REL(L, 0); int const oldtop{ lua_gettop(L) }; - DeepPrelude* const prelude{ static_cast(idfunc(L, DeepOp::New)) }; + DeepPrelude* const prelude{ newDeepObjectInternal(L) }; if (prelude == nullptr) { - return luaL_error( L, "idfunc(DeepOp::New) failed to create deep userdata (out of memory)"); + return luaL_error( L, "DeepFactory::newDeepObjectInternal failed to create deep userdata (out of memory)"); } - if( prelude->magic != DEEP_VERSION) + if( prelude->m_magic != DEEP_VERSION) { // just in case, don't leak the newly allocated deep userdata object - lua_pushlightuserdata( L, prelude); - idfunc( L, DeepOp::Delete); - return luaL_error( L, "Bad idfunc(DeepOp::New): DEEP_VERSION is incorrect, rebuild your implementation with the latest deep implementation"); + deleteDeepObjectInternal(L, prelude); + return luaL_error( L, "Bad Deep Factory: DEEP_VERSION is incorrect, rebuild your implementation with the latest deep implementation"); } - ASSERT_L(prelude->m_refcount.load(std::memory_order_relaxed) == 0); // 'push_deep_proxy' will lift it to 1 - prelude->idfunc = idfunc; + ASSERT_L(prelude->m_refcount.load(std::memory_order_relaxed) == 0); // 'DeepFactory::PushDeepProxy' will lift it to 1 + ASSERT_L(&prelude->m_factory == this); if( lua_gettop( L) - oldtop != 0) { // just in case, don't leak the newly allocated deep userdata object - lua_pushlightuserdata( L, prelude); - idfunc( L, DeepOp::Delete); - return luaL_error( L, "Bad idfunc(DeepOp::New): should not push anything on the stack"); + deleteDeepObjectInternal(L, prelude); + return luaL_error(L, "Bad DeepFactory::newDeepObjectInternal overload: should not push anything on the stack"); } - char const* const errmsg{ push_deep_proxy(L, prelude, nuv_, LookupMode::LaneBody) }; // proxy + char const* const errmsg{ DeepFactory::PushDeepProxy(L, prelude, nuv_, LookupMode::LaneBody) }; // proxy if (errmsg != nullptr) { return luaL_error( L, errmsg); @@ -419,11 +388,11 @@ int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_) * Reference count is not changed, and access to the deep userdata is not * serialized. It is the module's responsibility to prevent conflicting usage. */ -DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) +DeepPrelude* DeepFactory::toDeep(lua_State* L, int index) const { STACK_CHECK_START_REL(L, 0); - // ensure it is actually a deep userdata - if (get_idfunc(L, index, LookupMode::LaneBody) != idfunc) + // ensure it is actually a deep userdata we created + if (get_factory(L, index, LookupMode::LaneBody) != this) { return nullptr; // no metatable, or wrong kind } @@ -444,8 +413,8 @@ DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) */ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { - luaG_IdFunction const idfunc { get_idfunc(L, i, mode_) }; - if (idfunc == nullptr) + DeepFactory* const factory { get_factory(L, i, mode_) }; + if (factory == nullptr) { return false; // not a deep userdata } @@ -463,7 +432,7 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode lua_pop( L, 1); // ... u [uv]* STACK_CHECK( L, nuv); - char const* errmsg{ push_deep_proxy(L2, *lua_tofulluserdata(L, i), nuv, mode_) }; // u + char const* errmsg{ DeepFactory::PushDeepProxy(L2, *lua_tofulluserdata(L, i), nuv, mode_) }; // u // transfer all uservalues of the source in the destination { diff --git a/src/deep.h b/src/deep.h index 7be5c5d..7c0aa6d 100644 --- a/src/deep.h +++ b/src/deep.h @@ -28,34 +28,60 @@ enum class LookupMode FromKeeper // send a function from a keeper state to a lane }; -enum class DeepOp -{ - New, - Delete, - Metatable, - Module, -}; - -using luaG_IdFunction = void*(*)(lua_State* L, DeepOp op_); - // ################################################################################################ // xxh64 of string "DEEP_VERSION_3" generated at https://www.pelock.com/products/hash-calculator -static constexpr UniqueKey DEEP_VERSION{ 0xB2CC0FD9C0AE9674ull }; +static constexpr UniqueKey DEEP_VERSION{ 0xB2CC0FD9C0AE9674ull, "DEEP_VERSION_3" }; // should be used as header for deep userdata // a deep userdata is a full userdata that stores a single pointer to the actual DeepPrelude-derived object struct DeepPrelude { - UniqueKey const magic{ DEEP_VERSION }; - // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the idfunc - luaG_IdFunction idfunc { nullptr }; + UniqueKey const m_magic{ DEEP_VERSION }; + // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the factory + class DeepFactory& m_factory; // data is destroyed when refcount is 0 std::atomic m_refcount{ 0 }; + + DeepPrelude(DeepFactory& factory_) + : m_factory{ factory_ } + { + } }; -[[nodiscard]] char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); -void free_deep_prelude(lua_State* L, DeepPrelude* prelude_); +// external C modules should create a single object implementing that interface for each Deep userdata class they want to expose +class DeepFactory +{ + protected: + + // protected non-virtual destructor: Lanes won't manage the Factory's lifetime + DeepFactory() = default; + ~DeepFactory() = default; + + public: + + // non-copyable, non-movable + DeepFactory(DeepFactory const&) = delete; + DeepFactory(DeepFactory const&&) = delete; + DeepFactory& operator=(DeepFactory const&) = delete; + DeepFactory& operator=(DeepFactory const&&) = delete; + + private: + + // NVI: private overrides + virtual DeepPrelude* newDeepObjectInternal(lua_State* L) const = 0; + virtual void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const = 0; + virtual void createMetatable(lua_State* L) const = 0; + virtual char const* moduleName() const = 0; + + public: + + // NVI: public interface + int pushDeepUserdata(Dest L, int nuv_) const; + DeepPrelude* toDeep(lua_State* L, int index) const; + static void DeleteDeepObject(lua_State* L, DeepPrelude* o_); + static char const* PushDeepProxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); +}; -LANES_API [[nodiscard]] int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_); -LANES_API [[nodiscard]] DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index); +//LANES_API [[nodiscard]] int luaG_newdeepuserdata(Dest L, DeepFactory& factory_, int nuv_); +//LANES_API [[nodiscard]] DeepPrelude* luaG_todeep(lua_State* L, DeepFactory &factory_, int index); diff --git a/src/keeper.cpp b/src/keeper.cpp index 61321e1..36733e3 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -833,6 +833,8 @@ KeeperCallResult keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_ KeeperCallResult result; int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 }; int const top_K{ lua_gettop(K) }; + // if we didn't do anything wrong, the keeper stack should be clean + ASSERT_L(lua_gettop(K) == 0); STACK_GROW(K, 2); diff --git a/src/keeper.h b/src/keeper.h index 7ec8b15..c1ee244 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -34,7 +34,7 @@ struct Keepers static constexpr uintptr_t KEEPER_MAGIC_SHIFT{ 3 }; // crc64/we of string "NIL_SENTINEL" generated at http://www.nitrxgen.net/hashgen/ -static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull, "internal nil sentinel" }; +static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull, "linda.null" }; void init_keepers(Universe* U, lua_State* L); void close_keepers(Universe* U); diff --git a/src/lanes.cpp b/src/lanes.cpp index 1f795cc..d9262cf 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -544,14 +544,12 @@ static void selfdestruct_add(Lane* lane_) } } - // necessary so that calling free_deep_prelude doesn't crash because linda_id expects a linda lightuserdata at absolute slot 1 - lua_settop(L, 0); // no need to mutex-protect this as all threads in the universe are gone at that point if (U->timer_deep != nullptr) // test ins case some early internal error prevented Lanes from creating the deep timer { [[maybe_unused]] int const prev_ref_count{ U->timer_deep->m_refcount.fetch_sub(1, std::memory_order_relaxed) }; ASSERT_L(prev_ref_count == 1); // this should be the last reference - free_deep_prelude(L, U->timer_deep); + DeepFactory::DeleteDeepObject(L, U->timer_deep); U->timer_deep = nullptr; } @@ -1840,7 +1838,7 @@ LUAG_FUNC(configure) STACK_CHECK(L, 2); { - char const* errmsg{ push_deep_proxy(Dest{ L }, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep + char const* errmsg{ DeepFactory::PushDeepProxy(Dest{ L }, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep if (errmsg != nullptr) { return luaL_error(L, errmsg); diff --git a/src/linda.cpp b/src/linda.cpp index dbd6b21..103f4ed 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -45,6 +45,18 @@ THE SOFTWARE. // xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull, "linda.batched" }; +class LindaFactory : public DeepFactory +{ + private: + + DeepPrelude* newDeepObjectInternal(lua_State* L) const override; + void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const override; + void createMetatable(lua_State* L) const override; + char const* moduleName() const override; +}; +// I'm not totally happy with having a global variable. But since it's stateless, it will do for the time being. +static LindaFactory g_LindaFactory; + /* * Actual data is kept within a keeper state, which is hashed by the 'Linda' * pointer (which is same to all userdatas pointing to it). @@ -82,7 +94,8 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header static void operator delete(void* p_) { static_cast(p_)->U->internal_allocator.free(p_, sizeof(Linda)); } Linda(Universe* U_, uintptr_t group_, char const* name_, size_t len_) - : U{ U_ } + : DeepPrelude{ g_LindaFactory } + , U{ U_ } , group{ group_ << KEEPER_MAGIC_SHIFT } { setName(name_, len_); @@ -141,12 +154,13 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header return nullptr; } }; -[[nodiscard]] static void* linda_id(lua_State*, DeepOp); -template +// ################################################################################################# + +template [[nodiscard]] static inline Linda* ToLinda(lua_State* L, int idx_) { - Linda* const linda{ static_cast(luaG_todeep(L, linda_id, idx_)) }; + Linda* const linda{ static_cast(g_LindaFactory.toDeep(L, idx_)) }; if constexpr (!OPT) { luaL_argcheck(L, linda != nullptr, idx_, "expecting a linda object"); @@ -171,7 +185,6 @@ static void check_key_types(lua_State* L, int start_, int end_) case LuaType::LIGHTUSERDATA: { - // NIL_SENTINEL isn't publicly exposed, but it doesn't hurt to check static constexpr std::array, 3> to_check{ BATCH_SENTINEL, CANCEL_ERROR, NIL_SENTINEL }; for (UniqueKey const& key : to_check) { @@ -199,12 +212,16 @@ LUAG_FUNC(linda_protected_call) lua_State* const KL{ K ? K->L : nullptr }; if (KL == nullptr) return 0; + // if we didn't do anything wrong, the keeper stack should be clean + ASSERT_L(lua_gettop(KL) == 0); // retrieve the actual function to be called and move it before the arguments lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); // do a protected call int const rc{ lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0) }; + // whatever happens, the keeper state stack must be empty when we are done + lua_settop(KL, 0); // release the keeper keeper_release(K); @@ -838,179 +855,152 @@ LUAG_FUNC(linda_towatch) // ################################################################################################# -/* -* Identity function of a shared userdata object. -* -* lightuserdata= linda_id( "new" [, ...] ) -* = linda_id( "delete", lightuserdata ) -* -* Creation and cleanup of actual 'deep' objects. 'luaG_...' will wrap them into -* regular userdata proxies, per each state using the deep data. -* -* tbl= linda_id( "metatable" ) -* -* Returns a metatable for the proxy objects ('__gc' method not needed; will -* be added by 'luaG_...') -* -* string= linda_id( "module") -* -* Returns the name of the module that a state should require -* in order to keep a handle on the shared library that exported the idfunc -* -* = linda_id( str, ... ) -* -* For any other strings, the ID function must not react at all. This allows -* future extensions of the system. -*/ -[[nodiscard]] static void* linda_id(lua_State* L, DeepOp op_) +DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* L) const { - switch( op_) + size_t name_len = 0; + char const* linda_name = nullptr; + unsigned long linda_group = 0; + // should have a string and/or a number of the stack as parameters (name and group) + switch (lua_gettop(L)) { - case DeepOp::New: + default: // 0 + break; + + case 1: // 1 parameter, either a name or a group + if (lua_type(L, -1) == LUA_TSTRING) { - size_t name_len = 0; - char const* linda_name = nullptr; - unsigned long 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; + linda_name = lua_tolstring(L, -1, &name_len); + } + else + { + linda_group = (unsigned long) lua_tointeger(L, -1); + } + break; - case 1: // 1 parameter, either a name or a group - if (lua_type(L, -1) == LUA_TSTRING) - { - linda_name = lua_tolstring(L, -1, &name_len); - } - else - { - linda_group = (unsigned long) lua_tointeger(L, -1); - } - break; + case 2: // 2 parameters, a name and group, in that order + linda_name = lua_tolstring(L, -2, &name_len); + linda_group = (unsigned long) lua_tointeger(L, -1); + break; + } - case 2: // 2 parameters, a name and group, in that order - linda_name = lua_tolstring(L, -2, &name_len); - linda_group = (unsigned long) lua_tointeger(L, -1); - break; - } + /* The deep data is allocated separately of Lua stack; we might no + * longer be around when last reference to it is being released. + * One can use any memory allocation scheme. + * just don't use L's allocF because we don't know which state will get the honor of GCing the linda + */ + Universe* const U{ universe_get(L) }; + Linda* linda{ new (U) Linda{ U, linda_group, linda_name, name_len } }; + return linda; +} - /* The deep data is allocated separately of Lua stack; we might no - * longer be around when last reference to it is being released. - * One can use any memory allocation scheme. - * just don't use L's allocF because we don't know which state will get the honor of GCing the linda - */ - Universe* const U{ universe_get(L) }; - Linda* linda{ new (U) Linda{ U, linda_group, linda_name, name_len } }; - return linda; - } +// ################################################################################################# - case DeepOp::Delete: +void LindaFactory::deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const +{ + Linda* const linda{ static_cast(o_) }; + ASSERT_L(linda); + Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; + // if collected after the universe, keepers are already destroyed, and there is nothing to clear + if (myK) + { + // if collected from my own keeper, we can't acquire/release it + // because we are already inside a protected area, and trying to do so would deadlock! + bool const need_acquire_release{ myK->L != L }; + // Clean associated structures in the keeper state. + Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; + // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... + [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; + ASSERT_L(result.has_value() && result.value() == 0); + if (need_acquire_release) { - Linda* const linda{ lua_tolightuserdata(L, 1) }; - ASSERT_L(linda); - Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; - // if collected after the universe, keepers are already destroyed, and there is nothing to clear - if (myK) - { - // if collected from my own keeper, we can't acquire/release it - // because we are already inside a protected area, and trying to do so would deadlock! - bool const need_acquire_release{ myK->L != L }; - // Clean associated structures in the keeper state. - Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; - // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... - [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; - ASSERT_L(result.has_value() && result.value() == 0); - if (need_acquire_release) - { - keeper_release(K); - } - } - - delete linda; // operator delete overload ensures things go as expected - return nullptr; + keeper_release(K); } + } - case DeepOp::Metatable: - { - STACK_CHECK_START_REL(L, 0); - lua_newtable(L); - // metatable is its own index - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); + delete linda; // operator delete overload ensures things go as expected +} - // protect metatable from external access - lua_pushliteral(L, "Linda"); - lua_setfield(L, -2, "__metatable"); +// ################################################################################################# - lua_pushcfunction(L, LG_linda_tostring); - lua_setfield(L, -2, "__tostring"); +void LindaFactory::createMetatable(lua_State* L) const +{ + STACK_CHECK_START_REL(L, 0); + lua_newtable(L); + // metatable is its own index + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); - // Decoda __towatch support - lua_pushcfunction(L, LG_linda_towatch); - lua_setfield(L, -2, "__towatch"); + // protect metatable from external access + lua_pushliteral(L, "Linda"); + lua_setfield(L, -2, "__metatable"); - lua_pushcfunction(L, LG_linda_concat); - lua_setfield(L, -2, "__concat"); + lua_pushcfunction(L, LG_linda_tostring); + lua_setfield(L, -2, "__tostring"); - // protected calls, to ensure associated keeper is always released even in case of error - // all function are the protected call wrapper, where the actual operation is provided as upvalue - // note that this kind of thing can break function lookup as we use the function pointer here and there - // TODO: change that and use different functions! + // Decoda __towatch support + lua_pushcfunction(L, LG_linda_towatch); + lua_setfield(L, -2, "__towatch"); - lua_pushcfunction(L, LG_linda_send); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "send"); + lua_pushcfunction(L, LG_linda_concat); + lua_setfield(L, -2, "__concat"); - lua_pushcfunction(L, LG_linda_receive); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "receive"); + // protected calls, to ensure associated keeper is always released even in case of error + // all function are the protected call wrapper, where the actual operation is provided as upvalue + // note that this kind of thing can break function lookup as we use the function pointer here and there + // TODO: change that and use different functions! - lua_pushcfunction(L, LG_linda_limit); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "limit"); + lua_pushcfunction(L, LG_linda_send); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "send"); - lua_pushcfunction(L, LG_linda_set); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "set"); + lua_pushcfunction(L, LG_linda_receive); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "receive"); - lua_pushcfunction(L, LG_linda_count); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "count"); + lua_pushcfunction(L, LG_linda_limit); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "limit"); - lua_pushcfunction(L, LG_linda_get); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "get"); + lua_pushcfunction(L, LG_linda_set); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "set"); - lua_pushcfunction(L, LG_linda_cancel); - lua_setfield(L, -2, "cancel"); + lua_pushcfunction(L, LG_linda_count); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "count"); - lua_pushcfunction(L, LG_linda_deep); - lua_setfield(L, -2, "deep"); + lua_pushcfunction(L, LG_linda_get); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "get"); - lua_pushcfunction(L, LG_linda_dump); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "dump"); + lua_pushcfunction(L, LG_linda_cancel); + lua_setfield(L, -2, "cancel"); - // some constants - BATCH_SENTINEL.pushKey(L); - lua_setfield(L, -2, "batched"); + lua_pushcfunction(L, LG_linda_deep); + lua_setfield(L, -2, "deep"); - NIL_SENTINEL.pushKey(L); - lua_setfield(L, -2, "null"); + lua_pushcfunction(L, LG_linda_dump); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "dump"); - STACK_CHECK(L, 1); - return nullptr; - } + // some constants + BATCH_SENTINEL.pushKey(L); + lua_setfield(L, -2, "batched"); - case DeepOp::Module: - // linda is a special case because we know lanes must be loaded from the main lua state - // to be able to ever get here, so we know it will remain loaded as long a the main state is around - // in other words, forever. - default: - { - return nullptr; - } - } + NIL_SENTINEL.pushKey(L); + lua_setfield(L, -2, "null"); + + STACK_CHECK(L, 1); +} + +// ################################################################################################# + +char const* LindaFactory::moduleName() const +{ + // linda is a special case because we know lanes must be loaded from the main lua state + // to be able to ever get here, so we know it will remain loaded as long a the main state is around + // in other words, forever. + return nullptr; } // ################################################################################################# @@ -1034,5 +1024,5 @@ LUAG_FUNC(linda) luaL_checktype(L, 1, LUA_TSTRING); luaL_checktype(L, 2, LUA_TNUMBER); } - return luaG_newdeepuserdata(Dest{ L }, linda_id, 0); + return g_LindaFactory.pushDeepUserdata(Dest{ L }, 0); } -- cgit v1.2.3-55-g6feb