Netplay: Improve disconnection handling
This commit is contained in:
parent
5de071900d
commit
a7e787456c
|
@ -27,3 +27,6 @@
|
||||||
#if defined(DeleteFile)
|
#if defined(DeleteFile)
|
||||||
#undef DeleteFile
|
#undef DeleteFile
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(GetMessage)
|
||||||
|
#undef GetMessage
|
||||||
|
#endif
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,8 +9,15 @@ enum : s32
|
||||||
{
|
{
|
||||||
// Maximum number of emulated controllers.
|
// Maximum number of emulated controllers.
|
||||||
MAX_PLAYERS = 2,
|
MAX_PLAYERS = 2,
|
||||||
|
|
||||||
// Maximum netplay prediction frames
|
// Maximum netplay prediction frames
|
||||||
MAX_ROLLBACK_FRAMES = 8,
|
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
|
enum : u8
|
||||||
|
@ -29,14 +36,14 @@ bool IsActive();
|
||||||
|
|
||||||
/// Frees up resources associated with the current netplay session.
|
/// Frees up resources associated with the current netplay session.
|
||||||
/// Should only be called by System::ShutdownSystem().
|
/// 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.
|
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
|
||||||
void ExecuteNetplay();
|
void ExecuteNetplay();
|
||||||
|
|
||||||
void CollectInput(u32 slot, u32 bind, float value);
|
void CollectInput(u32 slot, u32 bind, float value);
|
||||||
|
|
||||||
void SendMsg(std::string msg);
|
void SendChatMessage(const std::string_view& msg);
|
||||||
|
|
||||||
s32 GetPing();
|
s32 GetPing();
|
||||||
u32 GetMaxPrediction();
|
u32 GetMaxPrediction();
|
||||||
|
|
|
@ -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
|
|
@ -3757,7 +3757,7 @@ void System::ShutdownSystem(bool save_resume_state)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (Netplay::IsActive())
|
if (Netplay::IsActive())
|
||||||
Netplay::CloseSession();
|
Netplay::SystemDestroyed();
|
||||||
|
|
||||||
if (save_resume_state)
|
if (save_resume_state)
|
||||||
SaveResumeState();
|
SaveResumeState();
|
||||||
|
|
|
@ -2227,6 +2227,9 @@ int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
Host::RunOnCPUThread([]() {
|
Host::RunOnCPUThread([]() {
|
||||||
const bool first = (s_netplay_test == 0);
|
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 h = first ? 1 : 2;
|
||||||
const int nh = first ? 2 : 1;
|
const int nh = first ? 2 : 1;
|
||||||
const int port_base = 31200;
|
const int port_base = 31200;
|
||||||
|
|
|
@ -97,7 +97,7 @@ void ImGuiManager::DrawNetplayMessages()
|
||||||
const float spacing = 5.0f * scale;
|
const float spacing = 5.0f * scale;
|
||||||
const float msg_spacing = 2.0f * scale;
|
const float msg_spacing = 2.0f * scale;
|
||||||
ImFont* font = ImGuiManager::GetFixedFont();
|
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();
|
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||||
|
|
||||||
// drop expired messages.. because of the reverse iteration below, we can't do it in there :/
|
// 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 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);
|
const ImVec2 text_size = font->CalcTextSizeA(font->FontSize, io.DisplaySize.x, 0.0f, text_start, text_end, nullptr);
|
||||||
|
|
||||||
dl->AddText(font, font->FontSize,
|
dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset),
|
||||||
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
|
|
||||||
IM_COL32(0, 0, 0, shadow_alpha), text_start, text_end);
|
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),
|
dl->AddText(font, font->FontSize, ImVec2(margin, position_y), IM_COL32(255, 255, 255, alpha), text_start, text_end);
|
||||||
IM_COL32(255, 255, 255, alpha), text_start, text_end);
|
|
||||||
|
|
||||||
position_y -= text_size.y + msg_spacing;
|
position_y -= text_size.y + msg_spacing;
|
||||||
}
|
}
|
||||||
|
@ -144,16 +142,13 @@ void ImGuiManager::DrawNetplayStats()
|
||||||
const float margin = 10.0f * scale;
|
const float margin = 10.0f * scale;
|
||||||
const float spacing = 5.0f * scale;
|
const float spacing = 5.0f * scale;
|
||||||
ImFont* font = ImGuiManager::GetFixedFont();
|
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();
|
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||||
ImVec2 text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, text,
|
dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset), IM_COL32(0, 0, 0, 100),
|
||||||
text.GetCharArray() + text.GetLength(), nullptr);
|
text, text.GetCharArray() + text.GetLength());
|
||||||
dl->AddText(font, font->FontSize,
|
dl->AddText(font, font->FontSize, ImVec2(margin, position_y), IM_COL32(255, 255, 255, 255), text,
|
||||||
ImVec2(ImGui::GetIO().DisplaySize.x - margin - text_size.x + shadow_offset, position_y + shadow_offset),
|
text.GetCharArray() + text.GetLength());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImGuiManager::DrawNetplayChatDialog()
|
void ImGuiManager::DrawNetplayChatDialog()
|
||||||
|
@ -178,7 +173,7 @@ void ImGuiManager::DrawNetplayChatDialog()
|
||||||
|
|
||||||
// sending netplay message
|
// sending netplay message
|
||||||
if (send_message && !s_netplay_chat_message.empty())
|
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 ImGuiIO& io = ImGui::GetIO();
|
||||||
const ImGuiStyle& style = ImGui::GetStyle();
|
const ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
|
Loading…
Reference in New Issue