16 std::string
packet_name(
const std::array<char, 32> &name) {
return std::string(name.data(), strnlen(name.data(), name.size())); }
19 const std::size_t size = std::min(name.size(), output.size() - 1U);
20 std::memcpy(output.data(), name.data(), size);
25 static constexpr char ALPHABET[] =
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
26 std::random_device random_device;
27 std::uniform_int_distribution<std::size_t> distribution(0,
sizeof(ALPHABET) - 2U);
28 std::string code(8,
'A');
29 for (
char &character : code)
30 character = ALPHABET[distribution(random_device)];
39 if (!socket.setblocking(
false)) {
43 port_mapping = std::make_unique<PortMapping>();
44 port_mapping->open(port);
45 session_active =
true;
47 assigned_player_id = 0;
48 local_player_name = player_name;
49 names[0] = player_name;
50 connected_players[0] =
true;
51 peers[0].connected =
true;
52 peers[0].name = player_name;
53 session_join_code = random_join_code();
59 if (!socket.connect(address, port)) {
63 if (!socket.setblocking(
false)) {
67 session_active =
true;
69 local_player_name = player_name;
79 session_active =
false;
81 send_accumulator = 0.0f;
83 assigned_player_id = 0xFFU;
84 local_player_name.clear();
85 session_join_code.clear();
88 connected_players = {};
93 if (!session_active) {
98 peers[assigned_player_id].state = local_state;
104 peers[player].idle_seconds += delta_time;
105 if (peers[player].idle_seconds > 5.0f) {
107 connected_players[player] =
false;
108 names[player].clear();
112 peers[0].idle_seconds += delta_time;
113 if (peers[0].idle_seconds > 5.0f) {
115 if (player != assigned_player_id) {
116 connected_players[player] =
false;
125 const ssize_t received = host_role ? socket.recvfrom(&packet,
sizeof(packet), sender) : socket.recvfrom(&packet,
sizeof(packet));
129 if (received !=
static_cast<ssize_t
>(
sizeof(packet)) || packet.magic != Packet{}.magic || packet.version != Packet{}.version || packet.size !=
sizeof(Packet)) {
134 const std::string supplied_code(packet.join_code.data(), strnlen(packet.join_code.data(), packet.join_code.size()));
135 if (supplied_code != session_join_code) {
138 const std::optional<std::uint8_t> player = find_or_assign_peer(sender);
139 if (!player.has_value()) {
142 Peer &peer = peers[*player];
143 if (packet.sequence <= peer.receive_sequence && peer.receive_sequence != 0) {
146 peer.receive_sequence = packet.sequence;
147 peer.idle_seconds = 0.0f;
148 peer.state = packet.state;
149 peer.name = packet_name(packet.player_name);
150 names[*player] = peer.name;
151 connected_players[*player] =
true;
152 result.
players.push_back({*player, packet.state});
154 peers[0].idle_seconds = 0.0f;
156 connected_players[player] = (packet.connected_mask & (1U << player)) != 0U;
159 assigned_player_id = packet.assigned_player_id;
160 names[assigned_player_id] = local_player_name;
161 connected_players[assigned_player_id] =
true;
164 names[packet.player_id] = packet_name(packet.player_name);
165 connected_players[packet.player_id] =
true;
166 result.
players.push_back({packet.player_id, packet.state});
171 send_accumulator += delta_time;
172 if (send_accumulator >= (1.0f / 30.0f)) {
173 send_accumulator = 0.0f;
181 send_host_relay(source, destination);
186 send_client_state(local_state);
188 }
else if (!host_role && assigned_player_id == 0xFFU) {
189 send_client_state(local_state);
206 static const std::string NO_MAPPING =
"Automatic router mapping unavailable; forward the UDP port manually.";
207 return port_mapping ? port_mapping->status() : NO_MAPPING;
210 void MultiplayerSession::send_client_state(
const NetworkState &state) {
212 packet.sequence = ++send_sequence;
213 packet.player_id = assigned_player_id;
214 packet.state = state;
215 set_packet_name(packet.player_name, local_player_name);
216 const std::size_t code_size = std::min(session_join_code.size(), packet.join_code.size() - 1U);
217 std::memcpy(packet.join_code.data(), session_join_code.data(), code_size);
218 packet.join_code[code_size] =
'\0';
219 socket.
sendto(&packet,
sizeof(packet));
222 void MultiplayerSession::send_host_relay(std::uint8_t source_player, std::uint8_t destination_player) {
224 packet.sequence = ++send_sequence;
225 packet.player_id = source_player;
226 packet.assigned_player_id = destination_player;
228 if (connected_players[player])
229 packet.connected_mask |=
static_cast<std::uint8_t
>(1U << player);
231 packet.state = peers[source_player].state;
233 socket.sendto(&packet,
sizeof(packet), peers[destination_player].address);
236 std::optional<std::uint8_t> MultiplayerSession::find_or_assign_peer(
const mxnetwork::SocketAddress &address) {
238 if (peers[player].
connected && peers[player].address == address) {
244 peers[player].connected =
true;
245 peers[player].address = address;
246 connected_players[player] =
true;
Lightweight exception wrapper for MXNetwork failures.
C++ wrapper around the MXNetwork socket API.
ssize_t sendto(const void *buf, size_t bytes)
Send a datagram using the configured socket type.
MultiplayerSession()
Initializes the networking subsystem.
void host(const std::string &port, const std::string &player_name)
Starts hosting a session.
~MultiplayerSession()
Stops the session and releases networking resources.
void join(const std::string &address, const std::string &port, const std::string &player_name, const std::string &join_code)
Joins a hosted session.
bool is_host() const
Returns whether this session is the host.
NetworkExchange exchange(const NetworkState &local_state, float delta_time)
Sends local state and receives remote updates.
bool connected() const
Returns whether the local player has an established peer connection.
const std::array< bool, NETWORK_PLAYER_COUNT > & player_connected() const
Returns connection flags indexed by player slot.
void stop()
Stops the active host or client session.
bool active() const
Returns whether a host or client session is active.
std::uint8_t local_player_id() const
Returns the assigned local player slot.
std::size_t player_count() const
Returns the number of connected players.
const std::string & port_mapping_status() const
Returns the automatic router port-mapping status.
const std::array< std::string, NETWORK_PLAYER_COUNT > & player_names() const
Returns display names indexed by player slot.
const std::string & join_code() const
Returns the session join code.
UDP multiplayer state exchange for Asteroids.
@ TYPE_INET_DGRAM
IPv4 datagram socket.
void set_packet_name(std::array< char, 32 > &output, const std::string &name)
std::string packet_name(const std::array< char, 32 > &name)
std::string random_join_code()
constexpr std::size_t NETWORK_PLAYER_COUNT
Maximum number of players in a session.
Automatic UDP port mapping for hosted multiplayer sessions.
Collection of remote player updates produced by one exchange tick.
std::vector< NetworkPlayerUpdate > players
Updates received during the tick.
Complete replicated gameplay state published by one player.