Merge pull request #7360 from Techjar/netplay-host-input-authority
NetPlay host input authority mode
This commit is contained in:
commit
9bcd7bfe1e
|
@ -372,6 +372,8 @@ bool BootCore(std::unique_ptr<BootParameters> boot)
|
|||
StartUp.bMMU = netplay_settings.m_MMU;
|
||||
StartUp.bFastmem = netplay_settings.m_Fastmem;
|
||||
StartUp.bHLE_BS2 = netplay_settings.m_SkipIPL;
|
||||
if (netplay_settings.m_HostInputAuthority && !netplay_settings.m_IsHosting)
|
||||
config_cache.bSetEmulationSpeed = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -595,6 +595,10 @@ void ChangeDeviceDeterministic(SIDevices device, int channel)
|
|||
|
||||
void UpdateDevices()
|
||||
{
|
||||
// Hinting NetPlay that all controllers will be polled in
|
||||
// succession, in order to optimize networking
|
||||
NetPlay::SetSIPollBatching(true);
|
||||
|
||||
// Update inputs at the rate of SI
|
||||
// Typically 120hz but is variable
|
||||
g_controller_interface.UpdateInput();
|
||||
|
@ -610,6 +614,9 @@ void UpdateDevices()
|
|||
!!s_channel[3].device->GetData(s_channel[3].in_hi.hex, s_channel[3].in_lo.hex);
|
||||
|
||||
UpdateInterrupts();
|
||||
|
||||
// Polling finished
|
||||
NetPlay::SetSIPollBatching(false);
|
||||
}
|
||||
|
||||
SIDevices GetDeviceType(int channel)
|
||||
|
|
|
@ -60,6 +60,7 @@ namespace NetPlay
|
|||
static std::mutex crit_netplay_client;
|
||||
static NetPlayClient* netplay_client = nullptr;
|
||||
static std::unique_ptr<IOS::HLE::FS::FileSystem> s_wii_sync_fs;
|
||||
static bool s_si_poll_batching;
|
||||
|
||||
// called from ---GUI--- thread
|
||||
NetPlayClient::~NetPlayClient()
|
||||
|
@ -407,6 +408,22 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
}
|
||||
break;
|
||||
|
||||
case NP_MSG_PAD_FIRST_RECEIVED:
|
||||
{
|
||||
PadMapping map;
|
||||
packet >> map;
|
||||
packet >> m_first_pad_status_received[map];
|
||||
m_first_pad_status_received_event.Set();
|
||||
}
|
||||
break;
|
||||
|
||||
case NP_MSG_HOST_INPUT_AUTHORITY:
|
||||
{
|
||||
packet >> m_host_input_authority;
|
||||
m_dialog->OnHostInputAuthorityChanged(m_host_input_authority);
|
||||
}
|
||||
break;
|
||||
|
||||
case NP_MSG_CHANGE_GAME:
|
||||
{
|
||||
{
|
||||
|
@ -532,7 +549,9 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
packet >> m_net_settings.m_SyncSaveData;
|
||||
packet >> m_net_settings.m_SaveDataRegion;
|
||||
m_net_settings.m_IsHosting = m_dialog->IsHosting();
|
||||
|
||||
m_net_settings.m_IsHosting = m_local_player->IsHost();
|
||||
m_net_settings.m_HostInputAuthority = m_host_input_authority;
|
||||
}
|
||||
|
||||
m_dialog->OnMsgStartGame();
|
||||
|
@ -636,7 +655,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
case SYNC_SAVE_DATA_RAW:
|
||||
{
|
||||
if (m_dialog->IsHosting())
|
||||
if (m_local_player->IsHost())
|
||||
return 0;
|
||||
|
||||
bool is_slot_a;
|
||||
|
@ -660,7 +679,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
case SYNC_SAVE_DATA_GCI:
|
||||
{
|
||||
if (m_dialog->IsHosting())
|
||||
if (m_local_player->IsHost())
|
||||
return 0;
|
||||
|
||||
bool is_slot_a;
|
||||
|
@ -696,7 +715,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet)
|
|||
|
||||
case SYNC_SAVE_DATA_WII:
|
||||
{
|
||||
if (m_dialog->IsHosting())
|
||||
if (m_local_player->IsHost())
|
||||
return 0;
|
||||
|
||||
const auto game = m_dialog->FindGameFile(m_selected_game);
|
||||
|
@ -1118,6 +1137,8 @@ bool NetPlayClient::StartGame(const std::string& path)
|
|||
|
||||
ClearBuffers();
|
||||
|
||||
m_first_pad_status_received.fill(false);
|
||||
|
||||
if (m_dialog->IsRecording())
|
||||
{
|
||||
if (Movie::IsReadOnly())
|
||||
|
@ -1366,7 +1387,7 @@ void NetPlayClient::OnConnectFailed(u8 reason)
|
|||
}
|
||||
|
||||
// called from ---CPU--- thread
|
||||
bool NetPlayClient::GetNetPads(const int pad_nb, GCPadStatus* pad_status)
|
||||
bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatus* pad_status)
|
||||
{
|
||||
// The interface for this is extremely silly.
|
||||
//
|
||||
|
@ -1385,11 +1406,17 @@ bool NetPlayClient::GetNetPads(const int pad_nb, GCPadStatus* pad_status)
|
|||
// The slot number is the "local" pad number, and what player
|
||||
// it actually means is the "in-game" pad number.
|
||||
|
||||
// When the 1st in-game pad is polled, we assume the others will
|
||||
// will be polled as well. To reduce latency, we poll all local
|
||||
// controllers at once and then send the status to the other
|
||||
// When the 1st in-game pad is polled and batching is set, the
|
||||
// others will be polled as well. To reduce latency, we poll all
|
||||
// local controllers at once and then send the status to the other
|
||||
// clients.
|
||||
if (IsFirstInGamePad(pad_nb))
|
||||
//
|
||||
// Batching is enabled when polled from VI. If batching is not
|
||||
// enabled, the poll is probably from MMIO, which can poll any
|
||||
// specific pad arbitrarily. In this case, we poll just that pad
|
||||
// and send it.
|
||||
|
||||
if (IsFirstInGamePad(pad_nb) && batching)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << static_cast<MessageId>(NP_MSG_PAD_DATA);
|
||||
|
@ -1398,34 +1425,44 @@ bool NetPlayClient::GetNetPads(const int pad_nb, GCPadStatus* pad_status)
|
|||
const int num_local_pads = NumLocalPads();
|
||||
for (int local_pad = 0; local_pad < num_local_pads; local_pad++)
|
||||
{
|
||||
switch (SConfig::GetInstance().m_SIDevice[local_pad])
|
||||
{
|
||||
case SerialInterface::SIDEVICE_WIIU_ADAPTER:
|
||||
*pad_status = GCAdapter::Input(local_pad);
|
||||
break;
|
||||
case SerialInterface::SIDEVICE_GC_CONTROLLER:
|
||||
default:
|
||||
*pad_status = Pad::GetStatus(local_pad);
|
||||
break;
|
||||
}
|
||||
|
||||
int ingame_pad = LocalPadToInGamePad(local_pad);
|
||||
|
||||
// adjust the buffer either up or down
|
||||
// inserting multiple padstates or dropping states
|
||||
while (m_pad_buffer[ingame_pad].Size() <= m_target_buffer_size)
|
||||
{
|
||||
// add to buffer
|
||||
m_pad_buffer[ingame_pad].Push(*pad_status);
|
||||
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, *pad_status, packet);
|
||||
send_packet = true;
|
||||
}
|
||||
send_packet = PollLocalPad(local_pad, packet) || send_packet;
|
||||
}
|
||||
|
||||
if (send_packet)
|
||||
SendAsync(std::move(packet));
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(-1);
|
||||
}
|
||||
|
||||
if (!batching)
|
||||
{
|
||||
int local_pad = InGamePadToLocalPad(pad_nb);
|
||||
if (local_pad < 4)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << static_cast<MessageId>(NP_MSG_PAD_DATA);
|
||||
if (PollLocalPad(local_pad, packet))
|
||||
SendAsync(std::move(packet));
|
||||
}
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(pad_nb);
|
||||
}
|
||||
|
||||
if (m_host_input_authority && !m_local_player->IsHost())
|
||||
{
|
||||
const bool buffer_over_target = m_pad_buffer[pad_nb].Size() > m_target_buffer_size + 1;
|
||||
if (!buffer_over_target)
|
||||
m_buffer_under_target_last = std::chrono::steady_clock::now();
|
||||
|
||||
std::chrono::duration<double> time_diff =
|
||||
std::chrono::steady_clock::now() - m_buffer_under_target_last;
|
||||
if (time_diff.count() >= 1.0 || !buffer_over_target)
|
||||
{
|
||||
// run fast if the buffer is overfilled, otherwise run normal speed
|
||||
SConfig::GetInstance().m_EmulationSpeed = buffer_over_target ? 0.0f : 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we either use the data pushed earlier, or wait for the
|
||||
|
@ -1534,6 +1571,86 @@ bool NetPlayClient::WiimoteUpdate(int _number, u8* data, const u8 size, u8 repor
|
|||
return true;
|
||||
}
|
||||
|
||||
bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
|
||||
{
|
||||
GCPadStatus pad_status;
|
||||
|
||||
switch (SConfig::GetInstance().m_SIDevice[local_pad])
|
||||
{
|
||||
case SerialInterface::SIDEVICE_WIIU_ADAPTER:
|
||||
pad_status = GCAdapter::Input(local_pad);
|
||||
break;
|
||||
case SerialInterface::SIDEVICE_GC_CONTROLLER:
|
||||
default:
|
||||
pad_status = Pad::GetStatus(local_pad);
|
||||
break;
|
||||
}
|
||||
|
||||
const int ingame_pad = LocalPadToInGamePad(local_pad);
|
||||
bool data_added = false;
|
||||
|
||||
if (m_host_input_authority)
|
||||
{
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
||||
data_added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// adjust the buffer either up or down
|
||||
// inserting multiple padstates or dropping states
|
||||
while (m_pad_buffer[ingame_pad].Size() <= m_target_buffer_size)
|
||||
{
|
||||
// add to buffer
|
||||
m_pad_buffer[ingame_pad].Push(pad_status);
|
||||
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
||||
data_added = true;
|
||||
}
|
||||
}
|
||||
|
||||
return data_added;
|
||||
}
|
||||
|
||||
void NetPlayClient::SendPadHostPoll(const PadMapping pad_num)
|
||||
{
|
||||
if (!m_local_player->IsHost())
|
||||
return;
|
||||
|
||||
if (pad_num < 0)
|
||||
{
|
||||
for (size_t i = 0; i < m_pad_map.size(); i++)
|
||||
{
|
||||
if (m_pad_map[i] <= 0)
|
||||
continue;
|
||||
|
||||
while (!m_first_pad_status_received[i])
|
||||
{
|
||||
if (!m_is_running.IsSet())
|
||||
return;
|
||||
|
||||
m_first_pad_status_received_event.Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_pad_map[pad_num] > 0)
|
||||
{
|
||||
while (!m_first_pad_status_received[pad_num])
|
||||
{
|
||||
if (!m_is_running.IsSet())
|
||||
return;
|
||||
|
||||
m_first_pad_status_received_event.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
sf::Packet packet;
|
||||
packet << static_cast<MessageId>(NP_MSG_PAD_HOST_POLL);
|
||||
packet << pad_num;
|
||||
SendAsync(std::move(packet));
|
||||
}
|
||||
|
||||
// called from ---GUI--- thread and ---NETPLAY--- thread (client side)
|
||||
bool NetPlayClient::StopGame()
|
||||
{
|
||||
|
@ -1542,6 +1659,7 @@ bool NetPlayClient::StopGame()
|
|||
// stop waiting for input
|
||||
m_gc_pad_event.Set();
|
||||
m_wii_pad_event.Set();
|
||||
m_first_pad_status_received_event.Set();
|
||||
|
||||
NetPlay_Disable();
|
||||
|
||||
|
@ -1564,6 +1682,7 @@ void NetPlayClient::Stop()
|
|||
// stop waiting for input
|
||||
m_gc_pad_event.Set();
|
||||
m_wii_pad_event.Set();
|
||||
m_first_pad_status_received_event.Set();
|
||||
|
||||
// Tell the server to stop if we have a pad mapped in game.
|
||||
if (LocalPlayerHasControllerMapped())
|
||||
|
@ -1719,6 +1838,12 @@ const PadMappingArray& NetPlayClient::GetWiimoteMapping() const
|
|||
return m_wiimote_map;
|
||||
}
|
||||
|
||||
void NetPlayClient::AdjustPadBufferSize(const unsigned int size)
|
||||
{
|
||||
m_target_buffer_size = size;
|
||||
m_dialog->OnPadBufferChanged(size);
|
||||
}
|
||||
|
||||
bool IsNetPlayRunning()
|
||||
{
|
||||
return netplay_client != nullptr;
|
||||
|
@ -1750,6 +1875,11 @@ void ClearWiiSyncFS()
|
|||
s_wii_sync_fs.reset();
|
||||
}
|
||||
|
||||
void SetSIPollBatching(bool state)
|
||||
{
|
||||
s_si_poll_batching = state;
|
||||
}
|
||||
|
||||
void NetPlay_Enable(NetPlayClient* const np)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(crit_netplay_client);
|
||||
|
@ -1767,12 +1897,12 @@ void NetPlay_Disable()
|
|||
|
||||
// called from ---CPU--- thread
|
||||
// Actual Core function which is called on every frame
|
||||
bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int numPAD, GCPadStatus* PadStatus)
|
||||
bool SerialInterface::CSIDevice_GCController::NetPlay_GetInput(int pad_num, GCPadStatus* status)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(NetPlay::crit_netplay_client);
|
||||
|
||||
if (NetPlay::netplay_client)
|
||||
return NetPlay::netplay_client->GetNetPads(numPAD, PadStatus);
|
||||
return NetPlay::netplay_client->GetNetPads(pad_num, NetPlay::s_si_poll_batching, status);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <SFML/Network/Packet.hpp>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
@ -43,6 +44,7 @@ public:
|
|||
virtual void OnMsgStartGame() = 0;
|
||||
virtual void OnMsgStopGame() = 0;
|
||||
virtual void OnPadBufferChanged(u32 buffer) = 0;
|
||||
virtual void OnHostInputAuthorityChanged(bool enabled) = 0;
|
||||
virtual void OnDesync(u32 frame, const std::string& player) = 0;
|
||||
virtual void OnConnectionLost() = 0;
|
||||
virtual void OnConnectionError(const std::string& message) = 0;
|
||||
|
@ -74,6 +76,8 @@ public:
|
|||
std::string revision;
|
||||
u32 ping;
|
||||
PlayerGameStatus game_status;
|
||||
|
||||
bool IsHost() const { return pid == 1; }
|
||||
};
|
||||
|
||||
class NetPlayClient : public TraversalClientClient
|
||||
|
@ -101,7 +105,7 @@ public:
|
|||
|
||||
// Send and receive pads values
|
||||
bool WiimoteUpdate(int _number, u8* data, const u8 size, u8 reporting_mode);
|
||||
bool GetNetPads(int pad_nb, GCPadStatus* pad_status);
|
||||
bool GetNetPads(int pad_nb, bool from_vi, GCPadStatus* pad_status);
|
||||
|
||||
u64 GetInitialRTCValue() const;
|
||||
|
||||
|
@ -121,6 +125,8 @@ public:
|
|||
const PadMappingArray& GetPadMapping() const;
|
||||
const PadMappingArray& GetWiimoteMapping() const;
|
||||
|
||||
void AdjustPadBufferSize(unsigned int size);
|
||||
|
||||
protected:
|
||||
void ClearBuffers();
|
||||
|
||||
|
@ -137,6 +143,10 @@ protected:
|
|||
std::array<Common::SPSCQueue<GCPadStatus>, 4> m_pad_buffer;
|
||||
std::array<Common::SPSCQueue<NetWiimote>, 4> m_wiimote_buffer;
|
||||
|
||||
std::array<bool, 4> m_first_pad_status_received{};
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_buffer_under_target_last;
|
||||
|
||||
NetPlayUI* m_dialog = nullptr;
|
||||
|
||||
ENetHost* m_client = nullptr;
|
||||
|
@ -148,6 +158,7 @@ protected:
|
|||
Common::Flag m_do_loop{true};
|
||||
|
||||
unsigned int m_target_buffer_size = 20;
|
||||
bool m_host_input_authority = false;
|
||||
|
||||
Player* m_local_player = nullptr;
|
||||
|
||||
|
@ -178,6 +189,9 @@ private:
|
|||
bool DecompressPacketIntoFile(sf::Packet& packet, const std::string& file_path);
|
||||
std::optional<std::vector<u8>> DecompressPacketIntoBuffer(sf::Packet& packet);
|
||||
|
||||
bool PollLocalPad(int local_pad, sf::Packet& packet);
|
||||
void SendPadHostPoll(PadMapping pad_num);
|
||||
|
||||
void UpdateDevices();
|
||||
void AddPadStateToPacket(int in_game_pad, const GCPadStatus& np, sf::Packet& packet);
|
||||
void SendWiimoteState(int in_game_pad, const NetWiimote& nw);
|
||||
|
@ -203,6 +217,7 @@ private:
|
|||
bool m_should_compute_MD5 = false;
|
||||
Common::Event m_gc_pad_event;
|
||||
Common::Event m_wii_pad_event;
|
||||
Common::Event m_first_pad_status_received_event;
|
||||
u8 m_sync_save_data_count = 0;
|
||||
u8 m_sync_save_data_success_count = 0;
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ struct NetSettings
|
|||
bool m_SyncSaveData;
|
||||
std::string m_SaveDataRegion;
|
||||
bool m_IsHosting;
|
||||
bool m_HostInputAuthority;
|
||||
};
|
||||
|
||||
struct NetTraversalConfig
|
||||
|
@ -110,6 +111,8 @@ enum
|
|||
NP_MSG_PAD_DATA = 0x60,
|
||||
NP_MSG_PAD_MAPPING = 0x61,
|
||||
NP_MSG_PAD_BUFFER = 0x62,
|
||||
NP_MSG_PAD_HOST_POLL = 0x63,
|
||||
NP_MSG_PAD_FIRST_RECEIVED = 0x64,
|
||||
|
||||
NP_MSG_WIIMOTE_DATA = 0x70,
|
||||
NP_MSG_WIIMOTE_MAPPING = 0x71,
|
||||
|
@ -120,6 +123,7 @@ enum
|
|||
NP_MSG_DISABLE_GAME = 0xA3,
|
||||
NP_MSG_GAME_STATUS = 0xA4,
|
||||
NP_MSG_IPL_STATUS = 0xA5,
|
||||
NP_MSG_HOST_INPUT_AUTHORITY = 0xA6,
|
||||
|
||||
NP_MSG_TIMEBASE = 0xB0,
|
||||
NP_MSG_DESYNC_DETECTED = 0xB1,
|
||||
|
@ -175,4 +179,5 @@ const NetSettings& GetNetSettings();
|
|||
IOS::HLE::FS::FileSystem* GetWiiSyncFS();
|
||||
void SetWiiSyncFS(std::unique_ptr<IOS::HLE::FS::FileSystem> fs);
|
||||
void ClearWiiSyncFS();
|
||||
void SetSIPollBatching(bool state);
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -307,13 +307,13 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket)
|
|||
|
||||
// send join message to already connected clients
|
||||
sf::Packet spac;
|
||||
spac << (MessageId)NP_MSG_PLAYER_JOIN;
|
||||
spac << static_cast<MessageId>(NP_MSG_PLAYER_JOIN);
|
||||
spac << player.pid << player.name << player.revision;
|
||||
SendToClients(spac);
|
||||
|
||||
// send new client success message with their id
|
||||
spac.clear();
|
||||
spac << (MessageId)0;
|
||||
spac << static_cast<MessageId>(0);
|
||||
spac << player.pid;
|
||||
Send(player.socket, spac);
|
||||
|
||||
|
@ -321,15 +321,24 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket)
|
|||
if (m_selected_game != "")
|
||||
{
|
||||
spac.clear();
|
||||
spac << (MessageId)NP_MSG_CHANGE_GAME;
|
||||
spac << static_cast<MessageId>(NP_MSG_CHANGE_GAME);
|
||||
spac << m_selected_game;
|
||||
Send(player.socket, spac);
|
||||
}
|
||||
|
||||
// send the pad buffer value
|
||||
if (!m_host_input_authority)
|
||||
{
|
||||
// send the pad buffer value
|
||||
spac.clear();
|
||||
spac << static_cast<MessageId>(NP_MSG_PAD_BUFFER);
|
||||
spac << static_cast<u32>(m_target_buffer_size);
|
||||
Send(player.socket, spac);
|
||||
}
|
||||
|
||||
// send input authority state
|
||||
spac.clear();
|
||||
spac << (MessageId)NP_MSG_PAD_BUFFER;
|
||||
spac << (u32)m_target_buffer_size;
|
||||
spac << static_cast<MessageId>(NP_MSG_HOST_INPUT_AUTHORITY);
|
||||
spac << m_host_input_authority;
|
||||
Send(player.socket, spac);
|
||||
|
||||
// sync GC SRAM with new client
|
||||
|
@ -340,7 +349,7 @@ unsigned int NetPlayServer::OnConnect(ENetPeer* socket)
|
|||
g_SRAM_netplay_initialized = true;
|
||||
}
|
||||
spac.clear();
|
||||
spac << (MessageId)NP_MSG_SYNC_GC_SRAM;
|
||||
spac << static_cast<MessageId>(NP_MSG_SYNC_GC_SRAM);
|
||||
for (size_t i = 0; i < sizeof(g_SRAM.p_SRAM); ++i)
|
||||
{
|
||||
spac << g_SRAM.p_SRAM[i];
|
||||
|
@ -497,6 +506,24 @@ void NetPlayServer::AdjustPadBufferSize(unsigned int size)
|
|||
SendAsyncToClients(std::move(spac));
|
||||
}
|
||||
|
||||
void NetPlayServer::SetHostInputAuthority(const bool enable)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lkg(m_crit.game);
|
||||
|
||||
m_host_input_authority = enable;
|
||||
|
||||
// tell clients about the new value
|
||||
sf::Packet spac;
|
||||
spac << static_cast<MessageId>(NP_MSG_HOST_INPUT_AUTHORITY);
|
||||
spac << m_host_input_authority;
|
||||
|
||||
SendAsyncToClients(std::move(spac));
|
||||
|
||||
// resend pad buffer to clients when disabled
|
||||
if (!m_host_input_authority)
|
||||
AdjustPadBufferSize(m_target_buffer_size);
|
||||
}
|
||||
|
||||
void NetPlayServer::SendAsyncToClients(sf::Packet&& packet)
|
||||
{
|
||||
{
|
||||
|
@ -559,13 +586,59 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player)
|
|||
packet >> pad.button >> pad.analogA >> pad.analogB >> pad.stickX >> pad.stickY >>
|
||||
pad.substickX >> pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
|
||||
|
||||
// Add to packet for relay to clients
|
||||
spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY
|
||||
if (m_host_input_authority)
|
||||
{
|
||||
m_last_pad_status[map] = pad;
|
||||
|
||||
if (!m_first_pad_status_received[map])
|
||||
{
|
||||
m_first_pad_status_received[map] = true;
|
||||
SendFirstReceivedToHost(map, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
spac << map << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY
|
||||
<< pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight
|
||||
<< pad.isConnected;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_host_input_authority)
|
||||
SendToClients(spac, player.pid);
|
||||
}
|
||||
break;
|
||||
|
||||
case NP_MSG_PAD_HOST_POLL:
|
||||
{
|
||||
PadMapping pad_num;
|
||||
packet >> pad_num;
|
||||
|
||||
sf::Packet spac;
|
||||
spac << static_cast<MessageId>(NP_MSG_PAD_DATA);
|
||||
|
||||
if (pad_num < 0)
|
||||
{
|
||||
for (size_t i = 0; i < m_pad_map.size(); i++)
|
||||
{
|
||||
if (m_pad_map[i] == -1)
|
||||
continue;
|
||||
|
||||
const GCPadStatus& pad = m_last_pad_status[i];
|
||||
spac << static_cast<PadMapping>(i) << pad.button << pad.analogA << pad.analogB << pad.stickX
|
||||
<< pad.stickY << pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight
|
||||
<< pad.isConnected;
|
||||
}
|
||||
}
|
||||
else if (m_pad_map.at(pad_num) != -1)
|
||||
{
|
||||
const GCPadStatus& pad = m_last_pad_status[pad_num];
|
||||
spac << pad_num << pad.button << pad.analogA << pad.analogB << pad.stickX << pad.stickY
|
||||
<< pad.substickX << pad.substickY << pad.triggerLeft << pad.triggerRight
|
||||
<< pad.isConnected;
|
||||
}
|
||||
|
||||
SendToClients(spac, player.pid);
|
||||
SendToClients(spac);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -911,7 +984,10 @@ bool NetPlayServer::StartGame()
|
|||
m_current_game = Common::Timer::GetTimeMs();
|
||||
|
||||
// no change, just update with clients
|
||||
AdjustPadBufferSize(m_target_buffer_size);
|
||||
if (!m_host_input_authority)
|
||||
AdjustPadBufferSize(m_target_buffer_size);
|
||||
|
||||
m_first_pad_status_received.fill(false);
|
||||
|
||||
const u64 initial_rtc = GetInitialNetPlayRTC();
|
||||
|
||||
|
@ -1291,6 +1367,15 @@ bool NetPlayServer::CompressBufferIntoPacket(const std::vector<u8>& in_buffer, s
|
|||
return true;
|
||||
}
|
||||
|
||||
void NetPlayServer::SendFirstReceivedToHost(const PadMapping map, const bool state)
|
||||
{
|
||||
sf::Packet pac;
|
||||
pac << static_cast<MessageId>(NP_MSG_PAD_FIRST_RECEIVED);
|
||||
pac << map;
|
||||
pac << state;
|
||||
Send(m_players.at(1).socket, pac);
|
||||
}
|
||||
|
||||
u64 NetPlayServer::GetInitialNetPlayRTC() const
|
||||
{
|
||||
const auto& config = SConfig::GetInstance();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "Common/Timer.h"
|
||||
#include "Common/TraversalClient.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
#include "InputCommon/GCPadStatus.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
|
@ -51,6 +52,7 @@ public:
|
|||
void SetWiimoteMapping(const PadMappingArray& mappings);
|
||||
|
||||
void AdjustPadBufferSize(unsigned int size);
|
||||
void SetHostInputAuthority(bool enable);
|
||||
|
||||
void KickPlayer(PlayerId player);
|
||||
|
||||
|
@ -79,11 +81,13 @@ private:
|
|||
Common::QoSSession qos_session;
|
||||
|
||||
bool operator==(const Client& other) const { return this == &other; }
|
||||
bool IsHost() const { return pid == 1; }
|
||||
};
|
||||
|
||||
bool SyncSaveData();
|
||||
bool CompressFileIntoPacket(const std::string& file_path, sf::Packet& packet);
|
||||
bool CompressBufferIntoPacket(const std::vector<u8>& in_buffer, sf::Packet& packet);
|
||||
void SendFirstReceivedToHost(PadMapping map, bool state);
|
||||
|
||||
u64 GetInitialNetPlayRTC() const;
|
||||
|
||||
|
@ -113,12 +117,16 @@ private:
|
|||
PadMappingArray m_wiimote_map;
|
||||
unsigned int m_save_data_synced_players = 0;
|
||||
bool m_start_pending = false;
|
||||
bool m_host_input_authority = false;
|
||||
|
||||
std::map<PlayerId, Client> m_players;
|
||||
|
||||
std::unordered_map<u32, std::vector<std::pair<PlayerId, u64>>> m_timebase_by_frame;
|
||||
bool m_desync_detected;
|
||||
|
||||
std::array<GCPadStatus, 4> m_last_pad_status{};
|
||||
std::array<bool, 4> m_first_pad_status_received{};
|
||||
|
||||
struct
|
||||
{
|
||||
std::recursive_mutex game;
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QPushButton>
|
||||
#include <QSignalBlocker>
|
||||
#include <QSpinBox>
|
||||
#include <QSplitter>
|
||||
#include <QTableWidget>
|
||||
|
@ -99,6 +100,7 @@ void NetPlayDialog::CreateMainLayout()
|
|||
m_record_input_box = new QCheckBox(tr("Record inputs"));
|
||||
m_reduce_polling_rate_box = new QCheckBox(tr("Reduce Polling Rate"));
|
||||
m_strict_settings_sync_box = new QCheckBox(tr("Strict Settings Sync"));
|
||||
m_host_input_authority_box = new QCheckBox(tr("Host Input Authority"));
|
||||
m_buffer_label = new QLabel(tr("Buffer:"));
|
||||
m_quit_button = new QPushButton(tr("Quit"));
|
||||
m_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
@ -142,6 +144,14 @@ void NetPlayDialog::CreateMainLayout()
|
|||
tr("This will sync additional graphics settings, and force everyone to the same internal "
|
||||
"resolution.\nMay prevent desync in some games that use EFB reads. Please ensure everyone "
|
||||
"uses the same video backend."));
|
||||
m_host_input_authority_box->setToolTip(
|
||||
tr("This gives the host control over when inputs are sent to the game, effectively "
|
||||
"decoupling players from each other in terms of buffering.\nThis allows players to have "
|
||||
"latency based solely on their connection to the host, rather than everyone's connection. "
|
||||
"Buffer works differently\nin this mode. The host always has no latency, and the buffer "
|
||||
"setting serves to prevent stutter, speeding up when the amount of buffered\ninputs "
|
||||
"exceeds the set limit. Input delay is instead based on ping to the host. This results in "
|
||||
"smoother gameplay on unstable connections."));
|
||||
|
||||
m_main_layout->addWidget(m_game_button, 0, 0);
|
||||
m_main_layout->addWidget(m_md5_button, 0, 1);
|
||||
|
@ -163,6 +173,7 @@ void NetPlayDialog::CreateMainLayout()
|
|||
options_boxes->addWidget(m_record_input_box);
|
||||
options_boxes->addWidget(m_reduce_polling_rate_box);
|
||||
options_boxes->addWidget(m_strict_settings_sync_box);
|
||||
options_boxes->addWidget(m_host_input_authority_box);
|
||||
|
||||
options_widget->addLayout(options_boxes, 0, 3, Qt::AlignTop);
|
||||
options_widget->setColumnStretch(3, 1000);
|
||||
|
@ -261,11 +272,20 @@ void NetPlayDialog::ConnectWidgets()
|
|||
if (value == m_buffer_size)
|
||||
return;
|
||||
|
||||
auto client = Settings::Instance().GetNetPlayClient();
|
||||
auto server = Settings::Instance().GetNetPlayServer();
|
||||
if (server)
|
||||
server->AdjustPadBufferSize(value);
|
||||
else
|
||||
client->AdjustPadBufferSize(value);
|
||||
});
|
||||
|
||||
connect(m_host_input_authority_box, &QCheckBox::toggled, [this](bool checked) {
|
||||
auto server = Settings::Instance().GetNetPlayServer();
|
||||
if (server)
|
||||
server->SetHostInputAuthority(checked);
|
||||
});
|
||||
|
||||
connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart);
|
||||
connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject);
|
||||
|
||||
|
@ -447,8 +467,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
|
|||
m_sync_save_data_box->setHidden(!is_hosting);
|
||||
m_reduce_polling_rate_box->setHidden(!is_hosting);
|
||||
m_strict_settings_sync_box->setHidden(!is_hosting);
|
||||
m_buffer_size_box->setHidden(!is_hosting);
|
||||
m_buffer_label->setHidden(!is_hosting);
|
||||
m_host_input_authority_box->setHidden(!is_hosting);
|
||||
m_kick_button->setHidden(!is_hosting);
|
||||
m_assign_ports_button->setHidden(!is_hosting);
|
||||
m_md5_button->setHidden(!is_hosting);
|
||||
|
@ -458,6 +477,8 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal)
|
|||
m_game_button->setEnabled(is_hosting);
|
||||
m_kick_button->setEnabled(false);
|
||||
|
||||
m_buffer_label->setText(is_hosting ? tr("Buffer:") : tr("Max Buffer:"));
|
||||
|
||||
QDialog::show();
|
||||
UpdateGUI();
|
||||
}
|
||||
|
@ -720,6 +741,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled)
|
|||
m_assign_ports_button->setEnabled(enabled);
|
||||
m_reduce_polling_rate_box->setEnabled(enabled);
|
||||
m_strict_settings_sync_box->setEnabled(enabled);
|
||||
m_host_input_authority_box->setEnabled(enabled);
|
||||
}
|
||||
|
||||
m_record_input_box->setEnabled(enabled);
|
||||
|
@ -744,12 +766,51 @@ void NetPlayDialog::OnMsgStopGame()
|
|||
|
||||
void NetPlayDialog::OnPadBufferChanged(u32 buffer)
|
||||
{
|
||||
QueueOnObject(this, [this, buffer] { m_buffer_size_box->setValue(buffer); });
|
||||
DisplayMessage(tr("Buffer size changed to %1").arg(buffer), "");
|
||||
QueueOnObject(this, [this, buffer] {
|
||||
const QSignalBlocker blocker(m_buffer_size_box);
|
||||
m_buffer_size_box->setValue(buffer);
|
||||
});
|
||||
DisplayMessage(m_host_input_authority && !IsHosting() ?
|
||||
tr("Max buffer size changed to %1").arg(buffer) :
|
||||
tr("Buffer size changed to %1").arg(buffer),
|
||||
"");
|
||||
|
||||
m_buffer_size = static_cast<int>(buffer);
|
||||
}
|
||||
|
||||
void NetPlayDialog::OnHostInputAuthorityChanged(bool enabled)
|
||||
{
|
||||
QueueOnObject(this, [this, enabled] {
|
||||
const bool is_hosting = IsHosting();
|
||||
const bool enable_buffer = is_hosting != enabled;
|
||||
|
||||
if (is_hosting)
|
||||
{
|
||||
m_buffer_size_box->setEnabled(enable_buffer);
|
||||
m_buffer_label->setEnabled(enable_buffer);
|
||||
m_buffer_size_box->setHidden(false);
|
||||
m_buffer_label->setHidden(false);
|
||||
|
||||
QSignalBlocker blocker(m_host_input_authority_box);
|
||||
m_host_input_authority_box->setChecked(enabled);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buffer_size_box->setEnabled(true);
|
||||
m_buffer_label->setEnabled(true);
|
||||
m_buffer_size_box->setHidden(!enable_buffer);
|
||||
m_buffer_label->setHidden(!enable_buffer);
|
||||
|
||||
if (enabled)
|
||||
m_buffer_size_box->setValue(1);
|
||||
}
|
||||
});
|
||||
DisplayMessage(enabled ? tr("Host input authority enabled") : tr("Host input authority disabled"),
|
||||
"");
|
||||
|
||||
m_host_input_authority = enabled;
|
||||
}
|
||||
|
||||
void NetPlayDialog::OnDesync(u32 frame, const std::string& player)
|
||||
{
|
||||
DisplayMessage(tr("Possible desync detected: %1 might have desynced at frame %2")
|
||||
|
|
|
@ -47,6 +47,7 @@ public:
|
|||
void OnMsgStartGame() override;
|
||||
void OnMsgStopGame() override;
|
||||
void OnPadBufferChanged(u32 buffer) override;
|
||||
void OnHostInputAuthorityChanged(bool enabled) override;
|
||||
void OnDesync(u32 frame, const std::string& player) override;
|
||||
void OnConnectionLost() override;
|
||||
void OnConnectionError(const std::string& message) override;
|
||||
|
@ -108,6 +109,7 @@ private:
|
|||
QCheckBox* m_record_input_box;
|
||||
QCheckBox* m_reduce_polling_rate_box;
|
||||
QCheckBox* m_strict_settings_sync_box;
|
||||
QCheckBox* m_host_input_authority_box;
|
||||
QPushButton* m_quit_button;
|
||||
QSplitter* m_splitter;
|
||||
|
||||
|
@ -124,4 +126,5 @@ private:
|
|||
int m_buffer_size = 0;
|
||||
int m_player_count = 0;
|
||||
int m_old_player_count = 0;
|
||||
bool m_host_input_authority = false;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue