From 3490d21bcdf291d4435bc40ce8851959eed30bf7 Mon Sep 17 00:00:00 2001 From: Patman64 Date: Sun, 8 Mar 2015 18:06:10 -0400 Subject: [PATCH] Make the register functions optionally return a cancel callback. It's now possible to have complete flexibility with cancelling bindings, i.e. it's possible to re-implement all of the other ways to cancel bindings using the callback. This sytem has been designed with complete safety in mind: - It is safe to call the callback more than once (does nothing after the first call). - The callback does not expire, and can be stored for as long as necessary. - If a callback is returned, there is no other way to cancel the binding (again, due to safety). --- ElunaBinding.h | 232 ++++++++++++++++++++++++++++++++--- ElunaUtility.h | 4 +- GlobalMethods.h | 315 +++++++++++++++++++++++++++++++++++++++++++----- LuaEngine.cpp | 197 ++++++++++++++++++++++++++---- LuaEngine.h | 2 +- 5 files changed, 673 insertions(+), 77 deletions(-) diff --git a/ElunaBinding.h b/ElunaBinding.h index ab2e667..85f3bd2 100644 --- a/ElunaBinding.h +++ b/ElunaBinding.h @@ -30,19 +30,24 @@ public: int functionReference; bool isTemporary; uint32 remainingShots; + int cancelCallbackRef; // Reference to a callback that will cancel this binding, or 0. Eluna& E; - Binding(Eluna& _E, int funcRef, uint32 shots) : + Binding(Eluna& _E, int funcRef, uint32 shots, int cancelCallbackRef) : functionReference(funcRef), - isTemporary(shots != 0), + isTemporary(shots != 0 && cancelCallbackRef == 0), remainingShots(shots), + cancelCallbackRef(cancelCallbackRef), E(_E) { } - // Remove our function from the registry when the Binding is deleted. ~Binding() { + // Remove our function and cancel callback from the registry when the Binding is deleted. + if (cancelCallbackRef > 0) + luaL_unref(E.L, LUA_REGISTRYINDEX, cancelCallbackRef); + luaL_unref(E.L, LUA_REGISTRYINDEX, functionReference); } }; @@ -63,6 +68,8 @@ public: // unregisters all registered functions and clears all registered events from the bindings virtual void Clear() { }; + + virtual void ClearOne(int ref, uint32 event_id, uint32 entry, uint64 guid) = 0; }; template @@ -81,8 +88,27 @@ public: for (EventToFunctionsMap::iterator itr = Bindings.begin(); itr != Bindings.end(); ++itr) { FunctionRefVector& funcrefvec = itr->second; + std::vector cancelRefVector; + for (FunctionRefVector::iterator it = funcrefvec.begin(); it != funcrefvec.end(); ++it) - delete *it; + { + Binding* binding = (*it); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } + funcrefvec.clear(); } Bindings.clear(); @@ -91,10 +117,51 @@ public: void Clear(uint32 event_id) { WriteGuard guard(GetLock()); + FunctionRefVector& v = Bindings[event_id]; + std::vector cancelRefVector; - for (FunctionRefVector::iterator itr = Bindings[event_id].begin(); itr != Bindings[event_id].end(); ++itr) - delete *itr; - Bindings[event_id].clear(); + for (FunctionRefVector::iterator itr = v.begin(); itr != v.end(); ++itr) + { + Binding* binding = (*itr); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } + + v.clear(); + } + + void ClearOne(int ref, uint32 event_id, uint32 entry, uint64 guid) override + { + ASSERT(entry == 0 && guid == 0); + WriteGuard guard(GetLock()); + + FunctionRefVector& funcrefvec = Bindings[event_id]; + + for (FunctionRefVector::iterator i = funcrefvec.begin(); i != funcrefvec.end(); ++i) + { + Binding* binding = (*i); + + if (binding->functionReference == ref) + { + i = funcrefvec.erase(i); + delete binding; + return; + } + } + + ASSERT(false && "tried to clear function ref that doesn't exist"); } // Pushes the function references and updates the counters on the binds and erases them if the counter would reach 0 @@ -111,6 +178,9 @@ public: if (binding->isTemporary) { + // Bad things will happen if there's a cancel callback (due to ref reuse). + ASSERT(binding->cancelCallbackRef == 0); + binding->remainingShots--; if (binding->remainingShots == 0) { @@ -124,10 +194,10 @@ public: Bindings.erase(event_id); }; - void Insert(int eventId, int funcRef, uint32 shots) // Inserts a new registered event + void Insert(int eventId, int funcRef, uint32 shots, int callbackRef = 0) // Inserts a new registered event { WriteGuard guard(GetLock()); - Bindings[eventId].push_back(new Binding(E, funcRef, shots)); + Bindings[eventId].push_back(new Binding(E, funcRef, shots, callbackRef)); } // Checks if there are events for ID @@ -169,8 +239,27 @@ public: for (EventToFunctionsMap::iterator it = funcmap.begin(); it != funcmap.end(); ++it) { FunctionRefVector& funcrefvec = it->second; + std::vector cancelRefVector; + for (FunctionRefVector::iterator i = funcrefvec.begin(); i != funcrefvec.end(); ++i) - delete *i; + { + Binding* binding = (*i); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } + funcrefvec.clear(); } funcmap.clear(); @@ -181,10 +270,51 @@ public: void Clear(uint32 entry, uint32 event_id) { WriteGuard guard(GetLock()); + FunctionRefVector& v = Bindings[entry][event_id]; + std::vector cancelRefVector; - for (FunctionRefVector::iterator itr = Bindings[entry][event_id].begin(); itr != Bindings[entry][event_id].end(); ++itr) - delete *itr; - Bindings[entry][event_id].clear(); + for (FunctionRefVector::iterator itr = v.begin(); itr != v.end(); ++itr) + { + Binding* binding = (*itr); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } + + v.clear(); + } + + void ClearOne(int ref, uint32 event_id, uint32 entry, uint64 guid) override + { + ASSERT(entry != 0 && guid == 0); + WriteGuard guard(GetLock()); + + FunctionRefVector& funcrefvec = Bindings[entry][event_id]; + + for (FunctionRefVector::iterator i = funcrefvec.begin(); i != funcrefvec.end(); ++i) + { + Binding* binding = (*i); + + if (binding->functionReference == ref) + { + i = funcrefvec.erase(i); + delete binding; + return; + } + } + + ASSERT(false && "tried to clear function ref that doesn't exist"); } // Pushes the function references and updates the counters on the binds and erases them if the counter would reach 0 @@ -201,6 +331,9 @@ public: if (binding->isTemporary) { + // Bad things will happen if there's a cancel callback (due to ref reuse). + ASSERT(binding->cancelCallbackRef == 0); + binding->remainingShots--; if (binding->remainingShots == 0) { @@ -217,10 +350,10 @@ public: Bindings.erase(entry); }; - void Insert(uint32 entryId, int eventId, int funcRef, uint32 shots) // Inserts a new registered event + void Insert(uint32 entryId, int eventId, int funcRef, uint32 shots, int callbackRef = 0) // Inserts a new registered event { WriteGuard guard(GetLock()); - Bindings[entryId][eventId].push_back(new Binding(E, funcRef, shots)); + Bindings[entryId][eventId].push_back(new Binding(E, funcRef, shots, callbackRef)); } // Returns true if the entry has registered binds @@ -279,8 +412,26 @@ public: for (EventToFunctionsMap::iterator it = funcmap.begin(); it != funcmap.end(); ++it) { FunctionRefVector& funcrefvec = it->second; + std::vector cancelRefVector; + for (FunctionRefVector::iterator i = funcrefvec.begin(); i != funcrefvec.end(); ++i) - delete *i; + { + Binding* binding = (*i); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } funcrefvec.clear(); } funcmap.clear(); @@ -294,12 +445,52 @@ public: { WriteGuard guard(GetLock()); FunctionRefVector& v = Bindings[guid][instanceId][event_id]; + std::vector cancelRefVector; for (FunctionRefVector::iterator itr = v.begin(); itr != v.end(); ++itr) - delete *itr; + { + Binding* binding = (*itr); + + // Can't call the callback now, since it might modify `v` and crash the server. + // Just add the ref to a list and call them all after this loop. + if (binding->cancelCallbackRef) + cancelRefVector.push_back(binding->cancelCallbackRef); + else + delete binding; // Don't bother removing from list, clear is called at end anyway. + } + + // Call all of the cancel callbacks for bindings with cancel callbacks. + for (std::vector::iterator i = cancelRefVector.begin(); i != cancelRefVector.end(); ++i) + { + lua_rawgeti(E.L, LUA_REGISTRYINDEX, (*i)); + lua_call(E.L, 0, 0); + } + v.clear(); } + void ClearOne(int ref, uint32 event_id, uint32 instance_id, uint64 guid) override + { + ASSERT(guid != 0); + WriteGuard guard(GetLock()); + + FunctionRefVector& funcrefvec = Bindings[guid][instance_id][event_id]; + + for (FunctionRefVector::iterator i = funcrefvec.begin(); i != funcrefvec.end(); ++i) + { + Binding* binding = (*i); + + if (binding->functionReference == ref) + { + i = funcrefvec.erase(i); + delete binding; + return; + } + } + + ASSERT(false && "tried to clear function ref that doesn't exist"); + } + // Pushes the function references and updates the counters on the binds and erases them if the counter would reach 0 void PushFuncRefs(lua_State* L, int event_id, uint64 guid, uint32 instanceId) { @@ -315,6 +506,9 @@ public: if (binding->isTemporary) { + // Bad things will happen if there's a cancel callback (due to ref reuse). + ASSERT(binding->cancelCallbackRef == 0); + binding->remainingShots--; if (binding->remainingShots == 0) { @@ -334,10 +528,10 @@ public: Bindings.erase(guid); }; - void Insert(uint64 guid, uint32 instanceId, int eventId, int funcRef, uint32 shots) // Inserts a new registered event + void Insert(uint64 guid, uint32 instanceId, int eventId, int funcRef, uint32 shots, int callbackRef = 0) // Inserts a new registered event { WriteGuard guard(GetLock()); - Bindings[guid][instanceId][eventId].push_back(new Binding(E, funcRef, shots)); + Bindings[guid][instanceId][eventId].push_back(new Binding(E, funcRef, shots, callbackRef)); } // Returns true if the entry has registered binds diff --git a/ElunaUtility.h b/ElunaUtility.h index 1ca5b29..2d60cb2 100644 --- a/ElunaUtility.h +++ b/ElunaUtility.h @@ -130,11 +130,11 @@ namespace ElunaUtil public: #ifdef USING_BOOST - typedef boost::shared_mutex LockType; + typedef boost::recursive_mutex LockType; typedef boost::shared_lock ReadGuard; typedef boost::unique_lock WriteGuard; #else - typedef ACE_RW_Thread_Mutex LockType; + typedef ACE_Recursive_Thread_Mutex LockType; typedef ACE_Read_Guard ReadGuard; typedef ACE_Write_Guard WriteGuard; #endif diff --git a/GlobalMethods.h b/GlobalMethods.h index bc0c720..02da109 100644 --- a/GlobalMethods.h +++ b/GlobalMethods.h @@ -497,54 +497,88 @@ namespace LuaGlobalFunctions return 1; } - static void RegisterEntryHelper(Eluna* E, lua_State* L, int regtype) + static int RegisterEntryHelper(Eluna* E, lua_State* L, int regtype) { uint32 entry = Eluna::CHECKVAL(L, 1); uint32 ev = Eluna::CHECKVAL(L, 2); luaL_checktype(L, 3, LUA_TFUNCTION); uint32 shots = Eluna::CHECKVAL(L, 4, 0); + bool returnCallback = Eluna::CHECKVAL(L, 5, false); + + if (shots > 0 && returnCallback) + { + luaL_argerror(L, 5, "cannot return a callback if shots is > 0"); + return 0; + } lua_pushvalue(L, 3); int functionRef = luaL_ref(L, LUA_REGISTRYINDEX); if (functionRef >= 0) - E->Register(regtype, entry, 0, 0, ev, functionRef, shots); + return E->Register(L, regtype, entry, 0, 0, ev, functionRef, shots, returnCallback); else luaL_argerror(L, 3, "unable to make a ref to function"); + return 0; } - static void RegisterEventHelper(Eluna* E, lua_State* L, int regtype) + static int RegisterEventHelper(Eluna* E, lua_State* L, int regtype) { uint32 ev = Eluna::CHECKVAL(L, 1); luaL_checktype(L, 2, LUA_TFUNCTION); uint32 shots = Eluna::CHECKVAL(L, 3, 0); + bool returnCallback = Eluna::CHECKVAL(L, 4, false); + + if (shots > 0 && returnCallback) + { + luaL_argerror(L, 5, "cannot return a callback if shots is > 0"); + return 0; + } lua_pushvalue(L, 2); int functionRef = luaL_ref(L, LUA_REGISTRYINDEX); if (functionRef >= 0) - E->Register(regtype, 0, 0, 0, ev, functionRef, shots); + return E->Register(L, regtype, 0, 0, 0, ev, functionRef, shots, returnCallback); else luaL_argerror(L, 2, "unable to make a ref to function"); + return 0; } - static void RegisterUniqueHelper(Eluna* E, lua_State* L, int regtype) + static int RegisterUniqueHelper(Eluna* E, lua_State* L, int regtype) { uint64 guid = Eluna::CHECKVAL(L, 1); uint32 instanceId = Eluna::CHECKVAL(L, 2); uint32 ev = Eluna::CHECKVAL(L, 3); luaL_checktype(L, 4, LUA_TFUNCTION); uint32 shots = Eluna::CHECKVAL(L, 5, 0); + bool returnCallback = Eluna::CHECKVAL(L, 6, false); + + if (shots > 0 && returnCallback) + { + luaL_argerror(L, 5, "cannot return a callback if shots is > 0"); + return 0; + } lua_pushvalue(L, 4); int functionRef = luaL_ref(L, LUA_REGISTRYINDEX); if (functionRef >= 0) - E->Register(regtype, 0, guid, instanceId, ev, functionRef, shots); + return E->Register(L, regtype, 0, guid, instanceId, ev, functionRef, shots, returnCallback); else luaL_argerror(L, 4, "unable to make a ref to function"); + return 0; } /** * Registers a server event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearServerEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearServerEvents] work as normal. + * * enum ServerEvents * { * // Server @@ -600,19 +634,35 @@ namespace LuaGlobalFunctions * SERVER_EVENT_COUNT * }; * + * @proto (event, function) + * @proto (event, function, shots) + * @proto cancel = (event, function, 0, true) + * * @param uint32 event : server event ID, refer to ServerEvents above * @param function function : function that will be called when the event occurs * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterServerEvent(Eluna* E, lua_State* L) { - RegisterEventHelper(E, L, Hooks::REGTYPE_SERVER); - return 0; + return RegisterEventHelper(E, L, Hooks::REGTYPE_SERVER); } /** * Registers a [Player] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearPlayerEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearPlayerEvents] work as normal. + * *
      * enum PlayerEvents
      * {
@@ -665,19 +715,35 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (event, function) + * @proto (event, function, shots) + * @proto cancel = (event, function, 0, true) + * * @param uint32 event : [Player] event Id, refer to PlayerEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterPlayerEvent(Eluna* E, lua_State* L) { - RegisterEventHelper(E, L, Hooks::REGTYPE_PLAYER); - return 0; + return RegisterEventHelper(E, L, Hooks::REGTYPE_PLAYER); } /** * Registers a [Guild] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearGuildEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearGuildEvents] work as normal. + * *
      * enum GuildEvents
      * {
@@ -698,19 +764,35 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (event, function) + * @proto (event, function, shots) + * @proto cancel = (event, function, 0, true) + * * @param uint32 event : [Guild] event Id, refer to GuildEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterGuildEvent(Eluna* E, lua_State* L) { - RegisterEventHelper(E, L, Hooks::REGTYPE_GUILD); - return 0; + return RegisterEventHelper(E, L, Hooks::REGTYPE_GUILD); } /** * Registers a [Group] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearGroupEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearGroupEvents] work as normal. + * *
      * enum GroupEvents
      * {
@@ -726,9 +808,16 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (event, function) + * @proto (event, function, shots) + * @proto cancel = (event, function, 0, true) + * * @param uint32 event : [Group] event Id, refer to GroupEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterGroupEvent(Eluna* E, lua_State* L) { @@ -739,6 +828,16 @@ namespace LuaGlobalFunctions /** * Registers a [BattleGround] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearBattleGroundEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearBattleGroundEvents] work as normal. + * *
      * enum BGEvents
      * {
@@ -750,19 +849,35 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (event, function) + * @proto (event, function, shots) + * @proto cancel = (event, function, 0, true) + * * @param uint32 event : [BattleGround] event Id, refer to BGEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterBGEvent(Eluna* E, lua_State* L) { - RegisterEventHelper(E, L, Hooks::REGTYPE_BG); - return 0; + return RegisterEventHelper(E, L, Hooks::REGTYPE_BG); } /** * Registers a [WorldPacket] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearPacketEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearPacketEvents] work as normal. + * *
      * enum PacketEvents
      * {
@@ -774,20 +889,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (entry, event, function) + * @proto (entry, event, function, shots) + * @proto cancel = (entry, event, function, 0, true) + * * @param uint32 entry : opcode * @param uint32 event : packet event Id, refer to PacketEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterPacketEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_PACKET); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_PACKET); } /** * Registers a [Creature] gossip event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearCreatureGossipEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearCreatureGossipEvents] work as normal. + * *
      * enum GossipEvents
      * {
@@ -797,20 +928,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (menu_id, event, function) + * @proto (menu_id, event, function, shots) + * @proto cancel = (menu_id, event, function, 0, true) + * * @param uint32 menu_id : [Creature] entry Id * @param uint32 event : [Creature] gossip event Id, refer to GossipEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterCreatureGossipEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_CREATURE_GOSSIP); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_CREATURE_GOSSIP); } /** * Registers a [GameObject] gossip event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearGameObjectGossipEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearGameObjectGossipEvents] work as normal. + * *
      * enum GossipEvents
      * {
@@ -820,20 +967,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (menu_id, event, function) + * @proto (menu_id, event, function, shots) + * @proto cancel = (menu_id, event, function, 0, true) + * * @param uint32 menu_id : [GameObject] entry Id * @param uint32 event : [GameObject] gossip event Id, refer to GossipEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterGameObjectGossipEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_GAMEOBJECT_GOSSIP); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_GAMEOBJECT_GOSSIP); } /** * Registers an [Item] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearItemEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearItemEvents] work as normal. + * *
      * enum ItemEvents
      * {
@@ -846,20 +1009,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (entry, event, function) + * @proto (entry, event, function, shots) + * @proto cancel = (entry, event, function, 0, true) + * * @param uint32 entry : [Item] entry Id * @param uint32 event : [Item] event Id, refer to ItemEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterItemEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM); } /** * Registers an [Item] gossip event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearItemGossipEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearItemGossipEvents] work as normal. + * *
      * enum GossipEvents
      * {
@@ -869,20 +1048,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (entry, event, function) + * @proto (entry, event, function, shots) + * @proto cancel = (entry, event, function, 0, true) + * * @param uint32 entry : [Item] entry Id * @param uint32 event : [Item] gossip event Id, refer to GossipEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterItemGossipEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM_GOSSIP); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_ITEM_GOSSIP); } /** * Registers a [Player] gossip event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearPlayerGossipEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearPlayerGossipEvents] work as normal. + * *
      * enum GossipEvents
      * {
@@ -892,20 +1087,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (menu_id, event, function) + * @proto (menu_id, event, function, shots) + * @proto cancel = (menu_id, event, function, 0, true) + * * @param uint32 menu_id : [Player] gossip menu Id * @param uint32 event : [Player] gossip event Id, refer to GossipEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterPlayerGossipEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_PLAYER_GOSSIP); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_PLAYER_GOSSIP); } /** * Registers a [Creature] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearCreatureEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearCreatureEvents] work as normal. + * *
      * enum CreatureEvents
      * {
@@ -950,20 +1161,36 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (entry, event, function) + * @proto (entry, event, function, shots) + * @proto cancel = (entry, event, function, 0, true) + * * @param uint32 entry : the ID of one or more [Creature]s * @param uint32 event : refer to CreatureEvents above * @param function function : function that will be called when the event occurs * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterCreatureEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_CREATURE); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_CREATURE); } /** * Registers a [Creature] event handler for a *single* [Creature]. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearUniqueCreatureEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearUniqueCreatureEvents] work as normal. + * *
      * enum CreatureEvents
      * {
@@ -1008,21 +1235,37 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (guid, instance_id, event, function) + * @proto (guid, instance_id, event, function, shots) + * @proto cancel = (guid, instance_id, event, function, 0, true) + * * @param uint64 guid : the GUID of a single [Creature] * @param uint32 instance_id : the instance ID of a single [Creature] * @param uint32 event : refer to CreatureEvents above * @param function function : function that will be called when the event occurs * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterUniqueCreatureEvent(Eluna* E, lua_State* L) { - RegisterUniqueHelper(E, L, Hooks::REGTYPE_CREATURE); - return 0; + return RegisterUniqueHelper(E, L, Hooks::REGTYPE_CREATURE); } /** * Registers a [GameObject] event handler. * + * If `return_callback` is `true`, a function will be returned which + * cancels this event binding. + * + * (The returned function is the **only** way to cancel the bindings; + * `shots` must be `0` (e.g. the binding will never expire), and + * [Global:ClearGameObjectEvents] will skip this binding.) + * + * If `return_callback` is `false`, nothing is returned, and `shots` and + * [Global:ClearGameObjectEvents] work as normal. + * *
      * enum GameObjectEvents
      * {
@@ -1043,15 +1286,21 @@ namespace LuaGlobalFunctions
      * };
      * 
* + * @proto (entry, event, function) + * @proto (entry, event, function, shots) + * @proto cancel = (entry, event, function, 0, true) + * * @param uint32 entry : [GameObject] entry Id * @param uint32 event : [GameObject] event Id, refer to GameObjectEvents above * @param function function : function to register * @param uint32 shots = 0 : the number of times the function will be called, 0 means "always call this function" + * @param bool return_callback = false + * + * @return function cancel : a function that cancels the binding when called */ int RegisterGameObjectEvent(Eluna* E, lua_State* L) { - RegisterEntryHelper(E, L, Hooks::REGTYPE_GAMEOBJECT); - return 0; + return RegisterEntryHelper(E, L, Hooks::REGTYPE_GAMEOBJECT); } /** @@ -2404,7 +2653,7 @@ namespace LuaGlobalFunctions * Otherwise, only event handlers for `event_type` are cleared. * * **NOTE:** this will affect all instances of the [Creature], not just one. - * To bind and unbind events to a single [Creature], see [Global:RegisterUniqueCreatureEvent] and [Global:ClearUniqueCreatureEvent]. + * To bind and unbind events to a single [Creature], see [Global:RegisterUniqueCreatureEvent] and [Global:ClearUniqueCreatureEvents]. * * @proto (entry) * @proto (entry, event_type) diff --git a/LuaEngine.cpp b/LuaEngine.cpp index ad7a75f..dc988b1 100644 --- a/LuaEngine.cpp +++ b/LuaEngine.cpp @@ -24,6 +24,7 @@ extern "C" { // Base lua libraries +#include "lua.h" #include "lualib.h" #include "lauxlib.h" @@ -906,56 +907,142 @@ ElunaObject* Eluna::CHECKTYPE(lua_State* luastate, int narg, const char* tname, return *ptrHold; } +static int cancelBinding(lua_State *L) +{ + int ref = lua_tointeger(L, lua_upvalueindex(1)); + uint32 event_id = lua_tounsigned(L, lua_upvalueindex(2)); + uint32 entry_id = lua_tounsigned(L, lua_upvalueindex(3)); + uint64 guid = Eluna::CHECKVAL(L, lua_upvalueindex(4)); + + // This marker (initially `false`) is used to protect against calling this callback twice. + // After the first call, `alreadyCalled` will be `true`, so we know not to proceed. + bool alreadyCalled = lua_toboolean(L, lua_upvalueindex(5)); + + if (alreadyCalled) + return 0; + else + { + lua_pushboolean(L, true); + lua_replace(L, lua_upvalueindex(5)); + } + + ElunaBind* bindings1 = (ElunaBind*)lua_touserdata(L, lua_upvalueindex(6)); + ASSERT(bindings1 != NULL); + bindings1->ClearOne(ref, event_id, entry_id, guid); + + return 0; +} + +static int createCancelCallback(lua_State* L, int ref, ElunaBind* bindings1, uint32 event_id, uint32 entry_id = 0, uint64 guid = 0) +{ + lua_pushinteger(L, ref); + lua_pushunsigned(L, event_id); + lua_pushunsigned(L, entry_id); + Eluna::Push(L, guid); + lua_pushboolean(L, false); + lua_pushlightuserdata(L, bindings1); + // Stack: ref, event_id, entry_id, guid, false, bindings1 + + lua_pushcclosure(L, &cancelBinding, 6); + // Stack: cancel_callback + + lua_pushvalue(L, -1); + return luaL_ref(L, LUA_REGISTRYINDEX); + // Stack: cancel_callback +} + // Saves the function reference ID given to the register type's store for given entry under the given event -void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int functionRef, uint32 shots) +int Eluna::Register(lua_State* L, uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int functionRef, uint32 shots, bool returnCallback) { switch (regtype) { case Hooks::REGTYPE_SERVER: if (evt < Hooks::SERVER_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, ServerEventBindings, evt); + ServerEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + ServerEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_PLAYER: if (evt < Hooks::PLAYER_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, PlayerEventBindings, evt); + PlayerEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + PlayerEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_GUILD: if (evt < Hooks::GUILD_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, GuildEventBindings, evt); + GuildEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + GuildEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_GROUP: if (evt < Hooks::GROUP_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, GroupEventBindings, evt); + GroupEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + GroupEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_VEHICLE: if (evt < Hooks::VEHICLE_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, VehicleEventBindings, evt); + VehicleEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + VehicleEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_BG: if (evt < Hooks::BG_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, BGEventBindings, evt); + BGEventBindings->Insert(evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + BGEventBindings->Insert(evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -966,11 +1053,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, PacketEventBindings, evt, id); + PacketEventBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } PacketEventBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -983,7 +1077,14 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, CreatureEventBindings, evt, id); + CreatureEventBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } CreatureEventBindings->Insert(id, evt, functionRef, shots); @@ -991,9 +1092,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u else { ASSERT(guid != 0); + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, CreatureUniqueBindings, evt, instanceId, guid); + CreatureUniqueBindings->Insert(guid, instanceId, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + CreatureUniqueBindings->Insert(guid, instanceId, evt, functionRef, shots); } - return; + + return 0; // Stack: (empty) } break; @@ -1004,11 +1114,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a creature with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, CreatureGossipBindings, evt, id); + CreatureGossipBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } CreatureGossipBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -1019,11 +1136,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, GameObjectEventBindings, evt, id); + GameObjectEventBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } GameObjectEventBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -1034,11 +1158,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a gameobject with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, GameObjectGossipBindings, evt, id); + GameObjectGossipBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } GameObjectGossipBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -1049,11 +1180,18 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, ItemEventBindings, evt, id); + ItemEventBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } ItemEventBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; @@ -1064,24 +1202,39 @@ void Eluna::Register(uint8 regtype, uint32 id, uint64 guid, uint32 instanceId, u { luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Couldn't find a item with (ID: %d)!", id); - return; + return 0; // Stack: (empty) + } + + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, ItemGossipBindings, evt, id); + ItemGossipBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback } ItemGossipBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; case Hooks::REGTYPE_PLAYER_GOSSIP: if (evt < Hooks::GOSSIP_EVENT_COUNT) { + if (returnCallback) + { + int callbackRef = createCancelCallback(L, functionRef, playerGossipBindings, evt, id); + playerGossipBindings->Insert(id, evt, functionRef, shots, callbackRef); + return 1; // Stack: callback + } + playerGossipBindings->Insert(id, evt, functionRef, shots); - return; + return 0; // Stack: (empty) } break; } luaL_unref(L, LUA_REGISTRYINDEX, functionRef); luaL_error(L, "Unknown event type (regtype %d, id %d, event %d)", regtype, id, evt); + return 0; } /* diff --git a/LuaEngine.h b/LuaEngine.h index 32a9789..44c9dca 100644 --- a/LuaEngine.h +++ b/LuaEngine.h @@ -272,7 +272,7 @@ public: bool ShouldReload() const { return reload; } bool IsEnabled() const { return enabled && IsInitialized(); } bool HasLuaState() const { return L != NULL; } - void Register(uint8 reg, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int func, uint32 shots); + int Register(lua_State* L, uint8 reg, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int func, uint32 shots, bool returnCallback); // Non-static pushes, to be used in hooks. // These just call the correct static version with the main thread's Lua state.