Files
simian/src/ScriptEngine.cpp
T
2025-11-06 15:57:45 +13:00

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