mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-21 09:49:01 +00:00
Merge branch 'AdmiralCurtiss-netplay-events'
This commit is contained in:
commit
fa950acffc
11 changed files with 318 additions and 1 deletions
|
@ -563,6 +563,9 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
|
||||||
HW::Init(system,
|
HW::Init(system,
|
||||||
NetPlay::IsNetPlayRunning() ? &(boot_session_data.GetNetplaySettings()->sram) : nullptr);
|
NetPlay::IsNetPlayRunning() ? &(boot_session_data.GetNetplaySettings()->sram) : nullptr);
|
||||||
|
|
||||||
|
if (NetPlay::IsNetPlayRunning())
|
||||||
|
NetPlay::NetPlay_RegisterEvents();
|
||||||
|
|
||||||
Common::ScopeGuard hw_guard{[&system] {
|
Common::ScopeGuard hw_guard{[&system] {
|
||||||
// We must set up this flag before executing HW::Shutdown()
|
// We must set up this flag before executing HW::Shutdown()
|
||||||
s_hardware_initialized = false;
|
s_hardware_initialized = false;
|
||||||
|
|
|
@ -212,6 +212,9 @@ void CoreTimingManager::DoState(PointerWrap& p)
|
||||||
// The stave state has changed the time, so our previous Throttle targets are invalid.
|
// 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.
|
// Especially when global_time goes down; So we create a fake throttle update.
|
||||||
ResetThrottle(m_globals.global_timer);
|
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<s64>(timepoint), unique_id, userdata, event_type});
|
||||||
|
std::push_heap(m_external_event_queue.begin(), m_external_event_queue.end(),
|
||||||
|
std::greater<Event>());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::lock_guard lk(m_ts_write_lock);
|
||||||
|
m_external_pending_queue.Push(
|
||||||
|
Event{static_cast<s64>(timepoint), unique_id, userdata, event_type});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CoreTimingManager::RemoveEvent(EventType* event_type)
|
void CoreTimingManager::RemoveEvent(EventType* event_type)
|
||||||
{
|
{
|
||||||
auto itr = std::remove_if(m_event_queue.begin(), m_event_queue.end(),
|
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));
|
m_event_queue.emplace_back(std::move(ev));
|
||||||
std::push_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
|
std::push_heap(m_event_queue.begin(), m_event_queue.end(), std::greater<Event>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<Event>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreTimingManager::Advance()
|
void CoreTimingManager::Advance()
|
||||||
|
@ -351,6 +379,16 @@ void CoreTimingManager::Advance()
|
||||||
evt.type->callback(m_system, evt.userdata, m_globals.global_timer - evt.time);
|
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<Event>());
|
||||||
|
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;
|
m_is_global_timer_sane = false;
|
||||||
|
|
||||||
// Still events left (scheduled in the future)
|
// Still events left (scheduled in the future)
|
||||||
|
|
|
@ -104,6 +104,13 @@ public:
|
||||||
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata = 0,
|
void ScheduleEvent(s64 cycles_into_future, EventType* event_type, u64 userdata = 0,
|
||||||
FromThread from = FromThread::CPU);
|
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.
|
// We only permit one event of each type in the queue at a time.
|
||||||
void RemoveEvent(EventType* event_type);
|
void RemoveEvent(EventType* event_type);
|
||||||
void RemoveAllEvents(EventType* event_type);
|
void RemoveAllEvents(EventType* event_type);
|
||||||
|
@ -172,6 +179,15 @@ private:
|
||||||
std::mutex m_ts_write_lock;
|
std::mutex m_ts_write_lock;
|
||||||
Common::SPSCQueue<Event, false> m_ts_queue;
|
Common::SPSCQueue<Event, false> 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<Event> m_external_event_queue;
|
||||||
|
Common::SPSCQueue<Event, false> m_external_pending_queue;
|
||||||
|
|
||||||
float m_last_oc_factor = 0.0f;
|
float m_last_oc_factor = 0.0f;
|
||||||
|
|
||||||
s64 m_idled_cycles = 0;
|
s64 m_idled_cycles = 0;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "Core/HW/SystemTimers.h"
|
#include "Core/HW/SystemTimers.h"
|
||||||
#include "Core/IOS/IOS.h"
|
#include "Core/IOS/IOS.h"
|
||||||
#include "Core/IOS/STM/STM.h"
|
#include "Core/IOS/STM/STM.h"
|
||||||
|
#include "Core/NetPlayClient.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
#include "VideoCommon/AsyncRequests.h"
|
#include "VideoCommon/AsyncRequests.h"
|
||||||
|
@ -266,6 +267,14 @@ void ProcessorInterfaceManager::ResetButton_Tap()
|
||||||
m_event_type_toggle_reset_button, false, CoreTiming::FromThread::ANY);
|
m_event_type_toggle_reset_button, false, CoreTiming::FromThread::ANY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProcessorInterfaceManager::ResetButton_Tap_FromUser()
|
||||||
|
{
|
||||||
|
if (NetPlay::IsNetPlayRunning())
|
||||||
|
NetPlay::ScheduleResetButtonTap();
|
||||||
|
else
|
||||||
|
ResetButton_Tap();
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessorInterfaceManager::PowerButton_Tap()
|
void ProcessorInterfaceManager::PowerButton_Tap()
|
||||||
{
|
{
|
||||||
if (!Core::IsRunning())
|
if (!Core::IsRunning())
|
||||||
|
|
|
@ -82,6 +82,7 @@ public:
|
||||||
|
|
||||||
// Thread-safe func which sets and clears reset button state automagically
|
// Thread-safe func which sets and clears reset button state automagically
|
||||||
void ResetButton_Tap();
|
void ResetButton_Tap();
|
||||||
|
void ResetButton_Tap_FromUser();
|
||||||
void PowerButton_Tap();
|
void PowerButton_Tap();
|
||||||
|
|
||||||
u32 m_interrupt_cause = 0;
|
u32 m_interrupt_cause = 0;
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
#include "Core/NetPlayClient.h"
|
#include "Core/NetPlayClient.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <condition_variable>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -49,6 +51,7 @@
|
||||||
#include "Core/HW/GBAPad.h"
|
#include "Core/HW/GBAPad.h"
|
||||||
#include "Core/HW/GCMemcard/GCMemcard.h"
|
#include "Core/HW/GCMemcard/GCMemcard.h"
|
||||||
#include "Core/HW/GCPad.h"
|
#include "Core/HW/GCPad.h"
|
||||||
|
#include "Core/HW/ProcessorInterface.h"
|
||||||
#include "Core/HW/SI/SI.h"
|
#include "Core/HW/SI/SI.h"
|
||||||
#include "Core/HW/SI/SI_Device.h"
|
#include "Core/HW/SI/SI_Device.h"
|
||||||
#include "Core/HW/SI/SI_DeviceGCController.h"
|
#include "Core/HW/SI/SI_DeviceGCController.h"
|
||||||
|
@ -84,6 +87,19 @@ static std::mutex crit_netplay_client;
|
||||||
static NetPlayClient* netplay_client = nullptr;
|
static NetPlayClient* netplay_client = nullptr;
|
||||||
static bool s_si_poll_batching = false;
|
static bool s_si_poll_batching = false;
|
||||||
|
|
||||||
|
struct ExternalEventSyncData
|
||||||
|
{
|
||||||
|
std::mutex mutex;
|
||||||
|
std::condition_variable cv;
|
||||||
|
std::map<PlayerId, u64> map;
|
||||||
|
ExternalEventID event_id = ExternalEventID::None;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::mutex s_event_sync_mutex;
|
||||||
|
static std::map<u64, ExternalEventSyncData> s_event_sync_map;
|
||||||
|
static CoreTiming::EventType* event_type_sync_time_for_ext_event = nullptr;
|
||||||
|
static CoreTiming::EventType* event_type_ext_event = nullptr;
|
||||||
|
|
||||||
// called from ---GUI--- thread
|
// called from ---GUI--- thread
|
||||||
NetPlayClient::~NetPlayClient()
|
NetPlayClient::~NetPlayClient()
|
||||||
{
|
{
|
||||||
|
@ -436,6 +452,14 @@ void NetPlayClient::OnData(sf::Packet& packet)
|
||||||
OnPowerButton();
|
OnPowerButton();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageID::ScheduleExternalEvent:
|
||||||
|
OnScheduleExternalEvent(packet);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MessageID::SyncTimepointForExternalEvent:
|
||||||
|
OnSyncTimepointForExternalEvent(packet);
|
||||||
|
break;
|
||||||
|
|
||||||
case MessageID::Ping:
|
case MessageID::Ping:
|
||||||
OnPing(packet);
|
OnPing(packet);
|
||||||
break;
|
break;
|
||||||
|
@ -952,6 +976,61 @@ void NetPlayClient::OnPowerButton()
|
||||||
m_dialog->OnMsgPowerButton();
|
m_dialog->OnMsgPowerButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetPlayClient::OnScheduleExternalEvent(sf::Packet& packet)
|
||||||
|
{
|
||||||
|
// A user (could be ourselves, too) has requested the scheduling of an external event.
|
||||||
|
|
||||||
|
ExternalEventID eeid;
|
||||||
|
packet >> eeid;
|
||||||
|
sf::Uint64 uid;
|
||||||
|
packet >> uid;
|
||||||
|
sf::Uint64 timepoint;
|
||||||
|
packet >> timepoint;
|
||||||
|
|
||||||
|
DEBUG_LOG_FMT(NETPLAY, "OnScheduleExternalEvent(): eeid {}, timepoint {}, uid {}", u8(eeid),
|
||||||
|
timepoint, uid);
|
||||||
|
|
||||||
|
// The 'uid' is unique per netplay session and is both used to provide an absolute ordering in
|
||||||
|
// case two events get assigned to the same timepoint, and also to keep track (in
|
||||||
|
// s_event_sync_map) of what event-related packet belongs to what event if multiple are in flight
|
||||||
|
// at the same time.
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lk(s_event_sync_mutex);
|
||||||
|
s_event_sync_map[static_cast<u64>(uid)].event_id = eeid;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& core_timing = Core::System::GetInstance().GetCoreTiming();
|
||||||
|
core_timing.ScheduleExternalEvent(static_cast<u64>(timepoint), event_type_sync_time_for_ext_event,
|
||||||
|
static_cast<u64>(uid), static_cast<u64>(uid));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetPlayClient::OnSyncTimepointForExternalEvent(sf::Packet& packet)
|
||||||
|
{
|
||||||
|
// A user (not ourselves) is currently waiting to execute an external event and has sent us their
|
||||||
|
// current timepoint to determine what the earliest timepoint the event can execute is.
|
||||||
|
|
||||||
|
PlayerId pid;
|
||||||
|
packet >> pid;
|
||||||
|
sf::Uint64 uid;
|
||||||
|
packet >> uid;
|
||||||
|
sf::Uint64 timepoint;
|
||||||
|
packet >> timepoint;
|
||||||
|
|
||||||
|
DEBUG_LOG_FMT(NETPLAY, "OnSyncTimepointForExternalEvent(): player id {}, timepoint {}, uid {}",
|
||||||
|
u8(pid), timepoint, uid);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lk(s_event_sync_mutex);
|
||||||
|
auto& event_data = s_event_sync_map[static_cast<u64>(uid)];
|
||||||
|
{
|
||||||
|
std::lock_guard lk2(event_data.mutex);
|
||||||
|
event_data.map[pid] = static_cast<u64>(timepoint);
|
||||||
|
}
|
||||||
|
event_data.cv.notify_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NetPlayClient::OnPing(sf::Packet& packet)
|
void NetPlayClient::OnPing(sf::Packet& packet)
|
||||||
{
|
{
|
||||||
u32 ping_key = 0;
|
u32 ping_key = 0;
|
||||||
|
@ -2361,6 +2440,14 @@ void NetPlayClient::RequestStopGame()
|
||||||
SendStopGamePacket();
|
SendStopGamePacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NetPlayClient::ScheduleExternalEvent(ExternalEventID id)
|
||||||
|
{
|
||||||
|
sf::Packet packet;
|
||||||
|
packet << MessageID::ScheduleExternalEvent;
|
||||||
|
packet << id;
|
||||||
|
SendAsync(std::move(packet));
|
||||||
|
}
|
||||||
|
|
||||||
void NetPlayClient::SendPowerButtonEvent()
|
void NetPlayClient::SendPowerButtonEvent()
|
||||||
{
|
{
|
||||||
sf::Packet packet;
|
sf::Packet packet;
|
||||||
|
@ -2687,6 +2774,12 @@ void SetSIPollBatching(bool state)
|
||||||
s_si_poll_batching = state;
|
s_si_poll_batching = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScheduleResetButtonTap()
|
||||||
|
{
|
||||||
|
ASSERT(IsNetPlayRunning());
|
||||||
|
netplay_client->ScheduleExternalEvent(ExternalEventID::ResetButton);
|
||||||
|
}
|
||||||
|
|
||||||
void SendPowerButtonEvent()
|
void SendPowerButtonEvent()
|
||||||
{
|
{
|
||||||
ASSERT(IsNetPlayRunning());
|
ASSERT(IsNetPlayRunning());
|
||||||
|
@ -2767,6 +2860,103 @@ void NetPlay_Disable()
|
||||||
std::lock_guard lk(crit_netplay_client);
|
std::lock_guard lk(crit_netplay_client);
|
||||||
netplay_client = nullptr;
|
netplay_client = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void NetPlayExtEvent(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
|
{
|
||||||
|
ExternalEventID eeid = static_cast<ExternalEventID>(userdata);
|
||||||
|
switch (eeid)
|
||||||
|
{
|
||||||
|
case ExternalEventID::ResetButton:
|
||||||
|
system.GetProcessorInterface().ResetButton_Tap();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
WARN_LOG_FMT(NETPLAY, "NetPlayExtEvent: Invalid event type. ({})", userdata);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetPlayClient::SendTimepointForNetPlayEvent(u64 timepoint, u64 uid)
|
||||||
|
{
|
||||||
|
sf::Packet packet;
|
||||||
|
packet << MessageID::SyncTimepointForExternalEvent;
|
||||||
|
packet << sf::Uint64(uid);
|
||||||
|
packet << sf::Uint64(timepoint);
|
||||||
|
SendAsync(std::move(packet));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NetPlaySyncEvent(Core::System& system, u64 userdata, s64 cyclesLate)
|
||||||
|
{
|
||||||
|
// An external event should be executed, but we're not sure of a valid timepoint for it yet. To
|
||||||
|
// figure this out, we now send our current timepoint to all other players (as they do the same),
|
||||||
|
// then once we know all timepoints we execute the event after the last timepoint of any player;
|
||||||
|
// this ensures that every player can run it at the same timepoint and thus stay deterministic
|
||||||
|
// with eachother.
|
||||||
|
|
||||||
|
auto& core_timing = system.GetCoreTiming();
|
||||||
|
const u64 timepoint = core_timing.GetTicks();
|
||||||
|
ExternalEventSyncData* ptr_event_data;
|
||||||
|
{
|
||||||
|
std::lock_guard lk(s_event_sync_mutex);
|
||||||
|
ptr_event_data = &s_event_sync_map[userdata];
|
||||||
|
}
|
||||||
|
auto& event_data = *ptr_event_data;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lk(event_data.mutex);
|
||||||
|
event_data.map[netplay_client->GetLocalPlayerId()] = timepoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
netplay_client->SendTimepointForNetPlayEvent(timepoint, userdata);
|
||||||
|
const size_t num_players = netplay_client->GetPlayers().size();
|
||||||
|
|
||||||
|
bool do_schedule = false;
|
||||||
|
u64 latest_timepoint = 0;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(event_data.mutex);
|
||||||
|
if (event_data.map.size() != num_players)
|
||||||
|
{
|
||||||
|
DEBUG_LOG_FMT(NETPLAY, "NetPlaySyncEvent({}): Waiting for timepoints.", userdata);
|
||||||
|
event_data.cv.wait_for(lock, std::chrono::seconds(5), [&event_data, num_players] {
|
||||||
|
return event_data.map.size() == num_players;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (event_data.map.size() != num_players)
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(NETPLAY, "NetPlaySyncEvent({}): Timed out waiting for timepoints.", userdata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const auto& it : event_data.map)
|
||||||
|
{
|
||||||
|
DEBUG_LOG_FMT(NETPLAY, "NetPlaySyncEvent({}): Timepoint {} from player {}.", userdata,
|
||||||
|
it.second, u8(it.first));
|
||||||
|
latest_timepoint = std::max(it.second, latest_timepoint);
|
||||||
|
}
|
||||||
|
do_schedule = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExternalEventID eeid = event_data.event_id;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lk(s_event_sync_mutex);
|
||||||
|
s_event_sync_map.erase(userdata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (do_schedule)
|
||||||
|
{
|
||||||
|
core_timing.ScheduleExternalEvent(latest_timepoint + 1, event_type_ext_event,
|
||||||
|
static_cast<u64>(eeid), userdata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetPlay_RegisterEvents()
|
||||||
|
{
|
||||||
|
auto& core_timing = Core::System::GetInstance().GetCoreTiming();
|
||||||
|
event_type_ext_event = core_timing.RegisterEvent("NetPlayExtEvent", NetPlayExtEvent);
|
||||||
|
event_type_sync_time_for_ext_event =
|
||||||
|
core_timing.RegisterEvent("NetPlaySyncEvent", NetPlaySyncEvent);
|
||||||
|
}
|
||||||
} // namespace NetPlay
|
} // namespace NetPlay
|
||||||
|
|
||||||
// stuff hacked into dolphin
|
// stuff hacked into dolphin
|
||||||
|
|
|
@ -130,6 +130,8 @@ public:
|
||||||
bool ChangeGame(const std::string& game);
|
bool ChangeGame(const std::string& game);
|
||||||
void SendChatMessage(const std::string& msg);
|
void SendChatMessage(const std::string& msg);
|
||||||
void RequestStopGame();
|
void RequestStopGame();
|
||||||
|
void SendTimepointForNetPlayEvent(u64 timepoint, u64 uid);
|
||||||
|
void ScheduleExternalEvent(ExternalEventID id);
|
||||||
void SendPowerButtonEvent();
|
void SendPowerButtonEvent();
|
||||||
void SendActiveGeckoCodes();
|
void SendActiveGeckoCodes();
|
||||||
void GetActiveGeckoCodes();
|
void GetActiveGeckoCodes();
|
||||||
|
@ -300,6 +302,8 @@ private:
|
||||||
void OnStartGame(sf::Packet& packet);
|
void OnStartGame(sf::Packet& packet);
|
||||||
void OnStopGame(sf::Packet& packet);
|
void OnStopGame(sf::Packet& packet);
|
||||||
void OnPowerButton();
|
void OnPowerButton();
|
||||||
|
void OnScheduleExternalEvent(sf::Packet& packet);
|
||||||
|
void OnSyncTimepointForExternalEvent(sf::Packet& packet);
|
||||||
void OnPing(sf::Packet& packet);
|
void OnPing(sf::Packet& packet);
|
||||||
void OnPlayerPingData(sf::Packet& packet);
|
void OnPlayerPingData(sf::Packet& packet);
|
||||||
void OnDesyncDetected(sf::Packet& packet);
|
void OnDesyncDetected(sf::Packet& packet);
|
||||||
|
@ -359,4 +363,6 @@ void NetPlay_Enable(NetPlayClient* const np);
|
||||||
void NetPlay_Disable();
|
void NetPlay_Disable();
|
||||||
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
|
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
|
||||||
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
|
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
|
||||||
|
|
||||||
|
void NetPlay_RegisterEvents();
|
||||||
} // namespace NetPlay
|
} // namespace NetPlay
|
||||||
|
|
|
@ -169,6 +169,9 @@ enum class MessageID : u8
|
||||||
HostInputAuthority = 0xA6,
|
HostInputAuthority = 0xA6,
|
||||||
PowerButton = 0xA7,
|
PowerButton = 0xA7,
|
||||||
|
|
||||||
|
ScheduleExternalEvent = 0xA8,
|
||||||
|
SyncTimepointForExternalEvent = 0xA9,
|
||||||
|
|
||||||
TimeBase = 0xB0,
|
TimeBase = 0xB0,
|
||||||
DesyncDetected = 0xB1,
|
DesyncDetected = 0xB1,
|
||||||
|
|
||||||
|
@ -220,6 +223,12 @@ enum class SyncCodeID : u8
|
||||||
Failure = 6,
|
Failure = 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ExternalEventID : u8
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
ResetButton = 1,
|
||||||
|
};
|
||||||
|
|
||||||
constexpr u32 MAX_NAME_LENGTH = 30;
|
constexpr u32 MAX_NAME_LENGTH = 30;
|
||||||
constexpr size_t CHUNKED_DATA_UNIT_SIZE = 16384;
|
constexpr size_t CHUNKED_DATA_UNIT_SIZE = 16384;
|
||||||
constexpr u32 MAX_ENET_MTU = 1392; // see https://github.com/lsalzman/enet/issues/132
|
constexpr u32 MAX_ENET_MTU = 1392; // see https://github.com/lsalzman/enet/issues/132
|
||||||
|
@ -257,6 +266,7 @@ std::string GetPlayerMappingString(PlayerId pid, const PadMappingArray& pad_map,
|
||||||
const PadMappingArray& wiimote_map);
|
const PadMappingArray& wiimote_map);
|
||||||
bool IsNetPlayRunning();
|
bool IsNetPlayRunning();
|
||||||
void SetSIPollBatching(bool state);
|
void SetSIPollBatching(bool state);
|
||||||
|
void ScheduleResetButtonTap();
|
||||||
void SendPowerButtonEvent();
|
void SendPowerButtonEvent();
|
||||||
std::string GetGBASavePath(int pad_num);
|
std::string GetGBASavePath(int pad_num);
|
||||||
PadDetails GetPadDetails(int pad_num);
|
PadDetails GetPadDetails(int pad_num);
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
#include "Core/NetPlayClient.h" //for NetPlayUI
|
#include "Core/NetPlayClient.h" //for NetPlayUI
|
||||||
#include "Core/NetPlayCommon.h"
|
#include "Core/NetPlayCommon.h"
|
||||||
#include "Core/SyncIdentifier.h"
|
#include "Core/SyncIdentifier.h"
|
||||||
|
#include "Core/System.h"
|
||||||
|
|
||||||
#include "DiscIO/Enums.h"
|
#include "DiscIO/Enums.h"
|
||||||
#include "DiscIO/RiivolutionPatcher.h"
|
#include "DiscIO/RiivolutionPatcher.h"
|
||||||
|
@ -1243,6 +1244,47 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case MessageID::ScheduleExternalEvent:
|
||||||
|
{
|
||||||
|
ExternalEventID eeid;
|
||||||
|
packet >> eeid;
|
||||||
|
|
||||||
|
sf::Packet spac;
|
||||||
|
spac << MessageID::ScheduleExternalEvent;
|
||||||
|
spac << eeid;
|
||||||
|
const u64 uid = m_external_event_uid_counter++;
|
||||||
|
spac << sf::Uint64(uid);
|
||||||
|
auto& core_timing = Core::System::GetInstance().GetCoreTiming();
|
||||||
|
|
||||||
|
// We schedule the event in the future so that it's likely it will reach all users in time.
|
||||||
|
// There's a syncing logic in place to ensure the event executes at the same timepoint
|
||||||
|
// everywhere even if this packet is a bit too late, but the sync can get tripped up (and
|
||||||
|
// subsequently time out) if there's too large of a time distance between multiple players
|
||||||
|
// executing the sync function, because one of them may be waiting for controller input while
|
||||||
|
// another is waiting for the event timepoint sync.
|
||||||
|
const u64 target_timepoint =
|
||||||
|
static_cast<u64>(core_timing.GetGlobals().global_timer) + SystemTimers::GetTicksPerSecond();
|
||||||
|
spac << sf::Uint64(target_timepoint);
|
||||||
|
SendToClients(spac);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MessageID::SyncTimepointForExternalEvent:
|
||||||
|
{
|
||||||
|
sf::Uint64 uid;
|
||||||
|
packet >> uid;
|
||||||
|
sf::Uint64 timepoint;
|
||||||
|
packet >> timepoint;
|
||||||
|
|
||||||
|
sf::Packet spac;
|
||||||
|
spac << MessageID::SyncTimepointForExternalEvent;
|
||||||
|
spac << player.pid;
|
||||||
|
spac << uid;
|
||||||
|
spac << timepoint;
|
||||||
|
SendToClients(spac, player.pid);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
PanicAlertFmtT("Unknown message with id:{0} received from player:{1} Kicking player!",
|
PanicAlertFmtT("Unknown message with id:{0} received from player:{1} Kicking player!",
|
||||||
static_cast<u8>(mid), player.pid);
|
static_cast<u8>(mid), player.pid);
|
||||||
|
|
|
@ -214,5 +214,7 @@ private:
|
||||||
Common::TraversalClient* m_traversal_client = nullptr;
|
Common::TraversalClient* m_traversal_client = nullptr;
|
||||||
NetPlayUI* m_dialog = nullptr;
|
NetPlayUI* m_dialog = nullptr;
|
||||||
NetPlayIndex m_index;
|
NetPlayIndex m_index;
|
||||||
|
|
||||||
|
u64 m_external_event_uid_counter = 0;
|
||||||
};
|
};
|
||||||
} // namespace NetPlay
|
} // namespace NetPlay
|
||||||
|
|
|
@ -1122,7 +1122,7 @@ void MainWindow::Reset()
|
||||||
auto& movie = system.GetMovie();
|
auto& movie = system.GetMovie();
|
||||||
if (movie.IsRecordingInput())
|
if (movie.IsRecordingInput())
|
||||||
movie.SetReset(true);
|
movie.SetReset(true);
|
||||||
system.GetProcessorInterface().ResetButton_Tap();
|
system.GetProcessorInterface().ResetButton_Tap_FromUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::FrameAdvance()
|
void MainWindow::FrameAdvance()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue