diff --git a/Scripts/SampleInspector Simplified.cpp b/Scripts/SampleInspector Simplified.cpp new file mode 100644 index 0000000..2c87f0b --- /dev/null +++ b/Scripts/SampleInspector Simplified.cpp @@ -0,0 +1,66 @@ +#include "ScriptRuntime.h" +#include "SceneObject.h" +#include "ThirdParty/imgui/imgui.h" + +namespace { + // Script state (persisted by AutoSetting binder) + bool autoRotate = false; + glm::vec3 spinSpeed = glm::vec3(0.0f, 45.0f, 0.0f); // deg/sec + glm::vec3 offset = glm::vec3(0.0f, 1.0f, 0.0f); + char targetName[128] = "MyTarget"; + + // Runtime behavior + static void ApplyAutoRotate(ScriptContext& ctx, float deltaTime) { + if (!autoRotate || !ctx.object) return; + ctx.SetRotation(ctx.object->rotation + spinSpeed * deltaTime); + } +} + +extern "C" void Script_OnInspector(ScriptContext& ctx) { + // Auto settings (loaded once, saved only when changed) + ctx.AutoSetting("autoRotate", autoRotate); + ctx.AutoSetting("spinSpeed", spinSpeed); + ctx.AutoSetting("offset", offset); + ctx.AutoSetting("targetName", targetName, sizeof(targetName)); + + ImGui::TextUnformatted("SampleInspector"); + ImGui::Separator(); + + bool changed = false; + changed |= ImGui::Checkbox("Auto Rotate", &autoRotate); + changed |= ImGui::DragFloat3("Spin Speed (deg/s)", &spinSpeed.x, 1.0f, -360.0f, 360.0f); + changed |= ImGui::DragFloat3("Offset", &offset.x, 0.1f); + changed |= ImGui::InputText("Target Name", targetName, sizeof(targetName)); + + if (changed) { + ctx.SaveAutoSettings(); + } + + if (ctx.object) { + ImGui::TextDisabled("Attached to: %s (id=%d)", ctx.object->name.c_str(), ctx.object->id); + + if (ImGui::Button("Apply Offset To Self")) { + ctx.SetPosition(ctx.object->position + offset); + } + } + if (ImGui::Button("Nudge Target")) { + if (SceneObject* target = ctx.FindObjectByName(targetName)) { + target->position += offset; + } + } +} + +void Begin(ScriptContext& ctx, float /*deltaTime*/) { +} + +void Spec(ScriptContext& ctx, float deltaTime) { + ApplyAutoRotate(ctx, deltaTime); +} + +void TestEditor(ScriptContext& ctx, float deltaTime) { + ApplyAutoRotate(ctx, deltaTime); +} + +void TickUpdate(ScriptContext& ctx, float deltaTime) { + ApplyAutoRotate(ctx, deltaTime); +} diff --git a/src/Common.h b/src/Common.h index 9f62b66..56f14eb 100644 --- a/src/Common.h +++ b/src/Common.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include "ThirdParty/imgui/imgui.h" @@ -41,6 +42,15 @@ constexpr float NEAR_PLANE = 0.1f; constexpr float FAR_PLANE = 100.0f; constexpr float PI = 3.14159265359f; +inline glm::vec3 NormalizeEulerDegrees(const glm::vec3& deg) { + auto wrap = [](float a) { + float r = std::fmod(a, 360.0f); + if (r < 0.0f) r += 360.0f; + return r; + }; + return glm::vec3(wrap(deg.x), wrap(deg.y), wrap(deg.z)); +} + // Forward declarations class Mesh; class OBJLoader; diff --git a/src/Engine.cpp b/src/Engine.cpp index 535e0d9..0d00092 100644 --- a/src/Engine.cpp +++ b/src/Engine.cpp @@ -1031,6 +1031,10 @@ void Engine::logToConsole(const std::string& message) { addConsoleMessage(message, ConsoleMessageType::Info); } +void Engine::addConsoleMessageFromScript(const std::string& message, ConsoleMessageType type) { + addConsoleMessage(message, type); +} + SceneObject* Engine::findObjectByName(const std::string& name) { auto it = std::find_if(sceneObjects.begin(), sceneObjects.end(), [&](const SceneObject& o) { return o.name == name; diff --git a/src/Engine.h b/src/Engine.h index 043c8c4..2d898a6 100644 --- a/src/Engine.h +++ b/src/Engine.h @@ -204,4 +204,6 @@ public: SceneObject* findObjectById(int id); fs::path resolveScriptBinary(const fs::path& sourcePath); void markProjectDirty(); + // Script-accessible logging wrapper + void addConsoleMessageFromScript(const std::string& message, ConsoleMessageType type); }; diff --git a/src/EnginePanels.cpp b/src/EnginePanels.cpp index 2873f31..2ca1d92 100644 --- a/src/EnginePanels.cpp +++ b/src/EnginePanels.cpp @@ -1853,6 +1853,7 @@ void Engine::renderInspectorPanel() { ImGui::Text("Rotation"); ImGui::PushItemWidth(-1); if (ImGui::DragFloat3("##Rotation", &obj.rotation.x, 1.0f, -360.0f, 360.0f)) { + obj.rotation = NormalizeEulerDegrees(obj.rotation); projectManager.currentProject.hasUnsavedChanges = true; } ImGui::PopItemWidth(); @@ -3176,7 +3177,7 @@ void Engine::renderViewport() { glm::vec3 t, r, s; DecomposeMatrix(newM, t, r, s); o.position = t; - o.rotation = glm::degrees(r); + o.rotation = NormalizeEulerDegrees(glm::degrees(r)); o.scale = s; }; diff --git a/src/ProjectManager.cpp b/src/ProjectManager.cpp index f1d1b93..6ee4e56 100644 --- a/src/ProjectManager.cpp +++ b/src/ProjectManager.cpp @@ -415,6 +415,7 @@ bool SceneSerializer::loadScene(const fs::path& filePath, ¤tObj->rotation.x, ¤tObj->rotation.y, ¤tObj->rotation.z); + currentObj->rotation = NormalizeEulerDegrees(currentObj->rotation); } else if (key == "scale") { sscanf(value.c_str(), "%f,%f,%f", ¤tObj->scale.x, diff --git a/src/SceneObject.h b/src/SceneObject.h index 53b33d5..be50305 100644 --- a/src/SceneObject.h +++ b/src/SceneObject.h @@ -87,6 +87,7 @@ struct ScriptComponent { std::string path; std::vector settings; std::string lastBinaryPath; + std::vector activeIEnums; // function pointers registered via IEnum_Start }; class SceneObject { diff --git a/src/ScriptCompiler.cpp b/src/ScriptCompiler.cpp index 08bd16d..6b3df37 100644 --- a/src/ScriptCompiler.cpp +++ b/src/ScriptCompiler.cpp @@ -204,10 +204,11 @@ bool ScriptCompiler::makeCommands(const ScriptBuildConfig& config, const fs::pat auto detectFunction = [](const std::string& source, const std::string& name) -> FunctionSpec { FunctionSpec spec; try { - std::regex ctxDeltaPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^,\\)]*,[^\\)]*(float|double)[^\\)]*\\)"); - std::regex ctxOnlyPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^\\)]*\\)"); - std::regex deltaPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*(float|double)[^\\)]*\\)"); - std::regex basicPattern("\\bvoid\\s+" + name + "\\s*\\(\\s*\\)"); + const std::string prefix = "\\bvoid\\s+(?:IEnum\\s+)?"; + std::regex ctxDeltaPattern(prefix + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^,\\)]*,[^\\)]*(float|double)[^\\)]*\\)"); + std::regex ctxOnlyPattern(prefix + name + "\\s*\\(\\s*ScriptContext\\s*[&*][^\\)]*\\)"); + std::regex deltaPattern(prefix + name + "\\s*\\(\\s*(float|double)[^\\)]*\\)"); + std::regex basicPattern(prefix + name + "\\s*\\(\\s*\\)"); if (std::regex_search(source, ctxDeltaPattern)) { spec.present = true; diff --git a/src/ScriptRuntime.cpp b/src/ScriptRuntime.cpp index eff68ec..167d69d 100644 --- a/src/ScriptRuntime.cpp +++ b/src/ScriptRuntime.cpp @@ -19,15 +19,191 @@ SceneObject* ScriptContext::FindObjectById(int id) { } void ScriptContext::SetPosition(const glm::vec3& pos) { - if (object) object->position = pos; + if (object) { + object->position = pos; + MarkDirty(); + } } void ScriptContext::SetRotation(const glm::vec3& rot) { - if (object) object->rotation = rot; + if (object) { + object->rotation = NormalizeEulerDegrees(rot); + MarkDirty(); + } } void ScriptContext::SetScale(const glm::vec3& scl) { - if (object) object->scale = scl; + if (object) { + object->scale = scl; + MarkDirty(); + } +} + +std::string ScriptContext::GetSetting(const std::string& key, const std::string& fallback) const { + if (!script) return fallback; + auto it = std::find_if(script->settings.begin(), script->settings.end(), + [&](const ScriptSetting& s){ return s.key == key; }); + return (it != script->settings.end()) ? it->value : fallback; +} + +void ScriptContext::SetSetting(const std::string& key, const std::string& value) { + if (!script) return; + auto it = std::find_if(script->settings.begin(), script->settings.end(), + [&](const ScriptSetting& s){ return s.key == key; }); + if (it != script->settings.end()) { + it->value = value; + } else { + script->settings.push_back({key, value}); + } + MarkDirty(); +} + +bool ScriptContext::GetSettingBool(const std::string& key, bool fallback) const { + std::string v = GetSetting(key, fallback ? "1" : "0"); + if (v == "1" || v == "true" || v == "True") return true; + if (v == "0" || v == "false" || v == "False") return false; + return fallback; +} + +void ScriptContext::SetSettingBool(const std::string& key, bool value) { + SetSetting(key, value ? "1" : "0"); +} + +glm::vec3 ScriptContext::GetSettingVec3(const std::string& key, const glm::vec3& fallback) const { + std::string v = GetSetting(key, ""); + if (v.empty()) return fallback; + glm::vec3 out = fallback; + std::stringstream ss(v); + std::string part; + for (int i = 0; i < 3 && std::getline(ss, part, ','); ++i) { + try { out[i] = std::stof(part); } catch (...) {} + } + return out; +} + +void ScriptContext::SetSettingVec3(const std::string& key, const glm::vec3& value) { + SetSetting(key, + std::to_string(value.x) + "," + + std::to_string(value.y) + "," + + std::to_string(value.z)); +} + +void ScriptContext::AddConsoleMessage(const std::string& message, ConsoleMessageType type) { + if (engine) { + engine->addConsoleMessageFromScript(message, type); + } +} + +void ScriptContext::AutoSetting(const std::string& key, bool& value) { + if (!script) return; + if (autoSettings.end() != std::find_if(autoSettings.begin(), autoSettings.end(), + [&](const AutoSettingEntry& e){ return e.key == key; })) return; + + value = GetSettingBool(key, value); + AutoSettingEntry entry; + entry.type = AutoSettingType::Bool; + entry.key = key; + entry.ptr = &value; + entry.initialBool = value; + autoSettings.push_back(entry); +} + +void ScriptContext::AutoSetting(const std::string& key, glm::vec3& value) { + if (!script) return; + if (autoSettings.end() != std::find_if(autoSettings.begin(), autoSettings.end(), + [&](const AutoSettingEntry& e){ return e.key == key; })) return; + + value = GetSettingVec3(key, value); + AutoSettingEntry entry; + entry.type = AutoSettingType::Vec3; + entry.key = key; + entry.ptr = &value; + entry.initialVec3 = value; + autoSettings.push_back(entry); +} + +void ScriptContext::AutoSetting(const std::string& key, char* buffer, size_t bufferSize) { + if (!script || !buffer || bufferSize == 0) return; + if (autoSettings.end() != std::find_if(autoSettings.begin(), autoSettings.end(), + [&](const AutoSettingEntry& e){ return e.key == key; })) return; + + std::string existing = GetSetting(key, std::string(buffer)); + if (!existing.empty()) { + std::snprintf(buffer, bufferSize, "%s", existing.c_str()); + } + AutoSettingEntry entry; + entry.type = AutoSettingType::StringBuf; + entry.key = key; + entry.ptr = buffer; + entry.bufSize = bufferSize; + entry.initialString = buffer; + autoSettings.push_back(entry); +} + +void ScriptContext::SaveAutoSettings() { + if (!script) return; + bool changed = false; + for (const auto& e : autoSettings) { + std::string newVal; + switch (e.type) { + case AutoSettingType::Bool: { + bool cur = *static_cast(e.ptr); + if (cur == e.initialBool) continue; + newVal = cur ? "1" : "0"; + break; + } + case AutoSettingType::Vec3: { + glm::vec3 cur = *static_cast(e.ptr); + if (glm::all(glm::epsilonEqual(cur, e.initialVec3, 1e-6f))) continue; + newVal = std::to_string(cur.x) + "," + std::to_string(cur.y) + "," + std::to_string(cur.z); + break; + } + case AutoSettingType::StringBuf: { + const char* cur = static_cast(e.ptr); + if (cur && e.initialString == cur) continue; + newVal = cur ? cur : ""; + if (!cur || newVal == e.initialString) continue; + break; + } + } + changed = true; + SetSetting(e.key, newVal); + } + if (changed) { + MarkDirty(); + } +} + +void ScriptContext::StartIEnum(void(*fn)(ScriptContext&, float)) { + if (!script || !fn) return; + auto& v = script->activeIEnums; + if (std::find(v.begin(), v.end(), reinterpret_cast(fn)) == v.end()) { + v.push_back(reinterpret_cast(fn)); + } +} + +void ScriptContext::StopIEnum(void(*fn)(ScriptContext&, float)) { + if (!script || !fn) return; + auto& v = script->activeIEnums; + auto it = std::find(v.begin(), v.end(), reinterpret_cast(fn)); + if (it != v.end()) { + v.erase(it); + } +} + +void ScriptContext::EnsureIEnum(void(*fn)(ScriptContext&, float)) { + if (!IsIEnumRunning(fn)) StartIEnum(fn); +} + +bool ScriptContext::IsIEnumRunning(void(*fn)(ScriptContext&, float)) const { + if (!script || !fn) return false; + auto it = std::find(script->activeIEnums.begin(), script->activeIEnums.end(), + reinterpret_cast(fn)); + return it != script->activeIEnums.end(); +} + +void ScriptContext::StopAllIEnums() { + if (script) script->activeIEnums.clear(); } void ScriptContext::MarkDirty() { @@ -133,6 +309,15 @@ void ScriptRuntime::tickModule(const fs::path& binaryPath, ScriptContext& ctx, f if (runTest && mod->testEditor) { mod->testEditor(ctx, deltaTime); } + + // Tick any IEnum tasks registered by the script (per ScriptComponent instance). + if (ctx.script && !ctx.script->activeIEnums.empty()) { + auto tasks = ctx.script->activeIEnums; // copy so tasks can modify the list + for (void* p : tasks) { + auto fn = reinterpret_cast(p); + if (fn) fn(ctx, deltaTime); + } + } } void ScriptRuntime::unloadAll() { diff --git a/src/ScriptRuntime.h b/src/ScriptRuntime.h index b9e1b80..8d470cd 100644 --- a/src/ScriptRuntime.h +++ b/src/ScriptRuntime.h @@ -10,6 +10,17 @@ struct ScriptContext { Engine* engine = nullptr; SceneObject* object = nullptr; ScriptComponent* script = nullptr; + enum class AutoSettingType { Bool, Vec3, StringBuf }; + struct AutoSettingEntry { + AutoSettingType type; + std::string key; + void* ptr = nullptr; + size_t bufSize = 0; + bool initialBool = false; + glm::vec3 initialVec3 = glm::vec3(0.0f); + std::string initialString; + }; + std::vector autoSettings; // Convenience helpers for scripts SceneObject* FindObjectByName(const std::string& name); @@ -17,6 +28,26 @@ struct ScriptContext { void SetPosition(const glm::vec3& pos); void SetRotation(const glm::vec3& rot); void SetScale(const glm::vec3& scl); + // Settings helpers (auto-mark dirty) + std::string GetSetting(const std::string& key, const std::string& fallback = "") const; + void SetSetting(const std::string& key, const std::string& value); + bool GetSettingBool(const std::string& key, bool fallback = false) const; + void SetSettingBool(const std::string& key, bool value); + glm::vec3 GetSettingVec3(const std::string& key, const glm::vec3& fallback = glm::vec3(0.0f)) const; + void SetSettingVec3(const std::string& key, const glm::vec3& value); + // Console helper + void AddConsoleMessage(const std::string& message, ConsoleMessageType type = ConsoleMessageType::Info); + // Auto-binding helpers: bind once per call, optionally load stored value, then SaveAutoSettings() writes back on change. + void AutoSetting(const std::string& key, bool& value); + void AutoSetting(const std::string& key, glm::vec3& value); + void AutoSetting(const std::string& key, char* buffer, size_t bufferSize); + void SaveAutoSettings(); + // IEnum helpers + void StartIEnum(void(*fn)(ScriptContext&, float)); + void StopIEnum(void(*fn)(ScriptContext&, float)); + void EnsureIEnum(void(*fn)(ScriptContext&, float)); + bool IsIEnumRunning(void(*fn)(ScriptContext&, float)) const; + void StopAllIEnums(); void MarkDirty(); }; @@ -28,6 +59,7 @@ public: using UpdateFn = void(*)(ScriptContext&, float); using TickUpdateFn = void(*)(ScriptContext&, float); using InspectorFn = void(*)(ScriptContext&); + using IEnumFn = void(*)(ScriptContext&, float); InspectorFn getInspector(const fs::path& binaryPath); void tickModule(const fs::path& binaryPath, ScriptContext& ctx, float deltaTime, @@ -50,3 +82,18 @@ private: std::unordered_map loaded; std::string lastError; }; + +// Lightweight coroutine-style helpers (opt-in and no-ops unless used by scripts). +#ifndef IEnum +#define IEnum +#endif + +#ifndef IEnum_Start +#define IEnum_Start(fn) ctx.StartIEnum(fn) +#endif +#ifndef IEnum_Stop +#define IEnum_Stop(fn) ctx.StopIEnum(fn) +#endif +#ifndef IEnum_Ensure +#define IEnum_Ensure(fn) ctx.EnsureIEnum(fn) +#endif