Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 529e51009c | |||
| 2e958fa1ec |
@@ -1,36 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out repository (with submodules)
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
# Ensure submodules (like external/angelscript and external/raylib) are checked out.
|
|
||||||
submodules: true
|
|
||||||
# Fetch full history so submodules can be cloned properly.
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Install system dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends \
|
|
||||||
build-essential cmake git pkg-config \
|
|
||||||
libx11-dev libxrandr-dev libxcursor-dev libxinerama-dev libxi-dev \
|
|
||||||
libgl1-mesa-dev libasound2-dev libpulse-dev libudev-dev libpng-dev
|
|
||||||
|
|
||||||
- name: Configure (CMake)
|
|
||||||
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
|
|
||||||
|
|
||||||
- name: Build unit tests
|
|
||||||
run: cmake --build build --target unit_tests -j $(nproc)
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
cd build
|
|
||||||
ctest --output-on-failure -j $(nproc)
|
|
||||||
@@ -4,6 +4,3 @@
|
|||||||
[submodule "external/angelscript"]
|
[submodule "external/angelscript"]
|
||||||
path = external/angelscript
|
path = external/angelscript
|
||||||
url = https://github.com/anjo76/angelscript.git
|
url = https://github.com/anjo76/angelscript.git
|
||||||
[submodule "external/acutest"]
|
|
||||||
path = external/acutest
|
|
||||||
url = https://github.com/mity/acutest
|
|
||||||
|
|||||||
Vendored
+2
-1
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.h": "c",
|
"*.h": "c",
|
||||||
"xstring": "cpp"
|
"xstring": "cpp",
|
||||||
|
"iostream": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+14
-8
@@ -14,12 +14,6 @@ add_subdirectory(external/raylib)
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
|
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
|
||||||
|
|
||||||
|
|
||||||
# -------------------------
|
|
||||||
# Tests
|
|
||||||
# -------------------------
|
|
||||||
enable_testing()
|
|
||||||
add_subdirectory(tests)
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# 3️⃣ scriptstdstring add-on
|
# 3️⃣ scriptstdstring add-on
|
||||||
# -------------------------
|
# -------------------------
|
||||||
@@ -31,8 +25,18 @@ target_include_directories(scriptstdstring
|
|||||||
PUBLIC external/angelscript/sdk/angelscript/include
|
PUBLIC external/angelscript/sdk/angelscript/include
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# debugger add-on
|
||||||
|
# ------------------------
|
||||||
|
add_library(debugger STATIC
|
||||||
|
external/angelscript/sdk/add_on/debugger/debugger.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(debugger PUBLIC
|
||||||
|
external/angelscript/sdk/angelscript/include
|
||||||
|
)
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# 3️⃣.1️⃣ scriptbuilder add-on
|
# scriptbuilder add-on
|
||||||
# -------------------------
|
# -------------------------
|
||||||
add_library(scriptbuilder STATIC
|
add_library(scriptbuilder STATIC
|
||||||
external/angelscript/sdk/add_on/scriptbuilder/scriptbuilder.cpp
|
external/angelscript/sdk/add_on/scriptbuilder/scriptbuilder.cpp
|
||||||
@@ -43,7 +47,7 @@ target_include_directories(scriptbuilder
|
|||||||
)
|
)
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
# 4️⃣ Main executable
|
# Main executable
|
||||||
# -------------------------
|
# -------------------------
|
||||||
add_executable(simian
|
add_executable(simian
|
||||||
main.cpp
|
main.cpp
|
||||||
@@ -61,6 +65,7 @@ target_include_directories(simian PUBLIC
|
|||||||
external/angelscript/sdk/angelscript/include
|
external/angelscript/sdk/angelscript/include
|
||||||
external/angelscript/sdk/add_on/scriptstdstring
|
external/angelscript/sdk/add_on/scriptstdstring
|
||||||
external/angelscript/sdk/add_on/scriptbuilder
|
external/angelscript/sdk/add_on/scriptbuilder
|
||||||
|
external/angelscript/sdk/add_on/debugger
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(simian
|
target_link_libraries(simian
|
||||||
@@ -68,6 +73,7 @@ target_link_libraries(simian
|
|||||||
angelscript
|
angelscript
|
||||||
scriptstdstring
|
scriptstdstring
|
||||||
scriptbuilder
|
scriptbuilder
|
||||||
|
debugger
|
||||||
)
|
)
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|||||||
Vendored
-1
Submodule external/acutest deleted from 31751b4089
+2
-8
@@ -5,20 +5,14 @@
|
|||||||
|
|
||||||
class HotReload {
|
class HotReload {
|
||||||
public:
|
public:
|
||||||
// Accept either a single script file or a directory to watch. If a directory is
|
HotReload(const std::string& filename);
|
||||||
// provided, any change to any file in the directory will trigger a reload.
|
|
||||||
HotReload(const std::string& pathToWatch);
|
|
||||||
|
|
||||||
bool CheckForChanges();
|
bool CheckForChanges();
|
||||||
void UpdateLastWriteTime();
|
void UpdateLastWriteTime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string path;
|
std::string scriptFile;
|
||||||
std::time_t lastWriteTime;
|
std::time_t lastWriteTime;
|
||||||
|
|
||||||
std::time_t GetFileWriteTime(const std::string& filename);
|
std::time_t GetFileWriteTime(const std::string& filename);
|
||||||
// Return the most recent write time found in the watched path. If `path` is a
|
|
||||||
// directory, this scans all files (non-recursive) and returns the newest
|
|
||||||
// modification time. If it's a file, it returns that file's write time.
|
|
||||||
std::time_t GetLatestWriteTimeInPath();
|
|
||||||
};
|
};
|
||||||
+4
-10
@@ -2,7 +2,7 @@
|
|||||||
#include "angelscript.h"
|
#include "angelscript.h"
|
||||||
#include "scriptbuilder.h"
|
#include "scriptbuilder.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include "debugger.h"
|
||||||
|
|
||||||
class ScriptEngine {
|
class ScriptEngine {
|
||||||
public:
|
public:
|
||||||
@@ -15,17 +15,11 @@ public:
|
|||||||
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
|
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
|
||||||
void GarbageCollect();
|
void GarbageCollect();
|
||||||
|
|
||||||
// Add a path used to resolve #include directives in scripts
|
|
||||||
void AddIncludePath(const std::string& path);
|
|
||||||
|
|
||||||
// Try to preserve script state across hot-reloads by calling optional
|
|
||||||
// __SerializeState()/__DeserializeState() hooks in the script module.
|
|
||||||
std::string CaptureScriptState();
|
|
||||||
void RestoreScriptState(const std::string &state);
|
|
||||||
|
|
||||||
asIScriptEngine* GetEngine() const { return engine; }
|
asIScriptEngine* GetEngine() const { return engine; }
|
||||||
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
|
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
|
||||||
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
|
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
|
||||||
|
void AddFileBreakPoint(const std::string& file, int line);
|
||||||
|
void AddFuncBreakPoint(const std::string& func);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
asIScriptEngine* engine;
|
asIScriptEngine* engine;
|
||||||
@@ -33,7 +27,7 @@ private:
|
|||||||
asIScriptFunction* drawFunc;
|
asIScriptFunction* drawFunc;
|
||||||
asIScriptModule* currentModule;
|
asIScriptModule* currentModule;
|
||||||
bool hasValidScript;
|
bool hasValidScript;
|
||||||
std::vector<std::string> includePaths;
|
CDebugger* debugger = nullptr;
|
||||||
|
|
||||||
static void MessageCallback(const asSMessageInfo* msg, void* param);
|
static void MessageCallback(const asSMessageInfo* msg, void* param);
|
||||||
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
|
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
|
||||||
|
|||||||
+3
-1
@@ -7,5 +7,7 @@ void Update(float dt) {
|
|||||||
x = 0;
|
x = 0;
|
||||||
Print("X position reset!");
|
Print("X position reset!");
|
||||||
Log(LOG_INFO, "Log INFO: reset happened");
|
Log(LOG_INFO, "Log INFO: reset happened");
|
||||||
|
Log(LOG_WARNING, "Log WARNING: reset happened");
|
||||||
|
Log(LOG_ERROR, "Log ERROR: reset happened");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}//
|
|
||||||
|
|||||||
+2
-9
@@ -2,7 +2,6 @@
|
|||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <filesystem>
|
|
||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
const char* Application::WINDOW_TITLE = "Raylib + AngelScript";
|
const char* Application::WINDOW_TITLE = "Raylib + AngelScript";
|
||||||
const char* Application::SCRIPT_FILE = "scripts/test.as";
|
const char* Application::SCRIPT_FILE = "scripts/test.as";
|
||||||
@@ -29,14 +28,8 @@ bool Application::Initialize() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize hot reload to watch the scripts directory so any script change
|
// Initialize hot reload
|
||||||
// (not just the main file) triggers a reload.
|
hotReload = new HotReload(SCRIPT_FILE);
|
||||||
{
|
|
||||||
std::filesystem::path p(SCRIPT_FILE);
|
|
||||||
std::string watchPath = p.parent_path().string();
|
|
||||||
if (watchPath.empty()) watchPath = ".";
|
|
||||||
hotReload = new HotReload(watchPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile initial script
|
// Compile initial script
|
||||||
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
|
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
|
||||||
|
|||||||
+5
-29
@@ -3,21 +3,21 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
HotReload::HotReload(const std::string& pathToWatch) : path(pathToWatch), lastWriteTime(0) {
|
HotReload::HotReload(const std::string& filename) : scriptFile(filename), lastWriteTime(0) {
|
||||||
UpdateLastWriteTime();
|
UpdateLastWriteTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HotReload::CheckForChanges() {
|
bool HotReload::CheckForChanges() {
|
||||||
std::time_t current = GetLatestWriteTimeInPath();
|
std::time_t currentWriteTime = GetFileWriteTime(scriptFile);
|
||||||
if (current != lastWriteTime) {
|
if (currentWriteTime != lastWriteTime) {
|
||||||
lastWriteTime = current;
|
lastWriteTime = currentWriteTime;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotReload::UpdateLastWriteTime() {
|
void HotReload::UpdateLastWriteTime() {
|
||||||
lastWriteTime = GetLatestWriteTimeInPath();
|
lastWriteTime = GetFileWriteTime(scriptFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
||||||
@@ -30,27 +30,3 @@ std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::time_t HotReload::GetLatestWriteTimeInPath() {
|
|
||||||
try {
|
|
||||||
fs::path p(path);
|
|
||||||
if (fs::is_regular_file(p)) {
|
|
||||||
return GetFileWriteTime(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a directory, scan files (non-recursively) and return the
|
|
||||||
// newest write time found.
|
|
||||||
if (fs::is_directory(p)) {
|
|
||||||
std::time_t newest = 0;
|
|
||||||
for (auto &entry : fs::directory_iterator(p)) {
|
|
||||||
if (!fs::is_regular_file(entry.path())) continue;
|
|
||||||
std::time_t t = GetFileWriteTime(entry.path().string());
|
|
||||||
if (t > newest) newest = t;
|
|
||||||
}
|
|
||||||
return newest;
|
|
||||||
}
|
|
||||||
} catch (const std::exception&) {
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
+42
-70
@@ -8,9 +8,6 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
|
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,6 +22,9 @@ bool ScriptEngine::Initialize() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugger = new CDebugger();
|
||||||
|
debugger->SetEngine(engine);
|
||||||
|
|
||||||
// Register std::string
|
// Register std::string
|
||||||
RegisterStdString(engine);
|
RegisterStdString(engine);
|
||||||
|
|
||||||
@@ -41,57 +41,13 @@ bool ScriptEngine::Initialize() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptEngine::AddIncludePath(const std::string& path) {
|
|
||||||
// thread-safe append
|
|
||||||
static std::mutex m;
|
|
||||||
std::lock_guard<std::mutex> lock(m);
|
|
||||||
includePaths.push_back(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ScriptEngine::CaptureScriptState() {
|
|
||||||
if (!currentModule || !engine) return std::string();
|
|
||||||
asIScriptFunction* ser = currentModule->GetFunctionByDecl("string __SerializeState()");
|
|
||||||
if (!ser) return std::string();
|
|
||||||
|
|
||||||
asIScriptContext* ctx = engine->CreateContext();
|
|
||||||
if (ctx->Prepare(ser) < 0) { ctx->Release(); return std::string(); }
|
|
||||||
int r = ctx->Execute();
|
|
||||||
std::string ret;
|
|
||||||
if (r == asEXECUTION_FINISHED) {
|
|
||||||
// Get return value (script std::string registered as value type)
|
|
||||||
void* obj = ctx->GetReturnObject();
|
|
||||||
if (obj) {
|
|
||||||
try {
|
|
||||||
std::string* s = reinterpret_cast<std::string*>(obj);
|
|
||||||
ret = *s;
|
|
||||||
} catch (...) {
|
|
||||||
// fallback: leave ret empty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx->Release();
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::RestoreScriptState(const std::string &state) {
|
|
||||||
if (!currentModule || !engine || state.empty()) return;
|
|
||||||
asIScriptFunction* des = currentModule->GetFunctionByDecl("void __DeserializeState(const string &in)");
|
|
||||||
if (!des) return;
|
|
||||||
|
|
||||||
asIScriptContext* ctx = engine->CreateContext();
|
|
||||||
if (ctx->Prepare(des) < 0) { ctx->Release(); return; }
|
|
||||||
// SetArgObject expects pointer to the script string object; AngelScript's
|
|
||||||
// std::string binding uses C++ std::string, so we pass the pointer.
|
|
||||||
ctx->SetArgObject(0, (void*)&state);
|
|
||||||
int r = ctx->Execute();
|
|
||||||
if (r != asEXECUTION_FINISHED) {
|
|
||||||
log_error("Failed to restore script state");
|
|
||||||
}
|
|
||||||
ctx->Release();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptEngine::Shutdown() {
|
void ScriptEngine::Shutdown() {
|
||||||
ClearCachedFunctions();
|
ClearCachedFunctions();
|
||||||
|
if (debugger) {
|
||||||
|
debugger->SetEngine(nullptr);
|
||||||
|
delete debugger;
|
||||||
|
debugger = nullptr;
|
||||||
|
}
|
||||||
if (engine) {
|
if (engine) {
|
||||||
engine->ShutDownAndRelease();
|
engine->ShutDownAndRelease();
|
||||||
engine = nullptr;
|
engine = nullptr;
|
||||||
@@ -162,6 +118,11 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
|
|||||||
updateFunc = currentModule->GetFunctionByName("Update");
|
updateFunc = currentModule->GetFunctionByName("Update");
|
||||||
drawFunc = currentModule->GetFunctionByName("Draw");
|
drawFunc = currentModule->GetFunctionByName("Draw");
|
||||||
hasValidScript = true;
|
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());
|
log_info("Script compiled and cached: %s", filename.c_str());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -172,6 +133,10 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
|
|||||||
asIScriptContext* ctx = engine->CreateContext();
|
asIScriptContext* ctx = engine->CreateContext();
|
||||||
if (!ctx) return;
|
if (!ctx) return;
|
||||||
|
|
||||||
|
if (debugger) {
|
||||||
|
ctx->SetLineCallback(asMETHOD(CDebugger, LineCallback), debugger, asCALL_THISCALL);
|
||||||
|
}
|
||||||
|
|
||||||
int r = ctx->Prepare(func);
|
int r = ctx->Prepare(func);
|
||||||
if (r < 0) {
|
if (r < 0) {
|
||||||
ctx->Release();
|
ctx->Release();
|
||||||
@@ -204,6 +169,13 @@ void ScriptEngine::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*) {
|
void ScriptEngine::MessageCallback(const asSMessageInfo* msg, void*) {
|
||||||
// Map AngelScript message types to log levels
|
// Map AngelScript message types to log levels
|
||||||
if (msg->type == asMSGTYPE_ERROR) {
|
if (msg->type == asMSGTYPE_ERROR) {
|
||||||
@@ -230,36 +202,36 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
|
|||||||
// Log the include request with clean filenames
|
// Log the include request with clean filenames
|
||||||
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
|
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
|
||||||
|
|
||||||
// Build the full path candidates: relative to 'from' and any include paths
|
// Build the full path relative to the including file's directory
|
||||||
std::vector<std::string> candidates;
|
std::string fullPath;
|
||||||
|
|
||||||
if (from && strlen(from) > 0) {
|
if (from && strlen(from) > 0) {
|
||||||
|
// Extract directory from the 'from' path
|
||||||
std::string fromPath(from);
|
std::string fromPath(from);
|
||||||
size_t lastSlash = fromPath.find_last_of("/\\");
|
size_t lastSlash = fromPath.find_last_of("/\\");
|
||||||
|
|
||||||
if (lastSlash != std::string::npos) {
|
if (lastSlash != std::string::npos) {
|
||||||
|
// Get the directory part
|
||||||
std::string directory = fromPath.substr(0, lastSlash + 1);
|
std::string directory = fromPath.substr(0, lastSlash + 1);
|
||||||
candidates.push_back(directory + include);
|
fullPath = directory + include;
|
||||||
|
} else {
|
||||||
|
// No directory in from path, use include as-is
|
||||||
|
fullPath = include;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
fullPath = include;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add user-specified include search paths
|
// Try to add the included file
|
||||||
// Note: includePaths is a member of the ScriptEngine instance, but this
|
int r = builder->AddSectionFromFile(fullPath.c_str());
|
||||||
// callback is static. For now we look for an environment variable or the
|
|
||||||
// include as-is. If you want dynamic include paths, expose a global pointer
|
|
||||||
// or userParam to pass the instance to the callback.
|
|
||||||
candidates.push_back(include);
|
|
||||||
|
|
||||||
int result = -1;
|
if (r < 0) {
|
||||||
for (auto &cand : candidates) {
|
log_error("Failed to include file: %s (tried path: %s)", include, fullPath.c_str());
|
||||||
result = builder->AddSectionFromFile(cand.c_str());
|
} else {
|
||||||
if (result >= 0) {
|
log_info("Successfully included: %s", include);
|
||||||
log_info("Successfully included: %s", cand.c_str());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log_error("Failed to include file: %s (tried %zu paths)", include, candidates.size());
|
return r;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
||||||
|
|||||||
+5
-18
@@ -21,7 +21,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include <string.h> // for memcpy
|
|
||||||
|
|
||||||
#define MAX_CALLBACKS 32
|
#define MAX_CALLBACKS 32
|
||||||
|
|
||||||
@@ -188,14 +187,6 @@ static void init_event(log_Event *ev, void *udata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void log_log(int level, const char *file, int line, const char *fmt, ...)
|
void log_log(int level, const char *file, int line, const char *fmt, ...)
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap, fmt);
|
|
||||||
log_vlog(level, file, line, fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap)
|
|
||||||
{
|
{
|
||||||
log_Event ev = {
|
log_Event ev = {
|
||||||
.fmt = fmt,
|
.fmt = fmt,
|
||||||
@@ -209,11 +200,9 @@ void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap
|
|||||||
if (!L.quiet && level >= L.level)
|
if (!L.quiet && level >= L.level)
|
||||||
{
|
{
|
||||||
init_event(&ev, stderr);
|
init_event(&ev, stderr);
|
||||||
va_list ap_copy;
|
va_start(ev.ap, fmt);
|
||||||
va_copy(ap_copy, ap);
|
|
||||||
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
|
|
||||||
stdout_callback(&ev);
|
stdout_callback(&ev);
|
||||||
va_end(ap_copy);
|
va_end(ev.ap);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
|
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
|
||||||
@@ -222,11 +211,9 @@ void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap
|
|||||||
if (level >= cb->level)
|
if (level >= cb->level)
|
||||||
{
|
{
|
||||||
init_event(&ev, cb->udata);
|
init_event(&ev, cb->udata);
|
||||||
va_list ap_copy;
|
va_start(ev.ap, fmt);
|
||||||
va_copy(ap_copy, ap);
|
|
||||||
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
|
|
||||||
cb->fn(&ev);
|
cb->fn(&ev);
|
||||||
va_end(ap_copy);
|
va_end(ev.ap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,5 +222,5 @@ void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap
|
|||||||
|
|
||||||
void raylib_log(int msgType, const char *text, va_list args)
|
void raylib_log(int msgType, const char *text, va_list args)
|
||||||
{
|
{
|
||||||
log_vlog(msgType, NULL, 0, text, args);
|
log_log(msgType, NULL, 0, text, args);
|
||||||
}
|
}
|
||||||
@@ -48,8 +48,6 @@ int log_add_callback(log_LogFn fn, void *udata, int level);
|
|||||||
int log_add_fp(FILE *fp, int level);
|
int log_add_fp(FILE *fp, int level);
|
||||||
|
|
||||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||||
// Variant that accepts a va_list for backends that already receive va_list
|
|
||||||
void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap);
|
|
||||||
|
|
||||||
|
|
||||||
void raylib_log(int msgType, const char *text, va_list args);
|
void raylib_log(int msgType, const char *text, va_list args);
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
|
||||||
|
|
||||||
# Simple unit test executable that compiles acutest.c directly
|
|
||||||
add_executable(unit_tests
|
|
||||||
unit_tests.cpp
|
|
||||||
${CMAKE_SOURCE_DIR}/src/HotReload.cpp
|
|
||||||
${CMAKE_SOURCE_DIR}/src/ScriptEngine.cpp
|
|
||||||
${CMAKE_SOURCE_DIR}/src/ScriptBindings.cpp
|
|
||||||
${CMAKE_SOURCE_DIR}/src/log/log.c
|
|
||||||
)
|
|
||||||
|
|
||||||
target_include_directories(unit_tests PRIVATE
|
|
||||||
${CMAKE_SOURCE_DIR}/external/acutest/include
|
|
||||||
${CMAKE_SOURCE_DIR}/include
|
|
||||||
${CMAKE_SOURCE_DIR}/src/log
|
|
||||||
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/angelscript/include
|
|
||||||
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptstdstring
|
|
||||||
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptbuilder
|
|
||||||
${CMAKE_SOURCE_DIR}/external/raylib/src
|
|
||||||
)
|
|
||||||
|
|
||||||
# Link AngelScript and its add-ons needed by ScriptEngine
|
|
||||||
target_link_libraries(unit_tests PRIVATE
|
|
||||||
angelscript
|
|
||||||
scriptstdstring
|
|
||||||
scriptbuilder
|
|
||||||
raylib
|
|
||||||
)
|
|
||||||
|
|
||||||
add_test(NAME unit_tests COMMAND unit_tests)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
#include "../external/acutest/include/acutest.h"
|
|
||||||
|
|
||||||
// This translation unit provides the single main() for Acutest.
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
#include "../external/acutest/include/acutest.h"
|
|
||||||
#include <string.h>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <thread>
|
|
||||||
#include <chrono>
|
|
||||||
#include "../include/HotReload.h"
|
|
||||||
#include "../include/ScriptEngine.h"
|
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
|
||||||
|
|
||||||
/* simple sanity test */
|
|
||||||
void test_one(void) {
|
|
||||||
TEST_CHECK(1 + 1 == 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_str(void) {
|
|
||||||
TEST_CHECK(strcmp("foo","foo") == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_hotreload_directory_watch(void) {
|
|
||||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_watch";
|
|
||||||
std::filesystem::create_directories(tmpDir);
|
|
||||||
|
|
||||||
std::filesystem::path f = tmpDir / "a.as";
|
|
||||||
std::ofstream ofs(f);
|
|
||||||
ofs << "// test" << std::endl;
|
|
||||||
ofs.close();
|
|
||||||
|
|
||||||
HotReload hr(tmpDir.string());
|
|
||||||
TEST_CHECK(hr.CheckForChanges() == false);
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(1s);
|
|
||||||
std::ofstream ofs2(f, std::ios::app);
|
|
||||||
ofs2 << "// changed" << std::endl;
|
|
||||||
ofs2.close();
|
|
||||||
|
|
||||||
TEST_CHECK(hr.CheckForChanges() == true);
|
|
||||||
|
|
||||||
std::filesystem::remove(f);
|
|
||||||
std::filesystem::remove(tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_scriptengine_compile_and_call(void) {
|
|
||||||
// Create a simple script file that defines Update(float) and a global
|
|
||||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_script";
|
|
||||||
std::filesystem::create_directories(tmpDir);
|
|
||||||
std::filesystem::path script = tmpDir / "simple.as";
|
|
||||||
std::ofstream ofs(script);
|
|
||||||
ofs << "float g_x = 0;\n";
|
|
||||||
ofs << "void Update(float dt) { g_x += dt * 10.0f; }\n";
|
|
||||||
ofs.close();
|
|
||||||
|
|
||||||
ScriptEngine se;
|
|
||||||
TEST_CHECK(se.Initialize() == true);
|
|
||||||
bool ok = se.CompileScript(script.string());
|
|
||||||
TEST_CHECK(ok == true);
|
|
||||||
asIScriptFunction* upd = se.GetUpdateFunction();
|
|
||||||
TEST_CHECK(upd != nullptr);
|
|
||||||
|
|
||||||
// Call Update with dt=0.1
|
|
||||||
se.CallScriptFunction(upd, 0.1f);
|
|
||||||
|
|
||||||
se.Shutdown();
|
|
||||||
|
|
||||||
std::filesystem::remove(script);
|
|
||||||
std::filesystem::remove(tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_scriptengine_include(void) {
|
|
||||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_include";
|
|
||||||
std::filesystem::create_directories(tmpDir);
|
|
||||||
std::filesystem::path inc = tmpDir / "inc.as";
|
|
||||||
std::ofstream ofsinc(inc);
|
|
||||||
ofsinc << "void Included() { Print(\"Included called\"); }\n";
|
|
||||||
ofsinc.close();
|
|
||||||
|
|
||||||
std::filesystem::path main = tmpDir / "main.as";
|
|
||||||
std::ofstream ofsmain(main);
|
|
||||||
ofsmain << "#include \"inc.as\"\n";
|
|
||||||
ofsmain << "void Update(float dt) { Included(); }\n";
|
|
||||||
ofsmain.close();
|
|
||||||
|
|
||||||
ScriptEngine se;
|
|
||||||
TEST_CHECK(se.Initialize() == true);
|
|
||||||
// Compile should succeed and include callback should resolve inc.as
|
|
||||||
bool ok = se.CompileScript(main.string());
|
|
||||||
TEST_CHECK(ok == true);
|
|
||||||
se.Shutdown();
|
|
||||||
|
|
||||||
std::filesystem::remove(inc);
|
|
||||||
std::filesystem::remove(main);
|
|
||||||
std::filesystem::remove(tmpDir);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_LIST = {
|
|
||||||
{ "one", test_one },
|
|
||||||
{ "str", test_str },
|
|
||||||
{ "hotreload_dir", test_hotreload_directory_watch },
|
|
||||||
{ "script_compile", test_scriptengine_compile_and_call },
|
|
||||||
{ "script_include", test_scriptengine_include },
|
|
||||||
{ NULL, NULL }
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user