#include "Scripting/ScriptEngine.h" #include "Components/GameObject.h" #include "Scene/Scene.h" #include "Scripting/ScriptComponent.h" #include namespace XCEngine { namespace Scripting { ScriptEngine& ScriptEngine::Get() { static ScriptEngine instance; return instance; } void ScriptEngine::SetRuntime(IScriptRuntime* runtime) { m_runtime = runtime ? runtime : &m_nullRuntime; } void ScriptEngine::OnRuntimeStart(Components::Scene* scene) { OnRuntimeStop(); if (!scene) { return; } m_runtimeScene = scene; m_runtimeRunning = true; m_runtime->OnRuntimeStart(scene); m_runtimeSceneCreatedSubscription = scene->OnGameObjectCreated().Subscribe( [this](Components::GameObject* gameObject) { HandleGameObjectCreated(gameObject); }); for (Components::GameObject* root : scene->GetRootGameObjects()) { CollectScriptComponents(root); } for (const ScriptInstanceKey& key : m_scriptOrder) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; } ScriptInstanceState& state = it->second; if (!ShouldScriptRun(state)) { continue; } EnsureScriptReady(state, true); } } void ScriptEngine::OnRuntimeStop() { if (m_runtimeScene && m_runtimeSceneCreatedSubscription != 0) { m_runtimeScene->OnGameObjectCreated().Unsubscribe(m_runtimeSceneCreatedSubscription); m_runtimeScene->OnGameObjectCreated().ProcessUnsubscribes(); m_runtimeSceneCreatedSubscription = 0; } if (!m_runtimeRunning) { m_runtimeScene = nullptr; m_scriptStates.clear(); m_scriptOrder.clear(); return; } std::vector keys = m_scriptOrder; for (const ScriptInstanceKey& key : keys) { auto it = m_scriptStates.find(key); if (it != m_scriptStates.end()) { StopTrackingScript(it->second, true); } } Components::Scene* stoppedScene = m_runtimeScene; m_scriptStates.clear(); m_scriptOrder.clear(); m_runtimeRunning = false; m_runtimeScene = nullptr; m_runtime->OnRuntimeStop(stoppedScene); } void ScriptEngine::OnFixedUpdate(float fixedDeltaTime) { if (!m_runtimeRunning) { return; } for (const ScriptInstanceKey& key : m_scriptOrder) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; } ScriptInstanceState& state = it->second; if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) { continue; } InvokeLifecycleMethod(state, ScriptLifecycleMethod::FixedUpdate, fixedDeltaTime); } } void ScriptEngine::OnUpdate(float deltaTime) { if (!m_runtimeRunning) { return; } for (const ScriptInstanceKey& key : m_scriptOrder) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; } ScriptInstanceState& state = it->second; if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) { continue; } if (state.startPending && !state.startCalled) { InvokeLifecycleMethod(state, ScriptLifecycleMethod::Start); state.startCalled = true; state.startPending = false; } InvokeLifecycleMethod(state, ScriptLifecycleMethod::Update, deltaTime); } } void ScriptEngine::OnLateUpdate(float deltaTime) { if (!m_runtimeRunning) { return; } for (const ScriptInstanceKey& key : m_scriptOrder) { auto it = m_scriptStates.find(key); if (it == m_scriptStates.end()) { continue; } ScriptInstanceState& state = it->second; if (!ShouldScriptRun(state) || !EnsureScriptReady(state, true) || !state.enabled) { continue; } InvokeLifecycleMethod(state, ScriptLifecycleMethod::LateUpdate, deltaTime); } } void ScriptEngine::OnScriptComponentEnabled(ScriptComponent* component) { if (!m_runtimeRunning || !component) { return; } ScriptInstanceState* state = TrackScriptComponent(component); if (!state) { return; } if (ShouldScriptRun(*state)) { EnsureScriptReady(*state, true); } } void ScriptEngine::OnScriptComponentDisabled(ScriptComponent* component) { if (!m_runtimeRunning || !component) { return; } ScriptInstanceState* state = FindState(component); if (!state || !state->enabled) { return; } InvokeLifecycleMethod(*state, ScriptLifecycleMethod::OnDisable); state->enabled = false; } void ScriptEngine::OnScriptComponentDestroyed(ScriptComponent* component) { if (!m_runtimeRunning || !component) { return; } ScriptInstanceState* state = FindState(component); if (!state) { return; } StopTrackingScript(*state, false); } bool ScriptEngine::HasTrackedScriptComponent(const ScriptComponent* component) const { return FindState(component) != nullptr; } bool ScriptEngine::HasRuntimeInstance(const ScriptComponent* component) const { const ScriptInstanceState* state = FindState(component); return state && state->instanceCreated; } size_t ScriptEngine::ScriptInstanceKeyHasher::operator()(const ScriptInstanceKey& key) const { const size_t h1 = std::hash{}(key.gameObjectUUID); const size_t h2 = std::hash{}(key.scriptComponentUUID); return h1 ^ (h2 + 0x9e3779b97f4a7c15ULL + (h1 << 6) + (h1 >> 2)); } void ScriptEngine::CollectScriptComponents(Components::GameObject* gameObject) { if (!gameObject) { return; } for (ScriptComponent* component : gameObject->GetComponents()) { TrackScriptComponent(component); } for (Components::GameObject* child : gameObject->GetChildren()) { CollectScriptComponents(child); } } void ScriptEngine::EnsureTrackedScriptsReady(Components::GameObject* gameObject) { if (!gameObject) { return; } for (ScriptComponent* component : gameObject->GetComponents()) { ScriptInstanceState* state = FindState(component); if (!state || !ShouldScriptRun(*state)) { continue; } EnsureScriptReady(*state, true); } for (Components::GameObject* child : gameObject->GetChildren()) { EnsureTrackedScriptsReady(child); } } void ScriptEngine::HandleGameObjectCreated(Components::GameObject* gameObject) { if (!m_runtimeRunning || !gameObject || gameObject->GetScene() != m_runtimeScene) { return; } CollectScriptComponents(gameObject); EnsureTrackedScriptsReady(gameObject); } ScriptEngine::ScriptInstanceState* ScriptEngine::TrackScriptComponent(ScriptComponent* component) { if (!component || !component->GetGameObject()) { return nullptr; } const ScriptInstanceKey key{ component->GetGameObject()->GetUUID(), component->GetScriptComponentUUID() }; auto it = m_scriptStates.find(key); if (it != m_scriptStates.end()) { it->second.context.scene = component->GetScene(); it->second.context.gameObject = component->GetGameObject(); it->second.context.component = component; it->second.context.gameObjectUUID = component->GetGameObject()->GetUUID(); it->second.context.scriptComponentUUID = component->GetScriptComponentUUID(); return &it->second; } ScriptInstanceState state; state.context.scene = component->GetScene(); state.context.gameObject = component->GetGameObject(); state.context.component = component; state.context.gameObjectUUID = component->GetGameObject()->GetUUID(); state.context.scriptComponentUUID = component->GetScriptComponentUUID(); auto [insertedIt, inserted] = m_scriptStates.emplace(key, std::move(state)); if (inserted) { m_scriptOrder.push_back(key); } return &insertedIt->second; } ScriptEngine::ScriptInstanceState* ScriptEngine::FindState(const ScriptComponent* component) { if (!component || !component->GetGameObject()) { return nullptr; } const ScriptInstanceKey key{ component->GetGameObject()->GetUUID(), component->GetScriptComponentUUID() }; auto it = m_scriptStates.find(key); return it != m_scriptStates.end() ? &it->second : nullptr; } const ScriptEngine::ScriptInstanceState* ScriptEngine::FindState(const ScriptComponent* component) const { if (!component || !component->GetGameObject()) { return nullptr; } const ScriptInstanceKey key{ component->GetGameObject()->GetUUID(), component->GetScriptComponentUUID() }; auto it = m_scriptStates.find(key); return it != m_scriptStates.end() ? &it->second : nullptr; } void ScriptEngine::RemoveTrackedScriptComponent(const ScriptComponent* component) { if (!component || !component->GetGameObject()) { return; } const ScriptInstanceKey key{ component->GetGameObject()->GetUUID(), component->GetScriptComponentUUID() }; m_scriptStates.erase(key); m_scriptOrder.erase( std::remove(m_scriptOrder.begin(), m_scriptOrder.end(), key), m_scriptOrder.end()); } bool ScriptEngine::ShouldScriptRun(const ScriptInstanceState& state) const { return m_runtimeRunning && state.context.scene == m_runtimeScene && state.context.scene != nullptr && state.context.scene->IsActive() && state.context.gameObject != nullptr && state.context.gameObject->IsActiveInHierarchy() && state.context.component != nullptr && state.context.component->IsEnabled() && state.context.component->HasScriptClass(); } bool ScriptEngine::EnsureScriptReady(ScriptInstanceState& state, bool invokeEnableIfNeeded) { if (!state.context.component || !state.context.gameObject || !state.context.scene || !state.context.component->HasScriptClass()) { return false; } if (!state.instanceCreated) { if (!m_runtime->CreateScriptInstance(state.context)) { return false; } state.instanceCreated = true; } if (!state.awakeCalled) { InvokeLifecycleMethod(state, ScriptLifecycleMethod::Awake); state.awakeCalled = true; } if (invokeEnableIfNeeded && !state.enabled && ShouldScriptRun(state)) { InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnEnable); state.enabled = true; if (!state.startCalled) { state.startPending = true; } } return true; } void ScriptEngine::InvokeLifecycleMethod(ScriptInstanceState& state, ScriptLifecycleMethod method, float deltaTime) { m_runtime->InvokeMethod(state.context, method, deltaTime); } void ScriptEngine::StopTrackingScript(ScriptInstanceState& state, bool runtimeStopping) { if (state.enabled) { InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnDisable); state.enabled = false; } if (state.instanceCreated) { InvokeLifecycleMethod(state, ScriptLifecycleMethod::OnDestroy); m_runtime->DestroyScriptInstance(state.context); state.instanceCreated = false; } state.startPending = false; if (!runtimeStopping) { RemoveTrackedScriptComponent(state.context.component); } } } // namespace Scripting } // namespace XCEngine