void ReadSnapshot <TInputStream>(int sequence, ref TInputStream input) where TInputStream : NetworkCompression.IInputStream { //input.SetStatsType(NetworkCompressionReader.Type.SnapshotSchema); counters.snapshotsIn++; // Snapshot may be delta compressed against one or more baselines // Baselines are indicated by sequence number of the package it was in var baseSequence = (int)input.ReadPackedIntDelta(sequence - 1, NetworkConfig.baseSequenceContext); bool enableNetworkPrediction = input.ReadRawBits(1) != 0; bool enableHashing = input.ReadRawBits(1) != 0; int baseSequence1 = 0; int baseSequence2 = 0; if (enableNetworkPrediction) { baseSequence1 = (int)input.ReadPackedIntDelta(baseSequence - 1, NetworkConfig.baseSequence1Context); baseSequence2 = (int)input.ReadPackedIntDelta(baseSequence1 - 1, NetworkConfig.baseSequence2Context); } if (clientDebug.IntValue > 2) { if (enableNetworkPrediction) { GameDebug.Log((baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + "(" + sequence + ") " + baseSequence + " - " + baseSequence1 + " - " + baseSequence2); } else { GameDebug.Log((baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + "(" + sequence + ") " + baseSequence); } } if (baseSequence == 0) { counters.fullSnapshotsIn++; } GameDebug.Assert(baseSequence == 0 || (sequence > baseSequence && sequence - baseSequence < NetworkConfig.snapshotDeltaCacheSize), "Attempting snapshot encoding with invalid baseline: {0}:{1}", sequence, baseSequence); var snapshotInfo = snapshots.Acquire(sequence); snapshotInfo.serverTime = (int)input.ReadPackedIntDelta(baseSequence != 0 ? snapshots[baseSequence].serverTime : 0, NetworkConfig.serverTimeContext); var temp = (int)input.ReadRawBits(8); serverSimTime = temp * 0.1f; // Only update time if received in-order.. // TODO consider dropping out of order snapshots // TODO detecting out-of-order on pack sequences if (snapshotInfo.serverTime > serverTime) { serverTime = snapshotInfo.serverTime; snapshotReceivedTime = NetworkUtils.stopwatch.ElapsedMilliseconds; } else { GameDebug.Log(string.Format("NetworkClient. Dropping out of order snaphot. Server time:{0} snapshot time:{1}", serverTime, snapshotInfo.serverTime)); } // Read schemas var schemaCount = input.ReadPackedUInt(NetworkConfig.schemaCountContext); for (int schemaIndex = 0; schemaIndex < schemaCount; ++schemaIndex) { var typeId = (ushort)input.ReadPackedUInt(NetworkConfig.schemaTypeIdContext); var entityType = new EntityTypeInfo() { typeId = typeId }; entityType.schema = NetworkSchema.ReadSchema(ref input); entityType.baseline = new byte[NetworkConfig.maxEntitySnapshotDataSize]; NetworkSchema.CopyFieldsToBuffer(entityType.schema, ref input, entityType.baseline); if (!entityTypes.ContainsKey(typeId)) { entityTypes.Add(typeId, entityType); } } // Remove any despawning entities that belong to older base sequences for (int i = 0; i < entities.Count; i++) { var e = entities[i]; if (e.type == null) { continue; } if (e.despawnSequence > 0 && e.despawnSequence <= baseSequence) { e.Reset(); } } // Read new spawns m_TempSpawnList.Clear(); var previousId = 1; var spawnCount = input.ReadPackedUInt(NetworkConfig.spawnCountContext); for (var spawnIndex = 0; spawnIndex < spawnCount; ++spawnIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; // Register the entity var typeId = (ushort)input.ReadPackedUInt(NetworkConfig.spawnTypeIdContext); //TODO: use another encoding GameDebug.Assert(entityTypes.ContainsKey(typeId), "Spawn request with unknown type id {0}", typeId); byte fieldMask = (byte)input.ReadRawBits(8); // Check if we already registered the entity since we can receive spawn information // in several snapshots before the client has ack a package containing the spawn // TODO (petera) need an max entity id for safety while (id >= entities.Count) { entities.Add(new EntityInfo()); } if (entities[id].type == null) { var e = entities[id]; e.type = entityTypes[typeId]; e.fieldMask = fieldMask; spawns.Add(id); } m_TempSpawnList.Add(id); } // Read despawns var despawnCount = input.ReadPackedUInt(NetworkConfig.despawnCountContext); for (var despawnIndex = 0; despawnIndex < despawnCount; ++despawnIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; // we may see despawns many times, only handle if we still have the entity GameDebug.Assert(id < entities.Count, "Getting despawn for id {0} but we only know about entities up to {1}", id, entities.Count); if (entities[id].type == null) { continue; } var entity = entities[id]; // Already in the process of being despawned. This happens with same-snapshot spawn/despawn cases if (entity.despawnSequence > 0) { continue; } // If we are spawning and despawning in same snapshot, delay actual deletion of // entity as we need it around to be able to read the update part of the snapshot if (m_TempSpawnList.Contains(id)) { entity.despawnSequence = sequence; // keep until baseSequence >= despawnSequence } else { entity.Reset(); // otherwise remove right away; no further updates coming, not even in this snap } // Add to despawns list so we can request despawn from game later GameDebug.Assert(!despawns.Contains(id), "Double despawn in same snaphot? {0}", id); despawns.Add(id); } // Predict all active entities for (var id = 0; id < entities.Count; id++) { var info = entities[id]; if (info.type == null) { continue; } // NOTE : As long as the server haven't gotten the spawn acked, it will keep sending // delta relative to 0, so we need to check if the entity was in the spawn list to determine // if the delta is relative to the last update or not int baseline0Time = 0; byte[] baseline0 = info.type.baseline; GameDebug.Assert(baseline0 != null, "Unable to find schema baseline for type {0}", info.type.typeId); if (baseSequence != 0 && !m_TempSpawnList.Contains(id)) { baseline0 = info.baselines.FindMax(baseSequence); GameDebug.Assert(baseline0 != null, "Unable to find baseline for seq {0} for id {1}", baseSequence, id); baseline0Time = snapshots[baseSequence].serverTime; } if (enableNetworkPrediction) { uint num_baselines = 1; // 1 because either we have schema baseline or we have a real baseline int baseline1Time = 0; int baseline2Time = 0; byte[] baseline1 = null; byte[] baseline2 = null; if (baseSequence1 != baseSequence) { baseline1 = info.baselines.FindMax(baseSequence1); if (baseline1 != null) { num_baselines = 2; baseline1Time = snapshots[baseSequence1].serverTime; } if (baseSequence2 != baseSequence1) { baseline2 = info.baselines.FindMax(baseSequence2); if (baseline2 != null) { num_baselines = 3; baseline2Time = snapshots[baseSequence2].serverTime; } } } // TODO (petera) are these clears needed? for (int i = 0, c = info.fieldsChangedPrediction.Length; i < c; ++i) { info.fieldsChangedPrediction[i] = 0; } for (int i = 0; i < NetworkConfig.maxEntitySnapshotDataSize; i++) { info.prediction[i] = 0; } NetworkPrediction.PredictSnapshot(info.prediction, info.fieldsChangedPrediction, info.type.schema, num_baselines, (uint)baseline0Time, baseline0, (uint)baseline1Time, baseline1, (uint)baseline2Time, baseline2, (uint)snapshotInfo.serverTime, info.fieldMask); } else { var f = info.fieldsChangedPrediction; for (var i = 0; i < f.Length; ++i) { f[i] = 0; } NetworkUtils.MemCopy(baseline0, 0, info.prediction, 0, info.type.schema.GetByteSize()); } } // Read updates var updateCount = input.ReadPackedUInt(NetworkConfig.updateCountContext); for (var updateIndex = 0; updateIndex < updateCount; ++updateIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; var info = entities[id]; uint hash = 0; // Copy prediction to temp buffer as we now overwrite info.prediction with fully unpacked // state by applying incoming delta to prediction. NetworkUtils.MemCopy(info.prediction, 0, tempSnapshotBuffer, 0, info.type.schema.GetByteSize()); DeltaReader.Read(ref input, info.type.schema, info.prediction, tempSnapshotBuffer, info.fieldsChangedPrediction, info.fieldMask, ref hash); if (enableHashing) { uint hashCheck = input.ReadRawBits(32); if (hash != hashCheck) { GameDebug.Log("Hash check fail for entity " + id); if (enableNetworkPrediction) { GameDebug.Assert(false, "Snapshot (" + snapshotInfo.serverTime + ") " + (baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + " - " + baseSequence1 + " - " + baseSequence2 + ". Sche: " + schemaCount + " Spwns: " + spawnCount + " Desp: " + despawnCount + " Upd: " + updateCount); } else { GameDebug.Assert(false, "Snapshot (" + snapshotInfo.serverTime + ") " + (baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + ". Sche: " + schemaCount + " Spwns: " + spawnCount + " Desp: " + despawnCount + " Upd: " + updateCount); } } } } uint snapshotHash = 0; // sum of hash for all (updated or not) entity snapshots uint numEnts = 0; for (int id = 0; id < entities.Count; id++) { var info = entities[id]; if (info.type == null) { continue; } // Skip despawned that have not also been spawned in this snapshot if (info.despawnSequence > 0 && !spawns.Contains(id)) { continue; } // If just spawned or if new snapshot is different from the last we deserialized, // we need to deserialize. Otherwise just ignore; no reason to deserialize the same // values again int schemaSize = info.type.schema.GetByteSize(); if (info.baselines.GetSize() == 0 || NetworkUtils.MemCmp(info.prediction, 0, info.lastUpdate, 0, schemaSize) != 0) { var data = info.baselines.Insert(sequence); NetworkUtils.MemCopy(info.prediction, 0, data, 0, schemaSize); if (sequence > info.lastUpdateSequence) { if (!updates.Contains(id)) { updates.Add(id); } NetworkUtils.MemCopy(info.prediction, 0, info.lastUpdate, 0, schemaSize); info.lastUpdateSequence = sequence; } } if (enableHashing && info.despawnSequence == 0) { snapshotHash += NetworkUtils.SimpleHash(info.prediction, schemaSize); numEnts++; } } if (clientDebug.IntValue > 1) { if (clientDebug.IntValue > 2 || spawnCount > 0 || despawnCount > 0 || schemaCount > 0 || baseSequence == 0) { string entityIds = ""; for (var i = 0; i < entities.Count; i++) { entityIds += entities[i].type == null ? ",-" : ("," + i); } string despawnIds = string.Join(",", despawns); string spawnIds = string.Join(",", spawns); string updateIds = string.Join(",", updates); if (enableNetworkPrediction) { GameDebug.Log((baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + " - " + baseSequence1 + " - " + baseSequence2 + ". Sche: " + schemaCount + " Spwns: " + spawnCount + "(" + spawnIds + ") Desp: " + despawnCount + "(" + despawnIds + ") Upd: " + updateCount + "(" + updateIds + ") Ents:" + entities.Count + " EntityIds:" + entityIds); } else { GameDebug.Log((baseSequence > 0 ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + ". Sche: " + schemaCount + " Spwns: " + spawnCount + "(" + spawnIds + ") Desp: " + despawnCount + "(" + despawnIds + ") Upd: " + updateCount + "(" + updateIds + ") Ents:" + entities.Count + " EntityIds:" + entityIds); } } } if (enableHashing) { uint numEntsCheck = input.ReadRawBits(32); if (numEntsCheck != numEnts) { GameDebug.Log("SYNC PROBLEM: server num ents: " + numEntsCheck + " us:" + numEnts); GameDebug.Assert(false); } } }
unsafe void ReadSnapshot <TInputStream>(int sequence, ref TInputStream input, ISnapshotConsumer consumer) where TInputStream : NetworkCompression.IInputStream { //input.SetStatsType(NetworkCompressionReader.Type.SnapshotSchema); counters.snapshotsIn++; // Snapshot may be delta compressed against one or more baselines // Baselines are indicated by sequence number of the package it was in var haveBaseline = input.ReadRawBits(1) == 1; var baseSequence = (int)input.ReadPackedIntDelta(sequence - 1, NetworkConfig.baseSequenceContext); bool enableNetworkPrediction = input.ReadRawBits(1) != 0; bool enableHashing = input.ReadRawBits(1) != 0; int baseSequence1 = 0; int baseSequence2 = 0; if (enableNetworkPrediction) { baseSequence1 = (int)input.ReadPackedIntDelta(baseSequence - 1, NetworkConfig.baseSequence1Context); baseSequence2 = (int)input.ReadPackedIntDelta(baseSequence1 - 1, NetworkConfig.baseSequence2Context); } if (clientDebug.IntValue > 2) { if (enableNetworkPrediction) { GameDebug.Log((haveBaseline ? "Snap [BL]" : "Snap [ ]") + "(" + sequence + ") " + baseSequence + " - " + baseSequence1 + " - " + baseSequence2); } else { GameDebug.Log((haveBaseline ? "Snap [BL]" : "Snap [ ]") + "(" + sequence + ") " + baseSequence); } } if (!haveBaseline) { counters.fullSnapshotsIn++; } GameDebug.Assert(!haveBaseline || (sequence > baseSequence && sequence - baseSequence < NetworkConfig.snapshotDeltaCacheSize), "Attempting snapshot encoding with invalid baseline: {0}:{1}", sequence, baseSequence); var snapshotInfo = snapshots.Acquire(sequence); snapshotInfo.serverTime = (int)input.ReadPackedIntDelta(haveBaseline ? snapshots[baseSequence].serverTime : 0, NetworkConfig.serverTimeContext); var temp = (int)input.ReadRawBits(8); serverSimTime = temp * 0.1f; // Only update time if received in-order.. // TODO consider dropping out of order snapshots // TODO detecting out-of-order on pack sequences if (snapshotInfo.serverTime > serverTime) { serverTime = snapshotInfo.serverTime; snapshotReceivedTime = NetworkUtils.stopwatch.ElapsedMilliseconds; } else { GameDebug.Log(string.Format("NetworkClient. Dropping out of order snaphot. Server time:{0} snapshot time:{1}", serverTime, snapshotInfo.serverTime)); } counters.AddSectionStats("snapShotHeader", input.GetBitPosition2(), new Color(0.5f, 0.5f, 0.5f)); // Used by thinclient that wants to very cheaply just do minimal handling of // snapshots if (m_DropSnapshots) { return; } // Read schemas var schemaCount = input.ReadPackedUInt(NetworkConfig.schemaCountContext); for (int schemaIndex = 0; schemaIndex < schemaCount; ++schemaIndex) { var typeId = (ushort)input.ReadPackedUInt(NetworkConfig.schemaTypeIdContext); var entityType = new EntityTypeInfo() { typeId = typeId }; entityType.schema = NetworkSchema.ReadSchema(ref input); counters.AddSectionStats("snapShotSchemas", input.GetBitPosition2(), new Color(0.0f, (schemaIndex & 1) == 1 ? 0.5f : 1.0f, 1.0f)); entityType.baseline = new uint[NetworkConfig.maxEntitySnapshotDataSize]; NetworkSchema.CopyFieldsToBuffer(entityType.schema, ref input, entityType.baseline); if (!entityTypes.ContainsKey(typeId)) { entityTypes.Add(typeId, entityType); } counters.AddSectionStats("snapShotSchemas", input.GetBitPosition2(), new Color(1.0f, (schemaIndex & 1) == 1 ? 0.5f : 1.0f, 1.0f)); } // Remove any despawning entities that belong to older base sequences for (int i = 0; i < entities.Count; i++) { var e = entities[i]; if (e.type == null) { continue; } if (e.despawnSequence > 0 && e.despawnSequence <= baseSequence) { e.Reset(); } } // Read new spawns m_TempSpawnList.Clear(); var previousId = 1; var spawnCount = input.ReadPackedUInt(NetworkConfig.spawnCountContext); for (var spawnIndex = 0; spawnIndex < spawnCount; ++spawnIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; // Register the entity var typeId = (ushort)input.ReadPackedUInt(NetworkConfig.spawnTypeIdContext); //TODO: use another encoding GameDebug.Assert(entityTypes.ContainsKey(typeId), "Spawn request with unknown type id {0}", typeId); byte fieldMask = (byte)input.ReadRawBits(8); // TODO (petera) need an max entity id for safety while (id >= entities.Count) { entities.Add(new EntityInfo()); } // Incoming spawn of different type than what we have for this id, so immediately nuke // the one we have to make room for the incoming if (entities[id].type != null && entities[id].type.typeId != typeId) { // This should only ever happen in case of no baseline as normally the server will // not reuse an id before all clients have acknowledged its despawn. GameDebug.Assert(haveBaseline == false, "Spawning entity but we already have with different type?"); GameDebug.Log("REPLACING old entity: " + id + " because snapshot gave us new type for this id"); despawns.Add(id); entities[id].Reset(); } // We can receive spawn information in several snapshots before our ack // has reached the server. Only pass on spawn to game layer once if (entities[id].type == null) { var e = entities[id]; e.type = entityTypes[typeId]; e.fieldMask = fieldMask; spawns.Add(id); } m_TempSpawnList.Add(id); } counters.AddSectionStats("snapShotSpawns", input.GetBitPosition2(), new Color(0, 0.58f, 0)); // Read despawns var despawnCount = input.ReadPackedUInt(NetworkConfig.despawnCountContext); // If we have no baseline, we need to clear all entities that are not being spawned if (!haveBaseline) { GameDebug.Assert(despawnCount == 0, "There should not be any despawns in a non-baseline snapshot"); for (int i = 0, c = entities.Count; i < c; ++i) { var e = entities[i]; if (e.type == null) { continue; } if (m_TempSpawnList.Contains(i)) { continue; } GameDebug.Log("NO BL SO PRUNING Stale entity: " + i); despawns.Add(i); e.Reset(); } } for (var despawnIndex = 0; despawnIndex < despawnCount; ++despawnIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; // we may see despawns many times, only handle if we still have the entity GameDebug.Assert(id < entities.Count, "Getting despawn for id {0} but we only know about entities up to {1}", id, entities.Count); if (entities[id].type == null) { continue; } var entity = entities[id]; // Already in the process of being despawned. This happens with same-snapshot spawn/despawn cases if (entity.despawnSequence > 0) { continue; } // If we are spawning and despawning in same snapshot, delay actual deletion of // entity as we need it around to be able to read the update part of the snapshot if (m_TempSpawnList.Contains(id)) { entity.despawnSequence = sequence; // keep until baseSequence >= despawnSequence } else { entity.Reset(); // otherwise remove right away; no further updates coming, not even in this snap } // Add to despawns list so we can request despawn from game later GameDebug.Assert(!despawns.Contains(id), "Double despawn in same snaphot? {0}", id); despawns.Add(id); } counters.AddSectionStats("snapShotDespawns", input.GetBitPosition2(), new Color(0.49f, 0, 0)); // Predict all active entities for (var id = 0; id < entities.Count; id++) { var info = entities[id]; if (info.type == null) { continue; } // NOTE : As long as the server haven't gotten the spawn acked, it will keep sending // delta relative to 0, so we need to check if the entity was in the spawn list to determine // if the delta is relative to the last update or not int baseline0Time = 0; uint[] baseline0 = info.type.baseline; GameDebug.Assert(baseline0 != null, "Unable to find schema baseline for type {0}", info.type.typeId); if (haveBaseline && !m_TempSpawnList.Contains(id)) { baseline0 = info.baselines.FindMax(baseSequence); GameDebug.Assert(baseline0 != null, "Unable to find baseline for seq {0} for id {1}", baseSequence, id); baseline0Time = snapshots[baseSequence].serverTime; } if (enableNetworkPrediction) { uint num_baselines = 1; // 1 because either we have schema baseline or we have a real baseline int baseline1Time = 0; int baseline2Time = 0; uint[] baseline1 = null; uint[] baseline2 = null; if (baseSequence1 != baseSequence) { baseline1 = info.baselines.FindMax(baseSequence1); if (baseline1 != null) { num_baselines = 2; baseline1Time = snapshots[baseSequence1].serverTime; } if (baseSequence2 != baseSequence1) { baseline2 = info.baselines.FindMax(baseSequence2); if (baseline2 != null) { num_baselines = 3; baseline2Time = snapshots[baseSequence2].serverTime; } } } // TODO (petera) are these clears needed? for (int i = 0, c = info.fieldsChangedPrediction.Length; i < c; ++i) { info.fieldsChangedPrediction[i] = 0; } for (int i = 0; i < NetworkConfig.maxEntitySnapshotDataSize; i++) { info.prediction[i] = 0; fixed(uint *prediction = info.prediction, baseline0p = baseline0, baseline1p = baseline1, baseline2p = baseline2) { NetworkPrediction.PredictSnapshot(prediction, info.fieldsChangedPrediction, info.type.schema, num_baselines, (uint)baseline0Time, baseline0p, (uint)baseline1Time, baseline1p, (uint)baseline2Time, baseline2p, (uint)snapshotInfo.serverTime, info.fieldMask); } } else { var f = info.fieldsChangedPrediction; for (var i = 0; i < f.Length; ++i) { f[i] = 0; } for (int i = 0, c = info.type.schema.GetByteSize() / 4; i < c; ++i) { info.prediction[i] = baseline0[i]; } } } // Read updates var updateCount = input.ReadPackedUInt(NetworkConfig.updateCountContext); for (var updateIndex = 0; updateIndex < updateCount; ++updateIndex) { var id = (int)input.ReadPackedIntDelta(previousId, NetworkConfig.idContext); previousId = id; var info = entities[id]; uint hash = 0; // Copy prediction to temp buffer as we now overwrite info.prediction with fully unpacked // state by applying incoming delta to prediction. for (int i = 0, c = info.type.schema.GetByteSize() / 4; i < c; ++i) { tempSnapshotBuffer[i] = info.prediction[i]; } DeltaReader.Read(ref input, info.type.schema, info.prediction, tempSnapshotBuffer, info.fieldsChangedPrediction, info.fieldMask, ref hash); if (enableHashing) { uint hashCheck = input.ReadRawBits(32); if (hash != hashCheck) { GameDebug.Log("Hash check fail for entity " + id); if (enableNetworkPrediction) { GameDebug.Assert(false, "Snapshot (" + snapshotInfo.serverTime + ") " + (haveBaseline ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + " - " + baseSequence1 + " - " + baseSequence2 + ". Sche: " + schemaCount + " Spwns: " + spawnCount + " Desp: " + despawnCount + " Upd: " + updateCount); } else { GameDebug.Assert(false, "Snapshot (" + snapshotInfo.serverTime + ") " + (haveBaseline ? "Snap [BL]" : "Snap [ ]") + " " + baseSequence + ". Sche: " + schemaCount + " Spwns: " + spawnCount + " Desp: " + despawnCount + " Upd: " + updateCount); } } } } if (enableNetworkPrediction) { counters.AddSectionStats("snapShotUpdatesPredict", input.GetBitPosition2(), haveBaseline ? new Color(0.09f, 0.38f, 0.93f) : Color.cyan); } else { counters.AddSectionStats("snapShotUpdatesNoPredict", input.GetBitPosition2(), haveBaseline ? new Color(0.09f, 0.38f, 0.93f) : Color.cyan); } uint snapshotHash = 0; // sum of hash for all (updated or not) entity snapshots uint numEnts = 0; for (int id = 0; id < entities.Count; id++) { var info = entities[id]; if (info.type == null) { continue; } // Skip despawned that have not also been spawned in this snapshot if (info.despawnSequence > 0 && !spawns.Contains(id)) { continue; } // If just spawned or if new snapshot is different from the last we deserialized, // we need to deserialize. Otherwise just ignore; no reason to deserialize the same // values again int schemaSize = info.type.schema.GetByteSize(); if (info.baselines.GetSize() == 0 || NetworkUtils.MemCmp(info.prediction, 0, info.lastUpdate, 0, schemaSize) != 0) { var data = info.baselines.Insert(sequence); for (int i = 0; i < schemaSize / 4; ++i) data[i] = info.prediction[i]; } if (sequence > info.lastUpdateSequence) { if (!updates.Contains(id)) { updates.Add(id); } for (int i = 0; i < schemaSize / 4; ++i) { info.lastUpdate[i] = info.prediction[i]; } info.lastUpdateSequence = sequence; } } if (enableHashing && info.despawnSequence == 0) { snapshotHash += NetworkUtils.SimpleHash(info.prediction, schemaSize); numEnts++; } }
unsafe void WriteSnapshot(ref RawOutputStream output) { Profiler.BeginSample("NetworkServer.WriteSnapshot()"); AddMessageContentFlag(NetworkMessage.Snapshot); bool enableNetworkPrediction = network_prediction.IntValue != 0; //bool enableHashing = debug_hashing.IntValue != 0; // Check if the baseline from the client is too old. We keep N number of snapshots on the server // so if the client baseline is older than that we cannot generate the snapshot. Furthermore, we require // the client to keep the last N updates for any entity, so even though the client might have much older // baselines for some entities we cannot guarantee it. // TODO : Can we make this simpler? var haveBaseline = maxSnapshotAck != 0; if (_server.m_ServerSequence - maxSnapshotAck >= NetworkConfig.snapshotDeltaCacheSize - 2) // -2 because we want 3 baselines! { if (serverDebug.IntValue > 0) { GameDebug.Log("ServerSequence ahead of latest ack'ed snapshot by more than cache size. " + (haveBaseline ? "nobaseline" : "baseline")); } haveBaseline = false; } var baseline = haveBaseline ? maxSnapshotAck : 0; int snapshot0Baseline = baseline; int snapshot1Baseline = baseline; int snapshot2Baseline = baseline; int snapshot0BaselineClient = snapshotPackageBaseline; int snapshot1BaselineClient = snapshotPackageBaseline; int snapshot2BaselineClient = snapshotPackageBaseline; if (enableNetworkPrediction && haveBaseline) { var end = snapshotPackageBaseline - NetworkConfig.clientAckCacheSize; end = end < 0 ? 0 : end; var a = snapshotPackageBaseline - 1; while (a > end) { if (snapshotAcks[a % NetworkConfig.clientAckCacheSize]) { var base1 = snapshotSeqs[a % NetworkConfig.clientAckCacheSize]; if (_server.m_ServerSequence - base1 < NetworkConfig.snapshotDeltaCacheSize - 2) { snapshot1Baseline = base1; snapshot1BaselineClient = a; snapshot2Baseline = snapshotSeqs[a % NetworkConfig.clientAckCacheSize]; snapshot2BaselineClient = a; } break; } a--; } a--; while (a > end) { if (snapshotAcks[a % NetworkConfig.clientAckCacheSize]) { var base2 = snapshotSeqs[a % NetworkConfig.clientAckCacheSize]; if (_server.m_ServerSequence - base2 < NetworkConfig.snapshotDeltaCacheSize - 2) { snapshot2Baseline = base2; snapshot2BaselineClient = a; } break; } a--; } } output.WriteRawBits(haveBaseline ? 1u : 0, 1); output.WritePackedIntDelta(snapshot0BaselineClient, outSequence - 1, NetworkConfig.baseSequenceContext); output.WriteRawBits(enableNetworkPrediction ? 1u : 0u, 1); //output.WriteRawBits(enableHashing ? 1u : 0u, 1); if (enableNetworkPrediction) { output.WritePackedIntDelta(haveBaseline ? snapshot1BaselineClient : 0, snapshot0BaselineClient - 1, NetworkConfig.baseSequence1Context); output.WritePackedIntDelta(haveBaseline ? snapshot2BaselineClient : 0, snapshot1BaselineClient - 1, NetworkConfig.baseSequence2Context); } // NETTODO: For us serverTime == tick but network layer only cares about a growing int output.WritePackedIntDelta(_server.serverTime, haveBaseline ? maxSnapshotTime : 0, NetworkConfig.serverTimeContext); // NETTODO: a more generic way to send stats var temp = _server.m_ServerSimTime * 10; output.WriteRawBits((byte)temp, 8); _server.m_TempTypeList.Clear(); _server.m_TempSpawnList.Clear(); _server.m_TempDespawnList.Clear(); _server.m_TempUpdateList.Clear(); _server.m_PredictionIndex = 0; for (int id = 0, c = _server.m_Entities.Count; id < c; id++) { var entity = _server.m_Entities[id]; // Skip freed if (entity.spawnSequence == 0) { continue; } bool spawnedSinceBaseline = (entity.spawnSequence > baseline); bool despawned = (entity.despawnSequence > 0); // Note to future self: This is a bit tricky... We consider lifetimes of entities // re the baseline (last ack'ed, so in the past) and the snapshot we are building (now) // There are 6 cases (S == spawn, D = despawn): // // --------------------------------- time -----------------------------------> // // BASELINE SNAPSHOT // | | // v v // 1. S-------D IGNORE // 2. S------------------D SEND DESPAWN // 3. S-------------------------------------D SEND UPDATE // 4. S-----D IGNORE // 5. S-----------------D SEND SPAWN + UPDATE // 6. S----------D INVALID (FUTURE) // if (despawned && entity.despawnSequence <= baseline) { continue; // case 1: ignore } if (despawned && !spawnedSinceBaseline) { _server.m_TempDespawnList.Add(id); // case 2: despawn continue; } if (spawnedSinceBaseline && despawned) { continue; // case 4: ignore } if (spawnedSinceBaseline) { _server.m_TempSpawnList.Add(id); // case 5: send spawn + update } // case 5. and 3. fall through to here and gets updated // Send data from latest tick var tickToSend = _server.m_ServerSequence; // If despawned, however, we have stopped generating updates so pick latest valid if (despawned) { tickToSend = Mathf.Max(entity.updateSequence, entity.despawnSequence - 1); } { var entityType = _server.m_EntityTypes[entity.typeId]; var snapshot = entity.snapshots[tickToSend]; // NOTE : As long as the server haven't gotten the spawn acked, it will keep sending // delta relative to 0 as we cannot know if we have a valid baseline on the client or not uint num_baselines = 1; // if there is no normal baseline, we use schema baseline so there is always one uint *baseline0 = entityType.baseline; int time0 = maxSnapshotTime; if (haveBaseline && entity.spawnSequence <= maxSnapshotAck) { baseline0 = entity.snapshots[snapshot0Baseline].start; } if (enableNetworkPrediction) { uint *baseline1 = entityType.baseline; uint *baseline2 = entityType.baseline; int time1 = maxSnapshotTime; int time2 = maxSnapshotTime; if (haveBaseline && entity.spawnSequence <= maxSnapshotAck) { GameDebug.Assert(_server.m_Snapshots[snapshot0Baseline % _server.m_Snapshots.Length].serverTime == maxSnapshotTime, "serverTime == maxSnapshotTime"); GameDebug.Assert(entity.snapshots.Exists(snapshot0Baseline), "Exists(snapshot0Baseline)"); // Newly spawned entities might not have earlier baselines initially if (snapshot1Baseline != snapshot0Baseline && entity.snapshots.Exists(snapshot1Baseline)) { num_baselines = 2; baseline1 = entity.snapshots[snapshot1Baseline].start; time1 = _server.m_Snapshots[snapshot1Baseline % _server.m_Snapshots.Length].serverTime; if (snapshot2Baseline != snapshot1Baseline && entity.snapshots.Exists(snapshot2Baseline)) { num_baselines = 3; baseline2 = entity.snapshots[snapshot2Baseline].start; //time2 = entity.snapshots[snapshot2Baseline].serverTime; time2 = _server.m_Snapshots[snapshot2Baseline % _server.m_Snapshots.Length].serverTime; } } } entity.prediction = _server.m_Prediction + _server.m_PredictionIndex; NetworkPrediction.PredictSnapshot(entity.prediction, entity.fieldsChangedPrediction, entityType.schema, num_baselines, (uint)time0, baseline0, (uint)time1, baseline1, (uint)time2, baseline2, (uint)_server.serverTime, entity.GetFieldMask(ConnectionId)); _server.m_PredictionIndex += entityType.schema.GetByteSize() / 4; //_server.statsProcessedOutgoing += entityType.schema.GetByteSize(); if (UnsafeUtility.MemCmp(entity.prediction, snapshot.start, entityType.schema.GetByteSize()) != 0) { _server.m_TempUpdateList.Add(id); } if (serverDebug.IntValue > 2) { GameDebug.Log((haveBaseline ? "Upd [BL]" : "Upd [ ]") + "num_baselines: " + num_baselines + " serverSequence: " + tickToSend + " " + snapshot0Baseline + "(" + snapshot0BaselineClient + "," + time0 + ") - " + snapshot1Baseline + "(" + snapshot1BaselineClient + "," + time1 + ") - " + snapshot2Baseline + "(" + snapshot2BaselineClient + "," + time2 + "). Sche: " + _server.m_TempTypeList.Count + " Spwns: " + _server.m_TempSpawnList.Count + " Desp: " + _server.m_TempDespawnList.Count + " Upd: " + _server.m_TempUpdateList.Count); } } else { var prediction = baseline0; var fcp = entity.fieldsChangedPrediction; for (int i = 0, l = fcp.Length; i < l; ++i) { fcp[i] = 0; } if (UnsafeUtility.MemCmp(prediction, snapshot.start, entityType.schema.GetByteSize()) != 0) { _server.m_TempUpdateList.Add(id); } if (serverDebug.IntValue > 2) { GameDebug.Log((haveBaseline ? "Upd [BL]" : "Upd [ ]") + snapshot0Baseline + "(" + snapshot0BaselineClient + "," + time0 + "). Sche: " + _server.m_TempTypeList.Count + " Spwns: " + _server.m_TempSpawnList.Count + " Desp: " + _server.m_TempDespawnList.Count + " Upd: " + _server.m_TempUpdateList.Count); } } } } if (serverDebug.IntValue > 1 && (_server.m_TempSpawnList.Count > 0 || _server.m_TempDespawnList.Count > 0)) { GameDebug.Log(ConnectionId + ": spwns: " + string.Join(",", _server.m_TempSpawnList) + " despwans: " + string.Join(",", _server.m_TempDespawnList)); } foreach (var pair in _server.m_EntityTypes) { if (pair.Value.createdSequence > maxSnapshotAck) { _server.m_TempTypeList.Add(pair.Value); } } output.WritePackedUInt((uint)_server.m_TempTypeList.Count, NetworkConfig.schemaCountContext); foreach (var typeInfo in _server.m_TempTypeList) { output.WritePackedUInt(typeInfo.typeId, NetworkConfig.schemaTypeIdContext); NetworkSchema.WriteSchema(typeInfo.schema, ref output); GameDebug.Assert(typeInfo.baseline != null); NetworkSchema.CopyFieldsFromBuffer(typeInfo.schema, typeInfo.baseline, ref output); } int previousId = 1; output.WritePackedUInt((uint)_server.m_TempSpawnList.Count, NetworkConfig.spawnCountContext); foreach (var id in _server.m_TempSpawnList) { output.WritePackedIntDelta(id, previousId, NetworkConfig.idContext); previousId = id; var entity = _server.m_Entities[id]; output.WritePackedUInt((uint)entity.typeId, NetworkConfig.spawnTypeIdContext); output.WriteRawBits(entity.GetFieldMask(ConnectionId), 8); } output.WritePackedUInt((uint)_server.m_TempDespawnList.Count, NetworkConfig.despawnCountContext); foreach (var id in _server.m_TempDespawnList) { output.WritePackedIntDelta(id, previousId, NetworkConfig.idContext); previousId = id; } int numUpdates = _server.m_TempUpdateList.Count; output.WritePackedUInt((uint)numUpdates, NetworkConfig.updateCountContext); foreach (var id in _server.m_TempUpdateList) { var entity = _server.m_Entities[id]; var entityType = _server.m_EntityTypes[entity.typeId]; uint *prediction = null; if (enableNetworkPrediction) { prediction = entity.prediction; } else { prediction = entityType.baseline; if (haveBaseline && entity.spawnSequence <= maxSnapshotAck) { prediction = entity.snapshots[snapshot0Baseline].start; } } output.WritePackedIntDelta(id, previousId, NetworkConfig.idContext); previousId = id; // TODO It is a mess that we have to repeat the logic about tickToSend from above here int tickToSend = _server.m_ServerSequence; if (entity.despawnSequence > 0) { tickToSend = Mathf.Max(entity.despawnSequence - 1, entity.updateSequence); } GameDebug.Assert(_server.m_ServerSequence - tickToSend < NetworkConfig.snapshotDeltaCacheSize); if (!entity.snapshots.Exists(tickToSend)) { GameDebug.Log("maxSnapAck: " + maxSnapshotAck); GameDebug.Log("lastWritten: " + snapshotServerLastWritten); GameDebug.Log("spawn: " + entity.spawnSequence); GameDebug.Log("despawn: " + entity.despawnSequence); GameDebug.Log("update: " + entity.updateSequence); GameDebug.Log("tick: " + _server.m_ServerSequence); GameDebug.Log("id: " + id); GameDebug.Log("snapshots: " + entity.snapshots.ToString()); GameDebug.Log("WOULD HAVE crashed looking for " + tickToSend + " changing to " + (entity.despawnSequence - 1)); tickToSend = entity.despawnSequence - 1; GameDebug.Assert(false, "Unable to find " + tickToSend + " in snapshots. Would update have worked?"); } var snapshotInfo = entity.snapshots[tickToSend]; // NOTE : As long as the server haven't gotten the spawn acked, it will keep sending // delta relative to 0 as we cannot know if we have a valid baseline on the client or not uint entity_hash = 0; DeltaWriter.Write(ref output, entityType.schema, snapshotInfo.start, prediction, entity.fieldsChangedPrediction, entity.GetFieldMask(ConnectionId), ref entity_hash); } if (!haveBaseline && serverDebug.IntValue > 0) { Debug.Log("Sending no-baseline snapshot. C: " + ConnectionId + " Seq: " + outSequence + " Max: " + maxSnapshotAck + " Total entities sent: " + _server.m_TempUpdateList.Count + " Type breakdown:"); //foreach (var c in _server.m_EntityTypes) { // Debug.Log(c.Value.name + " " + c.Key + " #" + (c.Value.stats_count) + " " + (c.Value.stats_bits / 8) + " bytes"); //} } snapshotSeqs[outSequence % NetworkConfig.clientAckCacheSize] = _server.m_ServerSequence; snapshotServerLastWritten = _server.m_ServerSequence; Profiler.EndSample(); }