Add StackTracePlus, Rewrite not to recreate eluna on reload

This commit is contained in:
Rochet2
2015-01-14 01:03:16 +02:00
parent e8d840ace9
commit 3d645d8048
13 changed files with 924 additions and 198 deletions

View File

@@ -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;

View File

@@ -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<uint64, LuaEvent*>(m_time + event->delay, event));
eventMap[event->funcRef] = event;
eventList.insert(std::pair<uint64, LuaEvent*>(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)

View File

@@ -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;

View File

@@ -1046,7 +1046,7 @@ namespace LuaGlobalFunctions
*/
int ReloadEluna(Eluna* /*E*/, lua_State* /*L*/)
{
Eluna::reload = true;
Eluna::ReloadEluna();
return 0;
}

View File

@@ -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<Hooks::ServerEvents>("ServerEvents", *this)),
PlayerEventBindings(new EventBind<Hooks::PlayerEvents>("PlayerEvents", *this)),
GuildEventBindings(new EventBind<Hooks::GuildEvents>("GuildEvents", *this)),
GroupEventBindings(new EventBind<Hooks::GroupEvents>("GroupEvents", *this)),
VehicleEventBindings(new EventBind<Hooks::VehicleEvents>("VehicleEvents", *this)),
BGEventBindings(new EventBind<Hooks::BGEvents>("BGEvents", *this)),
ServerEventBindings(NULL),
PlayerEventBindings(NULL),
GuildEventBindings(NULL),
GroupEventBindings(NULL),
VehicleEventBindings(NULL),
BGEventBindings(NULL),
PacketEventBindings(new EntryBind<Hooks::PacketEvents>("PacketEvents", *this)),
CreatureEventBindings(new EntryBind<Hooks::CreatureEvents>("CreatureEvents", *this)),
CreatureGossipBindings(new EntryBind<Hooks::GossipEvents>("GossipEvents (creature)", *this)),
GameObjectEventBindings(new EntryBind<Hooks::GameObjectEvents>("GameObjectEvents", *this)),
GameObjectGossipBindings(new EntryBind<Hooks::GossipEvents>("GossipEvents (gameobject)", *this)),
ItemEventBindings(new EntryBind<Hooks::ItemEvents>("ItemEvents", *this)),
ItemGossipBindings(new EntryBind<Hooks::GossipEvents>("GossipEvents (item)", *this)),
playerGossipBindings(new EntryBind<Hooks::GossipEvents>("GossipEvents (player)", *this)),
PacketEventBindings(NULL),
CreatureEventBindings(NULL),
CreatureGossipBindings(NULL),
GameObjectEventBindings(NULL),
GameObjectGossipBindings(NULL),
ItemEventBindings(NULL),
ItemGossipBindings(NULL),
playerGossipBindings(NULL),
CreatureUniqueBindings(new UniqueBind<Hooks::CreatureEvents>("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<Hooks::CreatureEvents>("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<Hooks::ServerEvents>("ServerEvents", *this);
PlayerEventBindings = new EventBind<Hooks::PlayerEvents>("PlayerEvents", *this);
GuildEventBindings = new EventBind<Hooks::GuildEvents>("GuildEvents", *this);
GroupEventBindings = new EventBind<Hooks::GroupEvents>("GroupEvents", *this);
VehicleEventBindings = new EventBind<Hooks::VehicleEvents>("VehicleEvents", *this);
BGEventBindings = new EventBind<Hooks::BGEvents>("BGEvents", *this);
// Replace this with map remove if making multithread version
//
PacketEventBindings = new EntryBind<Hooks::PacketEvents>("PacketEvents", *this);
CreatureEventBindings = new EntryBind<Hooks::CreatureEvents>("CreatureEvents", *this);
CreatureGossipBindings = new EntryBind<Hooks::GossipEvents>("GossipEvents (creature)", *this);
GameObjectEventBindings = new EntryBind<Hooks::GameObjectEvents>("GameObjectEvents", *this);
GameObjectGossipBindings = new EntryBind<Hooks::GossipEvents>("GossipEvents (gameobject)", *this);
ItemEventBindings = new EntryBind<Hooks::ItemEvents>("ItemEvents", *this);
ItemGossipBindings = new EntryBind<Hooks::GossipEvents>("GossipEvents (item)", *this);
playerGossipBindings = new EntryBind<Hooks::GossipEvents>("GossipEvents (player)", *this);
CreatureUniqueBindings = new UniqueBind<Hooks::CreatureEvents>("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<std::string, std::string> 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)

View File

@@ -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<LuaScript> ScriptList;
#ifdef TRINITY
typedef std::recursive_mutex LockType;
typedef std::lock_guard<LockType> Guard;
#else
typedef ACE_Recursive_Thread_Mutex LockType;
typedef ACE_Guard<LockType> 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<typename T> int SetupStack(EventBind<T>* event_bindings, EntryBind<T>* entry_bindings, UniqueBind<T>* guid_bindings, T event_id, uint32 entry, uint64 guid, uint32 instanceId, int number_of_arguments);
@@ -163,25 +213,9 @@ private:
}
public:
typedef std::list<LuaScript> ScriptList;
static Eluna* GEluna;
static bool reload;
static bool initialized;
#ifdef TRINITY
typedef std::recursive_mutex LockType;
typedef std::lock_guard<LockType> Guard;
#else
typedef ACE_Recursive_Thread_Mutex LockType;
typedef ACE_Guard<LockType> Guard;
#endif
static LockType lock;
lua_State* L;
uint32 event_level;
EventMgr* eventMgr;
EventBind<Hooks::ServerEvents>* ServerEventBindings;
@@ -202,26 +236,12 @@ public:
UniqueBind<Hooks::CreatureEvents>* 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<typename T>
static void Push(lua_State* luastate, T const* ptr)
{
ElunaTemplate<T>::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<WorldObject>(lua_State* L, int narg, boo
template<> ElunaObject* Eluna::CHECKOBJ<ElunaObject>(lua_State* L, int narg, bool error);
#define sEluna Eluna::GEluna
#define LOCK_ELUNA Eluna::Guard __guard(Eluna::lock)
#endif

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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)

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2010 Ignacio Burgue<75>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.

View File

@@ -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 <D:\trunk_git\sources\stacktraceplus\test\test.lua:7>
(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/

View File

@@ -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 ("<failed to get printable value>: '%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

View File

@@ -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