480 lines
11 KiB
C++
480 lines
11 KiB
C++
#include "context_cache.h"
|
|
#include "main/logging.h"
|
|
#include "compat/misc.h"
|
|
#include "str_util.h"
|
|
#include "files.h"
|
|
#include "util/format.h"
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <set>
|
|
#include <stack>
|
|
#include "main/references.h"
|
|
|
|
#ifndef MAX_CACHED_CALLS
|
|
#define MAX_CACHED_CALLS 512
|
|
#endif
|
|
|
|
#ifndef MAX_AS_CALLSTACK_PRINT_DEPTH
|
|
#define MAX_AS_CALLSTACK_PRINT_DEPTH 64
|
|
#endif
|
|
|
|
#ifdef PROFILE_EXECUTION
|
|
|
|
#include "../main/references.h"
|
|
|
|
struct ScriptStep {
|
|
asIScriptFunction* func;
|
|
int line;
|
|
mutable unsigned long long count;
|
|
|
|
bool operator<(const ScriptStep& other) const {
|
|
if(count > other.count)
|
|
return true;
|
|
else if(count == other.count) {
|
|
if(func > other.func)
|
|
return true;
|
|
else if(func == other.func && line > other.line)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool operator==(const ScriptStep& other) const {
|
|
return func == other.func && line == other.line;
|
|
}
|
|
|
|
ScriptStep() : func(0), line(0), count(0) {}
|
|
ScriptStep(asIScriptFunction* f, int l) : func(f), line(l), count(0) {}
|
|
};
|
|
|
|
namespace std {
|
|
template<>
|
|
struct hash<ScriptStep> {
|
|
size_t operator()(const ScriptStep& step) const {
|
|
return (size_t)step.func ^ (size_t)step.line;
|
|
};
|
|
};
|
|
};
|
|
#endif
|
|
|
|
bool LOG_CHATTY = false;
|
|
bool LOG_ERRORLOG = true;
|
|
|
|
namespace scripts {
|
|
|
|
struct ContextCache {
|
|
asIScriptContext* menuCtx;
|
|
asIScriptContext* clientCtx;
|
|
asIScriptContext* serverCtx;
|
|
|
|
ContextCache() : menuCtx(0), clientCtx(0), serverCtx(0) {
|
|
}
|
|
|
|
void reset(bool resetMenu = false) {
|
|
if(resetMenu && menuCtx) {
|
|
menuCtx->Release();
|
|
menuCtx = 0;
|
|
}
|
|
|
|
if(clientCtx) {
|
|
clientCtx->Release();
|
|
clientCtx = 0;
|
|
}
|
|
|
|
if(serverCtx) {
|
|
serverCtx->Release();
|
|
serverCtx = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
Threaded(ContextCache*) ctxCache = 0;
|
|
|
|
//Special logging/profiling systems via line callback
|
|
#ifdef DO_LINE_CALLBACK
|
|
|
|
#ifdef PROFILE_EXECUTION
|
|
threads::atomic_int profile_step;
|
|
threads::Mutex profile_lock;
|
|
|
|
typedef std::unordered_set<ScriptStep> scriptSteps;
|
|
std::unordered_map<asIScriptEngine*,scriptSteps*> scriptSets;
|
|
|
|
void logScriptProfile(asIScriptEngine* engine) {
|
|
profile_lock.lock();
|
|
auto iSet = scriptSets.find(engine);
|
|
if(iSet != scriptSets.end()) {
|
|
//Order steps by number of hits
|
|
std::set<ScriptStep> orderedSteps;
|
|
for(auto i = iSet->second->begin(), end = iSet->second->end(); i != end; ++i)
|
|
orderedSteps.insert(*i);
|
|
//We don't need the shared data now
|
|
profile_lock.release();
|
|
|
|
//Print line counts that registered more than 10 counts
|
|
for(auto i = orderedSteps.begin(), end = orderedSteps.end(); i != end && i->count > 10; ++i)
|
|
print("\t%8lld: %s line %d in %s", i->count, i->func->GetScriptSectionName(), i->line, i->func->GetName());
|
|
}
|
|
else {
|
|
profile_lock.release();
|
|
}
|
|
}
|
|
|
|
Threaded(double*) lastStep = 0;
|
|
|
|
#endif
|
|
|
|
void TraceExec(asIScriptContext *ctx, void *arg)
|
|
{
|
|
#ifdef LOG_EXECUTION
|
|
std::string msg;
|
|
for( asUINT n = 0; n < ctx->GetCallstackSize(); n++ )
|
|
msg += " ";
|
|
msg += ctx->GetFunction()->GetDeclaration();
|
|
if(void* ptr = ctx->GetThisPointer()) {
|
|
msg += "@";
|
|
msg += toString((unsigned int)ptr);
|
|
}
|
|
msg += " Line: ";
|
|
msg += toString(ctx->GetLineNumber());
|
|
print(msg);
|
|
#endif
|
|
#ifdef PROFILE_EXECUTION
|
|
if(++profile_step % PROFILE_EXECUTION == 0) {
|
|
double*& pLastTime = lastStep;
|
|
if(pLastTime == 0)
|
|
pLastTime = new double(0);
|
|
|
|
double time = devices.driver->getAccurateTime();
|
|
if(time - *pLastTime > 0.000001) {
|
|
*pLastTime = time;
|
|
ScriptStep step(ctx->GetFunction(),ctx->GetLineNumber());
|
|
profile_lock.lock();
|
|
|
|
auto iSet = scriptSets.find(ctx->GetEngine());
|
|
scriptSteps* set;
|
|
|
|
if(iSet != scriptSets.end()) {
|
|
set = iSet->second;
|
|
}
|
|
else {
|
|
set = new scriptSteps;
|
|
scriptSets[ctx->GetEngine()] = set;
|
|
}
|
|
|
|
auto iStep = set->find(step);
|
|
if(iStep != set->end()) {
|
|
++iStep->count;
|
|
}
|
|
else {
|
|
step.count = 1;
|
|
set->insert(step);
|
|
}
|
|
|
|
profile_lock.release();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
void logException(asIScriptContext* context) {
|
|
error("Script Exception: %s", context->GetExceptionString());
|
|
error(getStackTrace(context, true));
|
|
}
|
|
|
|
threads::Mutex errMtx;
|
|
std::unordered_set<std::string> printedErrors;
|
|
|
|
void excCallback(asIScriptContext* context) {
|
|
std::string errMsg;
|
|
errMsg += "Script Exception: ";
|
|
errMsg += context->GetExceptionString();
|
|
errMsg += "\n";
|
|
errMsg += getStackTrace(context);
|
|
|
|
if(!LOG_CHATTY) {
|
|
threads::Lock lck(errMtx);
|
|
if(printedErrors.find(errMsg) != printedErrors.end())
|
|
return;
|
|
printedErrors.insert(errMsg);
|
|
}
|
|
|
|
//Add to normal log
|
|
error(errMsg);
|
|
flushLog();
|
|
|
|
//Add to error log
|
|
if(LOG_ERRORLOG)
|
|
appendToErrorLog(errMsg);
|
|
}
|
|
|
|
void logException() {
|
|
auto* ctx = asGetActiveContext();
|
|
if(ctx)
|
|
logException(ctx);
|
|
else
|
|
error("No active script context to log.");
|
|
flushLog();
|
|
}
|
|
|
|
std::string getStackVariables(asIScriptContext* context, unsigned frame) {
|
|
std::string trace, line;
|
|
for(unsigned n = 0, ncnt = context->GetVarCount(frame); n < ncnt; ++n) {
|
|
if(!context->IsVarInScope(n, frame))
|
|
continue;
|
|
line += context->GetVarDeclaration(n, frame, false);
|
|
line += " = ";
|
|
line += getScriptVariable(
|
|
context->GetAddressOfVar(n, frame),
|
|
context->GetVarTypeId(n, frame),
|
|
true,
|
|
context->GetEngine());
|
|
line += "; ";
|
|
if(line.size() > 100) {
|
|
trace += line+"\n";
|
|
line.clear();
|
|
}
|
|
}
|
|
if(!line.empty())
|
|
trace += line+"\n";
|
|
|
|
void* ptr = context->GetThisPointer(frame);
|
|
if(ptr != nullptr) {
|
|
int thisType = context->GetThisTypeId(frame);
|
|
if(thisType >= 0) {
|
|
trace += context->GetEngine()->GetTypeDeclaration(thisType);
|
|
trace += " this = ";
|
|
trace += getScriptVariable(ptr, thisType, true, context->GetEngine());
|
|
trace += ";\n";
|
|
}
|
|
}
|
|
return trace;
|
|
}
|
|
|
|
std::string getStackTrace(asIScriptContext* context, bool verbose) {
|
|
std::string trace;
|
|
|
|
int cnt = std::min((int)context->GetCallstackSize(), MAX_AS_CALLSTACK_PRINT_DEPTH);
|
|
for(int i = 0; i < cnt; ++i) {
|
|
asIScriptFunction* func = context->GetFunction(i);
|
|
|
|
if(func) {
|
|
if(i == 0) {
|
|
const char* section = func->GetScriptSectionName();
|
|
if(section)
|
|
trace += std::string(" ") + section + "\n";
|
|
else
|
|
trace += " <Unknown>\n";
|
|
}
|
|
|
|
int line, column;
|
|
line = context->GetLineNumber(i, &column);
|
|
|
|
trace += format(" $1::$2 | Line $3 | Col $4\n", func->GetModuleName(), func->GetDeclaration(), line, column);
|
|
}
|
|
else {
|
|
trace += " <Unknown function>\n";
|
|
}
|
|
}
|
|
|
|
/*if(cnt != 0 && (verbose || getLogLevel() >= LL_Info)) {
|
|
trace += "########################################\n";
|
|
trace += getStackVariables(context);
|
|
trace += "########################################\n";
|
|
}*/
|
|
|
|
return trace;
|
|
}
|
|
|
|
asIScriptContext* makeContext(asIScriptEngine* engine) {
|
|
asIScriptContext* ctx = engine->CreateContext();
|
|
ctx->SetExceptionCallback(asFUNCTIONPR(excCallback, (asIScriptContext*), void), 0, asCALL_CDECL);
|
|
#ifdef DO_LINE_CALLBACK
|
|
ctx->SetLineCallback(asFUNCTION(TraceExec), 0, asCALL_CDECL);
|
|
#endif
|
|
return ctx;
|
|
}
|
|
|
|
std::vector<ContextCache*> caches;
|
|
threads::Mutex cacheMtx;
|
|
|
|
void initContextCache() {
|
|
ctxCache = new ContextCache();
|
|
|
|
{
|
|
threads::Lock lock(cacheMtx);
|
|
caches.push_back(ctxCache);
|
|
}
|
|
}
|
|
|
|
void freeContextCache() {
|
|
{
|
|
threads::Lock lock(cacheMtx);
|
|
auto it = std::find(caches.begin(), caches.end(), ctxCache);
|
|
if(it != caches.end())
|
|
caches.erase(it);
|
|
}
|
|
|
|
delete ctxCache;
|
|
}
|
|
|
|
void resetContextCache(bool resetMenu) {
|
|
//Only call this if no scripts will run for sure
|
|
threads::Lock lock(cacheMtx);
|
|
foreach(it, caches)
|
|
(*it)->reset(resetMenu);
|
|
}
|
|
|
|
asIScriptContext* fetchContext(asIScriptEngine* engine) {
|
|
ContextCache* cache = ctxCache;
|
|
|
|
if(engine == devices.engines.server) {
|
|
auto* ctx = cache->serverCtx;
|
|
if(!ctx) {
|
|
ctx = makeContext(engine);
|
|
cache->serverCtx = ctx;
|
|
}
|
|
return ctx;
|
|
}
|
|
else if(engine == devices.engines.client) {
|
|
auto* ctx = cache->clientCtx;
|
|
if(!ctx) {
|
|
ctx = makeContext(engine);
|
|
cache->clientCtx = ctx;
|
|
}
|
|
return ctx;
|
|
}
|
|
else { //menu
|
|
auto* ctx = cache->menuCtx;
|
|
if(!ctx) {
|
|
ctx = makeContext(engine);
|
|
cache->menuCtx = ctx;
|
|
}
|
|
return ctx;
|
|
}
|
|
}
|
|
|
|
std::string getScriptVariable(void *value, asUINT typeId, bool expandMembers, asIScriptEngine *engine) {
|
|
//From the AS debugger addon
|
|
std::stringstream s;
|
|
if( typeId == asTYPEID_VOID )
|
|
return "<void>";
|
|
else if( typeId == asTYPEID_BOOL )
|
|
return *(bool*)value ? "true" : "false";
|
|
else if( typeId == asTYPEID_INT8 )
|
|
s << (int)*(signed char*)value;
|
|
else if( typeId == asTYPEID_INT16 )
|
|
s << (int)*(signed short*)value;
|
|
else if( typeId == asTYPEID_INT32 )
|
|
s << *(signed int*)value;
|
|
else if( typeId == asTYPEID_INT64 )
|
|
#if defined(_MSC_VER) && _MSC_VER <= 1200
|
|
s << "{...}"; // MSVC6 doesn't like the << operator for 64bit integer
|
|
#else
|
|
s << *(asINT64*)value;
|
|
#endif
|
|
else if( typeId == asTYPEID_UINT8 )
|
|
s << (unsigned int)*(unsigned char*)value;
|
|
else if( typeId == asTYPEID_UINT16 )
|
|
s << (unsigned int)*(unsigned short*)value;
|
|
else if( typeId == asTYPEID_UINT32 )
|
|
s << *(unsigned int*)value;
|
|
else if( typeId == asTYPEID_UINT64 )
|
|
#if defined(_MSC_VER) && _MSC_VER <= 1200
|
|
s << "{...}"; // MSVC6 doesn't like the << operator for 64bit integer
|
|
#else
|
|
s << *(asQWORD*)value;
|
|
#endif
|
|
else if( typeId == asTYPEID_FLOAT )
|
|
s << *(float*)value;
|
|
else if( typeId == asTYPEID_DOUBLE )
|
|
s << *(double*)value;
|
|
else if( (typeId & asTYPEID_MASK_OBJECT) == 0 )
|
|
{
|
|
// The type is an enum
|
|
s << *(asUINT*)value;
|
|
|
|
// Check if the value matches one of the defined enums
|
|
/*for( int n = engine->GetEnumValueCount(typeId); n-- > 0; )
|
|
{
|
|
int enumVal;
|
|
const char *enumName = engine->GetEnumValueByIndex(typeId, n, &enumVal);
|
|
if( enumVal == *(int*)value )
|
|
{
|
|
s << ", " << enumName;
|
|
break;
|
|
}
|
|
}*/
|
|
}
|
|
else if( typeId & asTYPEID_SCRIPTOBJECT )
|
|
{
|
|
// Dereference handles, so we can see what it points to
|
|
if( typeId & asTYPEID_OBJHANDLE )
|
|
value = *(void**)value;
|
|
|
|
asIScriptObject *obj = (asIScriptObject *)value;
|
|
|
|
s << "{ " << obj << "";
|
|
|
|
if( obj && expandMembers )
|
|
{
|
|
asITypeInfo *type = obj->GetObjectType();
|
|
for( unsigned n = 0; n < obj->GetPropertyCount(); n++ )
|
|
{
|
|
s << std::endl << " " << type->GetPropertyDeclaration(n) << " = " << getScriptVariable(obj->GetAddressOfProperty(n), obj->GetPropertyTypeId(n), false, engine);
|
|
}
|
|
s << "\n}";
|
|
}
|
|
else {
|
|
s << " }";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Dereference handles, so we can see what it points to
|
|
if( typeId & asTYPEID_OBJHANDLE )
|
|
value = *(void**)value;
|
|
|
|
auto* type = engine->GetTypeInfoById(typeId);
|
|
if(type && value) {
|
|
auto* func = type->GetMethodByDecl("string opAdd(const string&in) const");
|
|
if(!func)
|
|
func = type->GetMethodByDecl("string opAdd(const string&) const");
|
|
if(!func)
|
|
func = type->GetMethodByDecl("string opAdd(string&) const");
|
|
if(!func)
|
|
func = type->GetMethodByDecl("string opAdd_r(string&) const");
|
|
if(func) {
|
|
std::string val;
|
|
std::string* ret;
|
|
|
|
auto* ctx = engine->CreateContext();
|
|
ctx->Prepare(func);
|
|
ctx->SetObject(value);
|
|
ctx->SetArgAddress(0, &val);
|
|
auto status = ctx->Execute();
|
|
if(status == asSUCCESS) {
|
|
ret = (std::string*)ctx->GetReturnObject();
|
|
s << *ret;
|
|
}
|
|
else {
|
|
s << "{ " << value << " }?";
|
|
}
|
|
}
|
|
else {
|
|
// Just print the address
|
|
s << "{ " << value << " }";
|
|
}
|
|
}
|
|
else {
|
|
// Just print the address
|
|
s << "{ " << value << " }";
|
|
}
|
|
}
|
|
|
|
return s.str();
|
|
}
|
|
|
|
};
|