mirror of
https://github.com/azerothcore/mod-ale
synced 2025-11-29 15:38:17 +08:00
Add StackTracePlus, Rewrite not to recreate eluna on reload
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1046,7 +1046,7 @@ namespace LuaGlobalFunctions
|
||||
*/
|
||||
int ReloadEluna(Eluna* /*E*/, lua_State* /*L*/)
|
||||
{
|
||||
Eluna::reload = true;
|
||||
Eluna::ReloadEluna();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
305
LuaEngine.cpp
305
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<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)
|
||||
|
||||
100
LuaEngine.h
100
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<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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
21
extensions/StackTracePlus/LICENSE
Normal file
21
extensions/StackTracePlus/LICENSE
Normal 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.
|
||||
128
extensions/StackTracePlus/README.md
Normal file
128
extensions/StackTracePlus/README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# StackTracePlus #
|
||||
|
||||
[](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/
|
||||
411
extensions/StackTracePlus/StackTracePlus.ext
Normal file
411
extensions/StackTracePlus/StackTracePlus.ext
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user