public unsafe void Execute(DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { var writer = NetDriver.BeginSend(UnreliablePipeline, ConnectionFromEntity[ConnectionEntity].Value, GlobalConstants.TargetPacketSize); WriteHeader(ref writer, AckFromEntity[ConnectionEntity]); var lenWriter = writer; writer.WriteUInt(0); writer.WriteUInt(0); // 销毁的entity uint despawnLen = 0; uint updateLen = 0; despawnLen = SerializeDespawnEntities(ref writer, DespawnChunks); if (writer.HasFailedWrites) { NetDriver.AbortSend(writer); throw new InvalidOperationException("单个快照中无法包含所有需要销毁的GhostId!"); } updateLen = SerializeEntities(ref writer, GhostChunks, default, ghostChunkComponentTypesPtr,
public void Execute(DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { for (int i = 0; i < GhostChunks.Length; i++) { ArchetypeChunk chunk = GhostChunks[i]; var entities = chunk.GetNativeArray(EntityTypeHandle); var buffers = chunk.GetBufferAccessor(SnapshotBufferTypeHandle); var datas = chunk.GetNativeArray(SnapshotComponentTypeHandle); var ghosts = chunk.GetNativeArray(GhostComponentTypeHandle); for (int ent = 0; ent < entities.Length; ent++) { GhostComponent ghostComponent = ghosts[ent]; var typeState = GhostTypeCollection[ghostComponent.GhostType]; SnapshotData snapshotData = datas[ent]; DynamicBuffer <SnapshotDataBuffer> snapshotDataBuffer = buffers[ent]; if (snapshotData.SnapshotSize == 0) { continue; } bool isGet = snapshotData.GetDataAtTick(snapshotDataBuffer, InterpolatedTargetTick, InterpolatedTargetTickFraction, out var dataAtTick); int baseOffset = typeState.FirstComponent; int numBaseComponents = typeState.NumComponents; // 跳过Tick int offset = GlobalConstants.TickSize; for (int j = 0; j < numBaseComponents; j++) { int compIdx = ComponentIndex[baseOffset + j].ComponentIndex; GhostComponentSerializer serializer = ComponentSerializers[compIdx]; DynamicComponentTypeHandle dynamicType = ghostChunkComponentTypesPtr[compIdx]; if (chunk.Has(dynamicType)) { IntPtr compPtr = (IntPtr)chunk .GetDynamicComponentDataArrayReinterpret <byte>(dynamicType, serializer.ComponentSize).GetUnsafePtr(); if (isGet) { serializer.CopyFromSnapshot.Ptr.Invoke( compPtr + serializer.ComponentSize * ent, (IntPtr)(&dataAtTick), offset); } } offset += serializer.DataSize; } } } }
public void Execute(DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { for (int i = 0; i < GhostChunks.Length; i++) { var chunk = GhostChunks[i]; var entities = chunk.GetNativeArray(EntityTypeHandle); var ghosts = chunk.GetNativeArray(GhostTypeHandle); var snapshots = chunk.GetNativeArray(SnapshotDataTypeHandle); var snapshotBuffers = chunk.GetBufferAccessor(SnapshotDataBufferTypeHandle); for (int ent = 0; ent < entities.Length; ent++) { var ghost = ghosts[ent]; var snapshot = snapshots[ent]; var snapshotBuffer = snapshotBuffers[ent]; var typeState = GhostTypeCollection[ghost.GhostType]; snapshot.LatestIndex = (snapshot.LatestIndex + 1) % GlobalConstants.SnapshotHistorySize; snapshots[ent] = snapshot; byte *snapshotData = (byte *)snapshotBuffer.GetUnsafePtr() + snapshot.SnapshotSize * snapshot.LatestIndex; // 写入Tick *(uint *)snapshotData = Tick; snapshotData += GlobalConstants.TickSize; int baseOffset = typeState.FirstComponent; int numBaseComponents = typeState.NumComponents; for (int j = 0; j < numBaseComponents; j++) { var compIdx = GhostComponentIndex[baseOffset + j].ComponentIndex; var serializer = GhostComponentSerializers[compIdx]; var dynamicType = ghostChunkComponentTypesPtr[compIdx]; if (chunk.Has(dynamicType)) { var compPrt = (IntPtr) chunk.GetDynamicComponentDataArrayReinterpret <byte>(dynamicType, serializer.ComponentSize).GetUnsafePtr(); serializer.CopyToSnapshot.Ptr.Invoke(compPrt + serializer.ComponentSize * ent, (IntPtr)snapshotData); } snapshotData += serializer.DataSize; } } } }
unsafe TestJob BuildJob() { var job = new TestJob(); DynamicComponentTypeHandle *ptr = (DynamicComponentTypeHandle *)&job.Type0; { for (int i = 0; i < Types.Length; i++) { ptr[i] = GetDynamicComponentTypeHandle(Types[i]); } } return(job); }
public static unsafe void PopulateListFromArray <T>(SystemBase system, NativeArray <ComponentType> componentTypes, bool readOnly, ref T list) where T : struct, IDynamicTypeList { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (UnsafeUtility.SizeOf <ArchetypeChunkComponentTypeDynamic8>() != UnsafeUtility.SizeOf <DynamicComponentTypeHandle>() * 8) { throw new System.Exception("Invalid type size, this will cause undefined behavior"); } #endif DynamicComponentTypeHandle *componentTypesPtr = list.GetData(); list.Length = componentTypes.Length; for (int i = 0; i < list.Length; ++i) { var compType = componentTypes[i]; if (readOnly) { compType.AccessModeType = ComponentType.AccessMode.ReadOnly; } componentTypesPtr[i] = system.GetDynamicComponentTypeHandle(compType); } }
public static unsafe void PopulateList <T>(SystemBase system, DynamicBuffer <GhostCollectionComponentType> ghostComponentCollection, bool readOnly, ref T list) where T : struct, IDynamicTypeList { #if ENABLE_UNITY_COLLECTIONS_CHECKS if (UnsafeUtility.SizeOf <ArchetypeChunkComponentTypeDynamic32>() != UnsafeUtility.SizeOf <DynamicComponentTypeHandle>() * 32) { throw new System.Exception("Invalid type size, this will cause undefined behavior"); } #endif var listLength = ghostComponentCollection.Length; DynamicComponentTypeHandle *GhostChunkComponentTypesPtr = list.GetData(); list.Length = listLength; for (int i = 0; i < list.Length; ++i) { var compType = ghostComponentCollection[i].Type; if (readOnly) { compType.AccessModeType = ComponentType.AccessMode.ReadOnly; } GhostChunkComponentTypesPtr[i] = system.GetDynamicComponentTypeHandle(compType); } }
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; }
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 void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { if (!predictionState.TryGetValue(chunk, out var state) || (*(PredictionBackupState *)state).entityCapacity != chunk.Capacity) { return; } var ghostComponents = chunk.GetNativeArray(ghostType); int ghostTypeId = ghostComponents[0].ghostType; var typeData = GhostTypeCollection[ghostTypeId]; var headerSize = PredictionBackupState.GetHeaderSize(); var entitySize = PredictionBackupState.GetEntitiesSize(chunk.Capacity, out var singleEntitySize); int baseOffset = typeData.FirstComponent; Entity *backupEntities = PredictionBackupState.GetEntities(state, headerSize); var entities = chunk.GetNativeArray(entityType); var predictedGhostComponents = chunk.GetNativeArray(predictedGhostType); byte *dataPtr = PredictionBackupState.GetData(state, headerSize, entitySize); int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; for (int comp = 0; comp < numBaseComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + 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) { continue; } var compSize = GhostComponentCollection[compIdx].ComponentSize; if (chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); for (int ent = 0; ent < entities.Length; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] == backupEntities[ent]) { SmoothComponent(compData + compSize * ent, dataPtr + compSize * ent, GhostComponentCollection[compIdx].ComponentType); } } } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } if (typeData.NumChildComponents > 0) { var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + 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) { continue; } var compSize = GhostComponentCollection[compIdx].ComponentSize; var entityIdx = GhostComponentIndex[baseOffset + comp].EntityIndex; for (int ent = 0; ent < chunk.Count; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] != backupEntities[ent]) { continue; } var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[entityIdx].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafePtr(); SmoothComponent(compData + compSize * childChunk.index, dataPtr + compSize * ent, GhostComponentCollection[compIdx].ComponentType); } } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } } }
//Sum up all the dynamic buffers raw data content size. Each buffer content size is aligned to 16 bytes private int GetChunkBuffersDataSize(GhostCollectionPrefabSerializer typeData, ArchetypeChunk chunk, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength, DynamicBuffer <GhostCollectionComponentIndex> GhostComponentIndex, DynamicBuffer <GhostComponentSerializer.State> GhostComponentCollection) { int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; int bufferTotalSize = 0; int baseOffset = typeData.FirstComponent; for (int comp = 0; comp < numBaseComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { continue; } if (chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var bufferData = chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]); for (int i = 0; i < bufferData.Length; ++i) { bufferTotalSize += bufferData.GetBufferCapacity(i) * GhostComponentCollection[serializerIdx].ComponentSize; } bufferTotalSize = GhostCollectionSystem.SnapshotSizeAligned(bufferTotalSize); } } if (typeData.NumChildComponents > 0) { var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { continue; } for (int ent = 0; ent < chunk.Count; ++ent) { var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var bufferData = childChunk.chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]); bufferTotalSize += bufferData.GetBufferCapacity(childChunk.index) * GhostComponentCollection[serializerIdx].ComponentSize; } bufferTotalSize = GhostCollectionSystem.SnapshotSizeAligned(bufferTotalSize); } } } return(bufferTotalSize); }
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength, DynamicComponentTypeHandle *userTypes, int userTypesLength) { if (!predictionState.TryGetValue(chunk, out var state) || (*(PredictionBackupState *)state).entityCapacity != chunk.Capacity) { return; } var GhostTypeCollection = GhostTypeCollectionFromEntity[GhostCollectionSingleton]; var GhostComponentIndex = GhostComponentIndexFromEntity[GhostCollectionSingleton]; var GhostComponentCollection = GhostComponentCollectionFromEntity[GhostCollectionSingleton]; var ghostComponents = chunk.GetNativeArray(ghostType); int ghostTypeId = ghostComponents.GetFirstGhostTypeId(); if (ghostTypeId < 0) { return; } var typeData = GhostTypeCollection[ghostTypeId]; var headerSize = PredictionBackupState.GetHeaderSize(); var entitySize = PredictionBackupState.GetEntitiesSize(chunk.Capacity, out var singleEntitySize); Entity *backupEntities = PredictionBackupState.GetEntities(state); var entities = chunk.GetNativeArray(entityType); var predictedGhostComponents = chunk.GetNativeArray(predictedGhostType); int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; int baseOffset = typeData.FirstComponent; var actions = new NativeList <SmoothingActionState>(Allocator.Temp); var childActions = new NativeList <SmoothingActionState>(Allocator.Temp); int backupOffset = headerSize + entitySize; // todo: this loop could be cached on chunk.capacity, because now we are re-calculating it everytime. for (int comp = 0; comp < typeData.NumComponents; ++comp) { int index = baseOffset + comp; int compIdx = GhostComponentIndex[index].ComponentIndex; int serializerIdx = GhostComponentIndex[index].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[index].SendMask & requiredSendMask) == 0) { continue; } if (GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { backupOffset += PredictionBackupState.GetDataSize(GhostSystemConstants.DynamicBufferComponentSnapshotSize, chunk.Capacity); continue; } var compSize = GhostComponentCollection[serializerIdx].ComponentSize; if (smoothingActions.TryGetValue(GhostComponentCollection[serializerIdx].ComponentType, out var action)) { action.compIndex = compIdx; action.compSize = compSize; action.serializerIndex = serializerIdx; action.entityIndex = GhostComponentIndex[index].EntityIndex; action.compBackupOffset = backupOffset; if (comp < numBaseComponents) { actions.Add(action); } else { childActions.Add(action); } } backupOffset += PredictionBackupState.GetDataSize(compSize, chunk.Capacity); } foreach (var action in actions) { if (chunk.Has(ghostChunkComponentTypesPtr[action.compIndex])) { for (int ent = 0; ent < entities.Length; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] != backupEntities[ent]) { continue; } var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[action.compIndex], action.compSize).GetUnsafePtr(); void *usrDataPtr = null; if (action.userTypeId >= 0 && chunk.Has(userTypes[action.userTypeId])) { var usrData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(userTypes[action.userTypeId], action.userTypeSize).GetUnsafeReadOnlyPtr(); usrDataPtr = usrData + action.userTypeSize * ent; } byte *dataPtr = ((byte *)state) + action.compBackupOffset; action.action.Ptr.Invoke(compData + action.compSize * ent, dataPtr + action.compSize * ent, usrDataPtr); } } } var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); foreach (var action in childActions) { for (int ent = 0; ent < chunk.Count; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] != backupEntities[ent]) { continue; } var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[action.entityIndex].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[action.compIndex])) { var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[action.compIndex], action.compSize).GetUnsafePtr(); void *usrDataPtr = null; if (action.userTypeId >= 0 && chunk.Has(userTypes[action.userTypeId])) { var usrData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(userTypes[action.userTypeId], action.userTypeSize).GetUnsafeReadOnlyPtr(); usrDataPtr = usrData + action.userTypeSize * ent; } byte *dataPtr = ((byte *)state) + action.compBackupOffset; action.action.Ptr.Invoke(compData + action.compSize * childChunk.index, dataPtr + action.compSize * ent, usrDataPtr); } } } }
public void Execute(DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { for (int i = 0; i < GhostChunks.Length; i++) { ArchetypeChunk chunk = GhostChunks[i]; var entities = chunk.GetNativeArray(EntityTypeHandle); var buffers = chunk.GetBufferAccessor(SnapshotBufferTypeHandle); var datas = chunk.GetNativeArray(SnapshotComponentTypeHandle); var ghosts = chunk.GetNativeArray(GhostComponentTypeHandle); for (int ent = 0; ent < entities.Length; ent++) { GhostComponent ghostComponent = ghosts[ent]; var typeState = GhostTypeCollection[ghostComponent.GhostType]; SnapshotData snapshotData = datas[ent]; DynamicBuffer <SnapshotDataBuffer> snapshotDataBuffer = buffers[ent]; if (snapshotData.SnapshotSize == 0) { continue; } IntPtr latestSnapshot = snapshotData.GetLatest(snapshotDataBuffer); int baseOffset = typeState.FirstComponent; int numBaseComponents = typeState.NumComponents; // 跳过Tick int offset = GlobalConstants.TickSize; for (int j = 0; j < numBaseComponents; j++) { int cmpIdx = ComponentIndex[baseOffset + j].ComponentIndex; var serializer = ComponentSerializers[cmpIdx]; DynamicComponentTypeHandle dynamicType = ghostChunkComponentTypesPtr[cmpIdx]; if (chunk.Has(dynamicType)) { IntPtr compPtr = (IntPtr)chunk .GetDynamicComponentDataArrayReinterpret <byte>(dynamicType, serializer.ComponentSize).GetUnsafePtr(); // 不回滚只更新值 if (serializer.IsUpdateValue) { var dataAtTick = new SnapshotData.DataAtTick { Tick = snapshotData.GetLatestTick(snapshotDataBuffer), InterpolationFactor = 1, SnapshotAfter = latestSnapshot, SnapshotBefore = latestSnapshot }; serializer.CopyFromSnapshot.Ptr.Invoke( compPtr + serializer.ComponentSize * ent, (IntPtr)(&dataAtTick), offset); } else { serializer.RestoreFromBackup.Ptr.Invoke( compPtr + serializer.ComponentSize * ent, latestSnapshot + offset); } } offset += serializer.DataSize; } } } }
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 void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { var GhostTypeCollection = GhostTypeCollectionFromEntity[GhostCollectionSingleton]; var GhostComponentIndex = GhostComponentIndexFromEntity[GhostCollectionSingleton]; var GhostComponentCollection = GhostComponentCollectionFromEntity[GhostCollectionSingleton]; var ghostComponents = chunk.GetNativeArray(ghostType); int ghostTypeId = ghostComponents.GetFirstGhostTypeId(); if (ghostTypeId < 0) { return; } var typeData = GhostTypeCollection[ghostTypeId]; var singleEntitySize = UnsafeUtility.SizeOf <Entity>(); int baseOffset = typeData.FirstComponent; if (!predictionState.TryGetValue(chunk, out var state) || (*(PredictionBackupState *)state).ghostType != ghostTypeId || (*(PredictionBackupState *)state).entityCapacity != chunk.Capacity) { int dataSize = 0; // Sum up the size of all components rounded up for (int comp = 0; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } //for buffers we store a a pair of uint: // uint length: the num of elements // uint backupDataOffset: the start position in the backup buffer if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { dataSize += PredictionBackupState.GetDataSize( GhostComponentCollection[serializerIdx].ComponentSize, chunk.Capacity); } else { dataSize += PredictionBackupState.GetDataSize(GhostSystemConstants.DynamicBufferComponentSnapshotSize, chunk.Capacity); } } //compute the space necessary to store the dynamic buffers data for the chunk int buffersDataCapacity = 0; if (typeData.NumBuffers > 0) { buffersDataCapacity = GetChunkBuffersDataSize(typeData, chunk, ghostChunkComponentTypesPtr, ghostChunkComponentTypesLength, GhostComponentIndex, GhostComponentCollection); } // Chunk does not exist in the history, or has changed ghost type in which case we need to create a new one state = PredictionBackupState.AllocNew(ghostTypeId, dataSize, chunk.Capacity, buffersDataCapacity); newPredictionState.Enqueue(new PredictionStateEntry { chunk = chunk, data = state }); } else { stillUsedPredictionState.TryAdd(chunk, 1); if (typeData.NumBuffers > 0) { //resize the backup state to fit the dynamic buffers contents var buffersDataCapacity = GetChunkBuffersDataSize(typeData, chunk, ghostChunkComponentTypesPtr, ghostChunkComponentTypesLength, GhostComponentIndex, GhostComponentCollection); int bufferBackupDataCapacity = PredictionBackupState.GetBufferDataCapacity(state); if (bufferBackupDataCapacity < buffersDataCapacity) { var dataSize = ((PredictionBackupState *)state)->dataSize; var newState = PredictionBackupState.AllocNew(ghostTypeId, dataSize, chunk.Capacity, buffersDataCapacity); UnsafeUtility.Free((void *)state, Allocator.Persistent); state = newState; updatedPredictionState.Enqueue(new PredictionStateEntry { chunk = chunk, data = newState }); } } } Entity *entities = PredictionBackupState.GetEntities(state); var srcEntities = chunk.GetNativeArray(entityType).GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(entities, srcEntities, chunk.Count * singleEntitySize); if (chunk.Count < chunk.Capacity) { UnsafeUtility.MemClear(entities + chunk.Count, (chunk.Capacity - chunk.Count) * singleEntitySize); } byte *dataPtr = PredictionBackupState.GetData(state); byte *bufferBackupDataPtr = PredictionBackupState.GetBufferDataPtr(state); int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; int bufferBackupDataOffset = 0; for (int comp = 0; comp < numBaseComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } var compSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer ? GhostSystemConstants.DynamicBufferComponentSnapshotSize : GhostComponentCollection[serializerIdx].ComponentSize; if (!chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { UnsafeUtility.MemClear(dataPtr, chunk.Count * compSize); } else if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(dataPtr, compData, chunk.Count * compSize); } else { var bufferData = chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]); var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize; //Use local variable to iterate and set the buffer offset and length. The dataptr must be //advanced "per chunk" to the next correct position var tempDataPtr = dataPtr; for (int i = 0; i < bufferData.Length; ++i) { //Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(i, out var size); ((int *)tempDataPtr)[0] = size; ((int *)tempDataPtr)[1] = bufferBackupDataOffset; if (size > 0) { UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte *)bufferPtr, size * bufElemSize); } bufferBackupDataOffset += size * bufElemSize; tempDataPtr += compSize; } bufferBackupDataOffset = GhostCollectionSystem.SnapshotSizeAligned(bufferBackupDataOffset); } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } if (typeData.NumChildComponents > 0) { var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } var isBuffer = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer; var compSize = isBuffer ? GhostSystemConstants.DynamicBufferComponentSnapshotSize : GhostComponentCollection[serializerIdx].ComponentSize; if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { //use a temporary for the iteration here. Otherwise when the dataptr is offset for the chunk, we //end up in the wrong position var tempDataPtr = dataPtr; for (int ent = 0; ent < chunk.Count; ++ent) { var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(tempDataPtr, compData + childChunk.index * compSize, compSize); } else { UnsafeUtility.MemClear(tempDataPtr, compSize); } tempDataPtr += compSize; } } else { var bufElemSize = GhostComponentCollection[serializerIdx].ComponentSize; var tempDataPtr = dataPtr; for (int ent = 0; ent < chunk.Count; ++ent) { var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var bufferData = childChunk.chunk.GetUntypedBufferAccessor(ref ghostChunkComponentTypesPtr[compIdx]); //Retrieve an copy each buffer data. Set size and offset in the backup buffer in the component backup var bufferPtr = bufferData.GetUnsafeReadOnlyPtrAndLength(childChunk.index, out var size); ((int *)tempDataPtr)[0] = size; ((int *)tempDataPtr)[1] = bufferBackupDataOffset; if (size > 0) { UnsafeUtility.MemCpy(bufferBackupDataPtr + bufferBackupDataOffset, (byte *)bufferPtr, size * bufElemSize); } bufferBackupDataOffset += size * bufElemSize; } else { //reset the entry to 0. Don't use memcpy in this case (is faster this way) ((long *)tempDataPtr)[0] = 0; } tempDataPtr += compSize; } bufferBackupDataOffset = GhostCollectionSystem.SnapshotSizeAligned(bufferBackupDataOffset); } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } } }
public void Execute(ArchetypeChunk chunk, int chunkIndex, int firstEntityIndex, DynamicComponentTypeHandle *ghostChunkComponentTypesPtr, int ghostChunkComponentTypesLength) { var ghostComponents = chunk.GetNativeArray(ghostType); int ghostTypeId = ghostComponents[0].ghostType; var typeData = GhostTypeCollection[ghostTypeId]; var headerSize = PredictionBackupState.GetHeaderSize(); var entitySize = PredictionBackupState.GetEntitiesSize(chunk.Capacity, out var singleEntitySize); int baseOffset = typeData.FirstComponent; if (!predictionState.TryGetValue(chunk, out var state) || (*(PredictionBackupState *)state).ghostType != ghostTypeId || (*(PredictionBackupState *)state).entityCapacity != chunk.Capacity) { int dataSize = 0; // Sum up the size of all components rounded up for (int comp = 0; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + 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) { continue; } dataSize += PredictionBackupState.GetDataSize(GhostComponentCollection[compIdx].ComponentSize, chunk.Capacity); } // Chunk does not exist in the history, or has changed ghost type in which case we need to create a new one state = (System.IntPtr)UnsafeUtility.Malloc(headerSize + entitySize + dataSize, 16, Allocator.Persistent); newPredictionState.Enqueue(new PredictionStateEntry { chunk = chunk, data = state }); (*(PredictionBackupState *)state).ghostType = ghostTypeId; (*(PredictionBackupState *)state).entityCapacity = chunk.Capacity; } else { stillUsedPredictionState.TryAdd(chunk, 1); } Entity *entities = PredictionBackupState.GetEntities(state, headerSize); var srcEntities = chunk.GetNativeArray(entityType).GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(entities, srcEntities, chunk.Count * singleEntitySize); if (chunk.Count < chunk.Capacity) { UnsafeUtility.MemClear(entities + chunk.Count, (chunk.Capacity - chunk.Count) * singleEntitySize); } byte *dataPtr = PredictionBackupState.GetData(state, headerSize, entitySize); int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; for (int comp = 0; comp < numBaseComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + 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) { continue; } var compSize = GhostComponentCollection[compIdx].ComponentSize; if (chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); UnsafeUtility.MemCpy(dataPtr, compData, chunk.Count * compSize); } else { UnsafeUtility.MemClear(dataPtr, chunk.Count * compSize); } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } if (typeData.NumChildComponents > 0) { var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + 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) { continue; } var compSize = GhostComponentCollection[compIdx].ComponentSize; for (int ent = 0; ent < chunk.Count; ++ent) { var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[GhostComponentIndex[baseOffset + comp].EntityIndex].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafePtr(); UnsafeUtility.MemCpy(dataPtr + ent * compSize, compData + childChunk.index * compSize, compSize); } else { UnsafeUtility.MemClear(dataPtr + ent * compSize, compSize); } } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } } }
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) { if (!predictionState.TryGetValue(chunk, out var state) || (*(PredictionBackupState *)state).entityCapacity != chunk.Capacity) { return; } var GhostTypeCollection = GhostTypeCollectionFromEntity[GhostCollectionSingleton]; var GhostComponentIndex = GhostComponentIndexFromEntity[GhostCollectionSingleton]; var GhostComponentCollection = GhostComponentCollectionFromEntity[GhostCollectionSingleton]; var ghostComponents = chunk.GetNativeArray(ghostType); int ghostTypeId = ghostComponents.GetFirstGhostTypeId(); if (ghostTypeId < 0) { return; } var typeData = GhostTypeCollection[ghostTypeId]; int baseOffset = typeData.FirstComponent; Entity *backupEntities = PredictionBackupState.GetEntities(state); var entities = chunk.GetNativeArray(entityType); var predictedGhostComponents = chunk.GetNativeArray(predictedGhostType); byte *dataPtr = PredictionBackupState.GetData(state); int numBaseComponents = typeData.NumComponents - typeData.NumChildComponents; for (int comp = 0; comp < numBaseComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } var compSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer ? GhostSystemConstants.DynamicBufferComponentSnapshotSize : GhostComponentCollection[serializerIdx].ComponentSize; if (chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { var compData = (byte *)chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); for (int ent = 0; ent < entities.Length; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] == backupEntities[ent]) { int errorIndex = GhostComponentIndex[baseOffset + comp].PredictionErrorBaseIndex; var errors = new UnsafeList <float>(((float *)predictionErrors.GetUnsafePtr()) + errorIndex + ThreadIndex * numPredictionErrors, numPredictionErrors - errorIndex); GhostComponentCollection[serializerIdx].ReportPredictionErrors.Ptr.Invoke((System.IntPtr)(compData + compSize * ent), (System.IntPtr)(dataPtr + compSize * ent), ref errors); } } } else { //FIXME Buffers need to report error for the size and an aggregate for each element in the buffer } } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } if (typeData.NumChildComponents > 0) { var linkedEntityGroupAccessor = chunk.GetBufferAccessor(linkedEntityGroupType); for (int comp = numBaseComponents; comp < typeData.NumComponents; ++comp) { int compIdx = GhostComponentIndex[baseOffset + comp].ComponentIndex; int serializerIdx = GhostComponentIndex[baseOffset + comp].SerializerIndex; #if ENABLE_UNITY_COLLECTIONS_CHECKS if (compIdx >= ghostChunkComponentTypesLength) { throw new System.InvalidOperationException("Component index out of range"); } #endif if ((GhostComponentIndex[baseOffset + comp].SendMask & requiredSendMask) == 0) { continue; } var compSize = GhostComponentCollection[serializerIdx].ComponentType.IsBuffer ? GhostSystemConstants.DynamicBufferComponentSnapshotSize : GhostComponentCollection[serializerIdx].ComponentSize; var entityIdx = GhostComponentIndex[baseOffset + comp].EntityIndex; for (int ent = 0; ent < chunk.Count; ++ent) { // If this entity did not predict anything there was no rollback and no need to debug it if (!GhostPredictionSystemGroup.ShouldPredict(tick, predictedGhostComponents[ent])) { continue; } if (entities[ent] != backupEntities[ent]) { continue; } var linkedEntityGroup = linkedEntityGroupAccessor[ent]; if (childEntityLookup.TryGetValue(linkedEntityGroup[entityIdx].Value, out var childChunk) && childChunk.chunk.Has(ghostChunkComponentTypesPtr[compIdx])) { if (!GhostComponentCollection[serializerIdx].ComponentType.IsBuffer) { var compData = (byte *)childChunk.chunk.GetDynamicComponentDataArrayReinterpret <byte>(ghostChunkComponentTypesPtr[compIdx], compSize).GetUnsafeReadOnlyPtr(); int errorIndex = GhostComponentIndex[baseOffset + comp].PredictionErrorBaseIndex; var errors = new UnsafeList <float>(((float *)predictionErrors.GetUnsafePtr()) + errorIndex + ThreadIndex * numPredictionErrors, numPredictionErrors - errorIndex); GhostComponentCollection[serializerIdx].ReportPredictionErrors.Ptr.Invoke((System.IntPtr)(compData + compSize * childChunk.index), (System.IntPtr)(dataPtr + compSize * ent), ref errors); } else { //FIXME Buffers need to report error for the size and an aggregate for each element in the buffer } } } dataPtr = PredictionBackupState.GetNextData(dataPtr, compSize, chunk.Capacity); } } }