diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index c40b122a70..f41a78ea57 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -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 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{}}); + + 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& 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& 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 new_save_state = + std::make_shared(std::vector{}, 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(inputs.at(local_player_port).size()) - + static_cast(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(inputs.at(local_player_port).size()) - + static_cast(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 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 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 diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 7ab716aef7..8f1f9fe3d3 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -43,6 +43,35 @@ struct SerializedWiimoteState; namespace NetPlay { +constexpr int rollback_frames_supported = 10; +using SaveState = std::pair, u64>; +// 0 is the closest SaveState in time from the current frame +class SaveStateArray +{ +public: + std::shared_ptr& New() + { + std::array, 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{}; + main_array = std::move(new_array); + + return main_array.at(0); + }; + + void reset() + { + for (auto& save_state : main_array) + save_state = std::shared_ptr{}; + } + + std::array, rollback_frames_supported> main_array; +}; + class NetPlayUI { public: @@ -179,6 +208,17 @@ public: static SyncIdentifier GetSDCardIdentifier(); + void OnFrameEnd(std::unique_lock& 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 m_wii_sync_fs; std::vector m_wii_sync_titles; std::string m_wii_sync_redirect_folder; + + std::vector> 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& 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 diff --git a/Source/Core/Core/NetPlayProto.h b/Source/Core/Core/NetPlayProto.h index a20aee39ce..adddaba571 100644 --- a/Source/Core/Core/NetPlayProto.h +++ b/Source/Core/Core/NetPlayProto.h @@ -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; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 0705e4d2a7..da8fd89ea2 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -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]; diff --git a/Source/Core/Core/PowerPC/CPUCoreBase.h b/Source/Core/Core/PowerPC/CPUCoreBase.h index 58f60416d5..8084d08ae0 100644 --- a/Source/Core/Core/PowerPC/CPUCoreBase.h +++ b/Source/Core/Core/PowerPC/CPUCoreBase.h @@ -10,6 +10,7 @@ public: virtual void Init() = 0; virtual void Shutdown() = 0; virtual void ClearCache() = 0; + virtual void RegisterCPUFunction(std::function function){}; virtual void Run() = 0; virtual void SingleStep() = 0; virtual const char* GetName() const = 0; diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.cpp b/Source/Core/Core/PowerPC/Jit64/Jit.cpp index 3c27e16fad..3e5c12dc9f 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit.cpp @@ -4,6 +4,7 @@ #include "Core/PowerPC/Jit64/Jit.h" #include +#include #include #include @@ -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) { diff --git a/Source/Core/Core/PowerPC/Jit64/Jit.h b/Source/Core/Core/PowerPC/Jit64/Jit.h index 0794dc34a3..6c1975839c 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit.h +++ b/Source/Core/Core/PowerPC/Jit64/Jit.h @@ -19,6 +19,7 @@ #include +#include #include #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 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 m_been_here; + + std::mutex m_external_functions_mutex; + std::queue> m_external_functions{}; }; void LogGeneratedX86(size_t size, const PPCAnalyst::CodeBuffer& code_buffer, const u8* normalEntry, diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index 11c8e9660e..11408259a9 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -204,9 +204,75 @@ static void DoState(Core::System& system, PointerWrap& p) #endif // USE_RETRO_ACHIEVEMENTS } +void DecompressBuffer(std::vector& buffer) +{ + //std::vector 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& buffer) +{ + /* std::vector 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& buffer) { - if (NetPlay::IsNetPlayRunning()) + if (!NetPlay::IsInRollbackMode() && NetPlay::IsNetPlayRunning()) { OSD::AddMessage("Loading savestates is disabled in Netplay to prevent desyncs"); return; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index 4ae4b5f315..fb3329389f 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -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"; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index f053772a18..3d3ba4ef58 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -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; diff --git a/Source/Core/DolphinQt/Settings.h b/Source/Core/DolphinQt/Settings.h index c1dae424fc..a1ceb0c8c5 100644 --- a/Source/Core/DolphinQt/Settings.h +++ b/Source/Core/DolphinQt/Settings.h @@ -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 m_continuously_frame_stepping = false; + std::shared_ptr m_client; std::shared_ptr m_server; ControllerInterface::HotplugCallbackHandle m_hotplug_callback_handle; diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 4f02c298b4..ab9f5edec2 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -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(); diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 66d377acd7..0aca94d490 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -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();