Netplay / Spectating: at resume session now. i have to now look at how to add them to the ggpo session.

This commit is contained in:
Jamie Meyer 2023-05-31 15:08:23 +02:00
parent a346e2b0e0
commit 632f310726
No known key found for this signature in database
GPG Key ID: BF30D71B2F1305C7
5 changed files with 156 additions and 40 deletions

View File

@ -21,13 +21,13 @@ SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks* cb, int num_players, in
* Initialize the UDP port
*/
// FIXME
abort();
//abort();
//_udp.Init(localport, &_poll, this);
/*
* Init the host endpoint
*/
//_host.Init(&_udp, _poll, 0, hostip, hostport, NULL);
_host.Init(peer, 0, NULL);
_host.Synchronize();
}

View File

@ -90,6 +90,12 @@ static void ShowChatMessage(s32 player_id, const std::string_view& message);
static void RequestReset(ResetRequestMessage::Reason reason, s32 causing_player_id = 0);
static void SendConnectRequest();
// Spectators
static bool IsSpectator(const ENetPeer* peer);
static s32 GetFreeSpectatorSlot();
static s32 GetSpectatorSlotForPeer(const ENetPeer* peer);
static void DropSpectator(s32 slot_id, DropPlayerReason reason);
// Controlpackets
static void HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt);
static void HandleControlMessage(s32 player_id, const ENetPacket* pkt);
@ -161,6 +167,8 @@ static Common::Timer s_last_host_connection_attempt;
// Spectators
static std::array<Peer, MAX_SPECTATORS> s_spectators;
static std::bitset<MAX_SPECTATORS> s_reset_spectators;
static s32 s_num_spectators = 0;
static bool s_local_spectating;
/// GGPO
@ -244,24 +252,14 @@ static bool SendControlPacket(s32 player_id, const PacketWrapper<T>& pkt)
template<typename T>
static void SendControlPacketToAll(const PacketWrapper<T>& pkt)
{
for (s32 i = 0; i < MAX_PLAYERS; i++)
for (s32 i = 0; i < MAX_PLAYERS + MAX_SPECTATORS; i++)
{
if (!s_peers[i].peer)
ENetPeer* peer_to_send = i < MAX_PLAYERS ? s_peers[i].peer : s_spectators[i - MAX_PLAYERS].peer;
if (!peer_to_send)
continue;
// last one?
bool last = true;
for (s32 j = i + 1; j < MAX_PLAYERS; j++)
{
if (s_peers[j].peer)
{
last = false;
break;
}
}
ENetPacket* pkt_to_send = last ? pkt.pkt : enet_packet_create(pkt.pkt->data, pkt.pkt->dataLength, pkt.pkt->flags);
const int rc = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt_to_send);
ENetPacket* pkt_to_send = enet_packet_create(pkt.pkt->data, pkt.pkt->dataLength, pkt.pkt->flags);
const int rc = enet_peer_send(peer_to_send, ENET_CHANNEL_CONTROL, pkt_to_send);
if (rc != 0)
{
Log_ErrorPrintf("enet_peer_send() to player %d failed: %d", i, rc);
@ -343,7 +341,7 @@ bool Netplay::Start(bool is_hosting, std::string nickname, const std::string& re
ENetAddress server_address;
server_address.host = ENET_HOST_ANY;
server_address.port = is_hosting ? static_cast<u16>(port) : ENET_PORT_ANY;
s_enet_host = enet_host_create(&server_address, MAX_PLAYERS - 1, NUM_ENET_CHANNELS, 0, 0);
s_enet_host = enet_host_create(&server_address, MAX_PLAYERS + MAX_SPECTATORS - 1, NUM_ENET_CHANNELS, 0, 0);
if (!s_enet_host)
{
Log_ErrorPrintf("Failed to create enet host.");
@ -603,8 +601,10 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
case ENET_EVENT_TYPE_RECEIVE:
{
const s32 player_id = GetPlayerIdForPeer(event->peer);
if (player_id < 0)
s32 player_id = GetPlayerIdForPeer(event->peer);
const s32 spectator_slot = GetSpectatorSlotForPeer(event->peer);
if (player_id < 0 && spectator_slot < 0)
{
// If it's a new connection, we need to handle connection request messages.
if (event->channelID == ENET_CHANNEL_CONTROL && IsHost())
@ -615,6 +615,9 @@ void Netplay::HandleEnetEvent(const ENetEvent* event)
if (event->channelID == ENET_CHANNEL_CONTROL)
{
if (player_id < 0)
player_id = spectator_slot + MAX_PLAYERS + 1;
HandleControlMessage(player_id, event->packet);
}
else if (event->channelID == ENET_CHANNEL_GGPO)
@ -740,6 +743,12 @@ void Netplay::CreateGGPOSession()
cb.free_buffer = NpFreeBuffCb;
cb.on_event = NpOnEventCb;
if (s_local_spectating)
{
ggpo_start_spectating(&s_ggpo, &cb, s_num_players, sizeof(Netplay::Input), s_peers[s_host_player_id].peer);
return;
}
ggpo_start_session(&s_ggpo, &cb, s_num_players, sizeof(Netplay::Input), MAX_ROLLBACK_FRAMES);
int player_num = 1;
@ -894,7 +903,8 @@ void Netplay::HandleConnectResponseMessage(s32 player_id, const ENetPacket* pkt)
Log_InfoPrintf("Received session details from host: ");
Log_InfoPrintf(" Console Region: %s", Settings::GetConsoleRegionDisplayName(msg->settings.console_region));
Log_InfoPrintf(" BIOS Hash: %s%s", msg->bios_hash.ToString().c_str(), msg->settings.was_fast_booted ? " (fast booted)" : "");
Log_InfoPrintf(" BIOS Hash: %s%s", msg->bios_hash.ToString().c_str(),
msg->settings.was_fast_booted ? " (fast booted)" : "");
Log_InfoPrintf(" Game Serial: %.*s", msg->game_serial_length, msg->GetGameSerial().data());
Log_InfoPrintf(" Game Title: %.*s", msg->game_title_length, msg->GetGameTitle().data());
Log_InfoPrintf(" Game Hash: %" PRIX64, msg->game_hash);
@ -951,10 +961,11 @@ void Netplay::SendConnectRequest()
{
DebugAssert(!IsHost());
Log_DevPrintf("Sending connect request to host with player id %d", s_player_id);
std::string req = s_local_spectating ? "as a spectator" : fmt::format("with player id {}", s_player_id);
Log_DevPrintf("Sending connect request to host %s", req.c_str());
auto pkt = NewControlPacket<JoinRequestMessage>();
pkt->mode = JoinRequestMessage::Mode::Player;
pkt->mode = s_local_spectating ? JoinRequestMessage::Mode::Spectator : JoinRequestMessage::Mode::Player;
pkt->requested_player_id = s_player_id;
std::memset(pkt->nickname, 0, sizeof(pkt->nickname));
std::memset(pkt->session_password, 0, sizeof(pkt->session_password));
@ -963,6 +974,50 @@ void Netplay::SendConnectRequest()
SendControlPacket(s_peers[s_host_player_id].peer, pkt);
}
bool Netplay::IsSpectator(const ENetPeer* peer)
{
for (s32 i = 0; i < MAX_SPECTATORS; i++)
{
if (s_spectators[i].peer == peer)
return true;
}
return false;
}
s32 Netplay::GetFreeSpectatorSlot()
{
for (s32 i = 0; i < MAX_SPECTATORS; i++)
{
if (!s_spectators[i].peer)
return i;
}
return -1;
}
s32 Netplay::GetSpectatorSlotForPeer(const ENetPeer* peer)
{
for (s32 i = 0; i < MAX_SPECTATORS; i++)
{
if (s_spectators[i].peer == peer)
return i;
}
return -1;
}
void Netplay::DropSpectator(s32 slot_id, DropPlayerReason reason)
{
Assert(IsHost());
DebugAssert(s_spectators[slot_id].peer);
Log_InfoPrintf("Dropping Spectator %d: %s", slot_id, s_spectators[slot_id].nickname.c_str());
Host::OnNetplayMessage(fmt::format(Host::TranslateString("Netplay", "{} left the session: {}").GetCharArray(),
s_spectators[slot_id].nickname, DropPlayerReasonToString(reason)));
enet_peer_disconnect_now(s_spectators[slot_id].peer, 0);
s_spectators[slot_id] = {};
s_num_spectators--;
}
void Netplay::UpdateConnectingState()
{
if (s_reset_start_time.GetTimeSeconds() >= MAX_CONNECT_TIME)
@ -1015,11 +1070,34 @@ void Netplay::HandleMessageFromNewPeer(ENetPeer* peer, const ENetPacket* pkt)
return;
}
// TODO: Spectators shouldn't get assigned a real player ID, they should go into a separate peer list.
if (msg->mode != JoinRequestMessage::Mode::Player)
if (msg->mode == JoinRequestMessage::Mode::Spectator)
{
response->result = JoinResponseMessage::Result::SessionClosed;
// something is really wrong if this isn't the host.
Assert(IsHost());
const s32 spectator_slot = GetFreeSpectatorSlot();
// no free slots? notify the peer.
if (spectator_slot < 0)
{
response->result = JoinResponseMessage::Result::ServerFull;
SendControlPacket(peer, response);
return;
}
Assert(s_num_spectators < MAX_SPECTATORS);
response->result = JoinResponseMessage::Result::Success;
response->player_id = MAX_PLAYERS + 1 + spectator_slot;
SendControlPacket(peer, response);
// continue and add peer to the list.
s_spectators[spectator_slot].peer = peer;
s_spectators[spectator_slot].nickname = msg->GetNickname();
s_num_spectators++;
// Force everyone to resync for now. sadly since ggpo currently only supports adding spectators during setup..
Reset();
// notify host that the spectator joined
Host::OnNetplayMessage(
fmt::format(Host::TranslateString("Netplay", "{} is joining the session as a Spectator.").GetCharArray(),
msg->GetNickname()));
return;
}
@ -1127,6 +1205,7 @@ void Netplay::HandleJoinResponseMessage(s32 player_id, const ENetPacket* pkt)
s_player_id = msg->player_id;
s_state = SessionState::Resetting;
s_reset_players.reset();
s_reset_spectators.reset();
s_reset_start_time.Reset();
}
@ -1201,9 +1280,10 @@ void Netplay::Reset()
// Any GGPO packets will get dropped, since the session's gone temporarily.
DestroyGGPOSession();
for (s32 i = 0; i < MAX_PLAYERS; i++)
for (s32 i = 0; i < MAX_PLAYERS + MAX_SPECTATORS; i++)
{
if (!s_peers[i].peer)
ENetPeer* peer_to_send = i < MAX_PLAYERS ? s_peers[i].peer : s_spectators[i - MAX_PLAYERS].peer;
if (!peer_to_send)
continue;
ENetPacket* pkt = enet_packet_create(nullptr, sizeof(header) + state_data_size, ENET_PACKET_FLAG_RELIABLE);
@ -1211,7 +1291,7 @@ void Netplay::Reset()
std::memcpy(pkt->data + sizeof(header), state.GetMemoryPointer(), state_data_size);
// This should never fail, we get errors back later..
const int rc = enet_peer_send(s_peers[i].peer, ENET_CHANNEL_CONTROL, pkt);
const int rc = enet_peer_send(peer_to_send, ENET_CHANNEL_CONTROL, pkt);
if (rc != 0)
Log_ErrorPrintf("enet_peer_send() for synchronization request failed: %d", rc);
}
@ -1226,6 +1306,7 @@ void Netplay::Reset()
s_state = SessionState::Resetting;
s_reset_players.reset();
s_reset_spectators.reset();
s_reset_players.set(s_player_id);
s_reset_start_time.Reset();
}
@ -1250,7 +1331,7 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
DestroyGGPOSession();
// Make sure we're connected to all players.
Assert(msg->num_players > 1);
Assert(msg->num_players > 1 || s_local_spectating);
Log_DevPrintf("Checking connections");
s_num_players = msg->num_players;
for (s32 i = 0; i < MAX_PLAYERS; i++)
@ -1318,6 +1399,15 @@ void Netplay::HandleResetMessage(s32 player_id, const ENetPacket* pkt)
s_state = SessionState::Resetting;
s_reset_cookie = msg->cookie;
s_reset_players.reset();
s_reset_spectators.reset();
if (s_local_spectating)
{
s_reset_spectators.set(s_player_id - (MAX_PLAYERS + 1));
s_reset_start_time.Reset();
return;
}
s_reset_players.set(s_player_id);
s_reset_start_time.Reset();
}
@ -1333,6 +1423,13 @@ void Netplay::HandleResetCompleteMessage(s32 player_id, const ENetPacket* pkt)
Log_ErrorPrintf("Received unexpected reset complete from player %d", player_id);
return;
}
else if (player_id > MAX_PLAYERS && player_id <= MAX_PLAYERS + MAX_SPECTATORS)
{
const s32 spectator_slot = player_id - (MAX_PLAYERS + 1);
s_reset_spectators.set(spectator_slot);
Log_DevPrintf("Spectator %d is now reset and ready", spectator_slot);
return;
}
else if (s_reset_players.test(player_id))
{
Log_ErrorPrintf("Received double reset from player %d", player_id);
@ -1369,9 +1466,10 @@ void Netplay::UpdateResetState()
{
if (IsHost())
{
if (static_cast<s32>(s_reset_players.count()) == s_num_players)
if (static_cast<s32>(s_reset_players.count()) == s_num_players &&
static_cast<s32>(s_reset_spectators.count()) == s_num_spectators)
{
Log_VerbosePrintf("All players synchronized, resuming!");
Log_VerbosePrintf("All players and spectators synchronized, resuming!");
SendControlPacketToAll(NewControlPacket<ResumeSessionMessage>());
CreateGGPOSession();
s_state = SessionState::Running;
@ -1392,6 +1490,16 @@ void Netplay::UpdateResetState()
Log_DevPrintf("Dropping player %d because they didn't connect in time", i);
DropPlayer(i, DropPlayerReason::ConnectTimeout);
}
for (s32 i = 0; i < MAX_SPECTATORS; i++)
{
if (s_reset_spectators.test(i))
continue;
// we'll check if we're done again next loop
Log_DevPrintf("Dropping Spectator %d because they didn't connect in time", i);
DropSpectator(i, DropPlayerReason::ConnectTimeout);
}
}
}
else
@ -1425,8 +1533,14 @@ void Netplay::UpdateResetState()
}
}
Log_InfoPrintf("p:%d/s:%d", s_num_players, s_num_spectators);
const s32 min_progress = IsHost() ? static_cast<int>(s_reset_players.count() + s_reset_spectators.count()) :
static_cast<int>(s_reset_players.count());
const s32 max_progress = IsHost() ? s_num_players + s_num_spectators : s_num_players;
PollEnet(Common::Timer::GetCurrentValue() + Common::Timer::ConvertMillisecondsToValue(16));
Host::DisplayLoadingScreen("Netplay synchronizing", 0, static_cast<int>(s_reset_players.count()), s_num_players);
Host::DisplayLoadingScreen("Netplay synchronizing", 0, min_progress, max_progress);
Host::PumpMessagesOnCPUThread();
}
@ -1875,12 +1989,12 @@ void Netplay::SetInputs(Netplay::Input inputs[2])
bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std::string password)
{
s_local_session_password = password;
s_local_session_password = std::move(password);
// TODO: This is going to blow away our memory cards, because for sync purposes we want all clients
// to have the same data, and we don't want to trash their local memcards. We should therefore load
// the memory cards for this game (based on game/global settings), and copy that to the temp card.
// TODO: input delay. GGPO Should support changing it on the fly.
const s32 input_delay = 1;
@ -1898,9 +2012,10 @@ bool Netplay::CreateSession(std::string nickname, s32 port, s32 max_players, std
return true;
}
bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password, bool spectating)
bool Netplay::JoinSession(std::string nickname, const std::string& hostname, s32 port, std::string password,
bool spectating)
{
s_local_session_password = password;
s_local_session_password = std::move(password);
s_local_spectating = spectating;
// TODO: input delay. GGPO Should support changing it on the fly.

View File

@ -85,7 +85,7 @@ void JoinNetplaySessionDialog::accept()
const bool spectating = m_ui.spectating->isChecked();
QDialog::accept();
g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password);
g_emu_thread->joinNetplaySession(nickname.trimmed(), hostname.trimmed(), port, password, spectating);
}
bool JoinNetplaySessionDialog::validate()

View File

@ -2247,7 +2247,7 @@ int main(int argc, char* argv[])
}
else
{
g_emu_thread->joinNetplaySession(nickname, remote, port, QString());
g_emu_thread->joinNetplaySession(nickname, remote, port, QString(), false);
}
});
}

View File

@ -188,7 +188,8 @@ public Q_SLOTS:
void applyCheat(quint32 index);
void reloadPostProcessingShaders();
void createNetplaySession(const QString& nickname, qint32 port, qint32 max_players, const QString& password);
void joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, bool spectating);
void joinNetplaySession(const QString& nickname, const QString& hostname, qint32 port, const QString& password,
bool spectating);
private Q_SLOTS:
void stopInThread();