Пример #1
0
            public unsafe void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength)
            {
                var prefabList             = ghostPrefabBufferFromEntity[prefabEntity];
                var entityList             = chunk.GetNativeArray(entityType);
                var snapshotDataList       = chunk.GetNativeArray(snapshotDataType);
                var snapshotDataBufferList = chunk.GetBufferAccessor(snapshotDataBufferType);

                var ghostTypeComponent = ghostTypeFromEntity[entityList[0]];
                int ghostType;

                for (ghostType = 0; ghostType < prefabList.Length; ++ghostType)
                {
                    if (ghostTypeFromEntity[prefabList[ghostType].Value] == ghostTypeComponent)
                    {
                        break;
                    }
                }
                if (ghostType >= prefabList.Length)
                {
                    throw new System.InvalidOperationException("Could not find ghost type in the collection");
                }

                var spawnList          = spawnListFromEntity[spawnListEntity];
                var typeData           = GhostTypeCollection[ghostType];
                var snapshotSize       = typeData.SnapshotSize;
                int changeMaskUints    = GhostCollectionSystem.ChangeMaskArraySizeInUInts(typeData.ChangeMaskBits);
                var snapshotBaseOffset = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                var serializerState    = new GhostSerializerState
                {
                    GhostFromEntity = ghostFromEntity
                };

                for (int i = 0; i < entityList.Length; ++i)
                {
                    var entity = entityList[i];

                    var ghostComponent = ghostFromEntity[entity];
                    ghostComponent.ghostType = ghostType;
                    ghostFromEntity[entity]  = ghostComponent;
                    // Set initial snapshot data
                    // Get the buffers, fill in snapshot size etc
                    snapshotDataList[i] = new SnapshotData {
                        SnapshotSize = snapshotSize, LatestIndex = 0
                    };
                    var snapshotDataBuffer = snapshotDataBufferList[i];
                    snapshotDataBuffer.ResizeUninitialized(snapshotSize * GhostSystemConstants.SnapshotHistorySize);
                    var snapshotPtr = (byte *)snapshotDataBuffer.GetUnsafePtr();
                    UnsafeUtility.MemClear(snapshotPtr, snapshotSize * GhostSystemConstants.SnapshotHistorySize);
                    *(uint *)snapshotPtr = spawnTick;
                    var snapshotOffset = snapshotBaseOffset;

                    int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents;
                    for (int comp = 0; comp < numBaseComponents; ++comp)
                    {
                        int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                        if (compIdx >= ghostChunkComponentTypesLength)
                        {
                            throw new InvalidOperationException("Component index out of range");
                        }
#endif
                        if (chunk.Has(ghostChunkComponentTypesPtr[compIdx]))
                        {
                            var compSize = GhostComponentCollection[compIdx].ComponentSize;
                            var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
                            compData += i * compSize;
                            GhostComponentCollection[compIdx].CopyToSnapshot.Ptr.Invoke((IntPtr)UnsafeUtility.AddressOf(ref serializerState), (IntPtr)snapshotPtr, snapshotOffset, snapshotSize, (IntPtr)compData, compSize, 1);
                        }
                        else
                        {
                            var componentSnapshotSize = GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                            UnsafeUtility.MemClear(snapshotPtr + snapshotOffset, componentSnapshotSize);
                        }
                        snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                    }
                    if (typeData.NumChildComponents > 0)
                    {
                        var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType);
                        for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp)
                        {
                            int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                            if (compIdx >= ghostChunkComponentTypesLength)
                            {
                                throw new InvalidOperationException("Component index out of range");
                            }
#endif
                            var compSize = GhostComponentCollection[compIdx].ComponentSize;

                            var linkedEntityGroup = linkedEntityGroupAccessor[i];
                            var childEnt          = linkedEntityGroup[GhostComponentIndex[typeData.FirstComponent + comp].EntityIndex].Value;
                            //We can skip here, becase the memory buffer offset is computed using the start-end entity indices
                            if (!childEntityLookup.TryGetValue(childEnt, out var childChunk) || !childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx]))
                            {
                                var componentSnapshotSize = GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                                UnsafeUtility.MemClear(snapshotPtr + snapshotOffset, componentSnapshotSize);
                            }
                            else
                            {
                                var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
                                compData += childChunk.index * compSize;
                                GhostComponentCollection[compIdx].CopyToSnapshot.Ptr.Invoke((IntPtr)UnsafeUtility.AddressOf(ref serializerState), (IntPtr)snapshotPtr, snapshotOffset, snapshotSize, (IntPtr)compData, compSize, 1);
                            }

                            snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                        }
                    }


                    // Remove request component
                    commandBuffer.RemoveComponent <PredictedGhostSpawnRequestComponent>(entity);
                    // Add to list of predictive spawn component - maybe use a singleton for this so spawn systems can just access it too
                    spawnList.Add(new PredictedGhostSpawn {
                        entity = entity, ghostType = ghostType, spawnTick = spawnTick
                    });
                }
            }
            public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength)
            {
                GhostComponentCollection = GhostComponentCollectionFromEntity[GhostCollectionSingleton];
                GhostTypeCollection      = GhostTypeCollectionFromEntity[GhostCollectionSingleton];
                GhostComponentIndex      = GhostComponentIndexFromEntity[GhostCollectionSingleton];

                bool  predicted          = chunk.Has(predictedGhostComponentType);
                uint  targetTick         = predicted ? predictedTargetTick : interpolatedTargetTick;
                float targetTickFraction = predicted ? 1.0f : interpolatedTargetTickFraction;

                var deserializerState = new GhostDeserializerState
                {
                    GhostMap    = GhostMap,
                    GhostOwner  = ghostOwnerId,
                    SendToOwner = SendToOwnerType.All
                };
                var ghostComponents = chunk.GetNativeArray(ghostType);
                int ghostTypeId     = ghostComponents.GetFirstGhostTypeId(out var firstGhost);

                if (ghostTypeId < 0)
                {
                    return;
                }
                var typeData = GhostTypeCollection[ghostTypeId];
                var ghostSnapshotDataArray          = chunk.GetNativeArray(ghostSnapshotDataType);
                var ghostSnapshotDataBufferArray    = chunk.GetBufferAccessor(ghostSnapshotDataBufferType);
                var ghostSnapshotDynamicBufferArray = chunk.GetBufferAccessor(ghostSnapshotDynamicDataBufferType);

                int changeMaskUints        = GhostCollectionSystem.ChangeMaskArraySizeInUInts(typeData.ChangeMaskBits);
                int snapshotOffset         = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                int snapshotDataAtTickSize = UnsafeUtility.SizeOf <SnapshotData.DataAtTick>();

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                var minMaxOffset = ThreadIndex * (JobsUtility.CacheLineSize / 4);
#endif
                var  dataAtTick  = new NativeArray <SnapshotData.DataAtTick>(ghostComponents.Length, Allocator.Temp);
                var  entityRange = new NativeList <int2>(ghostComponents.Length, Allocator.Temp);
                int2 nextRange   = default;
                var  predictedGhostComponentArray = chunk.GetNativeArray(predictedGhostComponentType);
                bool canBeStatic = typeData.StaticOptimization;
                // Find the ranges of entities which have data to apply, store the data to apply in an array while doing so
                for (int ent = firstGhost; ent < ghostComponents.Length; ++ent)
                {
                    // Pre spawned ghosts might not have the ghost type set yet - in that case we need to skip them until the GHostReceiveSystem has assigned the ghost type
                    if (ghostComponents[firstGhost].ghostType != ghostTypeId)
                    {
                        if (nextRange.y != 0)
                        {
                            entityRange.Add(nextRange);
                        }
                        nextRange = default;
                        continue;
                    }
                    var  snapshotDataBuffer = ghostSnapshotDataBufferArray[ent];
                    var  ghostSnapshotData  = ghostSnapshotDataArray[ent];
                    var  latestTick         = ghostSnapshotData.GetLatestTick(snapshotDataBuffer);
                    bool isStatic           = canBeStatic && ghostSnapshotData.WasLatestTickZeroChange(snapshotDataBuffer, changeMaskUints);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    if (latestTick != 0 && !isStatic)
                    {
                        if (minMaxSnapshotTick[minMaxOffset] == 0 || SequenceHelpers.IsNewer(minMaxSnapshotTick[minMaxOffset], latestTick))
                        {
                            minMaxSnapshotTick[minMaxOffset] = latestTick;
                        }
                        if (minMaxSnapshotTick[minMaxOffset + 1] == 0 || SequenceHelpers.IsNewer(latestTick, minMaxSnapshotTick[minMaxOffset + 1]))
                        {
                            minMaxSnapshotTick[minMaxOffset + 1] = latestTick;
                        }
                    }
#endif
                    if (ghostSnapshotData.GetDataAtTick(targetTick, typeData.PredictionOwnerOffset, targetTickFraction, snapshotDataBuffer, out var data, MaxExtrapolationTicks))
                    {
                        if (predicted)
                        {
                            // TODO: is this the right way to handle this?
                            data.InterpolationFactor = 0;
                            var snapshotTick  = *(uint *)data.SnapshotBefore;
                            var predictedData = predictedGhostComponentArray[ent];
                            // We want to contiue prediction from the last full tick we predicted last time
                            var predictionStartTick = predictionStateBackupTick;
                            // If there is no history, try to use the tick where we left off last time, will only be a valid tick if we ended with a full prediction tick as opposed to a fractional one
                            if (predictionStartTick == 0)
                            {
                                predictionStartTick = lastPredictedTick;
                            }
                            // If we do not have a backup or we got more data since last time we run from the tick we have snapshot data for
                            if (predictionStartTick == 0 || predictedData.AppliedTick != snapshotTick)
                            {
                                predictionStartTick = snapshotTick;
                            }
                            // If we have newer or equally new data in the
                            else if (!SequenceHelpers.IsNewer(predictionStartTick, snapshotTick))
                            {
                                predictionStartTick = snapshotTick;
                            }

                            // If we want to continue prediction, and this is not the currently applied prediction state we must restore the state from the backup
                            if (predictionStartTick != snapshotTick && predictionStartTick != lastPredictedTick)
                            {
                                // If we cannot restore the backup and continue prediction we roll back and resimulate
                                if (!RestorePredictionBackup(chunk, ent, typeData, ghostChunkComponentTypesPtr, ghostChunkComponentTypesLength))
                                {
                                    predictionStartTick = snapshotTick;
                                }
                            }


                            if (minPredictedTick[ThreadIndex] == 0 || SequenceHelpers.IsNewer(minPredictedTick[ThreadIndex], predictionStartTick))
                            {
                                minPredictedTick[ThreadIndex] = predictionStartTick;
                            }

                            if (predictionStartTick != snapshotTick)
                            {
                                if (nextRange.y != 0)
                                {
                                    entityRange.Add(nextRange);
                                }
                                nextRange = default;
                            }
                            else
                            {
                                predictedData.AppliedTick = snapshotTick;
                                if (nextRange.y == 0)
                                {
                                    nextRange.x = ent;
                                }
                                nextRange.y = ent + 1;
                            }
                            predictedData.PredictionStartTick = predictionStartTick;
                            predictedGhostComponentArray[ent] = predictedData;
                        }
                        else
                        {
                            // If this snapshot is static, and the data for the latest tick was applied during last interpolation update, we can just skip copying data
                            if (isStatic && !SequenceHelpers.IsNewer(latestTick, lastInterpolatedTick))
                            {
                                if (nextRange.y != 0)
                                {
                                    entityRange.Add(nextRange);
                                }
                                nextRange = default;
                            }
                            else
                            {
                                if (nextRange.y == 0)
                                {
                                    nextRange.x = ent;
                                }
                                nextRange.y = ent + 1;
                            }
                        }
                        dataAtTick[ent] = data;
                    }
Пример #3
0
            public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength)
            {
                bool  predicted          = chunk.Has(predictedGhostComponentType);
                uint  targetTick         = predicted ? predictedTargetTick : interpolatedTargetTick;
                float targetTickFraction = predicted ? 1.0f : interpolatedTargetTickFraction;

                var deserializerState = new GhostDeserializerState
                {
                    GhostMap = GhostMap
                };
                var ghostComponents              = chunk.GetNativeArray(ghostType);
                int ghostTypeId                  = ghostComponents[0].ghostType;
                var typeData                     = GhostTypeCollection[ghostTypeId];
                var ghostSnapshotDataArray       = chunk.GetNativeArray(ghostSnapshotDataType);
                var ghostSnapshotDataBufferArray = chunk.GetBufferAccessor(ghostSnapshotDataBufferType);

                int changeMaskUints        = GhostCollectionSystem.ChangeMaskArraySizeInUInts(typeData.ChangeMaskBits);
                int snapshotOffset         = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                int snapshotDataAtTickSize = UnsafeUtility.SizeOf <SnapshotData.DataAtTick>();

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                var minMaxOffset = ThreadIndex * (JobsUtility.CacheLineSize / 4);
#endif
                var  dataAtTick  = new NativeArray <SnapshotData.DataAtTick>(ghostComponents.Length, Allocator.Temp);
                var  entityRange = new NativeList <int2>(ghostComponents.Length, Allocator.Temp);
                int2 nextRange   = default;
                var  predictedGhostComponentArray = chunk.GetNativeArray(predictedGhostComponentType);
                bool canBeStatic = typeData.StaticOptimization;
                // Find the ranges of entities which have data to apply, store the data to apply in an array while doing so
                for (int ent = 0; ent < ghostComponents.Length; ++ent)
                {
                    var  snapshotDataBuffer = ghostSnapshotDataBufferArray[ent];
                    var  ghostSnapshotData  = ghostSnapshotDataArray[ent];
                    var  latestTick         = ghostSnapshotData.GetLatestTick(snapshotDataBuffer);
                    bool isStatic           = canBeStatic && ghostSnapshotData.WasLatestTickZeroChange(snapshotDataBuffer, changeMaskUints);
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    if (latestTick != 0 && !isStatic)
                    {
                        if (minMaxSnapshotTick[minMaxOffset] == 0 || SequenceHelpers.IsNewer(minMaxSnapshotTick[minMaxOffset], latestTick))
                        {
                            minMaxSnapshotTick[minMaxOffset] = latestTick;
                        }
                        if (minMaxSnapshotTick[minMaxOffset + 1] == 0 || SequenceHelpers.IsNewer(latestTick, minMaxSnapshotTick[minMaxOffset + 1]))
                        {
                            minMaxSnapshotTick[minMaxOffset + 1] = latestTick;
                        }
                    }
#endif
                    if (ghostSnapshotData.GetDataAtTick(targetTick, targetTickFraction, snapshotDataBuffer, out var data))
                    {
                        if (predicted)
                        {
                            // TODO: is this the right way to handle this?
                            data.InterpolationFactor = 0;
                            var snapshotTick  = *(uint *)data.SnapshotBefore;
                            var predictedData = predictedGhostComponentArray[ent];
                            // We want to contiue prediction from the last full tick we predicted last time
                            var predictionStartTick = predictionStateBackupTick;
                            // If there is no history, try to use the tick where we left off last time, will only be a valid tick if we ended with a full prediction tick as opposed to a fractional one
                            if (predictionStartTick == 0)
                            {
                                predictionStartTick = lastPredictedTick;
                            }
                            // If we do not have a backup or we got more data since last time we run from the tick we have snapshot data for
                            if (predictionStartTick == 0 || predictedData.AppliedTick != snapshotTick)
                            {
                                predictionStartTick = snapshotTick;
                            }
                            // If we have newer or equally new data in the
                            else if (!SequenceHelpers.IsNewer(predictionStartTick, snapshotTick))
                            {
                                predictionStartTick = snapshotTick;
                            }

                            // If we want to continue prediction, and this is not the currently applied prediction state we must restore the state from the backup
                            if (predictionStartTick != snapshotTick && predictionStartTick != lastPredictedTick)
                            {
                                // If we cannot restore the backup and continue prediction we roll back and resimulate
                                if (!RestorePredictionBackup(chunk, ent, typeData, ghostChunkComponentTypesPtr, ghostChunkComponentTypesLength))
                                {
                                    predictionStartTick = snapshotTick;
                                }
                            }


                            if (minPredictedTick[ThreadIndex] == 0 || SequenceHelpers.IsNewer(minPredictedTick[ThreadIndex], predictionStartTick))
                            {
                                minPredictedTick[ThreadIndex] = predictionStartTick;
                            }

                            if (predictionStartTick != snapshotTick)
                            {
                                if (nextRange.y != 0)
                                {
                                    entityRange.Add(nextRange);
                                }
                                nextRange = default;
                            }
                            else
                            {
                                predictedData.AppliedTick = snapshotTick;
                                if (nextRange.y == 0)
                                {
                                    nextRange.x = ent;
                                }
                                nextRange.y = ent + 1;
                            }
                            predictedData.PredictionStartTick = predictionStartTick;
                            predictedGhostComponentArray[ent] = predictedData;
                        }
                        else
                        {
                            // If this snapshot is static, and the data for the latest tick was applied during last interpolation update, we can just skip copying data
                            if (isStatic && !SequenceHelpers.IsNewer(latestTick, lastInterpolatedTick))
                            {
                                if (nextRange.y != 0)
                                {
                                    entityRange.Add(nextRange);
                                }
                                nextRange = default;
                            }
                            else
                            {
                                if (nextRange.y == 0)
                                {
                                    nextRange.x = ent;
                                }
                                nextRange.y = ent + 1;
                            }
                        }
                        dataAtTick[ent] = data;
                    }
                    else if (nextRange.y != 0)
                    {
                        entityRange.Add(nextRange);
                        nextRange = default;
                        if (predicted)
                        {
                            var predictionStartTick = predictionStateBackupTick;
                            if (predictionStateBackupTick != lastPredictedTick)
                            {
                                // Try to back up the thing
                                if (!RestorePredictionBackup(chunk, ent, typeData, ghostChunkComponentTypesPtr, ghostChunkComponentTypesLength))
                                {
                                    predictionStartTick = 0;
                                }
                            }
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                            if (predictionStartTick == 0)
                            {
                                throw new System.InvalidOperationException("Trying to predict a ghost without having a state to roll back to");
                            }
#endif
                        }
                    }
                }
                if (nextRange.y != 0)
                {
                    entityRange.Add(nextRange);
                }

                var requiredSendMask  = predicted ? GhostComponentSerializer.SendMask.Predicted : GhostComponentSerializer.SendMask.Interpolated;
                int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents;
                for (int comp = 0; comp < numBaseComponents; ++comp)
                {
                    int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                    if (compIdx >= ghostChunkComponentTypesLength)
                    {
                        throw new System.InvalidOperationException("Component index out of range");
                    }
#endif
                    if (chunk.Has(ghostChunkComponentTypesPtr[compIdx]) &&
                        (GhostComponentCollection[compIdx].SendMask & requiredSendMask) != 0)
                    {
                        var compSize = GhostComponentCollection[compIdx].ComponentSize;
                        var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafePtr();
                        for (var rangeIdx = 0; rangeIdx < entityRange.Length; ++rangeIdx)
                        {
                            var range        = entityRange[rangeIdx];
                            var snapshotData = (byte *)dataAtTick.GetUnsafeReadOnlyPtr();
                            snapshotData += snapshotDataAtTickSize * range.x;
                            GhostComponentCollection[compIdx].CopyFromSnapshot.Ptr.Invoke((System.IntPtr)UnsafeUtility.AddressOf(ref deserializerState), (System.IntPtr)snapshotData, snapshotOffset, snapshotDataAtTickSize, (System.IntPtr)(compData + range.x * compSize), compSize, range.y - range.x);
                        }
                    }
                    snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                }
                if (typeData.NumChildComponents > 0)
                {
                    var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType);
                    for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp)
                    {
                        int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                        if (compIdx >= ghostChunkComponentTypesLength)
                        {
                            throw new System.InvalidOperationException("Component index out of range");
                        }
#endif
                        if ((GhostComponentCollection[compIdx].SendMask & requiredSendMask) != 0)
                        {
                            var compSize = GhostComponentCollection[compIdx].ComponentSize;
                            for (var rangeIdx = 0; rangeIdx < entityRange.Length; ++rangeIdx)
                            {
                                var range = entityRange[rangeIdx];
                                for (int ent = range.x; ent < range.y; ++ent)
                                {
                                    var linkedEntityGroup = linkedEntityGroupAccessor[ent];
                                    if (!childEntityLookup.TryGetValue(linkedEntityGroup[GhostComponentIndex[typeData.FirstComponent + comp].EntityIndex].Value, out var childChunk))
                                    {
                                        continue;
                                    }
                                    if (!childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx]))
                                    {
                                        continue;
                                    }
                                    var compData     = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafePtr();
                                    var snapshotData = (byte *)dataAtTick.GetUnsafeReadOnlyPtr();
                                    snapshotData += snapshotDataAtTickSize * ent;
                                    GhostComponentCollection[compIdx].CopyFromSnapshot.Ptr.Invoke((System.IntPtr)UnsafeUtility.AddressOf(ref deserializerState), (System.IntPtr)snapshotData, snapshotOffset, snapshotDataAtTickSize, (System.IntPtr)(compData + childChunk.index * compSize), compSize, 1);
                                }
                            }
                        }
                        snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                    }
                }
            }
            public unsafe void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength)
            {
                var entityList                    = chunk.GetNativeArray(entityType);
                var snapshotDataList              = chunk.GetNativeArray(snapshotDataType);
                var snapshotDataBufferList        = chunk.GetBufferAccessor(snapshotDataBufferType);
                var snapshotDynamicDataBufferList = chunk.GetBufferAccessor(snapshotDynamicDataBufferType);

                var GhostCollection     = GhostCollectionFromEntity[GhostCollectionSingleton];
                var GhostTypeCollection = GhostTypeCollectionFromEntity[GhostCollectionSingleton];
                var ghostTypeComponent  = ghostTypeFromEntity[entityList[0]];
                int ghostType;

                for (ghostType = 0; ghostType < GhostCollection.Length; ++ghostType)
                {
                    if (GhostCollection[ghostType].GhostType == ghostTypeComponent)
                    {
                        break;
                    }
                }
                if (ghostType >= GhostCollection.Length)
                {
                    throw new InvalidOperationException("Could not find ghost type in the collection");
                }
                if (ghostType >= GhostTypeCollection.Length)
                {
                    return; // serialization data has not been loaded yet
                }
                var GhostComponentCollection = GhostComponentCollectionFromEntity[GhostCollectionSingleton];
                var GhostComponentIndex      = GhostComponentIndexFromEntity[GhostCollectionSingleton];

                var spawnList          = spawnListFromEntity[spawnListEntity];
                var typeData           = GhostTypeCollection[ghostType];
                var snapshotSize       = typeData.SnapshotSize;
                int changeMaskUints    = GhostCollectionSystem.ChangeMaskArraySizeInUInts(typeData.ChangeMaskBits);
                var snapshotBaseOffset = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                var serializerState    = new GhostSerializerState
                {
                    GhostFromEntity = ghostFromEntity
                };
                var buffersToSerialize = new NativeList <BufferDataEntry>(Allocator.Temp);

                for (int i = 0; i < entityList.Length; ++i)
                {
                    var entity = entityList[i];

                    var ghostComponent = ghostFromEntity[entity];
                    ghostComponent.ghostType = ghostType;
                    ghostFromEntity[entity]  = ghostComponent;
                    // Set initial snapshot data
                    // Get the buffers, fill in snapshot size etc
                    snapshotDataList[i] = new SnapshotData {
                        SnapshotSize = snapshotSize, LatestIndex = 0
                    };
                    var snapshotDataBuffer = snapshotDataBufferList[i];
                    snapshotDataBuffer.ResizeUninitialized(snapshotSize * GhostSystemConstants.SnapshotHistorySize);
                    var snapshotPtr = (byte *)snapshotDataBuffer.GetUnsafePtr();
                    UnsafeUtility.MemClear(snapshotPtr, snapshotSize * GhostSystemConstants.SnapshotHistorySize);
                    *(uint *)snapshotPtr = spawnTick;
                    var snapshotOffset            = snapshotBaseOffset;
                    var dynamicSnapshotDataOffset = 0;

                    //Loop through all the serializable components and copy their data into the snapshot.
                    //For buffers, we collect what we need to serialize and then we copy the content in a second
                    //step. This is necessary because we need to resize the dynamic snapshot buffer
                    int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents;
                    for (int comp = 0; comp < numBaseComponents; ++comp)
                    {
                        int compIdx       = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
                        int serializerIdx = GhostComponentIndex[typeData.FirstComponent + comp].SerializerIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                        if (compIdx >= ghostChunkComponentTypesLength)
                        {
                            throw new InvalidOperationException("Component index out of range");
                        }
#endif
                        var componentSnapshotSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer
                            ? GhostCollectionSystem.SnapshotSizeAligned(GhostSystemConstants.DynamicBufferComponentSnapshotSize)
                            : GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[serializerIdx].SnapshotSize);
                        var compSize = GhostComponentCollection[serializerIdx].ComponentSize;

                        if (!chunk.Has(ghostChunkComponentTypesPtr[compIdx]))
                        {
                            UnsafeUtility.MemClear(snapshotPtr + snapshotOffset, componentSnapshotSize);
                        }
                        else if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
                        {
                            var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
                            GhostComponentCollection[serializerIdx].CopyToSnapshot.Ptr.Invoke((IntPtr)UnsafeUtility.AddressOf(ref serializerState), (IntPtr)snapshotPtr, snapshotOffset, snapshotSize, (IntPtr)(compData + i * compSize), compSize, 1);
                        }

                        else
                        {
                            // Collect the buffer data to serialize by storing pointers, offset and size.
                            var bufData       = chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
                            var bufferPointer = (IntPtr)bufData.GetUnsafeReadOnlyPtrAndLength(i, out var bufferLen);
                            var snapshotData  = (uint *)(snapshotPtr + snapshotOffset);
                            snapshotData[0] = (uint)bufferLen;
                            snapshotData[1] = (uint)dynamicSnapshotDataOffset;
                            buffersToSerialize.Add(new BufferDataEntry
                            {
                                offset        = dynamicSnapshotDataOffset,
                                len           = bufferLen,
                                serializerIdx = serializerIdx,
                                bufferData    = bufferPointer
                            });
                            var maskSize = SnapshotDynamicBuffersHelper.GetDynamicDataChangeMaskSize(GhostComponentCollection[serializerIdx].ChangeMaskBits, bufferLen);
                            dynamicSnapshotDataOffset += GhostCollectionSystem.SnapshotSizeAligned(maskSize + GhostComponentCollection[serializerIdx].ComponentSize * bufferLen);
                        }
                        snapshotOffset += componentSnapshotSize;
                    }
                    if (typeData.NumChildComponents > 0)
                    {
                        var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType);
                        for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp)
                        {
                            int compIdx       = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
                            int serializerIdx = GhostComponentIndex[typeData.FirstComponent + comp].SerializerIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                            if (compIdx >= ghostChunkComponentTypesLength)
                            {
                                throw new System.InvalidOperationException("Component index out of range");
                            }
#endif
                            var compSize = GhostComponentCollection[serializerIdx].ComponentSize;

                            var linkedEntityGroup = linkedEntityGroupAccessor[i];
                            var childEnt          = linkedEntityGroup[GhostComponentIndex[typeData.FirstComponent + comp].EntityIndex].Value;

                            var componentSnapshotSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer
                                ? GhostCollectionSystem.SnapshotSizeAligned(GhostSystemConstants.DynamicBufferComponentSnapshotSize)
                                : GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[serializerIdx].SnapshotSize);

                            //We can skip here, becase the memory buffer offset is computed using the start-end entity indices
                            if (!childEntityLookup.TryGetValue(childEnt, out var childChunk) || !childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx]))
                            {
                                UnsafeUtility.MemClear(snapshotPtr + snapshotOffset, componentSnapshotSize);
                            }
                            else if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer)
                            {
                                var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr();
                                compData += childChunk.index * compSize;
                                GhostComponentCollection[serializerIdx].CopyToSnapshot.Ptr.Invoke((System.IntPtr)UnsafeUtility.AddressOf(ref serializerState),
                                                                                                  (System.IntPtr)snapshotPtr, snapshotOffset, snapshotSize, (System.IntPtr)compData, compSize, 1);
                            }
                            else
                            {
                                // Collect the buffer data to serialize by storing pointers, offset and size.
                                var bufData       = childChunk.chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]);
                                var bufferPointer = (IntPtr)bufData.GetUnsafeReadOnlyPtrAndLength(childChunk.index, out var bufferLen);
                                var snapshotData  = (uint *)(snapshotPtr + snapshotOffset);
                                snapshotData[0] = (uint)bufferLen;
                                snapshotData[1] = (uint)dynamicSnapshotDataOffset;
                                buffersToSerialize.Add(new BufferDataEntry
                                {
                                    offset        = dynamicSnapshotDataOffset,
                                    len           = bufferLen,
                                    serializerIdx = serializerIdx,
                                    bufferData    = bufferPointer
                                });
                                var maskSize = SnapshotDynamicBuffersHelper.GetDynamicDataChangeMaskSize(GhostComponentCollection[serializerIdx].ChangeMaskBits, bufferLen);
                                dynamicSnapshotDataOffset += GhostCollectionSystem.SnapshotSizeAligned(maskSize + GhostComponentCollection[serializerIdx].ComponentSize * bufferLen);
                            }
                            snapshotOffset += componentSnapshotSize;
                        }
                    }

                    //Second step (necessary only for buffers): resize the dynamicdata snapshot buffer and copy the buffer contents
                    if (GhostTypeCollection[ghostType].NumBuffers > 0 && buffersToSerialize.Length > 0)
                    {
                        var dynamicDataCapacity       = SnapshotDynamicBuffersHelper.CalculateBufferCapacity((uint)dynamicSnapshotDataOffset, out _);
                        var headerSize                = SnapshotDynamicBuffersHelper.GetHeaderSize();
                        var snapshotDynamicDataBuffer = snapshotDynamicDataBufferList[i];
                        snapshotDynamicDataBuffer.ResizeUninitialized((int)dynamicDataCapacity);
                        var snapshotDynamicDataBufferPtr = (byte *)snapshotDynamicDataBuffer.GetUnsafePtr() + headerSize;

                        ((uint *)snapshotDynamicDataBuffer.GetUnsafePtr())[0] = (uint)dynamicSnapshotDataOffset;
                        for (int buf = 0; buf < buffersToSerialize.Length; ++buf)
                        {
                            var entry           = buffersToSerialize[buf];
                            var dynamicDataSize = GhostComponentCollection[entry.serializerIdx].SnapshotSize;
                            var compSize        = GhostComponentCollection[entry.serializerIdx].ComponentSize;
                            var maskSize        = SnapshotDynamicBuffersHelper.GetDynamicDataChangeMaskSize(GhostComponentCollection[entry.serializerIdx].ChangeMaskBits, entry.len);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                            if ((maskSize + entry.offset + dynamicDataSize * entry.len) > dynamicDataCapacity)
                            {
                                throw new System.InvalidOperationException("Overflow writing data to dynamic snapshot memory buffer");
                            }
#endif
                            GhostComponentCollection[entry.serializerIdx].CopyToSnapshot.Ptr.Invoke((System.IntPtr)UnsafeUtility.AddressOf(ref serializerState),
                                                                                                    (System.IntPtr)snapshotDynamicDataBufferPtr + maskSize, entry.offset, dynamicDataSize,
                                                                                                    entry.bufferData, compSize, entry.len);
                        }
                    }

                    // Remove request component
                    commandBuffer.RemoveComponent <PredictedGhostSpawnRequestComponent>(entity);
                    // Add to list of predictive spawn component - maybe use a singleton for this so spawn systems can just access it too
                    spawnList.Add(new PredictedGhostSpawn {
                        entity = entity, ghostType = ghostType, spawnTick = spawnTick
                    });
                }
            }
Пример #5
0
            private bool DeserializeEntity(uint serverTick, ref DataStreamReader dataStream, ref DeserializeData data)
            {
                if (data.targetArchLen == 0)
                {
#if UNITY_EDITOR || DEVELOPMENT_BUILD
                    data.curPos = dataStream.GetBitsRead();
                    if (data.statCount > 0)
                    {
                        int statType = (int)data.targetArch;
                        netStats[statType * 3 + 4] = netStats[statType * 3 + 4] + data.statCount;
                        netStats[statType * 3 + 5] = netStats[statType * 3 + 5] + (uint)(data.curPos - data.startPos);
                        netStats[statType * 3 + 6] = netStats[statType * 3 + 6] + data.uncompressedCount;
                    }

                    data.startPos          = data.curPos;
                    data.statCount         = 0;
                    data.uncompressedCount = 0;
#endif
                    data.targetArch    = dataStream.ReadPackedUInt(compressionModel);
                    data.targetArchLen = dataStream.ReadPackedUInt(compressionModel);
                }

                --data.targetArchLen;

                if (data.baselineLen == 0)
                {
                    data.baselineTick  = serverTick - dataStream.ReadPackedUInt(compressionModel);
                    data.baselineTick2 = serverTick - dataStream.ReadPackedUInt(compressionModel);
                    data.baselineTick3 = serverTick - dataStream.ReadPackedUInt(compressionModel);
                    data.baselineLen   = dataStream.ReadPackedUInt(compressionModel);
                    if (data.baselineTick3 != serverTick &&
                        (data.baselineTick3 == data.baselineTick2 || data.baselineTick2 == data.baselineTick))
                    {
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                        UnityEngine.Debug.LogError("Received invalid snapshot baseline from server");
#endif
                        return(false);
                    }
                }

                --data.baselineLen;

                int  ghostId         = (int)dataStream.ReadPackedUInt(compressionModel);
                uint serverSpawnTick = 0;
                if (data.baselineTick == serverTick)
                {
                    serverSpawnTick = dataStream.ReadPackedUInt(compressionModel);
                }
                var   typeData        = GhostTypeCollection[(int)data.targetArch];
                int   changeMaskUints = GhostCollectionSystem.ChangeMaskArraySizeInUInts(typeData.ChangeMaskBits);
                int   snapshotOffset;
                int   snapshotSize = typeData.SnapshotSize;
                byte *baselineData = (byte *)UnsafeUtility.Malloc(snapshotSize, 16, Allocator.Temp);
                UnsafeUtility.MemClear(baselineData, snapshotSize);
                Entity gent;
                DynamicBuffer <SnapshotDataBuffer> snapshotDataBuffer;
                SnapshotData snapshotDataComponent;
                byte *       snapshotData;
                bool         existingGhost = ghostEntityMap.TryGetValue(ghostId, out gent);
                if (existingGhost && snapshotDataBufferFromEntity.HasComponent(gent) && ghostFromEntity[gent].ghostType == data.targetArch)
                {
                    snapshotDataBuffer = snapshotDataBufferFromEntity[gent];
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                    if (snapshotDataBuffer.Length != snapshotSize * GhostSystemConstants.SnapshotHistorySize)
                    {
                        throw new InvalidOperationException($"Invalid snapshot buffer size");
                    }
#endif
                    snapshotData                      = (byte *)snapshotDataBuffer.GetUnsafePtr();
                    snapshotDataComponent             = snapshotDataFromEntity[gent];
                    snapshotDataComponent.LatestIndex = (snapshotDataComponent.LatestIndex + 1) % GhostSystemConstants.SnapshotHistorySize;
                    snapshotDataFromEntity[gent]      = snapshotDataComponent;
                    if (data.baselineTick != serverTick)
                    {
                        for (int bi = 0; bi < snapshotDataBuffer.Length; bi += snapshotSize)
                        {
                            if (*(uint *)(snapshotData + bi) == data.baselineTick)
                            {
                                UnsafeUtility.MemCpy(baselineData, snapshotData + bi, snapshotSize);
                                break;
                            }
                        }

                        if (*(uint *)baselineData == 0)
                        {
                            return(false); // Ack desync detected
                        }
                    }

                    if (data.baselineTick3 != serverTick)
                    {
                        byte *baselineData2 = null;
                        byte *baselineData3 = null;
                        for (int bi = 0; bi < snapshotDataBuffer.Length; bi += snapshotSize)
                        {
                            if (*(uint *)(snapshotData + bi) == data.baselineTick2)
                            {
                                baselineData2 = snapshotData + bi;
                            }

                            if (*(uint *)(snapshotData + bi) == data.baselineTick3)
                            {
                                baselineData3 = snapshotData + bi;
                            }
                        }

                        if (baselineData2 == null || baselineData3 == null)
                        {
                            return(false); // Ack desync detected
                        }
                        snapshotOffset = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                        var predictor = new GhostDeltaPredictor(serverTick, data.baselineTick, data.baselineTick2, data.baselineTick3);
                        for (int comp = 0; comp < typeData.NumComponents; ++comp)
                        {
                            int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                            if (snapshotOffset + GhostComponentCollection[compIdx].SnapshotSize > snapshotSize)
                            {
                                throw new InvalidOperationException("Snapshot buffer overflow during predict");
                            }
#endif
                            GhostComponentCollection[compIdx].PredictDelta.Ptr.Invoke((IntPtr)(baselineData + snapshotOffset), (IntPtr)(baselineData2 + snapshotOffset), (IntPtr)(baselineData3 + snapshotOffset), ref predictor);
                            snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                        }
                    }
                }
                else
                {
                    if (existingGhost)
                    {
                        // The ghost entity map is out of date, clean it up
                        ghostEntityMap.Remove(ghostId);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                        if (ghostFromEntity.HasComponent(gent) && ghostFromEntity[gent].ghostType != data.targetArch)
                        {
                            UnityEngine.Debug.LogError("Received a ghost with an invalid ghost type");
                        }
                        UnityEngine.Debug.LogError("Found a ghost in the ghost map which does not have an entity connected to it. This can happen if you delete ghost entities on the client.");
#endif
                    }
                    if (data.baselineTick != serverTick)
                    {
                        // If the server specifies a baseline for a ghost we do not have that is an error
                        return(false);
                    }
                    ++data.newGhosts;

                    var ghostSpawnBuffer = ghostSpawnBufferFromEntity[ghostSpawnEntity];
                    snapshotDataBuffer = snapshotDataBufferFromEntity[ghostSpawnEntity];
                    var snapshotDataBufferOffset = snapshotDataBuffer.Length;
                    ghostSpawnBuffer.Add(new GhostSpawnBuffer {
                        GhostType = (int)data.targetArch, GhostID = ghostId, DataOffset = snapshotDataBufferOffset, ClientSpawnTick = serverTick, ServerSpawnTick = serverSpawnTick
                    });
                    snapshotDataBuffer.ResizeUninitialized(snapshotDataBufferOffset + snapshotSize);
                    snapshotData = (byte *)snapshotDataBuffer.GetUnsafePtr() + snapshotDataBufferOffset;
                    UnsafeUtility.MemClear(snapshotData, snapshotSize);
                    snapshotDataComponent = new SnapshotData {
                        SnapshotSize = snapshotSize, LatestIndex = 0
                    };
                }
                int maskOffset = 0;
                snapshotOffset          = GhostCollectionSystem.SnapshotSizeAligned(4 + changeMaskUints * 4);
                snapshotData           += snapshotSize * snapshotDataComponent.LatestIndex;
                *(uint *)(snapshotData) = serverTick;
                uint *changeMask = (uint *)(snapshotData + 4);
                for (int cm = 0; cm < changeMaskUints; ++cm)
                {
                    changeMask[cm] = dataStream.ReadPackedUIntDelta(((uint *)(baselineData + 4))[cm], compressionModel);
                }
                for (int comp = 0; comp < typeData.NumComponents; ++comp)
                {
                    int compIdx = GhostComponentIndex[typeData.FirstComponent + comp].ComponentIndex;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
                    if (maskOffset + GhostComponentCollection[compIdx].ChangeMaskBits > typeData.ChangeMaskBits || snapshotOffset + GhostComponentCollection[compIdx].SnapshotSize > snapshotSize)
                    {
                        throw new InvalidOperationException("Snapshot buffer overflow during deserialize");
                    }
#endif
                    GhostComponentCollection[compIdx].Deserialize.Ptr.Invoke((IntPtr)(snapshotData + snapshotOffset), (IntPtr)(baselineData + snapshotOffset), ref dataStream, ref compressionModel, (IntPtr)changeMask, maskOffset);
                    snapshotOffset += GhostCollectionSystem.SnapshotSizeAligned(GhostComponentCollection[compIdx].SnapshotSize);
                    maskOffset     += GhostComponentCollection[compIdx].ChangeMaskBits;
                }

#if UNITY_EDITOR || DEVELOPMENT_BUILD
                ++data.statCount;
                if (data.baselineTick == serverTick)
                {
                    ++data.uncompressedCount;
                }
#endif

                if (typeData.IsGhostGroup != 0)
                {
                    var groupLen = dataStream.ReadPackedUInt(compressionModel);
                    for (var i = 0; i < groupLen; ++i)
                    {
                        if (!DeserializeEntity(serverTick, ref dataStream, ref data))
                        {
                            return(false);
                        }
                    }
                }
                return(true);
            }