12 Commits

17 changed files with 968 additions and 171 deletions
+2 -1
View File
@@ -1 +1,2 @@
build/
build/
log.txt
+2 -1
View File
@@ -1,6 +1,7 @@
{
"files.associations": {
"*.h": "c",
"xstring": "cpp"
"xstring": "cpp",
"iostream": "cpp"
}
}
+36 -2
View File
@@ -25,21 +25,55 @@ target_include_directories(scriptstdstring
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
)
# -------------------------
# 4️⃣ Main executable
# scriptbuilder add-on
# -------------------------
add_executable(simian main.cpp)
add_library(scriptbuilder STATIC
external/angelscript/sdk/add_on/scriptbuilder/scriptbuilder.cpp
)
target_include_directories(scriptbuilder
PUBLIC external/angelscript/sdk/angelscript/include
)
# -------------------------
# Main executable
# -------------------------
add_executable(simian
main.cpp
src/Application.cpp
src/ScriptEngine.cpp
src/ScriptBindings.cpp
src/HotReload.cpp
src/log/log.c
)
target_include_directories(simian PUBLIC
include
src/log
external/raylib/src
external/angelscript/sdk/angelscript/include
external/angelscript/sdk/add_on/scriptstdstring
external/angelscript/sdk/add_on/scriptbuilder
external/angelscript/sdk/add_on/debugger
)
target_link_libraries(simian
raylib
angelscript
scriptstdstring
scriptbuilder
debugger
)
# -------------------------
+30
View File
@@ -3,6 +3,36 @@
**Simian** is a Raylib + AngelScript test project on Windows and Linux.
It demonstrates how to integrate **Raylib** for graphics and **AngelScript** for scripting, including the `scriptstdstring` add-on.
## Refactored Architecture
The project has been refactored from a single `main.cpp` file into a more maintainable modular structure:
### Core Components
- **Application** (`Application.h/cpp`) - Main application lifecycle and game loop
- **ScriptEngine** (`ScriptEngine.h/cpp`) - AngelScript engine management and script compilation
- **ScriptBindings** (`ScriptBindings.h/cpp`) - C++ to AngelScript function bindings
- **HotReload** (`HotReload.h/cpp`) - File monitoring for automatic script reloading
- **main.cpp** - Minimal entry point
### Directory Structure
```
Simian/
├─ include/ # Header files
│ ├─ Application.h
│ ├─ ScriptEngine.h
│ ├─ ScriptBindings.h
│ └─ HotReload.h
├─ src/ # Source files
│ ├─ Application.cpp
│ ├─ ScriptEngine.cpp
│ ├─ ScriptBindings.cpp
│ └─ HotReload.cpp
├─ external/ # Dependencies
├─ scripts/ # AngelScript files
├─ main.cpp # Entry point
└─ CMakeLists.txt # Build configuration
```
---
## Requirements
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "ScriptEngine.h"
#include "HotReload.h"
class Application {
public:
Application();
~Application();
bool Initialize();
void Run();
void Shutdown();
private:
ScriptEngine scriptEngine;
HotReload* hotReload;
bool scriptCompilationError;
FILE* logFile;
static const int WINDOW_WIDTH = 800;
static const int WINDOW_HEIGHT = 600;
static const int TARGET_FPS = 60;
static const char* WINDOW_TITLE;
static const char* SCRIPT_FILE;
void Update(float deltaTime);
void Draw();
};
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <string>
#include <chrono>
#include <filesystem>
class HotReload {
public:
HotReload(const std::string& filename);
bool CheckForChanges();
void UpdateLastWriteTime();
private:
std::string scriptFile;
std::time_t lastWriteTime;
std::time_t GetFileWriteTime(const std::string& filename);
};
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "angelscript.h"
#include <string>
class ScriptBindings {
public:
static void RegisterAll(asIScriptEngine* engine);
private:
static void Print(asIScriptGeneric* gen);
// Log(level, message) - generic to get script context
static void Log(asIScriptGeneric* gen);
static void AS_DrawText(const std::string &text, int x, int y, int fontSize, unsigned int color);
};
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include "angelscript.h"
#include "scriptbuilder.h"
#include <string>
#include "debugger.h"
class ScriptEngine {
public:
ScriptEngine();
~ScriptEngine();
bool Initialize();
void Shutdown();
bool CompileScript(const std::string& filename);
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
void GarbageCollect();
asIScriptEngine* GetEngine() const { return engine; }
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
void AddFileBreakPoint(const std::string& file, int line);
void AddFuncBreakPoint(const std::string& func);
private:
asIScriptEngine* engine;
asIScriptFunction* updateFunc;
asIScriptFunction* drawFunc;
asIScriptModule* currentModule;
bool hasValidScript;
CDebugger* debugger = nullptr;
static void MessageCallback(const asSMessageInfo* msg, void* param);
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
std::string ReadFile(const std::string& filename);
void ClearCachedFunctions();
};
+12 -158
View File
@@ -1,162 +1,16 @@
#include <iostream>
#include <filesystem>
#include <chrono>
#include <thread>
#include "raylib.h"
#include "angelscript.h"
#include "scriptstdstring.h"
#include <fstream>
#include <sstream>
#include <assert.h>
namespace fs = std::filesystem;
#include "Application.h"
#include "src/log/log.h"
asIScriptEngine* engine = nullptr;
std::string scriptFile = "scripts/test.as";
std::time_t lastWriteTime = 0;
// Cached script functions
asIScriptFunction* updateFunc = nullptr;
asIScriptFunction* drawFunc = nullptr;
// -------------------------
// Functions exposed to AngelScript
// -------------------------
void Print(const std::string &msg) {
std::cout << "[Script] " << msg << std::endl;
}
Color ColorFromUInt(unsigned int c) {
Color col;
col.r = (c >> 24) & 0xFF;
col.g = (c >> 16) & 0xFF;
col.b = (c >> 8) & 0xFF;
col.a = c & 0xFF;
return col;
}
void AS_DrawText(const std::string &text, int x, int y, int fontSize, unsigned int color) {
DrawText(text.c_str(), x, y, fontSize, ColorFromUInt(color));
}
// -------------------------
// AngelScript message callback
// -------------------------
void AngelScriptMessageCallback(const asSMessageInfo* msg, void* param) {
std::cout << msg->section << " (" << msg->row << "): " << msg->message << std::endl;
}
// -------------------------
// Utility to read script files
// -------------------------
std::string ReadFile(const std::string &filename) {
std::ifstream file(filename);
if (!file) return "";
std::stringstream ss;
ss << file.rdbuf();
return ss.str();
}
// -------------------------
// Compile script and cache Update/Draw
// -------------------------
bool compileScript(const std::string& filename) {
engine->GarbageCollect();
asIScriptModule* mod = engine->GetModule("main", asGM_ALWAYS_CREATE);
std::string code = ReadFile(filename);
if (code.empty()) {
std::cerr << "Failed to read script file: " << filename << "\n";
return false;
}
int r = mod->AddScriptSection(filename.c_str(), code.c_str());
if (r < 0) return false;
r = mod->Build();
if (r < 0) return false;
// Cache Update(float dt) and Draw() functions if they exist
updateFunc = mod->GetFunctionByName("Update");
drawFunc = mod->GetFunctionByName("Draw");
std::cout << "Script compiled and cached: " << filename << "\n";
return true;
}
// -------------------------
// Hot reload check
// -------------------------
void checkHotReload() {
auto ftime = fs::last_write_time(scriptFile);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
std::time_t t = std::chrono::system_clock::to_time_t(sctp);
if (t != lastWriteTime) {
lastWriteTime = t;
compileScript(scriptFile);
}
}
// -------------------------
// Call a cached script function
// -------------------------
void callScriptFunction(asIScriptFunction* func, float dt = 0.0f) {
if (!func) return;
asIScriptContext* ctx = engine->CreateContext();
ctx->Prepare(func);
if (func->GetParamCount() == 1) {
ctx->SetArgFloat(0, dt);
}
ctx->Execute();
ctx->Release();
}
// -------------------------
// Main
// -------------------------
int main() {
// Initialize Raylib
InitWindow(800, 600, "Raylib + AngelScript");
SetTargetFPS(60);
// Initialize AngelScript
engine = asCreateScriptEngine();
assert(engine);
RegisterStdString(engine);
engine->RegisterGlobalFunction("void DrawText(const string &in, int, int, int, uint)",
asFUNCTION(AS_DrawText), asCALL_CDECL);
int r = engine->SetMessageCallback(asFUNCTION(AngelScriptMessageCallback), nullptr, asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Print(const string &in)", asFUNCTION(Print), asCALL_CDECL);
assert(r >= 0);
compileScript(scriptFile);
// Main loop
while (!WindowShouldClose()) {
float dt = GetFrameTime();
checkHotReload();
callScriptFunction(updateFunc, dt);
BeginDrawing();
ClearBackground(RAYWHITE);
DrawText("Modify scripts/test.as to hot-reload!", 50, 50, 20, DARKGRAY);
callScriptFunction(drawFunc);
EndDrawing();
std::this_thread::sleep_for(std::chrono::milliseconds(10));
Application app;
if (!app.Initialize()) {
log_error("Failed to initialize application");
return -1;
}
CloseWindow();
engine->ShutDownAndRelease();
app.Run();
app.Shutdown();
return 0;
}
+3 -9
View File
@@ -1,11 +1,5 @@
float x = 50;
float y = 100;
void Update(float dt) {
x += 50 * dt; // move text horizontally
if (x > 800) x = 0;
}
#include "update.as"
void Draw() {
DrawText("Hello from AngelScript!", int(x), int(y), 20, 0xFF0000FF);
}
DrawText("Hello from AngelScript - Working perfectly!", int(x), int(y), 20, 0xFF0000FF);
}
+13
View File
@@ -0,0 +1,13 @@
float x = 50;
float y = 100;
void Update(float dt) {
x += 50 * dt;
if (x > 800) {
x = 0;
Print("X position reset!");
Log(LOG_INFO, "Log INFO: reset happened");
Log(LOG_WARNING, "Log WARNING: reset happened");
Log(LOG_ERROR, "Log ERROR: reset happened");
}
}
+98
View File
@@ -0,0 +1,98 @@
#include "Application.h"
#include "raylib.h"
#include <chrono>
#include <thread>
#include "log/log.h"
const char* Application::WINDOW_TITLE = "Raylib + AngelScript";
const char* Application::SCRIPT_FILE = "scripts/test.as";
Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr) {
}
Application::~Application() {
Shutdown();
}
bool Application::Initialize() {
logFile = fopen("log.txt", "w");
log_add_fp(logFile, LOG_TRACE);
SetTraceLogCallback(raylib_log);
// Initialize Raylib
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
SetTargetFPS(TARGET_FPS);
// Initialize AngelScript
if (!scriptEngine.Initialize()) {
log_error("Failed to initialize script engine");
return false;
}
// Initialize hot reload
hotReload = new HotReload(SCRIPT_FILE);
// Compile initial script
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
return true;
}
void Application::Run() {
while (!WindowShouldClose()) {
float deltaTime = GetFrameTime();
Update(deltaTime);
Draw();
// Small sleep to prevent excessive CPU usage
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void Application::Update(float deltaTime) {
// Check for hot reload
if (hotReload && hotReload->CheckForChanges()) {
bool success = scriptEngine.CompileScript(SCRIPT_FILE);
scriptCompilationError = !success;
if (!success) {
log_warn("Script compilation failed - keeping previous version");
}
}
// Call script Update function
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
}
void Application::Draw() {
BeginDrawing();
ClearBackground(RAYWHITE);
// Show script error status in top left if there's an error, otherwise show normal message
if (scriptCompilationError) {
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
}
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
EndDrawing();
}
void Application::Shutdown() {
if (hotReload) {
delete hotReload;
hotReload = nullptr;
}
scriptEngine.Shutdown();
// Clear the trace log callback before closing window to prevent logging after cleanup
SetTraceLogCallback(nullptr);
CloseWindow();
// Close log file after everything else is cleaned up
if (logFile) {
fclose(logFile);
logFile = nullptr;
}
}
+32
View File
@@ -0,0 +1,32 @@
#include "HotReload.h"
#include <iostream>
namespace fs = std::filesystem;
HotReload::HotReload(const std::string& filename) : scriptFile(filename), lastWriteTime(0) {
UpdateLastWriteTime();
}
bool HotReload::CheckForChanges() {
std::time_t currentWriteTime = GetFileWriteTime(scriptFile);
if (currentWriteTime != lastWriteTime) {
lastWriteTime = currentWriteTime;
return true;
}
return false;
}
void HotReload::UpdateLastWriteTime() {
lastWriteTime = GetFileWriteTime(scriptFile);
}
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
try {
auto ftime = fs::last_write_time(filename);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
return std::chrono::system_clock::to_time_t(sctp);
} catch (const std::exception&) {
return 0;
}
}
+116
View File
@@ -0,0 +1,116 @@
#include "ScriptBindings.h"
#include "raylib.h"
#include <iostream>
#include <assert.h>
#include "log/log.h"
// Expose log level constants to AngelScript
static int AS_LOG_TRACE = 1;
static int AS_LOG_DEBUG = 2;
static int AS_LOG_INFO = 3;
static int AS_LOG_WARNING = 4;
static int AS_LOG_ERROR = 5;
static int AS_LOG_FATAL = 6;
void ScriptBindings::RegisterAll(asIScriptEngine* engine) {
// Register Print function with generic calling convention to access context
int r = engine->RegisterGlobalFunction("void Print(const string &in)",
asFUNCTION(Print), asCALL_GENERIC);
assert(r >= 0);
// Register log level constants so scripts can refer to LOG_INFO, LOG_ERROR, etc.
r = engine->RegisterGlobalProperty("int LOG_TRACE", (void*)&AS_LOG_TRACE);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_DEBUG", (void*)&AS_LOG_DEBUG);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_INFO", (void*)&AS_LOG_INFO);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_WARNING", (void*)&AS_LOG_WARNING);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_ERROR", (void*)&AS_LOG_ERROR);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_FATAL", (void*)&AS_LOG_FATAL);
assert(r >= 0);
// Register Log(level, message) to allow scripts to log at different levels
r = engine->RegisterGlobalFunction("void Log(int, const string &in)",
asFUNCTION(Log), asCALL_GENERIC);
assert(r >= 0);
// Register DrawText function
r = engine->RegisterGlobalFunction("void DrawText(const string &in, int, int, int, uint)",
asFUNCTION(AS_DrawText), asCALL_CDECL);
assert(r >= 0);
}
void ScriptBindings::Print(asIScriptGeneric* gen) {
// Get the message parameter
std::string* msg = static_cast<std::string*>(gen->GetArgObject(0));
// Get the active context to retrieve script file and line number
asIScriptContext* ctx = asGetActiveContext();
if (ctx) {
const char* section = nullptr;
int line = 0;
// Get the current line number and script section
line = ctx->GetLineNumber(0, nullptr, &section);
if (section && line > 0) {
// Log with script file and line number
log_log(LOG_INFO, section, line, "%s", msg->c_str());
} else {
// Fallback if we can't get the info
log_info("%s", msg->c_str());
}
} else {
// No context available, just log normally
log_info("%s", msg->c_str());
}
}
void ScriptBindings::Log(asIScriptGeneric* gen) {
// arg0: int level, arg1: string message
int level = gen->GetArgDWord(0);
std::string* msg = static_cast<std::string*>(gen->GetArgObject(1));
// Obtain script context to fetch file/line
asIScriptContext* ctx = asGetActiveContext();
if (ctx) {
const char* section = nullptr;
int line = 0;
line = ctx->GetLineNumber(0, nullptr, &section);
if (section && line > 0) {
// Use the library's log_log to preserve filename/line info
log_log(level, section, line, "%s", msg ? msg->c_str() : "");
return;
}
}
// Fallback: log without script location
switch (level) {
case LOG_TRACE: log_trace("%s", msg ? msg->c_str() : ""); break;
case LOG_DEBUG: log_debug("%s", msg ? msg->c_str() : ""); break;
case LOG_WARNING: log_warn("%s", msg ? msg->c_str() : ""); break;
case LOG_ERROR: log_error("%s", msg ? msg->c_str() : ""); break;
case LOG_FATAL: log_fatal("%s", msg ? msg->c_str() : ""); break;
case LOG_INFO:
default:
log_info("%s", msg ? msg->c_str() : "");
break;
}
}
Color ColorFromUInt(unsigned int c) {
Color col;
col.r = (c >> 24) & 0xFF;
col.g = (c >> 16) & 0xFF;
col.b = (c >> 8) & 0xFF;
col.a = c & 0xFF;
return col;
}
void ScriptBindings::AS_DrawText(const std::string &text, int x, int y, int fontSize, unsigned int color) {
DrawText(text.c_str(), x, y, fontSize, ColorFromUInt(color));
}
+243
View File
@@ -0,0 +1,243 @@
#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();
}
+226
View File
@@ -0,0 +1,226 @@
/*
* Copyright (c) 2020 rxi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "log.h"
#define MAX_CALLBACKS 32
// Helper function to extract just the filename from a full path
static const char* get_filename(const char* path) {
if (!path) return "";
const char* filename = path;
const char* p = path;
// Find the last '/' or '\' in the path
while (*p) {
if (*p == '/' || *p == '\\') {
filename = p + 1;
}
p++;
}
return filename;
}
typedef struct
{
log_LogFn fn;
void *udata;
int level;
} Callback;
static struct
{
void *udata;
log_LockFn lock;
int level;
bool quiet;
Callback callbacks[MAX_CALLBACKS];
} L;
static const char *level_strings[] = {
"", "TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "FATAL"};
#ifdef LOG_USE_COLOR
static const char *level_colors[] = {
"","\x1b[94m", "\x1b[36m", "\x1b[32m", "\x1b[33m", "\x1b[31m", "\x1b[35m"};
#endif
static void stdout_callback(log_Event *ev)
{
char buf[16];
buf[strftime(buf, sizeof(buf), "%H:%M:%S", ev->time)] = '\0';
#ifdef LOG_USE_COLOR
if (ev->file)
{
fprintf(
ev->udata, "%s %s%-5s\x1b[0m \x1b[90m%s:%d:\x1b[0m ",
buf, level_colors[ev->level], level_strings[ev->level],
get_filename(ev->file), ev->line);
}
else
{
fprintf(
ev->udata, "%s %s%-5s\x1b[0m ",
buf, level_colors[ev->level], level_strings[ev->level]);
}
#else
if (ev->file)
{
fprintf(
ev->udata, "%s %-5s %s:%d: ",
buf, level_strings[ev->level], get_filename(ev->file), ev->line);
}
else
{
fprintf(
ev->udata, "%s %-5s: ",
buf, level_strings[ev->level]);
}
#endif
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void file_callback(log_Event *ev)
{
char buf[64];
buf[strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ev->time)] = '\0';
if (ev->file)
{
fprintf(ev->udata, "%s %-5s %s:%d: ", buf, level_strings[ev->level], get_filename(ev->file), ev->line);
}
else
{
fprintf(ev->udata, "%s %-5s: ", buf, level_strings[ev->level]);
}
vfprintf(ev->udata, ev->fmt, ev->ap);
fprintf(ev->udata, "\n");
fflush(ev->udata);
}
static void lock(void)
{
if (L.lock)
{
L.lock(true, L.udata);
}
}
static void unlock(void)
{
if (L.lock)
{
L.lock(false, L.udata);
}
}
const char *log_level_string(int level)
{
return level_strings[level];
}
void log_set_lock(log_LockFn fn, void *udata)
{
L.lock = fn;
L.udata = udata;
}
void log_set_level(int level)
{
L.level = level;
}
void log_set_quiet(bool enable)
{
L.quiet = enable;
}
int log_add_callback(log_LogFn fn, void *udata, int level)
{
for (int i = 0; i < MAX_CALLBACKS; i++)
{
if (!L.callbacks[i].fn)
{
L.callbacks[i] = (Callback){fn, udata, level};
return 0;
}
}
return -1;
}
int log_add_fp(FILE *fp, int level)
{
return log_add_callback(file_callback, fp, level);
}
static void init_event(log_Event *ev, void *udata)
{
if (!ev->time)
{
time_t t = time(NULL);
ev->time = localtime(&t);
}
ev->udata = udata;
}
void log_log(int level, const char *file, int line, const char *fmt, ...)
{
log_Event ev = {
.fmt = fmt,
.file = file,
.line = line,
.level = level,
};
lock();
if (!L.quiet && level >= L.level)
{
init_event(&ev, stderr);
va_start(ev.ap, fmt);
stdout_callback(&ev);
va_end(ev.ap);
}
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
{
Callback *cb = &L.callbacks[i];
if (level >= cb->level)
{
init_event(&ev, cb->udata);
va_start(ev.ap, fmt);
cb->fn(&ev);
va_end(ev.ap);
}
}
unlock();
}
void raylib_log(int msgType, const char *text, va_list args)
{
log_log(msgType, NULL, 0, text, args);
}
+59
View File
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2020 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>
#include "raylib.h"
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_VERSION "0.1.0"
#define LOG_USE_COLOR
typedef struct {
va_list ap;
const char *fmt;
const char *file;
struct tm *time;
void *udata;
int line;
int level;
} log_Event;
typedef void (*log_LogFn)(log_Event *ev);
typedef void (*log_LockFn)(bool lock, void *udata);
#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__)
#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
#define log_warn(...) log_log(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__)
#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__)
const char* log_level_string(int level);
void log_set_lock(log_LockFn fn, void *udata);
void log_set_level(int level);
void log_set_quiet(bool enable);
int log_add_callback(log_LogFn fn, void *udata, int level);
int log_add_fp(FILE *fp, int level);
void log_log(int level, const char *file, int line, const char *fmt, ...);
void raylib_log(int msgType, const char *text, va_list args);
#ifdef __cplusplus
}
#endif
#endif