diff --git a/src/core/core.cpp b/src/core/core.cpp index 2a4aa6e8f..c4ca7201f 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -799,7 +799,7 @@ float Core::GetProcessUptime() return static_cast(Timer::ConvertValueToSeconds(Timer::GetCurrentValue() - s_locals.process_start_time)); } -void Core::IdleUpdate() +void Core::IdleUpdate(u64 max_poll_time) { InputManager::PollSources(); @@ -812,6 +812,6 @@ void Core::IdleUpdate() Achievements::IdleUpdate(); #ifdef ENABLE_GDB_SERVER - GDBServer::Poll(0); + GDBServer::PollUntil(max_poll_time); #endif } diff --git a/src/core/core_private.h b/src/core/core_private.h index 2166ff9f9..734f9d883 100644 --- a/src/core/core_private.h +++ b/src/core/core_private.h @@ -48,7 +48,7 @@ bool CoreThreadInitialize(bool disable_worker_threads, Error* error); void CoreThreadShutdown(); /// Called to poll input when the session is not running. -void IdleUpdate(); +void IdleUpdate(u64 max_poll_time); } // namespace Core diff --git a/src/core/gdb_server.cpp b/src/core/gdb_server.cpp index 4af2c27c8..c5f46688e 100644 --- a/src/core/gdb_server.cpp +++ b/src/core/gdb_server.cpp @@ -16,6 +16,7 @@ #include "common/small_string.h" #include "common/string_util.h" #include "common/thirdparty/SmallVector.h" +#include "common/timer.h" #include "util/sockets.h" @@ -1038,10 +1039,30 @@ bool GDBServer::HasAnyClients() return !s_locals.clients.empty(); } -void GDBServer::Poll(u32 timeout_ms) +void GDBServer::PollUntil(u64 max_poll_time) { - if (s_locals.multiplexer) - s_locals.multiplexer->PollEventsWithTimeout(timeout_ms); + if (!s_locals.multiplexer) + return; + + if (max_poll_time == 0) + { + s_locals.multiplexer->PollEventsWithTimeout(0); + return; + } + + // Keep polling until we time out, because there could be multiple back<->forth packets. + // Break out if our pause state changes, because we'll probably need to run a frame. + const bool was_running = System::IsRunning(); + const bool exact = (was_running && g_settings.display_optimal_frame_pacing); + Timer::Value poll_start_time = Timer::GetCurrentValue(); + for (;;) + { + const u32 sleep_ms = static_cast(Timer::ConvertValueToMilliseconds(max_poll_time - poll_start_time)); + s_locals.multiplexer->PollEventsWithTimeout(sleep_ms); + poll_start_time = Timer::GetCurrentValue(); + if (poll_start_time >= max_poll_time || System::IsRunning() != was_running || (!exact && sleep_ms == 0)) + break; + } } void GDBServer::Shutdown() diff --git a/src/core/gdb_server.h b/src/core/gdb_server.h index 9b07683de..9927f11e4 100644 --- a/src/core/gdb_server.h +++ b/src/core/gdb_server.h @@ -16,7 +16,7 @@ namespace GDBServer { bool Initialize(u16 port); bool HasAnyClients(); -void Poll(u32 timeout_ms); +void PollUntil(u64 max_poll_time); void Shutdown(); void OnSystemPaused(); diff --git a/src/core/system.cpp b/src/core/system.cpp index fd015298c..8c9ddbcfb 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2044,7 +2044,7 @@ void System::FrameDone() #endif #ifdef ENABLE_GDB_SERVER - GDBServer::Poll(0); + GDBServer::PollUntil(0); #endif // Save states for rewind and runahead. @@ -2261,15 +2261,7 @@ void System::Throttle(Timer::Value current_time, Timer::Value sleep_until) // That way in a query->response->query->response chain, we don't process only one message per frame. if (GDBServer::HasAnyClients()) { - Timer::Value poll_start_time = current_time; - for (;;) - { - const u32 sleep_ms = static_cast(Timer::ConvertValueToMilliseconds(sleep_until - poll_start_time)); - GDBServer::Poll(sleep_ms); - poll_start_time = Timer::GetCurrentValue(); - if (poll_start_time >= sleep_until || (!g_settings.display_optimal_frame_pacing && sleep_ms == 0)) - break; - } + GDBServer::PollUntil(sleep_until); } else #endif @@ -5152,7 +5144,7 @@ void System::DoRewind() VideoThread::PresentCurrentFrame(); Host::PumpMessagesOnCoreThread(); - Core::IdleUpdate(); + Core::IdleUpdate(s_state.next_frame_time); // get back into it straight away if we're no longer rewinding if (!IsRewinding()) diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 04c9dd025..70d7d9943 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -115,7 +115,7 @@ static constexpr int IDLE_UPDATE_INTERVAL_WITH_FULLSCREEN_UI = 8; static constexpr int IDLE_UPDATE_INTERVAL_WHILE_DOWNLOADING = 10; /// Poll at 1ms when running GDB server. We can get rid of this once we move networking to its own thread. -static constexpr int IDLE_UPDATE_INTERVAL_WITH_GDB_CLIENTS = 1; +static constexpr int IDLE_UPDATE_INTERVAL_WITH_GDB_CLIENTS = 10; ////////////////////////////////////////////////////////////////////////// // Local function declarations @@ -2092,7 +2092,7 @@ void CoreThread::createIdleUpdateTimer() m_idle_update_timer = new QTimer(this); m_idle_update_timer->setSingleShot(false); m_idle_update_timer->setTimerType(Qt::CoarseTimer); - connect(m_idle_update_timer, &QTimer::timeout, &Core::IdleUpdate); + connect(m_idle_update_timer, &QTimer::timeout, this, &CoreThread::onIdleUpdateTimer); } void CoreThread::destroyIdleUpdateTimer() @@ -2123,6 +2123,25 @@ void CoreThread::stopIdleUpdateTimer() m_idle_update_timer->stop(); } +void CoreThread::onIdleUpdateTimer() +{ + if (!m_has_gdb_clients) + { + Core::IdleUpdate(0); + return; + } + + // When we have gdb clients, use the multiplexer to sleep instead. that way round-trips can + // be responded to as quickly as possible. + // + // NOTE: This is kinda screwy, because it'll block RunOnCoreThread() callbacks from running + // while we're polling. Hence why it's still capped at 10ms. + + const int timeout = m_idle_update_timer->interval(); + const u64 poll_until = Timer::GetCurrentValue() + Timer::ConvertMillisecondsToValue(static_cast(timeout)); + Core::IdleUpdate(poll_until); +} + void CoreThread::updateIdleTimerInterval() { if (!isCurrentThread()) @@ -2145,13 +2164,10 @@ void CoreThread::updateIdleTimerInterval() int CoreThread::getBackgroundControllerPollInterval() const { -#ifdef ENABLE_GDB_SERVER - if (GDBServer::HasAnyClients()) - return IDLE_UPDATE_INTERVAL_WITH_GDB_CLIENTS; -#endif - if (m_video_thread_run_idle) return IDLE_UPDATE_INTERVAL_WITH_FULLSCREEN_UI; + else if (m_has_gdb_clients) + return IDLE_UPDATE_INTERVAL_WITH_GDB_CLIENTS; else if (m_http_downloader_active) return IDLE_UPDATE_INTERVAL_WHILE_DOWNLOADING; else if (InputManager::GetPollableDeviceCount() > 0) @@ -2173,6 +2189,18 @@ void CoreThread::setHTTPDownloaderActive(bool active) updateIdleTimerInterval(); } +void CoreThread::setGDBActiveClients(bool active) +{ + if (!isCurrentThread()) + { + QMetaObject::invokeMethod(this, &CoreThread::setGDBActiveClients, Qt::QueuedConnection, active); + return; + } + + m_has_gdb_clients = active; + updateIdleTimerInterval(); +} + void CoreThread::setVideoThreadRunIdle(bool active) { if (!isCurrentThread()) @@ -2214,7 +2242,7 @@ void Host::OnHTTPDownloaderActiveChanged(bool active) void Host::OnGDBServerActiveClientsChanged(bool has_clients) { - g_core_thread->updateIdleTimerInterval(); + g_core_thread->setGDBActiveClients(has_clients); } void CoreThread::stop() diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index d2c644c9e..e536ca0d5 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -183,6 +183,7 @@ public: void startControllerTest(); void openGamePropertiesForCurrentGame(const QString& category = {}); void setHTTPDownloaderActive(bool active); + void setGDBActiveClients(bool active); void setVideoThreadRunIdle(bool active); void updateFullscreenUITheme(); void runOnCoreThread(const std::function& callback); @@ -205,6 +206,7 @@ private: void createIdleUpdateTimer(); void destroyIdleUpdateTimer(); + void onIdleUpdateTimer(); void bootOrLoadState(std::string path); @@ -218,6 +220,7 @@ private: bool m_shutdown_flag = false; bool m_http_downloader_active = false; + bool m_has_gdb_clients = false; bool m_video_thread_run_idle = false; bool m_is_fullscreen_ui_started = false; bool m_was_paused_by_focus_loss = false;