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