/// <summary> /// Checks for and processes any new commands coming in from the client. /// </summary> public void ReceiveClientCommands(EntityArray entityArray) { IncomingMessage incomingMessage; while ((incomingMessage = this.clientNetworkConnection.GetNextIncomingMessage()) != null) { int numberOfCommands = ClientUpdateSerializer <TCommandData> .Deserialize(incomingMessage, this.deserializedClientCommandHistory, out int newlatestFrameTickAcknowledgedByClient); if (this.LatestFrameTickAcknowledgedByClient < newlatestFrameTickAcknowledgedByClient) { this.LatestFrameTickAcknowledgedByClient = newlatestFrameTickAcknowledgedByClient; } for (int i = 0; i < numberOfCommands; i++) { ClientCommand <TCommandData> clientCommand = this.deserializedClientCommandHistory[i]; // Make sure we don't process a command we've already received and processed in a previous tick if (!clientCommand.HasData || clientCommand.ClientFrameTick <= this.LatestClientTickReceived) { continue; } this.parentServer.executeClientCommand(this, clientCommand); if (this.LatestClientTickReceived < clientCommand.ClientFrameTick) { this.LatestClientTickReceived = clientCommand.ClientFrameTick; } } } }
/// <summary> /// Updates the client state by processing server snapshots, updating rendered frame (with interpolation and prediction), and sending commands to the server. /// </summary> public void Update(TCommandData commandData) { Log <LogGameClient> .StartNew(); if (this.LatestServerTickReceived >= 0) { // Only tick the client if we started getting data from the server (i.e. fully connected) this.FrameTick++; } this.receiveServerUpdates(); if (this.HasRenderingStarted) { // Once we are rendering we can start taking user commands and sending them to the server // Take the latest command and add it to the command history buffer (overwriting an old command) ClientCommand <TCommandData> newClientCommand = this.clientCommandHistory.Dequeue(); newClientCommand.Update(this.FrameTick, this.RenderedSnapshot.ServerFrameTick, this.InterpolationStartSnapshot.ServerFrameTick, this.InterpolationEndSnapshot.ServerFrameTick, this.ShouldInterpolate, this.CommandingEntityID, commandData); this.clientCommandHistory.Enqueue(newClientCommand); if (this.FrameTick % this.NetworkSendRate == 0) { ClientUpdateSerializer <TCommandData> .Send(this.serverNetworkConnection, this.clientCommandHistory, this.LatestServerTickReceived, this.LatestFrameTickAcknowledgedByServer); } } this.updateRenderedSnapshot(); this.updatePrediction(); if (this.HasRenderingStarted) { this.SystemArray.ClientUpdate(this.RenderedSnapshot.EntityArray, this.GetCommandingEntity()); } }
/// <summary> /// Executes a given client command on behalf of the given client. /// </summary> private void executeClientCommand(ClientProxy client, ClientCommand <TCommandData> clientCommand) { // Make sure we have an entity to command, that the client thinks its commanding that same entity, and that the entity actually exists if (client.CommandingEntityID != -1 && clientCommand.CommandingEntityID == client.CommandingEntityID && this.EntityArray.TryGetEntity(client.CommandingEntityID, out Entity commandingEntity)) { EntitySnapshot lagCompensationSnapshot = this.getEntitySnapshotForServerFrameTick(clientCommand.RenderedTick); this.SystemArray.ProcessClientCommand(this.EntityArray, commandingEntity, clientCommand.CommandData, lagCompensationSnapshot?.EntityArray); } }