diff --git a/ElunaBinding.h b/ElunaBinding.h index 312ce37..ab2e667 100644 --- a/ElunaBinding.h +++ b/ElunaBinding.h @@ -135,6 +135,9 @@ public: { ReadGuard guard(GetLock()); + if (!E.IsEnabled()) + return false; + if (Bindings.empty()) return false; if (Bindings.find(eventId) == Bindings.end()) @@ -239,6 +242,9 @@ public: { ReadGuard guard(GetLock()); + if (!E.IsEnabled()) + return false; + if (Bindings.empty()) return false; diff --git a/ElunaEventMgr.cpp b/ElunaEventMgr.cpp index cc765c9..266b9d9 100644 --- a/ElunaEventMgr.cpp +++ b/ElunaEventMgr.cpp @@ -14,33 +14,6 @@ extern "C" #include "lauxlib.h" }; -LuaEvent::LuaEvent(ElunaEventProcessor* _events, int _funcRef, uint32 _delay, uint32 _calls) : -to_Abort(false), events(_events), funcRef(_funcRef), delay(_delay), calls(_calls) -{ -} - -LuaEvent::~LuaEvent() -{ - luaL_unref((*events->E)->L, LUA_REGISTRYINDEX, funcRef); // Free lua function ref -} - -void LuaEvent::Execute() -{ - LOCK_ELUNA; - // In multithread get map from object and the map's lua state - lua_rawgeti((*events->E)->L, LUA_REGISTRYINDEX, funcRef); - Eluna::Push((*events->E)->L, funcRef); - Eluna::Push((*events->E)->L, delay); - Eluna::Push((*events->E)->L, calls); - if (calls) // Must be before calling - --calls; - Eluna::Push((*events->E)->L, events->obj); - (*events->E)->ExecuteCall(4, 0); - - ASSERT(!(*events->E)->event_level); - (*events->E)->InvalidateObjects(); -} - ElunaEventProcessor::ElunaEventProcessor(Eluna** _E, WorldObject* _obj) : m_time(0), obj(_obj), E(_E) { if (obj) @@ -54,7 +27,7 @@ ElunaEventProcessor::~ElunaEventProcessor() { RemoveEvents_internal(); - if (obj && Eluna::initialized) + if (obj && Eluna::IsInitialized()) { EventMgr::WriteGuard guard((*E)->eventMgr->GetLock()); (*E)->eventMgr->processors.erase(this); @@ -66,31 +39,32 @@ void ElunaEventProcessor::Update(uint32 diff) m_time += diff; for (EventList::iterator it = eventList.begin(); it != eventList.end() && it->first <= m_time; it = eventList.begin()) { - LuaEvent* event = it->second; + LuaEvent* luaEvent = it->second; eventList.erase(it); - eventMap.erase(event->funcRef); + eventMap.erase(luaEvent->funcRef); - if (event->to_Abort) + if (!luaEvent->abort) { - delete event; - continue; + bool remove = luaEvent->repeats == 1; + if (!remove) + AddEvent(luaEvent); // Reschedule before calling incase RemoveEvents used + + // Call the timed event + (*E)->OnTimedEvent(luaEvent->funcRef, luaEvent->delay, luaEvent->repeats ? luaEvent->repeats-- : luaEvent->repeats, obj); + + if (!remove) + continue; } - bool remove = event->calls == 1; - if (!remove) - AddEvent(event); // Reschedule before calling incase RemoveEvents used - - event->Execute(); - - if (remove) - delete event; + // Event should be deleted (executed last time or set to be aborted) + RemoveEvent(luaEvent); } } void ElunaEventProcessor::RemoveEvents() { for (EventList::iterator it = eventList.begin(); it != eventList.end(); ++it) - it->second->to_Abort = true; + it->second->abort = true; } void ElunaEventProcessor::RemoveEvents_internal() @@ -103,7 +77,7 @@ void ElunaEventProcessor::RemoveEvents_internal() //} for (EventList::iterator it = eventList.begin(); it != eventList.end(); ++it) - delete it->second; + RemoveEvent(it->second); eventList.clear(); eventMap.clear(); @@ -112,18 +86,25 @@ void ElunaEventProcessor::RemoveEvents_internal() void ElunaEventProcessor::RemoveEvent(int eventId) { if (eventMap.find(eventId) != eventMap.end()) - eventMap[eventId]->to_Abort = true; + eventMap[eventId]->abort = true; } -void ElunaEventProcessor::AddEvent(LuaEvent* event) +void ElunaEventProcessor::AddEvent(LuaEvent* luaEvent) { - eventList.insert(std::pair(m_time + event->delay, event)); - eventMap[event->funcRef] = event; + eventList.insert(std::pair(m_time + luaEvent->delay, luaEvent)); + eventMap[luaEvent->funcRef] = luaEvent; } void ElunaEventProcessor::AddEvent(int funcRef, uint32 delay, uint32 repeats) { - AddEvent(new LuaEvent(this, funcRef, delay, repeats)); + AddEvent(new LuaEvent(funcRef, delay, repeats)); +} + +void ElunaEventProcessor::RemoveEvent(LuaEvent* luaEvent) +{ + // Free lua function ref + luaL_unref((*E)->L, LUA_REGISTRYINDEX, luaEvent->funcRef); + delete luaEvent; } EventMgr::EventMgr(Eluna** _E) : globalProcessor(new ElunaEventProcessor(_E, NULL)), E(_E) diff --git a/ElunaEventMgr.h b/ElunaEventMgr.h index 8390e42..01771f5 100644 --- a/ElunaEventMgr.h +++ b/ElunaEventMgr.h @@ -22,29 +22,20 @@ class EventMgr; class ElunaEventProcessor; class WorldObject; -class LuaEvent +struct LuaEvent { - friend class EventMgr; - friend class ElunaEventProcessor; - -public: - // Should never execute on dead events - void Execute(); - bool to_Abort; - -private: - LuaEvent(ElunaEventProcessor* _events, int _funcRef, uint32 _delay, uint32 _calls); - ~LuaEvent(); - - ElunaEventProcessor* events; // Pointer to events (holds the timed event) - int funcRef; // Lua function reference ID, also used as event ID + LuaEvent(int _funcRef, uint32 _delay, uint32 _repeats) : + delay(_delay), repeats(_repeats), funcRef(_funcRef), abort(false) + { + } uint32 delay; // Delay between event calls - uint32 calls; // Amount of calls to make, 0 for infinite + uint32 repeats; // Amount of repeats to make, 0 for infinite + int funcRef; // Lua function reference ID, also used as event ID + bool abort; // True if aborted and should not execute anymore }; class ElunaEventProcessor { - friend class LuaEvent; friend class EventMgr; public: @@ -64,7 +55,8 @@ public: private: void RemoveEvents_internal(); - void AddEvent(LuaEvent* Event); + void AddEvent(LuaEvent* luaEvent); + void RemoveEvent(LuaEvent* luaEvent); EventList eventList; uint64 m_time; WorldObject* obj; diff --git a/GlobalMethods.h b/GlobalMethods.h index f6b940e..c71683e 100644 --- a/GlobalMethods.h +++ b/GlobalMethods.h @@ -1046,7 +1046,7 @@ namespace LuaGlobalFunctions */ int ReloadEluna(Eluna* /*E*/, lua_State* /*L*/) { - Eluna::reload = true; + Eluna::ReloadEluna(); return 0; } diff --git a/LuaEngine.cpp b/LuaEngine.cpp index 60399d5..6788ad2 100644 --- a/LuaEngine.cpp +++ b/LuaEngine.cpp @@ -43,8 +43,35 @@ extern void RegisterFunctions(Eluna* E); void Eluna::Initialize() { - ASSERT(!initialized); + LOCK_ELUNA; + ASSERT(!IsInitialized()); + LoadScriptPaths(); + + // Must be before creating GEluna + // This is checked on Eluna creation + initialized = true; + + // Create global eluna + GEluna = new Eluna(); +} + +void Eluna::Uninitialize() +{ + LOCK_ELUNA; + ASSERT(IsInitialized()); + + delete GEluna; + GEluna = NULL; + + lua_scripts.clear(); + lua_extensions.clear(); + + initialized = false; +} + +void Eluna::LoadScriptPaths() +{ uint32 oldMSTime = ElunaUtil::GetCurrTime(); lua_scripts.clear(); @@ -64,43 +91,28 @@ void Eluna::Initialize() lua_requirepath.erase(lua_requirepath.end() - 1); ELUNA_LOG_DEBUG("[Eluna]: Loaded %u scripts in %u ms", uint32(lua_scripts.size() + lua_extensions.size()), ElunaUtil::GetTimeDiff(oldMSTime)); - - initialized = true; - - // Create global eluna - GEluna = new Eluna(); } -void Eluna::Uninitialize() +void Eluna::_ReloadEluna() { - ASSERT(initialized); + LOCK_ELUNA; + ASSERT(IsInitialized()); - delete GEluna; - GEluna = NULL; - - lua_scripts.clear(); - lua_extensions.clear(); - - initialized = false; -} - -void Eluna::ReloadEluna() -{ eWorld->SendServerMessage(SERVER_MSG_STRING, "Reloading Eluna..."); - EventMgr::ProcessorSet oldProcessors; - { - EventMgr::ReadGuard guard(sEluna->eventMgr->GetLock()); - oldProcessors = sEluna->eventMgr->processors; - } - Uninitialize(); - Initialize(); - { - EventMgr::WriteGuard guard(sEluna->eventMgr->GetLock()); - sEluna->eventMgr->processors.insert(oldProcessors.begin(), oldProcessors.end()); - } + // Remove all timed events + sEluna->eventMgr->RemoveEvents(); - // in multithread foreach: run scripts + // Close lua + sEluna->CloseLua(); + + // Reload script paths + LoadScriptPaths(); + + // Open new lua and libaraies + sEluna->OpenLua(); + + // Run scripts from laoded paths sEluna->RunScripts(); #ifdef TRINITY @@ -117,31 +129,76 @@ void Eluna::ReloadEluna() } Eluna::Eluna() : -L(luaL_newstate()), - event_level(0), push_counter(0), +enabled(false), +L(NULL), eventMgr(NULL), -ServerEventBindings(new EventBind("ServerEvents", *this)), -PlayerEventBindings(new EventBind("PlayerEvents", *this)), -GuildEventBindings(new EventBind("GuildEvents", *this)), -GroupEventBindings(new EventBind("GroupEvents", *this)), -VehicleEventBindings(new EventBind("VehicleEvents", *this)), -BGEventBindings(new EventBind("BGEvents", *this)), +ServerEventBindings(NULL), +PlayerEventBindings(NULL), +GuildEventBindings(NULL), +GroupEventBindings(NULL), +VehicleEventBindings(NULL), +BGEventBindings(NULL), -PacketEventBindings(new EntryBind("PacketEvents", *this)), -CreatureEventBindings(new EntryBind("CreatureEvents", *this)), -CreatureGossipBindings(new EntryBind("GossipEvents (creature)", *this)), -GameObjectEventBindings(new EntryBind("GameObjectEvents", *this)), -GameObjectGossipBindings(new EntryBind("GossipEvents (gameobject)", *this)), -ItemEventBindings(new EntryBind("ItemEvents", *this)), -ItemGossipBindings(new EntryBind("GossipEvents (item)", *this)), -playerGossipBindings(new EntryBind("GossipEvents (player)", *this)), +PacketEventBindings(NULL), +CreatureEventBindings(NULL), +CreatureGossipBindings(NULL), +GameObjectEventBindings(NULL), +GameObjectGossipBindings(NULL), +ItemEventBindings(NULL), +ItemGossipBindings(NULL), +playerGossipBindings(NULL), -CreatureUniqueBindings(new UniqueBind("CreatureEvents", *this)) +CreatureUniqueBindings(NULL) { + ASSERT(IsInitialized()); + + OpenLua(); + + // Replace this with map insert if making multithread version + // + + // Set event manager. Must be after setting sEluna + // on multithread have a map of state pointers and here insert this pointer to the map and then save a pointer of that pointer to the EventMgr + eventMgr = new EventMgr(&Eluna::GEluna); +} + +Eluna::~Eluna() +{ + CloseLua(); + + delete eventMgr; + eventMgr = NULL; +} + +void Eluna::CloseLua() +{ + OnLuaStateClose(); + + DestroyBindStores(); + + // Must close lua state after deleting stores and mgr + if (L) + lua_close(L); + L = NULL; +} + +void Eluna::OpenLua() +{ + CreateBindStores(); + + enabled = eConfigMgr->GetBoolDefault("Eluna.Enabled", true); + if (!IsEnabled()) + { + ELUNA_LOG_INFO("[Eluna]: Eluna is disabled in config"); + return; + } + + L = luaL_newstate(); + // open base lua libraries luaL_openlibs(L); @@ -165,25 +222,33 @@ CreatureUniqueBindings(new UniqueBind("CreatureEvents", * lua_pushstring(L, ""); // erase cpath lua_setfield(L, -2, "cpath"); lua_pop(L, 1); - - // Replace this with map insert if making multithread version - // - - // Set event manager. Must be after setting sEluna - // on multithread have a map of state pointers and here insert this pointer to the map and then save a pointer of that pointer to the EventMgr - eventMgr = new EventMgr(&Eluna::GEluna); } -Eluna::~Eluna() +void Eluna::CreateBindStores() { - OnLuaStateClose(); + DestroyBindStores(); - delete eventMgr; - eventMgr = NULL; + ServerEventBindings = new EventBind("ServerEvents", *this); + PlayerEventBindings = new EventBind("PlayerEvents", *this); + GuildEventBindings = new EventBind("GuildEvents", *this); + GroupEventBindings = new EventBind("GroupEvents", *this); + VehicleEventBindings = new EventBind("VehicleEvents", *this); + BGEventBindings = new EventBind("BGEvents", *this); - // Replace this with map remove if making multithread version - // + PacketEventBindings = new EntryBind("PacketEvents", *this); + CreatureEventBindings = new EntryBind("CreatureEvents", *this); + CreatureGossipBindings = new EntryBind("GossipEvents (creature)", *this); + GameObjectEventBindings = new EntryBind("GameObjectEvents", *this); + GameObjectGossipBindings = new EntryBind("GossipEvents (gameobject)", *this); + ItemEventBindings = new EntryBind("ItemEvents", *this); + ItemGossipBindings = new EntryBind("GossipEvents (item)", *this); + playerGossipBindings = new EntryBind("GossipEvents (player)", *this); + CreatureUniqueBindings = new UniqueBind("CreatureEvents (unique)", *this); +} + +void Eluna::DestroyBindStores() +{ delete ServerEventBindings; delete PlayerEventBindings; delete GuildEventBindings; @@ -200,6 +265,8 @@ Eluna::~Eluna() delete playerGossipBindings; delete BGEventBindings; + delete CreatureUniqueBindings; + ServerEventBindings = NULL; PlayerEventBindings = NULL; GuildEventBindings = NULL; @@ -216,8 +283,7 @@ Eluna::~Eluna() playerGossipBindings = NULL; BGEventBindings = NULL; - // Must close lua state after deleting stores and mgr - lua_close(L); + CreatureUniqueBindings = NULL; } void Eluna::AddScriptPath(std::string filename, const std::string& fullpath) @@ -350,11 +416,15 @@ void Eluna::GetScripts(std::string path) static bool ScriptPathComparator(const LuaScript& first, const LuaScript& second) { - return first.filepath.compare(second.filepath) < 0; + return first.filepath < second.filepath; } void Eluna::RunScripts() { + LOCK_ELUNA; + if (!IsEnabled()) + return; + uint32 oldMSTime = ElunaUtil::GetCurrTime(); uint32 count = 0; @@ -367,7 +437,9 @@ void Eluna::RunScripts() UNORDERED_MAP loaded; // filename, path lua_getglobal(L, "package"); + // Stack: package luaL_getsubtable(L, -1, "loaded"); + // Stack: package, modules int modules = lua_gettop(L); for (ScriptList::const_iterator it = scripts.begin(); it != scripts.end(); ++it) { @@ -380,6 +452,7 @@ void Eluna::RunScripts() loaded[it->filename] = it->filepath; lua_getfield(L, modules, it->filename.c_str()); + // Stack: package, modules, module if (!lua_isnoneornil(L, -1)) { lua_pop(L, 1); @@ -387,25 +460,38 @@ void Eluna::RunScripts() continue; } lua_pop(L, 1); - if (!luaL_loadfile(L, it->filepath.c_str()) && !lua_pcall(L, 0, 1, 0)) + // Stack: package, modules + + if (luaL_loadfile(L, it->filepath.c_str())) { + // Stack: package, modules, errmsg + ELUNA_LOG_ERROR("[Eluna]: Error loading `%s`", it->filepath.c_str()); + Report(L); + // Stack: package, modules + continue; + } + // Stack: package, modules, filefunc + + if (ExecuteCall(0, 1)) + { + // Stack: package, modules, result if (lua_isnoneornil(L, -1) || (lua_isboolean(L, -1) && !lua_toboolean(L, -1))) { + // if result evaluates to false, change it to true lua_pop(L, 1); Push(L, true); } lua_setfield(L, modules, it->filename.c_str()); + // Stack: package, modules // successfully loaded and ran file ELUNA_LOG_DEBUG("[Eluna]: Successfully loaded `%s`", it->filepath.c_str()); ++count; continue; } - ELUNA_LOG_ERROR("[Eluna]: Error loading `%s`", it->filepath.c_str()); - report(L); } + // Stack: package, modules lua_pop(L, 2); - ELUNA_LOG_INFO("[Eluna]: Executed %u Lua scripts in %u ms", count, ElunaUtil::GetTimeDiff(oldMSTime)); OnLuaStateOpen(); @@ -426,41 +512,98 @@ void Eluna::InvalidateObjects() lua_pop(L, 1); } -void Eluna::report(lua_State* luastate) +void Eluna::Report(lua_State* _L) { - const char* msg = lua_tostring(luastate, -1); + const char* msg = lua_tostring(_L, -1); ELUNA_LOG_ERROR("%s", msg); - lua_pop(luastate, 1); + lua_pop(_L, 1); } -void Eluna::ExecuteCall(int params, int res) +// Borrowed from http://stackoverflow.com/questions/12256455/print-stacktrace-from-c-code-with-embedded-lua +int Eluna::StackTrace(lua_State *_L) +{ + // Stack: errmsg + if (!lua_isstring(_L, -1)) /* 'message' not a string? */ + return 1; /* keep it intact */ + // Stack: errmsg, debug + lua_getglobal(_L, "debug"); + if (!lua_istable(_L, -1)) + { + lua_pop(_L, 1); + return 1; + } + // Stack: errmsg, debug, traceback + lua_getfield(_L, -1, "traceback"); + if (!lua_isfunction(_L, -1)) + { + lua_pop(_L, 2); + return 1; + } + lua_pushvalue(_L, -3); /* pass error message */ + lua_pushinteger(_L, 1); /* skip this function and traceback */ + // Stack: errmsg, debug, traceback, errmsg, 2 + lua_call(_L, 2, 1); /* call debug.traceback */ + + // dirty stack? + // Stack: errmsg, debug, tracemsg + return 1; +} + +bool Eluna::ExecuteCall(int params, int res) { int top = lua_gettop(L); + int base = top - params; // Expected: function, [parameters] - ASSERT(top > params); + ASSERT(base > 0); // Check function type - int type = lua_type(L, top - params); - if (type != LUA_TFUNCTION) + if (!lua_isfunction(L, base)) { - ELUNA_LOG_ERROR("[Eluna]: Cannot execute call: registered value is %s, not a function.", lua_typename(L, type)); - ASSERT(false); + ELUNA_LOG_ERROR("[Eluna]: Cannot execute call: registered value is %s, not a function.", luaL_tolstring(L, base, NULL)); + ASSERT(false); // stack probably corrupt } - // Objects are invalidated when event level hits 0 + bool usetrace = eConfigMgr->GetBoolDefault("Eluna.TraceBack", true); + if (usetrace) + { + lua_pushcfunction(L, &StackTrace); + // Stack: function, [parameters], traceback + lua_insert(L, base); + // Stack: traceback, function, [parameters] + } + + // Objects are invalidated when event_level hits 0 ++event_level; - int result = lua_pcall(L, params, res, 0); + int result = lua_pcall(L, params, res, usetrace ? base : 0); --event_level; + if (usetrace) + { + // Stack: traceback, [results or errmsg] + lua_remove(L, base); + } + // Stack: [results or errmsg] + // lua_pcall returns 0 on success. - // On error we report errors and push nils for expected amount of returned values + // On error print the error and push nils for expected amount of returned values if (result) { - report(L); + // Stack: errmsg + Report(L); + + // Force garbage collect + lua_gc(L, LUA_GCCOLLECT, 0); + + // Push nils for expected amount of results for (int i = 0; i < res; ++i) lua_pushnil(L); + // Stack: [nils] + return false; } + + // Stack: [results] + return true; } void Eluna::Push(lua_State* luastate) diff --git a/LuaEngine.h b/LuaEngine.h index d0c5cd7..6b2695b 100644 --- a/LuaEngine.h +++ b/LuaEngine.h @@ -103,14 +103,64 @@ struct LuaScript }; #define ELUNA_OBJECT_STORE "Eluna Object Store" +#define LOCK_ELUNA Eluna::Guard __guard(Eluna::GetLock()) class Eluna { +public: + typedef std::list ScriptList; +#ifdef TRINITY + typedef std::recursive_mutex LockType; + typedef std::lock_guard Guard; +#else + typedef ACE_Recursive_Thread_Mutex LockType; + typedef ACE_Guard Guard; +#endif + private: - // prevent copy + static bool reload; + static bool initialized; + static LockType lock; + + // Lua script locations + static ScriptList lua_scripts; + static ScriptList lua_extensions; + + // Lua script folder path + static std::string lua_folderpath; + // lua path variable for require() function + static std::string lua_requirepath; + + uint32 event_level; + // When a hook pushes arguments to be passed to event handlers + // this is used to keep track of how many arguments were pushed. + uint8 push_counter; + bool enabled; + + Eluna(); + ~Eluna(); + + // Prevent copy Eluna(Eluna const&); Eluna& operator=(const Eluna&); + void OpenLua(); + void CloseLua(); + void DestroyBindStores(); + void CreateBindStores(); + bool ExecuteCall(int params, int res); + void InvalidateObjects(); + + // Use ReloadEluna() to make eluna reload + // This is called on world update to reload eluna + static void _ReloadEluna(); + static void LoadScriptPaths(); + static void GetScripts(std::string path); + static void AddScriptPath(std::string filename, const std::string& fullpath); + + static int StackTrace(lua_State *_L); + static void Eluna::Report(lua_State* _L); + // Some helpers for hooks to call event handlers. // The bodies of the templates are in HookHelpers.h, so if you want to use them you need to #include "HookHelpers.h". template int SetupStack(EventBind* event_bindings, EntryBind* entry_bindings, UniqueBind* guid_bindings, T event_id, uint32 entry, uint64 guid, uint32 instanceId, int number_of_arguments); @@ -163,25 +213,9 @@ private: } public: - typedef std::list ScriptList; - static Eluna* GEluna; - static bool reload; - static bool initialized; - -#ifdef TRINITY - typedef std::recursive_mutex LockType; - typedef std::lock_guard Guard; -#else - typedef ACE_Recursive_Thread_Mutex LockType; - typedef ACE_Guard Guard; -#endif - - static LockType lock; lua_State* L; - uint32 event_level; - EventMgr* eventMgr; EventBind* ServerEventBindings; @@ -202,26 +236,12 @@ public: UniqueBind* CreatureUniqueBindings; - Eluna(); - ~Eluna(); - - static ScriptList lua_scripts; - static ScriptList lua_extensions; - static std::string lua_folderpath; - static std::string lua_requirepath; static void Initialize(); static void Uninitialize(); - // Use Eluna::reload = true; instead. - // This will be called on next update - static void ReloadEluna(); - static void GetScripts(std::string path); - static void AddScriptPath(std::string filename, const std::string& fullpath); - - static void report(lua_State* luastate); - void ExecuteCall(int params, int res); - void Register(uint8 reg, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int func, uint32 shots); - void RunScripts(); - void InvalidateObjects(); + // This function is used to make eluna reload + static void ReloadEluna() { LOCK_ELUNA; reload = true; } + static LockType& GetLock() { return lock; }; + static bool IsInitialized() { return initialized; } // Static pushes, can be used by anything, including methods. static void Push(lua_State* luastate); // nil @@ -241,16 +261,16 @@ public: static void Push(lua_State* luastate, Unit const* unit); static void Push(lua_State* luastate, Pet const* pet); static void Push(lua_State* luastate, TempSummon const* summon); - template static void Push(lua_State* luastate, T const* ptr) { ElunaTemplate::Push(luastate, ptr); } - // When a hook pushes arguments to be passed to event handlers - // this is used to keep track of how many arguments were pushed. - uint8 push_counter; + void RunScripts(); + bool GetReload() const { return reload; } + bool IsEnabled() const { return enabled && IsInitialized(); } + void Register(uint8 reg, uint32 id, uint64 guid, uint32 instanceId, uint32 evt, int func, uint32 shots); // Non-static pushes, to be used in hooks. // These just call the correct static version with the main thread's Lua state. @@ -284,6 +304,7 @@ public: CreatureAI* GetAI(Creature* creature); /* Custom */ + void OnTimedEvent(int funcRef, uint32 delay, uint32 calls, WorldObject* obj); bool OnCommand(Player* player, const char* text); void OnWorldUpdate(uint32 diff); void OnLootItem(Player* pPlayer, Item* pItem, uint32 count, uint64 guid); @@ -480,5 +501,4 @@ template<> WorldObject* Eluna::CHECKOBJ(lua_State* L, int narg, boo template<> ElunaObject* Eluna::CHECKOBJ(lua_State* L, int narg, bool error); #define sEluna Eluna::GEluna -#define LOCK_ELUNA Eluna::Guard __guard(Eluna::lock) #endif diff --git a/PlayerHooks.cpp b/PlayerHooks.cpp index d58ab7e..4e58e32 100644 --- a/PlayerHooks.cpp +++ b/PlayerHooks.cpp @@ -75,7 +75,7 @@ bool Eluna::OnCommand(Player* player, const char* text) std::transform(eluna.begin(), eluna.end(), eluna.begin(), ::tolower); if (std::string("eluna").find(eluna) == 0) { - Eluna::reload = true; + ReloadEluna(); return false; } } diff --git a/ServerHooks.cpp b/ServerHooks.cpp index ea7278c..e4c9691 100644 --- a/ServerHooks.cpp +++ b/ServerHooks.cpp @@ -9,14 +9,35 @@ #include "Hooks.h" #include "HookHelpers.h" -#include "ElunaTemplate.h" #include "LuaEngine.h" #include "ElunaBinding.h" -#include "ElunaTemplate.h" // Needed to be able to push AuctionHouseObjects. +#include "ElunaTemplate.h" #include "ElunaEventMgr.h" +#include "ElunaIncludes.h" using namespace Hooks; +void Eluna::OnTimedEvent(int funcRef, uint32 delay, uint32 calls, WorldObject* obj) +{ + LOCK_ELUNA; + ASSERT(!event_level); + + // Get function + lua_rawgeti(L, LUA_REGISTRYINDEX, funcRef); + + // Push parameters + Push(L, funcRef); + Push(L, delay); + Push(L, calls); + Push(L, obj); + + // Call function + ExecuteCall(4, 0); + + ASSERT(!event_level); + InvalidateObjects(); +} + void Eluna::OnLuaStateClose() { if (!ServerEventBindings->HasEvents(ELUNA_EVENT_ON_LUA_STATE_CLOSE)) @@ -273,12 +294,10 @@ void Eluna::OnShutdownCancel() void Eluna::OnWorldUpdate(uint32 diff) { - LOCK_ELUNA; - - if (reload) { - ReloadEluna(); - return; + LOCK_ELUNA; + if (reload) + _ReloadEluna(); } eventMgr->globalProcessor->Update(diff); @@ -286,6 +305,7 @@ void Eluna::OnWorldUpdate(uint32 diff) if (!ServerEventBindings->HasEvents(WORLD_EVENT_ON_UPDATE)) return; + LOCK_ELUNA; Push(diff); CallAllFunctions(ServerEventBindings, WORLD_EVENT_ON_UPDATE); } diff --git a/WorldObjectMethods.h b/WorldObjectMethods.h index ca0d61b..4786583 100644 --- a/WorldObjectMethods.h +++ b/WorldObjectMethods.h @@ -548,8 +548,8 @@ namespace LuaWorldObject * @param float y * @param float z * @param float o - * @param TempSummonType spawnType : defines how and when the creature despawns - * @param uint32 despawnTimer : despawn time in seconds + * @param [TempSummonType] spawnType = 8 : defines how and when the creature despawns + * @param uint32 despawnTimer = 0 : despawn time in seconds * @return [Creature] spawnedCreature */ int SpawnCreature(Eluna* /*E*/, lua_State* L, WorldObject* obj) diff --git a/extensions/StackTracePlus/LICENSE b/extensions/StackTracePlus/LICENSE new file mode 100644 index 0000000..49afdbb --- /dev/null +++ b/extensions/StackTracePlus/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2010 Ignacio Burgueño + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extensions/StackTracePlus/README.md b/extensions/StackTracePlus/README.md new file mode 100644 index 0000000..8216333 --- /dev/null +++ b/extensions/StackTracePlus/README.md @@ -0,0 +1,128 @@ +# StackTracePlus # + +[![Build Status](https://travis-ci.org/ignacio/StackTracePlus.png?branch=master)](https://travis-ci.org/ignacio/StackTracePlus) + +[StackTracePlus](https://github.com/ignacio/StackTracePlus) provides enhanced stack traces for [Lua 5.1, Lua 5.2][1] and [LuaJIT][2]. + +StackTracePlus can be used as a replacement for debug.traceback. It gives detailed information about locals, tries to guess +function names when they're not available, etc, so, instead of + + lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value + stack traceback: + D:\trunk_git\sources\stacktraceplus\test\test.lua:10: in function + (tail call): ? + D:\trunk_git\sources\stacktraceplus\test\test.lua:15: in main chunk + [C]: ? + +you'll get + + lua5.1.exe: D:\trunk_git\sources\stacktraceplus\test\test.lua:10: attempt to concatenate a nil value + Stack Traceback + =============== + (2) C function 'function: 00A8F418' + (3) Lua function 'g' at file 'D:\trunk_git\sources\stacktraceplus\test\test.lua:10' (best guess) + Local variables: + fun = table module + str = string: "hey" + tb = table: 027DCBE0 {dummy:1, blah:true, foo:bar} + (*temporary) = nil + (*temporary) = string: "text" + (*temporary) = string: "attempt to concatenate a nil value" + (4) tail call + (5) main chunk of file 'D:\trunk_git\sources\stacktraceplus\test\test.lua' at line 15 + (6) C function 'function: 002CA480' + +## Usage # + +StackTracePlus can be used as a replacement for `debug.traceback`, as an `xpcall` error handler or even from C code. Note that +only the Lua 5.1 interpreter allows the traceback function to be replaced "on the fly". LuaJIT and Lua 5.2 always calls luaL_traceback internally so there is no easy way to override that. + +```lua +local STP = require "StackTracePlus" + +debug.traceback = STP.stacktrace +function test() + local s = "this is a string" + local n = 42 + local t = { foo = "bar" } + local co = coroutine + local cr = coroutine.create + + error("an error") +end +test() +``` + +That script will output (only with Lua 5.1): + + lua5.1: example.lua:11: an error + Stack Traceback + =============== + (2) C function 'function: 006B5758' + (3) global C function 'error' + (4) Lua global 'test' at file 'example.lua:11' + Local variables: + s = string: "this is a string" + n = number: 42 + t = table: 006E5220 {foo:bar} + co = coroutine table + cr = C function: 003C7080 + (5) main chunk of file 'example.lua' at line 14 + (6) C function 'function: 00637B30' + +**StackTracePlus** is aware of the usual Lua libraries, like *coroutine*, *table*, *string*, *io*, etc and functions like +*print*, *pcall*, *assert*, and so on. + +You can also make STP aware of your own tables and functions by calling *add_known_function* and *add_known_table*. + +```lua +local STP = require "StackTracePlus" + +debug.traceback = STP.stacktrace +local my_table = { + f = function() end +} +function my_function() +end + +function test(data, func) + local s = "this is a string" + + error("an error") +end + +STP.add_known_table(my_table, "A description for my_table") +STP.add_known_function(my_function, "A description for my_function") + +test( my_table, my_function ) +``` + +Will output: + + lua5.1: ..\test\example2.lua:13: an error + Stack Traceback + =============== + (2) C function 'function: 0073AAA8' + (3) global C function 'error' + (4) Lua global 'test' at file '..\test\example2.lua:13' + Local variables: + data = A description for my_table + func = Lua function 'A description for my_function' (defined at line 7 of chunk ..\test\example2.lua) + s = string: "this is a string" + (5) main chunk of file '..\test\example2.lua' at line 19 + (6) C function 'function: 00317B30' + + +## Installation # +The easiest way to install is with [LuaRocks][3]. + + - luarocks install stacktraceplus + +If you don't want to use LuaRocks, just copy StackTracePlus.lua to Lua's path. + +## License # +**StackTracePlus** is available under the MIT license. + +[1]: http://www.lua.org/ +[2]: http://luajit.org/ +[3]: http://luarocks.org/ diff --git a/extensions/StackTracePlus/StackTracePlus.ext b/extensions/StackTracePlus/StackTracePlus.ext new file mode 100644 index 0000000..40c25c1 --- /dev/null +++ b/extensions/StackTracePlus/StackTracePlus.ext @@ -0,0 +1,411 @@ +-- tables +local _G = _G +local string, io, debug, coroutine = string, io, debug, coroutine + +-- functions +local tostring, print, require = tostring, print, require +local next, assert = next, assert +local pcall, type, pairs, ipairs = pcall, type, pairs, ipairs +local error = error + +assert(debug, "debug table must be available at this point") + +local io_open = io.open +local string_gmatch = string.gmatch +local string_sub = string.sub +local table_concat = table.concat + +local _M = { + max_tb_output_len = 70 -- controls the maximum length of the 'stringified' table before cutting with ' (more...)' +} + +-- this tables should be weak so the elements in them won't become uncollectable +local m_known_tables = { [_G] = "_G (global table)" } +local function add_known_module(name, desc) + local ok, mod = pcall(require, name) + if ok then + m_known_tables[mod] = desc + end +end + +add_known_module("string", "string module") +add_known_module("io", "io module") +add_known_module("os", "os module") +add_known_module("table", "table module") +add_known_module("math", "math module") +add_known_module("package", "package module") +add_known_module("debug", "debug module") +add_known_module("coroutine", "coroutine module") + +-- lua5.2 +add_known_module("bit32", "bit32 module") +-- luajit +add_known_module("bit", "bit module") +add_known_module("jit", "jit module") + + +local m_user_known_tables = {} + +local m_known_functions = {} +for _, name in ipairs{ + -- Lua 5.2, 5.1 + "assert", + "collectgarbage", + "dofile", + "error", + "getmetatable", + "ipairs", + "load", + "loadfile", + "next", + "pairs", + "pcall", + "print", + "rawequal", + "rawget", + "rawlen", + "rawset", + "require", + "select", + "setmetatable", + "tonumber", + "tostring", + "type", + "xpcall", + + -- Lua 5.1 + "gcinfo", + "getfenv", + "loadstring", + "module", + "newproxy", + "setfenv", + "unpack", + -- TODO: add table.* etc functions +} do + if _G[name] then + m_known_functions[_G[name]] = name + end +end + + + +local m_user_known_functions = {} + +local function safe_tostring (value) + local ok, err = pcall(tostring, value) + if ok then return err else return (": '%s'"):format(err) end +end + +-- Private: +-- Parses a line, looking for possible function definitions (in a very na?ve way) +-- Returns '(anonymous)' if no function name was found in the line +local function ParseLine(line) + assert(type(line) == "string") + --print(line) + local match = line:match("^%s*function%s+(%w+)") + if match then + --print("+++++++++++++function", match) + return match + end + match = line:match("^%s*local%s+function%s+(%w+)") + if match then + --print("++++++++++++local", match) + return match + end + match = line:match("^%s*local%s+(%w+)%s+=%s+function") + if match then + --print("++++++++++++local func", match) + return match + end + match = line:match("%s*function%s*%(") -- this is an anonymous function + if match then + --print("+++++++++++++function2", match) + return "(anonymous)" + end + return "(anonymous)" +end + +-- Private: +-- Tries to guess a function's name when the debug info structure does not have it. +-- It parses either the file or the string where the function is defined. +-- Returns '?' if the line where the function is defined is not found +local function GuessFunctionName(info) + --print("guessing function name") + if type(info.source) == "string" and info.source:sub(1,1) == "@" then + local file, err = io_open(info.source:sub(2), "r") + if not file then + print("file not found: "..tostring(err)) -- whoops! + return "?" + end + local line + for i = 1, info.linedefined do + line = file:read("*l") + end + if not line then + print("line not found") -- whoops! + return "?" + end + return ParseLine(line) + else + local line + local lineNumber = 0 + for l in string_gmatch(info.source, "([^\n]+)\n-") do + lineNumber = lineNumber + 1 + if lineNumber == info.linedefined then + line = l + break + end + end + if not line then + print("line not found") -- whoops! + return "?" + end + return ParseLine(line) + end +end + +--- +-- Dumper instances are used to analyze stacks and collect its information. +-- +local Dumper = {} + +Dumper.new = function(thread) + local t = { lines = {} } + for k,v in pairs(Dumper) do t[k] = v end + + t.dumping_same_thread = (thread == coroutine.running()) + + -- if a thread was supplied, bind it to debug.info and debug.get + -- we also need to skip this additional level we are introducing in the callstack (only if we are running + -- in the same thread we're inspecting) + if type(thread) == "thread" then + t.getinfo = function(level, what) + if t.dumping_same_thread and type(level) == "number" then + level = level + 1 + end + return debug.getinfo(thread, level, what) + end + t.getlocal = function(level, loc) + if t.dumping_same_thread then + level = level + 1 + end + return debug.getlocal(thread, level, loc) + end + else + t.getinfo = debug.getinfo + t.getlocal = debug.getlocal + end + + return t +end + +-- helpers for collecting strings to be used when assembling the final trace +function Dumper:add (text) + self.lines[#self.lines + 1] = text +end +function Dumper:add_f (fmt, ...) + self:add(fmt:format(...)) +end +function Dumper:concat_lines () + return table_concat(self.lines) +end + +--- +-- Private: +-- Iterates over the local variables of a given function. +-- +-- @param level The stack level where the function is. +-- +function Dumper:DumpLocals (level) + local prefix = "\t " + local i = 1 + + if self.dumping_same_thread then + level = level + 1 + end + + local name, value = self.getlocal(level, i) + if not name then + return + end + self:add("\tLocal variables:\r\n") + while name do + if type(value) == "number" then + self:add_f("%s%s = number: %g\r\n", prefix, name, value) + elseif type(value) == "boolean" then + self:add_f("%s%s = boolean: %s\r\n", prefix, name, tostring(value)) + elseif type(value) == "string" then + self:add_f("%s%s = string: %q\r\n", prefix, name, value) + elseif type(value) == "userdata" then + self:add_f("%s%s = %s\r\n", prefix, name, safe_tostring(value)) + elseif type(value) == "nil" then + self:add_f("%s%s = nil\r\n", prefix, name) + elseif type(value) == "table" then + if m_known_tables[value] then + self:add_f("%s%s = %s\r\n", prefix, name, m_known_tables[value]) + elseif m_user_known_tables[value] then + self:add_f("%s%s = %s\r\n", prefix, name, m_user_known_tables[value]) + else + local txt = "{" + for k,v in pairs(value) do + txt = txt..safe_tostring(k)..":"..safe_tostring(v) + if #txt > _M.max_tb_output_len then + txt = txt.." (more...)" + break + end + if next(value, k) then txt = txt..", " end + end + self:add_f("%s%s = %s %s\r\n", prefix, name, safe_tostring(value), txt.."}") + end + elseif type(value) == "function" then + local info = self.getinfo(value, "nS") + local fun_name = info.name or m_known_functions[value] or m_user_known_functions[value] + if info.what == "C" then + self:add_f("%s%s = C %s\r\n", prefix, name, (fun_name and ("function: " .. fun_name) or tostring(value))) + else + local source = info.short_src + if source:sub(2,7) == "string" then + source = source:sub(9) -- uno m?s, por el espacio que viene (string "Baragent.Main", por ejemplo) + end + --for k,v in pairs(info) do print(k,v) end + fun_name = fun_name or GuessFunctionName(info) + self:add_f("%s%s = Lua function '%s' (defined at line %d of chunk %s)\r\n", prefix, name, fun_name, info.linedefined, source) + end + elseif type(value) == "thread" then + self:add_f("%sthread %q = %s\r\n", prefix, name, tostring(value)) + end + i = i + 1 + name, value = self.getlocal(level, i) + end +end + + +--- +-- Public: +-- Collects a detailed stack trace, dumping locals, resolving function names when they're not available, etc. +-- This function is suitable to be used as an error handler with pcall or xpcall +-- +-- @param thread An optional thread whose stack is to be inspected (defaul is the current thread) +-- @param message An optional error string or object. +-- @param level An optional number telling at which level to start the traceback (default is 1) +-- +-- Returns a string with the stack trace and a string with the original error. +-- +function _M.stacktrace(thread, message, level) + if type(thread) ~= "thread" then + -- shift parameters left + thread, message, level = nil, thread, message + end + + thread = thread or coroutine.running() + + level = level or 1 + + local dumper = Dumper.new(thread) + + local original_error + + if type(message) == "table" then + dumper:add("an error object {\r\n") + local first = true + for k,v in pairs(message) do + if first then + dumper:add(" ") + first = false + else + dumper:add(",\r\n ") + end + dumper:add(safe_tostring(k)) + dumper:add(": ") + dumper:add(safe_tostring(v)) + end + dumper:add("\r\n}") + original_error = dumper:concat_lines() + elseif type(message) == "string" then + dumper:add(message) + original_error = message + end + + dumper:add("\r\n") + dumper:add[[ +Stack Traceback +=============== +]] + --print(error_message) + + local level_to_show = level + if dumper.dumping_same_thread then level = level + 1 end + + local info = dumper.getinfo(level, "nSlf") + while info do + if info.what == "main" then + if string_sub(info.source, 1, 1) == "@" then + dumper:add_f("(%d) main chunk of file '%s' at line %d\r\n", level_to_show, string_sub(info.source, 2), info.currentline) + else + dumper:add_f("(%d) main chunk of %s at line %d\r\n", level_to_show, info.short_src, info.currentline) + end + elseif info.what == "C" then + --print(info.namewhat, info.name) + --for k,v in pairs(info) do print(k,v, type(v)) end + local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name or tostring(info.func) + dumper:add_f("(%d) %s C function '%s'\r\n", level_to_show, info.namewhat, function_name) + --dumper:add_f("%s%s = C %s\r\n", prefix, name, (m_known_functions[value] and ("function: " .. m_known_functions[value]) or tostring(value))) + elseif info.what == "tail" then + --print("tail") + --for k,v in pairs(info) do print(k,v, type(v)) end--print(info.namewhat, info.name) + dumper:add_f("(%d) tail call\r\n", level_to_show) + dumper:DumpLocals(level) + elseif info.what == "Lua" then + local source = info.short_src + local function_name = m_user_known_functions[info.func] or m_known_functions[info.func] or info.name + if source:sub(2, 7) == "string" then + source = source:sub(9) + end + local was_guessed = false + if not function_name or function_name == "?" then + --for k,v in pairs(info) do print(k,v, type(v)) end + function_name = GuessFunctionName(info) + was_guessed = true + end + -- test if we have a file name + local function_type = (info.namewhat == "") and "function" or info.namewhat + if info.source and info.source:sub(1, 1) == "@" then + dumper:add_f("(%d) Lua %s '%s' at file '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") + elseif info.source and info.source:sub(1,1) == '#' then + dumper:add_f("(%d) Lua %s '%s' at template '%s:%d'%s\r\n", level_to_show, function_type, function_name, info.source:sub(2), info.currentline, was_guessed and " (best guess)" or "") + else + dumper:add_f("(%d) Lua %s '%s' at line %d of chunk '%s'\r\n", level_to_show, function_type, function_name, info.currentline, source) + end + dumper:DumpLocals(level) + else + dumper:add_f("(%d) unknown frame %s\r\n", level_to_show, info.what) + end + + level = level + 1 + level_to_show = level_to_show + 1 + info = dumper.getinfo(level, "nSlf") + end + + return dumper:concat_lines(), original_error +end + +-- +-- Adds a table to the list of known tables +function _M.add_known_table(tab, description) + if m_known_tables[tab] then + error("Cannot override an already known table") + end + m_user_known_tables[tab] = description +end + +-- +-- Adds a function to the list of known functions +function _M.add_known_function(fun, description) + if m_known_functions[fun] then + error("Cannot override an already known function") + end + m_user_known_functions[fun] = description +end + +return _M diff --git a/extensions/_Misc.ext b/extensions/_Misc.ext index 256e3af..de3258c 100644 --- a/extensions/_Misc.ext +++ b/extensions/_Misc.ext @@ -8,3 +8,7 @@ -- Randomize random math.randomseed(tonumber(tostring(os.time()):reverse():sub(1,6))) + +-- Set debug.traceback to use StackTracePlus to print full stack trace +local trace = require("StackTracePlus") +debug.traceback = trace.stacktrace