diff --git a/imgui.ini b/imgui.ini index 6310b76..1c57316 100644 --- a/imgui.ini +++ b/imgui.ini @@ -12,11 +12,11 @@ Collapsed=0 Pos=324,28 Size=645,580 Collapsed=0 -DockId=0x00000001,0 +DockId=0x00000004,0 [Window][Log Viewer] -Pos=324,610 -Size=645,102 +Pos=8,610 +Size=1264,102 Collapsed=0 DockId=0x00000002,0 @@ -77,13 +77,13 @@ Collapsed=0 [Window][Inspector] Pos=971,28 -Size=301,684 +Size=301,580 Collapsed=0 DockId=0x00000006,0 [Window][Scene] Pos=8,28 -Size=314,684 +Size=314,580 Collapsed=0 DockId=0x00000003,0 @@ -103,11 +103,11 @@ Size=306,82 Collapsed=0 [Docking][Data] -DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=X Selected=0x27A02DAA - DockNode ID=0x00000005 Parent=0x9076BACA SizeRef=961,684 Split=X - DockNode ID=0x00000003 Parent=0x00000005 SizeRef=314,684 Selected=0xE601B12F - DockNode ID=0x00000004 Parent=0x00000005 SizeRef=645,684 Split=Y - DockNode ID=0x00000001 Parent=0x00000004 SizeRef=1264,580 CentralNode=1 Selected=0x27A02DAA - DockNode ID=0x00000002 Parent=0x00000004 SizeRef=1264,102 Selected=0xBEDDA0C1 - DockNode ID=0x00000006 Parent=0x9076BACA SizeRef=301,684 Selected=0x36DC96AB +DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=Y Selected=0x27A02DAA + DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,580 Split=X + DockNode ID=0x00000005 Parent=0x00000001 SizeRef=961,684 Split=X + DockNode ID=0x00000003 Parent=0x00000005 SizeRef=314,684 Selected=0xE601B12F + DockNode ID=0x00000004 Parent=0x00000005 SizeRef=645,684 CentralNode=1 Selected=0x27A02DAA + DockNode ID=0x00000006 Parent=0x00000001 SizeRef=301,684 Selected=0x36DC96AB + DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,102 Selected=0xBEDDA0C1 diff --git a/include/Application.h b/include/Application.h index 452831c..22751ec 100644 --- a/include/Application.h +++ b/include/Application.h @@ -30,9 +30,15 @@ private: std::string path; std::string moduleName; asIScriptModule* module = nullptr; - asIScriptFunction* initFunc = nullptr; - asIScriptFunction* updateFunc = nullptr; - asIScriptFunction* shutdownFunc = 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; }; @@ -69,6 +75,7 @@ private: void UpdateEntityScripts(float deltaTime); void ShutdownEntityScripts(); void ProcessSceneScriptChanges(); + void ReloadEntityScriptModules(); EntityScriptModule* GetEntityScriptModule(const std::string& path); void RenderScene(); void Draw(); diff --git a/include/ECSComponents.h b/include/ECSComponents.h index 6acb29f..38a6770 100644 --- a/include/ECSComponents.h +++ b/include/ECSComponents.h @@ -61,6 +61,11 @@ 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; }; // SceneEntity component - marks entities owned by the scene diff --git a/include/scripting/ScriptEngine.h b/include/scripting/ScriptEngine.h index 649c1c4..6f016af 100644 --- a/include/scripting/ScriptEngine.h +++ b/include/scripting/ScriptEngine.h @@ -30,6 +30,14 @@ public: 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); + private: asIScriptEngine* engine; asIScriptFunction* updateFunc; diff --git a/scenes/demo.toml b/scenes/demo.toml index 90059b1..88c71d2 100644 --- a/scenes/demo.toml +++ b/scenes/demo.toml @@ -23,7 +23,7 @@ shader_fs = "shaders/toon.fs" id = "entity_3" parent = "entity_2" tag = "Child" -position = [0.000, 1.250, 0.000] +position = [0.000, 1.950, 0.000] scale = [0.600, 0.600, 0.600] rotation_deg = 0.000 model = "sphere" @@ -34,12 +34,13 @@ color = 0xFF0000FF outline_size = 0.000 shader_vs = "shaders/toon.vs" shader_fs = "shaders/toon.fs" +script = "scripts/ball.as" [[entity]] id = "entity_6" parent = "entity_2" tag = "Child 2" -position = [0.000, 1.000, 1.900] +position = [0.000, 1.998, 1.900] scale = [1.000, 1.000, 1.000] rotation_deg = 0.000 model = "sphere" diff --git a/scripts/ball.as b/scripts/ball.as index f21e1c1..c389c4f 100644 --- a/scripts/ball.as +++ b/scripts/ball.as @@ -1,104 +1,47 @@ -// ScriptComponent example: vertical sine movement +// Class-based entity script: one `EntityScript` instance per entity. +// Implements Init/Update/Shutdown so the engine will instantiate it per-entity. -array trackedEntities; -array baseX; -array baseY; -array baseZ; -array phase; - -float amplitude = 0.5f; -float speed = 1.5f; - -int FindEntityIndex(uint entity) +class EntityScript { - for (uint i = 0; i < trackedEntities.length(); ++i) + float baseX; + float baseY; + float baseZ; + float phase; + + // per-instance movement parameters (can be tuned per-entity) + float amplitude = 2.5f; + float speed = 2.5f; + + EntityScript() { - if (trackedEntities[i] == entity) - { - return int(i); - } + 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); + } + + void Shutdown(uint entity) + { + // nothing to clean up per-instance for this simple example } - return -1; -} - -void TrackEntity(uint entity) -{ - if (FindEntityIndex(entity) != -1) - { - return; - } - - if (!ECS::HasTransform(entity)) - { - ECS::AddTransform(entity, 0.0f, 0.0f, 0.0f); - } - - float x = 0.0f; - float y = 0.0f; - float z = 0.0f; - ECS::GetPosition(entity, x, y, z); - - trackedEntities.insertLast(entity); - baseX.insertLast(x); - baseY.insertLast(y); - baseZ.insertLast(z); - phase.insertLast(0.0f); -} - -void UntrackEntity(uint entity) -{ - int idx = FindEntityIndex(entity); - if (idx < 0) - { - return; - } - - trackedEntities.removeAt(idx); - baseX.removeAt(idx); - baseY.removeAt(idx); - baseZ.removeAt(idx); - phase.removeAt(idx); -} - -void EntityInit(uint entity) -{ - if (!ECS::IsValid(entity)) - { - return; - } - - TrackEntity(entity); -} - -void EntityUpdate(uint entity, float dt) -{ - if (!ECS::IsValid(entity)) - { - return; - } - - int idx = FindEntityIndex(entity); - if (idx < 0) - { - TrackEntity(entity); - idx = FindEntityIndex(entity); - if (idx < 0) - { - return; - } - } - - if (!ECS::HasTransform(entity)) - { - return; - } - - phase[idx] += dt; - float offset = Math::Sin(phase[idx] * speed + float(entity) * 0.1f) * amplitude; - ECS::SetPosition(entity, baseX[idx], baseY[idx] + offset, baseZ[idx]); -} - -void EntityShutdown(uint entity) -{ - UntrackEntity(entity); } diff --git a/src/Application.cpp b/src/Application.cpp index b3af0ff..eab479d 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -138,20 +138,26 @@ void Application::Update(float deltaTime) ProcessSceneScriptChanges(); // Check for hot reload - if (hotReload && hotReload->CheckForChanges()) - { - bool success = scriptEngine.CompileScript(currentSceneScriptPath); - scriptCompilationError = !success; - if (!success) - { - log_warn("Script compilation failed - keeping previous version"); - Toast::Warning("Script compilation failed - check console for details."); - } - else - { - Toast::Success("Script reloaded successfully."); - // Should call init function again or things don't work - scriptEngine.CallScriptFunction(scriptEngine.GetInitFunction()); + if (hotReload) { + bool changed = hotReload->CheckForChanges(); + if (changed) { + bool success = scriptEngine.CompileScript(currentSceneScriptPath); + scriptCompilationError = !success; + if (!success) + { + log_warn("Script compilation failed - keeping previous version"); + Toast::Warning("Script compilation failed - check console for details."); + } + else + { + Toast::Success("Script reloaded successfully."); + // Should call init function again or things don't work + scriptEngine.CallScriptFunction(scriptEngine.GetInitFunction()); + } + + // Refresh any entity script modules so class-based entity scripts + // pick up changes immediately (release old instances then recompile). + ReloadEntityScriptModules(); } } @@ -203,19 +209,55 @@ Application::EntityScriptModule* Application::GetEntityScriptModule(const std::s } if (moduleInfo.module) { - moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void EntityInit(uint)"); - if (!moduleInfo.initFunc) { - moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void Init(uint)"); + // Try to detect an object type to instantiate for per-entity scripts. + // Prefer an explicit type named 'EntityScript' or any type that + // implements the expected entity methods. Do NOT pick the first + // available type as that may be an add-on type (array/string/etc). + asITypeInfo* objType = nullptr; + int count = moduleInfo.module->GetObjectTypeCount(); + for (int i = 0; i < count; ++i) { + asITypeInfo* t = moduleInfo.module->GetObjectTypeByIndex(i); + const char* n = t->GetName(); + if (!n) continue; + std::string name(n); + // Prefer explicit EntityScript class name + if (name == "EntityScript") { objType = t; break; } + // Otherwise, accept any script class that exposes Update/Init/Shutdown + // (at least one of the methods must exist) + bool hasInit = (t->GetMethodByDecl("void Init(uint)") != nullptr); + bool hasUpdate = (t->GetMethodByDecl("void Update(uint, float)") != nullptr); + bool hasShutdown = (t->GetMethodByDecl("void Shutdown(uint)") != nullptr); + if (hasInit || hasUpdate || hasShutdown) { + objType = t; + break; + } } - moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void EntityUpdate(uint, float)"); - if (!moduleInfo.updateFunc) { - moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void Update(uint, float)"); - } + if (objType) { + moduleInfo.objectType = objType; + const char* n = objType->GetName(); + moduleInfo.objectTypeName = n ? n : std::string(); - moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void EntityShutdown(uint)"); - if (!moduleInfo.shutdownFunc) { - moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void Shutdown(uint)"); + // Cache object-methods (decls must match what's used in scripts) + moduleInfo.initFunc = objType->GetMethodByDecl("void Init(uint)"); + moduleInfo.updateFunc = objType->GetMethodByDecl("void Update(uint, float)"); + moduleInfo.shutdownFunc = objType->GetMethodByDecl("void Shutdown(uint)"); + } else { + // Also accept free functions for backward compatibility + moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void EntityInit(uint)"); + if (!moduleInfo.initFunc) { + moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void Init(uint)"); + } + + moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void EntityUpdate(uint, float)"); + if (!moduleInfo.updateFunc) { + moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void Update(uint, float)"); + } + + moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void EntityShutdown(uint)"); + if (!moduleInfo.shutdownFunc) { + moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void Shutdown(uint)"); + } } moduleInfo.valid = true; @@ -236,8 +278,16 @@ void Application::UpdateEntityScripts(float deltaTime) auto existingBinding = entityScriptBindings.find(entity); if (script.started && existingBinding != entityScriptBindings.end()) { auto* moduleInfo = GetEntityScriptModule(existingBinding->second); - if (moduleInfo && moduleInfo->valid && moduleInfo->shutdownFunc) { - scriptEngine.CallScriptFunction(moduleInfo->shutdownFunc, static_cast(entity), 0.0f); + if (moduleInfo && moduleInfo->valid) { + // If using object instances, call method on instance and release + if (script.scriptInstance && moduleInfo->shutdownFunc) { + scriptEngine.CallObjectMethod(moduleInfo->shutdownFunc, reinterpret_cast(script.scriptInstance), static_cast(entity), 0.0f); + scriptEngine.ReleaseObjectInstance(reinterpret_cast(script.scriptInstance)); + script.scriptInstance = nullptr; + } else if (moduleInfo->shutdownFunc) { + // Fallback to free-function shutdown + scriptEngine.CallScriptFunction(moduleInfo->shutdownFunc, static_cast(entity), 0.0f); + } } } script.started = false; @@ -254,8 +304,14 @@ void Application::UpdateEntityScripts(float deltaTime) if (previousPath != script.scriptPath) { if (!previousPath.empty()) { auto* oldModule = GetEntityScriptModule(previousPath); - if (oldModule && oldModule->valid && oldModule->shutdownFunc) { - scriptEngine.CallScriptFunction(oldModule->shutdownFunc, static_cast(entity), 0.0f); + if (oldModule && oldModule->valid) { + if (script.scriptInstance && oldModule->shutdownFunc) { + scriptEngine.CallObjectMethod(oldModule->shutdownFunc, reinterpret_cast(script.scriptInstance), static_cast(entity), 0.0f); + scriptEngine.ReleaseObjectInstance(reinterpret_cast(script.scriptInstance)); + script.scriptInstance = nullptr; + } else if (oldModule->shutdownFunc) { + scriptEngine.CallScriptFunction(oldModule->shutdownFunc, static_cast(entity), 0.0f); + } } } entityScriptBindings[entity] = script.scriptPath; @@ -268,14 +324,37 @@ void Application::UpdateEntityScripts(float deltaTime) } if (!script.started) { - if (moduleInfo->initFunc) { - scriptEngine.CallScriptFunction(moduleInfo->initFunc, static_cast(entity), 0.0f); + // Create per-entity instance if object-type based + if (!script.scriptInstance && moduleInfo->objectType) { + std::string classToCreate = !script.className.empty() ? script.className : moduleInfo->objectTypeName; + asIScriptObject* inst = scriptEngine.CreateObjectInstance(moduleInfo->module, classToCreate); + if (inst) { + script.scriptInstance = reinterpret_cast(inst); + // Call Init method if present + if (moduleInfo->initFunc) { + scriptEngine.CallObjectMethod(moduleInfo->initFunc, inst, static_cast(entity), 0.0f); + } + } else { + // Fallback: call free-function init if exists + if (moduleInfo->initFunc) { + scriptEngine.CallScriptFunction(moduleInfo->initFunc, static_cast(entity), 0.0f); + } + } + } else { + // No object instance model; use existing free-function init + if (moduleInfo->initFunc) { + scriptEngine.CallScriptFunction(moduleInfo->initFunc, static_cast(entity), 0.0f); + } } script.started = true; } if (moduleInfo->updateFunc) { - scriptEngine.CallScriptFunction(moduleInfo->updateFunc, static_cast(entity), deltaTime); + if (script.scriptInstance && moduleInfo->objectType) { + scriptEngine.CallObjectMethod(moduleInfo->updateFunc, reinterpret_cast(script.scriptInstance), static_cast(entity), deltaTime); + } else { + scriptEngine.CallScriptFunction(moduleInfo->updateFunc, static_cast(entity), deltaTime); + } } } @@ -300,8 +379,14 @@ void Application::ShutdownEntityScripts() } auto* moduleInfo = GetEntityScriptModule(script.scriptPath); - if (moduleInfo && moduleInfo->valid && moduleInfo->shutdownFunc) { - scriptEngine.CallScriptFunction(moduleInfo->shutdownFunc, static_cast(entity), 0.0f); + if (moduleInfo && moduleInfo->valid) { + if (script.scriptInstance && moduleInfo->shutdownFunc) { + scriptEngine.CallObjectMethod(moduleInfo->shutdownFunc, reinterpret_cast(script.scriptInstance), static_cast(entity), 0.0f); + scriptEngine.ReleaseObjectInstance(reinterpret_cast(script.scriptInstance)); + script.scriptInstance = nullptr; + } else if (moduleInfo->shutdownFunc) { + scriptEngine.CallScriptFunction(moduleInfo->shutdownFunc, static_cast(entity), 0.0f); + } } script.started = false; } @@ -342,6 +427,87 @@ void Application::ProcessSceneScriptChanges() scriptEngine.CallScriptFunction(scriptEngine.GetInitFunction()); } +void Application::ReloadEntityScriptModules() +{ + // Iterate all known entity script modules. For each, call shutdown on + // existing instances, release them, then recompile the module and + // refresh cached method pointers. + for (auto &kv : entityScriptModules) { + auto &moduleInfo = kv.second; + if (moduleInfo.path.empty()) continue; + + log_info("Reloading entity module: %s", moduleInfo.path.c_str()); + // For every entity bound to this path, release its current instance + for (auto it = entityScriptBindings.begin(); it != entityScriptBindings.end(); ++it) { + if (it->second != moduleInfo.path) continue; + entt::entity e = it->first; + if (!registry.valid(e)) continue; + auto &script = registry.get(e); + if (script.scriptInstance) { + // Call old module shutdown method if available + if (moduleInfo.shutdownFunc) { + scriptEngine.CallObjectMethod(moduleInfo.shutdownFunc, reinterpret_cast(script.scriptInstance), static_cast(e), 0.0f); + } + // Release the old instance + scriptEngine.ReleaseObjectInstance(reinterpret_cast(script.scriptInstance)); + script.scriptInstance = nullptr; + script.started = false; + } + } + + // Recompile module in-place using the same moduleName so the engine + // replaces the module's bytecode and types. + asIScriptModule* newModule = nullptr; + bool ok = scriptEngine.CompileModule(moduleInfo.moduleName, moduleInfo.path, &newModule); + moduleInfo.module = newModule; + moduleInfo.valid = ok && (moduleInfo.module != nullptr); + if (moduleInfo.valid) { + log_info("Reloaded entity module: %s", moduleInfo.path.c_str()); + } else { + log_warn("Failed to reload entity module: %s", moduleInfo.path.c_str()); + } + + // Re-detect object type and cached methods (same logic as in GetEntityScriptModule) + moduleInfo.objectType = nullptr; + moduleInfo.objectTypeName.clear(); + moduleInfo.initFunc = nullptr; + moduleInfo.updateFunc = nullptr; + moduleInfo.shutdownFunc = nullptr; + + if (moduleInfo.module) { + asITypeInfo* objType = nullptr; + int count = moduleInfo.module->GetObjectTypeCount(); + for (int i = 0; i < count; ++i) { + asITypeInfo* t = moduleInfo.module->GetObjectTypeByIndex(i); + const char* n = t->GetName(); + if (!n) continue; + std::string name(n); + if (name == "EntityScript") { objType = t; break; } + bool hasInit = (t->GetMethodByDecl("void Init(uint)") != nullptr); + bool hasUpdate = (t->GetMethodByDecl("void Update(uint, float)") != nullptr); + bool hasShutdown = (t->GetMethodByDecl("void Shutdown(uint)") != nullptr); + if (hasInit || hasUpdate || hasShutdown) { objType = t; break; } + } + + if (objType) { + moduleInfo.objectType = objType; + const char* n = objType->GetName(); + moduleInfo.objectTypeName = n ? n : std::string(); + moduleInfo.initFunc = objType->GetMethodByDecl("void Init(uint)"); + moduleInfo.updateFunc = objType->GetMethodByDecl("void Update(uint, float)"); + moduleInfo.shutdownFunc = objType->GetMethodByDecl("void Shutdown(uint)"); + } else { + moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void EntityInit(uint)"); + if (!moduleInfo.initFunc) moduleInfo.initFunc = moduleInfo.module->GetFunctionByDecl("void Init(uint)"); + moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void EntityUpdate(uint, float)"); + if (!moduleInfo.updateFunc) moduleInfo.updateFunc = moduleInfo.module->GetFunctionByDecl("void Update(uint, float)"); + moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void EntityShutdown(uint)"); + if (!moduleInfo.shutdownFunc) moduleInfo.shutdownFunc = moduleInfo.module->GetFunctionByDecl("void Shutdown(uint)"); + } + } + } +} + void Application::RenderScene() { // Query for active camera (first one found with Camera3DComponent) diff --git a/src/scripting/ScriptEngine.cpp b/src/scripting/ScriptEngine.cpp index 68a29aa..8c1028b 100644 --- a/src/scripting/ScriptEngine.cpp +++ b/src/scripting/ScriptEngine.cpp @@ -277,6 +277,75 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, unsigned int enti 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(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::ClearCachedFunctions() { updateFunc = nullptr; hasValidScript = false;