/// <summary> /// Reads and overwrites this entity snapshot with data from a binary source, but only if the binary source represents a newer snapshot. /// Returns true if this snapshot was actually updated from the deserialized binary source. /// </summary> public bool DeserializeIfNewer(EntitySnapshot previousEntitySnapshot, IReader reader) { int newServerFrameTick = reader.ReadInt32(); if (newServerFrameTick <= this.ServerFrameTick) { return(false); } this.ServerFrameTick = newServerFrameTick; this.EntityArray.Deserialize(previousEntitySnapshot?.EntityArray, reader); return(true); }
/// <summary> /// Returns the oldest entity snapshot that exists in the history buffer. /// </summary> private EntitySnapshot getOldestHistoryEntitySnapshot() { // The snapshot history isn't in any order so we need to check every snapshot EntitySnapshot oldestEntitySnapshot = this.entitySnapshotHistory[0]; foreach (EntitySnapshot entitySnapshot in this.entitySnapshotHistory) { if (entitySnapshot.ServerFrameTick < oldestEntitySnapshot.ServerFrameTick) { oldestEntitySnapshot = entitySnapshot; } } return(oldestEntitySnapshot); }
/// <summary> /// Deserializes a server update (entity snapshot and client-specific data) based on the given reader, basing incoming data on a previous snapshot's data. /// Only overwrites if the reader contains a newer server update. Returns true if the entity snapshot was actually updated from the reader. /// </summary> public static bool DeserializeIfNewer(IReader reader, EntitySnapshot[] previousEntitySnapshots, EntitySnapshot latestEntitySnapshot, out int latestClientTickAcknowledgedByServer, out int clientCommandingEntityID) { latestClientTickAcknowledgedByServer = reader.ReadInt32(); clientCommandingEntityID = reader.ReadInt32(); int previousServerFrameTick = reader.ReadInt32(); EntitySnapshot previousEntitySnapshot = null; foreach (EntitySnapshot entitySnapshot in previousEntitySnapshots) { if (entitySnapshot.ServerFrameTick == previousServerFrameTick) { previousEntitySnapshot = entitySnapshot; break; } } return(latestEntitySnapshot.DeserializeIfNewer(previousEntitySnapshot, reader)); }
/// <summary> /// Updates the server state by processing client input, updating entities, and sending state to clients. /// </summary> public void Update() { this.FrameTick++; foreach (ClientProxy client in this.clients) { int newCommandingEntityID = this.updateCommandingEntityID.Invoke(client.IsConnected, client.CommandingEntityID); if (client.IsConnected) { client.CommandingEntityID = newCommandingEntityID; client.ReceiveClientCommands(this.EntityArray); } else { client.Reset(); } } this.SystemArray.ServerUpdate(this.EntityArray); // Take a snapshot of the latest entity state and add it to the snapshot history buffer (overwriting an old snapshot) EntitySnapshot newEntitySnapshot = this.entitySnapshotHistory.Dequeue(); newEntitySnapshot.Update(this.FrameTick, this.EntityArray); this.entitySnapshotHistory.Enqueue(newEntitySnapshot); if (this.FrameTick % this.NetworkSendRate == 0) { foreach (ClientProxy client in this.clients) { if (client.IsConnected) { client.SendServerUpdate(newEntitySnapshot); } } } }
/// <summary> /// Updates this client's commanding entity so that it responds immediately to client commands (rather than waiting for the server to acknowledge /// the input). /// </summary> private void updatePrediction() { if (this.ShouldPredictInput && this.HasRenderingStarted && this.CommandingEntityID != -1 && this.RenderedSnapshot.EntityArray.TryGetEntity(this.CommandingEntityID, out Entity predictedEntity)) { // Start at the latest server update and apply all client commands that have been taken after that EntitySnapshot latestHistoryEntitySnapshot = this.getLatestHistoryEntitySnapshot(); if (latestHistoryEntitySnapshot.EntityArray.TryGetEntity(this.CommandingEntityID, out Entity latestHistoryEntity)) { latestHistoryEntity.CopyTo(predictedEntity); foreach (ClientCommand <TCommandData> clientCommand in this.clientCommandHistory) { // If the command was already received by the server then it shouldn't be predicted (the results are in the updates sent to us), if the command was for a different // entity than what we are currently commanding then we can't predict at all so ignore it if (!clientCommand.HasData || clientCommand.ClientFrameTick <= this.LatestFrameTickAcknowledgedByServer || clientCommand.CommandingEntityID != this.CommandingEntityID) { continue; } // Reapply all the commands we've sent that the server hasn't processed yet to get us to where we predict we should be this.SystemArray.PredictClientCommand(this.RenderedSnapshot.EntityArray, predictedEntity, clientCommand.CommandData); } } } }
/// <summary> /// Sends the client an update of the given entity state and other information. /// </summary> public void SendServerUpdate(EntitySnapshot latestEntitySnapshot) { EntitySnapshot previousEntitySnapshot = this.parentServer.getEntitySnapshotForServerFrameTick(this.LatestFrameTickAcknowledgedByClient); ServerUpdateSerializer.Send(this.clientNetworkConnection, previousEntitySnapshot, latestEntitySnapshot, this.LatestClientTickReceived, this.CommandingEntityID); }
/// <summary> /// Reads and overwrites this entity snapshot with data from a binary source, basing incoming data on a previous snapshot's data. /// </summary> public void Deserialize(EntitySnapshot previousEntitySnapshot, IReader reader) { this.ServerFrameTick = reader.ReadInt32(); this.EntityArray.Deserialize(previousEntitySnapshot?.EntityArray, reader); }
/// <summary> /// Writes this entity snapshot's data to a binary source, only writing data that has changed from a previous snapshot. /// </summary> public void Serialize(EntitySnapshot previousEntitySnapshot, IWriter writer) { writer.Write(this.ServerFrameTick); this.EntityArray.Serialize(previousEntitySnapshot?.EntityArray, writer); }
/// <summary> /// Copies all entity and meta-data to another entity snapshot. /// </summary> public void CopyTo(EntitySnapshot other) { other.Update(this.ServerFrameTick, this.EntityArray); }
/// <summary> /// Updates the rendered snapshot with latest data from the server, interpolating as necessary. /// </summary> private void updateRenderedSnapshot() { int renderedFrameTick = this.FrameTick - this.InterpolationRenderDelay; if (!this.HasInterpolationStarted) { // We haven't received enough data from the server yet to start interpolation rendering, // so keep polling until we get enough data, once we have enough data we can begin rendering. // The snapshot history isn't in any order so we need to check every snapshot for the closest ticks in both directions (start and end) EntitySnapshot newInterpolationStartSnapshot = null; EntitySnapshot newInterpolationEndSnapshot = null; foreach (EntitySnapshot entitySnapshot in this.entitySnapshotHistory) { if (entitySnapshot.ServerFrameTick == -1) { continue; } if (entitySnapshot.ServerFrameTick <= renderedFrameTick && (newInterpolationStartSnapshot == null || entitySnapshot.ServerFrameTick > newInterpolationStartSnapshot.ServerFrameTick)) { newInterpolationStartSnapshot = entitySnapshot; } if (entitySnapshot.ServerFrameTick > renderedFrameTick && (newInterpolationEndSnapshot == null || entitySnapshot.ServerFrameTick < newInterpolationEndSnapshot.ServerFrameTick)) { newInterpolationEndSnapshot = entitySnapshot; } } if (newInterpolationStartSnapshot != null && newInterpolationEndSnapshot != null) { newInterpolationStartSnapshot.CopyTo(this.InterpolationStartSnapshot); newInterpolationEndSnapshot.CopyTo(this.InterpolationEndSnapshot); } } // Check to see if we still can't interpolate after going through the latest received updates if (!this.HasInterpolationStarted) { return; } if (renderedFrameTick > this.InterpolationEndSnapshot.ServerFrameTick) { // Find the next closest entity snapshot to start interpolating to // The snapshot history isn't in any order so we need to check every snapshot closest next tick EntitySnapshot newInterpolationEndSnapshot = null; foreach (EntitySnapshot entitySnapshot in this.entitySnapshotHistory) { if (entitySnapshot.ServerFrameTick > renderedFrameTick && (newInterpolationEndSnapshot == null || entitySnapshot.ServerFrameTick < newInterpolationEndSnapshot.ServerFrameTick)) { newInterpolationEndSnapshot = entitySnapshot; } } if (newInterpolationEndSnapshot != null) { this.RenderedSnapshot.CopyTo(this.InterpolationStartSnapshot); newInterpolationEndSnapshot.CopyTo(this.InterpolationEndSnapshot); } } if (this.ShouldInterpolate) { // Clamp the interpolation frame tick to the maximum number of frames we are allowed to extrapolate, then render that // Always make sure the rendered state has the correct rendered frame tick number, even if extrapolation was clamped int interpolationFrameTick = Math.Min(renderedFrameTick, this.InterpolationEndSnapshot.ServerFrameTick + this.MaxExtrapolationTicks); this.RenderedSnapshot.Interpolate(this.InterpolationStartSnapshot, this.InterpolationEndSnapshot, interpolationFrameTick, renderedFrameTick); if (interpolationFrameTick < renderedFrameTick) { this.NumberOfNoInterpolationFrames++; } else if (this.InterpolationEndSnapshot.ServerFrameTick < renderedFrameTick) { this.NumberOfExtrapolatedFrames++; } } else { // Even though we aren't interpolating, we still want to use whatever the user picked as the render delay so // use the end interpolation state and just snap to it this.InterpolationEndSnapshot.CopyTo(this.RenderedSnapshot); } }
/// <summary> /// Serializes the given server update (entity snapshot and client-specific data) to the given writer, only writing data that has changed from a previous snapshot. /// </summary> public static void Serialize(IWriter writer, EntitySnapshot previousEntitySnapshot, EntitySnapshot latestEntitySnapshot, int latestClientTickReceived, int clientCommandingEntityID) { writer.Write(latestClientTickReceived); writer.Write(clientCommandingEntityID); writer.Write((previousEntitySnapshot == null) ? -1 : previousEntitySnapshot.ServerFrameTick); latestEntitySnapshot.Serialize(previousEntitySnapshot, writer); }
/// <summary> /// Serializes the given server update (entity snapshot and client-specific data) and immediately sends a packet, only writing data that has changed from a previous snapshot.. /// </summary> public static void Send(INetworkConnection clientNetworkConnection, EntitySnapshot previousEntitySnapshot, EntitySnapshot latestEntitySnapshot, int latestClientTickReceived, int clientCommandingEntityID) { OutgoingMessage outgoingMessage = clientNetworkConnection.GetOutgoingMessageToSend(); ServerUpdateSerializer.Serialize(outgoingMessage, previousEntitySnapshot, latestEntitySnapshot, latestClientTickReceived, clientCommandingEntityID); clientNetworkConnection.SendMessage(outgoingMessage); }