diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index 74b0aa48a..78802ccc4 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -48,6 +48,11 @@ enum class ControlMessage : u32 SynchronizeComplete, }; +enum class SessionMessage : u32 +{ + ChatMessage, +}; + #pragma pack(push, 1) struct ControlMessageHeader { @@ -55,6 +60,13 @@ struct ControlMessageHeader u32 size; }; +#pragma pack(push, 1) +struct SessionMessageHeader +{ + SessionMessage type; + u32 size; +}; + struct ControlConnectResponseMessage { enum class Result : u32 @@ -94,6 +106,15 @@ struct ControlSynchronizeCompleteMessage static ControlMessage MessageType() { return ControlMessage::SynchronizeComplete; } }; + +struct SessionChatMessage +{ + SessionMessageHeader header; + + u32 chat_message_size; + + static SessionMessage MessageType() { return SessionMessage::ChatMessage; } +}; #pragma pack(pop) using SaveStateBuffer = std::unique_ptr; @@ -142,6 +163,10 @@ static void HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt); static void HandleSynchronizeSessionMessage(s32 player_id, const ENetPacket* pkt); static void HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket* pkt); +// Sessionpackets +static void HandleSessionMessage(s32 player_id, const ENetPacket* pkt); +static void HandleSessionChatMessage(s32 player_id, const ENetPacket* pkt); + // l = local, r = remote static bool CreateGGPOSession(); static void DestroyGGPOSession(); @@ -170,7 +195,6 @@ static void Throttle(); // Desync Detection static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size); -static void GenerateDesyncReport(s32 desync_frame); ////////////////////////////////////////////////////////////////////////// // Variables @@ -252,7 +276,34 @@ static bool SendControlPacket(s32 player_id, const PacketWrapper& pkt) DebugAssert(player_id >= 0 && player_id < MAX_PLAYERS && s_peers[player_id].peer); return SendControlPacket(s_peers[player_id].peer, pkt); } +template +static PacketWrapper NewSessionPacket(u32 size = sizeof(T), u32 flags = ENET_PACKET_FLAG_RELIABLE) +{ + PacketWrapper ret = NewWrappedPacket(size, flags); + SessionMessageHeader* hdr = reinterpret_cast(ret.pkt->data); + hdr->type = T::MessageType(); + hdr->size = size; + return ret; +} +template +static bool SendSessionPacket(ENetPeer* peer, const PacketWrapper& pkt) +{ + const int rc = enet_peer_send(peer, ENET_CHANNEL_SESSION, pkt.pkt); + if (rc != 0) + { + Log_ErrorPrintf("enet_peer_send() failed: %d", rc); + enet_packet_destroy(pkt.pkt); + return false; + } + return true; +} +template +static bool SendSessionPacket(s32 player_id, const PacketWrapper& pkt) +{ + DebugAssert(player_id >= 0 && player_id < MAX_PLAYERS && s_peers[player_id].peer); + return SendSessionPacket(s_peers[player_id].peer, pkt); +} } // namespace Netplay // Netplay Impl @@ -472,6 +523,10 @@ void Netplay::HandleEnetEvent(const ENetEvent* event) { HandleControlMessage(player_id, event->packet); } + else if (event->channelID == ENET_CHANNEL_SESSION) + { + HandleSessionMessage(player_id, event->packet); + } else if (event->channelID == ENET_CHANNEL_GGPO) { Log_TracePrintf("Received %zu ggpo bytes from player %d", event->packet->dataLength, player_id); @@ -505,6 +560,10 @@ void Netplay::PollEnet(Common::Timer::Value until_time) const u32 enet_timeout = (current_time >= until_time) ? 0 : static_cast(Common::Timer::ConvertValueToMilliseconds(until_time - current_time)); + + // make sure s_enet_host exists + Assert(s_enet_host); + const int res = enet_host_service(s_enet_host, &event, enet_timeout); if (res > 0) { @@ -514,7 +573,6 @@ void Netplay::PollEnet(Common::Timer::Value until_time) current_time = Common::Timer::GetCurrentValue(); continue; } - // exit once we're nonblocking current_time = Common::Timer::GetCurrentValue(); if (enet_timeout == 0 || current_time >= until_time) @@ -1003,6 +1061,43 @@ void Netplay::HandleSynchronizeCompleteMessage(s32 player_id, const ENetPacket* CheckForCompleteResynchronize(); } +void Netplay::HandleSessionMessage(s32 player_id, const ENetPacket* pkt) +{ + if (pkt->dataLength < sizeof(ControlMessageHeader)) + { + Log_ErrorPrintf("Invalid control packet from player %d of size %zu", player_id, pkt->dataLength); + return; + } + + const SessionMessageHeader* hdr = reinterpret_cast(pkt->data); + switch (hdr->type) + { + case SessionMessage::ChatMessage: + HandleSessionChatMessage(player_id, pkt); + break; + + default: + Log_ErrorPrintf("Unhandled session packet %u from player %d of size %zu", hdr->type, player_id, pkt->dataLength); + break; + } +} + +void Netplay::HandleSessionChatMessage(s32 player_id, const ENetPacket* pkt) +{ + const SessionChatMessage* msg = reinterpret_cast(pkt->data); + if (pkt->dataLength < sizeof(SessionChatMessage) || + pkt->dataLength < (sizeof(SessionChatMessage) + msg->chat_message_size)) + { + // invalid chat message. ignore. + return; + } + + std::string message(pkt->data + sizeof(SessionChatMessage), + pkt->data + sizeof(SessionChatMessage) + msg->chat_message_size); + + Host::OnNetplayMessage(fmt::format("Player {}: {}", player_id + 1, message)); +} + void Netplay::CheckForCompleteResynchronize() { if (s_synchronized_players == s_num_players) @@ -1037,8 +1132,6 @@ void Netplay::SetSettings() // no block linking, it degrades savestate loading performance si.SetBoolValue("CPU", "RecompilerBlockLinking", false); - // not sure its needed but enabled for now... TODO - si.SetBoolValue("GPU", "UseSoftwareRendererForReadbacks", true); Host::Internal::SetNetplaySettingsLayer(&si); System::ApplySettings(false); @@ -1065,6 +1158,9 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { + // only activate timesync if its worth correcting. + if (std::abs(frame_delta) < 1.0f) + return; // Distribute the frame difference over the next N * 0.75 frames. // only part of the interval time is used since we want to come back to normal speed. // otherwise we will keep spiraling into unplayable gameplay. @@ -1140,37 +1236,6 @@ void Netplay::GenerateChecksumForFrame(int* checksum, int frame, unsigned char* // Log_VerbosePrintf("Netplay Checksum: f:%d wf:%d c:%u", frame, frame % num_group_of_pages, *checksum); } -void Netplay::GenerateDesyncReport(s32 desync_frame) -{ - std::string path = "\\netplaylogs\\desync_frame_" + std::to_string(desync_frame) + "_p" + - std::to_string(s_local_handle) + "_" + System::GetRunningSerial() + "_.txt"; - std::string filename = EmuFolders::Dumps + path; - - std::unique_ptr stream = - ByteStream::OpenFile(filename.c_str(), BYTESTREAM_OPEN_CREATE | BYTESTREAM_OPEN_WRITE | BYTESTREAM_OPEN_TRUNCATE | - BYTESTREAM_OPEN_ATOMIC_UPDATE | BYTESTREAM_OPEN_STREAMED); - if (!stream) - { - Log_VerbosePrint("desync log creation failed to create stream"); - return; - } - - if (!ByteStream::WriteBinaryToStream(stream.get(), - s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(), - s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize())) - { - Log_VerbosePrint("desync log creation failed to write the stream"); - stream->Discard(); - return; - } - /* stream->Write(s_save_buffer_pool.back().get()->state_stream.get()->GetMemoryPointer(), - s_save_buffer_pool.back().get()->state_stream.get()->GetMemorySize());*/ - - stream->Commit(); - - Log_VerbosePrintf("desync log created for frame %d", desync_frame); -} - void Netplay::AdvanceFrame() { ggpo_advance_frame(s_ggpo, 0); @@ -1178,19 +1243,13 @@ void Netplay::AdvanceFrame() void Netplay::RunFrame() { + PollEnet(0); + + if (!s_ggpo) + return; // housekeeping - // TODO: get rid of double polling - PollEnet(0); - if (!s_ggpo) - return; - ggpo_network_idle(s_ggpo); - PollEnet(0); - if (!s_ggpo) - return; - ggpo_idle(s_ggpo); - // run game auto result = GGPO_OK; int disconnect_flags = 0; @@ -1238,7 +1297,36 @@ Netplay::Input Netplay::ReadLocalInput() return inp; } -void Netplay::SendMsg(const char* msg) {} +void Netplay::SendMsg(std::string msg) +{ + SessionChatMessage header{}; + const size_t msg_size = msg.size(); + + header.header.type = SessionMessage::ChatMessage; + header.header.size = sizeof(SessionChatMessage) + msg_size; + header.chat_message_size = msg_size; + + ENetPacket* pkt = enet_packet_create(nullptr, sizeof(header) + msg_size, ENET_PACKET_FLAG_RELIABLE); + std::memcpy(pkt->data, &header, sizeof(header)); + std::memcpy(pkt->data + sizeof(header), msg.c_str(), msg_size); + + for (s32 i = 0; i < MAX_PLAYERS; i++) + { + if (!s_peers[i].peer) + continue; + + const int err = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_SESSION, pkt); + if (err != 0) + { + // failed to send netplay message? just clean it up. + Log_ErrorPrint("Failed to send netplay message"); + enet_packet_destroy(pkt); + } + } + + // add own netplay message locally to netplay messages + Host::OnNetplayMessage(fmt::format("Player {}: {}", s_local_handle, msg)); +} GGPOErrorCode Netplay::SyncInput(Netplay::Input inputs[2], int* disconnect_flags) { @@ -1377,8 +1465,7 @@ bool Netplay::NpAdvFrameCb(void* ctx, int flags) bool Netplay::NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame) { SaveStateBuffer our_buffer; - // min size is 2 because otherwise the desync logger doesnt have enough time to dump the state. - if (s_save_buffer_pool.size() < 2) + if (s_save_buffer_pool.empty()) { our_buffer = std::make_unique(); } @@ -1465,7 +1552,6 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) CurrentFrame(), ev->u.desync.nFrameOfDesync, CurrentFrame() - ev->u.desync.nFrameOfDesync, ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum)); - GenerateDesyncReport(ev->u.desync.nFrameOfDesync); break; default: Host::OnNetplayMessage(fmt::format("Netplay Event Code: {}", static_cast(ev->code))); diff --git a/src/core/netplay.h b/src/core/netplay.h index 7456a748b..513ead53c 100644 --- a/src/core/netplay.h +++ b/src/core/netplay.h @@ -8,7 +8,7 @@ namespace Netplay { enum : s32 { // Maximum number of emulated controllers. - MAX_PLAYERS = 2, + MAX_PLAYERS = 2, // Maximum netplay prediction frames MAX_ROLLBACK_FRAMES = 8, }; @@ -17,6 +17,7 @@ enum : u8 { ENET_CHANNEL_CONTROL = 0, ENET_CHANNEL_GGPO = 1, + ENET_CHANNEL_SESSION = 2, NUM_ENET_CHANNELS, }; @@ -36,7 +37,7 @@ void ExecuteNetplay(); void CollectInput(u32 slot, u32 bind, float value); -void SendMsg(const char* msg); +void SendMsg(std::string msg); s32 GetPing(); u32 GetMaxPrediction(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 9570a48a9..b8aab71f0 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1079,12 +1079,6 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const Q_ARG(quint16, remote_port), Q_ARG(int, input_delay), Q_ARG(const QString&, game_path)); return; } - // disable block linking and disable rewind and runahead during a netplay session - g_settings.cpu_recompiler_block_linking = false; - g_settings.rewind_enable = false; - g_settings.runahead_frames = 0; - - Log_WarningPrintf("Disabling block linking, runahead and rewind due to rollback."); auto remAddr = remote_addr.trimmed().toStdString(); auto gamePath = game_path.trimmed().toStdString(); @@ -1101,7 +1095,8 @@ void EmuThread::sendNetplayMessage(const QString& message) QMetaObject::invokeMethod(this, "sendNetplayMessage", Qt::QueuedConnection, Q_ARG(const QString&, message)); return; } - Netplay::SendMsg(message.toStdString().c_str()); + // TODO REDO NETPLAY UI + // Netplay::SendMsg(message.toStdString().c_str()); } void EmuThread::stopNetplaySession() diff --git a/src/frontend-common/imgui_netplay.cpp b/src/frontend-common/imgui_netplay.cpp index 8560ee2ab..09a4d7ce2 100644 --- a/src/frontend-common/imgui_netplay.cpp +++ b/src/frontend-common/imgui_netplay.cpp @@ -175,8 +175,10 @@ void ImGuiManager::DrawNetplayChatDialog() const bool close_chat = send_message || (s_netplay_chat_message.empty() && (ImGui::IsKeyPressed(ImGuiKey_Backspace)) || ImGui::IsKeyPressed(ImGuiKey_Escape)); + + // sending netplay message if (send_message && !s_netplay_chat_message.empty()) - Netplay::SendMsg(s_netplay_chat_message.c_str()); + Netplay::SendMsg(s_netplay_chat_message); const ImGuiIO& io = ImGui::GetIO(); const ImGuiStyle& style = ImGui::GetStyle();