Netplay: Improve disconnection handling

This commit is contained in:
Stenzek 2023-05-11 00:07:18 +10:00
parent 5de071900d
commit a7e787456c
7 changed files with 908 additions and 334 deletions

View File

@ -27,3 +27,6 @@
#if defined(DeleteFile)
#undef DeleteFile
#endif
#if defined(GetMessage)
#undef GetMessage
#endif

File diff suppressed because it is too large Load Diff

View File

@ -9,8 +9,15 @@ enum : s32
{
// Maximum number of emulated controllers.
MAX_PLAYERS = 2,
// Maximum netplay prediction frames
MAX_ROLLBACK_FRAMES = 8,
// Maximum length of a nickname
MAX_NICKNAME_LENGTH = 128,
// Maximum name of password for session
MAX_SESSION_PASSWORD_LENGTH = 128,
};
enum : u8
@ -29,14 +36,14 @@ bool IsActive();
/// Frees up resources associated with the current netplay session.
/// Should only be called by System::ShutdownSystem().
void CloseSession();
void SystemDestroyed();
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
void ExecuteNetplay();
void CollectInput(u32 slot, u32 bind, float value);
void SendMsg(std::string msg);
void SendChatMessage(const std::string_view& msg);
s32 GetPing();
u32 GetMaxPrediction();

241
src/core/netplay_packets.h Normal file
View File

@ -0,0 +1,241 @@
// SPDX-FileCopyrightText: 2023 Connor McLaughlin <stenzek@gmail.com> and contributors.
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#pragma once
#include "host.h"
#include "types.h"
#include "fmt/format.h"
namespace Netplay {
enum class SessionState
{
Inactive,
Initializing,
Connecting,
Resetting,
Running,
ClosingSession,
};
enum class ControlMessage : u32
{
// host->player
ConnectResponse,
Reset,
ResumeSession,
PlayerJoined,
DropPlayer,
CloseSession,
// player->host
ConnectRequest,
ResetComplete,
ResetRequest,
// bi-directional
SetNickname,
ChatMessage,
};
enum class DropPlayerReason : u32
{
ConnectTimeout,
DisconnectedFromHost,
};
#pragma pack(push, 1)
struct ControlMessageHeader
{
ControlMessage type;
u32 size;
};
struct ConnectRequestMessage
{
enum class Mode
{
Player,
Spectator,
};
ControlMessageHeader header;
Mode mode;
s32 requested_player_id;
char nickname[MAX_NICKNAME_LENGTH];
char session_password[MAX_SESSION_PASSWORD_LENGTH];
std::string_view GetNickname() const
{
const size_t len = strnlen(nickname, std::size(nickname));
return std::string_view(nickname, len);
}
std::string_view GetSessionPassword() const
{
const size_t len = strnlen(session_password, std::size(session_password));
return std::string_view(session_password, len);
}
static ControlMessage MessageType() { return ControlMessage::ConnectRequest; }
};
struct ConnectResponseMessage
{
enum class Result : u32
{
Success = 0,
ServerFull,
PlayerIDInUse,
SessionClosed,
};
ControlMessageHeader header;
Result result;
s32 player_id;
static ControlMessage MessageType() { return ControlMessage::ConnectResponse; }
};
struct ResetMessage
{
struct PlayerAddress
{
u32 host;
u16 port;
s16 controller_port; // -1 if not present
char nickname[MAX_NICKNAME_LENGTH];
std::string_view GetNickname() const
{
const size_t len = strnlen(nickname, std::size(nickname));
return std::string_view(nickname, len);
}
};
ControlMessageHeader header;
u32 cookie;
s32 num_players;
PlayerAddress players[MAX_PLAYERS];
u32 state_data_size;
// state_data_size bytes of state data follows
static ControlMessage MessageType() { return ControlMessage::Reset; }
};
struct ResetCompleteMessage
{
ControlMessageHeader header;
u32 cookie;
static ControlMessage MessageType() { return ControlMessage::ResetComplete; }
};
struct ResumeSessionMessage
{
ControlMessageHeader header;
static ControlMessage MessageType() { return ControlMessage::ResumeSession; }
};
struct PlayerJoinedMessage
{
ControlMessageHeader header;
s32 player_id;
static ControlMessage MessageType() { return ControlMessage::PlayerJoined; }
};
struct DropPlayerMessage
{
ControlMessageHeader header;
DropPlayerReason reason;
s32 player_id;
static ControlMessage MessageType() { return ControlMessage::DropPlayer; }
};
struct ResetRequestMessage
{
enum class Reason : u32
{
ConnectionLost,
};
ControlMessageHeader header;
Reason reason;
s32 causing_player_id;
std::string ReasonToString() const
{
switch (reason)
{
case Reason::ConnectionLost:
return fmt::format(Host::TranslateString("Netplay", "Connection lost to player {}.").GetCharArray(),
causing_player_id);
default:
return "Unknown";
}
}
static ControlMessage MessageType() { return ControlMessage::ResetRequest; }
};
struct CloseSessionMessage
{
enum class Reason : u32
{
HostRequest,
HostShutdown,
};
ControlMessageHeader header;
Reason reason;
std::string ReasonToString() const
{
switch (reason)
{
case Reason::HostRequest:
return Host::TranslateStdString("Netplay", "Session closed due to host request.");
case Reason::HostShutdown:
return Host::TranslateStdString("Netplay", "Session closed due to host shutdown.");
default:
return "Unknown";
}
}
static ControlMessage MessageType() { return ControlMessage::CloseSession; }
};
struct SetNicknameMessage
{
ControlMessageHeader header;
static ControlMessage MessageType() { return ControlMessage::SetNickname; }
};
struct ChatMessage
{
ControlMessageHeader header;
std::string_view GetMessage() const
{
return (header.size > sizeof(ChatMessage)) ?
std::string_view(reinterpret_cast<const char*>(this) + sizeof(ChatMessage),
header.size - sizeof(ChatMessage)) :
std::string_view();
}
static ControlMessage MessageType() { return ControlMessage::ChatMessage; }
};
#pragma pack(pop)
} // namespace Netplay

View File

@ -3757,7 +3757,7 @@ void System::ShutdownSystem(bool save_resume_state)
return;
if (Netplay::IsActive())
Netplay::CloseSession();
Netplay::SystemDestroyed();
if (save_resume_state)
SaveResumeState();

View File

@ -2227,6 +2227,9 @@ int main(int argc, char* argv[])
{
Host::RunOnCPUThread([]() {
const bool first = (s_netplay_test == 0);
if (!first)
QtHost::RunOnUIThread([]() { g_main_window->move(g_main_window->pos() + QPoint(500, 0)); });
const int h = first ? 1 : 2;
const int nh = first ? 2 : 1;
const int port_base = 31200;

View File

@ -97,7 +97,7 @@ void ImGuiManager::DrawNetplayMessages()
const float spacing = 5.0f * scale;
const float msg_spacing = 2.0f * scale;
ImFont* font = ImGuiManager::GetFixedFont();
float position_y = io.DisplaySize.y - margin - ((spacing + font->FontSize) * 2.0f) - font->FontSize;
float position_y = io.DisplaySize.y - margin - (100.0f * scale) - font->FontSize - spacing;
ImDrawList* dl = ImGui::GetBackgroundDrawList();
// drop expired messages.. because of the reverse iteration below, we can't do it in there :/
@ -121,11 +121,9 @@ void ImGuiManager::DrawNetplayMessages()
const char* text_end = text_start + iter->first.length();
const ImVec2 text_size = font->CalcTextSizeA(font->FontSize, io.DisplaySize.x, 0.0f, text_start, text_end, nullptr);
dl->AddText(font, font->FontSize,
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset),
IM_COL32(0, 0, 0, shadow_alpha), text_start, text_end);
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y),
IM_COL32(255, 255, 255, alpha), text_start, text_end);
dl->AddText(font, font->FontSize, ImVec2(margin, position_y), IM_COL32(255, 255, 255, alpha), text_start, text_end);
position_y -= text_size.y + msg_spacing;
}
@ -144,16 +142,13 @@ void ImGuiManager::DrawNetplayStats()
const float margin = 10.0f * scale;
const float spacing = 5.0f * scale;
ImFont* font = ImGuiManager::GetFixedFont();
const float position_y = ImGui::GetIO().DisplaySize.y - margin - font->FontSize - spacing - font->FontSize;
const float position_y = ImGui::GetIO().DisplaySize.y - margin - (100.0f * scale);
ImDrawList* dl = ImGui::GetBackgroundDrawList();
ImVec2 text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, text,
text.GetCharArray() + text.GetLength(), nullptr);
dl->AddText(font, font->FontSize,
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
IM_COL32(0, 0, 0, 100), text, text.GetCharArray() + text.GetLength());
dl->AddText(font, font->FontSize, ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x, position_y),
IM_COL32(255, 255, 255, 255), text, text.GetCharArray() + text.GetLength());
dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset), IM_COL32(0, 0, 0, 100),
text, text.GetCharArray() + text.GetLength());
dl->AddText(font, font->FontSize, ImVec2(margin, position_y), IM_COL32(255, 255, 255, 255), text,
text.GetCharArray() + text.GetLength());
}
void ImGuiManager::DrawNetplayChatDialog()
@ -178,7 +173,7 @@ void ImGuiManager::DrawNetplayChatDialog()
// sending netplay message
if (send_message && !s_netplay_chat_message.empty())
Netplay::SendMsg(s_netplay_chat_message);
Netplay::SendChatMessage(s_netplay_chat_message);
const ImGuiIO& io = ImGui::GetIO();
const ImGuiStyle& style = ImGui::GetStyle();