Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64023f875b | |||
| 24e4d33307 | |||
| 2ae078a82b | |||
| 26bcfac7d1 | |||
| dcc21f2ba2 | |||
| 2ed2a5c953 | |||
| f352b432d9 | |||
| 34304fcb2c | |||
| 3607ccee3b | |||
| 85c28e4107 | |||
| 8c0d567ab9 | |||
| 08814a3f6d | |||
| 2088773974 | |||
| 4380b92516 | |||
| 601f6640dc | |||
| 13c40743bf | |||
| 2cef544466 | |||
| 91c45d9f4c | |||
| a4848c0ed5 | |||
| 8fcbf64f5c | |||
| a990236330 | |||
| 5becf056c6 | |||
| e4f1018ee1 | |||
| c86eb421b1 | |||
| 1b32a3d86e | |||
| 2c1c64049c | |||
| 48580d0278 | |||
| 240a31848a | |||
| 18821b9cc9 | |||
| e1aa8eb6d5 | |||
| df51c75fb8 |
@@ -0,0 +1,39 @@
|
|||||||
|
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)
|
||||||
@@ -4,3 +4,12 @@
|
|||||||
[submodule "external/angelscript"]
|
[submodule "external/angelscript"]
|
||||||
path = external/angelscript
|
path = external/angelscript
|
||||||
url = https://github.com/anjo76/angelscript.git
|
url = https://github.com/anjo76/angelscript.git
|
||||||
|
[submodule "external/acutest"]
|
||||||
|
path = external/acutest
|
||||||
|
url = https://github.com/mity/acutest
|
||||||
|
[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
|
||||||
|
|||||||
Vendored
+90
-1
@@ -1,6 +1,95 @@
|
|||||||
{
|
{
|
||||||
"files.associations": {
|
"files.associations": {
|
||||||
"*.h": "c",
|
"*.h": "c",
|
||||||
"xstring": "cpp"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+48
-2
@@ -14,6 +14,38 @@ add_subdirectory(external/raylib)
|
|||||||
# -------------------------
|
# -------------------------
|
||||||
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
|
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
|
# 3️⃣ scriptstdstring add-on
|
||||||
# -------------------------
|
# -------------------------
|
||||||
@@ -45,6 +77,8 @@ add_executable(simian
|
|||||||
src/ScriptEngine.cpp
|
src/ScriptEngine.cpp
|
||||||
src/ScriptBindings.cpp
|
src/ScriptBindings.cpp
|
||||||
src/HotReload.cpp
|
src/HotReload.cpp
|
||||||
|
src/GuiManager.cpp
|
||||||
|
src/LogWindow.cpp
|
||||||
src/log/log.c
|
src/log/log.c
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,20 +89,32 @@ target_include_directories(simian PUBLIC
|
|||||||
external/angelscript/sdk/angelscript/include
|
external/angelscript/sdk/angelscript/include
|
||||||
external/angelscript/sdk/add_on/scriptstdstring
|
external/angelscript/sdk/add_on/scriptstdstring
|
||||||
external/angelscript/sdk/add_on/scriptbuilder
|
external/angelscript/sdk/add_on/scriptbuilder
|
||||||
|
external/imgui
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(simian
|
target_link_libraries(simian PRIVATE
|
||||||
raylib
|
raylib
|
||||||
angelscript
|
angelscript
|
||||||
scriptstdstring
|
scriptstdstring
|
||||||
scriptbuilder
|
scriptbuilder
|
||||||
|
imgui
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
# 5️⃣ Platform-specific linking
|
||||||
# -------------------------
|
# -------------------------
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(simian winmm gdi32)
|
target_link_libraries(simian PRIVATE winmm gdi32)
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
# Linux / macOS: Raylib handles system libs automatically
|
# Linux / macOS: Raylib handles system libs automatically
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# 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
|
||||||
@@ -1,200 +1,130 @@
|
|||||||
# Simian
|
## Simian
|
||||||
|
|
||||||
**Simian** is a Raylib + AngelScript test project on Windows and Linux.
|
Simian is a small Raylib + AngelScript test project, refactored to be easy to extend and to show a safe hot-reload workflow for AngelScript.
|
||||||
It demonstrates how to integrate **Raylib** for graphics and **AngelScript** for scripting, including the `scriptstdstring` add-on.
|
|
||||||
|
|
||||||
## Refactored Architecture
|
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.
|
||||||
|
|
||||||
The project has been refactored from a single `main.cpp` file into a more maintainable modular structure:
|
## What changed
|
||||||
|
|
||||||
### Core Components
|
- The original single-file demo was split into modules: Application, ScriptEngine, ScriptBindings and HotReload.
|
||||||
- **Application** (`Application.h/cpp`) - Main application lifecycle and game loop
|
- 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.
|
||||||
- **ScriptEngine** (`ScriptEngine.h/cpp`) - AngelScript engine management and script compilation
|
- CMake wrappers live in `cmake/` to build third-party projects that don't ship a CMakeLists.txt (example: ImGui, rlImGui). This keeps submodules untouched.
|
||||||
- **ScriptBindings** (`ScriptBindings.h/cpp`) - C++ to AngelScript function bindings
|
- A `Justfile` was added with convenient recipes for configure, build, run and cleaning.
|
||||||
- **HotReload** (`HotReload.h/cpp`) - File monitoring for automatic script reloading
|
|
||||||
- **main.cpp** - Minimal entry point
|
|
||||||
|
|
||||||
### Directory Structure
|
## Quick start (Windows)
|
||||||
```
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
Open a developer prompt for Visual Studio or use PowerShell / cmd configured for MSVC.
|
||||||
|
|
||||||
## Requirements
|
Clone with submodules:
|
||||||
|
|
||||||
### Windows
|
```powershell
|
||||||
- Windows 10/11 (tested)
|
git clone --recurse-submodules <repo-url>
|
||||||
- [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
|
|
||||||
|
|
||||||
### 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`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
### 1. Clone the repository with submodules
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone --recurse-submodules <your-repo-url>
|
|
||||||
cd Simian
|
cd Simian
|
||||||
```
|
```
|
||||||
|
|
||||||
If you forgot `--recurse-submodules`, run:
|
Using the included `Justfile` (optional):
|
||||||
|
|
||||||
```bash
|
```powershell
|
||||||
git submodule update --init --recursive
|
# 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)
|
||||||
```
|
```
|
||||||
|
|
||||||
This will pull **Raylib** and **AngelScript** into `external/`.
|
Or use raw CMake commands:
|
||||||
|
|
||||||
---
|
```powershell
|
||||||
|
mkdir build; cd build
|
||||||
### 2. Build with CMake
|
|
||||||
|
|
||||||
#### Windows (Visual Studio)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create build folder
|
|
||||||
mkdir build
|
|
||||||
cd build
|
|
||||||
|
|
||||||
# Generate Visual Studio solution (64-bit)
|
|
||||||
cmake -G "Visual Studio 17 2022" -A x64 ..
|
cmake -G "Visual Studio 17 2022" -A x64 ..
|
||||||
|
|
||||||
# Build Release version
|
|
||||||
cmake --build . --config Release
|
cmake --build . --config Release
|
||||||
```
|
# run:
|
||||||
|
|
||||||
After building, the executable will be at:
|
|
||||||
|
|
||||||
```
|
|
||||||
build\Release\simian.exe
|
build\Release\simian.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Linux (Unix Makefiles)
|
If you are using cmd.exe, the same commands apply (adjust quotes for generator if needed).
|
||||||
|
|
||||||
|
## Linux / Unix
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create build folder
|
git clone --recurse-submodules <repo-url>
|
||||||
mkdir build
|
cd Simian
|
||||||
cd build
|
mkdir build && cd build
|
||||||
|
|
||||||
# Generate Unix Makefiles
|
|
||||||
cmake -G "Unix Makefiles" ..
|
cmake -G "Unix Makefiles" ..
|
||||||
|
|
||||||
# Build
|
|
||||||
cmake --build .
|
cmake --build .
|
||||||
|
./simian
|
||||||
```
|
```
|
||||||
|
|
||||||
After building, the executable will be at:
|
## Project layout (important files)
|
||||||
|
|
||||||
```
|
|
||||||
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/
|
Simian/
|
||||||
├─ external/
|
├─ CMakeLists.txt # top level CMake
|
||||||
│ ├─ raylib/ # Git submodule
|
├─ Justfile # convenience tasks (configure/build/run/clean)
|
||||||
│ └─ angelscript/ # Git submodule (includes scriptstdstring add-on)
|
├─ include/ # public headers (Application, ScriptEngine, ScriptBindings, HotReload)
|
||||||
├─ scripts/ # Optional: .as scripts to be loaded at runtime
|
├─ src/ # implementation
|
||||||
├─ main.cpp # Entry point (Raylib + AngelScript)
|
├─ scripts/ # AngelScript source files loaded at runtime
|
||||||
├─ CMakeLists.txt # Build configuration
|
├─ external/ # git submodules (raylib, angelscript, optional imgui/rlImGui)
|
||||||
└─ README.md
|
└─ cmake/ # local CMake wrappers (imgui, rlImGui)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 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.
|
||||||
|
|
||||||
|
## ImGui / rlImGui (optional)
|
||||||
|
|
||||||
|
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/`:
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
To add ImGui/rlImGui as 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
|
||||||
|
git submodule update --init --recursive
|
||||||
|
```
|
||||||
|
|
||||||
|
Then re-run CMake from a clean build directory.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Quick smoke test after building:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# run the Debug build
|
||||||
|
build\Debug\simian.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see the Raylib window. The running console prints from script when the default script executes (e.g. "[Script] Hello from AngelScript!").
|
||||||
|
|
||||||
|
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"
|
||||||
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
* **AngelScript `string` support** is enabled via `scriptstdstring` add-on.
|
- 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.
|
||||||
* **Hot-reloading scripts** can be implemented by loading `.as` files from the `scripts/` folder.
|
- The codebase avoids changing tracked submodule contents; use the `cmake/` wrappers when a submodule doesn't include a CMake build.
|
||||||
* 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
|
## License
|
||||||
|
|
||||||
* Raylib: [Zlib License](https://github.com/raysan5/raylib#license)
|
* Raylib: Zlib License (see https://github.com/raysan5/raylib#license)
|
||||||
* AngelScript: [MIT License](https://github.com/angelcode/angelscript/blob/master/license.txt)
|
* AngelScript: MIT License (see https://github.com/angelcode/angelscript/blob/master/license.txt)
|
||||||
* Simian: Not open source (Yet)
|
* Simian: Not open source (yet)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
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)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
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 added at 31751b4089
+1
Submodule external/imgui added at e7d2d636af
+1
Submodule external/rlImGui added at 4d8a618429
@@ -0,0 +1,42 @@
|
|||||||
|
[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
|
||||||
|
|
||||||
+11
-3
@@ -1,24 +1,32 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "ScriptEngine.h"
|
#include "ScriptEngine.h"
|
||||||
#include "HotReload.h"
|
#include "HotReload.h"
|
||||||
|
#include "GuiManager.h"
|
||||||
|
#include "raylib.h"
|
||||||
|
|
||||||
class Application {
|
class Application {
|
||||||
public:
|
public:
|
||||||
Application();
|
Application();
|
||||||
~Application();
|
~Application();
|
||||||
|
|
||||||
bool Initialize();
|
bool Initialize(int argc, char* argv[]);
|
||||||
void Run();
|
void Run();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
bool IsEditorEnabled() const { return enableEditor; }
|
||||||
|
bool HasScriptCompilationError() const { return scriptCompilationError; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ScriptEngine scriptEngine;
|
ScriptEngine scriptEngine;
|
||||||
HotReload* hotReload;
|
HotReload* hotReload;
|
||||||
bool scriptCompilationError;
|
bool scriptCompilationError;
|
||||||
FILE* logFile;
|
FILE* logFile;
|
||||||
|
GuiManager guiManager;
|
||||||
|
bool enableEditor;
|
||||||
|
RenderTexture2D renderTexture; // renderTexture for Raylib rendering
|
||||||
|
|
||||||
static const int WINDOW_WIDTH = 800;
|
static const int WINDOW_WIDTH = 1280;
|
||||||
static const int WINDOW_HEIGHT = 600;
|
static const int WINDOW_HEIGHT = 720;
|
||||||
static const int TARGET_FPS = 60;
|
static const int TARGET_FPS = 60;
|
||||||
static const char* WINDOW_TITLE;
|
static const char* WINDOW_TITLE;
|
||||||
static const char* SCRIPT_FILE;
|
static const char* SCRIPT_FILE;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#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
|
||||||
|
};
|
||||||
+8
-2
@@ -5,14 +5,20 @@
|
|||||||
|
|
||||||
class HotReload {
|
class HotReload {
|
||||||
public:
|
public:
|
||||||
HotReload(const std::string& filename);
|
// Accept either a single script file or a directory to watch. If a directory is
|
||||||
|
// provided, any change to any file in the directory will trigger a reload.
|
||||||
|
HotReload(const std::string& pathToWatch);
|
||||||
|
|
||||||
bool CheckForChanges();
|
bool CheckForChanges();
|
||||||
void UpdateLastWriteTime();
|
void UpdateLastWriteTime();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string scriptFile;
|
std::string path;
|
||||||
std::time_t lastWriteTime;
|
std::time_t lastWriteTime;
|
||||||
|
|
||||||
std::time_t GetFileWriteTime(const std::string& filename);
|
std::time_t GetFileWriteTime(const std::string& filename);
|
||||||
|
// Return the most recent write time found in the watched path. If `path` is a
|
||||||
|
// directory, this scans all files (non-recursive) and returns the newest
|
||||||
|
// modification time. If it's a file, it returns that file's write time.
|
||||||
|
std::time_t GetLatestWriteTimeInPath();
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
#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;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "angelscript.h"
|
#include "angelscript.h"
|
||||||
#include "scriptbuilder.h"
|
#include "scriptbuilder.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class ScriptEngine {
|
class ScriptEngine {
|
||||||
public:
|
public:
|
||||||
@@ -14,6 +15,14 @@ public:
|
|||||||
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
|
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
|
||||||
void GarbageCollect();
|
void GarbageCollect();
|
||||||
|
|
||||||
|
// Add a path used to resolve #include directives in scripts
|
||||||
|
void AddIncludePath(const std::string& path);
|
||||||
|
|
||||||
|
// Try to preserve script state across hot-reloads by calling optional
|
||||||
|
// __SerializeState()/__DeserializeState() hooks in the script module.
|
||||||
|
std::string CaptureScriptState();
|
||||||
|
void RestoreScriptState(const std::string &state);
|
||||||
|
|
||||||
asIScriptEngine* GetEngine() const { return engine; }
|
asIScriptEngine* GetEngine() const { return engine; }
|
||||||
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
|
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
|
||||||
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
|
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
|
||||||
@@ -24,6 +33,7 @@ private:
|
|||||||
asIScriptFunction* drawFunc;
|
asIScriptFunction* drawFunc;
|
||||||
asIScriptModule* currentModule;
|
asIScriptModule* currentModule;
|
||||||
bool hasValidScript;
|
bool hasValidScript;
|
||||||
|
std::vector<std::string> includePaths;
|
||||||
|
|
||||||
static void MessageCallback(const asSMessageInfo* msg, void* param);
|
static void MessageCallback(const asSMessageInfo* msg, void* param);
|
||||||
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
|
static int IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
|||||||
|
#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
@@ -1,10 +1,10 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "src/log/log.h"
|
#include "src/log/log.h"
|
||||||
|
|
||||||
int main() {
|
int main(int argc, char* argv[]) {
|
||||||
Application app;
|
Application app;
|
||||||
|
|
||||||
if (!app.Initialize()) {
|
if (!app.Initialize(argc, argv)) {
|
||||||
log_error("Failed to initialize application");
|
log_error("Failed to initialize application");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
+2
-3
@@ -2,12 +2,11 @@ float x = 50;
|
|||||||
float y = 100;
|
float y = 100;
|
||||||
|
|
||||||
void Update(float dt) {
|
void Update(float dt) {
|
||||||
x += 50 * dt;
|
x += 64 * dt;
|
||||||
if (x > 800) {
|
if (x > 800) {
|
||||||
x = 0;
|
x = 0;
|
||||||
Print("X position reset!");
|
Print("X position reset!");
|
||||||
Log(LOG_INFO, "Log INFO: reset happened");
|
Log(LOG_INFO, "Log INFO: reset happened");
|
||||||
Log(LOG_WARNING, "Log WARNING: reset happened");
|
Toast::Info("X position has been reset.");
|
||||||
Log(LOG_ERROR, "Log ERROR: reset happened");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+140
-28
@@ -2,43 +2,101 @@
|
|||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
#include "log/log.h"
|
#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) {
|
#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() {
|
Application::~Application()
|
||||||
Shutdown();
|
{
|
||||||
|
// Reminder in case we change main.cpp, but we do not want to close the
|
||||||
|
// window more than once
|
||||||
|
// Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::Initialize() {
|
bool Application::Initialize(int argc, char *argv[])
|
||||||
logFile = fopen("log.txt", "w");
|
{
|
||||||
log_add_fp(logFile, LOG_TRACE);
|
if (fopen_s(&logFile, "log.txt", "w") != 0)
|
||||||
|
{
|
||||||
|
logFile = nullptr;
|
||||||
|
log_error("Failed to open log file");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
log_add_fp(logFile, LOG_TRACE);
|
||||||
|
}
|
||||||
|
|
||||||
SetTraceLogCallback(raylib_log);
|
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
|
// Initialize Raylib
|
||||||
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
|
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
|
||||||
SetTargetFPS(TARGET_FPS);
|
SetTargetFPS(TARGET_FPS);
|
||||||
|
|
||||||
// Initialize AngelScript
|
// Initialize AngelScript
|
||||||
if (!scriptEngine.Initialize()) {
|
if (!scriptEngine.Initialize())
|
||||||
|
{
|
||||||
log_error("Failed to initialize script engine");
|
log_error("Failed to initialize script engine");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize hot reload
|
// Initialize hot reload to watch the scripts directory so any script change
|
||||||
hotReload = new HotReload(SCRIPT_FILE);
|
// triggers a reload.
|
||||||
|
{
|
||||||
|
std::filesystem::path p(SCRIPT_FILE);
|
||||||
|
std::string watchPath = p.parent_path().string();
|
||||||
|
if (watchPath.empty())
|
||||||
|
watchPath = ".";
|
||||||
|
hotReload = new HotReload(watchPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Compile initial script
|
// Compile initial script
|
||||||
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
|
scriptCompilationError = !scriptEngine.CompileScript(SCRIPT_FILE);
|
||||||
|
|
||||||
|
if (enableEditor)
|
||||||
|
{
|
||||||
|
|
||||||
|
guiManager.Initialize(this);
|
||||||
|
|
||||||
|
renderTexture = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Run() {
|
void Application::Run()
|
||||||
while (!WindowShouldClose()) {
|
{
|
||||||
|
|
||||||
|
while (!WindowShouldClose())
|
||||||
|
{
|
||||||
float deltaTime = GetFrameTime();
|
float deltaTime = GetFrameTime();
|
||||||
|
|
||||||
Update(deltaTime);
|
Update(deltaTime);
|
||||||
@@ -49,13 +107,19 @@ void Application::Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Update(float deltaTime) {
|
void Application::Update(float deltaTime)
|
||||||
|
{
|
||||||
// Check for hot reload
|
// Check for hot reload
|
||||||
if (hotReload && hotReload->CheckForChanges()) {
|
if (hotReload && hotReload->CheckForChanges())
|
||||||
|
{
|
||||||
bool success = scriptEngine.CompileScript(SCRIPT_FILE);
|
bool success = scriptEngine.CompileScript(SCRIPT_FILE);
|
||||||
scriptCompilationError = !success;
|
scriptCompilationError = !success;
|
||||||
if (!success) {
|
if (!success)
|
||||||
|
{
|
||||||
log_warn("Script compilation failed - keeping previous version");
|
log_warn("Script compilation failed - keeping previous version");
|
||||||
|
Toast::Warning("Script compilation failed - check console for details.");
|
||||||
|
} else {
|
||||||
|
Toast::Success("Script reloaded successfully.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,27 +127,74 @@ void Application::Update(float deltaTime) {
|
|||||||
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
|
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Draw() {
|
void Application::Draw()
|
||||||
BeginDrawing();
|
{
|
||||||
ClearBackground(RAYWHITE);
|
if (enableEditor)
|
||||||
|
{
|
||||||
|
int screenWidth = GetScreenWidth();
|
||||||
|
int screenHeight = GetScreenHeight();
|
||||||
|
|
||||||
// Show script error status in top left if there's an error, otherwise show normal message
|
// Fallback to default values if screen dimensions are invalid
|
||||||
if (scriptCompilationError) {
|
if (screenWidth <= 0 || screenHeight <= 0)
|
||||||
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
|
{
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeginDrawing();
|
||||||
|
ClearBackground(RAYWHITE);
|
||||||
|
|
||||||
// Call script Draw function
|
// Show script error status in top left if there's an error, otherwise show normal message
|
||||||
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
|
if (scriptCompilationError)
|
||||||
|
{
|
||||||
|
DrawText("SCRIPT ERROR - Check console for details", 10, 10, 16, RED);
|
||||||
|
}
|
||||||
|
|
||||||
EndDrawing();
|
// Call script Draw function
|
||||||
|
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
|
||||||
|
|
||||||
|
EndDrawing();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Shutdown() {
|
void Application::Shutdown()
|
||||||
if (hotReload) {
|
{
|
||||||
|
if (hotReload)
|
||||||
|
{
|
||||||
delete hotReload;
|
delete hotReload;
|
||||||
hotReload = nullptr;
|
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();
|
scriptEngine.Shutdown();
|
||||||
|
|
||||||
// Clear the trace log callback before closing window to prevent logging after cleanup
|
// Clear the trace log callback before closing window to prevent logging after cleanup
|
||||||
@@ -91,7 +202,8 @@ void Application::Shutdown() {
|
|||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
|
||||||
// Close log file after everything else is cleaned up
|
// Close log file after everything else is cleaned up
|
||||||
if (logFile) {
|
if (logFile)
|
||||||
|
{
|
||||||
fclose(logFile);
|
fclose(logFile);
|
||||||
logFile = nullptr;
|
logFile = nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
+29
-5
@@ -3,21 +3,21 @@
|
|||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
HotReload::HotReload(const std::string& filename) : scriptFile(filename), lastWriteTime(0) {
|
HotReload::HotReload(const std::string& pathToWatch) : path(pathToWatch), lastWriteTime(0) {
|
||||||
UpdateLastWriteTime();
|
UpdateLastWriteTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HotReload::CheckForChanges() {
|
bool HotReload::CheckForChanges() {
|
||||||
std::time_t currentWriteTime = GetFileWriteTime(scriptFile);
|
std::time_t current = GetLatestWriteTimeInPath();
|
||||||
if (currentWriteTime != lastWriteTime) {
|
if (current != lastWriteTime) {
|
||||||
lastWriteTime = currentWriteTime;
|
lastWriteTime = current;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HotReload::UpdateLastWriteTime() {
|
void HotReload::UpdateLastWriteTime() {
|
||||||
lastWriteTime = GetFileWriteTime(scriptFile);
|
lastWriteTime = GetLatestWriteTimeInPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
||||||
@@ -30,3 +30,27 @@ std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::time_t HotReload::GetLatestWriteTimeInPath() {
|
||||||
|
try {
|
||||||
|
fs::path p(path);
|
||||||
|
if (fs::is_regular_file(p)) {
|
||||||
|
return GetFileWriteTime(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a directory, scan files (non-recursively) and return the
|
||||||
|
// newest write time found.
|
||||||
|
if (fs::is_directory(p)) {
|
||||||
|
std::time_t newest = 0;
|
||||||
|
for (auto &entry : fs::directory_iterator(p)) {
|
||||||
|
if (!fs::is_regular_file(entry.path())) continue;
|
||||||
|
std::time_t t = GetFileWriteTime(entry.path().string());
|
||||||
|
if (t > newest) newest = t;
|
||||||
|
}
|
||||||
|
return newest;
|
||||||
|
}
|
||||||
|
} catch (const std::exception&) {
|
||||||
|
// fall through
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#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});
|
||||||
|
}
|
||||||
+98
-37
@@ -3,6 +3,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
|
#include "gui/Toast.h"
|
||||||
|
|
||||||
// Expose log level constants to AngelScript
|
// Expose log level constants to AngelScript
|
||||||
static int AS_LOG_TRACE = 1;
|
static int AS_LOG_TRACE = 1;
|
||||||
@@ -12,76 +13,123 @@ static int AS_LOG_WARNING = 4;
|
|||||||
static int AS_LOG_ERROR = 5;
|
static int AS_LOG_ERROR = 5;
|
||||||
static int AS_LOG_FATAL = 6;
|
static int AS_LOG_FATAL = 6;
|
||||||
|
|
||||||
void ScriptBindings::RegisterAll(asIScriptEngine* engine) {
|
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)
|
||||||
|
{
|
||||||
// Register Print function with generic calling convention to access context
|
// Register Print function with generic calling convention to access context
|
||||||
int r = engine->RegisterGlobalFunction("void Print(const string &in)",
|
[[maybe_unused]] int r = engine->RegisterGlobalFunction("void Print(const string &in)",
|
||||||
asFUNCTION(Print), asCALL_GENERIC);
|
asFUNCTION(Print), asCALL_GENERIC);
|
||||||
assert(r >= 0);
|
assert(r >= 0);
|
||||||
|
|
||||||
// Register log level constants so scripts can refer to LOG_INFO, LOG_ERROR, etc.
|
// 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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
assert(r >= 0);
|
||||||
|
|
||||||
// Register Log(level, message) to allow scripts to log at different levels
|
// Register Log(level, message) to allow scripts to log at different levels
|
||||||
r = engine->RegisterGlobalFunction("void Log(int, const string &in)",
|
r = engine->RegisterGlobalFunction("void Log(int, const string &in)",
|
||||||
asFUNCTION(Log), asCALL_GENERIC);
|
asFUNCTION(Log), asCALL_GENERIC);
|
||||||
assert(r >= 0);
|
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
|
// Register DrawText function
|
||||||
r = engine->RegisterGlobalFunction("void DrawText(const string &in, int, int, int, uint)",
|
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);
|
assert(r >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptBindings::Print(asIScriptGeneric* gen) {
|
void ScriptBindings::Print(asIScriptGeneric *gen)
|
||||||
|
{
|
||||||
// Get the message parameter
|
// 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
|
// Get the active context to retrieve script file and line number
|
||||||
asIScriptContext* ctx = asGetActiveContext();
|
asIScriptContext *ctx = asGetActiveContext();
|
||||||
|
|
||||||
if (ctx) {
|
if (ctx)
|
||||||
const char* section = nullptr;
|
{
|
||||||
|
const char *section = nullptr;
|
||||||
int line = 0;
|
int line = 0;
|
||||||
|
|
||||||
// Get the current line number and script section
|
// Get the current line number and script section
|
||||||
line = ctx->GetLineNumber(0, nullptr, §ion);
|
line = ctx->GetLineNumber(0, nullptr, §ion);
|
||||||
|
|
||||||
if (section && line > 0) {
|
if (section && line > 0)
|
||||||
|
{
|
||||||
// Log with script file and line number
|
// Log with script file and line number
|
||||||
log_log(LOG_INFO, section, line, "%s", msg->c_str());
|
log_log(LOG_INFO, section, line, "%s", msg->c_str());
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// Fallback if we can't get the info
|
// Fallback if we can't get the info
|
||||||
log_info("%s", msg->c_str());
|
log_info("%s", msg->c_str());
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
// No context available, just log normally
|
// No context available, just log normally
|
||||||
log_info("%s", msg->c_str());
|
log_info("%s", msg->c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptBindings::Log(asIScriptGeneric* gen) {
|
void ScriptBindings::Log(asIScriptGeneric *gen)
|
||||||
|
{
|
||||||
// arg0: int level, arg1: string message
|
// arg0: int level, arg1: string message
|
||||||
int level = gen->GetArgDWord(0);
|
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
|
// Obtain script context to fetch file/line
|
||||||
asIScriptContext* ctx = asGetActiveContext();
|
asIScriptContext *ctx = asGetActiveContext();
|
||||||
if (ctx) {
|
if (ctx)
|
||||||
const char* section = nullptr;
|
{
|
||||||
|
const char *section = nullptr;
|
||||||
int line = 0;
|
int line = 0;
|
||||||
line = ctx->GetLineNumber(0, nullptr, §ion);
|
line = ctx->GetLineNumber(0, nullptr, §ion);
|
||||||
if (section && line > 0) {
|
if (section && line > 0)
|
||||||
|
{
|
||||||
// Use the library's log_log to preserve filename/line info
|
// Use the library's log_log to preserve filename/line info
|
||||||
log_log(level, section, line, "%s", msg ? msg->c_str() : "");
|
log_log(level, section, line, "%s", msg ? msg->c_str() : "");
|
||||||
return;
|
return;
|
||||||
@@ -89,20 +137,32 @@ void ScriptBindings::Log(asIScriptGeneric* gen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: log without script location
|
// Fallback: log without script location
|
||||||
switch (level) {
|
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_TRACE:
|
||||||
case LOG_WARNING: log_warn("%s", msg ? msg->c_str() : ""); break;
|
log_trace("%s", msg ? msg->c_str() : "");
|
||||||
case LOG_ERROR: log_error("%s", msg ? msg->c_str() : ""); break;
|
break;
|
||||||
case LOG_FATAL: log_fatal("%s", msg ? msg->c_str() : ""); break;
|
case LOG_DEBUG:
|
||||||
case LOG_INFO:
|
log_debug("%s", msg ? msg->c_str() : "");
|
||||||
default:
|
break;
|
||||||
log_info("%s", msg ? msg->c_str() : "");
|
case LOG_WARNING:
|
||||||
break;
|
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;
|
Color col;
|
||||||
col.r = (c >> 24) & 0xFF;
|
col.r = (c >> 24) & 0xFF;
|
||||||
col.g = (c >> 16) & 0xFF;
|
col.g = (c >> 16) & 0xFF;
|
||||||
@@ -111,6 +171,7 @@ Color ColorFromUInt(unsigned int c) {
|
|||||||
return col;
|
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));
|
DrawText(text.c_str(), x, y, fontSize, ColorFromUInt(color));
|
||||||
}
|
}
|
||||||
+71
-19
@@ -8,6 +8,9 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "log/log.h"
|
#include "log/log.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
|
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), currentModule(nullptr), hasValidScript(false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +41,55 @@ bool ScriptEngine::Initialize() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::AddIncludePath(const std::string& path) {
|
||||||
|
// thread-safe append
|
||||||
|
static std::mutex m;
|
||||||
|
std::lock_guard<std::mutex> lock(m);
|
||||||
|
includePaths.push_back(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ScriptEngine::CaptureScriptState() {
|
||||||
|
if (!currentModule || !engine) return std::string();
|
||||||
|
asIScriptFunction* ser = currentModule->GetFunctionByDecl("string __SerializeState()");
|
||||||
|
if (!ser) return std::string();
|
||||||
|
|
||||||
|
asIScriptContext* ctx = engine->CreateContext();
|
||||||
|
if (ctx->Prepare(ser) < 0) { ctx->Release(); return std::string(); }
|
||||||
|
int r = ctx->Execute();
|
||||||
|
std::string ret;
|
||||||
|
if (r == asEXECUTION_FINISHED) {
|
||||||
|
// Get return value (script std::string registered as value type)
|
||||||
|
void* obj = ctx->GetReturnObject();
|
||||||
|
if (obj) {
|
||||||
|
try {
|
||||||
|
std::string* s = reinterpret_cast<std::string*>(obj);
|
||||||
|
ret = *s;
|
||||||
|
} catch (...) {
|
||||||
|
// fallback: leave ret empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->Release();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptEngine::RestoreScriptState(const std::string &state) {
|
||||||
|
if (!currentModule || !engine || state.empty()) return;
|
||||||
|
asIScriptFunction* des = currentModule->GetFunctionByDecl("void __DeserializeState(const string &in)");
|
||||||
|
if (!des) return;
|
||||||
|
|
||||||
|
asIScriptContext* ctx = engine->CreateContext();
|
||||||
|
if (ctx->Prepare(des) < 0) { ctx->Release(); return; }
|
||||||
|
// SetArgObject expects pointer to the script string object; AngelScript's
|
||||||
|
// std::string binding uses C++ std::string, so we pass the pointer.
|
||||||
|
ctx->SetArgObject(0, (void*)&state);
|
||||||
|
int r = ctx->Execute();
|
||||||
|
if (r != asEXECUTION_FINISHED) {
|
||||||
|
log_error("Failed to restore script state");
|
||||||
|
}
|
||||||
|
ctx->Release();
|
||||||
|
}
|
||||||
|
|
||||||
void ScriptEngine::Shutdown() {
|
void ScriptEngine::Shutdown() {
|
||||||
ClearCachedFunctions();
|
ClearCachedFunctions();
|
||||||
if (engine) {
|
if (engine) {
|
||||||
@@ -163,7 +215,7 @@ void ScriptEngine::MessageCallback(const asSMessageInfo* msg, void*) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int ScriptEngine::IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, void* userParam) {
|
int ScriptEngine::IncludeCallback(const char* include, const char* from, CScriptBuilder* builder, [[maybe_unused]] void* userParam) {
|
||||||
// Extract just the filename from the from path for cleaner logging
|
// Extract just the filename from the from path for cleaner logging
|
||||||
const char* fromFile = from;
|
const char* fromFile = from;
|
||||||
if (from) {
|
if (from) {
|
||||||
@@ -178,36 +230,36 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
|
|||||||
// Log the include request with clean filenames
|
// Log the include request with clean filenames
|
||||||
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
|
log_info("Including: %s (from: %s)", include, fromFile ? fromFile : "main");
|
||||||
|
|
||||||
// Build the full path relative to the including file's directory
|
// Build the full path candidates: relative to 'from' and any include paths
|
||||||
std::string fullPath;
|
std::vector<std::string> candidates;
|
||||||
|
|
||||||
if (from && strlen(from) > 0) {
|
if (from && strlen(from) > 0) {
|
||||||
// Extract directory from the 'from' path
|
|
||||||
std::string fromPath(from);
|
std::string fromPath(from);
|
||||||
size_t lastSlash = fromPath.find_last_of("/\\");
|
size_t lastSlash = fromPath.find_last_of("/\\");
|
||||||
|
|
||||||
if (lastSlash != std::string::npos) {
|
if (lastSlash != std::string::npos) {
|
||||||
// Get the directory part
|
|
||||||
std::string directory = fromPath.substr(0, lastSlash + 1);
|
std::string directory = fromPath.substr(0, lastSlash + 1);
|
||||||
fullPath = directory + include;
|
candidates.push_back(directory + include);
|
||||||
} else {
|
|
||||||
// No directory in from path, use include as-is
|
|
||||||
fullPath = include;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fullPath = include;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to add the included file
|
// Add user-specified include search paths
|
||||||
int r = builder->AddSectionFromFile(fullPath.c_str());
|
// Note: includePaths is a member of the ScriptEngine instance, but this
|
||||||
|
// callback is static. For now we look for an environment variable or the
|
||||||
|
// include as-is. If you want dynamic include paths, expose a global pointer
|
||||||
|
// or userParam to pass the instance to the callback.
|
||||||
|
candidates.push_back(include);
|
||||||
|
|
||||||
if (r < 0) {
|
int result = -1;
|
||||||
log_error("Failed to include file: %s (tried path: %s)", include, fullPath.c_str());
|
for (auto &cand : candidates) {
|
||||||
} else {
|
result = builder->AddSectionFromFile(cand.c_str());
|
||||||
log_info("Successfully included: %s", include);
|
if (result >= 0) {
|
||||||
|
log_info("Successfully included: %s", cand.c_str());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r;
|
log_error("Failed to include file: %s (tried %zu paths)", include, candidates.size());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
||||||
|
|||||||
+27
-6
@@ -21,6 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include <string.h> // for memcpy
|
||||||
|
|
||||||
#define MAX_CALLBACKS 32
|
#define MAX_CALLBACKS 32
|
||||||
|
|
||||||
@@ -181,12 +182,28 @@ static void init_event(log_Event *ev, void *udata)
|
|||||||
if (!ev->time)
|
if (!ev->time)
|
||||||
{
|
{
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
ev->time = localtime(&t);
|
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->udata = udata;
|
ev->udata = udata;
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_log(int level, const char *file, int line, const char *fmt, ...)
|
void log_log(int level, const char *file, int line, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
va_start(ap, fmt);
|
||||||
|
log_vlog(level, file, line, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap)
|
||||||
{
|
{
|
||||||
log_Event ev = {
|
log_Event ev = {
|
||||||
.fmt = fmt,
|
.fmt = fmt,
|
||||||
@@ -200,9 +217,11 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
|
|||||||
if (!L.quiet && level >= L.level)
|
if (!L.quiet && level >= L.level)
|
||||||
{
|
{
|
||||||
init_event(&ev, stderr);
|
init_event(&ev, stderr);
|
||||||
va_start(ev.ap, fmt);
|
va_list ap_copy;
|
||||||
|
va_copy(ap_copy, ap);
|
||||||
|
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
|
||||||
stdout_callback(&ev);
|
stdout_callback(&ev);
|
||||||
va_end(ev.ap);
|
va_end(ap_copy);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
|
for (int i = 0; i < MAX_CALLBACKS && L.callbacks[i].fn; i++)
|
||||||
@@ -211,9 +230,11 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
|
|||||||
if (level >= cb->level)
|
if (level >= cb->level)
|
||||||
{
|
{
|
||||||
init_event(&ev, cb->udata);
|
init_event(&ev, cb->udata);
|
||||||
va_start(ev.ap, fmt);
|
va_list ap_copy;
|
||||||
|
va_copy(ap_copy, ap);
|
||||||
|
memcpy(&ev.ap, &ap_copy, sizeof(va_list));
|
||||||
cb->fn(&ev);
|
cb->fn(&ev);
|
||||||
va_end(ev.ap);
|
va_end(ap_copy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,5 +243,5 @@ void log_log(int level, const char *file, int line, const char *fmt, ...)
|
|||||||
|
|
||||||
void raylib_log(int msgType, const char *text, va_list args)
|
void raylib_log(int msgType, const char *text, va_list args)
|
||||||
{
|
{
|
||||||
log_log(msgType, NULL, 0, text, args);
|
log_vlog(msgType, NULL, 0, text, args);
|
||||||
}
|
}
|
||||||
@@ -48,6 +48,8 @@ int log_add_callback(log_LogFn fn, void *udata, int level);
|
|||||||
int log_add_fp(FILE *fp, int level);
|
int log_add_fp(FILE *fp, int level);
|
||||||
|
|
||||||
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
void log_log(int level, const char *file, int line, const char *fmt, ...);
|
||||||
|
// Variant that accepts a va_list for backends that already receive va_list
|
||||||
|
void log_vlog(int level, const char *file, int line, const char *fmt, va_list ap);
|
||||||
|
|
||||||
|
|
||||||
void raylib_log(int msgType, const char *text, va_list args);
|
void raylib_log(int msgType, const char *text, va_list args);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
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)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#include "../external/acutest/include/acutest.h"
|
||||||
|
|
||||||
|
// This translation unit provides the single main() for Acutest.
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
#include "../external/acutest/include/acutest.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include "../include/HotReload.h"
|
||||||
|
#include "../include/ScriptEngine.h"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
/* simple sanity test */
|
||||||
|
void test_one(void) {
|
||||||
|
TEST_CHECK(1 + 1 == 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_str(void) {
|
||||||
|
TEST_CHECK(strcmp("foo","foo") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_hotreload_directory_watch(void) {
|
||||||
|
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_watch";
|
||||||
|
std::filesystem::create_directories(tmpDir);
|
||||||
|
|
||||||
|
std::filesystem::path f = tmpDir / "a.as";
|
||||||
|
std::ofstream ofs(f);
|
||||||
|
ofs << "// test" << std::endl;
|
||||||
|
ofs.close();
|
||||||
|
|
||||||
|
HotReload hr(tmpDir.string());
|
||||||
|
TEST_CHECK(hr.CheckForChanges() == false);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(1s);
|
||||||
|
std::ofstream ofs2(f, std::ios::app);
|
||||||
|
ofs2 << "// changed" << std::endl;
|
||||||
|
ofs2.close();
|
||||||
|
|
||||||
|
TEST_CHECK(hr.CheckForChanges() == true);
|
||||||
|
|
||||||
|
std::filesystem::remove(f);
|
||||||
|
std::filesystem::remove(tmpDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_scriptengine_compile_and_call(void) {
|
||||||
|
// Create a simple script file that defines Update(float) and a global
|
||||||
|
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_script";
|
||||||
|
std::filesystem::create_directories(tmpDir);
|
||||||
|
std::filesystem::path script = tmpDir / "simple.as";
|
||||||
|
std::ofstream ofs(script);
|
||||||
|
ofs << "float g_x = 0;\n";
|
||||||
|
ofs << "void Update(float dt) { g_x += dt * 10.0f; }\n";
|
||||||
|
ofs.close();
|
||||||
|
|
||||||
|
ScriptEngine se;
|
||||||
|
TEST_CHECK(se.Initialize() == true);
|
||||||
|
bool ok = se.CompileScript(script.string());
|
||||||
|
TEST_CHECK(ok == true);
|
||||||
|
asIScriptFunction* upd = se.GetUpdateFunction();
|
||||||
|
TEST_CHECK(upd != nullptr);
|
||||||
|
|
||||||
|
// Call Update with dt=0.1
|
||||||
|
se.CallScriptFunction(upd, 0.1f);
|
||||||
|
|
||||||
|
se.Shutdown();
|
||||||
|
|
||||||
|
std::filesystem::remove(script);
|
||||||
|
std::filesystem::remove(tmpDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_scriptengine_include(void) {
|
||||||
|
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_include";
|
||||||
|
std::filesystem::create_directories(tmpDir);
|
||||||
|
std::filesystem::path inc = tmpDir / "inc.as";
|
||||||
|
std::ofstream ofsinc(inc);
|
||||||
|
ofsinc << "void Included() { Print(\"Included called\"); }\n";
|
||||||
|
ofsinc.close();
|
||||||
|
|
||||||
|
std::filesystem::path main = tmpDir / "main.as";
|
||||||
|
std::ofstream ofsmain(main);
|
||||||
|
ofsmain << "#include \"inc.as\"\n";
|
||||||
|
ofsmain << "void Update(float dt) { Included(); }\n";
|
||||||
|
ofsmain.close();
|
||||||
|
|
||||||
|
ScriptEngine se;
|
||||||
|
TEST_CHECK(se.Initialize() == true);
|
||||||
|
// Compile should succeed and include callback should resolve inc.as
|
||||||
|
bool ok = se.CompileScript(main.string());
|
||||||
|
TEST_CHECK(ok == true);
|
||||||
|
se.Shutdown();
|
||||||
|
|
||||||
|
std::filesystem::remove(inc);
|
||||||
|
std::filesystem::remove(main);
|
||||||
|
std::filesystem::remove(tmpDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_LIST = {
|
||||||
|
{ "one", test_one },
|
||||||
|
{ "str", test_str },
|
||||||
|
{ "hotreload_dir", test_hotreload_directory_watch },
|
||||||
|
{ "script_compile", test_scriptengine_compile_and_call },
|
||||||
|
{ "script_include", test_scriptengine_include },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user