243 lines
6.9 KiB
C++
243 lines
6.9 KiB
C++
#include "ScriptEngine.h"
|
|
#include "ScriptBindings.h"
|
|
#include "scriptstdstring.h"
|
|
#include "scriptbuilder.h"
|
|
#include <fstream>
|
|
#include <sstream>
|
|
#include <cstring>
|
|
#include <assert.h>
|
|
#include "log/log.h"
|
|
|
|
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
|
|
}
|
|
|
|
ScriptEngine::~ScriptEngine() {
|
|
Shutdown();
|
|
}
|
|
|
|
bool ScriptEngine::Initialize() {
|
|
engine = asCreateScriptEngine();
|
|
if (!engine) {
|
|
log_error("Failed to create AngelScript engine");
|
|
return false;
|
|
}
|
|
|
|
debugger = new CDebugger();
|
|
debugger->SetEngine(engine);
|
|
|
|
// Register std::string
|
|
RegisterStdString(engine);
|
|
|
|
// Register script bindings
|
|
ScriptBindings::RegisterAll(engine);
|
|
|
|
// Set message callback
|
|
int r = engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL);
|
|
if (r < 0) {
|
|
log_error("Failed to set message callback");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScriptEngine::Shutdown() {
|
|
ClearCachedFunctions();
|
|
if (debugger) {
|
|
debugger->SetEngine(nullptr);
|
|
delete debugger;
|
|
debugger = nullptr;
|
|
}
|
|
if (engine) {
|
|
engine->ShutDownAndRelease();
|
|
engine = nullptr;
|
|
}
|
|
currentModule = nullptr;
|
|
hasValidScript = false;
|
|
}
|
|
|
|
bool ScriptEngine::CompileScript(const std::string& filename) {
|
|
if (!engine) return false;
|
|
|
|
// Try to compile in a temporary module first using CScriptBuilder
|
|
CScriptBuilder tempBuilder;
|
|
tempBuilder.SetIncludeCallback(IncludeCallback, nullptr);
|
|
|
|
int r = tempBuilder.StartNewModule(engine, "temp");
|
|
if (r < 0) {
|
|
log_error("Failed to start new temp module");
|
|
return false;
|
|
}
|
|
|
|
r = tempBuilder.AddSectionFromFile(filename.c_str());
|
|
if (r < 0) {
|
|
log_error("Failed to add script section from file: %s", filename.c_str());
|
|
engine->DiscardModule("temp");
|
|
return false;
|
|
}
|
|
|
|
r = tempBuilder.BuildModule();
|
|
if (r < 0) {
|
|
log_error("Failed to build script: %s", filename.c_str());
|
|
engine->DiscardModule("temp");
|
|
return false;
|
|
}
|
|
|
|
// If we get here, compilation succeeded
|
|
// Now safely replace the main module
|
|
if (currentModule) {
|
|
engine->DiscardModule("main");
|
|
}
|
|
|
|
// Create new main module with the working code using CScriptBuilder
|
|
CScriptBuilder mainBuilder;
|
|
mainBuilder.SetIncludeCallback(IncludeCallback, nullptr);
|
|
|
|
r = mainBuilder.StartNewModule(engine, "main");
|
|
if (r >= 0) {
|
|
r = mainBuilder.AddSectionFromFile(filename.c_str());
|
|
}
|
|
if (r >= 0) {
|
|
r = mainBuilder.BuildModule();
|
|
}
|
|
|
|
// Clean up temp module
|
|
engine->DiscardModule("temp");
|
|
|
|
if (r < 0) {
|
|
// This shouldn't happen since we already tested compilation
|
|
log_error("Unexpected error when creating main module");
|
|
ClearCachedFunctions();
|
|
return false;
|
|
}
|
|
|
|
// Get the newly created module
|
|
currentModule = engine->GetModule("main");
|
|
|
|
// Cache new functions
|
|
updateFunc = currentModule->GetFunctionByName("Update");
|
|
drawFunc = currentModule->GetFunctionByName("Draw");
|
|
hasValidScript = true;
|
|
// If debugger is present, add a function breakpoint on Update to stop when Update is entered
|
|
// The debugger will transform function breakpoints into file breakpoints when the function is entered
|
|
if (debugger && updateFunc) {
|
|
debugger->AddFuncBreakPoint("Update");
|
|
}
|
|
log_info("Script compiled and cached: %s", filename.c_str());
|
|
return true;
|
|
}
|
|
|
|
void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
|
|
if (!func || !engine || !hasValidScript) return;
|
|
|
|
asIScriptContext* ctx = engine->CreateContext();
|
|
if (!ctx) return;
|
|
|
|
if (debugger) {
|
|
ctx->SetLineCallback(asMETHOD(CDebugger, LineCallback), debugger, asCALL_THISCALL);
|
|
}
|
|
|
|
int r = ctx->Prepare(func);
|
|
if (r < 0) {
|
|
ctx->Release();
|
|
return;
|
|
}
|
|
|
|
if (func->GetParamCount() == 1) {
|
|
ctx->SetArgFloat(0, dt);
|
|
}
|
|
|
|
r = ctx->Execute();
|
|
if (r != asEXECUTION_FINISHED) {
|
|
if (r == asEXECUTION_EXCEPTION) {
|
|
log_error("Script exception: %s", ctx->GetExceptionString());
|
|
}
|
|
}
|
|
|
|
ctx->Release();
|
|
}
|
|
|
|
void ScriptEngine::ClearCachedFunctions() {
|
|
updateFunc = nullptr;
|
|
drawFunc = nullptr;
|
|
hasValidScript = false;
|
|
}
|
|
|
|
void ScriptEngine::GarbageCollect() {
|
|
if (engine) {
|
|
engine->GarbageCollect();
|
|
}
|
|
}
|
|
|
|
|
|
void ScriptEngine::AddFileBreakPoint(const std::string &file, int line) {
|
|
if (debugger) debugger->AddFileBreakPoint(file, line);
|
|
}
|
|
void ScriptEngine::AddFuncBreakPoint(const std::string &func) {
|
|
if (debugger) debugger->AddFuncBreakPoint(func);
|
|
}
|
|
void ScriptEngine::MessageCallback(const asSMessageInfo* msg, void*) {
|
|
// Map AngelScript message types to log levels
|
|
if (msg->type == asMSGTYPE_ERROR) {
|
|
log_error("%s (%d): %s", msg->section, msg->row, msg->message);
|
|
} else if (msg->type == asMSGTYPE_WARNING) {
|
|
log_warn("%s (%d): %s", msg->section, msg->row, msg->message);
|
|
} else {
|
|
log_info("%s (%d): %s", msg->section, msg->row, msg->message);
|
|
}
|
|
}
|
|
|
|
int ScriptEngine::IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam) {
|
|
// Extract just the filename from the from path for cleaner logging
|
|
const char* fromFile = from;
|
|
if (from) {
|
|
const char* lastSlash = strrchr(from, '/');
|
|
const char* lastBackslash = strrchr(from, '\\');
|
|
const char* lastSep = (lastSlash > lastBackslash) ? lastSlash : lastBackslash;
|
|
if (lastSep) {
|
|
fromFile = lastSep + 1;
|
|
}
|
|
}
|
|
|
|
// Log the include request with clean filenames
|
|
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
|
|
|
|
// Build the full path relative to the including file's directory
|
|
std::string fullPath;
|
|
|
|
if (from && strlen(from) > 0) {
|
|
// Extract directory from the 'from' path
|
|
std::string fromPath(from);
|
|
size_t lastSlash = fromPath.find_last_of("/\\");
|
|
|
|
if (lastSlash != std::string::npos) {
|
|
// Get the directory part
|
|
std::string directory = fromPath.substr(0, lastSlash + 1);
|
|
fullPath = directory + include;
|
|
} else {
|
|
// No directory in from path, use include as-is
|
|
fullPath = include;
|
|
}
|
|
} else {
|
|
fullPath = include;
|
|
}
|
|
|
|
// Try to add the included file
|
|
int r = builder->AddSectionFromFile(fullPath.c_str());
|
|
|
|
if (r < 0) {
|
|
log_error("Failed to include file: %s (tried path: %s)", include, fullPath.c_str());
|
|
} else {
|
|
log_info("Successfully included: %s", include);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
|
std::ifstream file(filename);
|
|
if (!file) return "";
|
|
std::stringstream ss;
|
|
ss << file.rdbuf();
|
|
return ss.str();
|
|
} |