コード例 #1
0
        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++;
                }
            }
コード例 #2
0
    // Predict snapshot from baselines. Returns true if prediction is different from baseline 0 (if it need to be automatically predicted next frame).
    public static void PredictSnapshot(byte[] outputData, byte[] fieldsChangedPrediction, NetworkSchema schema, uint numBaselines, uint time0, byte[] baselineData0, uint time1, byte[] baselineData1, uint time2, byte[] baselineData2, uint time, byte fieldMask)
    {
        for (int i = 0, l = fieldsChangedPrediction.Length; i < l; ++i)
        {
            fieldsChangedPrediction[i] = 0;
        }

        if (numBaselines < 3)
        {
            System.Array.Copy(baselineData0, outputData, schema.GetByteSize());
            return;
        }

        var baselineStream0 = new ByteInputStream(baselineData0);
        var baselineStream1 = new ByteInputStream(baselineData1);
        var baselineStream2 = new ByteInputStream(baselineData2);
        var outputStream    = new ByteOutputStream(outputData);

        for (int i = 0; i < schema.fields.Count; ++i)
        {
            GameDebug.Assert(schema.fields[i].byteOffset == baselineStream0.GetBytePosition());
            GameDebug.Assert(schema.fields[i].byteOffset == baselineStream1.GetBytePosition());
            GameDebug.Assert(schema.fields[i].byteOffset == baselineStream2.GetBytePosition());

            var field = schema.fields[i];

            byte fieldByteOffset = (byte)((uint)i >> 3);
            byte fieldBitOffset  = (byte)((uint)i & 0x7);

            bool masked = (field.fieldMask & fieldMask) != 0;

            switch (field.fieldType)
            {
            case NetworkSchema.FieldType.Bool:
            {
                uint baseline0  = baselineStream0.ReadUInt8();
                uint baseline1  = baselineStream1.ReadUInt8();
                uint baseline2  = baselineStream2.ReadUInt8();
                uint prediction = baseline0;

                if (!masked)
                {
                    if (baseline0 != baseline1 && baseline1 != baseline2)
                    {
                        fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                    }
                }

                outputStream.WriteUInt8((byte)prediction);
                break;
            }

            case NetworkSchema.FieldType.UInt:
            case NetworkSchema.FieldType.Int:
            case NetworkSchema.FieldType.Float:
            {
                uint baseline0 = (uint)baselineStream0.ReadBits(field.bits);
                uint baseline1 = (uint)baselineStream1.ReadBits(field.bits);
                uint baseline2 = (uint)baselineStream2.ReadBits(field.bits);

                uint prediction = baseline0;
                if (!masked)
                {
                    if (field.delta)
                    {
                        bool predictionLikelyWrong;
                        prediction = NetworkPrediction.PredictUint(numBaselines, time0, baseline0, time1, baseline1, time2, baseline2, time, out predictionLikelyWrong);
                        if (predictionLikelyWrong)
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                    else
                    {
                        if (baseline0 != baseline1 && baseline1 != baseline2)
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                }

                outputStream.WriteBits(prediction, field.bits);          //RUTODO: fix this
                break;
            }

            case NetworkSchema.FieldType.Vector2:
            {
                uint bx0 = baselineStream0.ReadUInt32();
                uint by0 = baselineStream0.ReadUInt32();

                uint bx1 = baselineStream1.ReadUInt32();
                uint by1 = baselineStream1.ReadUInt32();

                uint bx2 = baselineStream2.ReadUInt32();
                uint by2 = baselineStream2.ReadUInt32();

                uint px = bx0;
                uint py = by0;
                if (!masked)
                {
                    if (field.delta)
                    {
                        bool predictionLikelyWrongX;
                        bool predictionLikelyWrongY;
                        px = NetworkPrediction.PredictUint(numBaselines, time0, bx0, time1, bx1, time2, bx2, time, out predictionLikelyWrongX);
                        py = NetworkPrediction.PredictUint(numBaselines, time0, by0, time1, by1, time2, by2, time, out predictionLikelyWrongY);
                        if (predictionLikelyWrongX || predictionLikelyWrongY)
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                    else
                    {
                        if ((bx0 != bx1 || by0 != by1) && (bx1 != bx2 || by1 != by2))
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                }

                outputStream.WriteUInt32(px);
                outputStream.WriteUInt32(py);
                break;
            }

            case NetworkSchema.FieldType.Vector3:
            {
                uint bx0 = baselineStream0.ReadUInt32();
                uint by0 = baselineStream0.ReadUInt32();
                uint bz0 = baselineStream0.ReadUInt32();

                uint bx1 = baselineStream1.ReadUInt32();
                uint by1 = baselineStream1.ReadUInt32();
                uint bz1 = baselineStream1.ReadUInt32();

                uint bx2 = baselineStream2.ReadUInt32();
                uint by2 = baselineStream2.ReadUInt32();
                uint bz2 = baselineStream2.ReadUInt32();

                uint px = bx0;
                uint py = by0;
                uint pz = bz0;

                if (!masked)
                {
                    if (field.delta)
                    {
                        bool predictionLikelyWrongX;
                        bool predictionLikelyWrongY;
                        bool predictionLikelyWrongZ;
                        px = NetworkPrediction.PredictUint(numBaselines, time0, bx0, time1, bx1, time2, bx2, time, out predictionLikelyWrongX);
                        py = NetworkPrediction.PredictUint(numBaselines, time0, by0, time1, by1, time2, by2, time, out predictionLikelyWrongY);
                        pz = NetworkPrediction.PredictUint(numBaselines, time0, bz0, time1, bz1, time2, bz2, time, out predictionLikelyWrongZ);

                        if (predictionLikelyWrongX || predictionLikelyWrongY || predictionLikelyWrongZ)
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                    else
                    {
                        if ((bx0 != bx1 || by0 != by1 || bz0 != bz1) && (bx1 != bx2 || by1 != by2 || bz1 != bz2))
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                }

                outputStream.WriteUInt32(px);
                outputStream.WriteUInt32(py);
                outputStream.WriteUInt32(pz);
                break;
            }

            case NetworkSchema.FieldType.Quaternion:
            {
                uint bx0 = baselineStream0.ReadUInt32();
                uint by0 = baselineStream0.ReadUInt32();
                uint bz0 = baselineStream0.ReadUInt32();
                uint bw0 = baselineStream0.ReadUInt32();

                uint bx1 = baselineStream1.ReadUInt32();
                uint by1 = baselineStream1.ReadUInt32();
                uint bz1 = baselineStream1.ReadUInt32();
                uint bw1 = baselineStream1.ReadUInt32();

                uint bx2 = baselineStream2.ReadUInt32();
                uint by2 = baselineStream2.ReadUInt32();
                uint bz2 = baselineStream2.ReadUInt32();
                uint bw2 = baselineStream2.ReadUInt32();

                uint px = bx0;
                uint py = by0;
                uint pz = bz0;
                uint pw = bw0;

                if (!masked)
                {
                    if (field.delta)
                    {
                        bool predictionLikelyWrongX;
                        bool predictionLikelyWrongY;
                        bool predictionLikelyWrongZ;
                        bool predictionLikelyWrongW;
                        px = NetworkPrediction.PredictUint(numBaselines, time0, bx0, time1, bx1, time2, bx2, time, out predictionLikelyWrongX);
                        py = NetworkPrediction.PredictUint(numBaselines, time0, by0, time1, by1, time2, by2, time, out predictionLikelyWrongY);
                        pz = NetworkPrediction.PredictUint(numBaselines, time0, bz0, time1, bz1, time2, bz2, time, out predictionLikelyWrongZ);
                        pw = NetworkPrediction.PredictUint(numBaselines, time0, bw0, time1, bw1, time2, bw2, time, out predictionLikelyWrongW);

                        if (predictionLikelyWrongX || predictionLikelyWrongY || predictionLikelyWrongZ || predictionLikelyWrongW)
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                    else
                    {
                        if ((bx0 != bx1 || by0 != by1 || bz0 != bz1 || bw0 != bw1) && (bx1 != bx2 || by1 != by2 || bz1 != bz2 || bw1 != bw2))
                        {
                            fieldsChangedPrediction[fieldByteOffset] |= (byte)(1 << fieldBitOffset);
                        }
                    }
                }

                outputStream.WriteUInt32(px);
                outputStream.WriteUInt32(py);
                outputStream.WriteUInt32(pz);
                outputStream.WriteUInt32(pw);
                break;
            }

            case NetworkSchema.FieldType.String:
            case NetworkSchema.FieldType.ByteArray:
            {
                baselineStream1.SkipByteArray(field.arraySize);
                baselineStream2.SkipByteArray(field.arraySize);
                outputStream.CopyByteArray(ref baselineStream0, field.arraySize);
                //TODO: predict me!
            }
            break;
            }
        }

        //fieldsChangedPrediction = 0;

        outputStream.Flush();
    }
コード例 #3
0
        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);
                }
            }
        }
コード例 #4
0
        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();
        }