6 Commits

Author SHA1 Message Date
nick 2c1c64049c chore: fix action to pull submodules
CI / build-and-test (push) Successful in 1m44s
CI / build-and-test (pull_request) Successful in 1m55s
2025-11-11 17:55:47 +13:00
nick 48580d0278 chore: gitea action
CI / build-and-test (push) Failing after 1m25s
2025-11-11 17:52:41 +13:00
nick 240a31848a chore: tests 2025-11-11 17:50:46 +13:00
nick 18821b9cc9 chore: no need to be so noisy 2025-11-06 17:09:49 +13:00
nick e1aa8eb6d5 Merge pull request 'fix: hot reload on all scripts' (#3) from fix/hotreload into main
Reviewed-on: #3
2025-11-06 04:08:19 +00:00
nick df51c75fb8 fix: hot reload on all scripts 2025-11-06 17:08:04 +13:00
16 changed files with 338 additions and 85 deletions
+36
View File
@@ -0,0 +1,36 @@
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)
+3
View File
@@ -4,3 +4,6 @@
[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
+1 -2
View File
@@ -1,7 +1,6 @@
{ {
"files.associations": { "files.associations": {
"*.h": "c", "*.h": "c",
"xstring": "cpp", "xstring": "cpp"
"iostream": "cpp"
} }
} }
+8 -14
View File
@@ -14,6 +14,12 @@ 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
# ------------------------- # -------------------------
@@ -25,18 +31,8 @@ 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
)
# ------------------------- # -------------------------
# scriptbuilder add-on # 3️⃣.1️⃣ 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
@@ -47,7 +43,7 @@ target_include_directories(scriptbuilder
) )
# ------------------------- # -------------------------
# Main executable # 4️⃣ Main executable
# ------------------------- # -------------------------
add_executable(simian add_executable(simian
main.cpp main.cpp
@@ -65,7 +61,6 @@ 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
@@ -73,7 +68,6 @@ target_link_libraries(simian
angelscript angelscript
scriptstdstring scriptstdstring
scriptbuilder scriptbuilder
debugger
) )
# ------------------------- # -------------------------
Vendored Submodule
+1
Submodule external/acutest added at 31751b4089
+8 -2
View File
@@ -5,14 +5,20 @@
class HotReload { class HotReload {
public: public:
HotReload(const std::string& filename); // Accept either a single script file or a directory to watch. If a directory is
// 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 scriptFile; std::string path;
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();
}; };
+10 -4
View File
@@ -2,7 +2,7 @@
#include "angelscript.h" #include "angelscript.h"
#include "scriptbuilder.h" #include "scriptbuilder.h"
#include <string> #include <string>
#include "debugger.h" #include <vector>
class ScriptEngine { class ScriptEngine {
public: public:
@@ -15,11 +15,17 @@ 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;
@@ -27,7 +33,7 @@ private:
asIScriptFunction* drawFunc; asIScriptFunction* drawFunc;
asIScriptModule* currentModule; asIScriptModule* currentModule;
bool hasValidScript; bool hasValidScript;
CDebugger* debugger = nullptr; std::vector<std::string> includePaths;
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);
+1 -3
View File
@@ -7,7 +7,5 @@ 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");
}
} }
}//
+9 -2
View File
@@ -2,6 +2,7 @@
#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";
@@ -28,8 +29,14 @@ bool Application::Initialize() {
return false; return false;
} }
// Initialize hot reload // Initialize hot reload to watch the scripts directory so any script change
hotReload = new HotReload(SCRIPT_FILE); // (not just the main file) triggers a reload.
{
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);
+29 -5
View File
@@ -3,21 +3,21 @@
namespace fs = std::filesystem; namespace fs = std::filesystem;
HotReload::HotReload(const std::string& filename) : scriptFile(filename), lastWriteTime(0) { HotReload::HotReload(const std::string& pathToWatch) : path(pathToWatch), lastWriteTime(0) {
UpdateLastWriteTime(); UpdateLastWriteTime();
} }
bool HotReload::CheckForChanges() { bool HotReload::CheckForChanges() {
std::time_t currentWriteTime = GetFileWriteTime(scriptFile); std::time_t current = GetLatestWriteTimeInPath();
if (currentWriteTime != lastWriteTime) { if (current != lastWriteTime) {
lastWriteTime = currentWriteTime; lastWriteTime = current;
return true; return true;
} }
return false; return false;
} }
void HotReload::UpdateLastWriteTime() { void HotReload::UpdateLastWriteTime() {
lastWriteTime = GetFileWriteTime(scriptFile); lastWriteTime = GetLatestWriteTimeInPath();
} }
std::time_t HotReload::GetFileWriteTime(const std::string& filename) { std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
@@ -30,3 +30,27 @@ 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;
}
+70 -42
View File
@@ -8,6 +8,9 @@
#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) {
} }
@@ -22,9 +25,6 @@ bool ScriptEngine::Initialize() {
return false; return false;
} }
debugger = new CDebugger();
debugger->SetEngine(engine);
// Register std::string // Register std::string
RegisterStdString(engine); RegisterStdString(engine);
@@ -41,13 +41,57 @@ 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;
@@ -118,11 +162,6 @@ 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;
} }
@@ -133,10 +172,6 @@ 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();
@@ -169,13 +204,6 @@ 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) {
@@ -202,36 +230,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 relative to the including file's directory // Build the full path candidates: relative to 'from' and any include paths
std::string fullPath; std::vector<std::string> candidates;
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);
fullPath = directory + include; candidates.push_back(directory + include);
} else {
// No directory in from path, use include as-is
fullPath = include;
} }
} else {
fullPath = include;
} }
// Try to add the included file // Add user-specified include search paths
int r = builder->AddSectionFromFile(fullPath.c_str()); // Note: includePaths is a member of the ScriptEngine instance, but this
// 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);
if (r < 0) { int result = -1;
log_error("Failed to include file: %s (tried path: %s)", include, fullPath.c_str()); for (auto &cand : candidates) {
} else { result = builder->AddSectionFromFile(cand.c_str());
log_info("Successfully included: %s", include); if (result >= 0) {
log_info("Successfully included: %s", cand.c_str());
return result;
}
} }
return r; log_error("Failed to include file: %s (tried %zu paths)", include, candidates.size());
return result;
} }
std::string ScriptEngine::ReadFile(const std::string& filename) { std::string ScriptEngine::ReadFile(const std::string& filename) {
+18 -5
View File
@@ -21,6 +21,7 @@
*/ */
#include "log.h" #include "log.h"
#include <string.h> // for memcpy
#define MAX_CALLBACKS 32 #define MAX_CALLBACKS 32
@@ -187,6 +188,14 @@ 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,
@@ -200,9 +209,11 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
if (!L.quiet && level >= L.level) if (!L.quiet && level >= L.level)
{ {
init_event(&ev, stderr); init_event(&ev, stderr);
va_start(ev.ap, fmt); va_list ap_copy;
va_copy(ap_copy, ap);
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
stdout_callback(&ev); stdout_callback(&ev);
va_end(ev.ap); va_end(ap_copy);
} }
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++) for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
@@ -211,9 +222,11 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
if (level >= cb->level) if (level >= cb->level)
{ {
init_event(&ev, cb->udata); init_event(&ev, cb->udata);
va_start(ev.ap, fmt); va_list ap_copy;
va_copy(ap_copy, ap);
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
cb->fn(&ev); cb->fn(&ev);
va_end(ev.ap); va_end(ap_copy);
} }
} }
@@ -222,5 +235,5 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
void raylib_log(int msgType, const char *text, va_list args) void raylib_log(int msgType, const char *text, va_list args)
{ {
log_log(msgType, NULL, 0, text, args); log_vlog(msgType, NULL, 0, text, args);
} }
+2
View File
@@ -48,6 +48,8 @@ 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);
+30
View File
@@ -0,0 +1,30 @@
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)
+3
View File
@@ -0,0 +1,3 @@
#include "../external/acutest/include/acutest.h"
// This translation unit provides the single main() for Acutest.
+103
View File
@@ -0,0 +1,103 @@
#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 }
};