From e58bc3f0075e2548385f988977f74ff2e4196d79 Mon Sep 17 00:00:00 2001 From: scawful Date: Mon, 6 Oct 2025 09:53:03 -0400 Subject: [PATCH] feat: Introduce TimingManager for Accurate Frame Timing - Added TimingManager class to provide precise timing for animations and frame pacing, addressing issues with ImGui::GetIO().DeltaTime. - Updated event handling in the window management to use SDL_PollEvent for improved responsiveness. - Integrated TimingManager into EditorManager, BackgroundRenderer, and WelcomeScreen for consistent delta time usage across the application. - Enhanced animation updates to utilize accurate frame timing, improving visual performance and user experience. --- src/app/core/timing.h | 119 +++++++++++++++++++++++ src/app/core/window.cc | 43 ++++---- src/app/editor/editor_manager.cc | 7 +- src/app/editor/ui/background_renderer.cc | 3 +- src/app/editor/ui/welcome_screen.cc | 4 +- 5 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 src/app/core/timing.h diff --git a/src/app/core/timing.h b/src/app/core/timing.h new file mode 100644 index 00000000..6a0ae3aa --- /dev/null +++ b/src/app/core/timing.h @@ -0,0 +1,119 @@ +#ifndef YAZE_APP_CORE_TIMING_H +#define YAZE_APP_CORE_TIMING_H + +#include +#include + +namespace yaze { +namespace core { + +/** + * @class TimingManager + * @brief Provides accurate timing for animations and frame pacing + * + * This class solves the issue where ImGui::GetIO().DeltaTime only updates + * when events are processed (mouse movement, etc). It uses SDL's performance + * counter to provide accurate timing regardless of input events. + */ +class TimingManager { + public: + static TimingManager& Get() { + static TimingManager instance; + return instance; + } + + /** + * @brief Update the timing manager (call once per frame) + * @return The delta time since last update in seconds + */ + float Update() { + uint64_t current_time = SDL_GetPerformanceCounter(); + float delta_time = 0.0f; + + if (last_time_ > 0) { + delta_time = (current_time - last_time_) / static_cast(frequency_); + + // Clamp delta time to prevent huge jumps (e.g., when debugging) + if (delta_time > 0.1f) { + delta_time = 0.1f; + } + + accumulated_time_ += delta_time; + frame_count_++; + + // Update FPS counter once per second + if (accumulated_time_ >= 1.0f) { + fps_ = static_cast(frame_count_) / accumulated_time_; + frame_count_ = 0; + accumulated_time_ = 0.0f; + } + } + + last_time_ = current_time; + last_delta_time_ = delta_time; + return delta_time; + } + + /** + * @brief Get the last frame's delta time in seconds + */ + float GetDeltaTime() const { + return last_delta_time_; + } + + /** + * @brief Get current FPS + */ + float GetFPS() const { + return fps_; + } + + /** + * @brief Get total elapsed time since first update + */ + float GetElapsedTime() const { + if (last_time_ == 0) return 0.0f; + uint64_t current_time = SDL_GetPerformanceCounter(); + return (current_time - first_time_) / static_cast(frequency_); + } + + /** + * @brief Reset the timing state + */ + void Reset() { + last_time_ = 0; + first_time_ = SDL_GetPerformanceCounter(); + accumulated_time_ = 0.0f; + frame_count_ = 0; + fps_ = 0.0f; + last_delta_time_ = 0.0f; + } + + private: + TimingManager() { + frequency_ = SDL_GetPerformanceFrequency(); + first_time_ = SDL_GetPerformanceCounter(); + last_time_ = 0; + accumulated_time_ = 0.0f; + frame_count_ = 0; + fps_ = 0.0f; + last_delta_time_ = 0.0f; + } + + uint64_t frequency_; + uint64_t first_time_; + uint64_t last_time_; + float accumulated_time_; + uint32_t frame_count_; + float fps_; + float last_delta_time_; + + TimingManager(const TimingManager&) = delete; + TimingManager& operator=(const TimingManager&) = delete; +}; + +} // namespace core +} // namespace yaze + +#endif // YAZE_APP_CORE_TIMING_H + diff --git a/src/app/core/window.cc b/src/app/core/window.cc index f705b986..f0cd0dd5 100644 --- a/src/app/core/window.cc +++ b/src/app/core/window.cc @@ -120,28 +120,29 @@ absl::Status ShutdownWindow(Window& window) { absl::Status HandleEvents(Window& window) { SDL_Event event; ImGuiIO& io = ImGui::GetIO(); - SDL_WaitEvent(&event); - ImGui_ImplSDL2_ProcessEvent(&event); - switch (event.type) { - case SDL_KEYDOWN: - case SDL_KEYUP: { - io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); - io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); - io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); - io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); - break; - } - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_CLOSE: - window.active_ = false; - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - io.DisplaySize.x = static_cast(event.window.data1); - io.DisplaySize.y = static_cast(event.window.data2); - break; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + switch (event.type) { + case SDL_KEYDOWN: + case SDL_KEYUP: { + io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); + io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); + io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); + io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); + break; } - break; + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_CLOSE: + window.active_ = false; + break; + case SDL_WINDOWEVENT_SIZE_CHANGED: + io.DisplaySize.x = static_cast(event.window.data1); + io.DisplaySize.y = static_cast(event.window.data2); + break; + } + break; + } } int mouseX; int mouseY; diff --git a/src/app/editor/editor_manager.cc b/src/app/editor/editor_manager.cc index 1d47c5d2..a4308941 100644 --- a/src/app/editor/editor_manager.cc +++ b/src/app/editor/editor_manager.cc @@ -11,6 +11,7 @@ #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "app/core/features.h" +#include "app/core/timing.h" #include "util/file_util.h" #include "app/core/project.h" #include "app/editor/code/assembly_editor.h" @@ -636,6 +637,10 @@ void EditorManager::Initialize(const std::string& filename) { } absl::Status EditorManager::Update() { + // Update timing manager for accurate delta time across the application + // This fixes animation timing issues that occur when mouse isn't moving + core::TimingManager::Get().Update(); + popup_manager_->DrawPopups(); ExecuteShortcuts(context_.shortcut_manager); toast_manager_.Draw(); @@ -1475,7 +1480,7 @@ void EditorManager::DrawMenuBar() { if (show_emulator_) { Begin("Emulator", &show_emulator_, ImGuiWindowFlags_MenuBar); - emulator_.Run(); + emulator_.Run(current_rom_); End(); } diff --git a/src/app/editor/ui/background_renderer.cc b/src/app/editor/ui/background_renderer.cc index e57c228f..442c6d84 100644 --- a/src/app/editor/ui/background_renderer.cc +++ b/src/app/editor/ui/background_renderer.cc @@ -3,6 +3,7 @@ #include #include +#include "app/core/timing.h" #include "app/gui/theme_manager.h" #include "imgui/imgui.h" @@ -23,7 +24,7 @@ void BackgroundRenderer::RenderDockingBackground(ImDrawList* draw_list, const Im const ImVec2& window_size, const Color& theme_color) { if (!draw_list) return; - UpdateAnimation(ImGui::GetIO().DeltaTime); + UpdateAnimation(core::TimingManager::Get().GetDeltaTime()); // Get current theme colors auto& theme_manager = ThemeManager::Get(); diff --git a/src/app/editor/ui/welcome_screen.cc b/src/app/editor/ui/welcome_screen.cc index 1cb561b4..c8fba878 100644 --- a/src/app/editor/ui/welcome_screen.cc +++ b/src/app/editor/ui/welcome_screen.cc @@ -10,6 +10,7 @@ #include "absl/time/clock.h" #include "absl/time/time.h" #include "app/core/project.h" +#include "app/core/timing.h" #include "app/gui/icons.h" #include "app/gui/theme_manager.h" #include "imgui/imgui.h" @@ -244,7 +245,8 @@ bool WelcomeScreen::Show(bool* p_open) { } // Smooth interpolation to target position (faster response) - float lerp_speed = 8.0f * ImGui::GetIO().DeltaTime; + // Use TimingManager for accurate delta time + float lerp_speed = 8.0f * yaze::core::TimingManager::Get().GetDeltaTime(); triforce_positions_[i].x += (target_pos.x - triforce_positions_[i].x) * lerp_speed; triforce_positions_[i].y += (target_pos.y - triforce_positions_[i].y) * lerp_speed;