From 1f75d4522210c4774f3de34ddea084cfa764e9e3 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 2 Mar 2016 11:34:39 +0100 Subject: Added cLuaState::cCallback for representing (resettable) Lua callbacks. --- src/Bindings/LuaState.h | 89 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 3 deletions(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index b795a80d4..bce08f0fe 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -80,8 +80,11 @@ public: /** Allows to use this class wherever an int (i. e. ref) is to be used */ explicit operator int(void) const { return m_Ref; } + /** Returns the Lua state associated with the value. */ + lua_State * GetLuaState(void) { return m_LuaState; } + protected: - cLuaState * m_LuaState; + lua_State * m_LuaState; int m_Ref; // Remove the copy-constructor: @@ -112,6 +115,69 @@ public: } ; + /** Represents a callback to Lua that C++ code can call. + Is thread-safe and unload-safe. + When the Lua state is unloaded, the callback returns an error instead of calling into non-existent code. + To receive the callback instance from the Lua side, use RefStack() or (better) cLuaState::GetStackValue(). + Note that instances of this class are tracked in the canon LuaState instance, so that they can be invalidated + when the LuaState is unloaded; due to multithreading issues they can only be tracked by-ptr, which has + an unfortunate effect of disabling the copy and move constructors. */ + class cCallback + { + public: + /** Creates an unbound callback instance. */ + cCallback(void) = default; + + ~cCallback() + { + Clear(); + } + + /** Calls the Lua callback, if still available. + Returns true if callback has been called. + Returns false if the Lua state isn't valid anymore. */ + template + bool Call(Args &&... args) + { + cCSLock Lock(m_CS); + if (!m_Ref.IsValid()) + { + return false; + } + cLuaState(m_Ref.GetLuaState()).Call(m_Ref, std::forward(args)...); + return true; + } + + /** Set the contained callback to the function in the specified Lua state's stack position. + If a callback has been previously contained, it is freed first. */ + bool RefStack(cLuaState & a_LuaState, int a_StackPos); + + /** Frees the contained callback, if any. */ + void Clear(void); + + protected: + friend class cLuaState; + + /** The mutex protecting m_Ref against multithreaded access */ + cCriticalSection m_CS; + + /** Reference to the Lua callback */ + cRef m_Ref; + + + /** Invalidates the callback, without untracking it from the cLuaState. + Called only from cLuaState when closing the Lua state. */ + void Invalidate(void); + + /** This class cannot be copied, because it is tracked in the LuaState by-ptr. */ + cCallback(const cCallback &) = delete; + + /** This class cannot be moved, because it is tracked in the LuaState by-ptr. */ + cCallback(cCallback &&) = delete; + }; + typedef SharedPtr cCallbackPtr; + + /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ class cRet { @@ -268,6 +334,7 @@ public: bool GetStackValue(int a_StackPos, bool & a_Value); bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result); bool GetStackValue(int a_StackPos, cRef & a_Ref); + bool GetStackValue(int a_StackPos, cCallback & a_Ref); bool GetStackValue(int a_StackPos, double & a_Value); bool GetStackValue(int a_StackPos, eBlockFace & a_Value); bool GetStackValue(int a_StackPos, eWeather & a_Value); @@ -453,8 +520,7 @@ protected: bool m_IsOwned; /** The subsystem name is used for reporting errors to the console, it is either "plugin %s" or "LuaScript" - whatever is given to the constructor - */ + whatever is given to the constructor. */ AString m_SubsystemName; /** Name of the currently pushed function (for the Push / Call chain) */ @@ -463,6 +529,15 @@ protected: /** Number of arguments currently pushed (for the Push / Call chain) */ int m_NumCurrentFunctionArgs; + /** The tracked callbacks. + This object will invalidate all of these when it is about to be closed. + Protected against multithreaded access by m_CSTrackedCallbacks. */ + std::vector m_TrackedCallbacks; + + /** Protects m_TrackedTallbacks against multithreaded access. */ + cCriticalSection m_CSTrackedCallbacks; + + /** Variadic template terminator: If there's nothing more to push / pop, just call the function. Note that there are no return values either, because those are prefixed by a cRet value, so the arg list is never empty. */ bool PushCallPop(void) @@ -545,6 +620,14 @@ protected: /** Tries to break into the MobDebug debugger, if it is installed. */ static int BreakIntoDebugger(lua_State * a_LuaState); + + /** Adds the specified callback to tracking. + The callback will be invalidated when this Lua state is about to be closed. */ + void TrackCallback(cCallback & a_Callback); + + /** Removes the specified callback from tracking. + The callback will no longer be invalidated when this Lua state is about to be closed. */ + void UntrackCallback(cCallback & a_Callback); } ; -- cgit v1.2.3 From 4489a89fdec9f4a507400150af34623899b64f46 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 2 Mar 2016 10:12:43 +0100 Subject: Changed plugin hook registrations to use cLuaState::cCallback. --- src/Bindings/LuaState.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index bce08f0fe..a3f15dcc9 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -155,6 +155,9 @@ public: /** Frees the contained callback, if any. */ void Clear(void); + /** Returns true if the contained callback is valid. */ + bool IsValid(void); + protected: friend class cLuaState; @@ -332,9 +335,10 @@ public: // Enum values are checked for their allowed values and fail if the value is not assigned. bool GetStackValue(int a_StackPos, AString & a_Value); bool GetStackValue(int a_StackPos, bool & a_Value); + bool GetStackValue(int a_StackPos, cCallback & a_Callback); + bool GetStackValue(int a_StackPos, cCallbackPtr & a_Callback); bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result); bool GetStackValue(int a_StackPos, cRef & a_Ref); - bool GetStackValue(int a_StackPos, cCallback & a_Ref); bool GetStackValue(int a_StackPos, double & a_Value); bool GetStackValue(int a_StackPos, eBlockFace & a_Value); bool GetStackValue(int a_StackPos, eWeather & a_Value); -- cgit v1.2.3 From fb4c3fc4d9325c28b8640f0e717e5e320334a7c6 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Fri, 10 Jun 2016 21:30:07 +0200 Subject: Changed cLuaWindow callbacks to use cLuaState::cCallback. --- src/Bindings/LuaState.h | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index a3f15dcc9..faa43b6d6 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -83,6 +83,15 @@ public: /** Returns the Lua state associated with the value. */ lua_State * GetLuaState(void) { return m_LuaState; } + /** Creates a Lua reference to the specified object instance in the specified Lua state. + This is useful to make anti-GC references for objects that were created by Lua and need to stay alive longer than Lua GC would normally guarantee. */ + template void CreateFromObject(cLuaState & a_LuaState, T && a_Object) + { + a_LuaState.Push(std::forward(a_Object)); + RefStack(a_LuaState, -1); + a_LuaState.Pop(); + } + protected: lua_State * m_LuaState; int m_Ref; @@ -158,6 +167,10 @@ public: /** Returns true if the contained callback is valid. */ bool IsValid(void); + /** Returns true if the callback resides in the specified Lua state. + Internally, compares the callback's canon Lua state. */ + bool IsSameLuaState(cLuaState & a_LuaState); + protected: friend class cLuaState; @@ -330,6 +343,9 @@ public: void Push(const UInt32 a_Value); void Push(std::chrono::milliseconds a_time); + /** Pops the specified number of values off the top of the Lua stack. */ + void Pop(int a_NumValuesToPop = 1); + // GetStackValue() retrieves the value at a_StackPos, if it is a valid type. If not, a_Value is unchanged. // Returns whether value was changed // Enum values are checked for their allowed values and fail if the value is not assigned. @@ -511,10 +527,14 @@ public: void ToString(int a_StackPos, AString & a_String); /** Logs all the elements' types on the API stack, with an optional header for the listing. */ - void LogStack(const char * a_Header = nullptr); + void LogStackValues(const char * a_Header = nullptr); /** Logs all the elements' types on the API stack, with an optional header for the listing. */ - static void LogStack(lua_State * a_LuaState, const char * a_Header = nullptr); + static void LogStackValues(lua_State * a_LuaState, const char * a_Header = nullptr); + + /** Returns the canon Lua state (the primary cLuaState instance that was used to create, rather than attach, the lua_State structure). + Returns nullptr if the canon Lua state cannot be queried. */ + cLuaState * QueryCanonLuaState(void); protected: -- cgit v1.2.3 From 24853397ef4648155d886b112e00c3e2c3d1e900 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 12 Jun 2016 16:53:24 +0200 Subject: LuaState: Implemented proper locking for cCallback. --- src/Bindings/LuaState.h | 57 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 14 deletions(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index faa43b6d6..88313f9fb 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -7,7 +7,7 @@ The contained lua_State can be either owned or attached. Owned lua_State is created by calling Create() and the cLuaState automatically closes the state Or, lua_State can be attached by calling Attach(), the cLuaState doesn't close such a state -Attaching a state will automatically close an owned state. +If owning a state, trying to attach a state will automatically close the previously owned state. Calling a Lua function is done internally by pushing the function using PushFunction(), then pushing the arguments and finally executing CallFunction(). cLuaState automatically keeps track of the number of @@ -16,8 +16,12 @@ the stack using GetStackValue(). All of this is wrapped in a templated function which is generated automatically by gen_LuaState_Call.lua script file into the LuaState_Call.inc file. Reference management is provided by the cLuaState::cRef class. This is used when you need to hold a reference to -any Lua object across several function calls; usually this is used for callbacks. The class is RAII-like, with -automatic resource management. +any Lua object across several function calls. The class is RAII-like, with automatic resource management. + +Callbacks management is provided by the cLuaState::cCallback class. Use a GetStackValue() with cCallbackPtr +parameter to store the callback, and then at any time you can use the cCallback's Call() templated function +to call the callback. The callback management takes care of cLuaState being destroyed - the cCallback object +stays valid but doesn't call into Lua code anymore, returning false for "failure" instead. */ @@ -39,6 +43,7 @@ extern "C" class cLuaServerHandle; class cLuaTCPLink; class cLuaUDPEndpoint; +class cPluginLua; @@ -49,6 +54,20 @@ class cLuaState { public: + /** Provides a RAII-style locking for the LuaState. + Used mainly by the cPluginLua internals to provide the actual locking for interface operations, such as callbacks. */ + class cLock + { + public: + cLock(cLuaState & a_LuaState): + m_Lock(a_LuaState.m_CS) + { + } + protected: + cCSLock m_Lock; + }; + + /** Used for storing references to object in the global registry. Can be bound (contains a reference) or unbound (doesn't contain reference). The reference can also be reset by calling RefStack(). */ @@ -127,15 +146,15 @@ public: /** Represents a callback to Lua that C++ code can call. Is thread-safe and unload-safe. When the Lua state is unloaded, the callback returns an error instead of calling into non-existent code. - To receive the callback instance from the Lua side, use RefStack() or (better) cLuaState::GetStackValue(). - Note that instances of this class are tracked in the canon LuaState instance, so that they can be invalidated - when the LuaState is unloaded; due to multithreading issues they can only be tracked by-ptr, which has - an unfortunate effect of disabling the copy and move constructors. */ + To receive the callback instance from the Lua side, use RefStack() or (better) cLuaState::GetStackValue() + with a cCallbackPtr. Note that instances of this class are tracked in the canon LuaState instance, so that + they can be invalidated when the LuaState is unloaded; due to multithreading issues they can only be tracked + by-ptr, which has an unfortunate effect of disabling the copy and move constructors. */ class cCallback { public: /** Creates an unbound callback instance. */ - cCallback(void) = default; + cCallback(void); ~cCallback() { @@ -148,13 +167,17 @@ public: template bool Call(Args &&... args) { - cCSLock Lock(m_CS); + auto cs = m_CS; + if (cs == nullptr) + { + return false; + } + cCSLock Lock(*cs); if (!m_Ref.IsValid()) { return false; } - cLuaState(m_Ref.GetLuaState()).Call(m_Ref, std::forward(args)...); - return true; + return cLuaState(m_Ref.GetLuaState()).Call(m_Ref, std::forward(args)...); } /** Set the contained callback to the function in the specified Lua state's stack position. @@ -175,7 +198,7 @@ public: friend class cLuaState; /** The mutex protecting m_Ref against multithreaded access */ - cCriticalSection m_CS; + cCriticalSection * m_CS; /** Reference to the Lua callback */ cRef m_Ref; @@ -185,10 +208,12 @@ public: Called only from cLuaState when closing the Lua state. */ void Invalidate(void); - /** This class cannot be copied, because it is tracked in the LuaState by-ptr. */ + /** This class cannot be copied, because it is tracked in the LuaState by-ptr. + Use cCallbackPtr for a copyable object. */ cCallback(const cCallback &) = delete; - /** This class cannot be moved, because it is tracked in the LuaState by-ptr. */ + /** This class cannot be moved, because it is tracked in the LuaState by-ptr. + Use cCallbackPtr for a copyable object. */ cCallback(cCallback &&) = delete; }; typedef SharedPtr cCallbackPtr; @@ -250,6 +275,8 @@ public: protected: lua_State * m_LuaState; + /** Used for debugging, Lua state's stack size is checked against this number to make sure + it is the same as when the value was pushed. */ int m_StackLen; // Remove the copy-constructor: @@ -538,6 +565,8 @@ public: protected: + cCriticalSection m_CS; + lua_State * m_LuaState; /** If true, the state is owned by this object and will be auto-Closed. False => attached state */ -- cgit v1.2.3 From 7a6670d1d110be96ed73ccab4f33c69e4a01f28d Mon Sep 17 00:00:00 2001 From: Mattes D Date: Sun, 12 Jun 2016 18:24:01 +0200 Subject: Removed dead code related to callbacks. --- src/Bindings/LuaState.h | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index 88313f9fb..d3acb2797 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -640,18 +640,10 @@ protected: */ bool PushFunction(const char * a_FunctionName); - /** Pushes a function that has been saved into the global registry, identified by a_FnRef. - Returns true if successful. Logs a warning on failure - */ - bool PushFunction(int a_FnRef); - /** Pushes a function that has been saved as a reference. Returns true if successful. Logs a warning on failure */ - bool PushFunction(const cRef & a_FnRef) - { - return PushFunction(static_cast(a_FnRef)); - } + bool PushFunction(const cRef & a_FnRef); /** Pushes a function that is stored in a referenced table by name Returns true if successful. Logs a warning on failure -- cgit v1.2.3 From bf88312a1664311968736b4ba7ce1458c8b0954e Mon Sep 17 00:00:00 2001 From: Mattes D Date: Mon, 27 Jun 2016 20:49:59 +0200 Subject: Converted cLuaState::cCallbackPtr into a UniquePtr. --- src/Bindings/LuaState.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index d3acb2797..106d8a783 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -216,7 +216,8 @@ public: Use cCallbackPtr for a copyable object. */ cCallback(cCallback &&) = delete; }; - typedef SharedPtr cCallbackPtr; + typedef UniquePtr cCallbackPtr; + typedef SharedPtr cCallbackSharedPtr; /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ @@ -380,6 +381,7 @@ public: bool GetStackValue(int a_StackPos, bool & a_Value); bool GetStackValue(int a_StackPos, cCallback & a_Callback); bool GetStackValue(int a_StackPos, cCallbackPtr & a_Callback); + bool GetStackValue(int a_StackPos, cCallbackSharedPtr & a_Callback); bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result); bool GetStackValue(int a_StackPos, cRef & a_Ref); bool GetStackValue(int a_StackPos, double & a_Value); -- cgit v1.2.3