2 Commits

Author SHA1 Message Date
nick 529e51009c feat: hardcoded breakpoint 2025-11-06 15:57:45 +13:00
nick 2e958fa1ec feat: added debugger - untested 2025-11-06 15:51:47 +13:00
35 changed files with 334 additions and 7260 deletions
-39
View File
@@ -1,39 +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)
- name: Build application
run: cmake --build build --target simian -j $(nproc)
-9
View File
@@ -4,12 +4,3 @@
[submodule "external/angelscript"]
path = external/angelscript
url = https://github.com/anjo76/angelscript.git
[submodule "external/acutest"]
path = external/acutest
url = https://github.com/mity/acutest
[submodule "external/imgui"]
path = external/imgui
url = https://github.com/ocornut/imgui.git
[submodule "external/rlImGui"]
path = external/rlImGui
url = https://github.com/raylib-extras/rlImGui.git
+1 -89
View File
@@ -2,94 +2,6 @@
"files.associations": {
"*.h": "c",
"xstring": "cpp",
"thread": "cpp",
"cctype": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"array": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"charconv": "cpp",
"chrono": "cpp",
"codecvt": "cpp",
"compare": "cpp",
"complex": "cpp",
"concepts": "cpp",
"condition_variable": "cpp",
"cstdint": "cpp",
"deque": "cpp",
"list": "cpp",
"map": "cpp",
"set": "cpp",
"string": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"exception": "cpp",
"algorithm": "cpp",
"functional": "cpp",
"iterator": "cpp",
"memory": "cpp",
"memory_resource": "cpp",
"numeric": "cpp",
"optional": "cpp",
"random": "cpp",
"ratio": "cpp",
"regex": "cpp",
"source_location": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"tuple": "cpp",
"type_traits": "cpp",
"utility": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"mutex": "cpp",
"new": "cpp",
"numbers": "cpp",
"ostream": "cpp",
"semaphore": "cpp",
"span": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"stop_token": "cpp",
"streambuf": "cpp",
"cinttypes": "cpp",
"typeindex": "cpp",
"typeinfo": "cpp",
"variant": "cpp",
"format": "cpp",
"*.m": "cpp",
"filesystem": "cpp",
"forward_list": "cpp",
"ios": "cpp",
"locale": "cpp",
"xfacet": "cpp",
"xhash": "cpp",
"xiosbase": "cpp",
"xlocale": "cpp",
"xlocbuf": "cpp",
"xlocinfo": "cpp",
"xlocmes": "cpp",
"xlocmon": "cpp",
"xlocnum": "cpp",
"xloctime": "cpp",
"xmemory": "cpp",
"xtr1common": "cpp",
"xtree": "cpp",
"xutility": "cpp"
"iostream": "cpp"
}
}
+16 -50
View File
@@ -14,38 +14,6 @@ add_subdirectory(external/raylib)
# -------------------------
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
# -------------------------
# Dear ImGui (docking branch)
# -------------------------
# Ensure the submodule exists at external/imgui (added with git submodule)
add_subdirectory(cmake/imgui)
# -------------------------
# rlImGui (optional - Raylib ImGui integration)
# -------------------------
# If you add rlImGui as a submodule under external/rlImGui, CMake will pick it up
option(USE_RLIMGUI "Enable rlImGui integration if found in external/rlImGui" ON)
set(RLIMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/rlImGui")
if(USE_RLIMGUI)
if(EXISTS "${RLIMGUI_ROOT}/CMakeLists.txt")
message(STATUS "rlImGui CMakeLists found at ${RLIMGUI_ROOT} - adding to build")
add_subdirectory(${RLIMGUI_ROOT} EXCLUDE_FROM_ALL)
set(HAVE_RLIMGUI TRUE)
elseif(EXISTS "${RLIMGUI_ROOT}")
message(STATUS "rlImGui sources found at ${RLIMGUI_ROOT} - using project wrapper cmake/rlImGui")
add_subdirectory(cmake/rlImGui)
set(HAVE_RLIMGUI TRUE)
else()
set(HAVE_RLIMGUI FALSE)
endif()
else()
set(HAVE_RLIMGUI FALSE)
endif()
# -------------------------
# Tests
# -------------------------
enable_testing()
add_subdirectory(tests)
# -------------------------
# 3️⃣ scriptstdstring add-on
# -------------------------
@@ -57,8 +25,18 @@ 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
)
# -------------------------
# 3️⃣.1️⃣ scriptbuilder add-on
# scriptbuilder add-on
# -------------------------
add_library(scriptbuilder STATIC
external/angelscript/sdk/add_on/scriptbuilder/scriptbuilder.cpp
@@ -69,7 +47,7 @@ target_include_directories(scriptbuilder
)
# -------------------------
# 4️⃣ Main executable
# Main executable
# -------------------------
add_executable(simian
main.cpp
@@ -77,8 +55,6 @@ add_executable(simian
src/ScriptEngine.cpp
src/ScriptBindings.cpp
src/HotReload.cpp
src/GuiManager.cpp
src/LogWindow.cpp
src/log/log.c
)
@@ -89,32 +65,22 @@ target_include_directories(simian PUBLIC
external/angelscript/sdk/angelscript/include
external/angelscript/sdk/add_on/scriptstdstring
external/angelscript/sdk/add_on/scriptbuilder
external/imgui
external/angelscript/sdk/add_on/debugger
)
target_link_libraries(simian PRIVATE
target_link_libraries(simian
raylib
angelscript
scriptstdstring
scriptbuilder
imgui
debugger
)
if(HAVE_RLIMGUI)
target_include_directories(simian PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian PRIVATE rlImGui)
# Expose HAVE_RLIMGUI to the C++ compiler so source can guard ImGui calls
target_compile_definitions(simian PRIVATE HAVE_RLIMGUI=1)
message(STATUS "Linking rlImGui into simian")
else()
message(STATUS "rlImGui not found - building without rlImGui integration")
endif()
# -------------------------
# 5️⃣ Platform-specific linking
# -------------------------
if(WIN32)
target_link_libraries(simian PRIVATE winmm gdi32)
target_link_libraries(simian winmm gdi32)
elseif(UNIX)
# Linux / macOS: Raylib handles system libs automatically
endif()
-76
View File
@@ -1,76 +0,0 @@
# Justfile for Simian project
# Usage: just <recipe>
# Default configuration
set shell := ["powershell", "-Command"]
# Variables
BUILD_DIR := "build"
CMAKE_CONFIG := "Release"
CMAKE_GENERATOR := "Visual Studio 17 2022"
CMAKE_ARCH := "x64"
# Initialize and update submodules
submodules:
@echo "Updating submodules..."
git submodule update --init --recursive
# Configure the project (out-of-source)
configure:
@echo "Configuring CMake (generator: {{CMAKE_GENERATOR}}, arch: {{CMAKE_ARCH}})"
cmake -G "{{CMAKE_GENERATOR}}" -A {{CMAKE_ARCH}} -S . -B {{BUILD_DIR}}
# Configure for Debug
configure-debug:
@echo "Configuring Debug"
cmake -G "{{CMAKE_GENERATOR}}" -A {{CMAKE_ARCH}} -S . -B {{BUILD_DIR}} -DCMAKE_BUILD_TYPE=Debug
# Build (Release by default)
build:
@echo "Building (config: {{CMAKE_CONFIG}})"
cmake --build {{BUILD_DIR}} --config {{CMAKE_CONFIG}}
# Build Debug
build-debug:
@echo "Building Debug"
cmake --build {{BUILD_DIR}} --config Debug
# Run the built executable (defaults to Debug for development)
run:
@echo "Running simian (Release)..."
{{BUILD_DIR}}\Release\simian.exe
# Run Debug build
run-debug:
@echo "Running simian (Debug)..."
{{BUILD_DIR}}\Debug\simian.exe
# Run Release build
run-release:
@echo "Running simian (Release)..."
{{BUILD_DIR}}\Release\simian.exe
# Clean build folder
clean:
@echo "Removing {{BUILD_DIR}} folder"
Remove-Item -Recurse -Force {{BUILD_DIR}} -ErrorAction SilentlyContinue
# Reconfigure, build and run (convenience)
rebuild-run: submodules configure build run
# Lint / formatting (placeholder)
format:
@echo "No formatter configured. Add commands here."
# Tests (placeholder - uses CTest if configured)
test:
@echo "Running tests (if configured)"
cmake --build {{BUILD_DIR}} --config {{CMAKE_CONFIG}} --target RUN_TESTS
# Provide a help recipe
help:
@echo "Available recipes: submodules, configure, configure-debug, build, build-debug, run, clean, rebuild-run, format, test"
# Notes for non-Windows shells
# If you're running on Git Bash or WSL, set shell and generator accordingly, e.g.:
# just -s --shell "bash -lc" configure
+167 -97
View File
@@ -1,130 +1,200 @@
## Simian
# Simian
Simian is a small Raylib + AngelScript test project, refactored to be easy to extend and to show a safe hot-reload workflow for AngelScript.
**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.
This README was updated to reflect the current layout, build paths, and the small CMake wrapper approach used to optionally build ImGui / rlImGui when those folders are present.
## Refactored Architecture
## What changed
The project has been refactored from a single `main.cpp` file into a more maintainable modular structure:
- The original single-file demo was split into modules: Application, ScriptEngine, ScriptBindings and HotReload.
- Hot-reloads are performed safely: scripts compile into a temporary module first and only replace the running module on successful compilation. Compilation errors will no longer crash the running app.
- CMake wrappers live in `cmake/` to build third-party projects that don't ship a CMakeLists.txt (example: ImGui, rlImGui). This keeps submodules untouched.
- A `Justfile` was added with convenient recipes for configure, build, run and cleaning.
## Quick start (Windows)
Open a developer prompt for Visual Studio or use PowerShell / cmd configured for MSVC.
Clone with submodules:
```powershell
git clone --recurse-submodules <repo-url>
cd Simian
```
Using the included `Justfile` (optional):
```powershell
# from project root
just configure # runs cmake -S . -B build -G "Visual Studio 17 2022" -A x64
just build # builds Debug by default (uses cmake --build)
just run # runs the built executable (Debug)
```
Or use raw CMake commands:
```powershell
mkdir build; cd build
cmake -G "Visual Studio 17 2022" -A x64 ..
cmake --build . --config Release
# run:
build\Release\simian.exe
```
If you are using cmd.exe, the same commands apply (adjust quotes for generator if needed).
## Linux / Unix
```bash
git clone --recurse-submodules <repo-url>
cd Simian
mkdir build && cd build
cmake -G "Unix Makefiles" ..
cmake --build .
./simian
```
## Project layout (important files)
### 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/
├─ CMakeLists.txt # top level CMake
├─ Justfile # convenience tasks (configure/build/run/clean)
├─ include/ # public headers (Application, ScriptEngine, ScriptBindings, HotReload)
├─ src/ # implementation
├─ scripts/ # AngelScript source files loaded at runtime
├─ external/ # git submodules (raylib, angelscript, optional imgui/rlImGui)
└─ cmake/ # local CMake wrappers (imgui, rlImGui)
├─ 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
```
## Hot-reload notes
---
- The app watches `scripts/` for changes. When a script is updated it is compiled into a temporary AngelScript module.
- If compilation succeeds, the temp module replaces the live `main` module and new functions are used. If compilation fails, the running module is left intact and a single-line error is shown on-screen.
- This avoids crashes that happen when a script with compilation errors would otherwise replace/break function pointers while the engine is executing.
## Requirements
## ImGui / rlImGui (optional)
### Windows
- Windows 10/11 (tested)
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with **Desktop development with C++** workload
- [CMake 3.25+](https://cmake.org/download/)
- Git
- Optional: VSCode for editing
The repository prefers to keep upstream code as submodules. Some upstream projects (like certain ImGui tags or rlImGui) don't provide a CMakeLists.txt. To support those without editing submodules, the project includes small wrappers in `cmake/`:
### Linux
- Ubuntu 20.04+ or equivalent Linux distribution
- GCC 9+ or Clang 10+
- [CMake 3.25+](https://cmake.org/download/)
- Git
- Development libraries: `sudo apt install build-essential cmake git`
- `cmake/imgui/` builds ImGui core (and optionally backends) when `external/imgui` is present.
- `cmake/rlImGui/` builds the rlImGui integration and links it to the `imgui` target if available.
---
If you prefer to use the upstream CMake (when present), the top-level `CMakeLists.txt` will prefer `external/rlImGui`'s CMake if it detects one; otherwise it falls back to the wrapper.
## Getting Started
To add ImGui/rlImGui as submodules:
### 1. Clone the repository with submodules
```powershell
# from repo root
git submodule add https://github.com/ocornut/imgui.git external/imgui
git submodule add https://github.com/NeoSpark314/rlImGui.git external/rlImGui
```bash
git clone --recurse-submodules <your-repo-url>
cd Simian
```
If you forgot `--recurse-submodules`, run:
```bash
git submodule update --init --recursive
```
Then re-run CMake from a clean build directory.
This will pull **Raylib** and **AngelScript** into `external/`.
Troubleshooting: if Git warns about "embedded git repository" when adding files, convert the folder to a submodule (remove cached tracked files, then add as submodule) — this README intentionally avoids modifying submodule contents.
---
## Tests / Verification
### 2. Build with CMake
Quick smoke test after building:
#### Windows (Visual Studio)
```powershell
# run the Debug build
build\Debug\simian.exe
```bash
# Create build folder
mkdir build
cd build
# Generate Visual Studio solution (64-bit)
cmake -G "Visual Studio 17 2022" -A x64 ..
# Build Release version
cmake --build . --config Release
```
You should see the Raylib window. The running console prints from script when the default script executes (e.g. "[Script] Hello from AngelScript!").
After building, the executable will be at:
If a script has a compilation error, the app will continue running and a brief error message is presented on the top-left of the window.
## Updating submodules
Keep submodules pinned for reproducible builds. To update them safely:
```powershell
cd external/raylib && git fetch && git checkout <commit-or-branch>
cd ../angelscript && git fetch && git checkout <commit-or-branch>
cd ../.. && git add external/raylib external/angelscript && git commit -m "Update submodules"
```
build\Release\simian.exe
```
#### Linux (Unix Makefiles)
```bash
# Create build folder
mkdir build
cd build
# Generate Unix Makefiles
cmake -G "Unix Makefiles" ..
# Build
cmake --build .
```
After building, the executable will be at:
```
build/simian
```
---
### 3. Run the project
#### Windows
```bash
build\Release\simian.exe
```
#### Linux
```bash
./build/simian
```
You should see a **Raylib window** open and the console will output:
```
[Script] Hello from AngelScript!
```
---
## Project Structure
```
Simian/
├─ external/
│ ├─ raylib/ # Git submodule
│ └─ angelscript/ # Git submodule (includes scriptstdstring add-on)
├─ scripts/ # Optional: .as scripts to be loaded at runtime
├─ main.cpp # Entry point (Raylib + AngelScript)
├─ CMakeLists.txt # Build configuration
└─ README.md
```
---
## Notes
- The project was developed and tested on Windows with Visual Studio; Linux builds are supported but may need X11 / audio dev packages installed depending on your environment.
- The codebase avoids changing tracked submodule contents; use the `cmake/` wrappers when a submodule doesn't include a CMake build.
* **AngelScript `string` support** is enabled via `scriptstdstring` add-on.
* **Hot-reloading scripts** can be implemented by loading `.as` files from the `scripts/` folder.
* Update submodules with:
```bash
cd external/raylib
git pull origin master
cd ../angelscript
git pull origin master
cd ../..
git add external/raylib external/angelscript
git commit -m "Update submodules"
```
---
## CMake Targets
* `simian` — main executable
* `raylib` — static library submodule
* `angelscript` — static library submodule
* `scriptstdstring` — static library add-on for AngelScript
---
## Tips
#### Windows
* Always use **VS2022 Developer Command Prompt** or VSCode terminal configured for MSVC.
#### Linux
* Make sure you have the required development packages installed.
* On some distributions you may need additional X11 development libraries.
#### General
* Pin submodules to specific commits for reproducible builds.
* To clean build: delete the `build/` folder and regenerate with CMake.
---
## License
* Raylib: Zlib License (see https://github.com/raysan5/raylib#license)
* AngelScript: MIT License (see https://github.com/angelcode/angelscript/blob/master/license.txt)
* Simian: Not open source (yet)
* Raylib: [Zlib License](https://github.com/raysan5/raylib#license)
* AngelScript: [MIT License](https://github.com/angelcode/angelscript/blob/master/license.txt)
* Simian: Not open source (Yet)
-63
View File
@@ -1,63 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(imgui_wrapper NONE)
# Root of the imgui submodule (expected to live at external/imgui)
set(IMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/imgui")
if(NOT EXISTS "${IMGUI_ROOT}/imgui.h")
message(FATAL_ERROR "ImGui not found in ${IMGUI_ROOT}. Run: git submodule update --init --recursive")
endif()
# Options: build demo and/or backends. By default we only build the core library which
# avoids pulling in platform deps (SDL/Android/etc) from the backends and examples.
option(IMGUI_BUILD_DEMO "Include imgui_demo.cpp in the build" OFF)
option(IMGUI_BUILD_BACKENDS "Include selected backend implementations from backends/" OFF)
# Core sources (explicit list - avoids accidentally including examples)
set(IMGUI_CORE_SRC
"${IMGUI_ROOT}/imgui.cpp"
"${IMGUI_ROOT}/imgui_draw.cpp"
"${IMGUI_ROOT}/imgui_tables.cpp"
"${IMGUI_ROOT}/imgui_widgets.cpp"
)
if(IMGUI_BUILD_DEMO)
list(APPEND IMGUI_CORE_SRC "${IMGUI_ROOT}/imgui_demo.cpp")
endif()
add_library(imgui STATIC ${IMGUI_CORE_SRC})
target_include_directories(imgui PUBLIC
${IMGUI_ROOT}
)
# Optionally include a small, safe set of backends (desktop common ones) when requested.
if(IMGUI_BUILD_BACKENDS)
# Gather backend sources and whitelist a few common desktop backends
file(GLOB IMGUI_BACKEND_SRCS "${IMGUI_ROOT}/backends/*.cpp")
set(IMGUI_BACKEND_WHITELIST
"imgui_impl_glfw.cpp"
"imgui_impl_opengl3.cpp"
"imgui_impl_win32.cpp"
"imgui_impl_sdl2.cpp"
)
set(IMGUI_SELECTED_BACKENDS)
foreach(_f IN LISTS IMGUI_BACKEND_SRCS)
get_filename_component(_name ${_f} NAME)
foreach(_allowed IN LISTS IMGUI_BACKEND_WHITELIST)
if(_name STREQUAL _allowed)
list(APPEND IMGUI_SELECTED_BACKENDS ${_f})
endif()
endforeach()
endforeach()
if(IMGUI_SELECTED_BACKENDS)
target_sources(imgui PRIVATE ${IMGUI_SELECTED_BACKENDS})
target_include_directories(imgui PUBLIC "${IMGUI_ROOT}/backends")
endif()
endif()
# Build position independent code to make static lib usable in shared contexts
set_target_properties(imgui PROPERTIES POSITION_INDEPENDENT_CODE ON)
-51
View File
@@ -1,51 +0,0 @@
cmake_minimum_required(VERSION 3.10)
project(rlImGui_wrapper NONE)
# Path to the rlImGui sources (submodule should be under external/rlImGui)
set(RLIMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/rlImGui")
if(NOT EXISTS "${RLIMGUI_ROOT}/README.md")
message(FATAL_ERROR "rlImGui not found in ${RLIMGUI_ROOT}. Add it as a submodule: git submodule add <url> external/rlImGui")
endif()
# Collect sources - rlImGui has sources under src/ and maybe example/backends
# Look for sources either under src/ or at the project root
file(GLOB RLIMGUI_ROOT_SRCS "${RLIMGUI_ROOT}/*.c" "${RLIMGUI_ROOT}/*.cpp")
file(GLOB RLIMGUI_SRC_DIR_SRCS "${RLIMGUI_ROOT}/src/*.c" "${RLIMGUI_ROOT}/src/*.cpp")
set(RLIMGUI_SOURCES ${RLIMGUI_ROOT_SRCS} ${RLIMGUI_SRC_DIR_SRCS})
# Exclude examples/tests if present
list(FILTER RLIMGUI_SOURCES EXCLUDE REGEX ".*example.*|.*test.*")
if(RLIMGUI_SOURCES)
add_library(rlImGui STATIC ${RLIMGUI_SOURCES})
# Add includes: rlImGui root and src if present
target_include_directories(rlImGui PUBLIC
${RLIMGUI_ROOT}
${RLIMGUI_ROOT}/src
)
# Ensure ImGui's headers are available (imgui submodule expected at external/imgui)
set(IMGUI_ROOT "${CMAKE_SOURCE_DIR}/external/imgui")
if(EXISTS "${IMGUI_ROOT}/imgui.h")
target_include_directories(rlImGui PUBLIC ${IMGUI_ROOT})
endif()
# If the imgui target exists (from cmake/imgui wrapper), link it so we reuse the same lib
if(TARGET imgui)
target_link_libraries(rlImGui PRIVATE imgui)
endif()
else()
message(FATAL_ERROR "No rlImGui sources found under ${RLIMGUI_ROOT}. Expected rlImGui.cpp or src/*.cpp")
endif()
# Provide a property so consumers can check availability
set_target_properties(rlImGui PROPERTIES EXPORT_NAME rlImGui)
# Try to link to raylib if available in the parent project
if(TARGET raylib)
target_link_libraries(rlImGui PRIVATE raylib)
endif()
-1
Submodule external/acutest deleted from 31751b4089
-1
Submodule external/imgui deleted from e7d2d636af
-1
Submodule external/rlImGui deleted from 4d8a618429
-42
View File
@@ -1,42 +0,0 @@
[Window][DockSpaceHost]
Pos=0,0
Size=1280,720
Collapsed=0
[Window][Debug##Default]
Pos=384,193
Size=400,400
Collapsed=0
[Window][Game Window]
Pos=8,29
Size=1264,513
Collapsed=0
DockId=0x00000001,0
[Window][Log Viewer]
Pos=8,544
Size=1264,168
Collapsed=0
DockId=0x00000002,0
[Window][##TOAST0]
Pos=1010,635
Size=250,65
Collapsed=0
[Window][##TOAST1]
Pos=1010,543
Size=250,65
Collapsed=0
[Window][##TOAST2]
Pos=982,485
Size=278,65
Collapsed=0
[Docking][Data]
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,29 Size=1264,683 Split=Y Selected=0x27A02DAA
DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,513 CentralNode=1 Selected=0x27A02DAA
DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,168 Selected=0xBEDDA0C1
+3 -11
View File
@@ -1,32 +1,24 @@
#pragma once
#include "ScriptEngine.h"
#include "HotReload.h"
#include "GuiManager.h"
#include "raylib.h"
class Application {
public:
Application();
~Application();
bool Initialize(int argc, char* argv[]);
bool Initialize();
void Run();
void Shutdown();
bool IsEditorEnabled() const { return enableEditor; }
bool HasScriptCompilationError() const { return scriptCompilationError; }
private:
ScriptEngine scriptEngine;
HotReload* hotReload;
bool scriptCompilationError;
FILE* logFile;
GuiManager guiManager;
bool enableEditor;
RenderTexture2D renderTexture; // renderTexture for Raylib rendering
static const int WINDOW_WIDTH = 1280;
static const int WINDOW_HEIGHT = 720;
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;
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "rlImGui.h"
#include "imgui.h"
#include "extras/IconsFontAwesome6.h"
#include "gui/fa-solid-900.h"
#include "gui/ImGuiNotify.hpp"
#include "LogWindow.h" // Include the new LogWindow class
class Application;
class GuiManager {
public:
GuiManager();
~GuiManager();
void Initialize(Application* application);
void Render(RenderTexture2D& renderTexture);
void Shutdown();
private:
void SetupDockspace(RenderTexture2D& renderTexture);
void RenderNotifications();
void RenderErrorBanner();
void SetTheme();
bool showLogWindow = true;
Application *app;
LogWindow logWindow; // Add LogWindow as a member
};
+4 -10
View File
@@ -5,20 +5,14 @@
class HotReload {
public:
// 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);
HotReload(const std::string& filename);
bool CheckForChanges();
void UpdateLastWriteTime();
private:
std::string path;
std::string scriptFile;
std::time_t lastWriteTime;
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();
};
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "imgui.h"
#include "log.h"
#include <vector>
#include <string>
struct LogEntry {
std::string message;
ImVec4 color;
};
class LogWindow {
public:
void Initialize();
void Render(bool& showLogWindow);
static void LogCallback(log_Event* ev);
private:
std::vector<LogEntry> logEntries;
};
+4 -10
View File
@@ -2,7 +2,7 @@
#include "angelscript.h"
#include "scriptbuilder.h"
#include <string>
#include <vector>
#include "debugger.h"
class ScriptEngine {
public:
@@ -14,18 +14,12 @@ public:
bool CompileScript(const std::string& filename);
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
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; }
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;
@@ -33,7 +27,7 @@ private:
asIScriptFunction* drawFunc;
asIScriptModule* currentModule;
bool hasValidScript;
std::vector<std::string> includePaths;
CDebugger* debugger = nullptr;
static void MessageCallback(const asSMessageInfo* msg, void* param);
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
#pragma once
#include "gui/ImGuiNotify.hpp"
#include <string>
#include <string_view>
#include <vector>
#include <cstdio>
#include <utility>
class Toast
{
public:
template <typename... Args>
static void Info(std::string_view fmt, Args&&... args)
{
ImGui::InsertNotification({ImGuiToastType::Info, 3000, Format(fmt, std::forward<Args>(args)...).c_str()});
}
template <typename... Args>
static void Warning(std::string_view fmt, Args&&... args)
{
ImGui::InsertNotification({ImGuiToastType::Warning, 4000, Format(fmt, std::forward<Args>(args)...).c_str()});
}
template <typename... Args>
static void Error(std::string_view fmt, Args&&... args)
{
ImGui::InsertNotification({ImGuiToastType::Error, 5000, Format(fmt, std::forward<Args>(args)...).c_str()});
}
template <typename... Args>
static void Success(std::string_view fmt, Args&&... args)
{
ImGui::InsertNotification({ImGuiToastType::Success, 2500, Format(fmt, std::forward<Args>(args)...).c_str()});
}
private:
template <typename... Args>
static std::string Format(std::string_view fmt, Args&&... args)
{
if constexpr (sizeof...(Args) == 0)
{
return std::string(fmt);
}
else
{
std::string format(fmt);
int size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...) + 1;
if (size <= 0)
return std::string(fmt);
std::vector<char> buffer(static_cast<size_t>(size));
std::snprintf(buffer.data(), static_cast<size_t>(size), format.c_str(), std::forward<Args>(args)...);
return std::string(buffer.data());
}
}
};
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,10 +1,10 @@
#include "Application.h"
#include "src/log/log.h"
int main(int argc, char* argv[]) {
int main() {
Application app;
if (!app.Initialize(argc, argv)) {
if (!app.Initialize()) {
log_error("Failed to initialize application");
return -1;
}
@@ -13,4 +13,4 @@ int main(int argc, char* argv[]) {
app.Shutdown();
return 0;
}
}
-17
View File
@@ -1,17 +0,0 @@
typedef void string;
void Print(const string&in);
void Log(int level, const string&in);
void DrawText(const string&in, int x, int y, int fontSize, int color);
int LOG_TRACE;
int LOG_DEBUG;
int LOG_INFO;
int LOG_WARNING;
int LOG_ERROR;
int LOG_FATAL;
namespace Toast {
void Info(const string&in);
void Warning(const string&in);
void Error(const string&in);
void Success(const string&in);
}
View File
+4 -3
View File
@@ -2,11 +2,12 @@ float x = 50;
float y = 100;
void Update(float dt) {
x += 64 * dt;
x += 50 * dt;
if (x > 800) {
x = 0;
Print("X position reset!");
Log(LOG_INFO, "Log INFO: reset happened");
Toast::Info("X position has been reset.");
Log(LOG_WARNING, "Log WARNING: reset happened");
Log(LOG_ERROR, "Log ERROR: reset happened");
}
}
}
+33 -145
View File
@@ -2,124 +2,60 @@
#include "raylib.h"
#include <chrono>
#include <thread>
#include <cstring>
#include <filesystem>
#include "log/log.h"
const char* Application::WINDOW_TITLE = "Raylib + AngelScript";
const char* Application::SCRIPT_FILE = "scripts/test.as";
#include "rlImGui.h"
#include "imgui.h"
#include "extras/IconsFontAwesome6.h"
#include "gui/ImGuiNotify.hpp"
#include "GuiManager.h"
#include "gui/Toast.h"
#ifdef _WIN32
// On Windows, fopen_s is already available, so no need to define it.
#else
// On non-Windows platforms, define fopen_s as a macro for fopen.
#define fopen_s(pFile, filename, mode) ((*(pFile) = fopen((filename), (mode))) == NULL)
#endif
const char *Application::WINDOW_TITLE = "Simian";
const char *Application::SCRIPT_FILE = "scripts/game.as";
Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr), renderTexture{} // Initialize renderTexture
{
Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr) {
}
Application::~Application()
{
// Reminder in case we change main.cpp, but we do not want to close the
// window more than once
// Shutdown();
Application::~Application() {
Shutdown();
}
bool Application::Initialize(int argc, char *argv[])
{
if (fopen_s(&logFile, "log.txt", "w") != 0)
{
logFile = nullptr;
log_error("Failed to open log file");
}
else
{
log_add_fp(logFile, LOG_TRACE);
}
bool Application::Initialize() {
logFile = fopen("log.txt", "w");
log_add_fp(logFile, LOG_TRACE);
SetTraceLogCallback(raylib_log);
enableEditor = false;
// Parse command-line arguments
for (int i = 1; i < argc; ++i)
{
if (std::strcmp(argv[i], "--editor") == 0)
{
enableEditor = true;
}
}
// Initialize Raylib
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
SetTargetFPS(TARGET_FPS);
// Initialize AngelScript
if (!scriptEngine.Initialize())
{
if (!scriptEngine.Initialize()) {
log_error("Failed to initialize script engine");
return false;
}
// Initialize hot reload to watch the scripts directory so any script change
// triggers a reload.
{
std::filesystem::path p(SCRIPT_FILE);
std::string watchPath = p.parent_path().string();
if (watchPath.empty())
watchPath = ".";
hotReload = new HotReload(watchPath);
}
// Initialize hot reload
hotReload = new HotReload(SCRIPT_FILE);
// Compile initial script
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
if (enableEditor)
{
guiManager.Initialize(this);
renderTexture = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT);
}
return true;
}
void Application::Run()
{
while (!WindowShouldClose())
{
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)
{
void Application::Update(float deltaTime) {
// Check for hot reload
if (hotReload && hotReload->CheckForChanges())
{
if (hotReload && hotReload->CheckForChanges()) {
bool success = scriptEngine.CompileScript(SCRIPT_FILE);
scriptCompilationError = !success;
if (!success)
{
if (!success) {
log_warn("Script compilation failed - keeping previous version");
Toast::Warning("Script compilation failed - check console for details.");
} else {
Toast::Success("Script reloaded successfully.");
}
}
@@ -127,83 +63,35 @@ void Application::Update(float deltaTime)
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
}
void Application::Draw()
{
if (enableEditor)
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
void Application::Draw() {
BeginDrawing();
ClearBackground(RAYWHITE);
// Fallback to default values if screen dimensions are invalid
if (screenWidth <= 0 || screenHeight <= 0)
{
log_warn("Invalid screen dimensions: %d x %d. Using default values.", screenWidth, screenHeight);
screenWidth = 800;
screenHeight = 600;
}
ImGuiIO &io = ImGui::GetIO();
io.DisplaySize = ImVec2((float)screenWidth, (float)screenHeight); // Set DisplaySize
BeginTextureMode(renderTexture);
ClearBackground(RAYWHITE);
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
EndTextureMode();
BeginDrawing();
ClearBackground(RAYWHITE);
guiManager.Render(renderTexture); // Render the GUI
EndDrawing();
// 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);
}
else
{
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());
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
EndDrawing();
}
EndDrawing();
}
void Application::Shutdown()
{
if (hotReload)
{
void Application::Shutdown() {
if (hotReload) {
delete hotReload;
hotReload = nullptr;
}
scriptEngine.Shutdown();
if (enableEditor)
{
guiManager.Shutdown();
}
// Shutdown rlImGui first while the GL context and window are still valid
rlImGuiShutdown();
// Shutdown script engine
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)
{
if (logFile) {
fclose(logFile);
logFile = nullptr;
}
-229
View File
@@ -1,229 +0,0 @@
#include "GuiManager.h"
#include "raylib.h"
#include <fstream> // Add this to fix the incomplete type error
#include <string>
#include <iostream>
#include "log.h"
#include "Application.h"
#include <filesystem> // Add this for cross-platform file handling
GuiManager::GuiManager() {}
GuiManager::~GuiManager() {}
void GuiManager::Initialize(Application *application)
{
app = application;
rlImGuiSetup(true);
ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
io.Fonts->AddFontDefault();
float baseFontSize = 16.0f;
float iconFontSize = baseFontSize * 2.0f / 3.0f;
static constexpr ImWchar iconsRanges[] = {ICON_MIN_FA, ICON_MAX_16_FA, 0};
ImFontConfig iconsConfig;
iconsConfig.MergeMode = true;
iconsConfig.PixelSnapH = true;
iconsConfig.GlyphMinAdvanceX = iconFontSize;
io.Fonts->AddFontFromMemoryCompressedTTF(fa_solid_900_compressed_data, fa_solid_900_compressed_size, iconFontSize, &iconsConfig, iconsRanges);
SetTheme();
logWindow.Initialize(); // Initialize the LogWindow
log_trace("GuiManager::Initialize - Started");
}
void GuiManager::Render(RenderTexture2D &renderTexture)
{
rlImGuiBegin();
SetupDockspace(renderTexture);
RenderErrorBanner();
RenderNotifications();
logWindow.Render(showLogWindow); // Render the LogWindow
rlImGuiEnd();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
void GuiManager::Shutdown()
{
rlImGuiShutdown();
}
void GuiManager::SetupDockspace(RenderTexture2D &renderTexture)
{
static bool dockspaceOpen = true;
static bool opt_fullscreen = true;
static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
if (opt_fullscreen)
{
ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->WorkPos);
ImGui::SetNextWindowSize(viewport->WorkSize);
ImGui::SetNextWindowViewport(viewport->ID);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
}
ImGui::Begin("DockSpaceHost", &dockspaceOpen, window_flags);
if (opt_fullscreen)
ImGui::PopStyleVar(2);
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Game"))
{
if (ImGui::MenuItem("Quit", "Ctrl + Q")) {
// Handle quit here
app->Shutdown();
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("View"))
{
ImGui::MenuItem("Log Viewer", "Ctrl + L", &showLogWindow);
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_L))
{
showLogWindow = !showLogWindow;
}
if (ImGui::GetIO().KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_Q))
{
app->Shutdown();
}
ImGuiID dockspace_id = ImGui::GetID("MyDockSpace");
ImGui::DockSpace(dockspace_id, ImVec2(0.0f, 0.0f), dockspace_flags);
// Add a Game Window
ImGui::Begin("Game Window");
// Render Raylib content to the Game Window
rlImGuiImageRenderTexture(&renderTexture);
ImGui::End();
ImGui::End();
}
void GuiManager::RenderErrorBanner()
{
if (!app || !app->HasScriptCompilationError())
return;
ImGui::SetNextWindowPos(ImVec2(0.0f, 0.0f));
ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, 0.0f));
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration |
ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings;
ImGui::Begin("PersistentBanner", nullptr, flags);
ImGui::TextColored(ImVec4(1, 0.4f, 0.4f, 1), ICON_FA_TRIANGLE_EXCLAMATION " Script Compilation Error - running last working version");
ImGui::End();
}
void GuiManager::RenderNotifications()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.10f, 0.10f, 0.10f, 1.00f));
ImGui::RenderNotifications();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(1);
}
void GuiManager::SetTheme()
{
ImVec4 *colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f);
colors[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_PopupBg] = ImVec4(0.19f, 0.19f, 0.19f, 0.92f);
colors[ImGuiCol_Border] = ImVec4(0.19f, 0.19f, 0.19f, 0.29f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.24f);
colors[ImGuiCol_FrameBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
colors[ImGuiCol_TitleBg] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.06f, 0.06f, 0.06f, 1.00f);
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 0.54f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
colors[ImGuiCol_CheckMark] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.34f, 0.34f, 0.34f, 0.54f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.54f);
colors[ImGuiCol_Button] = ImVec4(0.05f, 0.05f, 0.05f, 0.54f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.19f, 0.19f, 0.19f, 0.54f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.00f, 0.00f, 0.00f, 0.36f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.20f, 0.22f, 0.23f, 0.33f);
colors[ImGuiCol_Separator] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.44f, 0.44f, 0.44f, 0.29f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.40f, 0.44f, 0.47f, 1.00f);
colors[ImGuiCol_Tab] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TabHovered] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_TabActive] = ImVec4(0.20f, 0.20f, 0.20f, 0.36f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
// colors[ImGuiCol_DockingPreview] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
// colors[ImGuiCol_DockingEmptyBg] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.00f, 0.00f, 0.00f, 0.52f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.28f, 0.28f, 0.28f, 0.29f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.20f, 0.22f, 0.23f, 1.00f);
colors[ImGuiCol_DragDropTarget] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
colors[ImGuiCol_NavHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 0.00f, 0.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(1.00f, 0.00f, 0.00f, 0.35f);
colors[ImGuiCol_CheckMark] = ImVec4(0.33f, 0.67f, 0.86f, 1.00f);
ImGuiStyle &style = ImGui::GetStyle();
style.WindowPadding = ImVec2(8.00f, 8.00f);
style.FramePadding = ImVec2(5.00f, 2.00f);
style.CellPadding = ImVec2(6.00f, 6.00f);
style.ItemSpacing = ImVec2(6.00f, 6.00f);
style.ItemInnerSpacing = ImVec2(6.00f, 6.00f);
style.TouchExtraPadding = ImVec2(0.00f, 0.00f);
style.IndentSpacing = 25;
style.ScrollbarSize = 15;
style.GrabMinSize = 10;
style.WindowBorderSize = 1;
style.ChildBorderSize = 1;
style.PopupBorderSize = 1;
style.FrameBorderSize = 1;
style.TabBorderSize = 1;
style.WindowRounding = 7;
style.ChildRounding = 4;
style.FrameRounding = 3;
style.PopupRounding = 4;
style.ScrollbarRounding = 9;
style.GrabRounding = 3;
style.LogSliderDeadzone = 4;
style.TabRounding = 4;
}
+5 -29
View File
@@ -3,21 +3,21 @@
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();
}
bool HotReload::CheckForChanges() {
std::time_t current = GetLatestWriteTimeInPath();
if (current != lastWriteTime) {
lastWriteTime = current;
std::time_t currentWriteTime = GetFileWriteTime(scriptFile);
if (currentWriteTime != lastWriteTime) {
lastWriteTime = currentWriteTime;
return true;
}
return false;
}
void HotReload::UpdateLastWriteTime() {
lastWriteTime = GetLatestWriteTimeInPath();
lastWriteTime = GetFileWriteTime(scriptFile);
}
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
@@ -29,28 +29,4 @@ std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
} catch (const std::exception&) {
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;
}
-50
View File
@@ -1,50 +0,0 @@
#include "LogWindow.h"
#include "log.h"
void LogWindow::Initialize()
{
logEntries.clear();
log_add_callback(LogCallback, this, LOG_ALL);
}
void LogWindow::Render(bool& showLogWindow)
{
if (!showLogWindow)
return;
ImGui::Begin("Log Viewer");
ImGui::BeginChild("LogText", ImVec2(0, 0), true, ImGuiWindowFlags_HorizontalScrollbar);
for (const auto& entry : logEntries)
{
ImGui::TextColored(entry.color, "%s", entry.message.c_str());
}
if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
ImGui::SetScrollHereY(1.0f);
ImGui::EndChild();
ImGui::End();
}
void LogWindow::LogCallback(log_Event* ev)
{
LogWindow* logWindow = static_cast<LogWindow*>(ev->udata);
char buffer[1024];
vsnprintf(buffer, sizeof(buffer), ev->fmt, ev->ap);
ImVec4 color;
switch (ev->level)
{
case LOG_TRACE: color = ImVec4(0.5f, 0.5f, 0.5f, 1.0f); break;
case LOG_DEBUG: color = ImVec4(0.0f, 0.5f, 1.0f, 1.0f); break;
case LOG_INFO: color = ImVec4(0.0f, 1.0f, 0.0f, 1.0f); break;
case LOG_WARNING: color = ImVec4(1.0f, 1.0f, 0.0f, 1.0f); break;
case LOG_ERROR: color = ImVec4(1.0f, 0.0f, 0.0f, 1.0f); break;
case LOG_FATAL: color = ImVec4(1.0f, 0.0f, 1.0f, 1.0f); break;
default: color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break;
}
logWindow->logEntries.push_back({buffer, color});
}
+41 -102
View File
@@ -3,7 +3,6 @@
#include <iostream>
#include <assert.h>
#include "log/log.h"
#include "gui/Toast.h"
// Expose log level constants to AngelScript
static int AS_LOG_TRACE = 1;
@@ -13,123 +12,76 @@ static int AS_LOG_WARNING = 4;
static int AS_LOG_ERROR = 5;
static int AS_LOG_FATAL = 6;
static void AS_ToastInfo(const std::string& message)
{
Toast::Info(message);
}
static void AS_ToastWarning(const std::string& message)
{
Toast::Warning(message);
}
static void AS_ToastError(const std::string& message)
{
Toast::Error(message);
}
static void AS_ToastSuccess(const std::string& message)
{
Toast::Success(message);
}
void ScriptBindings::RegisterAll(asIScriptEngine *engine)
{
void ScriptBindings::RegisterAll(asIScriptEngine* engine) {
// Register Print function with generic calling convention to access context
[[maybe_unused]] int r = engine->RegisterGlobalFunction("void Print(const string &in)",
asFUNCTION(Print), asCALL_GENERIC);
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);
r = engine->RegisterGlobalProperty("int LOG_TRACE", (void*)&AS_LOG_TRACE);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_DEBUG", (void *)&AS_LOG_DEBUG);
r = engine->RegisterGlobalProperty("int LOG_DEBUG", (void*)&AS_LOG_DEBUG);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_INFO", (void *)&AS_LOG_INFO);
r = engine->RegisterGlobalProperty("int LOG_INFO", (void*)&AS_LOG_INFO);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_WARNING", (void *)&AS_LOG_WARNING);
r = engine->RegisterGlobalProperty("int LOG_WARNING", (void*)&AS_LOG_WARNING);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_ERROR", (void *)&AS_LOG_ERROR);
r = engine->RegisterGlobalProperty("int LOG_ERROR", (void*)&AS_LOG_ERROR);
assert(r >= 0);
r = engine->RegisterGlobalProperty("int LOG_FATAL", (void *)&AS_LOG_FATAL);
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);
asFUNCTION(Log), asCALL_GENERIC);
assert(r >= 0);
// Toast functions
engine->SetDefaultNamespace("Toast");
r = engine->RegisterGlobalFunction("void Info(const string &in)",
asFUNCTION(AS_ToastInfo), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Warning(const string &in)",
asFUNCTION(AS_ToastWarning), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Error(const string &in)",
asFUNCTION(AS_ToastError), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Success(const string &in)",
asFUNCTION(AS_ToastSuccess), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
// Register DrawText function
r = engine->RegisterGlobalFunction("void DrawText(const string &in, int, int, int, uint)",
asFUNCTION(AS_DrawText), asCALL_CDECL);
asFUNCTION(AS_DrawText), asCALL_CDECL);
assert(r >= 0);
}
void ScriptBindings::Print(asIScriptGeneric *gen)
{
void ScriptBindings::Print(asIScriptGeneric* gen) {
// Get the message parameter
std::string *msg = static_cast<std::string *>(gen->GetArgObject(0));
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;
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)
{
if (section && line > 0) {
// Log with script file and line number
log_log(LOG_INFO, section, line, "%s", msg->c_str());
}
else
{
} else {
// Fallback if we can't get the info
log_info("%s", msg->c_str());
}
}
else
{
} else {
// No context available, just log normally
log_info("%s", msg->c_str());
}
}
void ScriptBindings::Log(asIScriptGeneric *gen)
{
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));
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;
asIScriptContext* ctx = asGetActiveContext();
if (ctx) {
const char* section = nullptr;
int line = 0;
line = ctx->GetLineNumber(0, nullptr, &section);
if (section && line > 0)
{
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;
@@ -137,32 +89,20 @@ void ScriptBindings::Log(asIScriptGeneric *gen)
}
// 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;
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 ColorFromUInt(unsigned int c) {
Color col;
col.r = (c >> 24) & 0xFF;
col.g = (c >> 16) & 0xFF;
@@ -171,7 +111,6 @@ Color ColorFromUInt(unsigned int c)
return col;
}
void ScriptBindings::AS_DrawText(const std::string &text, int x, int y, int fontSize, unsigned int color)
{
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));
}
+47 -75
View File
@@ -8,9 +8,6 @@
#include <assert.h>
#include "log/log.h"
#include <vector>
#include <mutex>
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
}
@@ -25,6 +22,9 @@ bool ScriptEngine::Initialize() {
return false;
}
debugger = new CDebugger();
debugger->SetEngine(engine);
// Register std::string
RegisterStdString(engine);
@@ -41,57 +41,13 @@ bool ScriptEngine::Initialize() {
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() {
ClearCachedFunctions();
if (debugger) {
debugger->SetEngine(nullptr);
delete debugger;
debugger = nullptr;
}
if (engine) {
engine->ShutDownAndRelease();
engine = nullptr;
@@ -162,6 +118,11 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
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;
}
@@ -172,6 +133,10 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
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();
@@ -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*) {
// Map AngelScript message types to log levels
if (msg->type == asMSGTYPE_ERROR) {
@@ -215,7 +187,7 @@ void ScriptEngine::MessageCallback(const asSMessageInfo* msg, void*) {
}
}
int ScriptEngine::IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, [[maybe_unused]] void* userParam) {
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) {
@@ -230,36 +202,36 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
// Log the include request with clean filenames
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
// Build the full path candidates: relative to 'from' and any include paths
std::vector<std::string> candidates;
// 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);
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
// 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);
int result = -1;
for (auto &cand : candidates) {
result = builder->AddSectionFromFile(cand.c_str());
if (result >= 0) {
log_info("Successfully included: %s", cand.c_str());
return result;
}
// 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);
}
log_error("Failed to include file: %s (tried %zu paths)", include, candidates.size());
return result;
return r;
}
std::string ScriptEngine::ReadFile(const std::string& filename) {
+6 -27
View File
@@ -21,7 +21,6 @@
*/
#include "log.h"
#include <string.h> // for memcpy
#define MAX_CALLBACKS 32
@@ -182,28 +181,12 @@ static void init_event(log_Event *ev, void *udata)
if (!ev->time)
{
time_t t = time(NULL);
static struct tm tm_buf; // Thread-safe buffer for time struct
#ifdef _WIN32
localtime_s(&tm_buf, &t); // Use localtime_s on Windows
#else
localtime_r(&t, &tm_buf); // Use localtime_r on Linux/Unix
#endif
ev->time = &tm_buf;
ev->time = localtime(&t);
}
ev->udata = udata;
}
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 = {
.fmt = fmt,
@@ -217,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)
{
init_event(&ev, stderr);
va_list ap_copy;
va_copy(ap_copy, ap);
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
va_start(ev.ap, fmt);
stdout_callback(&ev);
va_end(ap_copy);
va_end(ev.ap);
}
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
@@ -230,11 +211,9 @@ void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap
if (level >= cb->level)
{
init_event(&ev, cb->udata);
va_list ap_copy;
va_copy(ap_copy, ap);
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
va_start(ev.ap, fmt);
cb->fn(&ev);
va_end(ap_copy);
va_end(ev.ap);
}
}
@@ -243,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)
{
log_vlog(msgType, NULL, 0, text, args);
log_log(msgType, NULL, 0, text, args);
}
-2
View File
@@ -48,8 +48,6 @@ 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, ...);
// 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);
-34
View File
@@ -1,34 +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
${CMAKE_SOURCE_DIR}/external/imgui
${CMAKE_SOURCE_DIR}/external/rlImGui
)
# Link AngelScript and its add-ons needed by ScriptEngine
target_link_libraries(unit_tests PRIVATE
angelscript
scriptstdstring
scriptbuilder
raylib
imgui
)
add_test(NAME unit_tests COMMAND unit_tests)
-3
View File
@@ -1,3 +0,0 @@
#include "../external/acutest/include/acutest.h"
// This translation unit provides the single main() for Acutest.
-103
View File
@@ -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 }
};