| 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 | |
| 29 | C4GameControlNetwork::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 | |
| 41 | C4GameControlNetwork::~C4GameControlNetwork() |
| 42 | { |
| 43 | Clear(); |
| 44 | } |
| 45 | |
| 46 | bool 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 | |
| 65 | void 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 | |
| 80 | void 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 | |
| 94 | bool 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 | |
| 102 | bool 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 | |
| 120 | bool 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 | |
| 126 | int32_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 | |
| 136 | int32_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 | |
| 145 | bool 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 | |
| 156 | void 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 | |
| 181 | void 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 | |
| 246 | C4ControlDeliveryType 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 | |
| 260 | void 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 | |
| 279 | void 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 | |
| 300 | void 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 | |
| 312 | void C4GameControlNetwork::ClearClients() |
| 313 | { |
| 314 | CStdLock ClientsLock(&ClientsCSec); |
| 315 | while (pClients) { C4GameControlClient *pClient = pClients; RemoveClient(pClient); delete pClient; } |
| 316 | } |
| 317 | |
| 318 | void 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 | |
| 329 | void 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 | |
| 348 | void 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 | |
| 360 | void 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 | |
| 382 | void 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 | |
| 449 | void 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 | |
| 509 | void C4GameControlNetwork::OnResComplete(C4Network2Res *pRes) |
| 510 | { |
| 511 | // player? |
| 512 | if (pRes->getType() == NRT_Player) |
| 513 | // check for ctrl ready |
| 514 | CheckCompleteCtrl(fSetEvent: true); |
| 515 | } |
| 516 | |
| 517 | void 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 | |
| 531 | void 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 | |
| 558 | void 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 | |
| 591 | C4GameControlClient *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 | |
| 600 | void 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 | |
| 615 | void 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 | |
| 633 | C4GameControlPacket *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 | |
| 644 | void 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 | |
| 653 | void 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 | |
| 679 | void 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 | |
| 741 | C4GameControlPacket *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 | |
| 786 | void 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 | |
| 803 | void 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 | |
| 834 | C4GameControlPacket::C4GameControlPacket() |
| 835 | : iClientID(C4ClientIDUnknown), |
| 836 | iCtrlTick(-1), |
| 837 | iTime(timeGetTime()), |
| 838 | pNext(nullptr) {} |
| 839 | |
| 840 | C4GameControlPacket::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 | |
| 849 | void C4GameControlPacket::Set(int32_t inClientID, int32_t inCtrlTick) |
| 850 | { |
| 851 | iClientID = inClientID; |
| 852 | iCtrlTick = inCtrlTick; |
| 853 | } |
| 854 | |
| 855 | void 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 | |
| 862 | void C4GameControlPacket::Add(const C4GameControlPacket &Ctrl2) |
| 863 | { |
| 864 | Ctrl.Append(Ctrl: Ctrl2.getControl()); |
| 865 | } |
| 866 | |
| 867 | void 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 | |
| 876 | C4GameControlClient::C4GameControlClient() |
| 877 | : iClientID(C4ClientIDUnknown), iPerformance(0), iNextControl(0) |
| 878 | { |
| 879 | szName[0] = '\0'; |
| 880 | } |
| 881 | |
| 882 | int32_t C4GameControlClient::getPerfStat() const |
| 883 | { |
| 884 | return iPerformance / 100; |
| 885 | } |
| 886 | |
| 887 | void C4GameControlClient::Set(int32_t inClientID, const char *sznName) |
| 888 | { |
| 889 | iClientID = inClientID; |
| 890 | SCopy(szSource: sznName, sTarget: szName, iMaxL: sizeof(szName) - 1); |
| 891 | } |
| 892 | |
| 893 | void C4GameControlClient::AddPerf(int32_t iTime) |
| 894 | { |
| 895 | iPerformance += (iTime * 100 - iPerformance) / 100; |
| 896 | } |
| 897 | |