31 Commits

Author SHA1 Message Date
nick fbd911a1f0 Merge pull request 'feat/filesystem' (#8) from feat/filesystem into main
CI / build-and-test (push) Successful in 2m40s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 11s
Reviewed-on: #8
2026-03-13 02:07:25 +00:00
nick 366023f6cb feat: load from game.zip for packaged files, fixed shader access via PhysFS
CI / build-and-test (push) Successful in 2m54s
CI / build-and-test (pull_request) Successful in 2m56s
2026-03-13 15:06:18 +13:00
nick bc99a1e53c feat: Globals value store as well
CI / build-and-test (push) Successful in 2m41s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 11s
2026-03-13 14:50:53 +13:00
nick d667952006 feat: Key Value store types
CI / build-and-test (push) Successful in 2m50s
2026-03-13 14:25:03 +13:00
nick 5e00d8c2ec feat: added kv store, correct file permissions
CI / build-and-test (push) Successful in 2m18s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 11s
2026-03-13 12:31:33 +13:00
nick d29369b286 Merge pull request 'feat/editor' (#7) from feat/editor into main
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 9s
CI / build-and-test (push) Successful in 2m24s
Reviewed-on: #7
2026-03-12 20:28:12 +00:00
nick 3e23f7da8e feat: added physFS
CI / build-and-test (push) Failing after 10m16s
CI / build-and-test (pull_request) Successful in 3m2s
2026-03-11 16:43:36 +13:00
nick 8c2ce637a2 feat: autoload script
CI / build-and-test (push) Failing after 14m23s
2026-03-11 15:58:51 +13:00
nick 5f031b80c2 chore: more tests
CI / build-and-test (push) Failing after 14m23s
2026-03-10 21:20:30 +13:00
nick eb155da5d8 chore: fix for tests
CI / build-and-test (push) Failing after 14m58s
2026-03-10 20:48:33 +13:00
nick 36fd4047dc fix: scene scripts and asset restore
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 13m56s
CI / build-and-test (push) Failing after 14m19s
2026-03-10 20:03:44 +13:00
nick 756b818f46 feat: asset management, editor camera
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 26s
CI / build-and-test (push) Failing after 2m36s
2026-03-09 21:31:51 +13:00
nick d7add3964d feat: additonal components, updated docs
CI / build-and-test (push) Successful in 2m37s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-09 17:37:29 +13:00
nick 8041286a9d feat: tool tips and groups for editable properties
CI / build-and-test (push) Successful in 2m4s
2026-03-09 13:51:45 +13:00
nick b3871716a1 feat: edit values and persist
CI / build-and-test (push) Successful in 2m10s
2026-03-09 13:33:54 +13:00
nick 2a74648ffd feat: editable properties
CI / build-and-test (push) Successful in 2m19s
2026-03-09 13:24:39 +13:00
nick cd754fda28 feat: class instances for entity scripts
CI / build-and-test (push) Successful in 2m9s
2026-03-09 12:55:55 +13:00
nick 9ebf77daec chore: trying to fix the wiki
CI / build-and-test (push) Successful in 2m28s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-08 22:07:23 +13:00
nick d6f2cc1ea3 feat: example entity script
CI / build-and-test (push) Successful in 2m13s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-08 21:48:40 +13:00
nick 7a3810805e fix: issue with duplicating entities when created by the script
CI / build-and-test (push) Successful in 2m21s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 21s
2026-03-08 20:47:09 +13:00
nick ae21d4a5d2 chore: docs update
CI / build-and-test (push) Successful in 2m5s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 22s
2026-03-08 20:32:14 +13:00
nick 0baabd689f chore: more docs attempts 2026-03-08 20:29:45 +13:00
nick 28328cbc0d feat: editor improvements
CI / build-and-test (push) Successful in 2m5s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 23s
2026-03-08 20:25:11 +13:00
nick 18ca7105c5 fix: mazimise window and notification freezing
CI / build-and-test (push) Successful in 2m33s
2026-03-08 19:24:39 +13:00
nick 9a4dba0a50 feat: scene editor
CI / build-and-test (push) Successful in 2m2s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 24s
2026-03-08 17:52:44 +13:00
nick b95365f6a6 chore: fixing wiki stuff
CI / build-and-test (push) Failing after 12m35s
2026-03-08 16:41:13 +13:00
Simian CI 271e2ea7b7 Update wiki from docs (CI) 2026-03-08 03:28:05 +00:00
nick d7f9019e61 chore: fix for headless raylib
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 31s
CI / build-and-test (push) Failing after 11m38s
2026-03-08 16:26:34 +13:00
nick 5367b31f4e chore: fixing CI
CI / build-and-test (push) Failing after 2m9s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 23s
2026-03-08 16:04:38 +13:00
nick e6a53baa1a chore: added tests and tidied docs
CI / build-and-test (push) Failing after 1m57s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 13s
2026-03-08 15:52:14 +13:00
nick 15e5d580da feat: Scenegraph
CI / build-and-test (push) Successful in 2m4s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 8s
2026-03-08 15:37:25 +13:00
76 changed files with 10308 additions and 487 deletions
+23 -5
View File
@@ -18,19 +18,37 @@ jobs:
with:
ssh-private-key: ${{ secrets.WIKI_SSH_KEY }}
- name: Add Gitea host key
run: |
mkdir -p ~/.ssh
ssh-keyscan -t rsa gitea.appstack.me >> ~/.ssh/known_hosts
- 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
# Workspace is always $GITHUB_WORKSPACE in Actions
WORKSPACE=${GITHUB_WORKSPACE:-.}
TEMP_WIKI=$(mktemp -d)
git clone "$WIKI_REPO" "$TEMP_WIKI"
cd "$TEMP_WIKI"
# Safe clean (keep .git)
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
# Copy from workspace/docs
cp -r "$WORKSPACE/docs"/* . 2>/dev/null || true
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
git commit -m "Update wiki from docs (CI) [skip ci]"
git push origin main
echo "✅ Synced to simian.wiki main"
fi
+6
View File
@@ -10,3 +10,9 @@
[submodule "external/rlImGui"]
path = external/rlImGui
url = https://github.com/raylib-extras/rlImGui.git
[submodule "external/physfs"]
path = external/physfs
url = https://github.com/icculus/physfs
[submodule "external/snkv"]
path = external/snkv
url = https://github.com/hash-anu/snkv
+60
View File
@@ -14,6 +14,48 @@ add_subdirectory(external/raylib)
# -------------------------
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
# -------------------------
# PhysFS (submodule)
# -------------------------
add_subdirectory(external/physfs)
# -------------------------
# SNKV (submodule)
# -------------------------
add_library(snkv STATIC
external/snkv/src/kvstore.c
external/snkv/src/btree.c
external/snkv/src/btmutex.c
external/snkv/src/pager.c
external/snkv/src/pcache.c
external/snkv/src/pcache1.c
external/snkv/src/wal.c
external/snkv/src/memjournal.c
external/snkv/src/bitvec.c
external/snkv/src/os.c
external/snkv/src/os_unix.c
external/snkv/src/os_win.c
external/snkv/src/os_kv.c
external/snkv/src/mutex.c
external/snkv/src/mutex_noop.c
external/snkv/src/mutex_unix.c
external/snkv/src/mutex_w32.c
external/snkv/src/malloc.c
external/snkv/src/status.c
external/snkv/src/global.c
external/snkv/src/hash.c
external/snkv/src/util.c
external/snkv/src/printf.c
external/snkv/src/random.c
external/snkv/src/threads.c
external/snkv/src/fault.c
external/snkv/src/mem1.c
)
target_include_directories(snkv PUBLIC
external/snkv/include
)
# -------------------------
# Dear ImGui (docking branch)
# -------------------------
@@ -79,6 +121,9 @@ target_include_directories(scriptarray
# -------------------------
add_library(simian_core STATIC
src/Application.cpp
src/PhysFSManager.cpp
src/SceneGraph.cpp
src/SceneLoader.cpp
src/scripting/ScriptEngine.cpp
src/scripting/ScriptBindings.cpp
src/scripting/ToastBindings.cpp
@@ -89,13 +134,20 @@ add_library(simian_core STATIC
src/scripting/ECSBindings.cpp
src/scripting/ResourceBindings.cpp
src/scripting/InputBindings.cpp
src/scripting/SceneBindings.cpp
src/scripting/PhysFSBindings.cpp
src/scripting/SNKVBindings.cpp
src/scripting/GlobalsBindings.cpp
src/HotReload.cpp
src/gui/GuiManager.cpp
src/gui/ImViewGuizmoIntegration.cpp
src/gui/LogWindow.cpp
src/log/log.c
src/ShaderManager.cpp
src/ModelManager.cpp
src/MaterialManager.cpp
src/AssetManager.cpp
src/TextureManager.cpp
src/InputManager.cpp
)
@@ -109,12 +161,17 @@ target_include_directories(simian_core PUBLIC
external/angelscript/sdk/add_on/scriptstdstring
external/angelscript/sdk/add_on/scriptbuilder
external/angelscript/sdk/add_on/scriptarray
external/physfs/src
external/snkv/include
external/imgui
external/ImViewGuizmo
)
target_link_libraries(simian_core PUBLIC
raylib
angelscript
PhysFS::PhysFS-static
snkv
scriptstdstring
scriptbuilder
scriptarray
@@ -154,6 +211,9 @@ if(MSVC)
target_compile_options(simian_core PRIVATE /W4 /EHsc)
else()
target_compile_options(simian_core PRIVATE -Wall -Wextra -pedantic)
if(TARGET raylib)
target_compile_options(raylib PRIVATE -Wno-missing-field-initializers)
endif()
endif()
# -------------------------
+12
View File
@@ -73,6 +73,16 @@ Simian/
- 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.
## Asset registry
Asset keys are persisted in a project-wide registry at `assets/registry.toml`. This file is loaded on startup and written on shutdown so scene references stay valid across sessions.
## Scene scripts (class-based)
Scene scripts now use class instances (similar to entity scripts). Implement a class with `Init()`, `Update(float)`, and `Shutdown()` methods. The engine picks the first class that exposes any of those methods and instantiates it.
See `scripts/game.as` and `scripts/scene_example.as` for examples.
## 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/`:
@@ -122,6 +132,7 @@ cd ../.. && git add external/raylib external/angelscript && git commit -m "Updat
- The project was developed and tested on Windows with Visual Studio; Linux builds are supported but may need X11 / audio dev packages installed depending on your environment.
- The codebase avoids changing tracked submodule contents; use the `cmake/` wrappers when a submodule doesn't include a CMake build.
- Runtime writes are limited to `user/`. Scene saves and asset registry writes require `--editor`.
## License
@@ -129,3 +140,4 @@ cd ../.. && git add external/raylib external/angelscript && git commit -m "Updat
* 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)
* SNKV: Apache-2.0 License (see https://github.com/hash-anu/snkv/blob/master/LICENSE)
+15
View File
@@ -0,0 +1,15 @@
# Simian asset registry
# Auto-generated - edits may be overwritten
[[model]]
key = "Torus"
type = "primitive"
primitive = "torus"
params = [0.5, 1, 32, 16]
[[model]]
key = "demo_cube"
type = "primitive"
primitive = "cube"
params = [1, 1, 1]
+104
View File
@@ -0,0 +1,104 @@
# Editor Overview
This document outlines the in-app editor UI, its panels, and common workflows.
## Launching the Editor
- Start the app with the editor flag enabled.
- The editor UI appears as dockable panels around the Game Window.
## Layout
- DockSpace host with a top menu bar.
- You can drag and drop to move panels around.
- Panels: Game Window, Scene, Assets, Inspector, Log Viewer.
## Menu Bar
### Game
- Quit: request a clean shutdown.
### Scene
- New: clear the current scene.
- Load: load a scene from a TOML-like file path.
- Save As: save the current scene to a file path.
### View
- Toggle Log Viewer, Scene, and Inspector panels.
- Toggle Assets panel.
## Game Window
- Renders the current scene view.
- Resizes with the available docked panel space.
## Scene Panel
- Displays the entity hierarchy as a tree.
- Drag and drop entities to re-parent.
- Drop on the bottom target to clear parent.
- Create Entity and Delete Selected controls.
## Inspector Panel
- Shows components for the selected entity.
- Add Component dropdown lists only components not yet on the entity.
- Remove buttons appear on removable components.
### Components
- Tag: rename the entity.
- Transform: position, scale, rotation (degrees in UI).
- Velocity: linear and angular velocity.
- Model Renderer: model id, shader id, color, outline.
- Texture: texture id.
- UV Transform: scale, offset, rotation (radians).
- Script: per-entity script path and enabled toggle.
- Camera: position, target, up, FOV, projection, helper actions.
- Light: direction, color, intensity, helper presets.
- Material: material id.
- Render Pass: order, depth flags, shader id.
- World Transform: read-only computed transform.
## Assets Panel
- Browse loaded textures, shaders, models, and materials.
- Load assets by key and path, or create primitive models.
- Unload assets by key.
## Log Viewer
- Shows engine log output in real time.
## Shortcuts
- Ctrl+Q: Quit
- Ctrl+L: Toggle Log Viewer
- Ctrl+N: New Scene
- Ctrl+O: Load Scene
- Ctrl+S: Save Scene
## Notes
- Scene Save and Load use file path text fields.
- Components can be assigned via asset keys in the inspector; numeric IDs remain as a fallback.
- World Transform is computed from hierarchy and local transforms and is read-only.
- UV Transform values are passed as shader uniforms `uvScale`, `uvOffset`, and `uvRotation` when present.
## Scene Scripts
- Scene scripts are loaded by the scene system and run once per scene.
- The editor lets you set a scene-level script path in the Scene panel.
- Preferred scene callbacks: `SceneInit()`, `SceneUpdate(float dt)`, `SceneShutdown()`.
- Legacy callbacks still work: `Init()`, `Update(float dt)`, `Shutdown()`.
## ScriptComponent
- ScriptComponent runs a per-entity script module.
- Set the script path in the Inspector and toggle enabled.
- Preferred callbacks: `EntityInit(uint entity)`, `EntityUpdate(uint entity, float dt)`, `EntityShutdown(uint entity)`.
- Legacy callbacks still work: `Init(uint entity)`, `Update(uint entity, float dt)`, `Shutdown(uint entity)`.
- Subject to change
+2 -3
View File
@@ -6,7 +6,6 @@ Edit markdown files here and push to the repository; CI will mirror them to the
## Pages
- [Scripting](Scripting.md) — scripting API and examples
- [Usage](Usage.md) — getting started and build instructions
- [Scenegraph](Scenegraph.md) — hierarchy components and scene format
- [Editor](Editor.md) - details on how to use the editor
## 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).
+124
View File
@@ -0,0 +1,124 @@
# Scenegraph
This project uses a lightweight hierarchy component to model parent/child relationships in ECS.
The scenegraph is EnTT-friendly and uses a linked-list structure for children.
## Components
### `Hierarchy`
Each entity can have a `Hierarchy` component with these fields:
- `parent` — parent entity or `null`
- `first` — first child entity
- `prev` / `next` — sibling links
This mirrors the common EnTT scenegraph pattern and avoids storing vectors of children inside a component.
### `ECSTransform` (local)
Stores local position/scale and a Y-axis rotation (radians).
### `WorldTransform` (computed)
Computed each frame from the hierarchy and local transform. Rendering uses `WorldTransform` so children move with parents.
## Update Order
1. Systems update local transforms (e.g., velocity system).
2. `UpdateWorldTransforms()` traverses the hierarchy and computes `WorldTransform`.
3. Rendering draws from `WorldTransform` + `ModelRenderer`.
## Script API
```angelscript
ECS::SetParent(child, parent);
ECS::ClearParent(child);
uint p = ECS::GetParent(child);
bool has = ECS::HasParent(child);
```
## Scene File Format (TOML)
Scenes are stored in `scenes/*.toml` using a minimal TOML-like structure. Example:
```toml
scene_script = "scripts/scene.as"
[[entity]]
id = "root"
tag = "Root"
position = [0.0, 0.0, 0.0]
rotation_deg = 0.0
scale = [1.0, 1.0, 1.0]
[[entity]]
id = "parent"
parent = "root"
tag = "Parent"
position = [2.0, 0.0, 0.0]
model = "cube"
model_asset = "unit_cube"
model_size = [1.0, 1.0, 1.0]
color = 0x00FF00FF
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
shader_asset = "toon"
texture = "assets/textures/grid.png"
texture_asset = "grid"
uv_scale = [2.0, 2.0]
uv_offset = [0.0, 0.0]
material_asset = "toon_mat"
[[entity]]
id = "child"
parent = "parent"
tag = "Child"
position = [0.0, 1.25, 0.0]
model = "sphere"
radius = 0.5
color = 0xFF0000FF
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
```
### Supported Keys
- `scene_script` (string) — scene-level script path (outside entity blocks)
- `id` (string) — unique identifier for parent links
- `parent` (string) — parent entity id
- `tag` (string) — `Tag` component value
- `position` (vec3)
- `rotation` (radians) or `rotation_deg` (degrees)
- `scale` (vec3)
- `model` — `cube`, `sphere`, `plane`, or file path
- `model_asset` (string) — asset key for models
- `model_id` (int) — numeric model id
- `model_size` (vec3) for cubes/planes
- `radius`, `rings`, `slices` for spheres
- `color` — hex `0xRRGGBBAA` (matches script colors)
- `outline_size` — optional model outline
- `shader_vs`, `shader_fs` — optional shader paths
- `shader_asset` (string) — asset key for shaders
- `shader_id` (int) — numeric shader id
- `texture` (string) — texture path
- `texture_asset` (string) — asset key for textures
- `texture_id` (int) — numeric texture id
- `material_asset` (string) — asset key for materials
- `material_id` (int) — numeric material id
- `uv_scale` (vec2)
- `uv_offset` (vec2)
- `uv_rotation` (radians)
- `script` (string) — per-entity script path
- `script_enabled` (bool)
- `script_class` (string)
- `script_var_*` (string) — per-field overrides for script-editable values
## Loading a Scene
```angelscript
uint root = Scene::Load("scenes/demo.toml");
```
`Scene::Load` returns a root entity that parents all top-level entities. Use `ECS::IsValid(root)` to check success on the scene load.
## Editor
Run the app with `--editor` to open the ImGui editor. The **Scene** window shows the hierarchy tree, supports drag-and-drop reparenting, and lets you create/delete entities. The **Inspector** window shows components for the selected entity and allows live editing.
+99 -1
View File
@@ -6,8 +6,106 @@ This page is a starting point for scripting documentation.
- Scripts live in `scripts/` and are executed by AngelScript.
- Predefined API and engine bindings are declared in `scripts/as.predefined`.
## Input Actions
- Query input with `IsActionPressed`, `IsActionDown`, `IsActionReleased`, and `GetActionStrength`.
- Register and bind at runtime with `RegisterAction`, `BindKeyboard`, `BindMouseButton`, `BindGamepadButton`, and `BindGamepadAxis`.
- Enums: `Key`, `MouseButton`, `GamepadButton`, `GamepadAxis`.
Example:
```angelscript
RegisterAction("boost");
BindKeyboard("boost", Key::LEFT_CONTROL);
BindGamepadButton("boost", 0, GamepadButton::RIGHT_TRIGGER_1);
RegisterAction("zoom");
BindGamepadAxis("zoom", 0, GamepadAxis::RIGHT_TRIGGER, 0.2f, false);
```
## ECS Texture + UV
- Texture and UV helpers live under the `ECS::` namespace.
- Texture: `AddTexture`, `SetTextureId`, `GetTextureId`, `HasTexture`, `RemoveTexture`.
- UV Transform: `AddUVTransform`, `SetUVScale`, `SetUVOffset`, `SetUVRotation`, `HasUVTransform`, `RemoveUVTransform`.
Example:
```angelscript
uint e = ECS::CreateEntity();
ECS::AddTexture(e, texId);
ECS::AddUVTransform(e, 2.0f, 2.0f, 0.0f, 0.0f, 0.0f);
ECS::SetUVOffset(e, 0.25f, 0.0f);
```
## Asset Manager
- Asset loading can be keyed for editor and scene usage.
- Use `Asset::Load*` helpers to create assets and reference them by key.
Example:
```angelscript
Asset::LoadTexture("grid", "assets/textures/grid.png");
Asset::LoadShader("toon", "shaders/toon.vs", "shaders/toon.fs");
Asset::LoadCube("unit_cube", 1.0f, 1.0f, 1.0f);
Asset::CreateMaterial("toon_mat", Asset::GetShaderId("toon"), 255, 255, 255, 255);
uint e = ECS::CreateEntity();
ECS::AddModelRenderer(e, int(Asset::GetModelId("unit_cube")), 0xFFFFFFFF, 0.0f);
ECS::SetModelRendererShader(e, Asset::GetShaderId("toon"));
ECS::AddTexture(e, Asset::GetTextureId("grid"));
ECS::AddMaterial(e, Asset::GetMaterialId("toon_mat"));
```
## Key-Value Store (SNKV)
- Databases are stored under `user/db/<name>.db`.
- `Open()` returns a handle (0 on failure). Use that handle for all operations.
- TTL values are in milliseconds and are applied relative to the current time.
Example:
```angelscript
uint db = KV::Open("session_cache");
if (db != 0) {
KV::Put(db, "player", "nick");
// Expire in 30 seconds
KV::PutTtl(db, "token", "abc123", 30000);
string value;
int64 remainingMs = 0;
if (KV::GetTtl(db, "token", value, remainingMs)) {
Print("token=" + value + " remaining=" + remainingMs);
}
KV::Close(db);
}
```
## Globals (in-memory)
- Cross-scene, in-memory store for runtime state.
- Uses the same typed overloads as the KV API.
- Values persist while the app is running, but are not saved to disk.
- `Get()` returns `false` if the key does not exist or the type does not match.
Example:
```angelscript
Globals::Set("difficulty", 2);
Globals::Set("player_name", "Nina");
int diff = 0;
string name;
if (Globals::Get("difficulty", diff)) {
Print("difficulty=" + diff);
}
if (Globals::Get("player_name", name)) {
Print("player=" + name);
}
```
## Examples
- `scripts/game.as` — demo showing ECS usage, rendering and input actions.
- `scripts/game.as` — demo showing ECS usage, rendering, scenegraph and input actions.
## How to document APIs
Add Markdown pages in the `docs/` folder. Use relative links and include code fences for examples.
+16 -3
View File
@@ -52,6 +52,14 @@ ctest --test-dir build --output-on-failure
- The engine watches the scripts directory and will hot-reload scripts when changed.
- Predefined API is declared in `scripts/as.predefined`.
## Filesystem / Writable Data
- `user/` is the only writable directory in runtime mode.
- In editor mode (`--editor`), scene files in `scenes/` and the asset registry at
`assets/registry.toml` are writable.
- Scene saves are blocked outside editor mode.
- Logs are written to `user/log.txt`.
## 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.
@@ -64,8 +72,8 @@ 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`.
1. The workflow `.gitea/workflows/sync-docs-to-wiki.yml` runs on pushes that affect `docs/**`.
2. The workflow requires an SSH private key in repository secrets named `WIKI_SSH_KEY`.
To create the deploy key and CI secret:
@@ -91,5 +99,10 @@ Notes:
## 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`.
- 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 `WIKI_SSH_KEY`.
- To debug locally, run the `scripts/sync_wiki.sh` script and watch for `git push` errors.
## Scenes
Scenes are stored as TOML files in `scenes/`. Use `Scene::Load("scenes/demo.toml")` from scripts to create entities with hierarchy relationships.
See [Scenegraph](Scenegraph.md) for the format and examples.
File diff suppressed because it is too large Load Diff
Vendored Submodule
+1
Submodule external/physfs added at 49cfd5fdd4
Vendored Submodule
+1
Submodule external/snkv added at a9370019a7
+58 -21
View File
@@ -9,40 +9,40 @@ Size=400,400
Collapsed=0
[Window][Game Window]
Pos=8,28
Size=1264,580
Pos=232,28
Size=663,507
Collapsed=0
DockId=0x00000001,0
DockId=0x00000004,0
[Window][Log Viewer]
Pos=8,610
Size=1264,102
Pos=8,537
Size=1264,175
Collapsed=0
DockId=0x00000002,0
[Window][##TOAST0]
Pos=1060,637
Size=200,63
Pos=1609,928
Size=225,63
Collapsed=0
[Window][##TOAST1]
Pos=1060,564
Size=200,63
Pos=1035,564
Size=225,63
Collapsed=0
[Window][##TOAST2]
Pos=1064,491
Size=196,63
Pos=1675,814
Size=225,63
Collapsed=0
[Window][##TOAST3]
Pos=1064,418
Size=196,63
Pos=1035,418
Size=225,63
Collapsed=0
[Window][##TOAST4]
Pos=1064,345
Size=196,63
Pos=1068,345
Size=192,63
Collapsed=0
[Window][Scene (0 entities)]
@@ -71,12 +71,49 @@ Size=71,108
Collapsed=0
[Window][##TOAST5]
Pos=1064,272
Size=196,63
Pos=1071,272
Size=189,63
Collapsed=0
[Docking][Data]
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
[Window][Inspector]
Pos=897,28
Size=375,507
Collapsed=0
DockId=0x00000006,0
[Window][Scene]
Pos=8,28
Size=222,507
Collapsed=0
DockId=0x00000003,0
[Window][New Scene]
Pos=512,318
Size=255,84
Collapsed=0
[Window][Save Scene]
Pos=807,454
Size=306,82
Collapsed=0
[Window][Load Scene]
Pos=807,454
Size=306,82
Collapsed=0
[Window][Assets]
Pos=8,761
Size=1904,274
Collapsed=0
DockId=0x00000002,1
[Docking][Data]
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=Y Selected=0x27A02DAA
DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,507 Split=X
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=887,684 Split=X
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=222,684 Selected=0xE601B12F
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=663,684 CentralNode=1 Selected=0x27A02DAA
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=375,684 Selected=0x36DC96AB
DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,175 Selected=0xBEDDA0C1
+53 -2
View File
@@ -6,8 +6,12 @@
#include "ShaderManager.h"
#include "ModelManager.h"
#include "MaterialManager.h"
#include "AssetManager.h"
#include "TextureManager.h"
#include "InputManager.h"
#include <entt.hpp>
#include <string>
#include <unordered_map>
class Application {
public:
@@ -21,12 +25,42 @@ public:
bool IsEditorEnabled() const { return enableEditor; }
bool HasScriptCompilationError() const { return scriptCompilationError; }
entt::registry& GetRegistry() { return registry; }
AssetManager& GetAssetManager() { return assetManager; }
InputManager& GetInputManager() { return inputManager; }
Camera3D& GetEditorCamera() { return editorCamera; }
bool IsUsingEditorCamera() const { return useEditorCamera; }
void SetUseEditorCamera(bool enabled) { useEditorCamera = enabled; }
private:
struct EntityScriptModule {
std::string path;
std::string moduleName;
asIScriptModule* module = nullptr;
// For object-instance based entity scripts we cache the object type
// (stored opaquely here to avoid requiring the AngelScript headers
// in all translation units) and the object-methods (Init/Update/Shutdown)
// to avoid repeated lookups.
asITypeInfo* objectType = nullptr;
std::string objectTypeName;
asIScriptFunction* initFunc = nullptr; // object method: Init(uint)
asIScriptFunction* updateFunc = nullptr; // object method: Update(uint,float)
asIScriptFunction* shutdownFunc = nullptr;// object method: Shutdown(uint)
bool valid = false;
};
struct SceneScriptInstance {
asIScriptObject* instance = nullptr;
std::string className;
asIScriptFunction* initFunc = nullptr;
asIScriptFunction* updateFunc = nullptr;
asIScriptFunction* shutdownFunc = nullptr;
};
ScriptEngine scriptEngine;
HotReload* hotReload;
bool scriptCompilationError;
FILE* logFile;
struct PHYSFS_File* logFile;
GuiManager guiManager;
bool enableEditor;
bool queueShutdown = false;
@@ -39,16 +73,33 @@ private:
ShaderManager shaderManager;
ModelManager modelManager;
MaterialManager materialManager;
AssetManager assetManager;
TextureManager textureManager;
InputManager inputManager;
std::unordered_map<std::string, EntityScriptModule> entityScriptModules;
std::unordered_map<entt::entity, std::string> entityScriptBindings;
unsigned int entityScriptModuleCounter = 0;
std::string currentSceneScriptPath;
SceneScriptInstance sceneScriptInstance;
Camera3D editorCamera;
bool useEditorCamera = false;
static const int WINDOW_WIDTH = 1280;
static const int WINDOW_HEIGHT = 720;
static const int TARGET_FPS = 60;
static const char* WINDOW_TITLE;
static const char* SCRIPT_FILE;
void Update(float deltaTime);
void UpdateSystems(float deltaTime);
void UpdateEntityScripts(float deltaTime);
void ShutdownEntityScripts();
bool ProcessSceneScriptChanges();
void ReloadEntityScriptModules();
EntityScriptModule* GetEntityScriptModule(const std::string& path);
void InitializeSceneScriptInstance();
void ShutdownSceneScriptInstance();
void CallSceneUpdate(float deltaTime);
void RenderScene();
void Draw();
bool RunAutoloadScript(bool abortOnFailure);
};
+144
View File
@@ -0,0 +1,144 @@
#pragma once
#include "raylib.h"
#include <string>
#include <unordered_map>
#include <vector>
class ModelManager;
class ShaderManager;
class MaterialManager;
class TextureManager;
struct AssetEntry {
unsigned int id = 0;
std::string source;
};
struct TextureDefinition {
std::string path;
};
struct ShaderDefinition {
std::string vsPath;
std::string fsPath;
};
struct ModelDefinition {
enum class Type {
Path,
Primitive,
Heightmap,
Cubicmap,
Mesh
};
Type type = Type::Path;
std::string path;
std::string primitive;
std::vector<float> params;
};
struct MaterialDefinition {
std::string shaderKey;
unsigned int shaderId = 0;
Color albedo = {255, 255, 255, 255};
bool hasShaderKey = false;
bool hasShaderId = false;
};
class AssetManager
{
public:
AssetManager();
~AssetManager();
void SetManagers(ModelManager* modelMgr, ShaderManager* shaderMgr,
MaterialManager* materialMgr, TextureManager* textureMgr);
// Register an existing asset ID under a key (no loading performed).
bool RegisterTexture(const std::string& key, unsigned int id, const std::string& source = std::string());
bool RegisterShader(const std::string& key, unsigned int id, const std::string& source = std::string());
bool RegisterModel(const std::string& key, unsigned int id, const std::string& source = std::string());
bool RegisterMaterial(const std::string& key, unsigned int id, const std::string& source = std::string());
// Load helpers that also register by key.
unsigned int LoadTexture(const std::string& key, const std::string& path);
unsigned int LoadShader(const std::string& key, const std::string& vsPath, const std::string& fsPath);
unsigned int LoadModel(const std::string& key, const std::string& path);
unsigned int LoadCube(const std::string& key, float width, float height, float length);
unsigned int LoadPoly(const std::string& key, int sides, float radius);
unsigned int LoadSphere(const std::string& key, float radius, int rings, int slices);
unsigned int LoadHemiSphere(const std::string& key, float radius, int rings, int slices);
unsigned int LoadPlane(const std::string& key, float width, float length, int resX, int resZ);
unsigned int LoadCylinder(const std::string& key, float radius, float height, int slices);
unsigned int LoadCone(const std::string& key, float radius, float height, int slices);
unsigned int LoadTorus(const std::string& key, float radius, float size, int radSeg, int sides);
unsigned int LoadKnot(const std::string& key, float radius, float size, int radSeg, int sides);
unsigned int LoadHeightmap(const std::string& key, const std::string& heightmapPath, float sizeX, float sizeY, float sizeZ);
unsigned int LoadCubicmap(const std::string& key, const std::string& cubicmapPath, float sizeX, float sizeY, float sizeZ);
unsigned int LoadFromMesh(const std::string& key, Mesh mesh, const std::string& source = std::string());
unsigned int CreateMaterial(const std::string& key, unsigned int shaderId, Color albedo);
bool ApplyMeshTangents(const std::string& key, int meshIndex = 0);
// Unload by key (removes registry entries).
bool UnloadTexture(const std::string& key);
bool UnloadShader(const std::string& key);
bool UnloadModel(const std::string& key);
bool UnloadMaterial(const std::string& key);
// Query by key.
unsigned int GetTextureId(const std::string& key) const;
unsigned int GetShaderId(const std::string& key) const;
unsigned int GetModelId(const std::string& key) const;
unsigned int GetMaterialId(const std::string& key) const;
// Query by id.
const std::string& GetTextureKey(unsigned int id) const;
const std::string& GetShaderKey(unsigned int id) const;
const std::string& GetModelKey(unsigned int id) const;
const std::string& GetMaterialKey(unsigned int id) const;
// Asset listing for editor UI.
std::vector<std::string> ListTextureKeys() const;
std::vector<std::string> ListShaderKeys() const;
std::vector<std::string> ListModelKeys() const;
std::vector<std::string> ListMaterialKeys() const;
// Source info (path or descriptive string).
std::string GetTextureSource(const std::string& key) const;
std::string GetShaderSource(const std::string& key) const;
std::string GetModelSource(const std::string& key) const;
std::string GetMaterialSource(const std::string& key) const;
// Asset registry persistence.
bool SaveRegistry(const std::string& path) const;
bool LoadRegistry(const std::string& path);
// Clear registry entries (does not unload underlying resources).
void Clear();
private:
ModelManager* modelManager = nullptr;
ShaderManager* shaderManager = nullptr;
MaterialManager* materialManager = nullptr;
TextureManager* textureManager = nullptr;
std::unordered_map<std::string, AssetEntry> textureAssets;
std::unordered_map<std::string, AssetEntry> shaderAssets;
std::unordered_map<std::string, AssetEntry> modelAssets;
std::unordered_map<std::string, AssetEntry> materialAssets;
std::unordered_map<std::string, TextureDefinition> textureDefs;
std::unordered_map<std::string, ShaderDefinition> shaderDefs;
std::unordered_map<std::string, ModelDefinition> modelDefs;
std::unordered_map<std::string, MaterialDefinition> materialDefs;
std::unordered_map<unsigned int, std::string> textureIds;
std::unordered_map<unsigned int, std::string> shaderIds;
std::unordered_map<unsigned int, std::string> modelIds;
std::unordered_map<unsigned int, std::string> materialIds;
const std::string& GetKeyForId(const std::unordered_map<unsigned int, std::string>& map, unsigned int id) const;
std::vector<std::string> ListKeys(const std::unordered_map<std::string, AssetEntry>& map) const;
};
+85 -9
View File
@@ -1,20 +1,31 @@
#pragma once
#include "raylib.h"
#include "raymath.h"
#include "entt.hpp"
#include <string>
#include <unordered_map>
// ECSTransform component - position, rotation, and scale
// ECSTransform component - position, rotation, scale, and cached local matrix
struct ECSTransform {
Vector3 position;
Vector3 rotation; // Euler rotation (radians)
Vector3 scale;
float rotation; // Rotation around Y axis in radians
Matrix localMatrix;
ECSTransform()
: position{0.0f, 0.0f, 0.0f}
, rotation{0.0f, 0.0f, 0.0f}
, scale{1.0f, 1.0f, 1.0f}
, rotation(0.0f) {}
, localMatrix(MatrixIdentity()) {}
ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rot = 0.0f)
: position(pos), scale(scl), rotation(rot) {}
ECSTransform(Vector3 pos, Vector3 scl, Vector3 rot)
: position(pos), rotation(rot), scale(scl), localMatrix(MatrixIdentity()) {}
ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rotY = 0.0f)
: position(pos)
, rotation{0.0f, rotY, 0.0f}
, scale(scl)
, localMatrix(MatrixIdentity()) {}
};
// Velocity component - for movement
@@ -30,23 +41,46 @@ struct Velocity {
: linear(lin), angular(ang) {}
};
// Sprite component - visual representation
struct Sprite {
// ModelRenderer component - 3D model rendering data
struct ModelRenderer {
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()
ModelRenderer()
: modelId(0)
, color(WHITE)
, outlineSize(0.0f)
, shaderId(0) {}
Sprite(int model, Color col = WHITE, float outline = 0.0f, unsigned int shader = 0)
ModelRenderer(int model, Color col = WHITE, float outline = 0.0f, unsigned int shader = 0)
: modelId(model), color(col), outlineSize(outline), shaderId(shader) {}
};
// Texture component - material texture reference
struct TextureComponent {
unsigned int textureId;
TextureComponent() : textureId(0) {}
TextureComponent(unsigned int id) : textureId(id) {}
};
// UV transform component - tiling and offset for texture sampling
struct UVTransform {
Vector2 scale;
Vector2 offset;
float rotation;
UVTransform()
: scale{1.0f, 1.0f}
, offset{0.0f, 0.0f}
, rotation(0.0f) {}
UVTransform(Vector2 s, Vector2 o = {0.0f, 0.0f}, float r = 0.0f)
: scale(s), offset(o), rotation(r) {}
};
// Tag component - simple string identifier
struct Tag {
std::string name;
@@ -55,6 +89,48 @@ struct Tag {
Tag(const std::string& n) : name(n) {}
};
// ScriptComponent - per-entity script reference
struct ScriptComponent {
std::string scriptPath;
bool enabled = true;
bool started = false;
// Optional: name of the script class to instantiate. If empty, engine
// will attempt to pick a reasonable object type from the module.
std::string className;
// Opaque pointer to the script object instance (asIScriptObject*).
void* scriptInstance = nullptr;
// Persisted overrides for editable script fields (name -> raw value string).
std::unordered_map<std::string, std::string> scriptOverrides;
};
// SceneEntity component - marks entities owned by the scene
struct SceneEntity {
bool persistent = false;
std::string id;
};
// Hierarchy component - parent/child links using EnTT-friendly linked list
struct Hierarchy {
entt::entity parent{entt::null};
entt::entity first{entt::null};
entt::entity prev{entt::null};
entt::entity next{entt::null};
};
// WorldTransform component - computed from hierarchy + local transform
struct WorldTransform {
Vector3 position;
Vector3 rotation; // Euler rotation (radians)
Vector3 scale;
Matrix worldMatrix;
WorldTransform()
: position{0.0f, 0.0f, 0.0f}
, rotation{0.0f, 0.0f, 0.0f}
, scale{1.0f, 1.0f, 1.0f}
, worldMatrix(MatrixIdentity()) {}
};
// Camera3D component - camera settings
struct Camera3DComponent {
Vector3 position;
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <string>
#include <chrono>
#include <filesystem>
class HotReload {
public:
+155
View File
@@ -0,0 +1,155 @@
#pragma once
#include "raylib.h"
#include "raymath.h"
#include "imgui.h"
#define IMVIEWGUIZMO_VEC3 Vector3
#define IMVIEWGUIZMO_VEC4 Vector4
#define IMVIEWGUIZMO_QUAT Quaternion
#define IMVIEWGUIZMO_MAT4 Matrix
namespace ImViewGuizmo {
namespace GizmoMath {
inline float get_vec_comp(const Vector3& v, int index) {
switch (index) {
case 0: return v.x;
case 1: return v.y;
default: return v.z;
}
}
inline float get_vec_comp(const Vector4& v, int index) {
switch (index) {
case 0: return v.x;
case 1: return v.y;
case 2: return v.z;
default: return v.w;
}
}
inline Vector3 make_vec3(float x, float y, float z) { return {x, y, z}; }
inline Vector4 make_vec4(float x, float y, float z, float w) { return {x, y, z, w}; }
inline Quaternion angleAxis(float angle, const Vector3& axis) {
return QuaternionFromAxisAngle(axis, angle);
}
inline Quaternion quatLookAt(const Vector3& dir, const Vector3& up) {
Vector3 forward = Vector3Normalize(dir);
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, forward));
Vector3 correctedUp = Vector3CrossProduct(forward, right);
Matrix mat = MatrixIdentity();
mat.m0 = right.x;
mat.m1 = right.y;
mat.m2 = right.z;
mat.m4 = correctedUp.x;
mat.m5 = correctedUp.y;
mat.m6 = correctedUp.z;
mat.m8 = forward.x;
mat.m9 = forward.y;
mat.m10 = forward.z;
return QuaternionFromMatrix(mat);
}
inline Matrix mat4_identity() { return MatrixIdentity(); }
inline Vector3 cross(const Vector3& a, const Vector3& b) {
return Vector3CrossProduct(a, b);
}
inline float dot(const Vector3& a, const Vector3& b) {
return Vector3DotProduct(a, b);
}
inline float dot(const Quaternion& a, const Quaternion& b) {
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
}
inline float length(const Vector3& v) {
return Vector3Length(v);
}
inline float length2(const Vector3& v) {
return v.x * v.x + v.y * v.y + v.z * v.z;
}
inline Vector3 normalize(const Vector3& v) {
return Vector3Normalize(v);
}
inline Vector3 mix(const Vector3& a, const Vector3& b, float t) {
return Vector3Lerp(a, b, t);
}
inline Vector3 add_vv(const Vector3& a, const Vector3& b) {
return Vector3Add(a, b);
}
inline Vector3 subtract_vv(const Vector3& a, const Vector3& b) {
return Vector3Subtract(a, b);
}
inline Vector3 multiply_vf(const Vector3& v, float f) {
return Vector3Scale(v, f);
}
inline Matrix mat4_cast(const Quaternion& q) {
return QuaternionToMatrix(q);
}
inline Matrix transpose(const Matrix& m) {
return MatrixTranspose(m);
}
inline Vector3 get_matrix_col(const Matrix& m, int col) {
switch (col) {
case 0: return {m.m0, m.m1, m.m2};
case 1: return {m.m4, m.m5, m.m6};
case 2: return {m.m8, m.m9, m.m10};
default: return {m.m12, m.m13, m.m14};
}
}
inline void set_matrix_col(Matrix& m, int col, const Vector4& v) {
switch (col) {
case 0:
m.m0 = v.x; m.m1 = v.y; m.m2 = v.z; m.m3 = v.w;
break;
case 1:
m.m4 = v.x; m.m5 = v.y; m.m6 = v.z; m.m7 = v.w;
break;
case 2:
m.m8 = v.x; m.m9 = v.y; m.m10 = v.z; m.m11 = v.w;
break;
default:
m.m12 = v.x; m.m13 = v.y; m.m14 = v.z; m.m15 = v.w;
break;
}
}
inline Quaternion multiply_qq(const Quaternion& a, const Quaternion& b) {
return QuaternionMultiply(a, b);
}
inline Vector3 multiply_qv(const Quaternion& q, const Vector3& v) {
return Vector3RotateByQuaternion(v, q);
}
inline Matrix multiply_mm(const Matrix& a, const Matrix& b) {
return MatrixMultiply(a, b);
}
inline Vector4 multiply_mv4(const Matrix& m, const Vector4& v) {
Vector4 result;
result.x = m.m0 * v.x + m.m4 * v.y + m.m8 * v.z + m.m12 * v.w;
result.y = m.m1 * v.x + m.m5 * v.y + m.m9 * v.z + m.m13 * v.w;
result.z = m.m2 * v.x + m.m6 * v.y + m.m10 * v.z + m.m14 * v.w;
result.w = m.m3 * v.x + m.m7 * v.y + m.m11 * v.z + m.m15 * v.w;
return result;
}
}
} // namespace ImViewGuizmo
#include "ImViewGuizmo.h"
+1
View File
@@ -52,6 +52,7 @@ public:
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);
void ClearAllActions();
// Input queries (called per frame)
bool IsActionPressed(const std::string& actionName) const;
+30
View File
@@ -4,6 +4,23 @@
#include <unordered_map>
#include <string>
enum class ModelSourceType {
None,
Path,
Cube,
Sphere,
Plane
};
struct ModelSource {
ModelSourceType type = ModelSourceType::None;
std::string path;
Vector3 size{1.0f, 1.0f, 1.0f};
float radius = 0.5f;
int rings = 16;
int slices = 16;
};
class ModelManager
{
public:
@@ -18,17 +35,30 @@ public:
// Returns a unique ID for the model, or 0 on failure
unsigned int LoadFromMesh(Mesh mesh);
// Load a model from a generated cube mesh
unsigned int LoadCube(float width, float height, float length);
// Load a model from a generated sphere mesh
unsigned int LoadSphere(float radius, int rings, int slices);
// Load a model from a generated plane mesh
unsigned int LoadPlane(float width, float length, int resX, int resZ);
// 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);
// Get source metadata for a model if available
const ModelSource* GetSource(unsigned int id) const;
// Unload all models
void UnloadAll();
private:
std::unordered_map<unsigned int, Model> models;
std::unordered_map<unsigned int, ModelSource> modelSources;
unsigned int nextId;
bool unloaded;
};
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <vector>
#include <cstdint>
struct PHYSFS_File;
namespace PhysFSManager {
bool Initialize(const char* argv0);
bool IsInitialized();
void Shutdown();
bool MountPath(const std::string& path, const std::string& mountPoint, bool append = true);
bool UnmountPath(const std::string& path);
void ClearSearchPath();
bool SetWriteDir(const std::string& path);
bool EnsureDir(const std::string& path);
void SetEditorEnabled(bool enabled);
bool Exists(const std::string& path);
bool IsDirectory(const std::string& path);
std::string GetRealDir(const std::string& path);
std::int64_t GetModTime(const std::string& path);
std::vector<std::string> ListFiles(const std::string& path);
bool ReadTextFile(const std::string& path, std::string& out);
bool WriteTextFile(const std::string& path, const std::string& contents);
PHYSFS_File* OpenRead(const std::string& path);
PHYSFS_File* OpenWrite(const std::string& path);
void Close(PHYSFS_File* file);
} // namespace PhysFSManager
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "entt.hpp"
// Attach child to parent (updates linked list)
void AttachChild(entt::registry& registry, entt::entity child, entt::entity parent);
// Detach child from its parent (if any)
void DetachFromParent(entt::registry& registry, entt::entity child);
// Detach all children from a parent (orphans them)
void DetachChildren(entt::registry& registry, entt::entity parent);
// Update WorldTransform for all entities using hierarchy traversal
void UpdateWorldTransforms(entt::registry& registry);
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "entt.hpp"
#include <string>
class ModelManager;
class ShaderManager;
class TextureManager;
class AssetManager;
// Set global context used by scene loading
void SetSceneContext(entt::registry* registry, ModelManager* modelManager, ShaderManager* shaderManager, TextureManager* textureManager);
void SetSceneAssetManager(AssetManager* assetManager);
// Load a scene from a TOML-like file. Returns the root entity (or entt::null on failure).
entt::entity LoadSceneFromFile(const std::string& path, bool clearExisting);
// Save the current scene to a TOML-like file. Returns true on success.
bool SaveSceneToFile(const std::string& path);
// Clear all entities from the registry
void ClearScene();
// Scene script path helpers
void SetSceneScriptPath(const std::string& path);
const std::string& GetSceneScriptPath();
bool ConsumeSceneScriptPath(std::string& outPath);
+9
View File
@@ -4,6 +4,11 @@
#include <unordered_map>
#include <string>
struct ShaderSource {
std::string vsPath;
std::string fsPath;
};
class ShaderManager
{
public:
@@ -20,11 +25,15 @@ public:
// Get a shader by ID (returns nullptr if not found)
Shader* Get(unsigned int id);
// Get source metadata for a shader if available
const ShaderSource* GetSource(unsigned int id) const;
// Unload all shaders
void UnloadAll();
private:
std::unordered_map<unsigned int, Shader> shaders;
std::unordered_map<unsigned int, ShaderSource> shaderSources;
unsigned int nextId;
bool unloaded;
};
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include "raylib.h"
#include <string>
#include <unordered_map>
struct TextureSource {
std::string path;
};
class TextureManager
{
public:
TextureManager();
~TextureManager();
// Load a texture from a file
// Returns a unique ID for the texture, or 0 on failure
unsigned int Load(const std::string& path);
// Unload a texture by ID
void Unload(unsigned int id);
// Get a texture by ID (returns nullptr if not found)
Texture2D* Get(unsigned int id);
// Get source metadata for a texture if available
const TextureSource* GetSource(unsigned int id) const;
// Unload all textures
void UnloadAll();
private:
std::unordered_map<unsigned int, Texture2D> textures;
std::unordered_map<unsigned int, TextureSource> textureSources;
unsigned int nextId;
bool unloaded;
};
+126
View File
@@ -5,6 +5,12 @@
#include "gui/fa-solid-900.h"
#include "gui/ImGuiNotify.hpp"
#include "LogWindow.h" // Include the new LogWindow class
#include "raylib.h"
#include <entt.hpp>
#include <string>
#include <unordered_map>
#include <vector>
#include <cstdint>
class Application;
@@ -15,15 +21,135 @@ public:
void Initialize(Application* application);
void Render(RenderTexture2D& renderTexture);
void ApplyRenderTextureResize(RenderTexture2D& renderTexture);
void Shutdown();
private:
enum class AssetUnloadType {
None,
Texture,
Shader,
Model,
Material
};
void SetupDockspace(RenderTexture2D& renderTexture);
void RenderNotifications();
void RenderErrorBanner();
void SetTheme();
void RenderSceneWindow();
void RenderAssetWindow();
void RenderInspectorWindow();
void RenderEntityNode(entt::entity entity);
bool IsEntityAncestor(entt::entity ancestor, entt::entity entity) const;
const char* GetEntityLabel(entt::entity entity);
struct ScriptEditableFieldMeta {
std::string name;
std::string type;
std::string group;
std::string description;
bool hasRange = false;
float rangeMin = 0.0f;
float rangeMax = 1.0f;
std::vector<std::string> options;
};
struct ScriptEditableCache {
std::string className;
std::vector<ScriptEditableFieldMeta> fields;
std::int64_t lastWriteTime = 0;
bool hasWriteTime = false;
};
enum class ScriptValueKind { Float, Int, Bool, String };
struct ScriptEditableValue {
ScriptValueKind kind = ScriptValueKind::Float;
bool hasValue = false;
bool hasBaseline = false;
float f = 0.0f;
float baselineF = 0.0f;
int i = 0;
int baselineI = 0;
bool b = false;
bool baselineB = false;
std::string s;
std::string baselineS;
};
void SyncTagBuffer(entt::entity entity);
void SyncScriptPathBuffer(entt::entity entity);
void RenderScriptEditableControls(entt::entity entity);
ScriptEditableCache &GetOrParseEditableCache(const std::string &path, const std::string &className);
bool showLogWindow = true;
bool showSceneWindow = true;
bool showInspectorWindow = true;
bool showAssetWindow = false;
Application *app;
LogWindow logWindow; // Add LogWindow as a member
entt::entity selectedEntity = entt::null;
entt::entity tagBufferEntity = entt::null;
char tagBuffer[128] = {0};
entt::entity scriptPathEntity = entt::null;
char scriptPathBuffer[260] = {0};
char sceneScriptPathBuffer[260] = {0};
std::string lastSceneScriptPath;
ImVec2 desiredRenderSize = ImVec2(0.0f, 0.0f);
ImGuiID mainDockspaceId = 0;
bool requestSceneNew = false;
bool requestSceneLoad = false;
bool requestSceneSave = false;
char sceneLoadPath[260] = {0};
char sceneSavePath[260] = {0};
int addComponentIndex = 0;
char textureKeyBuffer[128] = {0};
char texturePathBuffer[260] = {0};
int textureUnloadIndex = 0;
char shaderKeyBuffer[128] = {0};
char shaderVsPathBuffer[260] = {0};
char shaderFsPathBuffer[260] = {0};
int shaderUnloadIndex = 0;
char modelKeyBuffer[128] = {0};
char modelPathBuffer[260] = {0};
char modelHeightmapPathBuffer[260] = {0};
char modelCubicmapPathBuffer[260] = {0};
int modelUnloadIndex = 0;
int modelPrimitiveIndex = 0;
float modelWidth = 1.0f;
float modelHeight = 1.0f;
float modelLength = 1.0f;
float modelRadius = 0.5f;
int modelRings = 16;
int modelSlices = 16;
int modelResX = 1;
int modelResZ = 1;
int modelRadSeg = 16;
int modelSides = 8;
int modelPolySides = 6;
float modelSizeX = 1.0f;
float modelSizeY = 1.0f;
float modelSizeZ = 1.0f;
int modelAutoKeyCounter = 0;
char materialKeyBuffer[128] = {0};
int materialUnloadIndex = 0;
int materialShaderIndex = 0;
int materialShaderId = 0;
float materialAlbedo[4] = {1.0f, 1.0f, 1.0f, 1.0f};
AssetUnloadType pendingAssetUnloadType = AssetUnloadType::None;
std::string pendingAssetUnloadKey;
Vector3 editorCameraPivot = {0.0f, 0.0f, 0.0f};
Vector3 editorPivotOffset = {0.0f, 0.0f, 0.0f};
entt::entity editorPivotEntity = entt::null;
bool editorPivotInitialized = false;
std::unordered_map<std::string, ScriptEditableCache> scriptEditableCache;
std::unordered_map<entt::entity, std::unordered_map<std::string, ScriptEditableValue>> scriptEditableStaged;
std::unordered_map<entt::entity, void*> scriptEditableInstancePtr;
};
+3 -2
View File
@@ -41,7 +41,7 @@
// Warning: Requires ImGui docking with multi-viewport enabled
#ifndef NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW
#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW true // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window
#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW false // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window
#endif
/**
@@ -502,8 +502,9 @@ namespace ImGui
}
#if NOTIFY_RENDER_LIMIT > 0
if (i > NOTIFY_RENDER_LIMIT)
if (i >= NOTIFY_RENDER_LIMIT)
{
++i;
continue;
}
#endif
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterGlobalsBindings(asIScriptEngine* engine);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterPhysFSBindings(asIScriptEngine* engine);
+4 -1
View File
@@ -4,6 +4,9 @@ class asIScriptEngine;
class ShaderManager;
class ModelManager;
class MaterialManager;
class TextureManager;
class AssetManager;
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr);
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr, TextureManager* textureMgr);
void SetAssetManager(AssetManager* assetMgr);
void RegisterResourceBindings(asIScriptEngine *engine);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterSNKVBindings(asIScriptEngine* engine);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterSceneBindings(asIScriptEngine* engine);
+14
View File
@@ -12,7 +12,10 @@ public:
bool Initialize();
void Shutdown();
bool CompileScript(const std::string& filename);
bool CompileModule(const std::string& moduleName, const std::string& filename, asIScriptModule** outModule);
bool CallModuleFunction(asIScriptModule* module, const std::string& decl);
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
void CallScriptFunction(asIScriptFunction* func, unsigned int entityId, float dt);
void GarbageCollect();
// Add a path used to resolve #include directives in scripts
@@ -24,10 +27,21 @@ public:
void RestoreScriptState(const std::string &state);
asIScriptEngine* GetEngine() const { return engine; }
asIScriptModule* GetCurrentModule() const { return currentModule; }
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
asIScriptFunction* GetInitFunction() const { return initFunc; }
asIScriptFunction* GetShutdownFunction() const { return shutdownFunc; }
// Create and release script object instances for entity scripts
asIScriptObject* CreateObjectInstance(asIScriptModule* module, const std::string& className);
void ReleaseObjectInstance(asIScriptObject* obj);
// Retrieve an object's method by declaration (e.g. "void Update(uint, float)")
asIScriptFunction* GetObjectMethod(asIScriptModule* module, const std::string& className, const std::string& methodDecl);
// Call an object method; method must be an object method (use GetObjectMethod)
void CallObjectMethod(asIScriptFunction* method, asIScriptObject* object, unsigned int entityId, float dt);
// Call a scene object method (Init/Update/Shutdown) without entity id
void CallSceneObjectMethod(asIScriptFunction* method, asIScriptObject* object, float dt = 0.0f);
private:
asIScriptEngine* engine;
asIScriptFunction* updateFunc;
+62
View File
@@ -0,0 +1,62 @@
scene_script = "scripts/demo_scene.as"
[[entity]]
id = "entity_1"
tag = "Root"
position = [0.000, 0.000, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
[[entity]]
id = "entity_2"
parent = "entity_1"
tag = "Parent"
position = [2.000, -1.100, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
model_asset = "Torus"
model_id = 1
color = 0x00FF00FF
outline_size = 0.000
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
script = "scripts/ball.as"
script_var_amplitude = "1.748000"
script_var_speed = "0.699000"
script_var_phase = "0.000000"
[[entity]]
id = "entity_3"
parent = "entity_2"
tag = "Child"
position = [0.500, -2.175, 1.600]
scale = [0.600, 0.600, 0.600]
rotation_deg = [0.000, 0.000, 0.000]
model_asset = "Torus"
model_id = 1
color = 0xFF0000FF
outline_size = 0.000
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
script = "scripts/ball.as"
script_var_amplitude = "1.452000"
[[entity]]
id = "entity_6"
parent = "entity_2"
tag = "Child 2"
position = [0.000, 0.871, 1.900]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
model_asset = "demo_cube"
model = "cube"
model_size = [1.000, 1.000, 1.000]
color = 0x4523BAFF
outline_size = 0.000
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
script = "scripts/ball.as"
script_var_speed = "10.000000"
script_var_phase = "0.472000"
script_var_amplitude = "0.568000"
+49
View File
@@ -0,0 +1,49 @@
scene_script = "scripts/demo_scene.as"
[[entity]]
id = "entity_1048581"
tag = "Light"
position = [0.000, 0.000, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
[[entity]]
id = "entity_1048576"
tag = "Camera"
position = [0.000, 0.000, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
[[entity]]
id = "entity_1048578"
tag = "Root"
position = [0.000, 0.000, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
[[entity]]
id = "entity_1048579"
tag = "Parent"
position = [0.000, 0.000, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
model_asset = "Torus"
model_id = 1
color = 0xFFFFFFFF
outline_size = 0.000
[[entity]]
id = "entity_1048580"
parent = "entity_1048579"
tag = "Child"
position = [0.000, 1.400, 0.000]
scale = [1.000, 1.000, 1.000]
rotation_deg = [0.000, 0.000, 0.000]
model_asset = "demo_cube"
model = "cube"
model_size = [1.000, 1.000, 1.000]
color = 0xC11E1EFF
outline_size = 0.000
shader_vs = "shaders/toon.vs"
shader_fs = "shaders/toon.fs"
+161 -8
View File
@@ -44,7 +44,9 @@ namespace ECS {
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);
void SetRotationEuler(uint entity, float x, float y, float z);
float GetRotation(uint entity);
void GetRotationEuler(uint entity, float &out x, float &out y, float &out z);
bool HasTransform(uint entity);
void RemoveTransform(uint entity);
@@ -55,14 +57,29 @@ namespace ECS {
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);
// ModelRenderer component
void AddModelRenderer(uint entity, int modelId, uint color, float outlineSize = 0.0f);
void SetModelRendererColor(uint entity, uint color);
void SetModelRendererModel(uint entity, int modelId);
void SetModelRendererOutline(uint entity, float outlineSize);
void SetModelRendererShader(uint entity, uint shaderId);
bool HasModelRenderer(uint entity);
void RemoveModelRenderer(uint entity);
// Texture component
void AddTexture(uint entity, uint textureId);
void SetTextureId(uint entity, uint textureId);
uint GetTextureId(uint entity);
bool HasTexture(uint entity);
void RemoveTexture(uint entity);
// UV transform component
void AddUVTransform(uint entity, float scaleX, float scaleY, float offsetX = 0.0f, float offsetY = 0.0f, float rotation = 0.0f);
void SetUVScale(uint entity, float scaleX, float scaleY);
void SetUVOffset(uint entity, float offsetX, float offsetY);
void SetUVRotation(uint entity, float rotation);
bool HasUVTransform(uint entity);
void RemoveUVTransform(uint entity);
// Tag component
void AddTag(uint entity, const string &in name);
@@ -70,6 +87,12 @@ namespace ECS {
void SetTag(uint entity, const string &in name);
bool HasTag(uint entity);
void RemoveTag(uint entity);
// Hierarchy component
void SetParent(uint child, uint parent);
void ClearParent(uint child);
uint GetParent(uint child);
bool HasParent(uint child);
// Camera3D component
void AddCamera3D(uint entity, float px, float py, float pz, float tx, float ty, float tz, float fovy);
@@ -125,6 +148,39 @@ namespace Material {
void SetAlbedo(uint id, uint8 r, uint8 g, uint8 b, uint8 a);
}
// Asset manager (keyed assets)
namespace Asset {
uint LoadTexture(const string &in key, const string &in path);
uint LoadShader(const string &in key, const string &in vsPath, const string &in fsPath);
uint LoadModel(const string &in key, const string &in path);
uint LoadCube(const string &in key, float width, float height, float length);
uint LoadPoly(const string &in key, int sides, float radius);
uint LoadSphere(const string &in key, float radius, int rings, int slices);
uint LoadHemiSphere(const string &in key, float radius, int rings, int slices);
uint LoadPlane(const string &in key, float width, float length, int resX, int resZ);
uint LoadCylinder(const string &in key, float radius, float height, int slices);
uint LoadCone(const string &in key, float radius, float height, int slices);
uint LoadTorus(const string &in key, float radius, float size, int radSeg, int sides);
uint LoadKnot(const string &in key, float radius, float size, int radSeg, int sides);
uint LoadHeightmap(const string &in key, const string &in heightmapPath, float sizeX, float sizeY, float sizeZ);
uint LoadCubicmap(const string &in key, const string &in cubicmapPath, float sizeX, float sizeY, float sizeZ);
uint CreateMaterial(const string &in key, uint shaderId, uint8 r, uint8 g, uint8 b, uint8 a);
bool UnloadTexture(const string &in key);
bool UnloadShader(const string &in key);
bool UnloadModel(const string &in key);
bool UnloadMaterial(const string &in key);
uint GetTextureId(const string &in key);
uint GetShaderId(const string &in key);
uint GetModelId(const string &in key);
uint GetMaterialId(const string &in key);
bool ApplyMeshTangents(const string &in key, int meshIndex = 0);
}
namespace Scene {
uint Load(const string &in path, bool clearExisting = true);
void Clear();
}
namespace Texture {
class Image {
int width;
@@ -178,7 +234,10 @@ 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 BindGamepadButton(const string &in actionName, int gamepadId, int button);
void BindGamepadAxis(const string &in actionName, int gamepadId, int axis, float threshold = 0.5f, bool negative = false);
void ClearBindings(const string &in actionName);
void ClearAllActions();
namespace Key {
const int SPACE = 32;
@@ -204,3 +263,97 @@ namespace MouseButton {
const int MIDDLE = 2;
}
// Key-value store (SNKV)
namespace KV {
uint Open(const string &in name);
bool Close(uint handle);
bool Put(uint handle, const string &in key, const string &in value);
bool Put(uint handle, const string &in key, int value);
bool Put(uint handle, const string &in key, uint value);
bool Put(uint handle, const string &in key, int64 value);
bool Put(uint handle, const string &in key, float value);
bool Put(uint handle, const string &in key, double value);
bool Put(uint handle, const string &in key, bool value);
bool Put(uint handle, const string &in key, const array<uint8> &in value);
bool PutTtl(uint handle, const string &in key, const string &in value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, int value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, uint value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, int64 value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, float value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, double value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, bool value, int64 ttlMs);
bool PutTtl(uint handle, const string &in key, const array<uint8> &in value, int64 ttlMs);
bool Get(uint handle, const string &in key, string &out value);
bool Get(uint handle, const string &in key, int &out value);
bool Get(uint handle, const string &in key, uint &out value);
bool Get(uint handle, const string &in key, int64 &out value);
bool Get(uint handle, const string &in key, float &out value);
bool Get(uint handle, const string &in key, double &out value);
bool Get(uint handle, const string &in key, bool &out value);
bool Get(uint handle, const string &in key, array<uint8> &out value);
bool GetTtl(uint handle, const string &in key, string &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, int &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, uint &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, int64 &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, float &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, double &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, bool &out value, int64 &out remainingMs);
bool GetTtl(uint handle, const string &in key, array<uint8> &out value, int64 &out remainingMs);
bool Delete(uint handle, const string &in key);
bool Exists(uint handle, const string &in key);
bool TtlRemaining(uint handle, const string &in key, int64 &out remainingMs);
int PurgeExpired(uint handle);
}
// In-memory globals (cross-scene, not persisted)
namespace Globals {
bool Set(const string &in key, const string &in value);
bool Set(const string &in key, int value);
bool Set(const string &in key, uint value);
bool Set(const string &in key, int64 value);
bool Set(const string &in key, float value);
bool Set(const string &in key, double value);
bool Set(const string &in key, bool value);
bool Set(const string &in key, const array<uint8> &in value);
bool Get(const string &in key, string &out value);
bool Get(const string &in key, int &out value);
bool Get(const string &in key, uint &out value);
bool Get(const string &in key, int64 &out value);
bool Get(const string &in key, float &out value);
bool Get(const string &in key, double &out value);
bool Get(const string &in key, bool &out value);
bool Get(const string &in key, array<uint8> &out value);
bool Has(const string &in key);
bool Remove(const string &in key);
}
namespace GamepadButton {
const int UNKNOWN = 0;
const int LEFT_FACE_UP = 1;
const int LEFT_FACE_RIGHT = 2;
const int LEFT_FACE_DOWN = 3;
const int LEFT_FACE_LEFT = 4;
const int RIGHT_FACE_UP = 5;
const int RIGHT_FACE_RIGHT = 6;
const int RIGHT_FACE_DOWN = 7;
const int RIGHT_FACE_LEFT = 8;
const int LEFT_TRIGGER_1 = 9;
const int LEFT_TRIGGER_2 = 10;
const int RIGHT_TRIGGER_1 = 11;
const int RIGHT_TRIGGER_2 = 12;
const int MIDDLE_LEFT = 13;
const int MIDDLE = 14;
const int MIDDLE_RIGHT = 15;
const int LEFT_THUMB = 16;
const int RIGHT_THUMB = 17;
}
namespace GamepadAxis {
const int LEFT_X = 0;
const int LEFT_Y = 1;
const int RIGHT_X = 2;
const int RIGHT_Y = 3;
const int LEFT_TRIGGER = 4;
const int RIGHT_TRIGGER = 5;
}
+43
View File
@@ -0,0 +1,43 @@
// Autoload configuration script.
// Runs before any scene/game script to configure input and preload resources.
void Autoload() {
ClearAllActions();
// Movement
RegisterAction("move_up");
RegisterAction("move_down");
RegisterAction("move_left");
RegisterAction("move_right");
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
RegisterAction("jump");
RegisterAction("interact");
RegisterAction("attack");
BindKeyboard("jump", Key::SPACE);
BindKeyboard("interact", Key::E);
BindKeyboard("attack", Key::LEFT_CONTROL);
BindMouseButton("attack", MouseButton::LEFT);
// Gamepad (optional)
BindGamepadButton("jump", 0, GamepadButton::RIGHT_FACE_DOWN);
BindGamepadButton("interact", 0, GamepadButton::RIGHT_FACE_RIGHT);
BindGamepadAxis("move_left", 0, GamepadAxis::LEFT_X, 0.3f, true);
BindGamepadAxis("move_right", 0, GamepadAxis::LEFT_X, 0.3f, false);
// Pick the initial scene to load.
Scene::Load("scenes/demo.toml");
Globals::Set("demo_var",100);
}
+50
View File
@@ -0,0 +1,50 @@
// Class-based entity script: one `EntityScript` instance per entity.
// Implements Init/Update/Shutdown so the engine will instantiate it per-entity.
class EntityScript
{
float baseX;
float baseY;
float baseZ;
[group["Movement"]][desc["Phase accumulator for the sine wave"]][editable][range[0,1]]float phase;
// per-instance movement parameters (can be tuned per-entity)
[group["Movement"]][desc["Bounce height"]][editable][range[0,10]]float amplitude = 2.5f;
[group["Movement"]][desc["Cycles per second"]][editable][range[0,10]]float speed = 2.5f;
[group["State"]][desc["Debug animation state"]][editable][options[Idle,Walk,Run]] int state = 0;
EntityScript()
{
baseX = 0.0f;
baseY = 0.0f;
baseZ = 0.0f;
phase = 0.0f;
}
void Init(uint entity)
{
if (!ECS::IsValid(entity)) return;
if (!ECS::HasTransform(entity))
ECS::AddTransform(entity, 0.0f, 0.0f, 0.0f);
ECS::GetPosition(entity, baseX, baseY, baseZ);
phase = 0.0f;
}
void Update(uint entity, float dt)
{
if (!ECS::IsValid(entity)) return;
if (!ECS::HasTransform(entity)) return;
phase += dt;
float offset = Math::Sin(phase * speed + float(entity) * 0.1f) * amplitude;
ECS::SetPosition(entity, baseX, baseY + offset, baseZ);
ECS::SetRotationEuler(entity, 0.0f, phase * 50.0f * dt, 0.0f);
}
void Shutdown(uint entity)
{
// nothing to clean up per-instance for this simple example
}
}
+82
View File
@@ -0,0 +1,82 @@
// Demo scene script with input-driven behavior.
class SceneScript {
uint sceneRoot = 0;
uint camera = 0;
uint light = 0;
uint cube = 0;
float time = 0.0f;
bool highlight = false;
void Init() {
uint kvStore = KV::Open("demo_scene");
Log(LOG_INFO, "Demo scene script init");
int var;
if (Globals::Get("demo_var", var)) {
Log(LOG_INFO, "Loaded global variable 'demo_var' with value: " + var);
} else {
Log(LOG_INFO, "No global variable 'demo_var' found, starting fresh.");
var = 42;
}
Globals::Set("demo_var", var + 1);
int value;
string strValue;
if (KV::Get(kvStore, "int", value)) {
Log(LOG_INFO, "Loaded int value from KV store: " + value);
} else {
Log(LOG_INFO, "No int value found in KV store, starting fresh.");
value = 0;
}
if (KV::Get(kvStore, "str", strValue)) {
Log(LOG_INFO, "Loaded string value from KV store: " + strValue);
} else {
Log(LOG_INFO, "No string value found in KV store, starting fresh.");
strValue = "Hello, KV!";
}
cube = ECS::CreateEntity();
ECS::AddTransform(cube, 0.0f, 2.5f, 0.0f);
uint modelId = Asset::GetModelId("demo2_cube");
if (modelId == 0) {
modelId = Asset::LoadCube("demo2_cube", 1.0f, 1.0f, 1.0f);
}
ECS::AddModelRenderer(cube, int(modelId), 0xFFAA00FF, 0.0f);
ECS::AddTag(cube, "InputCube");
KV::Put(kvStore, "int", value+1);
KV::Put(kvStore, "str", strValue + " (updated " + value + ")");
var = 0;
if (Globals::Get("demo_var", var)) {
Log(LOG_INFO, "Updated global variable 'demo_var' to value: " + var);
}
}
void Shutdown() {
Log(LOG_INFO, "Demo scene script shutdown");
}
void Update(float dt) {
time += dt;
if (ECS::IsValid(cube) && ECS::HasTransform(cube)) {
ECS::SetRotationEuler(cube, 0.0f, time, time * 0.5f);
}
if (IsActionPressed("jump")) {
highlight = !highlight;
if (highlight) {
ECS::SetModelRendererColor(cube, 0x00FF88FF);
ECS::SetModelRendererOutline(cube, 2.0f);
Toast::Info("Highlight on");
} else {
ECS::SetModelRendererColor(cube, 0xFFAA00FF);
ECS::SetModelRendererOutline(cube, 0.0f);
Toast::Info("Highlight off");
}
}
if (IsActionPressed("attack")) {
ECS::SetLightDirection(light, -0.5f, 0.7f, -0.3f);
Toast::Success("Light toggled");
}
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ void Init() {
// === Create Cube with Outline ===
cube = ECS::CreateEntity();
ECS::AddTransform(cube, -1.5, 0.5, 0.0);
ECS::AddSprite(cube, cubeModel, 0xFFFFFFFF, 0.0);
ECS::AddModelRenderer(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
@@ -64,7 +64,7 @@ void Init() {
// === Create Sphere ===
sphere = ECS::CreateEntity();
ECS::AddTransform(sphere, 1.5, 0.5, 0.0);
ECS::AddSprite(sphere, sphereModel, 0xFFFFFFFF, 0.0);
ECS::AddModelRenderer(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
+5 -195
View File
@@ -1,197 +1,7 @@
// ECS-based game script demo with script-driven rendering
// This demonstrates creating entities with various components and managing rendering
// 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);
// Empty scene script to avoid auto-loading demo content.
class SceneScript {
void Init() {}
void Shutdown() {}
void Update(float dt) {
}
}
+20
View File
@@ -0,0 +1,20 @@
// Example scene script using a class instance.
class SceneScript {
float time = 0.0f;
void Init() {
Log(LOG_INFO, "SceneScript.Init (example)");
}
void Update(float dt) {
time += dt;
if (time > 1.0f) {
time = 0.0f;
Log(LOG_DEBUG, "SceneScript.Update tick");
}
}
void Shutdown() {
Log(LOG_INFO, "SceneScript.Shutdown (example)");
}
}
-35
View File
@@ -1,35 +0,0 @@
float x = 50;
float y = 100;
float r = 40.0f;
float theta = 0.0f;
float ix = 0.0f;
float iy = 0.0f;
Texture::Image img;
Texture::Texture2D tex;
void Init() {
Log(LOG_TRACE, "Initialization complete!");
Toast::Success("Script initialized successfully!");
img = Texture::LoadImage("assets/raylib_96x96.png");
Toast::Info("Image loaded successfully.");
tex = Texture::LoadTexture("assets/raylib_96x96.png");
Toast::Info("Texture loaded successfully.");
}
void Shutdown() {
Texture::Unload(img);
Texture::Unload(tex);
Log(LOG_TRACE, "Shutdown complete!");
}
void Update(float dt) {
x += 64 * dt;
theta += 2.0f * dt;
ix = 100+ r * Math::Cos(theta);
iy = 100+r * Math::Sin(theta);
if (x > 800) {
Log(LOG_TRACE, format("Reset happened at position {}", x));
x = 0;
Toast::Info(format("X position has been reset to {}", x));
}
}
+744 -97
View File
File diff suppressed because it is too large Load Diff
+1083
View File
File diff suppressed because it is too large Load Diff
+24 -29
View File
@@ -1,8 +1,7 @@
#include "HotReload.h"
#include "PhysFSManager.h"
#include <iostream>
namespace fs = std::filesystem;
HotReload::HotReload(const std::string& pathToWatch) : path(pathToWatch), lastWriteTime(0) {
UpdateLastWriteTime();
}
@@ -21,36 +20,32 @@ void HotReload::UpdateLastWriteTime() {
}
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
try {
auto ftime = fs::last_write_time(filename);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
return std::chrono::system_clock::to_time_t(sctp);
} catch (const std::exception&) {
return 0;
}
return static_cast<std::time_t>(PhysFSManager::GetModTime(filename));
}
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
if (PhysFSManager::Exists(path) && !PhysFSManager::IsDirectory(path)) {
return GetFileWriteTime(path);
}
if (PhysFSManager::IsDirectory(path)) {
std::time_t newest = 0;
auto entries = PhysFSManager::ListFiles(path);
for (const auto& entry : entries) {
if (entry == "." || entry == "..") continue;
std::string child = path;
if (!child.empty() && child.back() != '/') {
child.push_back('/');
}
child += entry;
if (PhysFSManager::IsDirectory(child)) {
continue;
}
std::time_t t = GetFileWriteTime(child);
if (t > newest) newest = t;
}
return newest;
}
return 0;
}
+4
View File
@@ -55,6 +55,10 @@ void InputManager::ClearBindings(const std::string& actionName) {
}
}
void InputManager::ClearAllActions() {
actions.clear();
}
bool InputManager::IsActionPressed(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
+60
View File
@@ -13,6 +13,7 @@ ModelManager::~ModelManager()
UnloadModel(pair.second);
}
models.clear();
modelSources.clear();
}
}
@@ -28,6 +29,10 @@ unsigned int ModelManager::Load(const std::string& path)
unsigned int id = nextId++;
models[id] = model;
ModelSource source;
source.type = ModelSourceType::Path;
source.path = path;
modelSources[id] = source;
log_info("Loaded model id=%u from %s", id, path.c_str());
return id;
@@ -44,6 +49,49 @@ unsigned int ModelManager::LoadFromMesh(Mesh mesh)
return id;
}
unsigned int ModelManager::LoadCube(float width, float height, float length)
{
Mesh mesh = GenMeshCube(width, height, length);
unsigned int id = LoadFromMesh(mesh);
if (id != 0) {
ModelSource source;
source.type = ModelSourceType::Cube;
source.size = {width, height, length};
modelSources[id] = source;
}
return id;
}
unsigned int ModelManager::LoadSphere(float radius, int rings, int slices)
{
Mesh mesh = GenMeshSphere(radius, rings, slices);
unsigned int id = LoadFromMesh(mesh);
if (id != 0) {
ModelSource source;
source.type = ModelSourceType::Sphere;
source.radius = radius;
source.rings = rings;
source.slices = slices;
modelSources[id] = source;
}
return id;
}
unsigned int ModelManager::LoadPlane(float width, float length, int resX, int resZ)
{
Mesh mesh = GenMeshPlane(width, length, resX, resZ);
unsigned int id = LoadFromMesh(mesh);
if (id != 0) {
ModelSource source;
source.type = ModelSourceType::Plane;
source.size = {width, 1.0f, length};
source.rings = resX;
source.slices = resZ;
modelSources[id] = source;
}
return id;
}
void ModelManager::Unload(unsigned int id)
{
auto it = models.find(id);
@@ -51,6 +99,7 @@ void ModelManager::Unload(unsigned int id)
{
UnloadModel(it->second);
models.erase(it);
modelSources.erase(id);
log_info("Unloaded model id=%u", id);
}
}
@@ -65,6 +114,16 @@ Model* ModelManager::Get(unsigned int id)
return nullptr;
}
const ModelSource* ModelManager::GetSource(unsigned int id) const
{
auto it = modelSources.find(id);
if (it != modelSources.end())
{
return &it->second;
}
return nullptr;
}
void ModelManager::UnloadAll()
{
if (!unloaded) {
@@ -73,6 +132,7 @@ void ModelManager::UnloadAll()
UnloadModel(pair.second);
}
models.clear();
modelSources.clear();
unloaded = true;
log_info("Unloaded all models");
}
+350
View File
@@ -0,0 +1,350 @@
#include "PhysFSManager.h"
#include "raylib.h"
#include "physfs.h"
#include "log/log.h"
#include <algorithm>
#include <cstring>
#include <climits>
namespace PhysFSManager {
static bool g_initialized = false;
static bool g_editorEnabled = false;
static std::string NormalizePath(std::string path) {
std::replace(path.begin(), path.end(), '\\', '/');
while (path.rfind("./", 0) == 0) {
path.erase(0, 2);
}
while (!path.empty() && path.front() == '/') {
path.erase(path.begin());
}
return path;
}
static bool StartsWith(const std::string& value, const std::string& prefix) {
return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0;
}
static bool IsWriteAllowed(const std::string& rawPath) {
std::string path = NormalizePath(rawPath);
if (path.empty()) {
return false;
}
if (path.find("..") != std::string::npos) {
return false;
}
if (path == "user" || StartsWith(path, "user/")) {
return true;
}
if (!g_editorEnabled) {
return false;
}
if (path == "scenes" || StartsWith(path, "scenes/")) {
return true;
}
if (path == "assets" || path == "assets/registry.toml") {
return true;
}
return false;
}
static unsigned char* RaylibLoadFileDataCallback(const char* fileName, int* dataSize) {
if (!fileName || !dataSize) {
return nullptr;
}
PHYSFS_File* file = PHYSFS_openRead(fileName);
if (!file) {
*dataSize = 0;
return nullptr;
}
PHYSFS_sint64 length = PHYSFS_fileLength(file);
if (length <= 0 || length > static_cast<PHYSFS_sint64>(INT32_MAX)) {
PHYSFS_close(file);
*dataSize = 0;
return nullptr;
}
unsigned char* data = static_cast<unsigned char*>(MemAlloc(static_cast<size_t>(length)));
if (!data) {
PHYSFS_close(file);
*dataSize = 0;
return nullptr;
}
PHYSFS_sint64 read = PHYSFS_readBytes(file, data, length);
PHYSFS_close(file);
if (read != length) {
MemFree(data);
*dataSize = 0;
return nullptr;
}
*dataSize = static_cast<int>(length);
return data;
}
static char* RaylibLoadFileTextCallback(const char* fileName) {
if (!fileName) {
return nullptr;
}
PHYSFS_File* file = PHYSFS_openRead(fileName);
if (!file) {
return nullptr;
}
PHYSFS_sint64 length = PHYSFS_fileLength(file);
if (length <= 0 || length > static_cast<PHYSFS_sint64>(INT32_MAX)) {
PHYSFS_close(file);
return nullptr;
}
char* text = static_cast<char*>(MemAlloc(static_cast<size_t>(length) + 1));
if (!text) {
PHYSFS_close(file);
return nullptr;
}
PHYSFS_sint64 read = PHYSFS_readBytes(file, text, length);
PHYSFS_close(file);
if (read != length) {
MemFree(text);
return nullptr;
}
text[length] = '\0';
return text;
}
static bool RaylibSaveFileDataCallback(const char* fileName, void* data, int dataSize) {
if (!fileName || !data || dataSize <= 0) {
return false;
}
if (!IsWriteAllowed(fileName)) {
log_warn("PhysFS write blocked: %s", fileName);
return false;
}
PHYSFS_File* file = PHYSFS_openWrite(fileName);
if (!file) {
return false;
}
PHYSFS_sint64 written = PHYSFS_writeBytes(file, data, dataSize);
PHYSFS_close(file);
return written == dataSize;
}
bool Initialize(const char* argv0) {
if (g_initialized) {
return true;
}
if (!PHYSFS_init(argv0)) {
log_error("PhysFS init failed: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
PHYSFS_permitSymbolicLinks(0);
// Configure raylib to use PhysFS for all file reads/writes.
SetLoadFileDataCallback(RaylibLoadFileDataCallback);
SetLoadFileTextCallback(RaylibLoadFileTextCallback);
SetSaveFileDataCallback(RaylibSaveFileDataCallback);
g_initialized = true;
return true;
}
bool IsInitialized() {
return g_initialized;
}
void Shutdown() {
if (!g_initialized) {
return;
}
ClearSearchPath();
SetLoadFileDataCallback(nullptr);
SetSaveFileDataCallback(nullptr);
PHYSFS_deinit();
g_initialized = false;
}
bool MountPath(const std::string& path, const std::string& mountPoint, bool append) {
const char* mount = mountPoint.empty() ? nullptr : mountPoint.c_str();
if (!PHYSFS_mount(path.c_str(), mount, append ? 1 : 0)) {
log_warn("PhysFS mount failed: %s -> %s (%s)", path.c_str(), mountPoint.c_str(),
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
return true;
}
bool UnmountPath(const std::string& path) {
if (!PHYSFS_unmount(path.c_str())) {
return false;
}
return true;
}
void ClearSearchPath() {
char** paths = PHYSFS_getSearchPath();
if (!paths) {
return;
}
for (char** i = paths; *i != nullptr; ++i) {
PHYSFS_unmount(*i);
}
PHYSFS_freeList(paths);
}
bool SetWriteDir(const std::string& path) {
if (!PHYSFS_setWriteDir(path.c_str())) {
log_warn("PhysFS set write dir failed: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
return false;
}
return true;
}
void SetEditorEnabled(bool enabled) {
g_editorEnabled = enabled;
}
bool EnsureDir(const std::string& path) {
if (path.empty()) {
return true;
}
if (!IsWriteAllowed(path)) {
log_warn("PhysFS mkdir blocked: %s", path.c_str());
return false;
}
std::string normalized = path;
std::replace(normalized.begin(), normalized.end(), '\\', '/');
size_t start = 0;
while (start < normalized.size()) {
size_t slash = normalized.find('/', start);
std::string segment = normalized.substr(0, slash);
if (!segment.empty() && !IsDirectory(segment)) {
if (!PHYSFS_mkdir(segment.c_str())) {
return false;
}
}
if (slash == std::string::npos) {
break;
}
start = slash + 1;
}
return true;
}
bool Exists(const std::string& path) {
return PHYSFS_exists(path.c_str()) != 0;
}
bool IsDirectory(const std::string& path) {
return PHYSFS_isDirectory(path.c_str()) != 0;
}
std::string GetRealDir(const std::string& path) {
const char* dir = PHYSFS_getRealDir(path.c_str());
return dir ? std::string(dir) : std::string();
}
std::int64_t GetModTime(const std::string& path) {
PHYSFS_Stat stat{};
if (!PHYSFS_stat(path.c_str(), &stat)) {
return 0;
}
return static_cast<std::int64_t>(stat.modtime);
}
std::vector<std::string> ListFiles(const std::string& path) {
std::vector<std::string> result;
char** files = PHYSFS_enumerateFiles(path.c_str());
if (!files) {
return result;
}
for (char** i = files; *i != nullptr; ++i) {
result.emplace_back(*i);
}
PHYSFS_freeList(files);
return result;
}
bool ReadTextFile(const std::string& path, std::string& out) {
out.clear();
PHYSFS_File* file = PHYSFS_openRead(path.c_str());
if (!file) {
return false;
}
PHYSFS_sint64 length = PHYSFS_fileLength(file);
if (length < 0) {
PHYSFS_close(file);
return false;
}
out.resize(static_cast<size_t>(length));
PHYSFS_sint64 read = PHYSFS_readBytes(file, out.data(), length);
PHYSFS_close(file);
if (read != length) {
out.clear();
return false;
}
return true;
}
bool WriteTextFile(const std::string& path, const std::string& contents) {
if (!IsWriteAllowed(path)) {
log_warn("PhysFS write blocked: %s", path.c_str());
return false;
}
PHYSFS_File* file = PHYSFS_openWrite(path.c_str());
if (!file) {
return false;
}
PHYSFS_sint64 written = PHYSFS_writeBytes(file, contents.data(), contents.size());
PHYSFS_close(file);
return written == static_cast<PHYSFS_sint64>(contents.size());
}
PHYSFS_File* OpenRead(const std::string& path) {
return PHYSFS_openRead(path.c_str());
}
PHYSFS_File* OpenWrite(const std::string& path) {
if (!IsWriteAllowed(path)) {
log_warn("PhysFS write blocked: %s", path.c_str());
return nullptr;
}
return PHYSFS_openWrite(path.c_str());
}
void Close(PHYSFS_File* file) {
if (file) {
PHYSFS_close(file);
}
}
} // namespace PhysFSManager
+145
View File
@@ -0,0 +1,145 @@
#include "SceneGraph.h"
#include "ECSComponents.h"
#include "raymath.h"
#include "entt.hpp"
static Matrix BuildLocalMatrix(const ECSTransform& local) {
Matrix scale = MatrixScale(local.scale.x, local.scale.y, local.scale.z);
Matrix rotation = MatrixRotateXYZ(local.rotation);
Matrix translation = MatrixTranslate(local.position.x, local.position.y, local.position.z);
return MatrixMultiply(MatrixMultiply(translation, rotation), scale);
}
static Vector3 ExtractScale(const Matrix& matrix) {
Vector3 scale;
scale.x = sqrtf(matrix.m0 * matrix.m0 + matrix.m1 * matrix.m1 + matrix.m2 * matrix.m2);
scale.y = sqrtf(matrix.m4 * matrix.m4 + matrix.m5 * matrix.m5 + matrix.m6 * matrix.m6);
scale.z = sqrtf(matrix.m8 * matrix.m8 + matrix.m9 * matrix.m9 + matrix.m10 * matrix.m10);
return scale;
}
static WorldTransform ComputeWorldFromParent(ECSTransform& local, const WorldTransform* parentWorld) {
local.localMatrix = BuildLocalMatrix(local);
WorldTransform world;
if (!parentWorld) {
world.worldMatrix = local.localMatrix;
world.position = local.position;
world.rotation = local.rotation;
world.scale = local.scale;
return world;
}
world.worldMatrix = MatrixMultiply(parentWorld->worldMatrix, local.localMatrix);
world.position = {world.worldMatrix.m12, world.worldMatrix.m13, world.worldMatrix.m14};
world.scale = ExtractScale(world.worldMatrix);
world.rotation = QuaternionToEuler(QuaternionFromMatrix(world.worldMatrix));
return world;
}
void AttachChild(entt::registry& registry, entt::entity child, entt::entity parent) {
if (child == parent) return;
DetachFromParent(registry, child);
auto& childHierarchy = registry.get_or_emplace<Hierarchy>(child);
auto& parentHierarchy = registry.get_or_emplace<Hierarchy>(parent);
childHierarchy.parent = parent;
childHierarchy.prev = entt::null;
childHierarchy.next = parentHierarchy.first;
if (parentHierarchy.first != entt::null) {
registry.get_or_emplace<Hierarchy>(parentHierarchy.first).prev = child;
}
parentHierarchy.first = child;
}
void DetachFromParent(entt::registry& registry, entt::entity child) {
auto* childHierarchy = registry.try_get<Hierarchy>(child);
if (!childHierarchy || childHierarchy->parent == entt::null) {
return;
}
entt::entity parent = childHierarchy->parent;
auto& parentHierarchy = registry.get_or_emplace<Hierarchy>(parent);
if (parentHierarchy.first == child) {
parentHierarchy.first = childHierarchy->next;
}
if (childHierarchy->prev != entt::null) {
registry.get_or_emplace<Hierarchy>(childHierarchy->prev).next = childHierarchy->next;
}
if (childHierarchy->next != entt::null) {
registry.get_or_emplace<Hierarchy>(childHierarchy->next).prev = childHierarchy->prev;
}
childHierarchy->parent = entt::null;
childHierarchy->prev = entt::null;
childHierarchy->next = entt::null;
}
void DetachChildren(entt::registry& registry, entt::entity parent) {
auto* parentHierarchy = registry.try_get<Hierarchy>(parent);
if (!parentHierarchy) {
return;
}
entt::entity child = parentHierarchy->first;
parentHierarchy->first = entt::null;
while (child != entt::null) {
auto* childHierarchy = registry.try_get<Hierarchy>(child);
entt::entity nextChild = childHierarchy ? childHierarchy->next : entt::null;
if (childHierarchy) {
childHierarchy->parent = entt::null;
childHierarchy->prev = entt::null;
childHierarchy->next = entt::null;
}
child = nextChild;
}
}
static void UpdateWorldRecursive(entt::registry& registry, entt::entity entity, const WorldTransform* parentWorld) {
const WorldTransform* currentWorld = parentWorld;
if (auto* local = registry.try_get<ECSTransform>(entity)) {
auto& world = registry.get_or_emplace<WorldTransform>(entity);
world = ComputeWorldFromParent(*local, parentWorld);
currentWorld = &world;
}
auto* hierarchy = registry.try_get<Hierarchy>(entity);
if (!hierarchy) {
return;
}
entt::entity child = hierarchy->first;
while (child != entt::null) {
auto* childHierarchy = registry.try_get<Hierarchy>(child);
entt::entity nextChild = childHierarchy ? childHierarchy->next : entt::null;
UpdateWorldRecursive(registry, child, currentWorld);
child = nextChild;
}
}
void UpdateWorldTransforms(entt::registry& registry) {
// Roots: entities with no hierarchy, or hierarchy parent == null
auto transformView = registry.view<ECSTransform>(entt::exclude<Hierarchy>);
for (auto entity : transformView) {
UpdateWorldRecursive(registry, entity, nullptr);
}
auto hierarchyView = registry.view<Hierarchy>();
for (auto entity : hierarchyView) {
const auto& hierarchy = hierarchyView.get<Hierarchy>(entity);
if (hierarchy.parent == entt::null) {
UpdateWorldRecursive(registry, entity, nullptr);
}
}
}
+963
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -13,6 +13,7 @@ ShaderManager::~ShaderManager()
UnloadShader(pair.second);
}
shaders.clear();
shaderSources.clear();
}
}
@@ -28,6 +29,7 @@ unsigned int ShaderManager::Load(const std::string& vsPath, const std::string& f
unsigned int id = nextId++;
shaders[id] = shader;
shaderSources[id] = ShaderSource{vsPath, fsPath};
log_info("Loaded shader id=%u from %s / %s (raylib id=%u)", id, vsPath.c_str(), fsPath.c_str(), shader.id);
return id;
@@ -40,6 +42,7 @@ void ShaderManager::Unload(unsigned int id)
{
UnloadShader(it->second);
shaders.erase(it);
shaderSources.erase(id);
log_info("Unloaded shader id=%u", id);
}
}
@@ -54,6 +57,16 @@ Shader* ShaderManager::Get(unsigned int id)
return nullptr;
}
const ShaderSource* ShaderManager::GetSource(unsigned int id) const
{
auto it = shaderSources.find(id);
if (it != shaderSources.end())
{
return &it->second;
}
return nullptr;
}
void ShaderManager::UnloadAll()
{
if (!unloaded) {
@@ -62,6 +75,7 @@ void ShaderManager::UnloadAll()
UnloadShader(pair.second);
}
shaders.clear();
shaderSources.clear();
unloaded = true;
log_info("Unloaded all shaders");
}
+74
View File
@@ -0,0 +1,74 @@
#include "TextureManager.h"
#include "log/log.h"
TextureManager::TextureManager() : nextId(1), unloaded(false)
{
}
TextureManager::~TextureManager()
{
if (!unloaded) {
for (auto& pair : textures) {
UnloadTexture(pair.second);
}
textures.clear();
textureSources.clear();
}
}
unsigned int TextureManager::Load(const std::string& path)
{
Texture2D texture = LoadTexture(path.c_str());
if (texture.id == 0) {
log_warn("Failed to load texture: %s", path.c_str());
return 0;
}
unsigned int id = nextId++;
textures[id] = texture;
textureSources[id] = TextureSource{path};
log_info("Loaded texture id=%u from %s", id, path.c_str());
return id;
}
void TextureManager::Unload(unsigned int id)
{
auto it = textures.find(id);
if (it != textures.end()) {
UnloadTexture(it->second);
textures.erase(it);
textureSources.erase(id);
log_info("Unloaded texture id=%u", id);
}
}
Texture2D* TextureManager::Get(unsigned int id)
{
auto it = textures.find(id);
if (it != textures.end()) {
return &it->second;
}
return nullptr;
}
const TextureSource* TextureManager::GetSource(unsigned int id) const
{
auto it = textureSources.find(id);
if (it != textureSources.end()) {
return &it->second;
}
return nullptr;
}
void TextureManager::UnloadAll()
{
if (!unloaded) {
for (auto& pair : textures) {
UnloadTexture(pair.second);
}
textures.clear();
textureSources.clear();
unloaded = true;
log_info("Unloaded all textures");
}
}
+1991 -7
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
#include "ImViewGuizmoRaymath.h"
+240 -40
View File
@@ -1,5 +1,6 @@
#include "scripting/ECSBindings.h"
#include "ECSComponents.h"
#include "SceneGraph.h"
#include <angelscript.h>
#include <assert.h>
#include <string>
@@ -20,7 +21,10 @@ uint32_t AS_CreateEntity() {
void AS_DestroyEntity(uint32_t entity) {
if (!registry) return;
registry->destroy((entt::entity)entity);
entt::entity e = (entt::entity)entity;
DetachFromParent(*registry, e);
DetachChildren(*registry, e);
registry->destroy(e);
}
bool AS_IsEntityValid(uint32_t entity) {
@@ -62,18 +66,34 @@ void AS_SetScale(uint32_t entity, float x, float y, float z) {
void AS_SetRotation(uint32_t entity, float rotation) {
if (!registry) return;
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
t->rotation = rotation;
t->rotation.y = rotation;
}
}
float AS_GetRotation(uint32_t entity) {
if (!registry) return 0.0f;
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
return t->rotation;
return t->rotation.y;
}
return 0.0f;
}
void AS_SetRotationEuler(uint32_t entity, float x, float y, float z) {
if (!registry) return;
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
t->rotation = {x, y, z};
}
}
void AS_GetRotationEuler(uint32_t entity, float* x, float* y, float* z) {
if (!registry || !x || !y || !z) return;
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
*x = t->rotation.x;
*y = t->rotation.y;
*z = t->rotation.z;
}
}
bool AS_HasTransform(uint32_t entity) {
if (!registry) return false;
return registry->all_of<ECSTransform>((entt::entity)entity);
@@ -115,57 +135,128 @@ void AS_RemoveVelocity(uint32_t entity) {
registry->remove<Velocity>((entt::entity)entity);
}
// === Sprite Component ===
// === ModelRenderer Component ===
void AS_AddSprite(uint32_t entity, int modelId, uint32_t color, float outlineSize) {
void AS_AddModelRenderer(uint32_t entity, int modelId, uint32_t color, float outlineSize) {
if (!registry) return;
Color col;
col.r = (color >> 24) & 0xFF;
col.g = (color >> 16) & 0xFF;
col.b = (color >> 8) & 0xFF;
col.a = color & 0xFF;
registry->emplace<Sprite>((entt::entity)entity, modelId, col, outlineSize);
registry->emplace<ModelRenderer>((entt::entity)entity, modelId, col, outlineSize);
}
void AS_SetSpriteColor(uint32_t entity, uint32_t color) {
void AS_SetModelRendererColor(uint32_t entity, uint32_t color) {
if (!registry) return;
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
s->color.r = (color >> 24) & 0xFF;
s->color.g = (color >> 16) & 0xFF;
s->color.b = (color >> 8) & 0xFF;
s->color.a = color & 0xFF;
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
renderer->color.r = (color >> 24) & 0xFF;
renderer->color.g = (color >> 16) & 0xFF;
renderer->color.b = (color >> 8) & 0xFF;
renderer->color.a = color & 0xFF;
}
}
void AS_SetSpriteModel(uint32_t entity, int modelId) {
void AS_SetModelRendererModel(uint32_t entity, int modelId) {
if (!registry) return;
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
s->modelId = modelId;
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
renderer->modelId = modelId;
}
}
void AS_SetSpriteOutline(uint32_t entity, float outlineSize) {
void AS_SetModelRendererOutline(uint32_t entity, float outlineSize) {
if (!registry) return;
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
s->outlineSize = outlineSize;
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
renderer->outlineSize = outlineSize;
}
}
void AS_SetSpriteShader(uint32_t entity, unsigned int shaderId) {
void AS_SetModelRendererShader(uint32_t entity, unsigned int shaderId) {
if (!registry) return;
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
s->shaderId = shaderId;
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
renderer->shaderId = shaderId;
}
}
bool AS_HasSprite(uint32_t entity) {
bool AS_HasModelRenderer(uint32_t entity) {
if (!registry) return false;
return registry->all_of<Sprite>((entt::entity)entity);
return registry->all_of<ModelRenderer>((entt::entity)entity);
}
void AS_RemoveSprite(uint32_t entity) {
void AS_RemoveModelRenderer(uint32_t entity) {
if (!registry) return;
registry->remove<Sprite>((entt::entity)entity);
registry->remove<ModelRenderer>((entt::entity)entity);
}
// === Texture Component ===
void AS_AddTexture(uint32_t entity, unsigned int textureId) {
if (!registry) return;
registry->emplace<TextureComponent>((entt::entity)entity, textureId);
}
void AS_SetTextureId(uint32_t entity, unsigned int textureId) {
if (!registry) return;
if (auto* t = registry->try_get<TextureComponent>((entt::entity)entity)) {
t->textureId = textureId;
}
}
unsigned int AS_GetTextureId(uint32_t entity) {
if (!registry) return 0;
if (auto* t = registry->try_get<TextureComponent>((entt::entity)entity)) {
return t->textureId;
}
return 0;
}
bool AS_HasTexture(uint32_t entity) {
if (!registry) return false;
return registry->all_of<TextureComponent>((entt::entity)entity);
}
void AS_RemoveTexture(uint32_t entity) {
if (!registry) return;
registry->remove<TextureComponent>((entt::entity)entity);
}
// === UV Transform Component ===
void AS_AddUVTransform(uint32_t entity, float scaleX, float scaleY, float offsetX, float offsetY, float rotation) {
if (!registry) return;
registry->emplace<UVTransform>((entt::entity)entity,
Vector2{scaleX, scaleY}, Vector2{offsetX, offsetY}, rotation);
}
void AS_SetUVScale(uint32_t entity, float scaleX, float scaleY) {
if (!registry) return;
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
uv->scale = {scaleX, scaleY};
}
}
void AS_SetUVOffset(uint32_t entity, float offsetX, float offsetY) {
if (!registry) return;
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
uv->offset = {offsetX, offsetY};
}
}
void AS_SetUVRotation(uint32_t entity, float rotation) {
if (!registry) return;
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
uv->rotation = rotation;
}
}
bool AS_HasUVTransform(uint32_t entity) {
if (!registry) return false;
return registry->all_of<UVTransform>((entt::entity)entity);
}
void AS_RemoveUVTransform(uint32_t entity) {
if (!registry) return;
registry->remove<UVTransform>((entt::entity)entity);
}
// === Tag Component ===
@@ -200,6 +291,44 @@ void AS_RemoveTag(uint32_t entity) {
registry->remove<Tag>((entt::entity)entity);
}
// === Hierarchy Component ===
void AS_SetParent(uint32_t child, uint32_t parent) {
if (!registry) return;
entt::entity childEntity = (entt::entity)child;
entt::entity parentEntity = (entt::entity)parent;
if (!registry->valid(childEntity)) return;
if (parent == 0 || !registry->valid(parentEntity)) {
DetachFromParent(*registry, childEntity);
return;
}
AttachChild(*registry, childEntity, parentEntity);
}
void AS_ClearParent(uint32_t child) {
if (!registry) return;
entt::entity childEntity = (entt::entity)child;
DetachFromParent(*registry, childEntity);
}
uint32_t AS_GetParent(uint32_t child) {
if (!registry) return 0;
if (auto* h = registry->try_get<Hierarchy>((entt::entity)child)) {
return static_cast<uint32_t>(h->parent);
}
return 0;
}
bool AS_HasParent(uint32_t child) {
if (!registry) return false;
if (auto* h = registry->try_get<Hierarchy>((entt::entity)child)) {
return h->parent != entt::null;
}
return false;
}
// === Camera3D Component ===
void AS_AddCamera3D(uint32_t entity, float px, float py, float pz, float tx, float ty, float tz, float fovy) {
@@ -398,10 +527,18 @@ void RegisterECSBindings(asIScriptEngine *engine) {
asFUNCTION(AS_SetRotation), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetRotationEuler(uint, float, float, float)",
asFUNCTION(AS_SetRotationEuler), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("float GetRotation(uint)",
asFUNCTION(AS_GetRotation), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void GetRotationEuler(uint, float &out, float &out, float &out)",
asFUNCTION(AS_GetRotationEuler), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool HasTransform(uint)",
asFUNCTION(AS_HasTransform), asCALL_CDECL);
assert(r >= 0);
@@ -431,33 +568,79 @@ void RegisterECSBindings(asIScriptEngine *engine) {
asFUNCTION(AS_RemoveVelocity), asCALL_CDECL);
assert(r >= 0);
// Sprite component
r = engine->RegisterGlobalFunction("void AddSprite(uint, int, uint, float = 0.0f)",
asFUNCTION(AS_AddSprite), asCALL_CDECL);
// ModelRenderer component
r = engine->RegisterGlobalFunction("void AddModelRenderer(uint, int, uint, float = 0.0f)",
asFUNCTION(AS_AddModelRenderer), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetSpriteColor(uint, uint)",
asFUNCTION(AS_SetSpriteColor), asCALL_CDECL);
r = engine->RegisterGlobalFunction("void SetModelRendererColor(uint, uint)",
asFUNCTION(AS_SetModelRendererColor), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetSpriteModel(uint, int)",
asFUNCTION(AS_SetSpriteModel), asCALL_CDECL);
r = engine->RegisterGlobalFunction("void SetModelRendererModel(uint, int)",
asFUNCTION(AS_SetModelRendererModel), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetSpriteOutline(uint, float)",
asFUNCTION(AS_SetSpriteOutline), asCALL_CDECL);
r = engine->RegisterGlobalFunction("void SetModelRendererOutline(uint, float)",
asFUNCTION(AS_SetModelRendererOutline), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetSpriteShader(uint, uint)",
asFUNCTION(AS_SetSpriteShader), asCALL_CDECL);
r = engine->RegisterGlobalFunction("void SetModelRendererShader(uint, uint)",
asFUNCTION(AS_SetModelRendererShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool HasSprite(uint)",
asFUNCTION(AS_HasSprite), asCALL_CDECL);
r = engine->RegisterGlobalFunction("bool HasModelRenderer(uint)",
asFUNCTION(AS_HasModelRenderer), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void RemoveSprite(uint)",
asFUNCTION(AS_RemoveSprite), asCALL_CDECL);
r = engine->RegisterGlobalFunction("void RemoveModelRenderer(uint)",
asFUNCTION(AS_RemoveModelRenderer), asCALL_CDECL);
assert(r >= 0);
// Texture component
r = engine->RegisterGlobalFunction("void AddTexture(uint, uint)",
asFUNCTION(AS_AddTexture), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetTextureId(uint, uint)",
asFUNCTION(AS_SetTextureId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetTextureId(uint)",
asFUNCTION(AS_GetTextureId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool HasTexture(uint)",
asFUNCTION(AS_HasTexture), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void RemoveTexture(uint)",
asFUNCTION(AS_RemoveTexture), asCALL_CDECL);
assert(r >= 0);
// UV Transform component
r = engine->RegisterGlobalFunction("void AddUVTransform(uint, float, float, float = 0.0f, float = 0.0f, float = 0.0f)",
asFUNCTION(AS_AddUVTransform), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetUVScale(uint, float, float)",
asFUNCTION(AS_SetUVScale), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetUVOffset(uint, float, float)",
asFUNCTION(AS_SetUVOffset), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetUVRotation(uint, float)",
asFUNCTION(AS_SetUVRotation), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool HasUVTransform(uint)",
asFUNCTION(AS_HasUVTransform), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void RemoveUVTransform(uint)",
asFUNCTION(AS_RemoveUVTransform), asCALL_CDECL);
assert(r >= 0);
// Tag component
@@ -481,6 +664,23 @@ void RegisterECSBindings(asIScriptEngine *engine) {
asFUNCTION(AS_RemoveTag), asCALL_CDECL);
assert(r >= 0);
// Hierarchy component
r = engine->RegisterGlobalFunction("void SetParent(uint, uint)",
asFUNCTION(AS_SetParent), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void ClearParent(uint)",
asFUNCTION(AS_ClearParent), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetParent(uint)",
asFUNCTION(AS_GetParent), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool HasParent(uint)",
asFUNCTION(AS_HasParent), asCALL_CDECL);
assert(r >= 0);
// Camera3D component
r = engine->RegisterGlobalFunction("void AddCamera3D(uint, float, float, float, float, float, float, float)",
asFUNCTION(AS_AddCamera3D), asCALL_CDECL);
+252
View File
@@ -0,0 +1,252 @@
#include "scripting/GlobalsBindings.h"
#include "scriptarray.h"
#include <angelscript.h>
#include <cassert>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
namespace {
enum class ValueType {
String,
Int,
UInt,
Int64,
Float,
Double,
Bool,
Bytes
};
using ValueData = std::variant<int, asUINT, int64_t, float, double, bool, std::string, std::vector<uint8_t>>;
struct Value {
ValueType type;
ValueData data;
};
std::unordered_map<std::string, Value> g_values;
std::vector<uint8_t> ArrayToBytes(const CScriptArray* value) {
std::vector<uint8_t> bytes;
if (!value) {
return bytes;
}
const asUINT size = value->GetSize();
bytes.resize(size);
for (asUINT i = 0; i < size; ++i) {
const uint8_t* element = static_cast<const uint8_t*>(value->At(i));
bytes[i] = *element;
}
return bytes;
}
bool BytesToArray(const std::vector<uint8_t>& bytes, CScriptArray* outValue) {
if (!outValue) {
return false;
}
outValue->Resize(static_cast<asUINT>(bytes.size()));
for (asUINT i = 0; i < outValue->GetSize(); ++i) {
*static_cast<uint8_t*>(outValue->At(i)) = bytes[i];
}
return true;
}
bool SetValue(const std::string& key, ValueType type, ValueData data) {
g_values[key] = Value{type, std::move(data)};
return true;
}
const Value* GetValue(const std::string& key, ValueType type) {
auto it = g_values.find(key);
if (it == g_values.end()) {
return nullptr;
}
if (it->second.type != type) {
return nullptr;
}
return &it->second;
}
bool AS_SetString(const std::string& key, const std::string& value) {
return SetValue(key, ValueType::String, value);
}
bool AS_SetInt(const std::string& key, int value) {
return SetValue(key, ValueType::Int, value);
}
bool AS_SetUInt(const std::string& key, asUINT value) {
return SetValue(key, ValueType::UInt, value);
}
bool AS_SetInt64(const std::string& key, int64_t value) {
return SetValue(key, ValueType::Int64, value);
}
bool AS_SetFloat(const std::string& key, float value) {
return SetValue(key, ValueType::Float, value);
}
bool AS_SetDouble(const std::string& key, double value) {
return SetValue(key, ValueType::Double, value);
}
bool AS_SetBool(const std::string& key, bool value) {
return SetValue(key, ValueType::Bool, value);
}
bool AS_SetArray(const std::string& key, const CScriptArray* value) {
return SetValue(key, ValueType::Bytes, ArrayToBytes(value));
}
bool AS_GetString(const std::string& key, std::string& outValue) {
const Value* value = GetValue(key, ValueType::String);
if (!value) {
return false;
}
outValue = std::get<std::string>(value->data);
return true;
}
bool AS_GetInt(const std::string& key, int& outValue) {
const Value* value = GetValue(key, ValueType::Int);
if (!value) {
return false;
}
outValue = std::get<int>(value->data);
return true;
}
bool AS_GetUInt(const std::string& key, asUINT& outValue) {
const Value* value = GetValue(key, ValueType::UInt);
if (!value) {
return false;
}
outValue = std::get<asUINT>(value->data);
return true;
}
bool AS_GetInt64(const std::string& key, int64_t& outValue) {
const Value* value = GetValue(key, ValueType::Int64);
if (!value) {
return false;
}
outValue = std::get<int64_t>(value->data);
return true;
}
bool AS_GetFloat(const std::string& key, float& outValue) {
const Value* value = GetValue(key, ValueType::Float);
if (!value) {
return false;
}
outValue = std::get<float>(value->data);
return true;
}
bool AS_GetDouble(const std::string& key, double& outValue) {
const Value* value = GetValue(key, ValueType::Double);
if (!value) {
return false;
}
outValue = std::get<double>(value->data);
return true;
}
bool AS_GetBool(const std::string& key, bool& outValue) {
const Value* value = GetValue(key, ValueType::Bool);
if (!value) {
return false;
}
outValue = std::get<bool>(value->data);
return true;
}
bool AS_GetArray(const std::string& key, CScriptArray* outValue) {
const Value* value = GetValue(key, ValueType::Bytes);
if (!value) {
return false;
}
return BytesToArray(std::get<std::vector<uint8_t>>(value->data), outValue);
}
bool AS_Has(const std::string& key) {
return g_values.find(key) != g_values.end();
}
bool AS_Remove(const std::string& key) {
return g_values.erase(key) > 0;
}
} // namespace
void RegisterGlobalsBindings(asIScriptEngine* engine) {
int r;
engine->SetDefaultNamespace("Globals");
r = engine->RegisterGlobalFunction("bool Set(const string &in key, const string &in value)",
asFUNCTION(AS_SetString), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, int value)",
asFUNCTION(AS_SetInt), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, uint value)",
asFUNCTION(AS_SetUInt), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, int64 value)",
asFUNCTION(AS_SetInt64), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, float value)",
asFUNCTION(AS_SetFloat), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, double value)",
asFUNCTION(AS_SetDouble), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, bool value)",
asFUNCTION(AS_SetBool), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Set(const string &in key, const array<uint8> &in value)",
asFUNCTION(AS_SetArray), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, string &out value)",
asFUNCTION(AS_GetString), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, int &out value)",
asFUNCTION(AS_GetInt), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, uint &out value)",
asFUNCTION(AS_GetUInt), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, int64 &out value)",
asFUNCTION(AS_GetInt64), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, float &out value)",
asFUNCTION(AS_GetFloat), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, double &out value)",
asFUNCTION(AS_GetDouble), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, bool &out value)",
asFUNCTION(AS_GetBool), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Get(const string &in key, array<uint8> &out value)",
asFUNCTION(AS_GetArray), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Has(const string &in key)",
asFUNCTION(AS_Has), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Remove(const string &in key)",
asFUNCTION(AS_Remove), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
}
+76
View File
@@ -38,10 +38,22 @@ static void AS_BindMouseButton(const std::string& actionName, int mouseButton) {
if (g_inputManager) g_inputManager->BindMouseButton(actionName, mouseButton);
}
static void AS_BindGamepadButton(const std::string& actionName, int gamepadId, int button) {
if (g_inputManager) g_inputManager->BindGamepadButton(actionName, gamepadId, button);
}
static void AS_BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative) {
if (g_inputManager) g_inputManager->BindGamepadAxis(actionName, gamepadId, axis, threshold, negative);
}
static void AS_ClearBindings(const std::string& actionName) {
if (g_inputManager) g_inputManager->ClearBindings(actionName);
}
static void AS_ClearAllActions() {
if (g_inputManager) g_inputManager->ClearAllActions();
}
void RegisterInputBindings(asIScriptEngine* engine) {
int r;
@@ -74,10 +86,22 @@ void RegisterInputBindings(asIScriptEngine* engine) {
r = engine->RegisterGlobalFunction("void BindMouseButton(const string &in, int)",
asFUNCTION(AS_BindMouseButton), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void BindGamepadButton(const string &in, int, int)",
asFUNCTION(AS_BindGamepadButton), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void BindGamepadAxis(const string &in, int, int, float = 0.5f, bool = false)",
asFUNCTION(AS_BindGamepadAxis), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void ClearBindings(const string &in)",
asFUNCTION(AS_ClearBindings), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void ClearAllActions()",
asFUNCTION(AS_ClearAllActions), asCALL_CDECL);
assert(r >= 0);
// Key constants for binding
r = engine->RegisterEnum("Key");
@@ -114,4 +138,56 @@ void RegisterInputBindings(asIScriptEngine* engine) {
assert(r >= 0);
r = engine->RegisterEnumValue("MouseButton", "MIDDLE", MOUSE_BUTTON_MIDDLE);
assert(r >= 0);
r = engine->RegisterEnum("GamepadButton");
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_UP", GAMEPAD_BUTTON_LEFT_FACE_UP);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_RIGHT", GAMEPAD_BUTTON_LEFT_FACE_RIGHT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_DOWN", GAMEPAD_BUTTON_LEFT_FACE_DOWN);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_LEFT", GAMEPAD_BUTTON_LEFT_FACE_LEFT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_UP", GAMEPAD_BUTTON_RIGHT_FACE_UP);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_RIGHT", GAMEPAD_BUTTON_RIGHT_FACE_RIGHT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_DOWN", GAMEPAD_BUTTON_RIGHT_FACE_DOWN);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_LEFT", GAMEPAD_BUTTON_RIGHT_FACE_LEFT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_TRIGGER_1", GAMEPAD_BUTTON_LEFT_TRIGGER_1);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_TRIGGER_2", GAMEPAD_BUTTON_LEFT_TRIGGER_2);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_TRIGGER_1", GAMEPAD_BUTTON_RIGHT_TRIGGER_1);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_TRIGGER_2", GAMEPAD_BUTTON_RIGHT_TRIGGER_2);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE_LEFT", GAMEPAD_BUTTON_MIDDLE_LEFT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE", GAMEPAD_BUTTON_MIDDLE);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE_RIGHT", GAMEPAD_BUTTON_MIDDLE_RIGHT);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "LEFT_THUMB", GAMEPAD_BUTTON_LEFT_THUMB);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_THUMB", GAMEPAD_BUTTON_RIGHT_THUMB);
assert(r >= 0);
r = engine->RegisterEnum("GamepadAxis");
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_X", GAMEPAD_AXIS_LEFT_X);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_Y", GAMEPAD_AXIS_LEFT_Y);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_X", GAMEPAD_AXIS_RIGHT_X);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_Y", GAMEPAD_AXIS_RIGHT_Y);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_TRIGGER", GAMEPAD_AXIS_LEFT_TRIGGER);
assert(r >= 0);
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_TRIGGER", GAMEPAD_AXIS_RIGHT_TRIGGER);
assert(r >= 0);
}
+73
View File
@@ -0,0 +1,73 @@
#include "scripting/PhysFSBindings.h"
#include "PhysFSManager.h"
#include "scriptarray.h"
#include <angelscript.h>
#include <cassert>
static asIScriptEngine* g_engine = nullptr;
static bool AS_Mount(const std::string& path, const std::string& mountPoint, bool append) {
return PhysFSManager::MountPath(path, mountPoint, append);
}
static bool AS_Unmount(const std::string& path) {
return PhysFSManager::UnmountPath(path);
}
static bool AS_Exists(const std::string& path) {
return PhysFSManager::Exists(path);
}
static bool AS_IsDirectory(const std::string& path) {
return PhysFSManager::IsDirectory(path);
}
static std::string AS_GetRealDir(const std::string& path) {
return PhysFSManager::GetRealDir(path);
}
static CScriptArray* AS_ListFiles(const std::string& path) {
if (!g_engine) return nullptr;
const std::vector<std::string> files = PhysFSManager::ListFiles(path);
asITypeInfo* arrayType = g_engine->GetTypeInfoByDecl("array<string>");
if (!arrayType) return nullptr;
CScriptArray* array = CScriptArray::Create(arrayType, static_cast<asUINT>(files.size()));
for (asUINT i = 0; i < static_cast<asUINT>(files.size()); ++i) {
static_cast<std::string*>(array->At(i))->assign(files[i]);
}
return array;
}
void RegisterPhysFSBindings(asIScriptEngine* engine) {
int r;
g_engine = engine;
engine->SetDefaultNamespace("PhysFS");
r = engine->RegisterGlobalFunction("bool Mount(const string &in, const string &in mountPoint = \"\", bool append = true)",
asFUNCTION(AS_Mount), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Unmount(const string &in)",
asFUNCTION(AS_Unmount), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool Exists(const string &in)",
asFUNCTION(AS_Exists), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool IsDirectory(const string &in)",
asFUNCTION(AS_IsDirectory), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("string GetRealDir(const string &in)",
asFUNCTION(AS_GetRealDir), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("array<string>@ ListFiles(const string &in)",
asFUNCTION(AS_ListFiles), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
}
+258 -7
View File
@@ -2,6 +2,8 @@
#include "ShaderManager.h"
#include "ModelManager.h"
#include "MaterialManager.h"
#include "TextureManager.h"
#include "AssetManager.h"
#include <angelscript.h>
#include <assert.h>
#include "raylib.h"
@@ -10,11 +12,18 @@
static ShaderManager* g_shaderManager = nullptr;
static ModelManager* g_modelManager = nullptr;
static MaterialManager* g_materialManager = nullptr;
static TextureManager* g_textureManager = nullptr;
static AssetManager* g_assetManager = nullptr;
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr) {
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr, TextureManager* textureMgr) {
g_shaderManager = shaderMgr;
g_modelManager = modelMgr;
g_materialManager = materialMgr;
g_textureManager = textureMgr;
}
void SetAssetManager(AssetManager* assetMgr) {
g_assetManager = assetMgr;
}
// === Shader Management ===
@@ -38,20 +47,17 @@ unsigned int AS_LoadModel(const std::string& 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);
return g_modelManager->LoadCube(width, height, length);
}
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);
return g_modelManager->LoadSphere(radius, rings, slices);
}
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);
return g_modelManager->LoadPlane(width, length, resX, resZ);
}
void AS_UnloadModel(unsigned int id) {
@@ -83,6 +89,141 @@ void AS_SetMaterialAlbedo(unsigned int id, unsigned char r, unsigned char g, uns
g_materialManager->SetAlbedo(id, albedo);
}
// === Texture Management ===
unsigned int AS_LoadTextureAsset(const std::string& path) {
if (!g_textureManager) return 0;
return g_textureManager->Load(path);
}
void AS_UnloadTextureAsset(unsigned int id) {
if (!g_textureManager) return;
g_textureManager->Unload(id);
}
// === Asset Manager ===
unsigned int AS_AssetLoadTexture(const std::string& key, const std::string& path) {
if (!g_assetManager) return 0;
return g_assetManager->LoadTexture(key, path);
}
unsigned int AS_AssetLoadShader(const std::string& key, const std::string& vsPath, const std::string& fsPath) {
if (!g_assetManager) return 0;
return g_assetManager->LoadShader(key, vsPath, fsPath);
}
unsigned int AS_AssetLoadModel(const std::string& key, const std::string& path) {
if (!g_assetManager) return 0;
return g_assetManager->LoadModel(key, path);
}
unsigned int AS_AssetLoadCube(const std::string& key, float width, float height, float length) {
if (!g_assetManager) return 0;
return g_assetManager->LoadCube(key, width, height, length);
}
unsigned int AS_AssetLoadPoly(const std::string& key, int sides, float radius) {
if (!g_assetManager) return 0;
return g_assetManager->LoadPoly(key, sides, radius);
}
unsigned int AS_AssetLoadSphere(const std::string& key, float radius, int rings, int slices) {
if (!g_assetManager) return 0;
return g_assetManager->LoadSphere(key, radius, rings, slices);
}
unsigned int AS_AssetLoadHemiSphere(const std::string& key, float radius, int rings, int slices) {
if (!g_assetManager) return 0;
return g_assetManager->LoadHemiSphere(key, radius, rings, slices);
}
unsigned int AS_AssetLoadPlane(const std::string& key, float width, float length, int resX, int resZ) {
if (!g_assetManager) return 0;
return g_assetManager->LoadPlane(key, width, length, resX, resZ);
}
unsigned int AS_AssetLoadCylinder(const std::string& key, float radius, float height, int slices) {
if (!g_assetManager) return 0;
return g_assetManager->LoadCylinder(key, radius, height, slices);
}
unsigned int AS_AssetLoadCone(const std::string& key, float radius, float height, int slices) {
if (!g_assetManager) return 0;
return g_assetManager->LoadCone(key, radius, height, slices);
}
unsigned int AS_AssetLoadTorus(const std::string& key, float radius, float size, int radSeg, int sides) {
if (!g_assetManager) return 0;
return g_assetManager->LoadTorus(key, radius, size, radSeg, sides);
}
unsigned int AS_AssetLoadKnot(const std::string& key, float radius, float size, int radSeg, int sides) {
if (!g_assetManager) return 0;
return g_assetManager->LoadKnot(key, radius, size, radSeg, sides);
}
unsigned int AS_AssetLoadHeightmap(const std::string& key, const std::string& heightmapPath, float sizeX, float sizeY, float sizeZ) {
if (!g_assetManager) return 0;
return g_assetManager->LoadHeightmap(key, heightmapPath, sizeX, sizeY, sizeZ);
}
unsigned int AS_AssetLoadCubicmap(const std::string& key, const std::string& cubicmapPath, float sizeX, float sizeY, float sizeZ) {
if (!g_assetManager) return 0;
return g_assetManager->LoadCubicmap(key, cubicmapPath, sizeX, sizeY, sizeZ);
}
unsigned int AS_AssetCreateMaterial(const std::string& key, unsigned int shaderId, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
if (!g_assetManager) return 0;
Color albedo = {r, g, b, a};
return g_assetManager->CreateMaterial(key, shaderId, albedo);
}
bool AS_AssetUnloadTexture(const std::string& key) {
if (!g_assetManager) return false;
return g_assetManager->UnloadTexture(key);
}
bool AS_AssetUnloadShader(const std::string& key) {
if (!g_assetManager) return false;
return g_assetManager->UnloadShader(key);
}
bool AS_AssetUnloadModel(const std::string& key) {
if (!g_assetManager) return false;
return g_assetManager->UnloadModel(key);
}
bool AS_AssetUnloadMaterial(const std::string& key) {
if (!g_assetManager) return false;
return g_assetManager->UnloadMaterial(key);
}
unsigned int AS_AssetGetTextureId(const std::string& key) {
if (!g_assetManager) return 0;
return g_assetManager->GetTextureId(key);
}
unsigned int AS_AssetGetShaderId(const std::string& key) {
if (!g_assetManager) return 0;
return g_assetManager->GetShaderId(key);
}
unsigned int AS_AssetGetModelId(const std::string& key) {
if (!g_assetManager) return 0;
return g_assetManager->GetModelId(key);
}
unsigned int AS_AssetGetMaterialId(const std::string& key) {
if (!g_assetManager) return 0;
return g_assetManager->GetMaterialId(key);
}
bool AS_AssetApplyMeshTangents(const std::string& key, int meshIndex) {
if (!g_assetManager) return false;
return g_assetManager->ApplyMeshTangents(key, meshIndex);
}
void RegisterResourceBindings(asIScriptEngine *engine)
{
int r;
@@ -140,5 +281,115 @@ void RegisterResourceBindings(asIScriptEngine *engine)
asFUNCTION(AS_SetMaterialAlbedo), asCALL_CDECL);
assert(r >= 0);
// === TextureAsset namespace ===
engine->SetDefaultNamespace("TextureAsset");
r = engine->RegisterGlobalFunction("uint Load(const string &in)",
asFUNCTION(AS_LoadTextureAsset), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Unload(uint)",
asFUNCTION(AS_UnloadTextureAsset), asCALL_CDECL);
assert(r >= 0);
// === Asset namespace ===
engine->SetDefaultNamespace("Asset");
r = engine->RegisterGlobalFunction("uint LoadTexture(const string &in, const string &in)",
asFUNCTION(AS_AssetLoadTexture), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadShader(const string &in, const string &in, const string &in)",
asFUNCTION(AS_AssetLoadShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadModel(const string &in, const string &in)",
asFUNCTION(AS_AssetLoadModel), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCube(const string &in, float, float, float)",
asFUNCTION(AS_AssetLoadCube), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadPoly(const string &in, int, float)",
asFUNCTION(AS_AssetLoadPoly), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadSphere(const string &in, float, int, int)",
asFUNCTION(AS_AssetLoadSphere), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadHemiSphere(const string &in, float, int, int)",
asFUNCTION(AS_AssetLoadHemiSphere), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadPlane(const string &in, float, float, int, int)",
asFUNCTION(AS_AssetLoadPlane), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCylinder(const string &in, float, float, int)",
asFUNCTION(AS_AssetLoadCylinder), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCone(const string &in, float, float, int)",
asFUNCTION(AS_AssetLoadCone), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadTorus(const string &in, float, float, int, int)",
asFUNCTION(AS_AssetLoadTorus), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadKnot(const string &in, float, float, int, int)",
asFUNCTION(AS_AssetLoadKnot), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadHeightmap(const string &in, const string &in, float, float, float)",
asFUNCTION(AS_AssetLoadHeightmap), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCubicmap(const string &in, const string &in, float, float, float)",
asFUNCTION(AS_AssetLoadCubicmap), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint CreateMaterial(const string &in, uint, uint8, uint8, uint8, uint8)",
asFUNCTION(AS_AssetCreateMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool UnloadTexture(const string &in)",
asFUNCTION(AS_AssetUnloadTexture), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool UnloadShader(const string &in)",
asFUNCTION(AS_AssetUnloadShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool UnloadModel(const string &in)",
asFUNCTION(AS_AssetUnloadModel), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool UnloadMaterial(const string &in)",
asFUNCTION(AS_AssetUnloadMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetTextureId(const string &in)",
asFUNCTION(AS_AssetGetTextureId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetShaderId(const string &in)",
asFUNCTION(AS_AssetGetShaderId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetModelId(const string &in)",
asFUNCTION(AS_AssetGetModelId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint GetMaterialId(const string &in)",
asFUNCTION(AS_AssetGetMaterialId), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool ApplyMeshTangents(const string &in, int = 0)",
asFUNCTION(AS_AssetApplyMeshTangents), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
}
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
#include "scripting/SceneBindings.h"
#include "SceneLoader.h"
#include <angelscript.h>
#include <assert.h>
static unsigned int AS_LoadScene(const std::string& path, bool clearExisting) {
entt::entity root = LoadSceneFromFile(path, clearExisting);
return static_cast<unsigned int>(root);
}
static void AS_ClearScene() {
ClearScene();
}
void RegisterSceneBindings(asIScriptEngine* engine) {
int r;
engine->SetDefaultNamespace("Scene");
r = engine->RegisterGlobalFunction("uint Load(const string &in, bool clearExisting = true)",
asFUNCTION(AS_LoadScene), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Clear()", asFUNCTION(AS_ClearScene), asCALL_CDECL);
assert(r >= 0);
engine->SetDefaultNamespace("");
}
+8
View File
@@ -7,6 +7,10 @@
#include "scripting/ECSBindings.h"
#include "scripting/ResourceBindings.h"
#include "scripting/InputBindings.h"
#include "scripting/SceneBindings.h"
#include "scripting/PhysFSBindings.h"
#include "scripting/SNKVBindings.h"
#include "scripting/GlobalsBindings.h"
void ScriptBindings::RegisterAll(asIScriptEngine *engine)
{
@@ -19,4 +23,8 @@ void ScriptBindings::RegisterAll(asIScriptEngine *engine)
RegisterECSBindings(engine);
RegisterResourceBindings(engine);
RegisterInputBindings(engine);
RegisterSceneBindings(engine);
RegisterPhysFSBindings(engine);
RegisterSNKVBindings(engine);
RegisterGlobalsBindings(engine);
}
+230 -14
View File
@@ -3,8 +3,7 @@
#include "scriptstdstring.h"
#include "scriptbuilder.h"
#include "scriptarray.h"
#include <fstream>
#include <sstream>
#include "PhysFSManager.h"
#include <cstring>
#include <assert.h>
#include "../log/log.h"
@@ -110,6 +109,12 @@ void ScriptEngine::Shutdown() {
bool ScriptEngine::CompileScript(const std::string& filename) {
if (!engine) return false;
std::string source;
if (!PhysFSManager::ReadTextFile(filename, source)) {
log_error("Failed to read script file: %s", filename.c_str());
return false;
}
// Try to compile in a temporary module first using CScriptBuilder
CScriptBuilder tempBuilder;
tempBuilder.SetIncludeCallback(IncludeCallback, nullptr);
@@ -120,7 +125,7 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
return false;
}
r = tempBuilder.AddSectionFromFile(filename.c_str());
r = tempBuilder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
if (r < 0) {
log_error("Failed to add script section from file: %s", filename.c_str());
engine->DiscardModule("temp");
@@ -146,7 +151,7 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
r = mainBuilder.StartNewModule(engine, "main");
if (r >= 0) {
r = mainBuilder.AddSectionFromFile(filename.c_str());
r = mainBuilder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
}
if (r >= 0) {
r = mainBuilder.BuildModule();
@@ -165,15 +170,68 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
// Get the newly created module
currentModule = engine->GetModule("main");
// Cache new functions
initFunc = currentModule->GetFunctionByName("Init");
updateFunc = currentModule->GetFunctionByName("Update");
shutdownFunc = currentModule->GetFunctionByName("Shutdown");
// Cache new functions (scene-specific names preferred)
initFunc = currentModule->GetFunctionByDecl("void SceneInit()");
if (!initFunc) {
initFunc = currentModule->GetFunctionByDecl("void Init()");
}
updateFunc = currentModule->GetFunctionByDecl("void SceneUpdate(float)");
if (!updateFunc) {
updateFunc = currentModule->GetFunctionByDecl("void Update(float)");
}
shutdownFunc = currentModule->GetFunctionByDecl("void SceneShutdown()");
if (!shutdownFunc) {
shutdownFunc = currentModule->GetFunctionByDecl("void Shutdown()");
}
hasValidScript = true;
log_info("Script compiled and cached: %s", filename.c_str());
return true;
}
bool ScriptEngine::CompileModule(const std::string& moduleName, const std::string& filename, asIScriptModule** outModule) {
if (!engine) return false;
std::string source;
if (!PhysFSManager::ReadTextFile(filename, source)) {
log_error("Failed to read script module file: %s", filename.c_str());
return false;
}
if (engine->GetModule(moduleName.c_str())) {
engine->DiscardModule(moduleName.c_str());
}
CScriptBuilder builder;
builder.SetIncludeCallback(IncludeCallback, nullptr);
int r = builder.StartNewModule(engine, moduleName.c_str());
if (r < 0) {
log_error("Failed to start module: %s", moduleName.c_str());
return false;
}
r = builder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
if (r < 0) {
log_error("Failed to add script section from file: %s", filename.c_str());
engine->DiscardModule(moduleName.c_str());
return false;
}
r = builder.BuildModule();
if (r < 0) {
log_error("Failed to build module: %s", moduleName.c_str());
engine->DiscardModule(moduleName.c_str());
return false;
}
if (outModule) {
*outModule = engine->GetModule(moduleName.c_str());
}
return true;
}
void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
if (!func || !engine || !hasValidScript) return;
@@ -200,6 +258,160 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
ctx->Release();
}
bool ScriptEngine::CallModuleFunction(asIScriptModule* module, const std::string& decl) {
if (!engine || !module) return false;
asIScriptFunction* func = module->GetFunctionByDecl(decl.c_str());
if (!func) return false;
asIScriptContext* ctx = engine->CreateContext();
if (!ctx) return false;
int r = ctx->Prepare(func);
if (r < 0) {
ctx->Release();
return false;
}
r = ctx->Execute();
if (r != asEXECUTION_FINISHED) {
if (r == asEXECUTION_EXCEPTION) {
log_error("Script exception: %s", ctx->GetExceptionString());
}
ctx->Release();
return false;
}
ctx->Release();
return true;
}
void ScriptEngine::CallScriptFunction(asIScriptFunction* func, unsigned int entityId, float dt) {
if (!func || !engine) return;
asIScriptContext* ctx = engine->CreateContext();
if (!ctx) return;
int r = ctx->Prepare(func);
if (r < 0) {
ctx->Release();
return;
}
const asUINT paramCount = func->GetParamCount();
if (paramCount >= 1) {
ctx->SetArgDWord(0, entityId);
}
if (paramCount >= 2) {
ctx->SetArgFloat(1, dt);
}
r = ctx->Execute();
if (r != asEXECUTION_FINISHED) {
if (r == asEXECUTION_EXCEPTION) {
log_error("Script exception: %s", ctx->GetExceptionString());
}
}
ctx->Release();
}
asIScriptObject* ScriptEngine::CreateObjectInstance(asIScriptModule* module, const std::string& className)
{
if (!engine || !module) return nullptr;
asITypeInfo* typeInfo = nullptr;
int count = module->GetObjectTypeCount();
for (int i = 0; i < count; ++i) {
asITypeInfo* t = module->GetObjectTypeByIndex(i);
const char* n = t->GetName();
if (n && className == n) {
typeInfo = t;
break;
}
}
if (!typeInfo) return nullptr;
void* raw = engine->CreateScriptObject(typeInfo);
return reinterpret_cast<asIScriptObject*>(raw);
}
void ScriptEngine::ReleaseObjectInstance(asIScriptObject* obj)
{
if (!obj) return;
// Prefer using the engine helper to properly destroy the script object
asITypeInfo* ti = obj->GetObjectType();
if (engine && ti) {
engine->ReleaseScriptObject(obj, ti);
} else {
obj->Release();
}
}
asIScriptFunction* ScriptEngine::GetObjectMethod(asIScriptModule* module, const std::string& className, const std::string& methodDecl)
{
if (!module) return nullptr;
asITypeInfo* typeInfo = module->GetTypeInfoByName(className.c_str());
if (!typeInfo) return nullptr;
return typeInfo->GetMethodByDecl(methodDecl.c_str());
}
void ScriptEngine::CallObjectMethod(asIScriptFunction* method, asIScriptObject* object, unsigned int entityId, float dt)
{
if (!method || !engine || !object) return;
asIScriptContext* ctx = engine->CreateContext();
if (!ctx) return;
int r = ctx->Prepare(method);
if (r < 0) { ctx->Release(); return; }
// Set the object instance for the call
ctx->SetObject(object);
const asUINT paramCount = method->GetParamCount();
if (paramCount >= 1) {
ctx->SetArgDWord(0, entityId);
}
if (paramCount >= 2) {
ctx->SetArgFloat(1, dt);
}
r = ctx->Execute();
if (r != asEXECUTION_FINISHED) {
if (r == asEXECUTION_EXCEPTION) {
log_error("Script exception: %s", ctx->GetExceptionString());
}
}
ctx->Release();
}
void ScriptEngine::CallSceneObjectMethod(asIScriptFunction* method, asIScriptObject* object, float dt)
{
if (!method || !engine || !object) return;
asIScriptContext* ctx = engine->CreateContext();
if (!ctx) return;
int r = ctx->Prepare(method);
if (r < 0) { ctx->Release(); return; }
ctx->SetObject(object);
const asUINT paramCount = method->GetParamCount();
if (paramCount >= 1) {
ctx->SetArgFloat(0, dt);
}
r = ctx->Execute();
if (r != asEXECUTION_FINISHED) {
if (r == asEXECUTION_EXCEPTION) {
log_error("Script exception: %s", ctx->GetExceptionString());
}
}
ctx->Release();
}
void ScriptEngine::ClearCachedFunctions() {
updateFunc = nullptr;
hasValidScript = false;
@@ -258,7 +470,11 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
int result = -1;
for (auto &cand : candidates) {
result = builder->AddSectionFromFile(cand.c_str());
std::string source;
if (!PhysFSManager::ReadTextFile(cand, source)) {
continue;
}
result = builder->AddSectionFromMemory(cand.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
if (result >= 0) {
log_info("Successfully included: %s", cand.c_str());
return result;
@@ -270,9 +486,9 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
}
std::string ScriptEngine::ReadFile(const std::string& filename) {
std::ifstream file(filename);
if (!file) return "";
std::stringstream ss;
ss << file.rdbuf();
return ss.str();
std::string out;
if (!PhysFSManager::ReadTextFile(filename, out)) {
return std::string();
}
return out;
}
+16 -1
View File
@@ -3,9 +3,12 @@ cmake_minimum_required(VERSION 3.16)
# Unit tests just need test sources + link to simian_core
add_executable(unit_tests
test.cpp
physfs_test_utils.cpp
hot_reload.cpp
script_engine_tests.cpp
math_bindings_tests.cpp
resource_tests.cpp
scenegraph_tests.cpp
)
target_include_directories(unit_tests PRIVATE
@@ -26,4 +29,16 @@ 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)
add_test(NAME math_bindings COMMAND unit_tests math_bindings)
add_test(NAME shader_manager COMMAND unit_tests shader_manager)
add_test(NAME model_manager COMMAND unit_tests model_manager)
add_test(NAME material_manager COMMAND unit_tests material_manager)
add_test(NAME asset_registry_material COMMAND unit_tests asset_registry_material)
add_test(NAME resource_bindings COMMAND unit_tests resource_bindings)
add_test(NAME scenegraph_world COMMAND unit_tests scenegraph_world)
add_test(NAME scenegraph_detach COMMAND unit_tests scenegraph_detach)
add_test(NAME scene_loader COMMAND unit_tests scene_loader)
add_test(NAME scene_loader_script_path COMMAND unit_tests scene_loader_script_path)
add_test(NAME scene_loader_clear_script COMMAND unit_tests scene_loader_clear_script)
add_test(NAME scene_loader_assets COMMAND unit_tests scene_loader_assets)
add_test(NAME scene_loader_sandbox COMMAND unit_tests scene_loader_sandbox)
+7 -1
View File
@@ -6,6 +6,7 @@
#include <thread>
#include <chrono>
#include "../include/HotReload.h"
#include "physfs_test_utils.h"
using namespace std::chrono_literals;
@@ -18,7 +19,10 @@ void test_hotreload_directory_watch(void) {
ofs << "// test" << std::endl;
ofs.close();
HotReload hr(tmpDir.string());
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "tmp_watch") == true);
HotReload hr("tmp_watch");
TEST_CHECK(hr.CheckForChanges() == false);
std::this_thread::sleep_for(1s);
@@ -28,6 +32,8 @@ void test_hotreload_directory_watch(void) {
TEST_CHECK(hr.CheckForChanges() == true);
ShutdownPhysFSTest();
std::filesystem::remove(f);
std::filesystem::remove(tmpDir);
}
+7 -1
View File
@@ -4,6 +4,7 @@
#include <fstream>
#include <cmath>
#include "../include/scripting/ScriptEngine.h"
#include "physfs_test_utils.h"
void test_math_bindings(void) {
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_math";
@@ -20,9 +21,12 @@ void test_math_bindings(void) {
script << "float testAbs = Math::Abs(-42.5f);\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
ScriptEngine se;
TEST_CHECK(se.Initialize() == true);
bool compiled = se.CompileScript(scriptPath.string());
bool compiled = se.CompileScript("math_test.as");
TEST_CHECK(compiled == true);
asIScriptEngine* engine = se.GetEngine();
@@ -58,6 +62,8 @@ void test_math_bindings(void) {
se.Shutdown();
ShutdownPhysFSTest();
std::filesystem::remove(scriptPath);
std::filesystem::remove(tmpDir);
}
+32
View File
@@ -0,0 +1,32 @@
#include "physfs_test_utils.h"
#include "PhysFSManager.h"
static bool g_initialized = false;
bool InitPhysFSTest(const std::filesystem::path& writeDir) {
if (!g_initialized) {
if (!PhysFSManager::Initialize("unit_tests")) {
return false;
}
g_initialized = true;
}
PhysFSManager::SetEditorEnabled(true);
PhysFSManager::SetWriteDir(writeDir.string());
return true;
}
bool MountPhysFSTest(const std::filesystem::path& realPath, const std::string& mountPoint) {
return PhysFSManager::MountPath(realPath.string(), mountPoint, true);
}
bool UnmountPhysFSTest(const std::filesystem::path& realPath) {
return PhysFSManager::UnmountPath(realPath.string());
}
void ShutdownPhysFSTest() {
if (g_initialized) {
PhysFSManager::Shutdown();
g_initialized = false;
}
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <filesystem>
#include <string>
bool InitPhysFSTest(const std::filesystem::path& writeDir);
bool MountPhysFSTest(const std::filesystem::path& realPath, const std::string& mountPoint);
bool UnmountPhysFSTest(const std::filesystem::path& realPath);
void ShutdownPhysFSTest();
+293
View File
@@ -0,0 +1,293 @@
#define TEST_NO_MAIN
#include "acutest.h"
#include <filesystem>
#include <fstream>
#include <cstdlib>
#include "../include/ShaderManager.h"
#include "../include/ModelManager.h"
#include "../include/MaterialManager.h"
#include "../include/TextureManager.h"
#include "../include/AssetManager.h"
#include "../include/scripting/ScriptEngine.h"
#include "../include/scripting/ResourceBindings.h"
#include "raylib.h"
#include "physfs_test_utils.h"
extern "C" {
void acutest_skip_(const char* file, int line, const char* fmt, ...);
}
static bool HasDisplay() {
#if defined(_WIN32)
return true;
#elif defined(__APPLE__)
return true;
#else
const char* display = std::getenv("DISPLAY");
const char* wayland = std::getenv("WAYLAND_DISPLAY");
return (display && *display) || (wayland && *wayland);
#endif
}
static bool InitRaylibHidden() {
const char* forceHeadless = std::getenv("SIMIAN_HEADLESS");
if (forceHeadless && forceHeadless[0] == '1') {
return false;
}
if (!HasDisplay()) {
return false;
}
if (!IsWindowReady()) {
SetConfigFlags(FLAG_WINDOW_HIDDEN);
InitWindow(1, 1, "simian_test");
SetTargetFPS(60);
}
return IsWindowReady();
}
static void ShutdownRaylibHidden() {
if (IsWindowReady()) {
CloseWindow();
}
}
static std::filesystem::path WriteTempShader(const std::filesystem::path& dir, const std::string& name, const std::string& code) {
std::filesystem::path path = dir / name;
std::ofstream ofs(path);
ofs << code;
return path;
}
void test_shader_manager(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_shader";
std::filesystem::create_directories(tmpDir);
const std::string vsCode =
"#version 330\n"
"layout(location = 0) in vec3 vertexPosition;\n"
"uniform mat4 mvp;\n"
"void main() {\n"
" gl_Position = mvp * vec4(vertexPosition, 1.0);\n"
"}\n";
const std::string fsCode =
"#version 330\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";
auto vsPath = WriteTempShader(tmpDir, "test.vs", vsCode);
auto fsPath = WriteTempShader(tmpDir, "test.fs", fsCode);
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
ShaderManager shaderManager;
unsigned int shaderId = shaderManager.Load("test.vs", "test.fs");
TEST_CHECK(shaderId != 0);
TEST_CHECK(shaderManager.Get(shaderId) != nullptr);
shaderManager.Unload(shaderId);
TEST_CHECK(shaderManager.Get(shaderId) == nullptr);
ShutdownRaylibHidden();
std::filesystem::remove(vsPath);
std::filesystem::remove(fsPath);
std::filesystem::remove(tmpDir);
}
void test_model_manager(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
ModelManager modelManager;
Mesh mesh = GenMeshCube(1.0f, 1.0f, 1.0f);
unsigned int modelId = modelManager.LoadFromMesh(mesh);
TEST_CHECK(modelId != 0);
TEST_CHECK(modelManager.Get(modelId) != nullptr);
modelManager.Unload(modelId);
TEST_CHECK(modelManager.Get(modelId) == nullptr);
ShutdownRaylibHidden();
}
void test_material_manager(void) {
MaterialManager materialManager;
Color albedo = {1, 2, 3, 4};
unsigned int materialId = materialManager.Create(7, albedo);
TEST_CHECK(materialId != 0);
MaterialData* data = materialManager.Get(materialId);
TEST_CHECK(data != nullptr);
TEST_CHECK(data->shaderId == 7);
TEST_CHECK(data->albedo.r == 1);
TEST_CHECK(data->albedo.g == 2);
TEST_CHECK(data->albedo.b == 3);
TEST_CHECK(data->albedo.a == 4);
materialManager.SetShader(materialId, 42);
TEST_CHECK(data->shaderId == 42);
Color newAlbedo = {10, 20, 30, 40};
materialManager.SetAlbedo(materialId, newAlbedo);
TEST_CHECK(data->albedo.r == 10);
TEST_CHECK(data->albedo.g == 20);
TEST_CHECK(data->albedo.b == 30);
TEST_CHECK(data->albedo.a == 40);
materialManager.Remove(materialId);
TEST_CHECK(materialManager.Get(materialId) == nullptr);
}
void test_resource_bindings_script(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_resource";
std::filesystem::create_directories(tmpDir);
const std::string vsCode =
"#version 330\n"
"layout(location = 0) in vec3 vertexPosition;\n"
"uniform mat4 mvp;\n"
"void main() {\n"
" gl_Position = mvp * vec4(vertexPosition, 1.0);\n"
"}\n";
const std::string fsCode =
"#version 330\n"
"out vec4 fragColor;\n"
"void main() {\n"
" fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
"}\n";
auto vsPath = WriteTempShader(tmpDir, "script.vs", vsCode);
auto fsPath = WriteTempShader(tmpDir, "script.fs", fsCode);
std::filesystem::path scriptPath = tmpDir / "resource_test.as";
{
std::ofstream script(scriptPath);
script << "uint shaderId = 0;\n";
script << "uint modelId = 0;\n";
script << "uint materialId = 0;\n";
script << "void Init() {\n";
script << " shaderId = Shader::Load(\"script.vs\", \"script.fs\");\n";
script << " modelId = Model::LoadCube(1.0f, 1.0f, 1.0f);\n";
script << " materialId = Material::Create(shaderId, 255, 0, 0, 255);\n";
script << "}\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
{
ShaderManager shaderManager;
ModelManager modelManager;
MaterialManager materialManager;
TextureManager textureManager;
SetResourceManagers(&shaderManager, &modelManager, &materialManager, &textureManager);
ScriptEngine se;
TEST_CHECK(se.Initialize() == true);
bool compiled = se.CompileScript("resource_test.as");
TEST_CHECK(compiled == true);
asIScriptFunction* initFunc = se.GetInitFunction();
TEST_CHECK(initFunc != nullptr);
se.CallScriptFunction(initFunc);
asIScriptEngine* engine = se.GetEngine();
TEST_CHECK(engine != nullptr);
asIScriptModule* module = engine ? engine->GetModule("main") : nullptr;
TEST_CHECK(module != nullptr);
auto fetchUInt = [module](const char* name) -> unsigned int {
int idx = module->GetGlobalVarIndexByName(name);
TEST_CHECK_(idx >= 0, "Global '%s' not found", name);
if (idx < 0) return 0;
void* address = module->GetAddressOfGlobalVar(idx);
TEST_CHECK_(address != nullptr, "Global '%s' address null", name);
if (!address) return 0;
return *static_cast<unsigned int*>(address);
};
unsigned int shaderId = fetchUInt("shaderId");
unsigned int modelId = fetchUInt("modelId");
unsigned int materialId = fetchUInt("materialId");
TEST_CHECK(shaderId != 0);
TEST_CHECK(modelId != 0);
TEST_CHECK(materialId != 0);
se.Shutdown();
}
std::filesystem::remove(scriptPath);
std::filesystem::remove(vsPath);
std::filesystem::remove(fsPath);
std::filesystem::remove(tmpDir);
ShutdownRaylibHidden();
}
void test_asset_registry_material(void) {
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_asset_registry";
std::filesystem::create_directories(tmpDir);
std::filesystem::path registryPath = tmpDir / "assets" / "registry.toml";
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
MaterialManager materialManager;
AssetManager assets;
assets.SetManagers(nullptr, nullptr, &materialManager, nullptr);
Color albedo = {10, 20, 30, 40};
unsigned int materialId = assets.CreateMaterial("test_mat", 0, albedo);
TEST_CHECK(materialId != 0);
bool saved = assets.SaveRegistry("assets/registry.toml");
TEST_CHECK(saved == true);
assets.Clear();
materialManager.Clear();
MaterialManager materialManagerReload;
AssetManager assetsReload;
assetsReload.SetManagers(nullptr, nullptr, &materialManagerReload, nullptr);
bool loaded = assetsReload.LoadRegistry("assets/registry.toml");
TEST_CHECK(loaded == true);
unsigned int loadedId = assetsReload.GetMaterialId("test_mat");
TEST_CHECK(loadedId != 0);
MaterialData* data = materialManagerReload.Get(loadedId);
TEST_CHECK(data != nullptr);
if (data) {
TEST_CHECK(data->albedo.r == 10);
TEST_CHECK(data->albedo.g == 20);
TEST_CHECK(data->albedo.b == 30);
TEST_CHECK(data->albedo.a == 40);
}
ShutdownPhysFSTest();
std::filesystem::remove(registryPath);
std::filesystem::remove(registryPath.parent_path());
std::filesystem::remove(tmpDir);
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
void test_shader_manager(void);
void test_model_manager(void);
void test_material_manager(void);
void test_resource_bindings_script(void);
void test_asset_registry_material(void);
+370
View File
@@ -0,0 +1,370 @@
#define TEST_NO_MAIN
#include "acutest.h"
#include <filesystem>
#include <fstream>
#include <cstdlib>
#include "../include/ECSComponents.h"
#include "../include/SceneGraph.h"
#include "../include/SceneLoader.h"
#include "../include/ModelManager.h"
#include "../include/ShaderManager.h"
#include "../include/TextureManager.h"
#include "../include/AssetManager.h"
#include "raylib.h"
#include "entt.hpp"
#include "physfs_test_utils.h"
extern "C" {
void acutest_skip_(const char* file, int line, const char* fmt, ...);
}
static bool HasDisplay() {
#if defined(_WIN32)
return true;
#elif defined(__APPLE__)
return true;
#else
const char* display = std::getenv("DISPLAY");
const char* wayland = std::getenv("WAYLAND_DISPLAY");
return (display && *display) || (wayland && *wayland);
#endif
}
static bool InitRaylibHidden() {
const char* forceHeadless = std::getenv("SIMIAN_HEADLESS");
if (forceHeadless && forceHeadless[0] == '1') {
return false;
}
if (!HasDisplay()) {
return false;
}
if (!IsWindowReady()) {
SetConfigFlags(FLAG_WINDOW_HIDDEN);
InitWindow(1, 1, "simian_test_scene");
SetTargetFPS(60);
}
return IsWindowReady();
}
static void ShutdownRaylibHidden() {
if (IsWindowReady()) {
CloseWindow();
}
}
void test_scenegraph_world_transform(void) {
entt::registry registry;
entt::entity parent = registry.create();
entt::entity child = registry.create();
registry.emplace<ECSTransform>(parent, Vector3{1.0f, 0.0f, 0.0f}, Vector3{2.0f, 2.0f, 2.0f}, 0.0f);
registry.emplace<ECSTransform>(child, Vector3{1.0f, 0.0f, 0.0f}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f);
AttachChild(registry, child, parent);
UpdateWorldTransforms(registry);
auto* childWorld = registry.try_get<WorldTransform>(child);
TEST_CHECK(childWorld != nullptr);
if (childWorld) {
TEST_CHECK(childWorld->position.x == 3.0f);
TEST_CHECK(childWorld->position.y == 0.0f);
TEST_CHECK(childWorld->position.z == 0.0f);
}
}
void test_scenegraph_detach(void) {
entt::registry registry;
entt::entity parent = registry.create();
entt::entity child = registry.create();
AttachChild(registry, child, parent);
DetachFromParent(registry, child);
auto* childHierarchy = registry.try_get<Hierarchy>(child);
TEST_CHECK(childHierarchy != nullptr);
if (childHierarchy) {
TEST_CHECK(childHierarchy->parent == entt::null);
TEST_CHECK(childHierarchy->prev == entt::null);
TEST_CHECK(childHierarchy->next == entt::null);
}
auto* parentHierarchy = registry.try_get<Hierarchy>(parent);
TEST_CHECK(parentHierarchy != nullptr);
if (parentHierarchy) {
TEST_CHECK(parentHierarchy->first == entt::null);
}
}
void test_scene_loader_basic(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
entt::registry registry;
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
scene << "position = [0.0, 0.0, 0.0]\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"parent\"\n";
scene << "parent = \"root\"\n";
scene << "tag = \"Parent\"\n";
scene << "position = [1.0, 0.0, 0.0]\n";
scene << "model = \"cube\"\n";
scene << "model_size = [1.0, 1.0, 1.0]\n";
scene << "color = 0x00FF00FF\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"child\"\n";
scene << "parent = \"parent\"\n";
scene << "tag = \"Child\"\n";
scene << "position = [0.0, 1.0, 0.0]\n";
scene << "model = \"sphere\"\n";
scene << "radius = 0.5\n";
scene << "color = 0xFF0000FF\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
entt::entity root = LoadSceneFromFile("scene.toml", true);
TEST_CHECK(root != entt::null);
}
entt::entity parentEntity = entt::null;
entt::entity childEntity = entt::null;
auto tagView = registry.view<Tag>();
for (auto entity : tagView) {
const auto& tag = tagView.get<Tag>(entity);
if (tag.name == "Parent") {
parentEntity = entity;
} else if (tag.name == "Child") {
childEntity = entity;
}
}
TEST_CHECK(parentEntity != entt::null);
TEST_CHECK(childEntity != entt::null);
if (parentEntity != entt::null && childEntity != entt::null) {
auto* childHierarchy = registry.try_get<Hierarchy>(childEntity);
TEST_CHECK(childHierarchy != nullptr);
if (childHierarchy) {
TEST_CHECK(childHierarchy->parent == parentEntity);
}
TEST_CHECK(registry.all_of<ModelRenderer>(parentEntity));
TEST_CHECK(registry.all_of<ModelRenderer>(childEntity));
}
ShutdownRaylibHidden();
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_scene_script_path(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_script";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "scene_script = \"scripts/demo_scene.as\"\n";
scene << "\n";
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
entt::entity root = LoadSceneFromFile("scene.toml", true);
TEST_CHECK(root != entt::null);
std::string sceneScript;
bool changed = ConsumeSceneScriptPath(sceneScript);
TEST_CHECK(changed == true);
TEST_CHECK(sceneScript == "scripts/demo_scene.as");
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_clear_scene_script(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
SetSceneScriptPath("scripts/old_scene.as");
std::string previous;
ConsumeSceneScriptPath(previous);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_clear";
std::filesystem::create_directories(tmpDir);
std::filesystem::path scenePath = tmpDir / "scene.toml";
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
LoadSceneFromFile("scene.toml", true);
std::string sceneScript;
bool changed = ConsumeSceneScriptPath(sceneScript);
TEST_CHECK(changed == true);
TEST_CHECK(sceneScript.empty());
ShutdownPhysFSTest();
std::filesystem::remove(scenePath);
std::filesystem::remove(tmpDir);
}
void test_scene_loader_asset_keys(void) {
if (!InitRaylibHidden()) {
TEST_SKIP("No display available for raylib");
return;
}
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_assets";
std::filesystem::create_directories(tmpDir);
std::filesystem::path registryPath = tmpDir / "assets" / "registry.toml";
std::filesystem::path scenePath = tmpDir / "scene_asset.toml";
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
AssetManager assets;
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
assets.LoadCube("demo_cube", 1.0f, 1.0f, 1.0f);
assets.SaveRegistry("assets/registry.toml");
}
entt::registry registry;
{
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
AssetManager assets;
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
assets.LoadRegistry("assets/registry.toml");
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
SetSceneAssetManager(&assets);
{
std::ofstream scene(scenePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
scene << "position = [0.0, 0.0, 0.0]\n";
scene << "model_asset = \"demo_cube\"\n";
scene << "color = 0xFFFFFFFF\n";
scene << "\n";
}
entt::entity root = LoadSceneFromFile("scene_asset.toml", true);
TEST_CHECK(root != entt::null);
auto tagView = registry.view<Tag>();
entt::entity rootEntity = entt::null;
for (auto entity : tagView) {
const auto& tag = tagView.get<Tag>(entity);
if (tag.name == "Root") {
rootEntity = entity;
break;
}
}
TEST_CHECK(rootEntity != entt::null);
if (rootEntity != entt::null) {
auto* renderer = registry.try_get<ModelRenderer>(rootEntity);
TEST_CHECK(renderer != nullptr);
if (renderer) {
unsigned int expectedId = assets.GetModelId("demo_cube");
TEST_CHECK(expectedId != 0);
TEST_CHECK(renderer->modelId == expectedId);
}
}
}
ShutdownRaylibHidden();
ShutdownPhysFSTest();
std::filesystem::remove(scenePath);
std::filesystem::remove(registryPath);
std::filesystem::remove(registryPath.parent_path());
std::filesystem::remove(tmpDir);
}
void test_scene_loader_rejects_escape(void) {
entt::registry registry;
ModelManager modelManager;
ShaderManager shaderManager;
TextureManager textureManager;
SetSceneContext(&registry, &modelManager, &shaderManager, &textureManager);
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_sandbox";
std::filesystem::create_directories(tmpDir);
std::filesystem::path outsidePath = std::filesystem::current_path() / "outside_scene.toml";
{
std::ofstream scene(outsidePath);
scene << "[[entity]]\n";
scene << "id = \"root\"\n";
scene << "tag = \"Root\"\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
entt::entity root = LoadSceneFromFile("../outside_scene.toml", true);
TEST_CHECK(root == entt::null);
ShutdownPhysFSTest();
std::filesystem::remove(outsidePath);
std::filesystem::remove(tmpDir);
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
void test_scenegraph_world_transform(void);
void test_scenegraph_detach(void);
void test_scene_loader_basic(void);
void test_scene_loader_asset_keys(void);
void test_scene_loader_scene_script_path(void);
void test_scene_loader_clear_scene_script(void);
void test_scene_loader_rejects_escape(void);
+13 -2
View File
@@ -3,6 +3,7 @@
#include <filesystem>
#include <fstream>
#include "../include/scripting/ScriptEngine.h"
#include "physfs_test_utils.h"
void test_scriptengine_compile_and_call(void) {
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_script";
@@ -14,9 +15,12 @@ void test_scriptengine_compile_and_call(void) {
ofs << "void Update(float dt) { g_x += dt * 10.0f; }\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
ScriptEngine se;
TEST_CHECK(se.Initialize() == true);
bool ok = se.CompileScript(script.string());
bool ok = se.CompileScript("simple.as");
TEST_CHECK(ok == true);
asIScriptFunction* upd = se.GetUpdateFunction();
TEST_CHECK(upd != nullptr);
@@ -25,6 +29,8 @@ void test_scriptengine_compile_and_call(void) {
se.Shutdown();
ShutdownPhysFSTest();
std::filesystem::remove(script);
std::filesystem::remove(tmpDir);
}
@@ -46,12 +52,17 @@ void test_scriptengine_include(void) {
ofsmain << "void Update(float dt) { Included(); }\n";
}
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
ScriptEngine se;
TEST_CHECK(se.Initialize() == true);
bool ok = se.CompileScript(main.string());
bool ok = se.CompileScript("main.as");
TEST_CHECK(ok == true);
se.Shutdown();
ShutdownPhysFSTest();
std::filesystem::remove(inc);
std::filesystem::remove(main);
std::filesystem::remove(tmpDir);
+14
View File
@@ -2,10 +2,24 @@
#include "hot_reload.h"
#include "script_engine_tests.h"
#include "math_bindings_tests.h"
#include "resource_tests.h"
#include "scenegraph_tests.h"
TEST_LIST = {
{ "hotreload_dir", test_hotreload_directory_watch },
{ "script_compile", test_scriptengine_compile_and_call },
{ "script_include", test_scriptengine_include },
{ "math_bindings", test_math_bindings },
{ "shader_manager", test_shader_manager },
{ "model_manager", test_model_manager },
{ "material_manager", test_material_manager },
{ "asset_registry_material", test_asset_registry_material },
{ "resource_bindings", test_resource_bindings_script },
{ "scenegraph_world", test_scenegraph_world_transform },
{ "scenegraph_detach", test_scenegraph_detach },
{ "scene_loader", test_scene_loader_basic },
{ "scene_loader_script_path", test_scene_loader_scene_script_path },
{ "scene_loader_clear_script", test_scene_loader_clear_scene_script },
{ "scene_loader_assets", test_scene_loader_asset_keys },
{ "scene_loader_sandbox", test_scene_loader_rejects_escape },
{ NULL, NULL }
};