mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
401 lines
13 KiB
C++
401 lines
13 KiB
C++
#pragma once
|
|
|
|
#include "V8Tree/Instance.h"
|
|
#include "V8Tree/Service.h"
|
|
#include "script/ThreadRef.h"
|
|
#include "script/ScriptContext.h"
|
|
|
|
struct lua_State;
|
|
struct lua_Debug;
|
|
struct Table;
|
|
|
|
namespace RBX
|
|
{
|
|
class Script;
|
|
class ModuleScript;
|
|
class DataModel;
|
|
|
|
namespace Scripting
|
|
{
|
|
class ScriptDebugger;
|
|
|
|
enum BreakOnErrorMode
|
|
{
|
|
BreakOnErrorMode_Never = 0,
|
|
BreakOnErrorMode_AllExceptions,
|
|
BreakOnErrorMode_UnhandledExceptions
|
|
};
|
|
|
|
enum ExecutionMode
|
|
{
|
|
ExecutionMode_Continue = 0,
|
|
ExecutionMode_Break
|
|
};
|
|
|
|
struct ISpecialBreakpoint
|
|
{
|
|
virtual ~ISpecialBreakpoint() {}
|
|
virtual bool hitTest(lua_State* L, lua_Debug *ar) = 0;
|
|
lua_State* baseThread;
|
|
};
|
|
|
|
extern const char* const sDebuggerManager;
|
|
// Contains all data related to Lua debugging of Scripts
|
|
class DebuggerManager
|
|
: public DescribedNonCreatable<DebuggerManager, Instance, sDebuggerManager, Reflection::ClassDescriptor::INTERNAL_LOCAL, Security::LocalUser>
|
|
{
|
|
typedef DescribedNonCreatable<DebuggerManager, Instance, sDebuggerManager, Reflection::ClassDescriptor::INTERNAL_LOCAL, Security::LocalUser> Super;
|
|
public:
|
|
typedef boost::unordered_map<const Instance*, ScriptDebugger*> Debuggers;
|
|
private:
|
|
bool enabled;
|
|
Debuggers debuggers;
|
|
|
|
rbx::signals::connection errorSignalConnection;
|
|
rbx::signals::connection descendantAddedSignalConnection;
|
|
|
|
typedef boost::unordered_map<const Instance*, boost::shared_ptr<ScriptDebugger> > UnaddedDebuggers;
|
|
UnaddedDebuggers unaddedDebuggers;
|
|
|
|
typedef boost::unordered_map<const lua_State*, ScriptDebugger*> DebuggersLookup;
|
|
DebuggersLookup debuggersLookup;
|
|
|
|
RBX::DataModel *dataModel;
|
|
BreakOnErrorMode breakOnErrorMode;
|
|
|
|
boost::scoped_ptr<ISpecialBreakpoint> specialBreakpoint;
|
|
ExecutionMode executionMode;
|
|
std::list<Lua::WeakThreadRef> pausedThreads, resumingPausedThreads, errorThreads;
|
|
bool resuming;
|
|
bool scriptAutoResume;
|
|
|
|
public:
|
|
DebuggerManager();
|
|
~DebuggerManager();
|
|
|
|
static DebuggerManager& singleton();
|
|
|
|
void setDataModel(RBX::DataModel *pDataModel);
|
|
RBX::DataModel* getDataModel();
|
|
|
|
void enableDebugging();
|
|
bool getEnabled() const { return enabled; }
|
|
|
|
BreakOnErrorMode getBreakOnErrorMode() const { return breakOnErrorMode; }
|
|
void setBreakOnErrorMode(BreakOnErrorMode mode);
|
|
|
|
static Reflection::Variant readWatchValue(std::string expression, int stackFrame, lua_State* L);
|
|
|
|
const Debuggers& getDebuggers()
|
|
{
|
|
return debuggers;
|
|
}
|
|
shared_ptr<const Instances> getDebuggers_Reflection();
|
|
|
|
ScriptDebugger* findDebugger(lua_State* L);
|
|
ScriptDebugger* findDebugger(Instance* script);
|
|
|
|
shared_ptr<ScriptDebugger> addDebugger(Instance* script);
|
|
shared_ptr<Instance> addDebugger_Reflection(shared_ptr<Instance> script);
|
|
void addDebugger(shared_ptr<ScriptDebugger> debugger);
|
|
|
|
void populateForLookup(lua_State* L, ScriptDebugger* debugger);
|
|
|
|
void pause();
|
|
void resume();
|
|
void stepOver();
|
|
void stepInto();
|
|
void stepOut();
|
|
|
|
void reset();
|
|
void setScriptAutoResume(bool state) { scriptAutoResume = state; }
|
|
|
|
static void hook(lua_State* L, lua_Debug *ar);
|
|
|
|
rbx::signal<void(shared_ptr<Instance>)> debuggerAdded;
|
|
rbx::signal<void(shared_ptr<Instance>)> debuggerRemoved;
|
|
|
|
protected:
|
|
/*override*/ bool askForbidChild(const Instance* instance) const;
|
|
/*override*/ void verifyAddChild(const Instance* newChild) const;
|
|
/*override*/ void onChildAdded(Instance* child);
|
|
/*override*/ void onChildRemoved(Instance* child);
|
|
/*override*/ void onChildChanged(Instance* instance, const PropertyChanged& event);
|
|
|
|
void addScriptDebugger(Instance* instance);
|
|
void onErrorSignal(lua_State* L);
|
|
void onHook(lua_State* L, lua_Debug *ar);
|
|
|
|
void addUnaddedDebuggerForAddedDescendant(shared_ptr<RBX::Instance> instance);
|
|
};
|
|
|
|
class DebuggerBreakpoint;
|
|
class DebuggerWatch;
|
|
|
|
extern const char* const sScriptDebugger;
|
|
// Debugs an RBX::Script
|
|
class ScriptDebugger
|
|
: public DescribedCreatable<ScriptDebugger, Instance, sScriptDebugger, Reflection::ClassDescriptor::PERSISTENT_HIDDEN, Security::LocalUser>
|
|
{
|
|
public:
|
|
typedef boost::unordered_map<int, DebuggerBreakpoint*> Breakpoints;
|
|
typedef std::vector<DebuggerWatch*> Watches;
|
|
struct PausedThreadData;
|
|
typedef boost::unordered_map<long, PausedThreadData> PausedThreads;
|
|
private:
|
|
typedef DescribedCreatable<ScriptDebugger, Instance, sScriptDebugger, Reflection::ClassDescriptor::PERSISTENT_HIDDEN, Security::LocalUser> Super;
|
|
|
|
Breakpoints breakpoints;
|
|
Watches watches;
|
|
|
|
boost::scoped_ptr<ISpecialBreakpoint> specialBreakpoint;
|
|
|
|
shared_ptr<Instance> script;
|
|
rbx::signals::scoped_connection scriptStartedConnection;
|
|
rbx::signals::scoped_connection scriptStoppedConnection;
|
|
rbx::signals::scoped_connection scriptParentChangedConnection;
|
|
rbx::signals::scoped_connection scriptClonedConnection;
|
|
Lua::WeakThreadRef rootThread; // The root thread of script. Set when the Script starts and reset when it stops
|
|
|
|
typedef boost::function<void(lua_State* L, lua_Debug *ar)> HookFunction;
|
|
HookFunction hookFunction; // used to overload the hook function
|
|
|
|
Lua::WeakThreadRef pausedThread; // the thread that a breakpoint hit
|
|
Lua::WeakThreadRef errorThread; // the thread that has error
|
|
lua_Debug *breakpointHookData; // set for a short period of time during the hook when we encounter a breakpoint
|
|
void* globalRawScriptPtr;
|
|
Table* prevFuncTable;
|
|
void* prevRawScriptPtr;
|
|
int currentLine; // the current line when a breakpoint is hit
|
|
bool ignoreDebuggerBreak; // whether to ignore breakpoint at the current line
|
|
long currentThreadID;
|
|
PausedThreads pausedThreads;
|
|
bool rootThreadResumed;
|
|
|
|
public:
|
|
ScriptDebugger()
|
|
:currentLine(0)
|
|
,breakpointHookData(NULL)
|
|
,globalRawScriptPtr(NULL)
|
|
,prevFuncTable(NULL)
|
|
,prevRawScriptPtr(NULL)
|
|
,ignoreDebuggerBreak(false)
|
|
,currentThreadID(0)
|
|
,rootThreadResumed(false)
|
|
{}
|
|
ScriptDebugger(Instance& script);
|
|
~ScriptDebugger();
|
|
|
|
Instance* getScript() const { return script.get(); }
|
|
|
|
void setScript(Script* value);
|
|
void setScript(ModuleScript* value);
|
|
|
|
std::string getScriptPath() const;
|
|
void setScriptPath(std::string scriptPath);
|
|
|
|
void setIgnoreDebuggerBreak(bool state) { ignoreDebuggerBreak = state; }
|
|
|
|
DebuggerBreakpoint* findBreakpoint(int line);
|
|
shared_ptr<DebuggerBreakpoint> setBreakpoint(int line);
|
|
shared_ptr<Instance> setBreakpoint_Reflection(int line);
|
|
const Breakpoints& getBreakpoints()
|
|
{
|
|
return breakpoints;
|
|
}
|
|
shared_ptr<const Instances> getBreakpoints_Reflection();
|
|
|
|
shared_ptr<DebuggerWatch> addWatch(std::string expression);
|
|
shared_ptr<Instance> addWatch_Reflection(std::string expression);
|
|
const Watches& getWatches()
|
|
{
|
|
return watches;
|
|
}
|
|
shared_ptr<const Instances> getWatches_Reflection();
|
|
Reflection::Variant getWatchValue(DebuggerWatch* watch, int stackFrame = 0);
|
|
Reflection::Variant getWatchValue_Reflection(shared_ptr<Instance> watch);
|
|
|
|
Reflection::Variant getKeyValue(std::string key, int stackFrame);
|
|
|
|
bool isDebugging() const
|
|
{
|
|
return !rootThread.empty();
|
|
}
|
|
|
|
bool isPaused() const;
|
|
|
|
bool hasError() const
|
|
{
|
|
return !errorThread.empty();
|
|
}
|
|
|
|
int getCurrentLine() const
|
|
{
|
|
return currentLine;
|
|
}
|
|
void pause();
|
|
void resume();
|
|
void resumeTo(int line);
|
|
void stepOver();
|
|
void stepInto();
|
|
void stepOut();
|
|
|
|
struct FunctionInfo
|
|
{
|
|
boost::shared_ptr<RBX::Instance> script;
|
|
int frame;
|
|
std::string name;
|
|
std::string what;
|
|
std::string namewhat;
|
|
std::string short_src;
|
|
int currentline;
|
|
int linedefined;
|
|
int lastlinedefined;
|
|
};
|
|
typedef std::vector<FunctionInfo> Stack;
|
|
Stack getStack();
|
|
shared_ptr<const Reflection::ValueArray> getStack_Reflection();
|
|
shared_ptr<const Reflection::ValueMap> getLocals(int stackIndex);
|
|
shared_ptr<const Reflection::ValueMap> getUpvalues(int stackIndex);
|
|
shared_ptr<const Reflection::ValueMap> getGlobals();
|
|
void setLocal(std::string name, Reflection::Variant value, int stackFrame = 0);
|
|
void setUpvalue(std::string name, Reflection::Variant value, int stackFrame = 0);
|
|
void setGlobal(std::string name, Reflection::Variant value);
|
|
|
|
void handleError(lua_State* L);
|
|
void updateHook();
|
|
|
|
ScriptContext::Result resumeThread(lua_State* L, bool evalLineHookForCurrentLine = false);
|
|
bool handleHook(lua_State* L, lua_Debug *ar);
|
|
bool onLineHook(lua_State* L, lua_Debug *ar);
|
|
void debuggerBreak(lua_State* L, lua_Debug *ar);
|
|
|
|
struct PausedThreadData
|
|
{
|
|
int pausedLine;
|
|
Lua::WeakThreadRef thread;
|
|
|
|
bool hasError;
|
|
Stack callStack;
|
|
std::string errorMessage;
|
|
|
|
PausedThreadData()
|
|
:hasError(false)
|
|
,pausedLine(0)
|
|
{
|
|
}
|
|
};
|
|
const PausedThreads& getPausedThreads() { return pausedThreads; }
|
|
|
|
bool isPausedThread(long threadID);
|
|
bool isErrorThread(long threadID);
|
|
|
|
bool isRootThread(long threadID);
|
|
bool isRootThreadResumed() { return rootThreadResumed; }
|
|
|
|
void setCurrentThread(long threadID);
|
|
long getCurrentThread() { return currentThreadID; }
|
|
|
|
rbx::signal<void(int)> encounteredBreak;
|
|
rbx::signal<void()> resuming;
|
|
|
|
rbx::signal<void(shared_ptr<Instance>)> breakpointAdded;
|
|
rbx::signal<void(shared_ptr<Instance>)> breakpointRemoved;
|
|
rbx::signal<void(shared_ptr<Instance>)> watchAdded;
|
|
rbx::signal<void(shared_ptr<Instance>)> watchRemoved;
|
|
rbx::signal<void(int, std::string, Stack)> scriptErrorDetected;
|
|
|
|
protected:
|
|
/*override*/ bool askForbidChild(const Instance* instance) const;
|
|
/*override*/ void verifySetParent(const Instance* newParent) const;
|
|
/*override*/ void verifyAddChild(const Instance* newChild) const;
|
|
/*override*/ void onChildAdded(Instance* child);
|
|
/*override*/ void onChildRemoved(Instance* child);
|
|
|
|
private:
|
|
void onScriptStarting(lua_State* L);
|
|
void onScriptStopped();
|
|
void onScriptParentChanged(shared_ptr<RBX::Instance> newParent);
|
|
void onScriptCloned(boost::shared_ptr<Instance> clonedScript);
|
|
bool shouldBreak(DebuggerBreakpoint* bp, lua_State* L);
|
|
bool hasDifferentScriptInstances(lua_State* L);
|
|
void handleError(std::string errorMessage, const Stack& stack = Stack());
|
|
void setScript(Instance* value);
|
|
boost::shared_ptr<ScriptDebugger> createClone(boost::shared_ptr<Instance> clonedScript);
|
|
|
|
template<class R>
|
|
void withPausedThreadHook(lua_State* L, lua_Debug *ar, boost::function<R(lua_State* L, lua_Debug *ar)> f, R& r, shared_ptr<std::string>& error);
|
|
|
|
// TODO: template specialization for R=void
|
|
template<class R>
|
|
R withPausedThread(boost::function<R(lua_State* L, lua_Debug *ar)> f);
|
|
static shared_ptr<Reflection::ValueMap> readLocals(int stackIndex, lua_State* L);
|
|
static shared_ptr<Reflection::ValueMap> readUpvalues(int stackIndex, lua_State* L);
|
|
static shared_ptr<Reflection::ValueMap> readGlobals(lua_State* L);
|
|
static Stack readStack(lua_State* L);
|
|
static RBX::Instance* getScriptForLuaState(lua_State* L);
|
|
static void updateRootThread(ScriptDebugger* scriptDebugger, lua_State *L);
|
|
static void setLuaHook(ScriptDebugger* scriptDebugger, int hookMask, lua_State *L);
|
|
};
|
|
|
|
extern const char* const sDebuggerBreakpoint;
|
|
class DebuggerBreakpoint
|
|
: public DescribedCreatable<DebuggerBreakpoint, Instance, sDebuggerBreakpoint, Reflection::ClassDescriptor::PERSISTENT_HIDDEN, Security::LocalUser>
|
|
{
|
|
bool enabled;
|
|
int line;
|
|
std::string condition;
|
|
public:
|
|
DebuggerBreakpoint();
|
|
DebuggerBreakpoint(int line);
|
|
~DebuggerBreakpoint();
|
|
|
|
int getLine() const { return line; }
|
|
|
|
bool isEnabled() const { return enabled; }
|
|
|
|
const std::string& getCondition() const { return condition; }
|
|
|
|
static Reflection::BoundProp<bool> prop_Enabled;
|
|
static Reflection::BoundProp<std::string> prop_Condition;
|
|
|
|
protected:
|
|
/*override*/ void verifySetParent(const Instance* newParent) const;
|
|
/*override*/ bool askForbidChild(const Instance* instance) const { return true; }
|
|
/*override*/ void verifyAddChild(const Instance* newChild) const
|
|
{
|
|
throw std::runtime_error("DebuggerBreakpoint can have no children");
|
|
}
|
|
private:
|
|
void setLine(int line);
|
|
static Reflection::BoundProp<int> prop_Line_Data;
|
|
|
|
};
|
|
|
|
extern const char* const sDebuggerWatch;
|
|
class DebuggerWatch
|
|
: public DescribedCreatable<DebuggerWatch, Instance, sDebuggerWatch, Reflection::ClassDescriptor::PERSISTENT, Security::LocalUser>
|
|
{
|
|
std::string expression;
|
|
public:
|
|
DebuggerWatch() {}
|
|
DebuggerWatch(std::string expression);
|
|
const std::string& getCondition() const { return expression; }
|
|
void checkExpressionSyntax();
|
|
|
|
const std::string& getExpression() const { return expression; }
|
|
|
|
static Reflection::BoundProp<std::string> prop_Expression;
|
|
|
|
protected:
|
|
/*override*/ void verifySetParent(const Instance* newParent) const;
|
|
/*override*/ bool askForbidChild(const Instance* instance) const { return true; }
|
|
/*override*/ void verifyAddChild(const Instance* newChild) const
|
|
{
|
|
throw std::runtime_error("DebuggerWatch can have no children");
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|