17 Commits

Author SHA1 Message Date
nick 50ec04aa12 chore: minor change to commit docs
CI / build-and-test (push) Successful in 2m2s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 14s
2026-03-08 15:03:16 +13:00
nick 95deb65106 chore: trying CI workflow
CI / build-and-test (push) Successful in 2m8s
2026-03-08 15:00:19 +13:00
nick 9f8686345c fix: move CI piplein to correct folder
CI / build-and-test (push) Successful in 1m58s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 5s
2026-03-08 14:50:43 +13:00
nick 33226dfae1 feat: Wiki pages and sync
CI / build-and-test (push) Has been cancelled
2026-03-08 14:48:55 +13:00
nick d0c28dc663 feat: input manager, refactor compliation for efficiency
CI / build-and-test (push) Successful in 2m2s
2026-03-08 12:14:50 +13:00
nick a37910de25 chore: remove g_ prfix noise
CI / build-and-test (push) Successful in 2m0s
2026-03-08 11:09:09 +13:00
nick 4116678556 fix: shaders work again now
CI / build-and-test (push) Has been cancelled
2026-03-08 11:08:12 +13:00
nick 0cce80e956 chore: code code tidy and style tweaks
CI / build-and-test (push) Successful in 2m7s
2026-03-08 10:37:38 +13:00
nick 05b0773189 fix: Windows unsigned int type compatibility
CI / build-and-test (push) Successful in 2m32s
2026-03-06 18:03:48 +13:00
nick 283d057507 chore(wip): attempting to get shaders working again
CI / build-and-test (push) Successful in 2m5s
2026-03-06 14:51:54 +13:00
nick 82886349ae feat: refactor to move more elements to the scripting layer
CI / build-and-test (push) Successful in 2m13s
2026-03-06 14:32:25 +13:00
nick 670f65b37b fix: rendering on linux and toon shader 2026-03-06 11:24:28 +13:00
nick 766f7ecd7a fix: include all tests
CI / build-and-test (push) Successful in 2m24s
2026-03-06 09:45:29 +13:00
nick aa4c7cf58a feat: entt scripting support
CI / build-and-test (push) Failing after 11m9s
2026-03-05 23:22:09 +13:00
nick 8f7bafb43f chore: include license for entt
CI / build-and-test (push) Successful in 2m33s
2026-03-05 22:39:57 +13:00
nick fc4489d211 fix: code re-org
CI / build-and-test (push) Has been cancelled
2026-03-05 22:38:41 +13:00
nick 10f6df42c6 Remove entt submodule reference, progress on shader testing
CI / build-and-test (push) Waiting to run
2026-03-05 22:25:29 +13:00
40 changed files with 98531 additions and 128 deletions
+36
View File
@@ -0,0 +1,36 @@
name: Sync Docs to Gitea Wiki
on:
push:
paths:
- 'docs/**'
jobs:
sync:
name: Sync docs to Gitea wiki
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Start SSH agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.WIKI_SSH_KEY }}
- name: Sync docs to wiki
env:
WIKI_REPO: git@gitea.appstack.me:nick/simian.wiki.git
run: |
git config --global user.name "Simian CI"
git config --global user.email "ci@simian.local"
git clone "$WIKI_REPO" wiki
rsync -av --delete docs/ wiki/
cd wiki
git add -A
if git diff --staged --quiet; then
echo "No wiki changes"
else
git commit -m "Update wiki from docs (CI)"
git push origin HEAD
fi
+45 -20
View File
@@ -42,11 +42,6 @@ else()
set(HAVE_RLIMGUI FALSE)
endif()
# -------------------------
# Tests
# -------------------------
enable_testing()
add_subdirectory(tests)
# -------------------------
# 3️⃣ scriptstdstring add-on
# -------------------------
add_library(scriptstdstring STATIC
@@ -69,26 +64,42 @@ target_include_directories(scriptbuilder
)
# -------------------------
# 4️⃣ Main executable
# 3️⃣.2️⃣ scriptarray add-on
# -------------------------
add_executable(simian
main.cpp
add_library(scriptarray STATIC
external/angelscript/sdk/add_on/scriptarray/scriptarray.cpp
)
target_include_directories(scriptarray
PUBLIC external/angelscript/sdk/angelscript/include
)
# -------------------------
# 4️⃣ Simian Core Library (SHARED CODE)
# -------------------------
add_library(simian_core STATIC
src/Application.cpp
src/scripting/ScriptEngine.cpp
src/scripting/ScriptBindings.cpp
src/scripting/ToastBindings.cpp
src/scripting/LogBindings.cpp
src/scripting/DrawBindings.cpp
src/scripting/ImageBindings.cpp
src/scripting/TextureBindings.cpp
src/scripting/MathBindings.cpp
src/scripting/ECSBindings.cpp
src/scripting/ResourceBindings.cpp
src/scripting/InputBindings.cpp
src/HotReload.cpp
src/gui/GuiManager.cpp
src/gui/LogWindow.cpp
src/log/log.c
src/ShaderManager.cpp
src/ModelManager.cpp
src/MaterialManager.cpp
src/InputManager.cpp
)
target_include_directories(simian PUBLIC
target_include_directories(simian_core PUBLIC
include
include/scripting
include/gui
@@ -97,29 +108,38 @@ target_include_directories(simian PUBLIC
external/angelscript/sdk/angelscript/include
external/angelscript/sdk/add_on/scriptstdstring
external/angelscript/sdk/add_on/scriptbuilder
external/angelscript/sdk/add_on/scriptarray
external/imgui
)
target_link_libraries(simian PRIVATE
target_link_libraries(simian_core PUBLIC
raylib
angelscript
scriptstdstring
scriptbuilder
scriptarray
imgui
)
if(HAVE_RLIMGUI)
target_include_directories(simian PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian PRIVATE rlImGui)
target_include_directories(simian_core PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian_core PUBLIC 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")
target_compile_definitions(simian_core PUBLIC HAVE_RLIMGUI=1)
message(STATUS "Linking rlImGui into simian_core")
else()
message(STATUS "rlImGui not found - building without rlImGui integration")
endif()
# -------------------------
# 5️⃣ Platform-specific linking
# 5️⃣ Main executable (just main.cpp)
# -------------------------
add_executable(simian main.cpp)
target_link_libraries(simian PRIVATE simian_core)
# -------------------------
# 6️⃣ Platform-specific linking
# -------------------------
if(WIN32)
target_link_libraries(simian PRIVATE winmm gdi32)
@@ -128,11 +148,16 @@ elseif(UNIX)
endif()
# -------------------------
# 6️⃣ Optional: Compile flags
# 7️⃣ Optional: Compile flags
# -------------------------
if(MSVC)
target_compile_options(simian PRIVATE /W4 /EHsc)
target_compile_options(simian_core PRIVATE /W4 /EHsc)
else()
target_compile_options(simian PRIVATE -Wall -Wextra -pedantic)
target_compile_options(simian_core PRIVATE -Wall -Wextra -pedantic)
endif()
# -------------------------
# -------------------------
# 8️⃣ Tests
# -------------------------
enable_testing()
add_subdirectory(tests)
+1
View File
@@ -128,3 +128,4 @@ cd ../.. && git add external/raylib external/angelscript && git commit -m "Updat
* 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)
* Entt: MIT License (see https://github.com/skypjack/entt/blob/main/LICENSE)
+12
View File
@@ -0,0 +1,12 @@
# Simian Documentation
This folder contains project documentation that is synced to the project's Gitea wiki.
Edit markdown files here and push to the repository; CI will mirror them to the wiki.
## Pages
- [Scripting](Scripting.md) — scripting API and examples
- [Usage](Usage.md) — getting started and build instructions
## Notes
The CI workflow (.github/workflows/sync-docs-to-wiki.yml) clones the wiki repository and copies these files into it.
Make sure `WIKI_SSH_KEY` is configured in repository secrets (SSH private key with push access to the wiki).
+13
View File
@@ -0,0 +1,13 @@
# Scripting
This page is a starting point for scripting documentation.
## Overview
- Scripts live in `scripts/` and are executed by AngelScript.
- Predefined API and engine bindings are declared in `scripts/as.predefined`.
## Examples
- `scripts/game.as` — demo showing ECS usage, rendering and input actions.
## How to document APIs
Add Markdown pages in the `docs/` folder. Use relative links and include code fences for examples.
+95
View File
@@ -0,0 +1,95 @@
# Usage
This page documents how to build, run and maintain the Simian project and how to publish documentation to the Gitea wiki.
## Prerequisites
- Git
- CMake (>= 3.16; 3.25 recommended)
- A C++ toolchain (Visual Studio on Windows, GCC/Clang on Linux/macOS)
- Optional: SSH access to your Gitea instance (for wiki sync)
If you just cloned the repository, initialise submodules:
```bash
git submodule update --init --recursive
```
## Build (Windows / MSVC)
```powershell
mkdir build
cd build
cmake -S .. -B . -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release
# Run
.\Release\simian.exe
```
## Build (Linux / macOS)
```bash
mkdir build
cd build
cmake ..
cmake --build . --config Release
./simian
```
## Run tests
From the project root (after configuring the build):
```bash
cmake --build build --config Release --target RUN_TESTS
# or use CTest
ctest --test-dir build --output-on-failure
```
## Scripting / Hot-reload
- Scripts live in the `scripts/` directory (e.g. `scripts/game.as`).
- The engine watches the scripts directory and will hot-reload scripts when changed.
- Predefined API is declared in `scripts/as.predefined`.
## Documentation & Wiki
Docs source lives in the `docs/` folder. The repository contains a CI workflow and a local script to mirror `docs/` to the Gitea wiki.
Manual sync (local):
```bash
bash scripts/sync_wiki.sh
```
CI sync (automated):
1. The workflow `.github/workflows/sync-docs-to-wiki.yml` runs on pushes that affect `docs/**`.
2. The workflow requires an SSH private key in repository secrets named `GITEA_SSH_KEY`.
To create the deploy key and CI secret:
```bash
# Generate a keypair (do not commit the private key)
ssh-keygen -t ed25519 -C "simian-wiki-ci" -f simian_wiki_ci -N ""
# Add simian_wiki_ci.pub as a deploy key in your Gitea repo: Settings → Deploy Keys
# Enable write/push access for that key.
# Copy the private key (simian_wiki_ci) into your CI provider as secret WIKI_SSH_KEY.
```
Notes:
- The workflow clones the wiki remote `git@gitea.appstack.me:nick/simian.wiki.git` and pushes updates.
- If your Gitea host/paths differ, update `.github/workflows/sync-docs-to-wiki.yml` or the `scripts/sync_wiki.sh` script accordingly.
## Adding / Updating Documentation
1. Add or edit Markdown files under `docs/`.
2. Put images in `docs/images/` and reference them with relative paths, e.g. `![Alt](images/diagram.png)`.
3. Commit and push to the repo; CI will sync changes automatically (once secrets/deploy-key are configured).
## Troubleshooting
- If the CI job fails with authentication errors, confirm the deploy key was added to the Gitea repo and the private key is stored in the CI secrets as `GITEA_SSH_KEY`.
- To debug locally, run the `scripts/sync_wiki.sh` script and watch for `git push` errors.
+46 -14
View File
@@ -10,24 +10,24 @@ Collapsed=0
[Window][Game Window]
Pos=8,28
Size=1264,684
Size=1264,580
Collapsed=0
DockId=0x9076BACA,0
DockId=0x00000001,0
[Window][Log Viewer]
Pos=8,28
Size=1264,684
Pos=8,610
Size=1264,102
Collapsed=0
DockId=0x9076BACA,1
DockId=0x00000002,0
[Window][##TOAST0]
Pos=1007,637
Size=253,63
Pos=1060,637
Size=200,63
Collapsed=0
[Window][##TOAST1]
Pos=1064,564
Size=196,63
Pos=1060,564
Size=200,63
Collapsed=0
[Window][##TOAST2]
@@ -36,15 +36,47 @@ Size=196,63
Collapsed=0
[Window][##TOAST3]
Pos=1048,442
Size=212,57
Pos=1064,418
Size=196,63
Collapsed=0
[Window][##TOAST4]
Pos=1048,375
Size=212,57
Pos=1064,345
Size=196,63
Collapsed=0
[Window][Scene (0 entities)]
Pos=60,60
Size=32,36
Collapsed=1
[Window][Comic Engine]
Pos=60,60
Size=387,337
Collapsed=0
[Window][Scene (1 entities)]
Pos=60,60
Size=71,56
Collapsed=0
[Window][Scene (2 entities)]
Pos=60,60
Size=71,82
Collapsed=0
[Window][Scene (3 entities)]
Pos=60,60
Size=71,108
Collapsed=0
[Window][##TOAST5]
Pos=1064,272
Size=196,63
Collapsed=0
[Docking][Data]
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 CentralNode=1 Selected=0x27A02DAA
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=Y Selected=0x27A02DAA
DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,580 CentralNode=1 Selected=0x27A02DAA
DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,102 Selected=0xBEDDA0C1
+16
View File
@@ -3,6 +3,11 @@
#include "HotReload.h"
#include "gui/GuiManager.h"
#include "raylib.h"
#include "ShaderManager.h"
#include "ModelManager.h"
#include "MaterialManager.h"
#include "InputManager.h"
#include <entt.hpp>
class Application {
public:
@@ -27,6 +32,15 @@ private:
bool queueShutdown = false;
RenderTexture2D renderTexture; // renderTexture for Raylib rendering
// ECS
entt::registry registry;
// Resource managers
ShaderManager shaderManager;
ModelManager modelManager;
MaterialManager materialManager;
InputManager inputManager;
static const int WINDOW_WIDTH = 1280;
static const int WINDOW_HEIGHT = 720;
static const int TARGET_FPS = 60;
@@ -34,5 +48,7 @@ private:
static const char* SCRIPT_FILE;
void Update(float deltaTime);
void UpdateSystems(float deltaTime);
void RenderScene();
void Draw();
};
+106
View File
@@ -0,0 +1,106 @@
#pragma once
#include "raylib.h"
#include <string>
// ECSTransform component - position, rotation, and scale
struct ECSTransform {
Vector3 position;
Vector3 scale;
float rotation; // Rotation around Y axis in radians
ECSTransform()
: position{0.0f, 0.0f, 0.0f}
, scale{1.0f, 1.0f, 1.0f}
, rotation(0.0f) {}
ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rot = 0.0f)
: position(pos), scale(scl), rotation(rot) {}
};
// Velocity component - for movement
struct Velocity {
Vector3 linear; // Linear velocity (units per second)
float angular; // Angular velocity (radians per second)
Velocity()
: linear{0.0f, 0.0f, 0.0f}
, angular(0.0f) {}
Velocity(Vector3 lin, float ang = 0.0f)
: linear(lin), angular(ang) {}
};
// Sprite component - visual representation
struct Sprite {
size_t modelId; // Index into models array
Color color; // Tint color
float outlineSize; // Outline thickness
unsigned int shaderId; // Shader to use (0 = no shader)
Sprite()
: modelId(0)
, color(WHITE)
, outlineSize(0.0f)
, shaderId(0) {}
Sprite(int model, Color col = WHITE, float outline = 0.0f, unsigned int shader = 0)
: modelId(model), color(col), outlineSize(outline), shaderId(shader) {}
};
// Tag component - simple string identifier
struct Tag {
std::string name;
Tag() : name("") {}
Tag(const std::string& n) : name(n) {}
};
// Camera3D component - camera settings
struct Camera3DComponent {
Vector3 position;
Vector3 target;
Vector3 up;
float fovy;
int projection; // 0 = CAMERA_PERSPECTIVE, 1 = CAMERA_ORTHOGRAPHIC
Camera3DComponent()
: position{0.0f, 10.0f, 10.0f}
, target{0.0f, 0.0f, 0.0f}
, up{0.0f, 1.0f, 0.0f}
, fovy(45.0f)
, projection(0) {}
};
// Light component - directional light
struct LightComponent {
Vector3 direction;
Color color;
float intensity;
LightComponent()
: direction{0.5f, 0.7f, 0.3f}
, color(WHITE)
, intensity(1.0f) {}
};
// Material component - references shader and material managers
struct MaterialComponent {
unsigned int materialId; // ID in MaterialManager
MaterialComponent() : materialId(0) {}
MaterialComponent(unsigned int id) : materialId(id) {}
};
// RenderPass component - controls rendering order and properties
struct RenderPassComponent {
int order; // Render order (lower = earlier)
bool depthTest; // Enable depth testing
bool depthWrite; // Enable depth writing
unsigned int shaderId; // Override shader for this pass (0 = use material shader)
RenderPassComponent()
: order(0)
, depthTest(true)
, depthWrite(true)
, shaderId(0) {}
};
+81
View File
@@ -0,0 +1,81 @@
#pragma once
#include "raylib.h"
#include <string>
#include <vector>
#include <unordered_map>
// Input types supported by the action system
enum class InputType {
Keyboard,
MouseButton,
GamepadButton,
GamepadAxis
};
// Represents a single input binding (e.g., KEY_SPACE, MOUSE_BUTTON_LEFT)
struct InputBinding {
InputType type;
int code; // Key code, mouse button, or gamepad button
int gamepadId; // For gamepad inputs (0-3)
float axisThreshold; // For axis inputs (e.g., trigger > 0.5)
bool negative; // For axis inputs (e.g., left stick left = negative)
InputBinding()
: type(InputType::Keyboard)
, code(0)
, gamepadId(0)
, axisThreshold(0.5f)
, negative(false) {}
InputBinding(InputType t, int c, int gamepad = 0)
: type(t)
, code(c)
, gamepadId(gamepad)
, axisThreshold(0.5f)
, negative(false) {}
};
// Manages input actions with remappable bindings
class InputManager {
public:
InputManager();
~InputManager();
// Action management
void RegisterAction(const std::string& actionName);
void UnregisterAction(const std::string& actionName);
bool HasAction(const std::string& actionName) const;
// Binding management
void BindKeyboard(const std::string& actionName, int keyCode);
void BindMouseButton(const std::string& actionName, int mouseButton);
void BindGamepadButton(const std::string& actionName, int gamepadId, int button);
void BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative = false);
void ClearBindings(const std::string& actionName);
// Input queries (called per frame)
bool IsActionPressed(const std::string& actionName) const;
bool IsActionDown(const std::string& actionName) const;
bool IsActionReleased(const std::string& actionName) const;
float GetActionStrength(const std::string& actionName) const; // For analog inputs
// Configuration
void LoadFromFile(const std::string& filepath);
void SaveToFile(const std::string& filepath) const;
void SetupDefaultBindings(); // Define default controls
// Get list of all actions
std::vector<std::string> GetAllActions() const;
// Get bindings for an action
const std::vector<InputBinding>& GetBindings(const std::string& actionName) const;
private:
std::unordered_map<std::string, std::vector<InputBinding>> actions;
// Helper to check if a single binding is active
bool IsBindingPressed(const InputBinding& binding) const;
bool IsBindingDown(const InputBinding& binding) const;
bool IsBindingReleased(const InputBinding& binding) const;
float GetBindingStrength(const InputBinding& binding) const;
};
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "raylib.h"
#include <unordered_map>
#include <string>
struct MaterialData
{
unsigned int shaderId;
Color albedo;
MaterialData() : shaderId(0), albedo(WHITE) {}
};
class MaterialManager
{
public:
MaterialManager();
~MaterialManager();
// Create a material with a shader ID
// Returns a unique ID for the material
unsigned int Create(unsigned int shaderId, Color albedo = WHITE);
// Remove a material by ID
void Remove(unsigned int id);
// Get material data by ID (returns nullptr if not found)
MaterialData* Get(unsigned int id);
// Update material shader
void SetShader(unsigned int id, unsigned int shaderId);
// Update material albedo
void SetAlbedo(unsigned int id, Color albedo);
// Clear all materials
void Clear();
private:
std::unordered_map<unsigned int, MaterialData> materials;
unsigned int nextId;
bool cleared;
};
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "raylib.h"
#include <unordered_map>
#include <string>
class ModelManager
{
public:
ModelManager();
~ModelManager();
// Load a model from a file
// Returns a unique ID for the model, or 0 on failure
unsigned int Load(const std::string& path);
// Load a model from a mesh
// Returns a unique ID for the model, or 0 on failure
unsigned int LoadFromMesh(Mesh mesh);
// Unload a model by ID
void Unload(unsigned int id);
// Get a model by ID (returns nullptr if not found)
Model* Get(unsigned int id);
// Unload all models
void UnloadAll();
private:
std::unordered_map<unsigned int, Model> models;
unsigned int nextId;
bool unloaded;
};
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include "raylib.h"
#include <unordered_map>
#include <string>
class ShaderManager
{
public:
ShaderManager();
~ShaderManager();
// Load a shader from vertex and fragment shader files
// Returns a unique ID for the shader, or 0 on failure
unsigned int Load(const std::string& vsPath, const std::string& fsPath);
// Unload a shader by ID
void Unload(unsigned int id);
// Get a shader by ID (returns nullptr if not found)
Shader* Get(unsigned int id);
// Unload all shaders
void UnloadAll();
private:
std::unordered_map<unsigned int, Shader> shaders;
unsigned int nextId;
bool unloaded;
};
+95903
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <angelscript.h>
#include <entt.hpp>
// Set the global registry that scripts will interact with
void SetGlobalRegistry(entt::registry* reg);
void RegisterECSBindings(asIScriptEngine *engine);
+7
View File
@@ -0,0 +1,7 @@
#pragma once
class asIScriptEngine;
class InputManager;
void SetGlobalInputManager(InputManager* inputManager);
void RegisterInputBindings(asIScriptEngine* engine);
+9
View File
@@ -0,0 +1,9 @@
#pragma once
class asIScriptEngine;
class ShaderManager;
class ModelManager;
class MaterialManager;
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr);
void RegisterResourceBindings(asIScriptEngine *engine);
-2
View File
@@ -25,14 +25,12 @@ public:
asIScriptEngine* GetEngine() const { return engine; }
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
asIScriptFunction* GetInitFunction() const { return initFunc; }
asIScriptFunction* GetShutdownFunction() const { return shutdownFunc; }
private:
asIScriptEngine* engine;
asIScriptFunction* updateFunc;
asIScriptFunction* drawFunc;
asIScriptFunction* initFunc;
asIScriptFunction* shutdownFunc;
asIScriptModule* currentModule;
+144 -9
View File
@@ -1,6 +1,18 @@
typedef void string;
string format(const string&in fmt, const ?&in ...);
// Array template (provided by scriptarray add-on)
external shared class array<T> {
uint length() const;
void resize(uint);
void insertAt(uint, const T&in);
void insertLast(const T&in);
void removeAt(uint);
void removeLast();
T& opIndex(uint);
const T& opIndex(uint) const;
}
// Logging functions
void Print(const string&in);
void Log(int level, const string&in);
@@ -19,14 +31,98 @@ namespace Toast {
void Success(const string&in);
}
// Drawing functions
namespace Draw {
void Pixel(int x, int y, int color);
void Line(int startX, int startY, int endX, int endY, int color);
void Circle(int centerX, int centerY, float radius, int color);
void Text(const string&in text, int x, int y, int fontSize, int color);
void Rectangle(int x, int y, int width, int height, int color);
void FPS(int x, int y);
// ECS functions
namespace ECS {
// Entity management
uint CreateEntity();
void DestroyEntity(uint entity);
bool IsValid(uint entity);
// Transform component
void AddTransform(uint entity, float x, float y, float z);
void SetPosition(uint entity, float x, float y, float z);
void GetPosition(uint entity, float &out x, float &out y, float &out z);
void SetScale(uint entity, float x, float y, float z);
void SetRotation(uint entity, float rotation);
float GetRotation(uint entity);
bool HasTransform(uint entity);
void RemoveTransform(uint entity);
// Velocity component
void AddVelocity(uint entity, float vx, float vy, float vz, float angular = 0.0f);
void SetVelocity(uint entity, float vx, float vy, float vz);
void SetAngularVelocity(uint entity, float angular);
bool HasVelocity(uint entity);
void RemoveVelocity(uint entity);
// Sprite component
void AddSprite(uint entity, int modelId, uint color, float outlineSize = 0.0f);
void SetSpriteColor(uint entity, uint color);
void SetSpriteModel(uint entity, int modelId);
void SetSpriteOutline(uint entity, float outlineSize);
void SetSpriteShader(uint entity, uint shaderId);
bool HasSprite(uint entity);
void RemoveSprite(uint entity);
// Tag component
void AddTag(uint entity, const string &in name);
string GetTag(uint entity);
void SetTag(uint entity, const string &in name);
bool HasTag(uint entity);
void RemoveTag(uint entity);
// Camera3D component
void AddCamera3D(uint entity, float px, float py, float pz, float tx, float ty, float tz, float fovy);
void SetCameraPosition(uint entity, float x, float y, float z);
void SetCameraTarget(uint entity, float x, float y, float z);
void SetCameraFovy(uint entity, float fovy);
bool HasCamera3D(uint entity);
void RemoveCamera3D(uint entity);
// Light component
void AddLight(uint entity, float dx, float dy, float dz, float intensity = 1.0f);
void SetLightDirection(uint entity, float x, float y, float z);
void SetLightIntensity(uint entity, float intensity);
void SetLightColor(uint entity, uint color);
bool HasLight(uint entity);
void RemoveLight(uint entity);
// Material component
void AddMaterial(uint entity, uint materialId);
void SetMaterialId(uint entity, uint materialId);
uint GetMaterialId(uint entity);
bool HasMaterial(uint entity);
void RemoveMaterial(uint entity);
// RenderPass component
void AddRenderPass(uint entity, int order, bool depthTest, bool depthWrite, uint shaderId);
void SetRenderPassOrder(uint entity, int order);
void SetRenderPassShader(uint entity, uint shaderId);
bool HasRenderPass(uint entity);
void RemoveRenderPass(uint entity);
}
// Shader resource management
namespace Shader {
uint Load(const string &in vsPath, const string &in fsPath);
void Unload(uint id);
}
// Model resource management
namespace Model {
uint Load(const string &in path);
uint LoadCube(float width, float height, float length);
uint LoadSphere(float radius, int rings, int slices);
uint LoadPlane(float width, float length, int resX, int resZ);
void Unload(uint id);
}
// Material resource management
namespace Material {
uint Create(uint shaderId, uint8 r, uint8 g, uint8 b, uint8 a);
void Remove(uint id);
void SetShader(uint id, uint shaderId);
void SetAlbedo(uint id, uint8 r, uint8 g, uint8 b, uint8 a);
}
namespace Texture {
@@ -68,4 +164,43 @@ namespace Math {
float Ceil(float value);
float Modf(float x, float y);
float Abs(float value);
}
}
// Raylib input and timing functions
float GetTime();
bool IsKeyPressed(int key);
// Input Action System
bool IsActionPressed(const string &in actionName);
bool IsActionDown(const string &in actionName);
bool IsActionReleased(const string &in actionName);
float GetActionStrength(const string &in actionName);
void RegisterAction(const string &in actionName);
void BindKeyboard(const string &in actionName, int keyCode);
void BindMouseButton(const string &in actionName, int mouseButton);
void ClearBindings(const string &in actionName);
namespace Key {
const int SPACE = 32;
const int ESCAPE = 256;
const int ENTER = 257;
const int TAB = 258;
const int BACKSPACE = 259;
const int RIGHT = 262;
const int LEFT = 263;
const int DOWN = 264;
const int UP = 265;
const int W = 87;
const int A = 65;
const int S = 83;
const int D = 68;
const int E = 69;
const int LEFT_CONTROL = 341;
}
namespace MouseButton {
const int LEFT = 0;
const int RIGHT = 1;
const int MIDDLE = 2;
}
+101
View File
@@ -0,0 +1,101 @@
// Example script showing how to use the new script-driven rendering system
// Global variables to track our resources
uint toonShader = 0;
uint outlineShader = 0;
uint cubeModel = 0;
uint sphereModel = 0;
uint redMaterial = 0;
uint blueMaterial = 0;
// Entities
uint camera = 0;
uint light = 0;
uint cube = 0;
uint sphere = 0;
void Init() {
Toast::Info("Initializing script-driven rendering...");
// === Load Shaders ===
toonShader = Shader::Load("shaders/toon.vs", "shaders/toon.fs");
outlineShader = Shader::Load("shaders/outline.vs", "shaders/outline.fs");
if (toonShader == 0 || outlineShader == 0) {
Toast::Error("Failed to load shaders!");
return;
}
// === Load Models ===
cubeModel = Model::LoadCube(1.0, 1.0, 1.0);
sphereModel = Model::LoadSphere(0.5, 16, 16);
// === Create Materials ===
// Red material with toon shader
redMaterial = Material::Create(toonShader, 255, 100, 100, 255);
// Blue material with toon shader
blueMaterial = Material::Create(toonShader, 100, 100, 255, 255);
// === Set up Camera ===
camera = ECS::CreateEntity();
ECS::AddCamera3D(camera,
5.0, 5.0, 5.0, // position
0.0, 0.0, 0.0, // target
45.0); // fovy
ECS::AddTag(camera, "MainCamera");
// === Set up Light ===
light = ECS::CreateEntity();
ECS::AddLight(light, 0.5, 0.7, 0.3, 1.0); // direction + intensity
ECS::AddTag(light, "MainLight");
// === Create Cube with Outline ===
cube = ECS::CreateEntity();
ECS::AddTransform(cube, -1.5, 0.5, 0.0);
ECS::AddSprite(cube, cubeModel, 0xFFFFFFFF, 0.0);
ECS::AddMaterial(cube, redMaterial);
ECS::AddTag(cube, "Cube");
ECS::AddVelocity(cube, 0.0, 0.0, 0.0, 1.0); // rotating
// Add outline render pass (order 0 = first)
ECS::AddRenderPass(cube, 0, false, true, outlineShader); // order=0, no depth test
// === Create Sphere ===
sphere = ECS::CreateEntity();
ECS::AddTransform(sphere, 1.5, 0.5, 0.0);
ECS::AddSprite(sphere, sphereModel, 0xFFFFFFFF, 0.0);
ECS::AddMaterial(sphere, blueMaterial);
ECS::AddTag(sphere, "Sphere");
ECS::AddVelocity(sphere, 0.5, 0.0, 0.2, 0.5); // moving and rotating
// Add main render pass (order 1 = second, after outlines)
ECS::AddRenderPass(sphere, 1, true, true, 0); // order=1, with depth test
Toast::Success("Script-driven rendering initialized!");
}
bool lightToggle = false;
void Update(float dt) {
// Camera orbital movement
if (ECS::HasCamera3D(camera)) {
float angle = GetTime() * 0.3;
float radius = 7.0;
float x = Math::Cos(angle) * radius;
float z = Math::Sin(angle) * radius;
ECS::SetCameraPosition(camera, x, 5.0, z);
}
// Example: Toggle light direction with space
if (IsKeyPressed(Key::SPACE)) {
lightToggle = !lightToggle;
if (lightToggle) {
ECS::SetLightDirection(light, -0.5, 0.7, -0.3);
Toast::Info("Light direction changed!");
} else {
ECS::SetLightDirection(light, 0.5, 0.7, 0.3);
Toast::Info("Light direction reset!");
}
}
}
+196 -10
View File
@@ -1,11 +1,197 @@
#include "update.as"
// ECS-based game script demo with script-driven rendering
// This demonstrates creating entities with various components and managing rendering
void Draw() {
Draw::FPS(10, 10);
Draw::Text(format("Hello from AngelScript - at x {} and y {}", int(x), int(y)), int(x), int(y), 20, 0xFF0000FF);
Draw::Rectangle(300, 200, 200, 100, 0x00FF00FF);
Draw::Pixel(250, 200, 0x0000FFFF);
Draw::Line(int(x), int(y), 400, 300, 0xFFFF00FF);
Draw::Circle(600, 400, 50.0f, 0xFF00FFFF);
Texture::Draw(tex, int(ix), int(iy), 0xFFFFFFFF);
}
// Resource IDs
uint toonShader = 0;
uint outlineShader = 0;
uint cubeModel = 0;
uint sphereModel = 0;
uint defaultMaterial = 0;
// Scene entities
uint camera = 0;
uint light = 0;
// Store entity IDs for manipulation
array<uint> entities;
uint player;
uint spinningCube;
float time = 0.0f;
void Init() {
Log(LOG_TRACE, "Initialization complete - ECS Demo with Script-Driven Rendering!");
Toast::Success("ECS Script initialized successfully!");
// === Load shaders ===
toonShader = Shader::Load("shaders/toon.vs", "shaders/toon.fs");
outlineShader = Shader::Load("shaders/outline.vs", "shaders/outline.fs");
if (toonShader == 0 || outlineShader == 0) {
Toast::Error("Failed to load shaders!");
return;
}
// === Load models ===
cubeModel = Model::LoadCube(1.0, 1.0, 1.0);
sphereModel = Model::LoadSphere(0.5, 16, 16);
// === Create default material ===
defaultMaterial = Material::Create(toonShader, 255, 255, 255, 255);
// === Set up camera ===
camera = ECS::CreateEntity();
ECS::AddCamera3D(camera,
5.0, 5.0, 5.0, // position
0.0, 0.0, 0.0, // target
45.0); // fovy
ECS::AddTag(camera, "MainCamera");
// === Set up light ===
light = ECS::CreateEntity();
ECS::AddLight(light, 0.5, 0.7, 0.3, 1.0); // direction + intensity
ECS::AddTag(light, "MainLight");
// === Create player entity at origin with outline ===
player = ECS::CreateEntity();
ECS::AddTransform(player, 0.0f, 0.0f, 0.0f);
ECS::AddSprite(player, sphereModel, 0xFF0000FF, 0.0f); // Red sphere
ECS::SetSpriteShader(player, toonShader); // Apply toon shader
ECS::AddTag(player, "Player");
Log(LOG_INFO, "Created player entity: " + player);
// === Create a spinning cube ===
spinningCube = ECS::CreateEntity();
ECS::AddTransform(spinningCube, 2.0f, 0.0f, 0.0f);
ECS::AddSprite(spinningCube, cubeModel, 0x00FF00FF, 0.0f); // Green cube
ECS::SetSpriteShader(spinningCube, toonShader); // Apply toon shader
ECS::AddVelocity(spinningCube, 0.0f, 0.0f, 0.0f, 2.0f); // Angular velocity
ECS::AddTag(spinningCube, "Spinner");
Log(LOG_INFO, "Created spinning cube: " + spinningCube);
// === Create orbiting entities ===
for (int i = 0; i < 5; i++) {
uint entity = ECS::CreateEntity();
float angle = (float(i) / 5.0f) * 6.28318f; // 2*PI
float radius = 3.0f;
float x = Math::Cos(angle) * radius;
float z = Math::Sin(angle) * radius;
ECS::AddTransform(entity, x, 0.0f, z);
// Different colors for each
uint color = 0;
uint8 r = 255, g = 255, b = 255;
if (i == 0) { color = 0xFFFF00FF; r = 255; g = 255; b = 0; } // Yellow else if (i == 1) { color = 0xFF00FFFF; r = 255; g = 0; b = 255; } // Magenta else if (i == 2) { color = 0x00FFFFFF; r = 0; g = 255; b = 255; } // Cyan else if (i == 3) { color = 0xFFA500FF; r = 255; g = 165; b = 0; } // Orange else { color = 0xFF69B4FF; r = 255; g = 105; b = 180; } // Pink
ECS::AddSprite(entity, sphereModel, color, 0.0f);
ECS::SetSpriteShader(entity, toonShader); // Apply toon shader
ECS::AddTag(entity, "Orbiter" + i);
entities.insertLast(entity);
}
// === Create a moving entity ===
uint mover = ECS::CreateEntity();
ECS::AddTransform(mover, -3.0f, 0.5f, 0.0f);
ECS::AddSprite(mover, cubeModel, 0xFFFFFFFF, 0.0f); // White cube
ECS::SetSpriteShader(mover, toonShader); // Apply toon shader
ECS::AddVelocity(mover, 1.0f, 0.0f, 0.5f, 0.0f); // Moving velocity
ECS::AddTag(mover, "Mover");
entities.insertLast(mover);
Toast::Success("Created " + (entities.length() + 2) + " entities with script-driven rendering!");
}
void Shutdown() {
Log(LOG_TRACE, "Shutdown complete!");
}
bool lightToggle = false;
void Update(float dt) {
time += dt;
// Update camera to orbit around the scene
if (ECS::HasCamera3D(camera)) {
float angle = time * 0.3;
float radius = 7.0;
float x = Math::Cos(angle) * radius;
float z = Math::Sin(angle) * radius;
ECS::SetCameraPosition(camera, x, 5.0, z);
}
// Update player position - simple back and forth motion
float playerX = Math::Sin(time) * 2.0f;
ECS::SetPosition(player, playerX, 0.0f, 0.0f);
// Update orbiting entities in a circle
for (uint i = 0; i < entities.length() - 1; i++) {
uint entity = entities[i];
if (!ECS::IsValid(entity)) continue;
float angle = (float(i) / 5.0f) * 6.28318f + time * 0.5f;
float radius = 3.0f;
float x = Math::Cos(angle) * radius;
float z = Math::Sin(angle) * radius;
float y = Math::Sin(time * 2.0f + float(i)) * 0.5f; // Bobbing motion
ECS::SetPosition(entity, x, y, z);
ECS::SetRotation(entity, time + float(i));
}
// The mover entity wraps around using the Velocity component
// which is handled by the C++ movement system
if (entities.length() > 0) {
uint mover = entities[entities.length() - 1];
if (ECS::IsValid(mover) && ECS::HasTransform(mover)) {
float x, y, z;
ECS::GetPosition(mover, x, y, z);
// Wrap around
if (x > 5.0f) {
ECS::SetPosition(mover, -5.0f, y, z);
}
if (z > 5.0f) {
ECS::SetPosition(mover, x, y, -5.0f);
}
}
}
// === Input Action System Demo ===
// Toggle light with jump action
if (IsActionPressed("jump")) {
lightToggle = !lightToggle;
if (lightToggle) {
ECS::SetLightDirection(light, -0.5, 0.7, -0.3);
Toast::Info("Light direction changed!");
} else {
ECS::SetLightDirection(light, 0.5, 0.7, 0.3);
Toast::Info("Light direction reset!");
}
}
// Move player entity with input actions
if (ECS::IsValid(player) && ECS::HasTransform(player)) {
float x, y, z;
ECS::GetPosition(player, x, y, z);
float moveSpeed = 3.0f * dt;
if (IsActionDown("move_left")) {
x -= moveSpeed;
}
if (IsActionDown("move_right")) {
x += moveSpeed;
}
if (IsActionDown("move_up")) {
z -= moveSpeed;
}
if (IsActionDown("move_down")) {
z += moveSpeed;
}
ECS::SetPosition(player, x, y, z);
}
}
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
# Script to sync local docs/ to the Gitea wiki for this repo.
# Requires SSH access to the wiki (deploy key or account key).
WIKI_REPO="git@gitea.appstack.me:nick/simian.wiki.git"
TMPDIR="$(mktemp -d)"
echo "Cloning wiki to $TMPDIR"
git clone "$WIKI_REPO" "$TMPDIR"
echo "Syncing docs/ -> wiki/"
rsync -av --delete docs/ "$TMPDIR"/
pushd "$TMPDIR" > /dev/null
git add -A
if git diff --staged --quiet; then
echo "No changes to wiki"
else
git commit -m "Update wiki from docs"
git push origin HEAD
fi
popd > /dev/null
rm -rf "$TMPDIR"
echo "Done."
+9
View File
@@ -0,0 +1,9 @@
#version 330
out vec4 fragColor;
uniform vec4 outlineColor; // Color of the outline
void main() {
fragColor = outlineColor;
}
+21
View File
@@ -0,0 +1,21 @@
#version 330
layout(location = 0) in vec3 vertexPosition;
layout(location = 2) in vec3 vertexNormal;
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matNormal;
uniform float outlineThickness;
void main() {
// Transform normal to world space
vec3 worldNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
// Expand vertices along normals in world space
vec3 worldPos = vec3(matModel * vec4(vertexPosition, 1.0));
vec3 expandedPos = worldPos + worldNormal * outlineThickness;
// Transform to clip space
gl_Position = mvp * vec4(vertexPosition + vertexNormal * outlineThickness, 1.0);
}
+22
View File
@@ -0,0 +1,22 @@
#version 330
in vec3 fragNormal;
in vec3 fragPosition;
uniform vec4 albedo;
uniform vec3 lightDir;
out vec4 fragColor;
void main() {
// Use uniform light direction, with fallback
vec3 light = normalize(lightDir);
float bandCount = 3.0;
vec3 normal = normalize(fragNormal);
float intensity = max(dot(normal, light), 0.0);
intensity = floor(intensity * bandCount) / bandCount;
intensity = max(intensity, 0.3); // Minimum visibility
fragColor = vec4(albedo.rgb * intensity, albedo.a);
}
+27
View File
@@ -0,0 +1,27 @@
#version 330
// Raylib's default attribute locations
layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec2 vertexTexCoord;
layout(location = 2) in vec3 vertexNormal;
layout(location = 3) in vec4 vertexColor;
// Raylib's built-in uniforms (automatically provided)
uniform mat4 mvp; // Model-View-Projection matrix
uniform mat4 matModel; // Model matrix
uniform mat4 matNormal; // Normal matrix for transforming normals
out vec3 fragNormal;
out vec3 fragPosition;
void main() {
// Transform position
vec4 worldPos = matModel * vec4(vertexPosition, 1.0);
fragPosition = worldPos.xyz;
// Transform normal properly
fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
// Final position
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+153 -38
View File
@@ -1,9 +1,15 @@
#include "Application.h"
#include "ECSComponents.h"
#include "scripting/ECSBindings.h"
#include "scripting/ResourceBindings.h"
#include "raylib.h"
#include "rlgl.h"
#include "raymath.h"
#include <chrono>
#include <thread>
#include <cstring>
#include <filesystem>
#include <entt.hpp>
#include "log/log.h"
#include "rlImGui.h"
@@ -17,10 +23,12 @@
// 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), renderTexture{}
{
}
@@ -55,6 +63,7 @@ bool Application::Initialize(int argc, char *argv[])
enableEditor = true;
}
}
// Initialize Raylib
InitWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_TITLE);
SetTargetFPS(TARGET_FPS);
@@ -66,6 +75,20 @@ bool Application::Initialize(int argc, char *argv[])
return false;
}
// Set up ECS bindings with our registry
SetGlobalRegistry(&registry);
// Set up resource managers for script access
extern void SetResourceManagers(ShaderManager*, ModelManager*, MaterialManager*);
SetResourceManagers(&shaderManager, &modelManager, &materialManager);
// Set up input manager for script access
extern void SetGlobalInputManager(InputManager*);
SetGlobalInputManager(&inputManager);
// Setup default input bindings
inputManager.SetupDefaultBindings();
// Initialize hot reload to watch the scripts directory so any script change
// triggers a reload.
{
@@ -81,9 +104,7 @@ bool Application::Initialize(int argc, char *argv[])
if (enableEditor)
{
guiManager.Initialize(this);
renderTexture = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT);
}
@@ -116,63 +137,151 @@ void Application::Update(float deltaTime)
{
log_warn("Script compilation failed - keeping previous version");
Toast::Warning("Script compilation failed - check console for details.");
} else {
}
else
{
Toast::Success("Script reloaded successfully.");
//Should call init function again or things don't work
// Should call init function again or things don't work
scriptEngine.CallScriptFunction(scriptEngine.GetInitFunction());
}
}
// Call script Update function
scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime);
// Update ECS systems
UpdateSystems(deltaTime);
}
void Application::UpdateSystems(float deltaTime)
{
// Movement system - apply velocity to transform
auto view = registry.view<ECSTransform, Velocity>();
view.each([deltaTime](auto &transform, auto &velocity)
{
// Apply linear velocity
transform.position.x += velocity.linear.x * deltaTime;
transform.position.y += velocity.linear.y * deltaTime;
transform.position.z += velocity.linear.z * deltaTime;
// Apply angular velocity
transform.rotation += velocity.angular * deltaTime; });
}
void Application::RenderScene()
{
// Query for active camera (first one found with Camera3DComponent)
Camera3D camera = {};
bool hasCamera = false;
auto cameraView = registry.view<Camera3DComponent>();
cameraView.each([&](auto& camComp) {
if (!hasCamera) {
camera.position = camComp.position;
camera.target = camComp.target;
camera.up = camComp.up;
camera.fovy = camComp.fovy;
camera.projection = camComp.projection;
hasCamera = true;
}
});
// If no camera found, use a default one
if (!hasCamera) {
camera.position = {5, 5, 5};
camera.target = {0, 0, 0};
camera.up = {0, 1, 0};
camera.fovy = 45;
camera.projection = CAMERA_PERSPECTIVE;
}
// Find the first light or use a default
Vector3 lightDir = {0.5f, 0.7f, 0.3f};
auto lightView = registry.view<LightComponent>();
lightView.each([&](auto& lightComp) {
lightDir = Vector3Normalize(lightComp.direction);
});
BeginMode3D(camera);
DrawGrid(10, 1);
// Render all entities with Transform and Sprite components
auto view = registry.view<ECSTransform, Sprite>();
view.each([&](auto& transform, auto& sprite) {
if (sprite.modelId > 0) {
Model* model = modelManager.Get(static_cast<unsigned int>(sprite.modelId));
if (model) {
// If sprite has a shader, apply it with uniforms
if (sprite.shaderId > 0) {
Shader* shader = shaderManager.Get(static_cast<unsigned int>(sprite.shaderId));
if (shader) {
// Set shader uniforms
int albedoLoc = GetShaderLocation(*shader, "albedo");
int lightDirLoc = GetShaderLocation(*shader, "lightDir");
if (albedoLoc != -1) {
Vector4 albedo = {
sprite.color.r / 255.0f,
sprite.color.g / 255.0f,
sprite.color.b / 255.0f,
sprite.color.a / 255.0f
};
SetShaderValue(*shader, albedoLoc, &albedo, SHADER_UNIFORM_VEC4);
}
if (lightDirLoc != -1) {
SetShaderValue(*shader, lightDirLoc, &lightDir, SHADER_UNIFORM_VEC3);
}
// Assign shader to model's first material
model->materials[0].shader = *shader;
}
}
// Draw model using Sprite.color as the tint
DrawModelEx(*model, transform.position, Vector3{0, 1, 0},
transform.rotation * RAD2DEG, transform.scale,
sprite.color);
}
}
});
EndMode3D();
}
void Application::Draw()
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
// 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;
}
if (enableEditor)
{
int screenWidth = GetScreenWidth();
int screenHeight = GetScreenHeight();
// 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
io.DisplaySize = ImVec2((float)screenWidth, (float)screenHeight);
BeginTextureMode(renderTexture);
ClearBackground(RAYWHITE);
// Call script Draw function
scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction());
ClearBackground(SKYBLUE);
RenderScene();
EndTextureMode();
BeginDrawing();
ClearBackground(RAYWHITE);
guiManager.Render(renderTexture); // Render the GUI
guiManager.Render(renderTexture);
EndDrawing();
}
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());
ClearBackground(SKYBLUE);
RenderScene();
EndDrawing();
}
}
@@ -185,6 +294,7 @@ void Application::Shutdown()
hotReload = nullptr;
}
scriptEngine.Shutdown();
if (enableEditor)
{
@@ -194,8 +304,13 @@ void Application::Shutdown()
// Shutdown rlImGui first while the GL context and window are still valid
rlImGuiShutdown();
// Shutdown script engine
scriptEngine.Shutdown();
// Clear ECS registry first to release all component data
registry.clear();
// Unload all resources before closing window (while GL context is still valid)
materialManager.Clear();
shaderManager.UnloadAll();
modelManager.UnloadAll();
// Clear the trace log callback before closing window to prevent logging after cleanup
SetTraceLogCallback(nullptr);
+241
View File
@@ -0,0 +1,241 @@
#include "InputManager.h"
#include "log/log.h"
#include <fstream>
#include <sstream>
#include <cmath>
InputManager::InputManager() {
}
InputManager::~InputManager() {
}
void InputManager::RegisterAction(const std::string& actionName) {
if (actions.find(actionName) == actions.end()) {
actions[actionName] = std::vector<InputBinding>();
log_debug("Registered input action: %s", actionName.c_str());
}
}
void InputManager::UnregisterAction(const std::string& actionName) {
actions.erase(actionName);
}
bool InputManager::HasAction(const std::string& actionName) const {
return actions.find(actionName) != actions.end();
}
void InputManager::BindKeyboard(const std::string& actionName, int keyCode) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::Keyboard, keyCode));
log_debug("Bound key %d to action: %s", keyCode, actionName.c_str());
}
void InputManager::BindMouseButton(const std::string& actionName, int mouseButton) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::MouseButton, mouseButton));
}
void InputManager::BindGamepadButton(const std::string& actionName, int gamepadId, int button) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::GamepadButton, button, gamepadId));
}
void InputManager::BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative) {
RegisterAction(actionName);
InputBinding binding(InputType::GamepadAxis, axis, gamepadId);
binding.axisThreshold = threshold;
binding.negative = negative;
actions[actionName].push_back(binding);
}
void InputManager::ClearBindings(const std::string& actionName) {
if (HasAction(actionName)) {
actions[actionName].clear();
}
}
bool InputManager::IsActionPressed(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingPressed(binding)) {
return true;
}
}
return false;
}
bool InputManager::IsActionDown(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingDown(binding)) {
return true;
}
}
return false;
}
bool InputManager::IsActionReleased(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingReleased(binding)) {
return true;
}
}
return false;
}
float InputManager::GetActionStrength(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return 0.0f;
float maxStrength = 0.0f;
for (const auto& binding : it->second) {
float strength = GetBindingStrength(binding);
if (std::abs(strength) > std::abs(maxStrength)) {
maxStrength = strength;
}
}
return maxStrength;
}
bool InputManager::IsBindingPressed(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyPressed(binding.code);
case InputType::MouseButton:
return IsMouseButtonPressed(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonPressed(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis: {
// Axis "pressed" is harder to detect - simplified to just checking threshold
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? value < -binding.axisThreshold : value > binding.axisThreshold;
}
return false;
}
}
return false;
}
bool InputManager::IsBindingDown(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyDown(binding.code);
case InputType::MouseButton:
return IsMouseButtonDown(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonDown(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis:
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? value < -binding.axisThreshold : value > binding.axisThreshold;
}
return false;
}
return false;
}
bool InputManager::IsBindingReleased(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyReleased(binding.code);
case InputType::MouseButton:
return IsMouseButtonReleased(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonReleased(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis:
return false; // Axes don't have a clear "released" state
}
return false;
}
float InputManager::GetBindingStrength(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyDown(binding.code) ? 1.0f : 0.0f;
case InputType::MouseButton:
return IsMouseButtonDown(binding.code) ? 1.0f : 0.0f;
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonDown(binding.gamepadId, binding.code) ? 1.0f : 0.0f;
}
return 0.0f;
case InputType::GamepadAxis:
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? -value : value;
}
return 0.0f;
}
return 0.0f;
}
void InputManager::SetupDefaultBindings() {
// Movement
BindKeyboard("move_up", KEY_W);
BindKeyboard("move_up", KEY_UP);
BindKeyboard("move_down", KEY_S);
BindKeyboard("move_down", KEY_DOWN);
BindKeyboard("move_left", KEY_A);
BindKeyboard("move_left", KEY_LEFT);
BindKeyboard("move_right", KEY_D);
BindKeyboard("move_right", KEY_RIGHT);
// Actions
BindKeyboard("jump", KEY_SPACE);
BindKeyboard("interact", KEY_E);
BindKeyboard("attack", KEY_LEFT_CONTROL);
BindMouseButton("attack", MOUSE_BUTTON_LEFT);
// Gamepad (if available)
BindGamepadButton("jump", 0, GAMEPAD_BUTTON_RIGHT_FACE_DOWN);
BindGamepadButton("interact", 0, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT);
BindGamepadAxis("move_left", 0, GAMEPAD_AXIS_LEFT_X, 0.3f, true);
BindGamepadAxis("move_right", 0, GAMEPAD_AXIS_LEFT_X, 0.3f, false);
log_info("Input manager: Default bindings configured");
}
std::vector<std::string> InputManager::GetAllActions() const {
std::vector<std::string> result;
for (const auto& pair : actions) {
result.push_back(pair.first);
}
return result;
}
const std::vector<InputBinding>& InputManager::GetBindings(const std::string& actionName) const {
static std::vector<InputBinding> empty;
auto it = actions.find(actionName);
if (it != actions.end()) {
return it->second;
}
return empty;
}
void InputManager::LoadFromFile(const std::string& filepath) {
// TODO: Implement config file loading (JSON or simple text format)
log_info("Input config loading not yet implemented: %s", filepath.c_str());
}
void InputManager::SaveToFile(const std::string& filepath) const {
// TODO: Implement config file saving
log_info("Input config saving not yet implemented: %s", filepath.c_str());
}
+76
View File
@@ -0,0 +1,76 @@
#include "MaterialManager.h"
#include "log/log.h"
MaterialManager::MaterialManager() : nextId(1), cleared(false)
{
}
MaterialManager::~MaterialManager()
{
// Only clear if not already done
if (!cleared) {
materials.clear();
}
}
unsigned int MaterialManager::Create(unsigned int shaderId, Color albedo)
{
unsigned int id = nextId++;
MaterialData data;
data.shaderId = shaderId;
data.albedo = albedo;
materials[id] = data;
log_info("Created material id=%u with shader=%u", id, shaderId);
return id;
}
void MaterialManager::Remove(unsigned int id)
{
auto it = materials.find(id);
if (it != materials.end())
{
materials.erase(it);
log_info("Removed material id=%u", id);
}
}
MaterialData* MaterialManager::Get(unsigned int id)
{
auto it = materials.find(id);
if (it != materials.end())
{
return &it->second;
}
return nullptr;
}
void MaterialManager::SetShader(unsigned int id, unsigned int shaderId)
{
auto it = materials.find(id);
if (it != materials.end())
{
it->second.shaderId = shaderId;
log_info("Updated material id=%u shader to %u", id, shaderId);
}
}
void MaterialManager::SetAlbedo(unsigned int id, Color albedo)
{
auto it = materials.find(id);
if (it != materials.end())
{
it->second.albedo = albedo;
}
}
void MaterialManager::Clear()
{
if (!cleared) {
materials.clear();
cleared = true;
log_info("Cleared all materials");
}
}
+79
View File
@@ -0,0 +1,79 @@
#include "ModelManager.h"
#include "log/log.h"
ModelManager::ModelManager() : nextId(1), unloaded(false)
{
}
ModelManager::~ModelManager()
{
// Only unload if not already done
if (!unloaded) {
for (auto& pair : models) {
UnloadModel(pair.second);
}
models.clear();
}
}
unsigned int ModelManager::Load(const std::string& path)
{
Model model = LoadModel(path.c_str());
if (model.meshCount == 0)
{
log_warn("Failed to load model: %s", path.c_str());
return 0;
}
unsigned int id = nextId++;
models[id] = model;
log_info("Loaded model id=%u from %s", id, path.c_str());
return id;
}
unsigned int ModelManager::LoadFromMesh(Mesh mesh)
{
Model model = LoadModelFromMesh(mesh);
unsigned int id = nextId++;
models[id] = model;
log_info("Loaded model id=%u from mesh", id);
return id;
}
void ModelManager::Unload(unsigned int id)
{
auto it = models.find(id);
if (it != models.end())
{
UnloadModel(it->second);
models.erase(it);
log_info("Unloaded model id=%u", id);
}
}
Model* ModelManager::Get(unsigned int id)
{
auto it = models.find(id);
if (it != models.end())
{
return &it->second;
}
return nullptr;
}
void ModelManager::UnloadAll()
{
if (!unloaded) {
for (auto& pair : models)
{
UnloadModel(pair.second);
}
models.clear();
unloaded = true;
log_info("Unloaded all models");
}
}
+68
View File
@@ -0,0 +1,68 @@
#include "ShaderManager.h"
#include "log/log.h"
ShaderManager::ShaderManager() : nextId(1), unloaded(false)
{
}
ShaderManager::~ShaderManager()
{
// Only unload if not already done
if (!unloaded) {
for (auto& pair : shaders) {
UnloadShader(pair.second);
}
shaders.clear();
}
}
unsigned int ShaderManager::Load(const std::string& vsPath, const std::string& fsPath)
{
Shader shader = LoadShader(vsPath.c_str(), fsPath.c_str());
if (shader.id == 0)
{
log_warn("Failed to load shader: %s / %s", vsPath.c_str(), fsPath.c_str());
return 0;
}
unsigned int id = nextId++;
shaders[id] = shader;
log_info("Loaded shader id=%u from %s / %s (raylib id=%u)", id, vsPath.c_str(), fsPath.c_str(), shader.id);
return id;
}
void ShaderManager::Unload(unsigned int id)
{
auto it = shaders.find(id);
if (it != shaders.end())
{
UnloadShader(it->second);
shaders.erase(it);
log_info("Unloaded shader id=%u", id);
}
}
Shader* ShaderManager::Get(unsigned int id)
{
auto it = shaders.find(id);
if (it != shaders.end())
{
return &it->second;
}
return nullptr;
}
void ShaderManager::UnloadAll()
{
if (!unloaded) {
for (auto& pair : shaders)
{
UnloadShader(pair.second);
}
shaders.clear();
unloaded = true;
log_info("Unloaded all shaders");
}
}
+2
View File
@@ -33,6 +33,8 @@ void GuiManager::Render(RenderTexture2D &renderTexture)
RenderErrorBanner();
RenderNotifications();
logWindow.Render(showLogWindow); // Render the LogWindow
rlImGuiEnd();
ImGui::UpdatePlatformWindows();
File diff suppressed because it is too large Load Diff
+117
View File
@@ -0,0 +1,117 @@
#include "scripting/InputBindings.h"
#include "InputManager.h"
#include <angelscript.h>
#include <cassert>
static InputManager* g_inputManager = nullptr;
void SetGlobalInputManager(InputManager* inputManager) {
g_inputManager = inputManager;
}
// AngelScript wrapper functions
static bool AS_IsActionPressed(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionPressed(actionName) : false;
}
static bool AS_IsActionDown(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionDown(actionName) : false;
}
static bool AS_IsActionReleased(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionReleased(actionName) : false;
}
static float AS_GetActionStrength(const std::string& actionName) {
return g_inputManager ? g_inputManager->GetActionStrength(actionName) : 0.0f;
}
static void AS_RegisterAction(const std::string& actionName) {
if (g_inputManager) g_inputManager->RegisterAction(actionName);
}
static void AS_BindKeyboard(const std::string& actionName, int keyCode) {
if (g_inputManager) g_inputManager->BindKeyboard(actionName, keyCode);
}
static void AS_BindMouseButton(const std::string& actionName, int mouseButton) {
if (g_inputManager) g_inputManager->BindMouseButton(actionName, mouseButton);
}
static void AS_ClearBindings(const std::string& actionName) {
if (g_inputManager) g_inputManager->ClearBindings(actionName);
}
void RegisterInputBindings(asIScriptEngine* engine) {
int r;
// Input action queries
r = engine->RegisterGlobalFunction("bool IsActionPressed(const string &in)",
asFUNCTION(AS_IsActionPressed), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool IsActionDown(const string &in)",
asFUNCTION(AS_IsActionDown), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool IsActionReleased(const string &in)",
asFUNCTION(AS_IsActionReleased), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("float GetActionStrength(const string &in)",
asFUNCTION(AS_GetActionStrength), asCALL_CDECL);
assert(r >= 0);
// Allow runtime binding from scripts
r = engine->RegisterGlobalFunction("void RegisterAction(const string &in)",
asFUNCTION(AS_RegisterAction), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void BindKeyboard(const string &in, int)",
asFUNCTION(AS_BindKeyboard), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void BindMouseButton(const string &in, int)",
asFUNCTION(AS_BindMouseButton), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void ClearBindings(const string &in)",
asFUNCTION(AS_ClearBindings), asCALL_CDECL);
assert(r >= 0);
// Key constants for binding
r = engine->RegisterEnum("Key");
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "W", KEY_W);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "A", KEY_A);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "S", KEY_S);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "D", KEY_D);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "SPACE", KEY_SPACE);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "E", KEY_E);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "LEFT_CONTROL", KEY_LEFT_CONTROL);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "UP", KEY_UP);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "DOWN", KEY_DOWN);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "LEFT", KEY_LEFT);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "RIGHT", KEY_RIGHT);
assert(r >= 0);
r = engine->RegisterEnum("MouseButton");
assert(r >= 0);
// Mouse button constants
r = engine->RegisterEnumValue("MouseButton", "LEFT", MOUSE_BUTTON_LEFT);
assert(r >= 0);
r= engine->RegisterEnumValue("MouseButton", "RIGHT", MOUSE_BUTTON_RIGHT);
assert(r >= 0);
r = engine->RegisterEnumValue("MouseButton", "MIDDLE", MOUSE_BUTTON_MIDDLE);
assert(r >= 0);
}
+144
View File
@@ -0,0 +1,144 @@
#include "scripting/ResourceBindings.h"
#include "ShaderManager.h"
#include "ModelManager.h"
#include "MaterialManager.h"
#include <angelscript.h>
#include <assert.h>
#include "raylib.h"
// Global resource manager pointers for script access
static ShaderManager* g_shaderManager = nullptr;
static ModelManager* g_modelManager = nullptr;
static MaterialManager* g_materialManager = nullptr;
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr) {
g_shaderManager = shaderMgr;
g_modelManager = modelMgr;
g_materialManager = materialMgr;
}
// === Shader Management ===
unsigned int AS_LoadShader(const std::string& vsPath, const std::string& fsPath) {
if (!g_shaderManager) return 0;
return g_shaderManager->Load(vsPath, fsPath);
}
void AS_UnloadShader(unsigned int id) {
if (!g_shaderManager) return;
g_shaderManager->Unload(id);
}
// === Model Management ===
unsigned int AS_LoadModel(const std::string& path) {
if (!g_modelManager) return 0;
return g_modelManager->Load(path);
}
unsigned int AS_LoadModelCube(float width, float height, float length) {
if (!g_modelManager) return 0;
Mesh mesh = GenMeshCube(width, height, length);
return g_modelManager->LoadFromMesh(mesh);
}
unsigned int AS_LoadModelSphere(float radius, int rings, int slices) {
if (!g_modelManager) return 0;
Mesh mesh = GenMeshSphere(radius, rings, slices);
return g_modelManager->LoadFromMesh(mesh);
}
unsigned int AS_LoadModelPlane(float width, float length, int resX, int resZ) {
if (!g_modelManager) return 0;
Mesh mesh = GenMeshPlane(width, length, resX, resZ);
return g_modelManager->LoadFromMesh(mesh);
}
void AS_UnloadModel(unsigned int id) {
if (!g_modelManager) return;
g_modelManager->Unload(id);
}
// === Material Management ===
unsigned int AS_CreateMaterial(unsigned int shaderId, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
if (!g_materialManager) return 0;
Color albedo = {r, g, b, a};
return g_materialManager->Create(shaderId, albedo);
}
void AS_RemoveMaterial(unsigned int id) {
if (!g_materialManager) return;
g_materialManager->Remove(id);
}
void AS_SetMaterialShader(unsigned int id, unsigned int shaderId) {
if (!g_materialManager) return;
g_materialManager->SetShader(id, shaderId);
}
void AS_SetMaterialAlbedo(unsigned int id, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
if (!g_materialManager) return;
Color albedo = {r, g, b, a};
g_materialManager->SetAlbedo(id, albedo);
}
void RegisterResourceBindings(asIScriptEngine *engine)
{
int r;
// === Shader namespace ===
engine->SetDefaultNamespace("Shader");
r = engine->RegisterGlobalFunction("uint Load(const string &in, const string &in)",
asFUNCTION(AS_LoadShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Unload(uint)",
asFUNCTION(AS_UnloadShader), asCALL_CDECL);
assert(r >= 0);
// === Model namespace ===
engine->SetDefaultNamespace("Model");
r = engine->RegisterGlobalFunction("uint Load(const string &in)",
asFUNCTION(AS_LoadModel), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCube(float, float, float)",
asFUNCTION(AS_LoadModelCube), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadSphere(float, int, int)",
asFUNCTION(AS_LoadModelSphere), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadPlane(float, float, int, int)",
asFUNCTION(AS_LoadModelPlane), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Unload(uint)",
asFUNCTION(AS_UnloadModel), asCALL_CDECL);
assert(r >= 0);
// === Material namespace ===
engine->SetDefaultNamespace("Material");
r = engine->RegisterGlobalFunction("uint Create(uint, uint8, uint8, uint8, uint8)",
asFUNCTION(AS_CreateMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Remove(uint)",
asFUNCTION(AS_RemoveMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetShader(uint, uint)",
asFUNCTION(AS_SetMaterialShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetAlbedo(uint, uint8, uint8, uint8, uint8)",
asFUNCTION(AS_SetMaterialAlbedo), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
}
+6 -2
View File
@@ -1,18 +1,22 @@
#include "scripting/ScriptBindings.h"
#include "scripting/ToastBindings.h"
#include "scripting/LogBindings.h"
#include "scripting/DrawBindings.h"
#include "scripting/ImageBindings.h"
#include "scripting/TextureBindings.h"
#include "scripting/MathBindings.h"
#include "scripting/ECSBindings.h"
#include "scripting/ResourceBindings.h"
#include "scripting/InputBindings.h"
void ScriptBindings::RegisterAll(asIScriptEngine *engine)
{
// Delegate to separate binding files
RegisterToastBindings(engine);
RegisterLogBindings(engine);
RegisterDrawBindings(engine);
RegisterImageBindings(engine);
RegisterTextureBindings(engine);
RegisterMathBindings(engine);
RegisterECSBindings(engine);
RegisterResourceBindings(engine);
RegisterInputBindings(engine);
}
+5 -3
View File
@@ -2,6 +2,7 @@
#include "scripting/ScriptBindings.h"
#include "scriptstdstring.h"
#include "scriptbuilder.h"
#include "scriptarray.h"
#include <fstream>
#include <sstream>
#include <cstring>
@@ -11,7 +12,7 @@
#include <vector>
#include <mutex>
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) {
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) {
}
ScriptEngine::~ScriptEngine() {
@@ -28,6 +29,9 @@ bool ScriptEngine::Initialize() {
// Register std::string
RegisterStdString(engine);
// Register array template
RegisterScriptArray(engine, true);
// Register script bindings
ScriptBindings::RegisterAll(engine);
@@ -164,7 +168,6 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
// Cache new functions
initFunc = currentModule->GetFunctionByName("Init");
updateFunc = currentModule->GetFunctionByName("Update");
drawFunc = currentModule->GetFunctionByName("Draw");
shutdownFunc = currentModule->GetFunctionByName("Shutdown");
hasValidScript = true;
log_info("Script compiled and cached: %s", filename.c_str());
@@ -199,7 +202,6 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
void ScriptEngine::ClearCachedFunctions() {
updateFunc = nullptr;
drawFunc = nullptr;
hasValidScript = false;
}
+10 -1
View File
@@ -3,7 +3,16 @@
#include <assert.h>
#include <string>
Color ColorFromUInt(unsigned int c);
// Utility function to convert uint color to Raylib Color
Color ColorFromUInt(unsigned int c)
{
Color col;
col.r = (c >> 24) & 0xFF;
col.g = (c >> 16) & 0xFF;
col.b = (c >> 8) & 0xFF;
col.a = c & 0xFF;
return col;
}
Texture2D AS_LoadTexture(const std::string &filename)
{
+6 -1
View File
@@ -2,25 +2,30 @@
#include "gui/Toast.h"
#include <angelscript.h>
#include <assert.h>
#include <string>
#include "../log/log.h"
// Toast binding functions
static void AS_ToastInfo(const std::string& message)
{
log_info("%s", message.c_str());
Toast::Info(message);
}
static void AS_ToastWarning(const std::string& message)
{
log_warn("%s", message.c_str());
Toast::Warning(message);
}
static void AS_ToastError(const std::string& message)
{
log_error("%s", message.c_str());
Toast::Error(message);
}
static void AS_ToastSuccess(const std::string& message)
{
log_info("%s", message.c_str());
Toast::Success(message);
}
+14 -28
View File
@@ -1,43 +1,29 @@
cmake_minimum_required(VERSION 3.16)
# Simple unit test executable that compiles acutest.c directly
# Unit tests just need test sources + link to simian_core
add_executable(unit_tests
test.cpp
hot_reload.cpp
script_engine_tests.cpp
math_bindings_tests.cpp
${CMAKE_SOURCE_DIR}/src/HotReload.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ScriptEngine.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ScriptBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ToastBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/LogBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/DrawBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ImageBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/TextureBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/MathBindings.cpp
${CMAKE_SOURCE_DIR}/src/log/log.c
)
target_include_directories(unit_tests PRIVATE
${CMAKE_SOURCE_DIR}/tests
${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
${CMAKE_SOURCE_DIR}/external/acutest
)
# Link AngelScript and its add-ons needed by ScriptEngine
target_link_libraries(unit_tests PRIVATE
angelscript
scriptstdstring
scriptbuilder
raylib
imgui
)
# Link the shared simian_core library - that's it!
target_link_libraries(unit_tests PRIVATE simian_core)
add_test(NAME unit_tests COMMAND unit_tests)
# Platform-specific linking for tests too
if(WIN32)
target_link_libraries(unit_tests PRIVATE winmm gdi32)
endif()
add_test(NAME unit_tests COMMAND unit_tests)
add_test(NAME hotreload_dir COMMAND unit_tests hotreload_dir)
add_test(NAME script_compile COMMAND unit_tests script_compile)
add_test(NAME script_include COMMAND unit_tests script_include)
add_test(NAME math_bindings COMMAND unit_tests math_bindings)