From 2a9c6bbd1dcf0c4fba64390fc1ed4f8ed2898f6f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Mon, 16 Feb 2026 22:32:02 +0100 Subject: [PATCH] Refactor engine main loop to allow external stepping in Web --- Source/Engine/Engine/Engine.cpp | 170 +++++++++++++++++--------------- Source/Engine/Engine/Engine.h | 3 + Source/Engine/Main/Web/main.cpp | 40 +++++++- 3 files changed, 134 insertions(+), 79 deletions(-) diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 3e1080d4f..1458fb52e 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -74,7 +74,7 @@ int32 Engine::ExitCode = 0; Window* Engine::MainWindow = nullptr; double EngineIdleTime = 0; -int32 Engine::Main(const Char* cmdLine) +int32 Engine::OnInit(const Char* cmdLine) { #if COMPILE_WITH_PROFILER extern void InitProfilerMemory(const Char* cmdLine, int32 stage); @@ -151,7 +151,7 @@ int32 Engine::Main(const Char* cmdLine) #if LOG_ENABLE Log::Logger::Dispose(); #endif - return 0; + return 1; } #endif @@ -171,88 +171,96 @@ int32 Engine::Main(const Char* cmdLine) EngineImpl::IsReady = true; PROFILE_MEM_END(); + return 0; +} + +void Engine::OnLoop() +{ + // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend + const bool useSleep = !PLATFORM_WEB; // TODO: this should probably be a platform setting + if ((useSleep && Time::UpdateFPS > ZeroTolerance) || !Platform::GetHasFocus()) + { + double nextTick = Time::GetNextTick(); + double timeToTick = nextTick - Platform::GetTimeSeconds(); + + // Sleep less than needed, some platforms may sleep slightly more than requested + if (timeToTick > 0.002) + { + PROFILE_CPU_NAMED("Idle"); + auto sleepStart = Platform::GetTimeSeconds(); + Platform::Sleep(1); + auto sleepEnd = Platform::GetTimeSeconds(); + EngineIdleTime += sleepEnd - sleepStart; + } + } + + // App paused logic + if (Platform::GetIsPaused()) + { + OnPause(); + do + { + Platform::Sleep(10); + Platform::Tick(); + } while (Platform::GetIsPaused() && !ShouldExit()); + if (ShouldExit()) + return; + OnUnpause(); + } + + // Use the same time for all ticks to improve synchronization + const double time = Platform::GetTimeSeconds(); + + // Update application (will gather data and other platform related events) + { + PROFILE_CPU_NAMED("Platform.Tick"); + Platform::Tick(); +#if COMPILE_WITH_PROFILER + extern void TickProfilerMemory(); + TickProfilerMemory(); +#endif + } + + // Update game logic + if (Time::OnBeginUpdate(time)) + { + OnUpdate(); + OnLateUpdate(); + Time::OnEndUpdate(); + EngineIdleTime = 0; + } + + // Start physics simulation + if (Time::OnBeginPhysics(time)) + { + OnFixedUpdate(); + OnLateFixedUpdate(); + Time::OnEndPhysics(); + } + + // Draw frame + if (Time::OnBeginDraw(time)) + { + OnDraw(); + Time::OnEndDraw(); + } +} + +int32 Engine::Main(const Char* cmdLine) +{ + // Initialize + ExitCode = OnInit(cmdLine); + if (ExitCode != 0) + return ExitCode; + // Main engine loop - const bool useSleep = true; // TODO: this should probably be a platform setting while (!ShouldExit()) { - // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend - if ((useSleep && Time::UpdateFPS > ZeroTolerance) || !Platform::GetHasFocus()) - { - double nextTick = Time::GetNextTick(); - double timeToTick = nextTick - Platform::GetTimeSeconds(); - - // Sleep less than needed, some platforms may sleep slightly more than requested - if (timeToTick > 0.002) - { - PROFILE_CPU_NAMED("Idle"); - auto sleepStart = Platform::GetTimeSeconds(); - Platform::Sleep(1); - auto sleepEnd = Platform::GetTimeSeconds(); - EngineIdleTime += sleepEnd - sleepStart; - } - } - - // App paused logic - if (Platform::GetIsPaused()) - { - OnPause(); - do - { - Platform::Sleep(10); - Platform::Tick(); - } while (Platform::GetIsPaused() && !ShouldExit()); - if (ShouldExit()) - break; - OnUnpause(); - } - - // Use the same time for all ticks to improve synchronization - const double time = Platform::GetTimeSeconds(); - - // Update application (will gather data and other platform related events) - { - PROFILE_CPU_NAMED("Platform.Tick"); - Platform::Tick(); -#if COMPILE_WITH_PROFILER - extern void TickProfilerMemory(); - TickProfilerMemory(); -#endif - } - - // Update game logic - if (Time::OnBeginUpdate(time)) - { - OnUpdate(); - OnLateUpdate(); - Time::OnEndUpdate(); - EngineIdleTime = 0; - } - - // Start physics simulation - if (Time::OnBeginPhysics(time)) - { - OnFixedUpdate(); - OnLateFixedUpdate(); - Time::OnEndPhysics(); - } - - // Draw frame - if (Time::OnBeginDraw(time)) - { - OnDraw(); - Time::OnEndDraw(); - } + OnLoop(); } - // Call on exit event + // Shutdown OnExit(); - - // Delete temporary directory only if Engine is closing normally (after crash user/developer can restore some data) - if (FileSystem::DirectoryExists(Globals::TemporaryFolder)) - { - FileSystem::DeleteDirectory(Globals::TemporaryFolder); - } - return ExitCode; } @@ -556,6 +564,12 @@ void Engine::OnExit() #endif Platform::Exit(); + + // Delete temporary directory only if Engine is closing normally (after crash user/developer can restore some data) + if (ExitCode == 0 && FileSystem::DirectoryExists(Globals::TemporaryFolder)) + { + FileSystem::DeleteDirectory(Globals::TemporaryFolder); + } } void EngineImpl::InitLog() diff --git a/Source/Engine/Engine/Engine.h b/Source/Engine/Engine/Engine.h index 1af16a930..7b8de05e9 100644 --- a/Source/Engine/Engine/Engine.h +++ b/Source/Engine/Engine/Engine.h @@ -14,6 +14,7 @@ class JsonAsset; /// API_CLASS(Static) class FLAXENGINE_API Engine { + friend class PlatformMain; DECLARE_SCRIPTING_TYPE_NO_SPAWN(Engine); public: @@ -215,6 +216,8 @@ public: API_PROPERTY() static bool HasGameViewportFocus(); private: + static int32 OnInit(const Char* cmdLine); + static void OnLoop(); static void OnPause(); static void OnUnpause(); }; diff --git a/Source/Engine/Main/Web/main.cpp b/Source/Engine/Main/Web/main.cpp index 5ea325542..c70be4a99 100644 --- a/Source/Engine/Main/Web/main.cpp +++ b/Source/Engine/Main/Web/main.cpp @@ -3,10 +3,48 @@ #if PLATFORM_WEB #include "Engine/Engine/Engine.h" +#include + +class PlatformMain +{ + int32 State = 0; + + static void Loop() + { + // Tick engine + Engine::OnLoop(); + + if (Engine::ShouldExit()) + { + // Exit engine + Engine::OnExit(); + emscripten_cancel_main_loop(); + emscripten_force_exit(Engine::ExitCode); + return; + } + } + +public: + static int32 Main() + { + // Initialize engine + int32 result = Engine::OnInit(TEXT("")); + if (result != 0) + return result; + + // Setup main loop to be called by Emscripten + emscripten_set_main_loop(Loop, -1, false); + + // Run the first loop + Loop(); + + return 0; + } +}; int main() { - return Engine::Main(TEXT("")); + return PlatformMain::Main(); } #endif