Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
Messaging.cpp
Go to the documentation of this file.
1#include "Messaging.hpp"
2
3#include <deque>
4#include <algorithm>
5#include <optional>
6#include <array>
7#include <utility>
8
10{
11 namespace
12 {
13 std::deque<actions::Message> messageQueue;
14
15 bool currentServerMode = true;
16 bool currentNetworkMode = false;
17
18 bool initialized = false;
19 std::unique_ptr<Transport> network;
20 std::optional<actions::Message> pendingAdmission;
21 std::deque<actions::Message> waitingAdmissions;
22 std::array<std::uint32_t, rules::MaxPlayers> playerOwners{};
23 std::deque<std::uint32_t> disconnectedSources;
25 std::function<bool()> networkStarter;
26 bool hostDisconnected{};
27 bool wasConnected{};
28 }
29
31 {
32 network.reset();
33 pendingAdmission.reset();
34 waitingAdmissions.clear();
35 playerOwners.fill(0);
36 disconnectedSources.clear();
37 networkStarter = {};
38 hostDisconnected = false;
39 wasConnected = false;
40 // MESS_InitializeSystem() original :
41 //
42 // MessageQueue.head = -1;
43 // MESS_ServerMode = TRUE;
44 // MESS_NetworkMode = FALSE;
45
46 messageQueue.clear();
47
48 currentServerMode = true;
49 currentNetworkMode = false;
50
51 initialized = true;
52
53 return true;
54 }
55
56 void shutdown()
57 {
58 network.reset();
59 pendingAdmission.reset();
60 waitingAdmissions.clear();
61 messageQueue.clear();
62
63 currentServerMode = true;
64 currentNetworkMode = false;
65
66 initialized = false;
67 }
68
70 {
71 disconnectedSources.clear();
72 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
73 setPlayerOwner(player, 0);
74 }
75
76 void setPlayerOwner(rules::PlayerNumber player, std::uint32_t source)
77 {
78 if (player >= rules::MaxPlayers) return;
79 playerOwners[player] = source;
80 if (network) network->setPlayerOwner(player, source);
81 }
82
83 std::uint32_t playerOwner(rules::PlayerNumber player)
84 {
85 return player < rules::MaxPlayers ? playerOwners[player] : 0;
86 }
87
89 {
90 if (!network || !network->gameplayEnabled()) return true;
91 if (message.sourceId != 0 && !network->sourceConnected(message.sourceId))
92 {
93 // Only the exact terminal packet remains useful after a peer dies.
94 // Never admit a queued registration/action for an orphaned source.
95 static const std::vector<std::uint8_t> stopPacket{'S', 'T', 'O', 'P', 0, 0, 0, 0};
96 return message.action == actions::Type::VoiceChat &&
98 message.toPlayer == rules::BankPlayer && message.binaryDataA == stopPacket;
99 }
100 if (message.fromPlayer == rules::BankPlayer) return message.sourceId == 0;
101 if (message.fromPlayer < state.numberOfPlayers)
102 return playerOwner(message.fromPlayer) == message.sourceId;
103 return message.fromPlayer == rules::SpectatorPlayer;
104 }
105
107 {
108 network.reset();
109 pendingAdmission.reset();
110 waitingAdmissions.clear();
111 disconnectedSources.clear();
112 messageQueue.clear();
113 playerOwners.fill(0);
114 currentNetworkMode = false;
115 currentServerMode = true;
116 wasConnected = false;
117 gameplaySynchronized = false;
118 }
119
120 void setNetworkStarter(std::function<bool()> starter)
121 {
122 networkStarter = std::move(starter);
123 }
124
126 {
127 if (network && network->gameplayEnabled()) return true;
128 stopNetwork();
129 return networkStarter && networkStarter();
130 }
131
133 {
134 return network && network->gameplayEnabled();
135 }
136
142
144 {
145 if (network && !currentServerMode && network->gameplayEnabled())
147 }
148
150 {
151 return std::exchange(hostDisconnected, false);
152 }
153
154 bool startNetwork(std::unique_ptr<Transport> transport)
155 {
156 if (!initialized || network || !transport)
157 return false;
158 pendingAdmission.reset();
159 currentServerMode = transport->server();
160 currentNetworkMode = false;
162 messageQueue.clear(); // Discard notifications from local startup.
163 network = std::move(transport);
164 wasConnected = false;
165 gameplaySynchronized = false;
166 hostDisconnected = false;
167 for (rules::PlayerNumber player = 0; player < rules::MaxPlayers; ++player)
168 network->setPlayerOwner(player, playerOwners[player]);
169 return true;
170 }
171
173 {
174 if (!initialized || !network)
175 return;
176 network->pump();
177 currentNetworkMode = network->active();
178 if (!currentServerMode && network->gameplayEnabled() &&
179 !currentNetworkMode && (wasConnected || !network->error().empty()))
180 {
181 stopNetwork();
182 hostDisconnected = true;
183 return;
184 }
186 std::uint32_t source = 0;
187 while (network->receiveDisconnectedSource(source))
188 {
189 if (pendingAdmission && pendingAdmission->sourceId == source) pendingAdmission.reset();
190 std::erase_if(waitingAdmissions, [source](const auto& admission)
191 { return admission.sourceId == source; });
192 if (source != 0 && std::find(playerOwners.begin(), playerOwners.end(), source) != playerOwners.end() &&
193 std::find(disconnectedSources.begin(), disconnectedSources.end(), source) == disconnectedSources.end())
194 disconnectedSources.push_back(source);
195 }
197 pendingAdmission.reset();
199 messageQueue.clear();
201 std::erase_if(messageQueue, [](const actions::Message& message)
202 {
203 return message.action == actions::Type::VoiceChat ||
205 });
206 // Drain the existing local work before delivering admission to RULE.
207 // Importing later voice frames here would consume the slots needed by
208 // its configuration, AI state, compact state and contract notifications.
210 return;
212 while (messageQueue.size() < MessageQueueCapacity &&
213 network->receive(received))
214 {
217 continue;
218 if (currentServerMode && received.sourceId != 0 &&
220 {
222 pendingAdmission = std::move(received);
223 break;
224 }
225 messageQueue.push_back(std::move(received));
226 }
227 }
228
229 std::string_view networkError()
230 {
231 return network ? network->error() : std::string_view{};
232 }
233
234 bool sendAction(const actions::Message& message)
235 {
236 if (!initialized)
237 {
238 return false;
239 }
240
241 if (network && !currentNetworkMode &&
242 (message.action == actions::Type::VoiceChat ||
244 return false;
245
247 {
248 // The transport assigns the connection identity. RULE on the host
249 // authenticates the current player owner before dispatch; no echo.
250 if (!network->gameplayEnabled() && message.action != actions::Type::VoiceChat)
251 return false;
252 return network->send(message);
253 }
254
256 {
257 return false;
258 }
259
261 message.fromPlayer == rules::BankPlayer &&
262 (message.toPlayer == rules::AllPlayers ||
263 (network->gameplayEnabled() && message.toPlayer < rules::MaxPlayers)) &&
264 static_cast<unsigned>(message.action) >= 80u &&
265 !network->send(message))
266 {
267 return false;
268 }
269
270 auto local = message;
271 local.sourceId = 0;
272 messageQueue.push_back(std::move(local));
273
274 return true;
275 }
276
278 actions::Type action,
279 rules::PlayerNumber fromPlayer,
280 rules::PlayerNumber toPlayer,
281 std::int64_t numberA,
282 std::int64_t numberB,
283 std::int64_t numberC,
284 std::int64_t numberD,
285 std::wstring_view stringA)
286 {
287 actions::Message message{};
288
289 message.action = action;
290 message.fromPlayer = fromPlayer;
291 message.toPlayer = toPlayer;
292
293 message.numberA = numberA;
294 message.numberB = numberB;
295 message.numberC = numberC;
296 message.numberD = numberD;
297
298 const std::size_t count =
299 std::min(
300 stringA.size(),
301 message.stringA.size() - 1
302 );
303
304 std::copy_n(
305 stringA.begin(),
306 count,
307 message.stringA.begin()
308 );
309
310 message.stringA[count] = L'\0';
311
312 return sendAction(message);
313 }
314
316 {
317 messageQueue.clear();
318 // Admission describes a live connection, so a local game restart must
319 // not discard its still-required resync.
320 }
321
322 std::size_t currentQueueSize()
323 {
324 return messageQueue.size();
325 }
326
328 {
329 pumpNetwork();
330 if (!initialized)
331 return false;
332 // Resolve ownership only when RULE can consume the complete reply batch.
333 // Slot indices may have changed through a preceding delete/reorder/load.
334 while (messageQueue.empty() && !disconnectedSources.empty())
335 {
336 const auto source = disconnectedSources.front();
337 const auto owner = std::find(playerOwners.begin(), playerOwners.end(), source);
338 if (owner == playerOwners.end())
339 {
340 disconnectedSources.pop_front();
341 continue;
342 }
343 const auto player = static_cast<rules::PlayerNumber>(owner - playerOwners.begin());
344 setPlayerOwner(player, 0);
345 message = {};
348 message.toPlayer = rules::BankPlayer;
349 message.numberA = player;
350 return true;
351 }
352 if (messageQueue.empty())
353 {
354 if (!pendingAdmission && !waitingAdmissions.empty())
355 {
356 pendingAdmission = std::move(waitingAdmissions.front());
357 waitingAdmissions.pop_front();
358 }
359 if (!pendingAdmission) return false;
360 // Return directly instead of enqueuing: the complete queue remains
361 // available to the synchronous RULE resync batch. Voice-only reads
362 // deliberately leave admission pending throughout animation locks.
363 message = std::move(*pendingAdmission);
364 pendingAdmission.reset();
365 if (network && !network->admitSource(message.sourceId)) return false;
366 return true;
367 }
368
369 message = std::move(messageQueue.front());
370 messageQueue.pop_front();
371 return true;
372 }
373
375 {
376 pumpNetwork();
377 if (!initialized)
378 return false;
379
380 const auto isVoice = [](const actions::Message& queued)
381 {
382 return queued.action == actions::Type::VoiceChat ||
383 queued.action == actions::Type::NotifyVoiceChat;
384 };
385 const auto found = std::find_if(messageQueue.begin(), messageQueue.end(), isVoice);
386 if (found != messageQueue.end())
387 {
388 message = std::move(*found);
389 messageQueue.erase(found);
390 return true;
391 }
393 return false;
394
395 // Admission waits for an empty ordinary queue, but an animation lock
396 // must not silence established speakers until that queue can drain.
397 // Return one voice message directly, leaving room for its RULE echo.
398 // Later admissions all request the same AllPlayers refresh, so one
399 // pending resync covers them without an unbounded side queue.
401 for (unsigned budget = 16; budget > 0 &&
403 {
404 if (currentServerMode && received.sourceId != 0 &&
406 received.numberA == rules::AllPlayers)
407 {
408 // Gameplay peers each require admission; never collapse their
409 // identities while the ordinary queue is animation-locked.
410 if (network->gameplayEnabled())
411 {
412 if (!pendingAdmission)
413 {
414 pendingAdmission = std::move(received);
415 continue;
416 }
417 if (received.sourceId != pendingAdmission->sourceId &&
418 network->sourceConnected(received.sourceId) &&
419 std::none_of(waitingAdmissions.begin(), waitingAdmissions.end(),
420 [&](const auto& queued) { return queued.sourceId == received.sourceId; }) &&
422 waitingAdmissions.push_back(std::move(received));
423 continue;
424 }
425 continue;
426 }
427 if (isVoice(received))
428 {
429 message = std::move(received);
430 return true;
431 }
432 messageQueue.push_back(std::move(received));
433 }
434 return false;
435 }
436
438 {
439 return currentServerMode;
440 }
441
443 {
444 return currentNetworkMode;
445 }
446
447 std::size_t queuedActionCount()
448 {
449 return messageQueue.size() + (pendingAdmission ? 1u : 0u) + (network ? network->queued() : 0u);
450 }
451}
452
453
std::size_t count
std::shared_ptr< const data::HmdTextureImage > source
unsigned state
unsigned index
bool receiveAction(actions::Message &message)
bool startConfiguredNetwork()
std::size_t currentQueueSize()
bool consumeHostDisconnected()
std::size_t queuedActionCount()
std::string_view networkError()
constexpr std::size_t MessageQueueCapacity
Definition Messaging.hpp:14
bool authenticSender(const rules::GameState &state, const actions::Message &message)
Definition Messaging.cpp:88
bool startNetwork(std::unique_ptr< Transport > transport)
void setNetworkStarter(std::function< bool()> starter)
std::uint32_t playerOwner(rules::PlayerNumber player)
Definition Messaging.cpp:83
bool sendAction(const actions::Message &message)
void noteClientResynchronized()
void setPlayerOwner(rules::PlayerNumber player, std::uint32_t source)
Definition Messaging.cpp:76
bool receiveVoiceChatOnly(actions::Message &message)
constexpr PlayerNumber BankPlayer
Definition RuleTypes.hpp:15
std::uint8_t PlayerNumber
Definition RuleTypes.hpp:12
constexpr PlayerNumber SpectatorPlayer
Definition RuleTypes.hpp:22
constexpr PlayerNumber MaxPlayers
Definition RuleTypes.hpp:14
constexpr PlayerNumber AllPlayers
Definition RuleTypes.hpp:17
rules::PlayerNumber toPlayer
Definition Actions.hpp:131
rules::PlayerNumber fromPlayer
Definition Actions.hpp:128
std::vector< std::uint8_t > binaryDataA
Definition Actions.hpp:143