diff --git a/Source/dvlnet/base.cpp b/Source/dvlnet/base.cpp index f75aed08a..7e3d1d4a5 100644 --- a/Source/dvlnet/base.cpp +++ b/Source/dvlnet/base.cpp @@ -41,7 +41,7 @@ void base::HandleAccept(packet &pkt) } if (pkt.Cookie() == cookie_self) { plr_self = pkt.NewPlayer(); - connected_table[plr_self] = true; + Connect(plr_self); } if (game_init_info != pkt.Info()) { if (pkt.Info().size() != sizeof(GameData)) { @@ -58,6 +58,48 @@ void base::HandleAccept(packet &pkt) } } +void base::HandleConnect(packet &pkt) +{ + plr_t newPlayer = pkt.NewPlayer(); + Connect(newPlayer); +} + +void base::HandleTurn(packet &pkt) +{ + plr_t src = pkt.Source(); + PlayerState &playerState = playerStateTable_[src]; + playerState.waitForTurns = true; + + std::deque &turnQueue = playerState.turnQueue; + const turn_t &turn = pkt.Turn(); + turnQueue.push_back(turn); + MakeReady(turn.SequenceNumber); +} + +void base::HandleDisconnect(packet &pkt) +{ + plr_t newPlayer = pkt.NewPlayer(); + if (newPlayer != plr_self) { + if (IsConnected(newPlayer)) { + auto leaveinfo = pkt.LeaveInfo(); + _SNETEVENT ev; + ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME; + ev.playerid = pkt.NewPlayer(); + ev.data = reinterpret_cast(&leaveinfo); + ev.databytes = sizeof(leaveinfo_t); + RunEventHandler(ev); + DisconnectNet(newPlayer); + ClearMsg(newPlayer); + PlayerState &playerState = playerStateTable_[newPlayer]; + playerState.isConnected = false; + playerState.waitForTurns = false; + playerState.turnQueue.clear(); + } + } else { + ABORT(); // we were dropped by the owner?!? + } +} + void base::ClearMsg(plr_t plr) { message_queue.erase(std::remove_if(message_queue.begin(), @@ -68,42 +110,38 @@ void base::ClearMsg(plr_t plr) message_queue.end()); } +void base::Connect(plr_t player) +{ + PlayerState &playerState = playerStateTable_[player]; + playerState.isConnected = true; +} + +bool base::IsConnected(plr_t player) const +{ + const PlayerState &playerState = playerStateTable_[player]; + return playerState.isConnected; +} + void base::RecvLocal(packet &pkt) { if (pkt.Source() < MAX_PLRS) { - connected_table[pkt.Source()] = true; + Connect(pkt.Source()); } switch (pkt.Type()) { case PT_MESSAGE: message_queue.emplace_back(pkt.Source(), pkt.Message()); break; case PT_TURN: - turn_queue[pkt.Source()].push_back(pkt.Turn()); + HandleTurn(pkt); break; case PT_JOIN_ACCEPT: HandleAccept(pkt); break; case PT_CONNECT: - connected_table[pkt.NewPlayer()] = true; // this can probably be removed + HandleConnect(pkt); break; case PT_DISCONNECT: - if (pkt.NewPlayer() != plr_self) { - if (connected_table[pkt.NewPlayer()]) { - auto leaveinfo = pkt.LeaveInfo(); - _SNETEVENT ev; - ev.eventid = EVENT_TYPE_PLAYER_LEAVE_GAME; - ev.playerid = pkt.NewPlayer(); - ev.data = reinterpret_cast(&leaveinfo); - ev.databytes = sizeof(leaveinfo_t); - RunEventHandler(ev); - connected_table[pkt.NewPlayer()] = false; - DisconnectNet(pkt.NewPlayer()); - ClearMsg(pkt.NewPlayer()); - turn_queue[pkt.NewPlayer()].clear(); - } - } else { - ABORT(); // we were dropped by the owner?!? - } + HandleDisconnect(pkt); break; default: break; @@ -145,53 +183,136 @@ bool base::SNetSendMessage(int playerId, void *data, unsigned int size) return true; } +bool base::AllTurnsArrived() +{ + for (auto i = 0; i < MAX_PLRS; ++i) { + PlayerState &playerState = playerStateTable_[i]; + if (!playerState.waitForTurns) + continue; + + std::deque &turnQueue = playerState.turnQueue; + if (turnQueue.empty()) { + LogDebug("Turn missing from player {}", i); + return false; + } + } + + return true; +} + bool base::SNetReceiveTurns(char **data, size_t *size, uint32_t *status) { poll(); - bool allTurnsArrived = true; + for (auto i = 0; i < MAX_PLRS; ++i) { status[i] = 0; - if (connected_table[i]) { - status[i] |= PS_CONNECTED; - if (turn_queue[i].empty()) - allTurnsArrived = false; + + PlayerState &playerState = playerStateTable_[i]; + if (!playerState.waitForTurns) + continue; + + status[i] |= PS_CONNECTED; + + std::deque &turnQueue = playerState.turnQueue; + while (!turnQueue.empty()) { + const turn_t &turn = turnQueue.front(); + seq_t diff = turn.SequenceNumber - next_turn; + if (diff <= 0x7F) + break; + turnQueue.pop_front(); } } - if (allTurnsArrived) { + + if (AllTurnsArrived()) { for (auto i = 0; i < MAX_PLRS; ++i) { - if (connected_table[i]) { - size[i] = sizeof(turn_t); - status[i] |= PS_ACTIVE; - status[i] |= PS_TURN_ARRIVED; - turn_last[i] = turn_queue[i].front(); - turn_queue[i].pop_front(); - data[i] = reinterpret_cast(&turn_last[i]); - } + PlayerState &playerState = playerStateTable_[i]; + if (!playerState.waitForTurns) + continue; + + std::deque &turnQueue = playerState.turnQueue; + if (turnQueue.empty()) + continue; + + const turn_t &turn = turnQueue.front(); + if (turn.SequenceNumber != next_turn) + continue; + + playerState.lastTurnValue = turn.Value; + turnQueue.pop_front(); + + size[i] = sizeof(int32_t); + status[i] |= PS_ACTIVE; + status[i] |= PS_TURN_ARRIVED; + data[i] = reinterpret_cast(&playerState.lastTurnValue); } + + next_turn++; + return true; } + for (auto i = 0; i < MAX_PLRS; ++i) { - if (connected_table[i]) { - if (!turn_queue[i].empty()) { - status[i] |= PS_ACTIVE; - } - } + PlayerState &playerState = playerStateTable_[i]; + if (!playerState.waitForTurns) + continue; + + std::deque &turnQueue = playerState.turnQueue; + if (turnQueue.empty()) + continue; + + status[i] |= PS_ACTIVE; } + return false; } bool base::SNetSendTurn(char *data, unsigned int size) { - if (size != sizeof(turn_t)) + if (size != sizeof(int32_t)) ABORT(); + turn_t turn; - std::memcpy(&turn, data, sizeof(turn)); - auto pkt = pktfty->make_packet(plr_self, PLR_BROADCAST, turn); - send(*pkt); - turn_queue[plr_self].push_back(pkt->Turn()); + turn.SequenceNumber = next_turn; + std::memcpy(&turn.Value, data, size); + + PlayerState &playerState = playerStateTable_[plr_self]; + std::deque &turnQueue = playerState.turnQueue; + turnQueue.push_back(turn); + SendTurnIfReady(turn); return true; } +void base::SendTurnIfReady(turn_t turn) +{ + PlayerState &playerState = playerStateTable_[plr_self]; + bool &ready = playerState.waitForTurns; + + if (!ready) + ready = IsGameHost(); + + if (ready) { + auto pkt = pktfty->make_packet(plr_self, PLR_BROADCAST, turn); + send(*pkt); + } +} + +void base::MakeReady(seq_t sequenceNumber) +{ + PlayerState &playerState = playerStateTable_[plr_self]; + if (playerState.waitForTurns) + return; + + next_turn = sequenceNumber; + playerState.waitForTurns = true; + + std::deque &turnQueue = playerState.turnQueue; + if (!turnQueue.empty()) { + turn_t &turn = turnQueue.front(); + turn.SequenceNumber = next_turn; + SendTurnIfReady(turn); + } +} + void base::SNetGetProviderCaps(struct _SNETCAPS *caps) { caps->size = 0; // engine writes only ?!? @@ -248,7 +369,7 @@ bool base::SNetDropPlayer(int playerid, uint32_t flags) plr_t base::GetOwner() { for (auto i = 0; i < MAX_PLRS; ++i) { - if (connected_table[i]) { + if (IsConnected(i)) { return i; } } @@ -257,13 +378,21 @@ plr_t base::GetOwner() bool base::SNetGetOwnerTurnsWaiting(uint32_t *turns) { - *turns = turn_queue[GetOwner()].size(); + poll(); + + plr_t owner = GetOwner(); + PlayerState &playerState = playerStateTable_[owner]; + std::deque &turnQueue = playerState.turnQueue; + *turns = turnQueue.size(); + return true; } bool base::SNetGetTurnsInTransit(uint32_t *turns) { - *turns = turn_queue[plr_self].size(); + PlayerState &playerState = playerStateTable_[plr_self]; + std::deque &turnQueue = playerState.turnQueue; + *turns = turnQueue.size(); return true; } diff --git a/Source/dvlnet/base.h b/Source/dvlnet/base.h index 51c1383b0..82e96819e 100644 --- a/Source/dvlnet/base.h +++ b/Source/dvlnet/base.h @@ -62,24 +62,42 @@ protected: } }; + struct PlayerState { + bool isConnected = {}; + bool waitForTurns = {}; + std::deque turnQueue; + int32_t lastTurnValue = {}; + }; + + seq_t next_turn; message_t message_last; std::deque message_queue; - std::array turn_last = {}; - std::array, MAX_PLRS> turn_queue; - std::array connected_table = {}; plr_t plr_self = PLR_BROADCAST; cookie_t cookie_self = 0; std::unique_ptr pktfty; - void HandleAccept(packet &pkt); + void Connect(plr_t player); void RecvLocal(packet &pkt); void RunEventHandler(_SNETEVENT &ev); + [[nodiscard]] bool IsConnected(plr_t player) const; + virtual bool IsGameHost() = 0; + private: + std::array playerStateTable_; + plr_t GetOwner(); + bool AllTurnsArrived(); + void MakeReady(seq_t sequenceNumber); + void SendTurnIfReady(turn_t turn); void ClearMsg(plr_t plr); + + void HandleAccept(packet &pkt); + void HandleConnect(packet &pkt); + void HandleTurn(packet &pkt); + void HandleDisconnect(packet &pkt); }; } // namespace net diff --git a/Source/dvlnet/base_protocol.h b/Source/dvlnet/base_protocol.h index 68df34ea2..24ca8e242 100644 --- a/Source/dvlnet/base_protocol.h +++ b/Source/dvlnet/base_protocol.h @@ -31,6 +31,9 @@ public: virtual ~base_protocol() = default; +protected: + virtual bool IsGameHost(); + private: P proto; typedef typename P::endpoint endpoint; @@ -129,7 +132,7 @@ int base_protocol

::create(std::string addrstr) if (wait_network()) { plr_self = 0; - connected_table[plr_self] = true; + Connect(plr_self); } return (plr_self == PLR_BROADCAST ? -1 : plr_self); } @@ -145,6 +148,12 @@ int base_protocol

::join(std::string addrstr) return (plr_self == PLR_BROADCAST ? -1 : plr_self); } +template +bool base_protocol

::IsGameHost() +{ + return firstpeer == endpoint(); +} + template void base_protocol

::poll() { @@ -206,7 +215,7 @@ void base_protocol

::handle_join_request(packet &pkt, endpoint sender) plr_t i; for (i = 0; i < MAX_PLRS; ++i) { if (i != plr_self && !peers[i]) { - connected_table[i] = true; + Connect(i); peers[i] = sender; break; } @@ -295,7 +304,7 @@ void base_protocol

::recv_ingame(packet &pkt, endpoint sender) } // addrinfo packets - connected_table[pkt.NewPlayer()] = true; + Connect(pkt.NewPlayer()); peers[pkt.NewPlayer()].unserialize(pkt.Info()); return; } else if (pkt.Source() >= MAX_PLRS) { @@ -303,7 +312,7 @@ void base_protocol

::recv_ingame(packet &pkt, endpoint sender) LogDebug("Invalid packet: packet source ({}) >= MAX_PLRS", pkt.Source()); return; } else if (sender == firstpeer && pkt.Type() == PT_JOIN_ACCEPT) { - connected_table[pkt.Source()] = true; + Connect(pkt.Source()); peers[pkt.Source()] = sender; firstpeer = endpoint(); } else if (sender != peers[pkt.Source()]) { diff --git a/Source/dvlnet/packet.h b/Source/dvlnet/packet.h index 84afd8ec5..2bcf0d67c 100644 --- a/Source/dvlnet/packet.h +++ b/Source/dvlnet/packet.h @@ -33,8 +33,8 @@ enum packet_type : uint8_t { const char *packet_type_to_string(uint8_t packetType); typedef uint8_t plr_t; +typedef uint8_t seq_t; typedef uint32_t cookie_t; -typedef int turn_t; // change int to something else in devilution code later typedef int leaveinfo_t; // also change later #ifdef PACKET_ENCRYPTION typedef std::array key_t; @@ -43,6 +43,11 @@ typedef std::array key_t; using key_t = uint8_t; #endif +struct turn_t { + seq_t SequenceNumber; + int32_t Value; +}; + static constexpr plr_t PLR_MASTER = 0xFE; static constexpr plr_t PLR_BROADCAST = 0xFF; @@ -149,7 +154,8 @@ void packet_proc

::process_data() self.process_element(m_message); break; case PT_TURN: - self.process_element(m_turn); + self.process_element(m_turn.SequenceNumber); + self.process_element(m_turn.Value); break; case PT_JOIN_REQUEST: self.process_element(m_cookie); diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index 59cda1003..b6b5ec593 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -69,6 +69,11 @@ int tcp_client::join(std::string addrstr) return plr_self; } +bool tcp_client::IsGameHost() +{ + return local_server != nullptr; +} + void tcp_client::poll() { ioc.poll(); diff --git a/Source/dvlnet/tcp_client.h b/Source/dvlnet/tcp_client.h index c358ec7ca..4e8558fb9 100644 --- a/Source/dvlnet/tcp_client.h +++ b/Source/dvlnet/tcp_client.h @@ -30,6 +30,9 @@ public: virtual std::string make_default_gamename(); +protected: + virtual bool IsGameHost(); + private: frame_queue recv_queue; buffer_t recv_buffer = buffer_t(frame_queue::max_frame_size);