From 1ce5e2425d86abf78ff176053b7b09bf979abb3b Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Mon, 2 Jan 2023 03:27:45 +0100 Subject: [PATCH] CoreTiming: Add support for secondary event type that does not affect timing. --- Source/Core/Core/CoreTiming.cpp | 38 +++++++++++++++++++++++++++++++++ Source/Core/Core/CoreTiming.h | 16 ++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index ae0a7bd2bf..9b104eb64b 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -212,6 +212,9 @@ void CoreTimingManager::DoState(PointerWrap& p) // The stave state has changed the time, so our previous Throttle targets are invalid. // Especially when global_time goes down; So we create a fake throttle update. ResetThrottle(m_globals.global_timer); + + // Throw away pending external events when loading state, they no longer apply. + m_external_event_queue.clear(); } } @@ -282,6 +285,24 @@ void CoreTimingManager::ScheduleEvent(s64 cycles_into_future, EventType* event_t } } +void CoreTimingManager::ScheduleExternalEvent(u64 timepoint, EventType* event_type, u64 userdata, + u64 unique_id) +{ + if (Core::IsCPUThread()) + { + m_external_event_queue.emplace_back( + Event{static_cast(timepoint), unique_id, userdata, event_type}); + std::push_heap(m_external_event_queue.begin(), m_external_event_queue.end(), + std::greater()); + } + else + { + std::lock_guard lk(m_ts_write_lock); + m_external_pending_queue.Push( + Event{static_cast(timepoint), unique_id, userdata, event_type}); + } +} + void CoreTimingManager::RemoveEvent(EventType* event_type) { auto itr = std::remove_if(m_event_queue.begin(), m_event_queue.end(), @@ -322,6 +343,13 @@ void CoreTimingManager::MoveEvents() m_event_queue.emplace_back(std::move(ev)); std::push_heap(m_event_queue.begin(), m_event_queue.end(), std::greater()); } + + for (Event ev; m_external_pending_queue.Pop(ev);) + { + m_external_event_queue.emplace_back(std::move(ev)); + std::push_heap(m_external_event_queue.begin(), m_external_event_queue.end(), + std::greater()); + } } void CoreTimingManager::Advance() @@ -351,6 +379,16 @@ void CoreTimingManager::Advance() evt.type->callback(m_system, evt.userdata, m_globals.global_timer - evt.time); } + while (!m_external_event_queue.empty() && + m_external_event_queue.front().time <= m_globals.global_timer) + { + Event evt = std::move(m_external_event_queue.front()); + std::pop_heap(m_external_event_queue.begin(), m_external_event_queue.end(), + std::greater()); + m_external_event_queue.pop_back(); + evt.type->callback(m_system, evt.userdata, m_globals.global_timer - evt.time); + } + m_is_global_timer_sane = false; // Still events left (scheduled in the future) diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 6c60b74479..d20ab11889 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -104,6 +104,13 @@ public: void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata = 0, FromThread from = FromThread::CPU); + // Similar to ScheduleEvent, but enqueues an event in the secondary event queue that does not + // affect timing logic and isn't savestated. Used primarily for handling events in a deterministic + // manner during netplay. Note that 'timepoint' is absolute (instead of ScheduleEvent's relative) + // and that the user should try to provide a 'unique_id' for consistent event ordering if they + // happen to be at the same timepoint. + void ScheduleExternalEvent(u64 timepoint, EventType* event_type, u64 userdata, u64 unique_id); + // We only permit one event of each type in the queue at a time. void RemoveEvent(EventType* event_type); void RemoveAllEvents(EventType* event_type); @@ -172,6 +179,15 @@ private: std::mutex m_ts_write_lock; Common::SPSCQueue m_ts_queue; + // A second event queue that is used for timing 'external' events that are sent by the emulator + // rather than by the emulated game. Netplay uses these for syncing non-controller-button events + // sent by a single client, such as a press of the physical Reset button on the console, or an + // unplugging of a controller. These don't affect timing logic (and thus will not run at a precise + // time, but instead at the first opportunity given by the regular events) and do not get written + // to savestates. + std::vector m_external_event_queue; + Common::SPSCQueue m_external_pending_queue; + float m_last_oc_factor = 0.0f; s64 m_idled_cycles = 0;