From 2d58789d66f1b63ad63304584c7ac43284b540b8 Mon Sep 17 00:00:00 2001 From: Mattes D Date: Wed, 6 Jul 2016 20:52:04 +0200 Subject: Converted cLuaState::cTableRef to use cTrackedRef. This makes the table-based callbacks resistent to LuaState unloads and safer to use. --- src/Bindings/LuaState.h | 201 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 139 insertions(+), 62 deletions(-) (limited to 'src/Bindings/LuaState.h') diff --git a/src/Bindings/LuaState.h b/src/Bindings/LuaState.h index c34feca9d..bc88fbf1b 100644 --- a/src/Bindings/LuaState.h +++ b/src/Bindings/LuaState.h @@ -120,46 +120,84 @@ public: } ; - /** Used for calling functions stored in a reference-stored table */ - class cTableRef + /** Represents a reference to a Lua object that has a tracked lifetime - + - when the Lua state to which it belongs is closed, the object is kept alive, but invalidated. + Is thread-safe and unload-safe. + To receive the cTrackedRef 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 cTrackedRef { - int m_TableRef; - const char * m_FnName; + friend class ::cLuaState; public: - cTableRef(int a_TableRef, const char * a_FnName) : - m_TableRef(a_TableRef), - m_FnName(a_FnName) - { - } + /** Creates an unbound ref instance. */ + cTrackedRef(void); - cTableRef(const cRef & a_TableRef, const char * a_FnName) : - m_TableRef(static_cast(a_TableRef)), - m_FnName(a_FnName) + ~cTrackedRef() { + Clear(); } - int GetTableRef(void) const { return m_TableRef; } - const char * GetFnName(void) const { return m_FnName; } - } ; + /** Set the contained reference to the object at the specified Lua state's stack position. + If another reference has been previously contained, it is freed first. */ + bool RefStack(cLuaState & a_LuaState, int a_StackPos); + + /** Frees the contained reference, if any. */ + void Clear(void); + + /** Returns true if the contained reference is valid. */ + bool IsValid(void); + + /** Returns true if the reference resides in the specified Lua state. + Internally, compares the reference's canon Lua state. */ + bool IsSameLuaState(cLuaState & a_LuaState); + + 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); + + /** Returns the internal reference. + Only to be used when the cLuaState's CS is held and the cLuaState is known to be valid. */ + cRef & GetRef() { return m_Ref; } + + /** This class cannot be copied, because it is tracked in the LuaState by-ptr. + Use a smart pointer for a copyable object. */ + cTrackedRef(const cTrackedRef &) = delete; - /** Represents a callback to Lua that C++ code can call. + /** This class cannot be moved, because it is tracked in the LuaState by-ptr. + Use a smart pointer for a copyable object. */ + cTrackedRef(cTrackedRef &&) = delete; + }; + typedef UniquePtr cTrackedRefPtr; + typedef SharedPtr cTrackedRefSharedPtr; + + + /** Represents a stored 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() 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 + class cCallback: + public cTrackedRef { + typedef cTrackedRef Super; + public: - /** Creates an unbound callback instance. */ - cCallback(void); - ~cCallback() - { - Clear(); - } + cCallback(void) {} /** Calls the Lua callback, if still available. Returns true if callback has been called. @@ -181,32 +219,11 @@ public: } /** 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. */ + If a callback has been previously contained, it is unreferenced first. + Returns true on success, false on failure (not a function at the specified stack pos). */ bool RefStack(cLuaState & a_LuaState, int a_StackPos); - /** Frees the contained callback, if any. */ - void Clear(void); - - /** 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; - - /** 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. Use cCallbackPtr for a copyable object. */ @@ -220,6 +237,47 @@ public: typedef SharedPtr cCallbackSharedPtr; + /** Represents a stored Lua table with callback functions that C++ code can call. + Is thread-safe and unload-safe. + When Lua state is unloaded, the CallFn() will return failure instead of calling into non-existent code. + 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 cTableRef: + public cTrackedRef + { + typedef cTrackedRef Super; + public: + cTableRef(void) {} + + /** Calls the Lua function stored under the specified name in the referenced table, if still available. + Returns true if callback has been called. + Returns false if the Lua state isn't valid anymore, or the function doesn't exist. */ + template + bool CallTableFn(const char * a_FnName, Args &&... args) + { + auto cs = m_CS; + if (cs == nullptr) + { + return false; + } + cCSLock Lock(*cs); + if (!m_Ref.IsValid()) + { + return false; + } + return cLuaState(m_Ref.GetLuaState()).CallTableFn(m_Ref, a_FnName, std::forward(args)...); + } + + /** Set the contained reference to the table in the specified Lua state's stack position. + If another table has been previously contained, it is unreferenced first. + Returns true on success, false on failure (not a table at the specified stack pos). */ + bool RefStack(cLuaState & a_LuaState, int a_StackPos); + }; + typedef UniquePtr cTableRefPtr; + + /** A dummy class that's used only to delimit function args from return values for cLuaState::Call() */ class cRet { @@ -381,8 +439,13 @@ public: 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, cTableRef & a_TableRef); + bool GetStackValue(int a_StackPos, cTableRefPtr & a_TableRef); bool GetStackValue(int a_StackPos, cPluginManager::CommandResult & a_Result); bool GetStackValue(int a_StackPos, cRef & a_Ref); + bool GetStackValue(int a_StackPos, cTrackedRef & a_Ref); + bool GetStackValue(int a_StackPos, cTrackedRefPtr & a_Ref); + bool GetStackValue(int a_StackPos, cTrackedRefSharedPtr & 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); @@ -583,15 +646,30 @@ 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; + /** The tracked references. + The cLuaState will invalidate all of these when it is about to be closed. + Protected against multithreaded access by m_CSTrackedRefs. */ + std::vector m_TrackedRefs; - /** Protects m_TrackedTallbacks against multithreaded access. */ - cCriticalSection m_CSTrackedCallbacks; + /** Protects m_TrackedRefs against multithreaded access. */ + cCriticalSection m_CSTrackedRefs; + /** Call the Lua function specified by name in the table stored as a reference. + Returns true if call succeeded, false if there was an error (not a table ref, function name not found). + A special param of cRet & signifies the end of param list and the start of return values. + Example call: CallTableFn(TableRef, "FnName", Param1, Param2, Param3, cLuaState::Return, Ret1, Ret2) */ + template + bool CallTableFn(const cRef & a_TableRef, const char * a_FnName, Args &&... args) + { + if (!PushFunction(a_TableRef, a_FnName)) + { + // Pushing the function failed + return false; + } + return PushCallPop(std::forward(args)...); + } + /** 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) @@ -646,10 +724,9 @@ protected: */ 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 - */ - bool PushFunction(const cTableRef & a_TableRef); + /** Pushes a function that is stored under the specified name in a table that has been saved as a reference. + Returns true if successful. */ + bool PushFunction(const cRef & a_TableRef, const char * a_FnName); /** Pushes a usertype of the specified class type onto the stack */ // void PushUserType(void * a_Object, const char * a_Type); @@ -667,13 +744,13 @@ 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); + /** Adds the specified reference to tracking. + The reference will be invalidated when this Lua state is about to be closed. */ + void TrackRef(cTrackedRef & a_Ref); - /** 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); + /** Removes the specified reference from tracking. + The reference will no longer be invalidated when this Lua state is about to be closed. */ + void UntrackRef(cTrackedRef & a_Ref); } ; -- cgit v1.2.3