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 }); } }
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 }); } }
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); }