From aa4c7cf58a5cc3853a32671eebb95e7d5065bf4c Mon Sep 17 00:00:00 2001 From: Nick Koirala Date: Thu, 5 Mar 2026 23:22:09 +1300 Subject: [PATCH] feat: entt scripting support --- CMakeLists.txt | 19 +- imgui.ini | 8 +- include/Application.h | 17 +- include/ECSComponents.h | 54 +++++ include/scripting/ECSBindings.h | 8 + include/scripting/ScriptEngine.h | 2 - scripts/as.predefined | 58 +++++- scripts/game.as | 117 ++++++++++- src/Application.cpp | 241 ++++++++++------------- src/scripting/ECSBindings.cpp | 317 ++++++++++++++++++++++++++++++ src/scripting/ScriptBindings.cpp | 4 +- src/scripting/ScriptEngine.cpp | 8 +- src/scripting/TextureBindings.cpp | 11 +- tests/CMakeLists.txt | 4 +- 14 files changed, 694 insertions(+), 174 deletions(-) create mode 100644 include/ECSComponents.h create mode 100644 include/scripting/ECSBindings.h create mode 100644 src/scripting/ECSBindings.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 79f42d4..40a0a36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,10 @@ endif() # ------------------------- enable_testing() add_subdirectory(tests) + +# ------------------------- +# 3️⃣.2️⃣ ECS Bindings +# ------------------------- # ------------------------- # 3️⃣ scriptstdstring add-on # ------------------------- @@ -68,6 +72,17 @@ target_include_directories(scriptbuilder PUBLIC external/angelscript/sdk/angelscript/include ) +# ------------------------- +# 3️⃣.2️⃣ scriptarray add-on +# ------------------------- +add_library(scriptarray STATIC + external/angelscript/sdk/add_on/scriptarray/scriptarray.cpp +) + +target_include_directories(scriptarray + PUBLIC external/angelscript/sdk/angelscript/include +) + # ------------------------- # 4️⃣ Main executable # ------------------------- @@ -78,10 +93,10 @@ add_executable(simian src/scripting/ScriptBindings.cpp src/scripting/ToastBindings.cpp src/scripting/LogBindings.cpp - src/scripting/DrawBindings.cpp src/scripting/ImageBindings.cpp src/scripting/TextureBindings.cpp src/scripting/MathBindings.cpp + src/scripting/ECSBindings.cpp src/HotReload.cpp src/gui/GuiManager.cpp src/gui/LogWindow.cpp @@ -97,6 +112,7 @@ target_include_directories(simian PUBLIC external/angelscript/sdk/angelscript/include external/angelscript/sdk/add_on/scriptstdstring external/angelscript/sdk/add_on/scriptbuilder + external/angelscript/sdk/add_on/scriptarray external/imgui ) @@ -105,6 +121,7 @@ target_link_libraries(simian PRIVATE angelscript scriptstdstring scriptbuilder + scriptarray imgui ) diff --git a/imgui.ini b/imgui.ini index 3cc24d0..2e3c391 100644 --- a/imgui.ini +++ b/imgui.ini @@ -21,13 +21,13 @@ Collapsed=0 DockId=0x00000002,0 [Window][##TOAST0] -Pos=1007,637 -Size=253,63 +Pos=1033,637 +Size=227,63 Collapsed=0 [Window][##TOAST1] -Pos=1064,564 -Size=196,63 +Pos=1035,564 +Size=225,63 Collapsed=0 [Window][##TOAST2] diff --git a/include/Application.h b/include/Application.h index 78a6ee3..e4fcc4e 100644 --- a/include/Application.h +++ b/include/Application.h @@ -3,6 +3,8 @@ #include "HotReload.h" #include "gui/GuiManager.h" #include "raylib.h" +#include +#include class Application { public: @@ -27,6 +29,17 @@ private: bool queueShutdown = false; RenderTexture2D renderTexture; // renderTexture for Raylib rendering + // ECS + entt::registry registry; + std::vector models; + + // Scene/rendering data + Camera3D camera; + Shader toonShader; + Vector3 lightDir; + float globalBandCount; + bool dayMode; + static const int WINDOW_WIDTH = 1280; static const int WINDOW_HEIGHT = 720; static const int TARGET_FPS = 60; @@ -34,7 +47,7 @@ private: static const char* SCRIPT_FILE; void Update(float deltaTime); + void UpdateSystems(float deltaTime); + void RenderScene(); void Draw(); - - void SpawnCube(); }; \ No newline at end of file diff --git a/include/ECSComponents.h b/include/ECSComponents.h new file mode 100644 index 0000000..d1b7c13 --- /dev/null +++ b/include/ECSComponents.h @@ -0,0 +1,54 @@ +#pragma once +#include "raylib.h" +#include + +// ECSTransform component - position, rotation, and scale +struct ECSTransform { + Vector3 position; + Vector3 scale; + float rotation; // Rotation around Y axis in radians + + ECSTransform() + : position{0.0f, 0.0f, 0.0f} + , scale{1.0f, 1.0f, 1.0f} + , rotation(0.0f) {} + + ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rot = 0.0f) + : position(pos), scale(scl), rotation(rot) {} +}; + +// Velocity component - for movement +struct Velocity { + Vector3 linear; // Linear velocity (units per second) + float angular; // Angular velocity (radians per second) + + Velocity() + : linear{0.0f, 0.0f, 0.0f} + , angular(0.0f) {} + + Velocity(Vector3 lin, float ang = 0.0f) + : linear(lin), angular(ang) {} +}; + +// Sprite component - visual representation +struct Sprite { + int modelId; // Index into models array + Color color; // Tint color + float outlineSize; // Outline thickness + + Sprite() + : modelId(0) + , color(WHITE) + , outlineSize(0.0f) {} + + Sprite(int model, Color col = WHITE, float outline = 0.0f) + : modelId(model), color(col), outlineSize(outline) {} +}; + +// Tag component - simple string identifier +struct Tag { + std::string name; + + Tag() : name("") {} + Tag(const std::string& n) : name(n) {} +}; diff --git a/include/scripting/ECSBindings.h b/include/scripting/ECSBindings.h new file mode 100644 index 0000000..5036374 --- /dev/null +++ b/include/scripting/ECSBindings.h @@ -0,0 +1,8 @@ +#pragma once +#include +#include + +// Set the global registry that scripts will interact with +void SetGlobalRegistry(entt::registry* reg); + +void RegisterECSBindings(asIScriptEngine *engine); diff --git a/include/scripting/ScriptEngine.h b/include/scripting/ScriptEngine.h index cb20b77..650936d 100644 --- a/include/scripting/ScriptEngine.h +++ b/include/scripting/ScriptEngine.h @@ -25,14 +25,12 @@ public: asIScriptEngine* GetEngine() const { return engine; } asIScriptFunction* GetUpdateFunction() const { return updateFunc; } - asIScriptFunction* GetDrawFunction() const { return drawFunc; } asIScriptFunction* GetInitFunction() const { return initFunc; } asIScriptFunction* GetShutdownFunction() const { return shutdownFunc; } private: asIScriptEngine* engine; asIScriptFunction* updateFunc; - asIScriptFunction* drawFunc; asIScriptFunction* initFunc; asIScriptFunction* shutdownFunc; asIScriptModule* currentModule; diff --git a/scripts/as.predefined b/scripts/as.predefined index 18beee9..b26ad4b 100644 --- a/scripts/as.predefined +++ b/scripts/as.predefined @@ -1,6 +1,18 @@ typedef void string; string format(const string&in fmt, const ?&in ...); +// Array template (provided by scriptarray add-on) +external shared interface array { + uint length() const; + void resize(uint); + void insertAt(uint, const T&in); + void insertLast(const T&in); + void removeAt(uint); + void removeLast(); + T& opIndex(uint); + const T& opIndex(uint) const; +} + // Logging functions void Print(const string&in); void Log(int level, const string&in); @@ -19,14 +31,44 @@ namespace Toast { void Success(const string&in); } -// Drawing functions -namespace Draw { - void Pixel(int x, int y, int color); - void Line(int startX, int startY, int endX, int endY, int color); - void Circle(int centerX, int centerY, float radius, int color); - void Text(const string&in text, int x, int y, int fontSize, int color); - void Rectangle(int x, int y, int width, int height, int color); - void FPS(int x, int y); +// ECS functions +namespace ECS { + // Entity management + uint CreateEntity(); + void DestroyEntity(uint entity); + bool IsValid(uint entity); + + // Transform component + void AddTransform(uint entity, float x, float y, float z); + void SetPosition(uint entity, float x, float y, float z); + void GetPosition(uint entity, float &out x, float &out y, float &out z); + void SetScale(uint entity, float x, float y, float z); + void SetRotation(uint entity, float rotation); + float GetRotation(uint entity); + bool HasTransform(uint entity); + void RemoveTransform(uint entity); + + // Velocity component + void AddVelocity(uint entity, float vx, float vy, float vz, float angular = 0.0f); + void SetVelocity(uint entity, float vx, float vy, float vz); + void SetAngularVelocity(uint entity, float angular); + bool HasVelocity(uint entity); + void RemoveVelocity(uint entity); + + // Sprite component + void AddSprite(uint entity, int modelId, uint color, float outlineSize = 0.0f); + void SetSpriteColor(uint entity, uint color); + void SetSpriteModel(uint entity, int modelId); + void SetSpriteOutline(uint entity, float outlineSize); + bool HasSprite(uint entity); + void RemoveSprite(uint entity); + + // Tag component + void AddTag(uint entity, const string &in name); + string GetTag(uint entity); + void SetTag(uint entity, const string &in name); + bool HasTag(uint entity); + void RemoveTag(uint entity); } namespace Texture { diff --git a/scripts/game.as b/scripts/game.as index 5dfa215..01cc269 100644 --- a/scripts/game.as +++ b/scripts/game.as @@ -1,11 +1,108 @@ -#include "update.as" +// ECS-based game script demo +// This demonstrates creating entities with various components -void Draw() { - Draw::FPS(10, 10); - Draw::Text(format("Hello from AngelScript - at x {} and y {}", int(x), int(y)), int(x), int(y), 20, 0xFF0000FF); - Draw::Rectangle(300, 200, 200, 100, 0x00FF00FF); - Draw::Pixel(250, 200, 0x0000FFFF); - Draw::Line(int(x), int(y), 400, 300, 0xFFFF00FF); - Draw::Circle(600, 400, 50.0f, 0xFF00FFFF); - Texture::Draw(tex, int(ix), int(iy), 0xFFFFFFFF); -} \ No newline at end of file +// Store entity IDs for manipulation +array entities; +uint player; +uint spinningCube; +float time = 0.0f; + +void Init() { + Log(LOG_TRACE, "Initialization complete - ECS Demo!"); + Toast::Success("ECS Script initialized successfully!"); + + // Create a player entity at origin + player = ECS::CreateEntity(); + ECS::AddTransform(player, 0.0f, 0.0f, 0.0f); + ECS::AddSprite(player, 0, 0xFF0000FF, 0.0f); // Red cube, model 0 + 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, 0, 0x00FF00FF, 0.0f); // Green cube + 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; + if (i == 0) color = 0xFFFF00FF; // Yellow + else if (i == 1) color = 0xFF00FFFF; // Magenta + else if (i == 2) color = 0x00FFFFFF; // Cyan + else if (i == 3) color = 0xFFA500FF; // Orange + else color = 0xFF69B4FF; // Pink + + ECS::AddSprite(entity, 0, color, 0.0f); + 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, 0, 0xFFFFFFFF, 0.0f); // White cube + 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() + 3) + " entities!"); +} + +void Shutdown() { + Log(LOG_TRACE, "Shutdown complete!"); +} + +void Update(float dt) { + time += dt; + + // 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); + } + } + } +} diff --git a/src/Application.cpp b/src/Application.cpp index 3c834ef..aee6863 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -1,4 +1,6 @@ #include "Application.h" +#include "ECSComponents.h" +#include "scripting/ECSBindings.h" #include "raylib.h" #include #include @@ -18,10 +20,18 @@ // On non-Windows platforms, define fopen_s as a macro for fopen. #define fopen_s(pFile, filename, mode) ((*(pFile) = fopen((filename), (mode))) == NULL) #endif + const char *Application::WINDOW_TITLE = "Simian"; const char *Application::SCRIPT_FILE = "scripts/game.as"; -Application::Application() : hotReload(nullptr), scriptCompilationError(false), logFile(nullptr), renderTexture{} // Initialize renderTexture +Application::Application() + : hotReload(nullptr) + , scriptCompilationError(false) + , logFile(nullptr) + , renderTexture{} + , lightDir{0.5f, 0.7f, 0.3f} + , globalBandCount(3.0f) + , dayMode(true) { } @@ -32,36 +42,6 @@ Application::~Application() // Shutdown(); } -/// Shader and rendering data for the demo scene -Camera3D camera; -RenderTexture2D sceneRT, maskRT; -Shader toonShader, outlineShader; -Model cube; - -float globalBandCount = 3.0f; -Vector3 lightDir = {0.5f, 0.7f, 0.3f}; -bool showEditor = true; -bool dayMode = true; -entt::registry ecs; -std::vector models; -struct ECS_Transform -{ - Vector3 pos, scale; - float rot; -}; -struct Render -{ - int modelId; - float outlineSize; - Color color; -}; -struct ECS_Material -{ - Shader shader; - int lightLoc, bandLoc; -}; -/// ====== - bool Application::Initialize(int argc, char *argv[]) { if (fopen_s(&logFile, "log.txt", "w") != 0) @@ -98,6 +78,9 @@ bool Application::Initialize(int argc, char *argv[]) return false; } + // Set up ECS bindings with our registry + SetGlobalRegistry(®istry); + // Initialize hot reload to watch the scripts directory so any script change // triggers a reload. { @@ -113,44 +96,30 @@ bool Application::Initialize(int argc, char *argv[]) if (enableEditor) { - guiManager.Initialize(this); - renderTexture = LoadRenderTexture(WINDOW_WIDTH, WINDOW_HEIGHT); } + // Set up camera camera.position = {5, 5, 5}; camera.target = {0, 0, 0}; camera.up = {0, 1, 0}; camera.fovy = 45; - // Assets - cube = LoadModelFromMesh(GenMeshCube(1, 1, 1)); - models.push_back(cube); - // Shaders (toon + outline) + // Load models + Model cube = LoadModelFromMesh(GenMeshCube(1, 1, 1)); + + // Load shaders toonShader = LoadShader(0, "shaders/toon.fs"); - outlineShader = LoadShader(0, "shaders/outline.fs"); - - // Render textures - sceneRT = LoadRenderTexture(1280, 720); - maskRT = LoadRenderTexture(1280, 720); - - // Cache uniform locations + + // Assign shader to cube material cube.materials[0].shader = toonShader; + + models.push_back(cube); return true; } -void Application::SpawnCube() -{ - auto e = ecs.create(); - ecs.emplace( - e, - Vector3{static_cast(rand() % 3 - 1), 0.0f, static_cast(rand() % 3 - 1)}, // Position - Vector3{1.0f, 1.0f, 1.0f}, // Scale - 0.0f // Rotation - ); - ecs.emplace(e, 0, 2.0f, YELLOW); -} + void Application::Run() { scriptEngine.CallScriptFunction(scriptEngine.GetInitFunction()); @@ -189,24 +158,75 @@ void Application::Update(float deltaTime) // Call script Update function scriptEngine.CallScriptFunction(scriptEngine.GetUpdateFunction(), deltaTime); + // Update ECS systems + UpdateSystems(deltaTime); + UpdateCamera(&camera, CAMERA_ORBITAL); // Day/night toggle if (IsKeyPressed(KEY_SPACE)) dayMode = !dayMode; globalBandCount = dayMode ? 3.0f : 2.0f; +} - // F1 editor toggle - if (IsKeyPressed(KEY_F1)) - showEditor = !showEditor; +void Application::UpdateSystems(float deltaTime) +{ + // Movement system - apply velocity to transform + auto view = registry.view(); + for (auto entity : view) + { + auto& transform = view.get(entity); + auto& velocity = view.get(entity); + + // Apply linear velocity + transform.position.x += velocity.linear.x * deltaTime; + transform.position.y += velocity.linear.y * deltaTime; + transform.position.z += velocity.linear.z * deltaTime; + + // Apply angular velocity + transform.rotation += velocity.angular * deltaTime; + } +} - if (IsKeyPressed(KEY_F2)) - SpawnCube(); +void Application::RenderScene() +{ + BeginMode3D(camera); + + DrawGrid(10, 1); + DrawLine3D(Vector3{0, 0, 0}, Vector3{lightDir.x * 5.0f, lightDir.y * 5.0f, lightDir.z * 5.0f}, RED); + + // Render all entities with ECSTransform and Sprite components + BeginShaderMode(toonShader); + SetShaderValue(toonShader, GetShaderLocation(toonShader, "lightDir"), &lightDir, SHADER_UNIFORM_VEC3); + SetShaderValue(toonShader, GetShaderLocation(toonShader, "bandCount"), &globalBandCount, SHADER_UNIFORM_FLOAT); + + auto view = registry.view(); + for (auto entity : view) + { + const auto& transform = view.get(entity); + const auto& sprite = view.get(entity); + + // Set color for this entity + Vector4 color = { + sprite.color.r / 255.0f, + sprite.color.g / 255.0f, + sprite.color.b / 255.0f, + sprite.color.a / 255.0f + }; + SetShaderValue(toonShader, GetShaderLocation(toonShader, "albedo"), &color, SHADER_UNIFORM_VEC4); + + // Draw the model with transform + Model& model = models[sprite.modelId]; + DrawModelEx(model, transform.position, Vector3{0, 1, 0}, transform.rotation * RAD2DEG, + transform.scale, WHITE); + } + + EndShaderMode(); + EndMode3D(); } void Application::Draw() { - int screenWidth = GetScreenWidth(); int screenHeight = GetScreenHeight(); @@ -218,85 +238,26 @@ void Application::Draw() screenHeight = 600; } - ImGuiIO &io = ImGui::GetIO(); - io.DisplaySize = ImVec2((float)screenWidth, (float)screenHeight); // Set DisplaySize - - BeginTextureMode(renderTexture); - - ClearBackground(SKYBLUE); - BeginMode3D(camera); - DrawGrid(10, 1); - DrawLine3D(Vector3{0, 0, 0}, Vector3{lightDir.x * 5.0f, lightDir.y * 5.0f, lightDir.z * 5.0f}, RED); - BeginShaderMode(toonShader); - SetShaderValue(toonShader, GetShaderLocation(toonShader, "lightDir"), &lightDir, SHADER_UNIFORM_VEC3); - SetShaderValue(toonShader, GetShaderLocation(toonShader, "bandCount"), &globalBandCount, SHADER_UNIFORM_FLOAT); - - auto view = ecs.view(); - view.each([&](const auto &t, const auto &r) - { - Vector4 color = {r.color.r / 255.0f, r.color.g / 255.0f, r.color.b / 255.0f, r.color.a / 255.0f}; - SetShaderValue(toonShader, GetShaderLocation(toonShader, "albedo"), &color, SHADER_UNIFORM_VEC4); - DrawModel(models[r.modelId], t.pos, 1.0f, WHITE); // Use WHITE since color is handled by the shader - }); - - EndShaderMode(); - - EndMode3D(); - // Call script Draw function - scriptEngine.CallScriptFunction(scriptEngine.GetDrawFunction()); - - // 2. Silhouette mask - // BeginTextureMode(maskRT); - // ClearBackground(WHITE); - // BeginMode3D(camera); - - // for (auto e : ecs.view()) - // { - // auto &t = ecs.get(e); - // DrawModel(models[ecs.get(e).modelId], t.pos, 1.0f, BLACK); - // } - - // EndMode3D(); - // EndTextureMode(); - - // // 3. Outline postprocess - // BeginShaderMode(outlineShader); - // Rectangle src = {0, 0, (float)maskRT.texture.width, (float)-maskRT.texture.height}; - // DrawTextureRec(maskRT.texture, src, {0, 0}, WHITE); - EndShaderMode(); - EndTextureMode(); if (enableEditor) { + ImGuiIO &io = ImGui::GetIO(); + io.DisplaySize = ImVec2((float)screenWidth, (float)screenHeight); + + BeginTextureMode(renderTexture); + ClearBackground(SKYBLUE); + RenderScene(); + EndTextureMode(); + BeginDrawing(); ClearBackground(RAYWHITE); - - guiManager.Render(renderTexture); // Render the GUI - - // rlImGuiBegin(); - // ImGui::Begin("Comic Engine"); - // std::string fpsText = "FPS: " + std::to_string(GetFPS()); - // ImGui::Text(fpsText.c_str()); - // if (ImGui::Button("Spawn Cube")) - // SpawnCube(); - // ImGui::Checkbox("Day Mode", &dayMode); - // ImGui::SliderFloat("Band Count", &globalBandCount, 1.0f, 5.0f); - // ImGui::ColorEdit3("Light Dir", &lightDir.x); - // ImGui::Text("F1: Toggle editor"); - // ImGui::End(); - - // // Scene hierarchy - // std::string sceneTitle = "Scene (" + std::to_string(ecs.view().size()) + " entities)"; - // ImGui::Begin(sceneTitle.c_str()); - // for (auto e : ecs.view()) - // { - // if (ImGui::Button(std::string("Cube " + std::to_string((int)e)).c_str())) - // { - // ecs.get(e).pos.x += 1.0f; - // } - // } - // ImGui::End(); - // rlImGuiEnd(); - + guiManager.Render(renderTexture); + EndDrawing(); + } + else + { + BeginDrawing(); + ClearBackground(SKYBLUE); + RenderScene(); EndDrawing(); } } @@ -322,11 +283,11 @@ void Application::Shutdown() scriptEngine.Shutdown(); // Unload assets - UnloadModel(cube); + for (auto& model : models) + { + UnloadModel(model); + } UnloadShader(toonShader); - UnloadShader(outlineShader); - UnloadRenderTexture(sceneRT); - UnloadRenderTexture(maskRT); // Clear the trace log callback before closing window to prevent logging after cleanup SetTraceLogCallback(nullptr); diff --git a/src/scripting/ECSBindings.cpp b/src/scripting/ECSBindings.cpp new file mode 100644 index 0000000..454d21a --- /dev/null +++ b/src/scripting/ECSBindings.cpp @@ -0,0 +1,317 @@ +#include "scripting/ECSBindings.h" +#include "ECSComponents.h" +#include +#include +#include + +// Global registry pointer for script access +static entt::registry* g_registry = nullptr; + +void SetGlobalRegistry(entt::registry* reg) { + g_registry = reg; +} + +// === Entity Management === + +uint32_t AS_CreateEntity() { + if (!g_registry) return 0; + return (uint32_t)g_registry->create(); +} + +void AS_DestroyEntity(uint32_t entity) { + if (!g_registry) return; + g_registry->destroy((entt::entity)entity); +} + +bool AS_IsEntityValid(uint32_t entity) { + if (!g_registry) return false; + return g_registry->valid((entt::entity)entity); +} + +// === Transform Component === + +void AS_AddTransform(uint32_t entity, float x, float y, float z) { + if (!g_registry) return; + g_registry->emplace((entt::entity)entity, + Vector3{x, y, z}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f); +} + +void AS_SetPosition(uint32_t entity, float x, float y, float z) { + if (!g_registry) return; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + t->position = {x, y, z}; + } +} + +void AS_GetPosition(uint32_t entity, float* x, float* y, float* z) { + if (!g_registry || !x || !y || !z) return; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + *x = t->position.x; + *y = t->position.y; + *z = t->position.z; + } +} + +void AS_SetScale(uint32_t entity, float x, float y, float z) { + if (!g_registry) return; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + t->scale = {x, y, z}; + } +} + +void AS_SetRotation(uint32_t entity, float rotation) { + if (!g_registry) return; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + t->rotation = rotation; + } +} + +float AS_GetRotation(uint32_t entity) { + if (!g_registry) return 0.0f; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + return t->rotation; + } + return 0.0f; +} + +bool AS_HasTransform(uint32_t entity) { + if (!g_registry) return false; + return g_registry->all_of((entt::entity)entity); +} + +void AS_RemoveTransform(uint32_t entity) { + if (!g_registry) return; + g_registry->remove((entt::entity)entity); +} + +// === Velocity Component === + +void AS_AddVelocity(uint32_t entity, float vx, float vy, float vz, float angular) { + if (!g_registry) return; + g_registry->emplace((entt::entity)entity, Vector3{vx, vy, vz}, angular); +} + +void AS_SetVelocity(uint32_t entity, float vx, float vy, float vz) { + if (!g_registry) return; + if (auto* v = g_registry->try_get((entt::entity)entity)) { + v->linear = {vx, vy, vz}; + } +} + +void AS_SetAngularVelocity(uint32_t entity, float angular) { + if (!g_registry) return; + if (auto* v = g_registry->try_get((entt::entity)entity)) { + v->angular = angular; + } +} + +bool AS_HasVelocity(uint32_t entity) { + if (!g_registry) return false; + return g_registry->all_of((entt::entity)entity); +} + +void AS_RemoveVelocity(uint32_t entity) { + if (!g_registry) return; + g_registry->remove((entt::entity)entity); +} + +// === Sprite Component === + +void AS_AddSprite(uint32_t entity, int modelId, uint32_t color, float outlineSize) { + if (!g_registry) return; + Color col; + col.r = (color >> 24) & 0xFF; + col.g = (color >> 16) & 0xFF; + col.b = (color >> 8) & 0xFF; + col.a = color & 0xFF; + g_registry->emplace((entt::entity)entity, modelId, col, outlineSize); +} + +void AS_SetSpriteColor(uint32_t entity, uint32_t color) { + if (!g_registry) return; + if (auto* s = g_registry->try_get((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; + } +} + +void AS_SetSpriteModel(uint32_t entity, int modelId) { + if (!g_registry) return; + if (auto* s = g_registry->try_get((entt::entity)entity)) { + s->modelId = modelId; + } +} + +void AS_SetSpriteOutline(uint32_t entity, float outlineSize) { + if (!g_registry) return; + if (auto* s = g_registry->try_get((entt::entity)entity)) { + s->outlineSize = outlineSize; + } +} + +bool AS_HasSprite(uint32_t entity) { + if (!g_registry) return false; + return g_registry->all_of((entt::entity)entity); +} + +void AS_RemoveSprite(uint32_t entity) { + if (!g_registry) return; + g_registry->remove((entt::entity)entity); +} + +// === Tag Component === + +void AS_AddTag(uint32_t entity, const std::string& name) { + if (!g_registry) return; + g_registry->emplace((entt::entity)entity, name); +} + +std::string AS_GetTag(uint32_t entity) { + if (!g_registry) return ""; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + return t->name; + } + return ""; +} + +void AS_SetTag(uint32_t entity, const std::string& name) { + if (!g_registry) return; + if (auto* t = g_registry->try_get((entt::entity)entity)) { + t->name = name; + } +} + +bool AS_HasTag(uint32_t entity) { + if (!g_registry) return false; + return g_registry->all_of((entt::entity)entity); +} + +void AS_RemoveTag(uint32_t entity) { + if (!g_registry) return; + g_registry->remove((entt::entity)entity); +} + +// === Registration === + +void RegisterECSBindings(asIScriptEngine *engine) { + int r; + + engine->SetDefaultNamespace("ECS"); + + // Entity management + r = engine->RegisterGlobalFunction("uint CreateEntity()", + asFUNCTION(AS_CreateEntity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void DestroyEntity(uint)", + asFUNCTION(AS_DestroyEntity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool IsValid(uint)", + asFUNCTION(AS_IsEntityValid), asCALL_CDECL); + assert(r >= 0); + + // Transform component + r = engine->RegisterGlobalFunction("void AddTransform(uint, float, float, float)", + asFUNCTION(AS_AddTransform), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetPosition(uint, float, float, float)", + asFUNCTION(AS_SetPosition), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void GetPosition(uint, float &out, float &out, float &out)", + asFUNCTION(AS_GetPosition), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetScale(uint, float, float, float)", + asFUNCTION(AS_SetScale), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetRotation(uint, float)", + asFUNCTION(AS_SetRotation), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("float GetRotation(uint)", + asFUNCTION(AS_GetRotation), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool HasTransform(uint)", + asFUNCTION(AS_HasTransform), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void RemoveTransform(uint)", + asFUNCTION(AS_RemoveTransform), asCALL_CDECL); + assert(r >= 0); + + // Velocity component + r = engine->RegisterGlobalFunction("void AddVelocity(uint, float, float, float, float = 0.0f)", + asFUNCTION(AS_AddVelocity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetVelocity(uint, float, float, float)", + asFUNCTION(AS_SetVelocity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetAngularVelocity(uint, float)", + asFUNCTION(AS_SetAngularVelocity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool HasVelocity(uint)", + asFUNCTION(AS_HasVelocity), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void RemoveVelocity(uint)", + 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); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetSpriteColor(uint, uint)", + asFUNCTION(AS_SetSpriteColor), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetSpriteModel(uint, int)", + asFUNCTION(AS_SetSpriteModel), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetSpriteOutline(uint, float)", + asFUNCTION(AS_SetSpriteOutline), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool HasSprite(uint)", + asFUNCTION(AS_HasSprite), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void RemoveSprite(uint)", + asFUNCTION(AS_RemoveSprite), asCALL_CDECL); + assert(r >= 0); + + // Tag component + r = engine->RegisterGlobalFunction("void AddTag(uint, const string &in)", + asFUNCTION(AS_AddTag), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("string GetTag(uint)", + asFUNCTION(AS_GetTag), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void SetTag(uint, const string &in)", + asFUNCTION(AS_SetTag), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("bool HasTag(uint)", + asFUNCTION(AS_HasTag), asCALL_CDECL); + assert(r >= 0); + + r = engine->RegisterGlobalFunction("void RemoveTag(uint)", + asFUNCTION(AS_RemoveTag), asCALL_CDECL); + assert(r >= 0); + + engine->SetDefaultNamespace(""); +} diff --git a/src/scripting/ScriptBindings.cpp b/src/scripting/ScriptBindings.cpp index d61fa91..707e647 100644 --- a/src/scripting/ScriptBindings.cpp +++ b/src/scripting/ScriptBindings.cpp @@ -1,18 +1,18 @@ #include "scripting/ScriptBindings.h" #include "scripting/ToastBindings.h" #include "scripting/LogBindings.h" -#include "scripting/DrawBindings.h" #include "scripting/ImageBindings.h" #include "scripting/TextureBindings.h" #include "scripting/MathBindings.h" +#include "scripting/ECSBindings.h" void ScriptBindings::RegisterAll(asIScriptEngine *engine) { // Delegate to separate binding files RegisterToastBindings(engine); RegisterLogBindings(engine); - RegisterDrawBindings(engine); RegisterImageBindings(engine); RegisterTextureBindings(engine); RegisterMathBindings(engine); + RegisterECSBindings(engine); } \ No newline at end of file diff --git a/src/scripting/ScriptEngine.cpp b/src/scripting/ScriptEngine.cpp index c64195b..f44a8be 100644 --- a/src/scripting/ScriptEngine.cpp +++ b/src/scripting/ScriptEngine.cpp @@ -2,6 +2,7 @@ #include "scripting/ScriptBindings.h" #include "scriptstdstring.h" #include "scriptbuilder.h" +#include "scriptarray.h" #include #include #include @@ -11,7 +12,7 @@ #include #include -ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) { +ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) { } ScriptEngine::~ScriptEngine() { @@ -28,6 +29,9 @@ bool ScriptEngine::Initialize() { // Register std::string RegisterStdString(engine); + // Register array template + RegisterScriptArray(engine, true); + // Register script bindings ScriptBindings::RegisterAll(engine); @@ -164,7 +168,6 @@ bool ScriptEngine::CompileScript(const std::string& filename) { // Cache new functions initFunc = currentModule->GetFunctionByName("Init"); updateFunc = currentModule->GetFunctionByName("Update"); - drawFunc = currentModule->GetFunctionByName("Draw"); shutdownFunc = currentModule->GetFunctionByName("Shutdown"); hasValidScript = true; log_info("Script compiled and cached: %s", filename.c_str()); @@ -199,7 +202,6 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) { void ScriptEngine::ClearCachedFunctions() { updateFunc = nullptr; - drawFunc = nullptr; hasValidScript = false; } diff --git a/src/scripting/TextureBindings.cpp b/src/scripting/TextureBindings.cpp index 48f6b0e..b4905ff 100644 --- a/src/scripting/TextureBindings.cpp +++ b/src/scripting/TextureBindings.cpp @@ -3,7 +3,16 @@ #include #include -Color ColorFromUInt(unsigned int c); +// Utility function to convert uint color to Raylib Color +Color ColorFromUInt(unsigned int c) +{ + Color col; + col.r = (c >> 24) & 0xFF; + col.g = (c >> 16) & 0xFF; + col.b = (c >> 8) & 0xFF; + col.a = c & 0xFF; + return col; +} Texture2D AS_LoadTexture(const std::string &filename) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 155b313..340f355 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,7 +11,7 @@ add_executable(unit_tests ${CMAKE_SOURCE_DIR}/src/scripting/ScriptBindings.cpp ${CMAKE_SOURCE_DIR}/src/scripting/ToastBindings.cpp ${CMAKE_SOURCE_DIR}/src/scripting/LogBindings.cpp - ${CMAKE_SOURCE_DIR}/src/scripting/DrawBindings.cpp + ${CMAKE_SOURCE_DIR}/src/scripting/ECSBindings.cpp ${CMAKE_SOURCE_DIR}/src/scripting/ImageBindings.cpp ${CMAKE_SOURCE_DIR}/src/scripting/TextureBindings.cpp ${CMAKE_SOURCE_DIR}/src/scripting/MathBindings.cpp @@ -25,6 +25,7 @@ target_include_directories(unit_tests PRIVATE ${CMAKE_SOURCE_DIR}/external/angelscript/sdk/angelscript/include ${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptstdstring ${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptbuilder + ${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptarray ${CMAKE_SOURCE_DIR}/external/raylib/src ${CMAKE_SOURCE_DIR}/external/imgui ${CMAKE_SOURCE_DIR}/external/rlImGui @@ -36,6 +37,7 @@ target_link_libraries(unit_tests PRIVATE angelscript scriptstdstring scriptbuilder + scriptarray raylib imgui )