1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2020, The LegacyClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17/* control managament: network part */
18
19#include "C4Include.h"
20
21#include <C4Application.h>
22#include <C4GameControlNetwork.h>
23#include <C4GameControl.h>
24#include <C4Game.h>
25#include <C4Log.h>
26
27// *** C4GameControlNetwork
28
29C4GameControlNetwork::C4GameControlNetwork(C4GameControl *pnParent)
30 : fEnabled(false), fRunning(false), iClientID(C4ClientIDUnknown),
31 fActivated(false), iTargetTick(-1),
32 iControlPreSend(1), iWaitStart(-1), iAvgControlSendTime(0), iTargetFPS(DefaultTargetFPS),
33 iControlSent(0), iControlReady(0),
34 pCtrlStack(nullptr),
35 iNextControlReqeust(0),
36 pParent(pnParent)
37{
38 assert(pParent);
39}
40
41C4GameControlNetwork::~C4GameControlNetwork()
42{
43 Clear();
44}
45
46bool C4GameControlNetwork::Init(int32_t inClientID, bool fnHost, int32_t iStartTick, bool fnActivated, C4Network2 *pnNetwork) // by main thread
47{
48 if (IsEnabled()) Clear();
49 // init
50 iClientID = inClientID; fHost = fnHost;
51 Game.Control.ControlTick = iStartTick;
52 iControlSent = iControlReady = Game.Control.getNextControlTick() - 1;
53 fActivated = fnActivated;
54 pNetwork = pnNetwork;
55 // check
56 CheckCompleteCtrl(fSetEvent: false);
57 // make sure no control has been lost
58 pNetwork->Clients.BroadcastMsgToConnClients(rPkt: MkC4NetIOPacket(cStatus: PID_ControlReq, Pkt: C4PacketControlReq(iControlReady + 1)));
59 // ok
60 fEnabled = true; fRunning = false;
61 iTargetFPS = DefaultTargetFPS; iNextControlReqeust = timeGetTime() + C4ControlRequestInterval;
62 return true;
63}
64
65void C4GameControlNetwork::Clear() // by main thread
66{
67 fEnabled = false; fRunning = false;
68 iAvgControlSendTime = 0;
69 ClearCtrl(); ClearClients();
70 // clear sync control
71 SyncControl.Clear();
72 while (pSyncCtrlQueue)
73 {
74 C4GameControlPacket *pPkt = pSyncCtrlQueue;
75 pSyncCtrlQueue = pPkt->pNext;
76 delete pPkt;
77 }
78}
79
80void C4GameControlNetwork::Execute() // by main thread
81{
82 // Control ticks only
83 if (Game.FrameCounter % Game.Control.ControlRate)
84 return;
85
86 // Save time the control tick was reached
87 if (iWaitStart == -1)
88 iWaitStart = timeGetTime();
89
90 // Execute any queued sync control
91 ExecQueuedSyncCtrl();
92}
93
94bool C4GameControlNetwork::CtrlReady(int32_t iTick) // by main thread
95{
96 // check for complete control and pack it
97 CheckCompleteCtrl(fSetEvent: false);
98 // control ready?
99 return iControlReady >= iTick;
100}
101
102bool C4GameControlNetwork::GetControl(C4Control *pCtrl, int32_t iTick) // by main thread
103{
104 // lock
105 CStdLock CtrlLock(&CtrlCSec);
106 // look for control
107 C4GameControlPacket *pPkt = getCtrl(iClientID: C4ClientIDAll, iCtrlTick: iTick);
108 if (!pPkt)
109 return false;
110 // set
111 pCtrl->Clear();
112 pCtrl->Append(Ctrl: pPkt->getControl());
113 // calc performance
114 CalcPerformance(iCtrlTick: iTick);
115 iWaitStart = -1;
116 // ok
117 return true;
118}
119
120bool C4GameControlNetwork::ClientReady(int32_t iClientID, int32_t iTick) // by main thread
121{
122 if (eMode == CNM_Central && !fHost) return true;
123 return !!getCtrl(iClientID, iCtrlTick: iTick);
124}
125
126int32_t C4GameControlNetwork::ClientPerfStat(int32_t iClientID) // by main thread
127{
128 if (eMode == CNM_Central && !fHost) return true;
129 // get client
130 CStdLock ClientsLock(&ClientsCSec);
131 C4GameControlClient *pClient = getClient(iID: iClientID);
132 // return performance
133 return pClient ? pClient->getPerfStat() : 0;
134}
135
136int32_t C4GameControlNetwork::ClientNextControl(int32_t iClientID) // by main thread
137{
138 // get client
139 CStdLock ClientsLock(&ClientsCSec);
140 C4GameControlClient *pClient = getClient(iID: iClientID);
141 // return performance
142 return pClient ? pClient->getNextControl() : 0;
143}
144
145bool C4GameControlNetwork::CtrlNeeded(int32_t iFrame) const // by main thread
146{
147 if (!IsEnabled() || !fActivated) return false;
148 // check: should we send something at the moment?
149 int32_t iSendFor = pParent->getCtrlTick(iFrame: iFrame + iControlPreSend);
150 // target tick set? do special check
151 if (iTargetTick >= 0 && iControlSent >= iTargetTick) return false;
152 // control sent for this ctrl tick?
153 return iSendFor > iControlSent;
154}
155
156void C4GameControlNetwork::DoInput(const C4Control &Input) // by main thread
157{
158 if (!fEnabled) return;
159 // pack
160 C4GameControlPacket *pCtrl = new C4GameControlPacket();
161 pCtrl->Set(iClientID, iCtrlTick: iControlSent + 1, Ctrl: Input);
162 // client in central or async mode: send to host (will resend it to the other clients)
163 C4NetIOPacket CtrlPkt = MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pCtrl);
164 if (eMode != CNM_Decentral)
165 {
166 if (!fHost)
167 if (!pNetwork->Clients.SendMsgToHost(rPkt: CtrlPkt))
168 pParent->GetLogger()->error(msg: "Failed to send control to host!");
169 }
170 // decentral mode: always broadcast to everybody
171 else if (!pNetwork->Clients.BroadcastMsgToClients(rPkt: CtrlPkt))
172 pParent->GetLogger()->error(msg: "Failed to broadcast control!");
173 // add to list
174 AddCtrl(pCtrl);
175 // ok, control is sent for this control tick
176 iControlSent++;
177 // ctrl complete?
178 CheckCompleteCtrl(fSetEvent: false);
179}
180
181void C4GameControlNetwork::DoInput(C4PacketType eCtrlType, C4ControlPacket *pCtrl, C4ControlDeliveryType eDelivery) // by main thread
182{
183 if (!fEnabled) return;
184
185 // Create packet
186 C4PacketControlPkt CtrlPkt(eDelivery, C4IDPacket(eCtrlType, pCtrl, false));
187
188 switch (eDelivery)
189 {
190 // Sync control
191 case CDT_Sync:
192 {
193 if (!fHost)
194 {
195 // Client: send to host
196 if (!Game.Network.Clients.SendMsgToHost(rPkt: MkC4NetIOPacket(cStatus: PID_ControlPkt, Pkt: CtrlPkt)))
197 {
198 LogFatalNTr(message: "Network: could not send direct control packet!"); break;
199 }
200 delete pCtrl;
201 }
202 else
203 {
204 // Host: send to all clients
205 Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_ControlPkt, Pkt: CtrlPkt));
206 // Execute at once, if possible
207 if (Game.Network.isFrozen())
208 {
209 pParent->ExecControlPacket(eCtrlType, pPkt: pCtrl);
210 delete pCtrl;
211 C4PacketExecSyncCtrl Pkt(pParent->ControlTick);
212 Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_ExecSyncCtrl, Pkt));
213 }
214 else
215 {
216 // Safe back otherwise
217 SyncControl.Add(eType: eCtrlType, pCtrl);
218 // And sync
219 Game.Network.Sync();
220 }
221 }
222 }
223 break;
224
225 // Direct/private control:
226 case CDT_Direct:
227 case CDT_Private:
228 {
229 // Send to all clients
230 if (!Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_ControlPkt, Pkt: CtrlPkt)))
231 {
232 LogFatalNTr(message: "Network: could not send direct control packet!"); break;
233 }
234 // Exec at once
235 pParent->ExecControlPacket(eCtrlType, pPkt: pCtrl);
236 delete pCtrl;
237 }
238 break;
239
240 // Only some delivery types support single packets (queue control must be tick-stamped)
241 default:
242 assert(false);
243 }
244}
245
246C4ControlDeliveryType C4GameControlNetwork::DecideControlDelivery() const
247{
248 // This doesn't make sense for clients
249 if (!fHost)
250 return CDT_Queue;
251 // Decide the fastest control delivery type atm. Note this is a guess.
252 // Control sent with the returned delivery type may in theory be delayed infinitely.
253 if (Game.Network.isFrozen())
254 return CDT_Sync;
255 if (!Game.Clients.getLocal()->isActivated())
256 return CDT_Sync;
257 return CDT_Queue;
258}
259
260void C4GameControlNetwork::ExecSyncControl() // by main thread
261{
262 assert(fHost);
263
264 // This is a callback from C4Network informing that a point where accumulated sync control
265 // can be executed has been reached (it's "momentarily" safe to execute)
266
267 // Nothing to do? Save some sweat.
268 if (!SyncControl.firstPkt())
269 return;
270
271 // So let's spread the word, so clients will call ExecSyncControl, too.
272 C4PacketExecSyncCtrl Pkt(pParent->ControlTick);
273 Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_ExecSyncCtrl, Pkt));
274
275 // Execute it
276 ExecSyncControl(iControlTick: Pkt.getControlTick());
277}
278
279void C4GameControlNetwork::ExecSyncControl(int32_t iControlTick) // by main thread
280{
281 // Nothing to do?
282 if (!SyncControl.firstPkt())
283 return;
284
285 // Copy control and clear
286 C4Control Control = SyncControl;
287 SyncControl.Clear();
288
289 // Given control tick reached?
290 if (pParent->ControlTick == iControlTick)
291 pParent->ExecControl(rCtrl: Control);
292 else if (pParent->ControlTick > iControlTick)
293 // The host should make sure this doesn't happen.
294 pParent->GetLogger()->error(fmt: "Fatal: got sync control to execute for ctrl tick {}, but already in ctrl tick {}!", args&: iControlTick, args&: pParent->ControlTick);
295 else
296 // This sync control must be executed later, so safe it back
297 AddSyncCtrlToQueue(Ctrl: Control, iTick: iControlTick);
298}
299
300void C4GameControlNetwork::AddClient(int32_t iClientID, const char *szName) // by main thread
301{
302 // security
303 if (!fEnabled || getClient(iID: iClientID)) return;
304 // create new
305 C4GameControlClient *pClient = new C4GameControlClient();
306 pClient->Set(iClientID, szName);
307 pClient->SetNextControl(Game.Control.ControlTick);
308 // add client
309 AddClient(pClient);
310}
311
312void C4GameControlNetwork::ClearClients()
313{
314 CStdLock ClientsLock(&ClientsCSec);
315 while (pClients) { C4GameControlClient *pClient = pClients; RemoveClient(pClient); delete pClient; }
316}
317
318void C4GameControlNetwork::CopyClientList(const C4ClientList &rClients)
319{
320 CStdLock ClientLock(&ClientsCSec);
321 // create local copy of activated client list
322 ClearClients();
323 C4Client *pClient = nullptr;
324 while (pClient = rClients.getClient(pAfter: pClient))
325 if (pClient->isActivated())
326 AddClient(iClientID: pClient->getID(), szName: pClient->getName());
327}
328
329void C4GameControlNetwork::SetRunning(bool fnRunning, int32_t inTargetTick) // by main thread
330{
331 assert(fEnabled);
332 // check for redundant update, stop if running (safety)
333 if (fRunning == fnRunning && iTargetTick == inTargetTick) return;
334 fRunning = false;
335 // set
336 iTargetTick = inTargetTick;
337 fRunning = fnRunning;
338 // run?
339 if (fRunning)
340 {
341 // refresh client list
342 CopyClientList(rClients: Game.Clients);
343 // check for complete ctrl
344 CheckCompleteCtrl(fSetEvent: false);
345 }
346}
347
348void C4GameControlNetwork::SetActivated(bool fnActivated) // by main thread
349{
350 assert(fEnabled);
351 // no change? ignore
352 if (fActivated == fnActivated) return;
353 // set
354 fActivated = fnActivated;
355 // Activated? Start to send control at next tick
356 if (fActivated)
357 iControlSent = Game.Control.getNextControlTick() - 1;
358}
359
360void C4GameControlNetwork::SetCtrlMode(C4GameControlNetworkMode enMode) // by main thread
361{
362 assert(fEnabled);
363 // no change?
364 if (eMode == enMode) return;
365 // set mode
366 eMode = enMode;
367 // changed to decentral? rebroadcast all own control
368 if (enMode == CNM_Decentral)
369 {
370 CStdLock CtrlLock(&CtrlCSec); C4GameControlPacket *pPkt;
371 for (int32_t iCtrlTick = Game.Control.ControlTick; pPkt = getCtrl(iClientID, iCtrlTick); iCtrlTick++)
372 Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pPkt));
373 }
374 else if (enMode == CNM_Central && fHost)
375 {
376 CStdLock CtrlLock(&CtrlCSec); C4GameControlPacket *pPkt;
377 for (int32_t iCtrlTick = Game.Control.ControlTick; pPkt = getCtrl(iClientID: C4ClientIDAll, iCtrlTick); iCtrlTick++)
378 Game.Network.Clients.BroadcastMsgToClients(rPkt: MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pPkt));
379 }
380}
381
382void C4GameControlNetwork::CalcPerformance(int32_t iCtrlTick)
383{
384 CStdLock ControlLock(&CtrlCSec);
385 CStdLock ClientLock(&ClientsCSec);
386 // should only be called if ready
387 assert(CtrlReady(iCtrlTick));
388 // calc perfomance for all clients
389 int32_t iClientsPing = 0; int32_t iPingClientCount = 0; int32_t iNumTunnels = 0; int32_t iHostPing = 0;
390 for (C4GameControlClient *pClient = pClients; pClient; pClient = pClient->pNext)
391 {
392 // Some rudimentary PreSend-calculation
393 // get associated connection - nullptr for self
394 C4Network2Client *pNetClt = Game.Network.Clients.GetClientByID(iID: pClient->getClientID());
395 if (pNetClt && !pNetClt->isLocal())
396 {
397 C4Network2IOConnection *pConn = pNetClt->getMsgConn();
398 if (!pConn)
399 // remember tunnel
400 ++iNumTunnels;
401 else
402 // store ping
403 if (pClient->getClientID() == C4ClientIDHost)
404 iHostPing = pConn->getPingTime();
405 else
406 {
407 iClientsPing += pConn->getPingTime();
408 ++iPingClientCount;
409 }
410 }
411 // Performance statistics
412 // find control (may not be found, if we only got the complete ctrl)
413 C4GameControlPacket *pCtrl = getCtrl(iClientID: pClient->getClientID(), iCtrlTick);
414 if (!pCtrl) continue;
415 // calc stats
416 pClient->AddPerf(iTime: pCtrl->getTime() - iWaitStart);
417 }
418 // Now do PreSend-calcs based on ping times
419 int32_t iControlSendTime;
420 if (eMode == CNM_Decentral)
421 {
422 // average ping time
423 iControlSendTime = (iClientsPing + iHostPing * (iNumTunnels + 1)) / (iPingClientCount + iNumTunnels + 1);
424 // decentral mode: Only half the ping is used if there are no tunnels
425 if (!iNumTunnels) iControlSendTime /= 2;
426 }
427 else
428 {
429 // central mode: Control must go to host and back
430 iControlSendTime = iHostPing;
431 }
432 // calc some average
433 if (iControlSendTime)
434 {
435 iAvgControlSendTime = (iAvgControlSendTime * 149 + iControlSendTime * 1000) / 150;
436 // now calculate the all-time optimum PreSend there is
437 int32_t iBestPreSend = BoundBy(bval: (iTargetFPS * iAvgControlSendTime) / 1000000 + 1, lbound: 1, rbound: 15);
438 // fixed PreSend?
439 if (iTargetFPS <= 0) iBestPreSend = -iTargetFPS;
440 // Ha! Set it!
441 if (getControlPreSend() != iBestPreSend)
442 {
443 setControlPreSend(iBestPreSend);
444 Game.GraphicsSystem.FlashMessage(szMessage: std::format(fmt: "PreSend: {} - TargetFPS: {}", args&: iBestPreSend, args&: iTargetFPS).c_str());
445 }
446 }
447}
448
449void C4GameControlNetwork::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
450{
451 // security
452 if (!pConn)
453 return;
454
455#define GETPKT(type, name) \
456 assert(pPacket); \
457 const type &name = static_cast<const type &>(*pPacket);
458
459 switch (cStatus)
460 {
461 case PID_Control: // control
462 {
463 GETPKT(C4GameControlPacket, rPkt);
464 HandleControl(iByClientID: pConn->getClientID(), rPkt);
465 }
466 break;
467
468 case PID_ControlReq: // control request
469 {
470 if (!IsEnabled()) break;
471 if (pConn->isClosed() || !pConn->isAccepted()) break;
472 GETPKT(C4PacketControlReq, rPkt);
473 HandleControlReq(rPkt, pConn);
474 }
475 break;
476
477 case PID_ControlPkt: // single control packet (main thread!)
478 {
479 GETPKT(C4PacketControlPkt, rPkt);
480 // security
481 if (!fEnabled) break;
482 if (rPkt.getCtrl().getPktType() < CID_First) break;
483 // create copy (HandleControlPkt will store or delete)
484 C4IDPacket Copy(rPkt.getCtrl());
485 // some sanity checks
486 C4ControlPacket *pCtrlPkt = static_cast<C4ControlPacket *>(Copy.getPkt());
487 if (!pConn->isHost() && pConn->getClientID() != pCtrlPkt->getByClient())
488 break;
489 // handle
490 HandleControlPkt(eCtrlType: Copy.getPktType(), pPkt: pCtrlPkt, eType: rPkt.getDelivery());
491 Copy.Default();
492 }
493 break;
494
495 case PID_ExecSyncCtrl:
496 {
497 GETPKT(C4PacketExecSyncCtrl, rPkt);
498 // security
499 if (!fEnabled) break;
500 // handle
501 ExecSyncControl(iControlTick: rPkt.getControlTick());
502 }
503 break;
504 }
505
506#undef GETPKT
507}
508
509void C4GameControlNetwork::OnResComplete(C4Network2Res *pRes)
510{
511 // player?
512 if (pRes->getType() == NRT_Player)
513 // check for ctrl ready
514 CheckCompleteCtrl(fSetEvent: true);
515}
516
517void C4GameControlNetwork::HandleControl(int32_t iByClientID, const C4GameControlPacket &rPkt)
518{
519 // already got that control? just ignore
520 if (getCtrl(iClientID: rPkt.getClientID(), iCtrlTick: rPkt.getCtrlTick())) return;
521 // create copy, add to list
522 C4GameControlPacket *pCopy = new C4GameControlPacket(rPkt);
523 AddCtrl(pCtrl: pCopy);
524 // check: control complete?
525 if (IsEnabled())
526 CheckCompleteCtrl(fSetEvent: true);
527 // note that C4GameControlNetwork will save incoming control even before
528 // Init() was called.
529}
530
531void C4GameControlNetwork::HandleControlReq(const C4PacketControlReq &rPkt, C4Network2IOConnection *pConn)
532{
533 CStdLock CtrlLock(&CtrlCSec);
534 for (int iTick = rPkt.getCtrlTick(); ; iTick++)
535 {
536 // search complete control
537 C4GameControlPacket *pCtrl = getCtrl(iClientID: C4ClientIDAll, iCtrlTick: iTick);
538 if (pCtrl)
539 {
540 // send
541 pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pCtrl));
542 continue;
543 }
544 // send everything we have for this tick (this is an emergency case, so efficiency
545 // isn't that important for now).
546 bool fFound = false;
547 for (pCtrl = pCtrlStack; pCtrl; pCtrl = pCtrl->pNext)
548 if (pCtrl->getCtrlTick() == iTick)
549 {
550 pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pCtrl));
551 fFound = true;
552 }
553 // nothing found for this tick?
554 if (!fFound) break;
555 }
556}
557
558void C4GameControlNetwork::HandleControlPkt(C4PacketType eCtrlType, C4ControlPacket *pCtrl, C4ControlDeliveryType eType) // main thread
559{
560 // direct control? execute at once
561 if (eType == CDT_Direct || eType == CDT_Private)
562 {
563 pParent->ExecControlPacket(eCtrlType, pPkt: pCtrl);
564 delete pCtrl;
565 return;
566 }
567
568 // sync ctrl from client? resend
569 if (fHost && eType == CDT_Sync)
570 {
571 DoInput(eCtrlType, pCtrl, eDelivery: eType);
572 return;
573 }
574
575 // Execute queued control first
576 ExecQueuedSyncCtrl();
577
578 // Execute at once, if possible
579 if (Game.Network.isFrozen())
580 {
581 pParent->ExecControlPacket(eCtrlType, pPkt: pCtrl);
582 delete pCtrl;
583 }
584 else
585 {
586 // Safe back otherwise
587 SyncControl.Add(eType: eCtrlType, pCtrl);
588 }
589}
590
591C4GameControlClient *C4GameControlNetwork::getClient(int32_t iID) // by both
592{
593 CStdLock ClientsLock(&ClientsCSec);
594 for (C4GameControlClient *pClient = pClients; pClient; pClient = pClient->pNext)
595 if (pClient->getClientID() == iID)
596 return pClient;
597 return nullptr;
598}
599
600void C4GameControlNetwork::AddClient(C4GameControlClient *pClient) // by main thread
601{
602 if (!pClient) return;
603 // lock
604 CStdLock ClientsLock(&ClientsCSec);
605 // add (ordered)
606 C4GameControlClient *pPrev = nullptr, *pPos = pClients;
607 for (; pPos; pPrev = pPos, pPos = pPos->pNext)
608 if (pPos->getClientID() > pClient->getClientID())
609 break;
610 // insert
611 (pPrev ? pPrev->pNext : pClients) = pClient;
612 pClient->pNext = pPos;
613}
614
615void C4GameControlNetwork::RemoveClient(C4GameControlClient *pClient) // by main thread
616{
617 // obtain lock
618 CStdLock ClientsLock(&ClientsCSec);
619 // first client?
620 if (pClient == pClients)
621 pClients = pClient->pNext;
622 else
623 {
624 C4GameControlClient *pPrev;
625 for (pPrev = pClients; pPrev && pPrev->pNext; pPrev = pPrev->pNext)
626 if (pPrev->pNext == pClient)
627 break;
628 if (pPrev && pPrev->pNext == pClient)
629 pPrev->pNext = pClient->pNext;
630 }
631}
632
633C4GameControlPacket *C4GameControlNetwork::getCtrl(int32_t iClientID, int32_t iCtrlTick) // by both
634{
635 // lock
636 CStdLock CtrlLock(&CtrlCSec);
637 // search
638 for (C4GameControlPacket *pCtrl = pCtrlStack; pCtrl; pCtrl = pCtrl->pNext)
639 if (pCtrl->getClientID() == iClientID && pCtrl->getCtrlTick() == iCtrlTick)
640 return pCtrl;
641 return nullptr;
642}
643
644void C4GameControlNetwork::AddCtrl(C4GameControlPacket *pCtrl) // by both
645{
646 // lock
647 CStdLock CtrlLock(&CtrlCSec);
648 // add to list
649 pCtrl->pNext = pCtrlStack;
650 pCtrlStack = pCtrl;
651}
652
653void C4GameControlNetwork::ClearCtrl(int32_t iBeforeTick) // by main thread
654{
655 // lock
656 CStdLock CtrlLock(&CtrlCSec);
657 // clear all old control
658 C4GameControlPacket *pCtrl = pCtrlStack, *pLast = nullptr;
659 while (pCtrl)
660 {
661 // old?
662 if (iBeforeTick == -1 || pCtrl->getCtrlTick() < iBeforeTick)
663 {
664 // unlink
665 C4GameControlPacket *pDelete = pCtrl;
666 pCtrl = pCtrl->pNext;
667 (pLast ? pLast->pNext : pCtrlStack) = pCtrl;
668 // delete
669 delete pDelete;
670 }
671 else
672 {
673 pLast = pCtrl;
674 pCtrl = pCtrl->pNext;
675 }
676 }
677}
678
679void C4GameControlNetwork::CheckCompleteCtrl(bool fSetEvent) // by both
680{
681 // only when running (client list may be invalid)
682 if (!fRunning || !fEnabled) return;
683
684 CStdLock CtrlLock(&CtrlCSec);
685 CStdLock ClientLock(&ClientsCSec);
686
687 for (;;)
688 {
689 // control available?
690 C4GameControlPacket *pComplete = getCtrl(iClientID: C4ClientIDAll, iCtrlTick: iControlReady + 1);
691 // get stop tick
692 int32_t iStopTick = iTargetTick;
693 if (pSyncCtrlQueue)
694 if (iStopTick < 0 || iStopTick > pSyncCtrlQueue->getCtrlTick())
695 iStopTick = pSyncCtrlQueue->getCtrlTick();
696 // pack control?
697 if (!pComplete)
698 {
699 // own control not ready?
700 if (fActivated && iControlSent <= iControlReady) break;
701 // no clients? no need to pack more than one tick into the future
702 if (!pClients && Game.Control.ControlTick <= iControlReady) break;
703 // stop packing?
704 if (iStopTick >= 0 && iControlReady + 1 >= iStopTick) break;
705 // central mode and not host?
706 if (eMode != CNM_Decentral && !fHost) break;
707 // (try to) pack
708 if (!(pComplete = PackCompleteCtrl(iTick: iControlReady + 1)))
709 break;
710 }
711 // preexecute to check if it's ready for execute
712 if (!pComplete->getControl().PreExecute(logger: pParent->GetLogger()))
713 break;
714 // ok, control for this tick is ready
715 iControlReady++;
716 // set event, yield
717 if (fSetEvent && Game.GameGo && iControlReady >= Game.Control.ControlTick)
718 Application.NextTick(fYield: true);
719 }
720 // clear old ctrl
721 if (Game.Control.ControlTick >= C4ControlBacklog)
722 ClearCtrl(iBeforeTick: Game.Control.ControlTick - C4ControlBacklog);
723 // target ctrl tick to reach?
724 if (iControlReady < iTargetTick &&
725 (!fActivated || iControlSent > iControlReady) &&
726 timeGetTime() >= iNextControlReqeust)
727 {
728 pParent->GetLogger()->info(fmt: "Recovering: Requesting control for tick {}...", args: iControlReady + 1);
729 // make request
730 C4NetIOPacket Pkt = MkC4NetIOPacket(cStatus: PID_ControlReq, Pkt: C4PacketControlReq(iControlReady + 1));
731 // send control requests
732 if (eMode == CNM_Central)
733 Game.Network.Clients.SendMsgToHost(rPkt: Pkt);
734 else
735 Game.Network.Clients.BroadcastMsgToConnClients(rPkt: Pkt);
736 // set time for next request
737 iNextControlReqeust = timeGetTime() + C4ControlRequestInterval;
738 }
739}
740
741C4GameControlPacket *C4GameControlNetwork::PackCompleteCtrl(int32_t iTick)
742{
743 CStdLock CtrlLock(&CtrlCSec);
744 CStdLock ClientLock(&ClientsCSec);
745
746 // check if ctrl by all clients is ready
747 C4GameControlClient *pClient;
748 for (pClient = pClients; pClient; pClient = pClient->pNext)
749 if (!getCtrl(iClientID: pClient->getClientID(), iCtrlTick: iTick))
750 break;
751 if (pClient)
752 {
753 // async mode: wait n extra frames for slow clients
754 const int iMaxWait = Game.Control.ControlRate * (Config.Network.AsyncMaxWait * 1000) / iTargetFPS;
755 if (eMode != CNM_Async || iWaitStart == -1 || timeGetTime() <= iWaitStart + iMaxWait)
756 return nullptr;
757 }
758
759 // create packet
760 C4GameControlPacket *pComplete = new C4GameControlPacket();
761 pComplete->Set(iClientID: C4ClientIDAll, iCtrlTick: iTick);
762
763 // pack everything in ID order (client list is ordered this way)
764 for (pClient = pClients; pClient; pClient = pClient->pNext)
765 {
766 if (const auto pCtrl = getCtrl(iClientID: pClient->getClientID(), iCtrlTick: iTick))
767 {
768 pComplete->Add(Ctrl: *pCtrl);
769 }
770 }
771
772 // add to list
773 AddCtrl(pCtrl: pComplete);
774
775 // host: send to clients (central and async mode)
776 if (eMode != CNM_Decentral)
777 Game.Network.Clients.BroadcastMsgToConnClients(rPkt: MkC4NetIOPacket(cStatus: PID_Control, Pkt: *pComplete));
778
779 // advance control request time
780 iNextControlReqeust = std::max<uint32_t>(a: iNextControlReqeust, b: timeGetTime() + C4ControlRequestInterval);
781
782 // return
783 return pComplete;
784}
785
786void C4GameControlNetwork::AddSyncCtrlToQueue(const C4Control &Ctrl, int32_t iTick) // by main thread
787{
788 // search place in queue. It's vitally important that new packets are placed
789 // behind packets for the same tick, so they will be executed in the right order.
790 C4GameControlPacket *pAfter = nullptr, *pBefore = pSyncCtrlQueue;
791 while (pBefore && pBefore->getCtrlTick() <= iTick)
792 {
793 pAfter = pBefore; pBefore = pBefore->pNext;
794 }
795 // create
796 C4GameControlPacket *pnPkt = new C4GameControlPacket();
797 pnPkt->Set(iClientID: C4ClientIDUnknown, iCtrlTick: iTick, Ctrl);
798 // insert
799 (pAfter ? pAfter->pNext : pSyncCtrlQueue) = pnPkt;
800 pnPkt->pNext = pBefore;
801}
802
803void C4GameControlNetwork::ExecQueuedSyncCtrl() // by main thread
804{
805 // security
806 while (pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() < pParent->ControlTick)
807 {
808 pParent->GetLogger()->error(fmt: "Fatal: got sync control to execute for ctrl tick {}, but already in ctrl tick {}!", args: pSyncCtrlQueue->getCtrlTick(), args&: pParent->ControlTick);
809 // remove it
810 C4GameControlPacket *pPkt = pSyncCtrlQueue;
811 pSyncCtrlQueue = pPkt->pNext;
812 delete pPkt;
813 }
814 // nothing to do?
815 if (!pSyncCtrlQueue || pSyncCtrlQueue->getCtrlTick() > pParent->ControlTick)
816 return;
817 // this should hold by now
818 assert(pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() == pParent->ControlTick);
819 do
820 {
821 // execute it
822 pParent->ExecControl(rCtrl: pSyncCtrlQueue->getControl());
823 // remove the packet
824 C4GameControlPacket *pPkt = pSyncCtrlQueue;
825 pSyncCtrlQueue = pPkt->pNext;
826 delete pPkt;
827 } while (pSyncCtrlQueue && pSyncCtrlQueue->getCtrlTick() == pParent->ControlTick);
828 // refresh copy of client list
829 CopyClientList(rClients: Game.Clients);
830}
831
832// *** C4GameControlPacket
833
834C4GameControlPacket::C4GameControlPacket()
835 : iClientID(C4ClientIDUnknown),
836 iCtrlTick(-1),
837 iTime(timeGetTime()),
838 pNext(nullptr) {}
839
840C4GameControlPacket::C4GameControlPacket(const C4GameControlPacket &Pkt2)
841 : C4PacketBase(Pkt2), iClientID(Pkt2.getClientID()),
842 iCtrlTick(Pkt2.getCtrlTick()),
843 iTime(timeGetTime()),
844 pNext(nullptr)
845{
846 Ctrl.Copy(Ctrl: Pkt2.getControl());
847}
848
849void C4GameControlPacket::Set(int32_t inClientID, int32_t inCtrlTick)
850{
851 iClientID = inClientID;
852 iCtrlTick = inCtrlTick;
853}
854
855void C4GameControlPacket::Set(int32_t inClientID, int32_t inCtrlTick, const C4Control &nCtrl)
856{
857 iClientID = inClientID;
858 iCtrlTick = inCtrlTick;
859 Ctrl.Copy(Ctrl: nCtrl);
860}
861
862void C4GameControlPacket::Add(const C4GameControlPacket &Ctrl2)
863{
864 Ctrl.Append(Ctrl: Ctrl2.getControl());
865}
866
867void C4GameControlPacket::CompileFunc(StdCompiler *pComp)
868{
869 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iClientID), szName: "ClientID", rDefault: C4ClientIDUnknown));
870 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iCtrlTick), szName: "CtrlTick", rDefault: -1));
871 pComp->Value(rStruct: mkNamingAdapt(rValue&: Ctrl, szName: "Ctrl"));
872}
873
874// *** C4GameControlClient
875
876C4GameControlClient::C4GameControlClient()
877 : iClientID(C4ClientIDUnknown), iPerformance(0), iNextControl(0)
878{
879 szName[0] = '\0';
880}
881
882int32_t C4GameControlClient::getPerfStat() const
883{
884 return iPerformance / 100;
885}
886
887void C4GameControlClient::Set(int32_t inClientID, const char *sznName)
888{
889 iClientID = inClientID;
890 SCopy(szSource: sznName, sTarget: szName, iMaxL: sizeof(szName) - 1);
891}
892
893void C4GameControlClient::AddPerf(int32_t iTime)
894{
895 iPerformance += (iTime * 100 - iPerformance) / 100;
896}
897