Resolve merge conflicts
This commit is contained in:
commit
ad37599dba
|
@ -8,6 +8,7 @@
|
|||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <latch>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
|
@ -40,9 +41,13 @@
|
|||
#include "Core/Config/SessionSettings.h"
|
||||
#include "Core/Config/WiimoteSettings.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/GeckoCode.h"
|
||||
#include "Core/HW/EXI/EXI.h"
|
||||
#include "Core/HW/EXI/EXI_DeviceIPL.h"
|
||||
#include "Core/PowerPC/CPUCoreBase.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/State.h"
|
||||
#ifdef HAS_LIBMGBA
|
||||
#include "Core/HW/GBACore.h"
|
||||
#endif
|
||||
|
@ -75,6 +80,8 @@
|
|||
#include "UICommon/GameFile.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "Common/HookableEvent.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
namespace NetPlay
|
||||
{
|
||||
|
@ -83,6 +90,11 @@ using namespace WiimoteCommon;
|
|||
static std::mutex crit_netplay_client;
|
||||
static NetPlayClient* netplay_client = nullptr;
|
||||
static bool s_si_poll_batching = false;
|
||||
static std::atomic<bool> is_rollingback;
|
||||
|
||||
static Common::EventHook s_after_frame_event = AfterFrameEvent::Register(
|
||||
[](const Core::System& system) { OnFrameEnd(); },
|
||||
"Netplay::OnFrameEnd");
|
||||
|
||||
// called from ---GUI--- thread
|
||||
NetPlayClient::~NetPlayClient()
|
||||
|
@ -689,10 +701,22 @@ void NetPlayClient::OnPadData(sf::Packet& packet)
|
|||
pad.substickY >> pad.triggerLeft >> pad.triggerRight >> pad.isConnected;
|
||||
}
|
||||
|
||||
// Trusting server for good map value (>=0 && <4)
|
||||
// add to pad buffer
|
||||
m_pad_buffer.at(map).Push(pad);
|
||||
m_gc_pad_event.Set();
|
||||
if (m_net_settings.m_RollbackMode)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(crit_netplay_client);
|
||||
inputs.at(map).push_back(pad);
|
||||
}
|
||||
|
||||
wait_for_inputs.notify_all();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Trusting server for good map value (>=0 && <4)
|
||||
// add to pad buffer
|
||||
m_pad_buffer.at(map).Push(pad);
|
||||
m_gc_pad_event.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -924,7 +948,7 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
|
|||
|
||||
packet >> m_net_settings.save_data_region;
|
||||
packet >> m_net_settings.sync_codes;
|
||||
|
||||
packet >> m_net_settings.m_RollbackMode;
|
||||
packet >> m_net_settings.golf_mode;
|
||||
packet >> m_net_settings.use_fma;
|
||||
packet >> m_net_settings.hide_remote_gbas;
|
||||
|
@ -935,6 +959,13 @@ void NetPlayClient::OnStartGame(sf::Packet& packet)
|
|||
m_net_settings.is_hosting = m_local_player->IsHost();
|
||||
}
|
||||
|
||||
inputs.clear();
|
||||
for (int i = 0; i < m_players.size(); i++)
|
||||
inputs.push_back(std::vector<GCPadStatus>{GCPadStatus{}});
|
||||
|
||||
save_states.reset();
|
||||
current_frame = 0;
|
||||
|
||||
m_dialog->OnMsgStartGame();
|
||||
}
|
||||
|
||||
|
@ -981,22 +1012,25 @@ void NetPlayClient::OnPlayerPingData(sf::Packet& packet)
|
|||
|
||||
void NetPlayClient::OnDesyncDetected(sf::Packet& packet)
|
||||
{
|
||||
int pid_to_blame;
|
||||
u32 frame;
|
||||
packet >> pid_to_blame;
|
||||
packet >> frame;
|
||||
|
||||
std::string player = "??";
|
||||
std::lock_guard lkp(m_crit.players);
|
||||
if (!m_net_settings.m_RollbackMode)
|
||||
{
|
||||
const auto it = m_players.find(pid_to_blame);
|
||||
if (it != m_players.end())
|
||||
player = it->second.name;
|
||||
int pid_to_blame;
|
||||
u32 frame;
|
||||
packet >> pid_to_blame;
|
||||
packet >> frame;
|
||||
|
||||
std::string player = "??";
|
||||
std::lock_guard lkp(m_crit.players);
|
||||
{
|
||||
const auto it = m_players.find(pid_to_blame);
|
||||
if (it != m_players.end())
|
||||
player = it->second.name;
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(NETPLAY, "Player {} ({}) desynced!", player, pid_to_blame);
|
||||
|
||||
m_dialog->OnDesync(frame, player);
|
||||
}
|
||||
|
||||
INFO_LOG_FMT(NETPLAY, "Player {} ({}) desynced!", player, pid_to_blame);
|
||||
|
||||
m_dialog->OnDesync(frame, player);
|
||||
}
|
||||
|
||||
void NetPlayClient::OnSyncSaveData(sf::Packet& packet)
|
||||
|
@ -1520,6 +1554,128 @@ void NetPlayClient::OnGameDigestAbort()
|
|||
m_dialog->AbortGameDigest();
|
||||
}
|
||||
|
||||
bool NetPlayClient::LoadFromFrame(u64 frame)
|
||||
{
|
||||
auto save_state = std::find_if(
|
||||
save_states.main_array.begin(), save_states.main_array.end(),
|
||||
[frame](const std::shared_ptr<SaveState>& save) { return save->second == frame; });
|
||||
|
||||
if (save_state == save_states.main_array.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
State::LoadFromBuffer(Core::System::GetInstance(), (**save_state).first);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void NetPlayClient::RollbackToFrame(u64 frame)
|
||||
{
|
||||
is_rollingback = true;
|
||||
if (LoadFromFrame(frame))
|
||||
{
|
||||
frame_to_stop_at = current_frame;
|
||||
current_frame = frame;
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
is_rollingback = false;
|
||||
DEBUG_LOG_FMT(NETPLAY, "Failed to roll back to frame {}!", frame);
|
||||
}
|
||||
}
|
||||
|
||||
void NetPlayClient::OnFrameEnd(std::unique_lock<std::mutex>& lock)
|
||||
{
|
||||
// this function is only called in rollback mode, but the logic to skip it is in
|
||||
// OnFrameEnd() (the one not in NetPlayClient::)
|
||||
sf::Packet packet;
|
||||
packet << MessageID::PadData;
|
||||
|
||||
bool send_packet = false;
|
||||
const int num_local_pads = NumLocalPads();
|
||||
for (int local_pad = 0; local_pad < num_local_pads; local_pad++)
|
||||
{
|
||||
// inputs for local players are acquired here
|
||||
send_packet = PollLocalPad(local_pad, packet) || send_packet;
|
||||
}
|
||||
|
||||
if (send_packet)
|
||||
SendAsync(std::move(packet));
|
||||
|
||||
std::shared_ptr<SaveState> new_save_state =
|
||||
std::make_shared<SaveState>(std::vector<u8>{}, current_frame);
|
||||
Core::System& system = Core::System::GetInstance();
|
||||
auto save_state_lambda = [&system, new_save_state]() {
|
||||
State::SaveToBuffer(system, new_save_state->first);
|
||||
};
|
||||
|
||||
system.GetJitInterface().GetCore()->RegisterCPUFunction(save_state_lambda);
|
||||
|
||||
save_states.New() = new_save_state;
|
||||
|
||||
// Wait for inputs if others are behind us, continue if we're behind them
|
||||
int local_player_port = -1;
|
||||
for (int i = 0; i < m_pad_map.size(); i++)
|
||||
{
|
||||
if (m_pad_map.at(i) == m_local_player->pid)
|
||||
local_player_port = i;
|
||||
}
|
||||
|
||||
bool needs_to_rollback = false;
|
||||
u64 farthest_rollback_frame = 0;
|
||||
|
||||
for (int remote_players = 0; remote_players < inputs.size(); remote_players++)
|
||||
{
|
||||
auto frame_difference = static_cast<long long>(inputs.at(local_player_port).size()) -
|
||||
static_cast<long long>(inputs.at(remote_players).size());
|
||||
if (remote_players == local_player_port)
|
||||
continue;
|
||||
|
||||
if (frame_difference <= delay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (frame_difference <= rollback_frames_supported + delay)
|
||||
{
|
||||
needs_to_rollback = true;
|
||||
farthest_rollback_frame = std::max(inputs.at(local_player_port).size() - frame_difference,
|
||||
farthest_rollback_frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
while (frame_difference > rollback_frames_supported + delay)
|
||||
{
|
||||
frame_difference = static_cast<long long>(inputs.at(local_player_port).size()) -
|
||||
static_cast<long long>(inputs.at(remote_players).size());
|
||||
wait_for_inputs.wait_for(lock, 1ms);
|
||||
}
|
||||
|
||||
needs_to_rollback = true;
|
||||
farthest_rollback_frame = rollback_frames_supported + delay;
|
||||
remote_players = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_to_rollback)
|
||||
RollbackToFrame(farthest_rollback_frame);
|
||||
}
|
||||
|
||||
bool NetPlayClient::IsRollingBack()
|
||||
{
|
||||
return is_rollingback.load();
|
||||
}
|
||||
|
||||
bool NetPlayClient::IsInRollbackMode()
|
||||
{
|
||||
return m_net_settings.m_RollbackMode;
|
||||
}
|
||||
|
||||
void NetPlayClient::Send(const sf::Packet& packet, const u8 channel_id)
|
||||
{
|
||||
Common::ENet::SendPacket(m_server, packet, channel_id);
|
||||
|
@ -1992,129 +2148,138 @@ void NetPlayClient::OnConnectFailed(Common::TraversalConnectFailedReason reason)
|
|||
// called from ---CPU--- thread
|
||||
bool NetPlayClient::GetNetPads(const int pad_nb, const bool batching, GCPadStatus* pad_status)
|
||||
{
|
||||
// The interface for this is extremely silly.
|
||||
//
|
||||
// Imagine a physical device that links three GameCubes together
|
||||
// and emulates NetPlay that way. Which GameCube controls which
|
||||
// in-game controllers can be configured on the device (m_pad_map)
|
||||
// but which sockets on each individual GameCube should be used
|
||||
// to control which players? The solution that Dolphin uses is
|
||||
// that we hardcode the knowledge that they go in order, so if
|
||||
// you have a 3P game with three GameCubes, then every single
|
||||
// controller should be plugged into slot 1.
|
||||
//
|
||||
// If you have a 4P game, then one of the GameCubes will have
|
||||
// a controller plugged into slot 1, and another in slot 2.
|
||||
//
|
||||
// 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 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// When here when told to so we don't deadlock in certain situations
|
||||
while (m_wait_on_input)
|
||||
if (m_net_settings.m_RollbackMode)
|
||||
{
|
||||
if (!m_is_running.IsSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_wait_on_input_received)
|
||||
{
|
||||
// Tell the server we've acknowledged the message
|
||||
sf::Packet spac;
|
||||
spac << MessageID::GolfPrepare;
|
||||
Send(spac);
|
||||
|
||||
m_wait_on_input_received = false;
|
||||
}
|
||||
|
||||
m_wait_on_input_event.Wait();
|
||||
if (is_rollingback && inputs.at(pad_nb).size() > current_frame)
|
||||
*pad_status = inputs.at(pad_nb).at(current_frame);
|
||||
else
|
||||
*pad_status = inputs.at(pad_nb).back();
|
||||
}
|
||||
|
||||
if (IsFirstInGamePad(pad_nb) && batching)
|
||||
else
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << MessageID::PadData;
|
||||
// The interface for this is extremely silly.
|
||||
//
|
||||
// Imagine a physical device that links three GameCubes together
|
||||
// and emulates NetPlay that way. Which GameCube controls which
|
||||
// in-game controllers can be configured on the device (m_pad_map)
|
||||
// but which sockets on each individual GameCube should be used
|
||||
// to control which players? The solution that Dolphin uses is
|
||||
// that we hardcode the knowledge that they go in order, so if
|
||||
// you have a 3P game with three GameCubes, then every single
|
||||
// controller should be plugged into slot 1.
|
||||
//
|
||||
// If you have a 4P game, then one of the GameCubes will have
|
||||
// a controller plugged into slot 1, and another in slot 2.
|
||||
//
|
||||
// The slot number is the "local" pad number, and what player
|
||||
// it actually means is the "in-game" pad number.
|
||||
|
||||
bool send_packet = false;
|
||||
const int num_local_pads = NumLocalPads();
|
||||
for (int local_pad = 0; local_pad < num_local_pads; local_pad++)
|
||||
// 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.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// When here when told to so we don't deadlock in certain situations
|
||||
while (m_wait_on_input)
|
||||
{
|
||||
send_packet = PollLocalPad(local_pad, packet) || send_packet;
|
||||
if (!m_is_running.IsSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_wait_on_input_received)
|
||||
{
|
||||
// Tell the server we've acknowledged the message
|
||||
sf::Packet spac;
|
||||
spac << MessageID::GolfPrepare;
|
||||
Send(spac);
|
||||
|
||||
m_wait_on_input_received = false;
|
||||
}
|
||||
|
||||
m_wait_on_input_event.Wait();
|
||||
}
|
||||
|
||||
if (send_packet)
|
||||
SendAsync(std::move(packet));
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(-1);
|
||||
}
|
||||
|
||||
if (!batching)
|
||||
{
|
||||
const int local_pad = InGamePadToLocalPad(pad_nb);
|
||||
if (local_pad < 4)
|
||||
if (IsFirstInGamePad(pad_nb) && batching)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << MessageID::PadData;
|
||||
if (PollLocalPad(local_pad, packet))
|
||||
|
||||
bool send_packet = false;
|
||||
const int num_local_pads = NumLocalPads();
|
||||
for (int local_pad = 0; local_pad < num_local_pads; local_pad++)
|
||||
{
|
||||
send_packet = PollLocalPad(local_pad, packet) || send_packet;
|
||||
}
|
||||
|
||||
if (send_packet)
|
||||
SendAsync(std::move(packet));
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(-1);
|
||||
}
|
||||
|
||||
if (!batching)
|
||||
{
|
||||
const int local_pad = InGamePadToLocalPad(pad_nb);
|
||||
if (local_pad < 4)
|
||||
{
|
||||
sf::Packet packet;
|
||||
packet << MessageID::PadData;
|
||||
if (PollLocalPad(local_pad, packet))
|
||||
SendAsync(std::move(packet));
|
||||
}
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(pad_nb);
|
||||
}
|
||||
|
||||
if (m_host_input_authority)
|
||||
SendPadHostPoll(pad_nb);
|
||||
}
|
||||
|
||||
if (m_host_input_authority)
|
||||
{
|
||||
if (m_local_player->pid != m_current_golfer)
|
||||
{
|
||||
// CoreTiming acts funny and causes what looks like frame skip if
|
||||
// we toggle the emulation speed too quickly, so to prevent this
|
||||
// we wait until the buffer has been over for at least 1 second.
|
||||
|
||||
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)
|
||||
if (m_local_player->pid != m_current_golfer)
|
||||
{
|
||||
// run fast if the buffer is overfilled, otherwise run normal speed
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, buffer_over_target ? 0.0f : 1.0f);
|
||||
// CoreTiming acts funny and causes what looks like frame skip if
|
||||
// we toggle the emulation speed too quickly, so to prevent this
|
||||
// we wait until the buffer has been over for at least 1 second.
|
||||
|
||||
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
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, buffer_over_target ? 0.0f : 1.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set normal speed when we're the host, otherwise it can get stuck at unlimited
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set normal speed when we're the host, otherwise it can get stuck at unlimited
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we either use the data pushed earlier, or wait for the
|
||||
// other clients to send it to us
|
||||
while (m_pad_buffer[pad_nb].Size() == 0)
|
||||
{
|
||||
if (!m_is_running.IsSet())
|
||||
// Now, we either use the data pushed earlier, or wait for the
|
||||
// other clients to send it to us
|
||||
while (m_pad_buffer[pad_nb].Size() == 0)
|
||||
{
|
||||
return false;
|
||||
if (!m_is_running.IsSet())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_gc_pad_event.Wait();
|
||||
}
|
||||
|
||||
m_gc_pad_event.Wait();
|
||||
m_pad_buffer[pad_nb].Pop(*pad_status);
|
||||
}
|
||||
|
||||
m_pad_buffer[pad_nb].Pop(*pad_status);
|
||||
|
||||
auto& movie = Core::System::GetInstance().GetMovie();
|
||||
if (movie.IsRecordingInput())
|
||||
{
|
||||
|
@ -2193,33 +2358,42 @@ bool NetPlayClient::PollLocalPad(const int local_pad, sf::Packet& packet)
|
|||
pad_status = Pad::GetStatus(local_pad);
|
||||
}
|
||||
|
||||
if (m_host_input_authority)
|
||||
if (m_net_settings.m_RollbackMode)
|
||||
{
|
||||
if (m_local_player->pid != m_current_golfer)
|
||||
{
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
||||
data_added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// set locally
|
||||
m_last_pad_status[ingame_pad] = pad_status;
|
||||
m_first_pad_status_received[ingame_pad] = true;
|
||||
}
|
||||
inputs.at(ingame_pad).push_back(pad_status);
|
||||
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)
|
||||
if (m_host_input_authority)
|
||||
{
|
||||
// add to buffer
|
||||
m_pad_buffer[ingame_pad].Push(pad_status);
|
||||
if (m_local_player->pid != m_current_golfer)
|
||||
{
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
||||
data_added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// set locally
|
||||
m_last_pad_status[ingame_pad] = pad_status;
|
||||
m_first_pad_status_received[ingame_pad] = 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;
|
||||
// add to packet
|
||||
AddPadStateToPacket(ingame_pad, pad_status, packet);
|
||||
data_added = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2255,12 +2429,13 @@ void NetPlayClient::SendPadHostPoll(const PadIndex pad_num)
|
|||
// pads (used for batched polls), while 0..3 will poll the respective pad (used for MMIO polls).
|
||||
// See GetNetPads for more details.
|
||||
//
|
||||
// If the local buffer is non-empty, we skip actually buffering and sending new pad data, this way
|
||||
// don't end up with permanent local latency. It does create a period of time where no inputs are
|
||||
// accepted, but under typical circumstances this is not noticeable.
|
||||
// If the local buffer is non-empty, we skip actually buffering and sending new pad data, this
|
||||
// way don't end up with permanent local latency. It does create a period of time where no
|
||||
// inputs are accepted, but under typical circumstances this is not noticeable.
|
||||
//
|
||||
// Additionally, we wait until some actual pad data has been received before buffering and sending
|
||||
// it, otherwise controllers get calibrated wrongly with the default values of GCPadStatus.
|
||||
// Additionally, we wait until some actual pad data has been received before buffering and
|
||||
// sending it, otherwise controllers get calibrated wrongly with the default values of
|
||||
// GCPadStatus.
|
||||
|
||||
if (m_local_player->pid != m_current_golfer)
|
||||
return;
|
||||
|
@ -2767,6 +2942,39 @@ void NetPlay_Disable()
|
|||
std::lock_guard lk(crit_netplay_client);
|
||||
netplay_client = nullptr;
|
||||
}
|
||||
|
||||
void OnFrameEnd()
|
||||
{
|
||||
if (IsNetPlayRunning() && netplay_client)
|
||||
{
|
||||
if (netplay_client->IsInRollbackMode())
|
||||
{
|
||||
if (!is_rollingback)
|
||||
{
|
||||
std::unique_lock lock(crit_netplay_client);
|
||||
netplay_client->OnFrameEnd(lock);
|
||||
}
|
||||
else if (netplay_client->current_frame >= netplay_client->frame_to_stop_at)
|
||||
{
|
||||
Config::SetCurrent(Config::MAIN_EMULATION_SPEED, 1.0);
|
||||
is_rollingback = false;
|
||||
}
|
||||
|
||||
netplay_client->current_frame++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsRollingBack()
|
||||
{
|
||||
return netplay_client->IsRollingBack();
|
||||
}
|
||||
|
||||
bool IsInRollbackMode()
|
||||
{
|
||||
return netplay_client->IsInRollbackMode();
|
||||
}
|
||||
|
||||
} // namespace NetPlay
|
||||
|
||||
// stuff hacked into dolphin
|
||||
|
|
|
@ -43,6 +43,35 @@ struct SerializedWiimoteState;
|
|||
|
||||
namespace NetPlay
|
||||
{
|
||||
constexpr int rollback_frames_supported = 10;
|
||||
using SaveState = std::pair<std::vector<u8>, u64>;
|
||||
// 0 is the closest SaveState in time from the current frame
|
||||
class SaveStateArray
|
||||
{
|
||||
public:
|
||||
std::shared_ptr<SaveState>& New()
|
||||
{
|
||||
std::array<std::shared_ptr<SaveState>, rollback_frames_supported> new_array{};
|
||||
|
||||
for (int i = rollback_frames_supported - 2; i >= 0; i--)
|
||||
{
|
||||
new_array.at(i + 1) = main_array.at(i);
|
||||
}
|
||||
new_array.at(0) = std::shared_ptr<SaveState>{};
|
||||
main_array = std::move(new_array);
|
||||
|
||||
return main_array.at(0);
|
||||
};
|
||||
|
||||
void reset()
|
||||
{
|
||||
for (auto& save_state : main_array)
|
||||
save_state = std::shared_ptr<SaveState>{};
|
||||
}
|
||||
|
||||
std::array<std::shared_ptr<SaveState>, rollback_frames_supported> main_array;
|
||||
};
|
||||
|
||||
class NetPlayUI
|
||||
{
|
||||
public:
|
||||
|
@ -179,6 +208,17 @@ public:
|
|||
|
||||
static SyncIdentifier GetSDCardIdentifier();
|
||||
|
||||
void OnFrameEnd(std::unique_lock<std::mutex>& lock);
|
||||
bool IsRollingBack();
|
||||
bool IsInRollbackMode();
|
||||
|
||||
// Only for use in NetPlayClient.cpp >:(
|
||||
size_t current_frame = 0;
|
||||
// Only for use in NetPlayClient.cpp >:(
|
||||
size_t frame_to_stop_at = 0;
|
||||
|
||||
bool done_fast_forwarding;
|
||||
|
||||
protected:
|
||||
struct AsyncQueueEntry
|
||||
{
|
||||
|
@ -350,10 +390,24 @@ private:
|
|||
std::unique_ptr<IOS::HLE::FS::FileSystem> m_wii_sync_fs;
|
||||
std::vector<u64> m_wii_sync_titles;
|
||||
std::string m_wii_sync_redirect_folder;
|
||||
|
||||
std::vector<std::vector<GCPadStatus>> inputs;
|
||||
int delay = 2;
|
||||
std::condition_variable wait_for_inputs;
|
||||
SaveStateArray save_states;
|
||||
|
||||
|
||||
bool LoadFromFrame(u64 frame);
|
||||
void RollbackToFrame(u64 frame);
|
||||
};
|
||||
|
||||
void NetPlay_Enable(NetPlayClient* const np);
|
||||
void NetPlay_Disable();
|
||||
bool NetPlay_GetWiimoteData(const std::span<NetPlayClient::WiimoteDataBatchEntry>& entries);
|
||||
unsigned int NetPlay_GetLocalWiimoteForSlot(unsigned int slot);
|
||||
void OnFrameEnd();
|
||||
// tells when Dolphin is actually mid rollback
|
||||
bool IsRollingBack();
|
||||
// tells if we're using rollback networking
|
||||
bool IsInRollbackMode();
|
||||
} // namespace NetPlay
|
||||
|
|
|
@ -105,6 +105,7 @@ struct NetSettings
|
|||
bool golf_mode = false;
|
||||
bool use_fma = false;
|
||||
bool hide_remote_gbas = false;
|
||||
bool m_RollbackMode = false;
|
||||
|
||||
Sram sram;
|
||||
|
||||
|
|
|
@ -1450,6 +1450,7 @@ bool NetPlayServer::SetupNetSettings()
|
|||
settings.strict_settings_sync = Config::Get(Config::NETPLAY_STRICT_SETTINGS_SYNC);
|
||||
settings.sync_codes = Config::Get(Config::NETPLAY_SYNC_CODES);
|
||||
settings.golf_mode = Config::Get(Config::NETPLAY_NETWORK_MODE) == "golf";
|
||||
settings.m_RollbackMode = Config::Get(Config::NETPLAY_NETWORK_MODE) == "rollback";
|
||||
settings.use_fma = DoAllPlayersHaveHardwareFMA();
|
||||
settings.hide_remote_gbas = Config::Get(Config::NETPLAY_HIDE_REMOTE_GBAS);
|
||||
|
||||
|
@ -1659,6 +1660,7 @@ bool NetPlayServer::StartGame()
|
|||
spac << m_settings.golf_mode;
|
||||
spac << m_settings.use_fma;
|
||||
spac << m_settings.hide_remote_gbas;
|
||||
spac << m_settings.m_RollbackMode;
|
||||
|
||||
for (size_t i = 0; i < sizeof(m_settings.sram); ++i)
|
||||
spac << m_settings.sram[i];
|
||||
|
|
|
@ -10,6 +10,7 @@ public:
|
|||
virtual void Init() = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
virtual void ClearCache() = 0;
|
||||
virtual void RegisterCPUFunction(std::function<void()> function){};
|
||||
virtual void Run() = 0;
|
||||
virtual void SingleStep() = 0;
|
||||
virtual const char* GetName() const = 0;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "Core/PowerPC/Jit64/Jit.h"
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
|
@ -31,6 +32,7 @@
|
|||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/ProcessorInterface.h"
|
||||
#include "Core/MachineContext.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
||||
#include "Core/PowerPC/Jit64/JitAsm.h"
|
||||
|
@ -879,6 +881,18 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
|
|||
// TODO: Test if this or AlignCode16 make a difference from GetCodePtr
|
||||
b->normalEntry = AlignCode4();
|
||||
|
||||
{
|
||||
std::unique_lock lock(m_external_functions_mutex);
|
||||
while (!m_external_functions.empty())
|
||||
{
|
||||
auto external_function = m_external_functions.front();
|
||||
m_external_functions.pop();
|
||||
lock.unlock();
|
||||
external_function();
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
|
||||
// Used to get a trace of the last few blocks before a crash, sometimes VERY useful
|
||||
if (m_im_here_debug)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include <queue>
|
||||
#include <rangeset/rangesizeset.h>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
@ -106,6 +107,13 @@ public:
|
|||
|
||||
bool Cleanup();
|
||||
|
||||
// Runs a function on the CPU during the next JIT compilation
|
||||
void RegisterCPUFunction(std::function<void()> function) override
|
||||
{
|
||||
std::lock_guard lock(m_external_functions_mutex);
|
||||
m_external_functions.push(function);
|
||||
}
|
||||
|
||||
void GenerateConstantOverflow(bool overflow);
|
||||
void GenerateConstantOverflow(s64 val);
|
||||
void GenerateOverflow(Gen::CCFlags cond = Gen::CCFlags::CC_NO);
|
||||
|
@ -284,6 +292,9 @@ private:
|
|||
const bool m_im_here_debug = false;
|
||||
const bool m_im_here_log = false;
|
||||
std::map<u32, int> m_been_here;
|
||||
|
||||
std::mutex m_external_functions_mutex;
|
||||
std::queue<std::function<void()>> m_external_functions{};
|
||||
};
|
||||
|
||||
void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry,
|
||||
|
|
|
@ -204,9 +204,75 @@ static void DoState(Core::System& system, PointerWrap& p)
|
|||
#endif // USE_RETRO_ACHIEVEMENTS
|
||||
}
|
||||
|
||||
void DecompressBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
//std::vector<u8> return_vector{};
|
||||
//size_t i = 0;
|
||||
//while (true)
|
||||
//{
|
||||
// lzo_uint32 cur_len = 0; // number of bytes to read
|
||||
// lzo_uint new_len = 0; // number of bytes to write
|
||||
|
||||
// if (i > buffer.size())
|
||||
// break;
|
||||
|
||||
// std::memcpy(&cur_len, buffer.data() + i, sizeof(cur_len));
|
||||
|
||||
// std::memcpy(out, buffer.data() + i, cur_len);
|
||||
// const int res = lzo1x_decompress(out, cur_len, &buffer[i], &new_len, nullptr);
|
||||
// if (res != LZO_E_OK)
|
||||
// {
|
||||
// // This doesn't seem to happen anymore.
|
||||
// PanicAlertFmtT("Internal LZO Error - decompression failed ({0}) ({1}, {2}) \n"
|
||||
// "Try loading the state again",
|
||||
// res, i, new_len);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// i += new_len;
|
||||
//}
|
||||
}
|
||||
|
||||
void CompressBuffer(std::vector<u8>& buffer)
|
||||
{
|
||||
/* std::vector<u8> return_vector{};
|
||||
lzo_uint i = 0;
|
||||
while (true)
|
||||
{
|
||||
lzo_uint32 cur_len = 0;
|
||||
lzo_uint out_len = 0;
|
||||
|
||||
if ((i + IN_LEN) >= buffer.size())
|
||||
{
|
||||
cur_len = (lzo_uint32)(buffer.size() - i);
|
||||
}
|
||||
else
|
||||
{
|
||||
cur_len = IN_LEN;
|
||||
}
|
||||
|
||||
if (lzo1x_1_compress(buffer.data() + i, cur_len, out, &out_len, wrkmem) != LZO_E_OK)
|
||||
PanicAlertFmtT("Internal LZO Error - compression failed");
|
||||
|
||||
size_t old_size = return_vector.size();
|
||||
return_vector.resize(sizeof(out_len));
|
||||
std::memcpy(return_vector.data() + old_size, &out_len, sizeof(out_len));
|
||||
|
||||
old_size = return_vector.size();
|
||||
return_vector.resize(out_len);
|
||||
std::memcpy(return_vector.data() + old_size, out, out_len);
|
||||
|
||||
if (cur_len != IN_LEN)
|
||||
break;
|
||||
|
||||
i += cur_len;
|
||||
}
|
||||
buffer = return_vector;*/
|
||||
}
|
||||
|
||||
void LoadFromBuffer(Core::System& system, std::vector<u8>& buffer)
|
||||
{
|
||||
if (NetPlay::IsNetPlayRunning())
|
||||
if (!NetPlay::IsInRollbackMode() && NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
OSD::AddMessage("Loading savestates is disabled in Netplay to prevent desyncs");
|
||||
return;
|
||||
|
|
|
@ -188,6 +188,9 @@ void NetPlayDialog::CreateMainLayout()
|
|||
"configured by the host.\nSuitable for competitive games where fairness and minimal "
|
||||
"latency are most important."));
|
||||
m_fixed_delay_action->setCheckable(true);
|
||||
m_rollback_action = m_network_menu->addAction(tr("Rollback"));
|
||||
m_rollback_action->setToolTip(tr("[WIP]"));
|
||||
m_rollback_action->setCheckable(true);
|
||||
m_host_input_authority_action = m_network_menu->addAction(tr("Host Input Authority"));
|
||||
m_host_input_authority_action->setToolTip(
|
||||
tr("Host has control of sending all inputs to the game, as received from other players, "
|
||||
|
@ -204,6 +207,7 @@ void NetPlayDialog::CreateMainLayout()
|
|||
m_network_mode_group = new QActionGroup(this);
|
||||
m_network_mode_group->setExclusive(true);
|
||||
m_network_mode_group->addAction(m_fixed_delay_action);
|
||||
m_network_mode_group->addAction(m_rollback_action);
|
||||
m_network_mode_group->addAction(m_host_input_authority_action);
|
||||
m_network_mode_group->addAction(m_golf_mode_action);
|
||||
m_fixed_delay_action->setChecked(true);
|
||||
|
@ -377,6 +381,7 @@ void NetPlayDialog::ConnectWidgets()
|
|||
[hia_function] { hia_function(true); });
|
||||
connect(m_golf_mode_action, &QAction::toggled, this, [hia_function] { hia_function(true); });
|
||||
connect(m_fixed_delay_action, &QAction::toggled, this, [hia_function] { hia_function(false); });
|
||||
connect(m_rollback_action, &QAction::toggled, this, [hia_function] { hia_function(false); });
|
||||
|
||||
connect(m_start_button, &QPushButton::clicked, this, &NetPlayDialog::OnStart);
|
||||
connect(m_quit_button, &QPushButton::clicked, this, &NetPlayDialog::reject);
|
||||
|
@ -425,6 +430,7 @@ void NetPlayDialog::ConnectWidgets()
|
|||
connect(m_golf_mode_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
|
||||
connect(m_golf_mode_overlay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
|
||||
connect(m_fixed_delay_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
|
||||
connect(m_rollback_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
|
||||
connect(m_hide_remote_gbas_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings);
|
||||
}
|
||||
|
||||
|
@ -863,6 +869,7 @@ void NetPlayDialog::SetOptionsEnabled(bool enabled)
|
|||
m_host_input_authority_action->setEnabled(enabled);
|
||||
m_golf_mode_action->setEnabled(enabled);
|
||||
m_fixed_delay_action->setEnabled(enabled);
|
||||
m_rollback_action->setEnabled(enabled);
|
||||
}
|
||||
|
||||
m_record_input_action->setEnabled(enabled);
|
||||
|
@ -1158,6 +1165,10 @@ void NetPlayDialog::LoadSettings()
|
|||
{
|
||||
m_fixed_delay_action->setChecked(true);
|
||||
}
|
||||
else if (network_mode == "rollback")
|
||||
{
|
||||
m_rollback_action->setChecked(true);
|
||||
}
|
||||
else if (network_mode == "hostinputauthority")
|
||||
{
|
||||
m_host_input_authority_action->setChecked(true);
|
||||
|
@ -1204,6 +1215,10 @@ void NetPlayDialog::SaveSettings()
|
|||
{
|
||||
network_mode = "hostinputauthority";
|
||||
}
|
||||
else if (m_rollback_action->isChecked())
|
||||
{
|
||||
network_mode = "rollback";
|
||||
}
|
||||
else if (m_golf_mode_action->isChecked())
|
||||
{
|
||||
network_mode = "golf";
|
||||
|
|
|
@ -156,6 +156,7 @@ private:
|
|||
QAction* m_host_input_authority_action;
|
||||
QAction* m_golf_mode_action;
|
||||
QAction* m_golf_mode_overlay_action;
|
||||
QAction* m_rollback_action;
|
||||
QAction* m_fixed_delay_action;
|
||||
QAction* m_hide_remote_gbas_action;
|
||||
QPushButton* m_quit_button;
|
||||
|
|
|
@ -120,6 +120,9 @@ public:
|
|||
bool IsUSBKeyboardConnected() const;
|
||||
void SetUSBKeyboardConnected(bool connected);
|
||||
|
||||
void SetIsContinuouslyFrameStepping(bool is_stepping);
|
||||
bool GetIsContinuouslyFrameStepping() const;
|
||||
|
||||
// Graphics
|
||||
Config::ShowCursor GetCursorVisibility() const;
|
||||
bool GetLockCursor() const;
|
||||
|
@ -229,6 +232,8 @@ private:
|
|||
Settings();
|
||||
|
||||
bool m_batch = false;
|
||||
std::atomic<bool> m_continuously_frame_stepping = false;
|
||||
|
||||
std::shared_ptr<NetPlay::NetPlayClient> m_client;
|
||||
std::shared_ptr<NetPlay::NetPlayServer> m_server;
|
||||
ControllerInterface::HotplugCallbackHandle m_hotplug_callback_handle;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
|
||||
#include "VideoCommon/BPFunctions.h"
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
|
@ -355,7 +356,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager, XFStateManager&
|
|||
// display.
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
if (g_ActiveConfig.bImmediateXFB)
|
||||
if (g_ActiveConfig.bImmediateXFB && !NetPlay::IsRollingBack())
|
||||
{
|
||||
// below div two to convert from bytes to pixels - it expects width, not stride
|
||||
u64 ticks = system.GetCoreTiming().GetTicks();
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "Core/HW/VideoInterface.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/NetPlayClient.h"
|
||||
|
||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||
|
||||
|
@ -829,7 +830,7 @@ void Presenter::Present()
|
|||
RenderXFBToScreen(render_target_rc, m_xfb_entry->texture.get(), render_source_rc);
|
||||
}
|
||||
|
||||
if (m_onscreen_ui)
|
||||
if (m_onscreen_ui && !NetPlay::IsRollingBack())
|
||||
{
|
||||
m_onscreen_ui->Finalize();
|
||||
m_onscreen_ui->DrawImGui();
|
||||
|
@ -848,7 +849,7 @@ void Presenter::Present()
|
|||
SetSuggestedWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
|
||||
}
|
||||
|
||||
if (m_onscreen_ui)
|
||||
if (m_onscreen_ui && !NetPlay::IsRollingBack())
|
||||
m_onscreen_ui->BeginImGuiFrame(m_backbuffer_width, m_backbuffer_height);
|
||||
|
||||
g_gfx->EndUtilityDrawing();
|
||||
|
|
Loading…
Reference in New Issue