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 }); } }
protected unsafe override void OnUpdate() { m_GhostReceiveSystem.LastGhostMapWriter.Complete(); var interpolationTargetTick = m_ClientSimulationSystemGroup.InterpolationTick; if (m_ClientSimulationSystemGroup.InterpolationTickFraction < 1) { --interpolationTargetTick; } //var predictionTargetTick = m_ClientSimulationSystemGroup.ServerTick; var prefabsEntity = GetSingletonEntity <GhostCollection>(); var prefabs = EntityManager.GetBuffer <GhostCollectionPrefab>(prefabsEntity).ToNativeArray(Allocator.Temp); var ghostSpawnEntity = GetSingletonEntity <GhostSpawnQueueComponent>(); var ghostSpawnBufferComponent = EntityManager.GetBuffer <GhostSpawnBuffer>(ghostSpawnEntity); var snapshotDataBufferComponent = EntityManager.GetBuffer <SnapshotDataBuffer>(ghostSpawnEntity); var ghostSpawnBuffer = ghostSpawnBufferComponent.ToNativeArray(Allocator.Temp); var snapshotDataBuffer = snapshotDataBufferComponent.ToNativeArray(Allocator.Temp); ghostSpawnBufferComponent.ResizeUninitialized(0); snapshotDataBufferComponent.ResizeUninitialized(0); var spawnedGhosts = new NativeList <SpawnedGhostMapping>(16, Allocator.Temp); var nonSpawnedGhosts = new NativeList <NonSpawnedGhostMapping>(16, Allocator.Temp); var ghostCollectionSingleton = GetSingletonEntity <GhostCollection>(); for (int i = 0; i < ghostSpawnBuffer.Length; ++i) { var ghost = ghostSpawnBuffer[i]; Entity entity = Entity.Null; byte * snapshotData = null; var ghostTypeCollection = EntityManager.GetBuffer <GhostCollectionPrefabSerializer>(ghostCollectionSingleton); var snapshotSize = ghostTypeCollection[ghost.GhostType].SnapshotSize; bool hasBuffers = ghostTypeCollection[ghost.GhostType].NumBuffers > 0; if (ghost.SpawnType == GhostSpawnBuffer.Type.Interpolated) { // Add to m_DelayedSpawnQueue entity = EntityManager.CreateEntity(); EntityManager.AddComponentData(entity, new GhostComponent { ghostId = ghost.GhostID, ghostType = ghost.GhostType, spawnTick = ghost.ServerSpawnTick }); var newBuffer = EntityManager.AddBuffer <SnapshotDataBuffer>(entity); newBuffer.ResizeUninitialized(snapshotSize * GhostSystemConstants.SnapshotHistorySize); snapshotData = (byte *)newBuffer.GetUnsafePtr(); //Add also the SnapshotDynamicDataBuffer if the entity has buffers to copy the dynamic contents if (hasBuffers) { EntityManager.AddBuffer <SnapshotDynamicDataBuffer>(entity); } EntityManager.AddComponentData(entity, new SnapshotData { SnapshotSize = snapshotSize, LatestIndex = 0 }); m_DelayedSpawnQueue.Enqueue(new GhostSpawnSystem.DelayedSpawnGhost { ghostId = ghost.GhostID, ghostType = ghost.GhostType, clientSpawnTick = ghost.ClientSpawnTick, serverSpawnTick = ghost.ServerSpawnTick, oldEntity = entity }); nonSpawnedGhosts.Add(new NonSpawnedGhostMapping { ghostId = ghost.GhostID, entity = entity }); } else if (ghost.SpawnType == GhostSpawnBuffer.Type.Predicted) { // TODO: this could allow some time for the prefab to load before giving an error if (prefabs[ghost.GhostType].GhostPrefab == Entity.Null) { ReportMissingPrefab(); continue; } // Spawn directly entity = ghost.PredictedSpawnEntity != Entity.Null ? ghost.PredictedSpawnEntity : EntityManager.Instantiate(prefabs[ghost.GhostType].GhostPrefab); if (EntityManager.HasComponent <GhostPrefabMetaDataComponent>(prefabs[ghost.GhostType].GhostPrefab)) { ref var toRemove = ref EntityManager.GetComponentData <GhostPrefabMetaDataComponent>(prefabs[ghost.GhostType].GhostPrefab).Value.Value.DisableOnPredictedClient; //Need copy because removing component will invalidate the buffer pointer, since introduce structural changes var linkedEntityGroup = EntityManager.GetBuffer <LinkedEntityGroup>(entity).ToNativeArray(Allocator.Temp); for (int rm = 0; rm < toRemove.Length; ++rm) { var compType = ComponentType.ReadWrite(TypeManager.GetTypeIndexFromStableTypeHash(toRemove[rm].StableHash)); EntityManager.RemoveComponent(linkedEntityGroup[toRemove[rm].EntityIndex].Value, compType); } } EntityManager.SetComponentData(entity, new GhostComponent { ghostId = ghost.GhostID, ghostType = ghost.GhostType, spawnTick = ghost.ServerSpawnTick }); var newBuffer = EntityManager.GetBuffer <SnapshotDataBuffer>(entity); newBuffer.ResizeUninitialized(snapshotSize * GhostSystemConstants.SnapshotHistorySize); snapshotData = (byte *)newBuffer.GetUnsafePtr(); EntityManager.SetComponentData(entity, new SnapshotData { SnapshotSize = snapshotSize, LatestIndex = 0 }); spawnedGhosts.Add(new SpawnedGhostMapping { ghost = new SpawnedGhost { ghostId = ghost.GhostID, spawnTick = ghost.ServerSpawnTick }, entity = entity }); } if (entity != Entity.Null) { UnsafeUtility.MemClear(snapshotData, snapshotSize * GhostSystemConstants.SnapshotHistorySize); UnsafeUtility.MemCpy(snapshotData, (byte *)snapshotDataBuffer.GetUnsafeReadOnlyPtr() + ghost.DataOffset, snapshotSize); if (hasBuffers) { //Resize and copy the associated dynamic buffer snapshot data var snapshotDynamicBuffer = EntityManager.GetBuffer <SnapshotDynamicDataBuffer>(entity); var dynamicDataCapacity = SnapshotDynamicBuffersHelper.CalculateBufferCapacity(ghost.DynamicDataSize, out var _); snapshotDynamicBuffer.ResizeUninitialized((int)dynamicDataCapacity); var dynamicSnapshotData = (byte *)snapshotDynamicBuffer.GetUnsafePtr(); if (dynamicSnapshotData == null) { throw new InvalidOperationException("snapshot dynamic data buffer not initialized but ghost has dynamic buffer contents"); } // Update the dynamic data header (uint[GhostSystemConstants.SnapshotHistorySize)]) by writing the used size for the current slot // (for new spawned entity is 0). Is un-necessary to initialize all the header slots to 0 since that information is only used // for sake of delta compression and, because that depend on the acked tick, only initialized and relevant slots are accessed in general. // For more information about the layout, see SnapshotData.cs. ((uint *)dynamicSnapshotData)[0] = ghost.DynamicDataSize; var headerSize = SnapshotDynamicBuffersHelper.GetHeaderSize(); UnsafeUtility.MemCpy(dynamicSnapshotData + headerSize, (byte *)snapshotDataBuffer.GetUnsafeReadOnlyPtr() + ghost.DataOffset + snapshotSize, ghost.DynamicDataSize); } } }