public unsafe void Execute() { for (int chunk = 0; chunk < spawnChunks.Length; ++chunk) { var entities = spawnChunks[chunk].GetNativeArray(entityType); int ghostType = serializers.FindSerializer(spawnChunks[chunk].Archetype); var ghostState = (GhostSystemStateComponent *)UnsafeUtility.Malloc( UnsafeUtility.SizeOf <GhostSystemStateComponent>() * entities.Length, UnsafeUtility.AlignOf <GhostSystemStateComponent>(), Allocator.TempJob); for (var ent = 0; ent < entities.Length; ++ent) { int newId; if (!freeGhostIds.TryDequeue(out newId)) { newId = allocatedGhostIds[0]; allocatedGhostIds[0] = newId + 1; } ghostState[ent] = new GhostSystemStateComponent { ghostId = newId, ghostTypeIndex = ghostType, despawnTick = 0 }; // This runs after simulation. If an entity is created in the begin barrier and destroyed before this // runs there can be errors. To get around those we add the ghost system state component before everything // using the begin barrier, and set the value here commandBuffer.SetComponent(entities[ent], ghostState[ent]); } var pc = new PrioChunk { chunk = spawnChunks[chunk], ghostState = ghostState, priority = serializers.CalculateImportance(ghostType, spawnChunks[chunk]), // Age is always 1 for new chunks startIndex = 0, ghostType = ghostType }; serialSpawnChunks.Add(pc); } }
public unsafe void Execute() { var snapshotAck = ackFromEntity[connectionEntity]; var ackTick = snapshotAck.LastReceivedSnapshotByRemote; DataStreamWriter dataStream = new DataStreamWriter(2048, Allocator.Temp); dataStream.Clear(); dataStream.Write((byte)NetworkStreamProtocol.Snapshot); dataStream.Write(localTime); // dataStream.Write(snapshotAck.LastReceivedRemoteTime - (localTime - snapshotAck.LastReceiveTimestamp)); // TODO: LZ: // to be confirmed // we should send "t0 + (T1 - T0)", but not "t0 - (T1 - T0)" // // because: // RTT should equals to : (t1 - t0) - (T1 - T0) = t1 - [t0 + (T1 - T0)] // t0: A send time // snapshotAck.LastReceivedRemoteTime // T0: B receive time // snapshotAck.LastReceiveTimestamp // T1: B send time // localTime // t1: A receive time dataStream.Write(snapshotAck.LastReceivedRemoteTime + (localTime - snapshotAck.LastReceiveTimestamp)); dataStream.Write(currentTick); int entitySize = UnsafeUtility.SizeOf <Entity>(); var despawnLenWriter = dataStream.Write((uint)0); var updateLenWriter = dataStream.Write((uint)0); uint despawnLen = 0; // TODO: if not all despawns fit, sort them based on age and maybe time since last send // TODO: only resend despawn on nack // FIXME: the TargetPacketSize cannot be used since CleanupGhostJob relies on all ghosts being sent every frame for (var chunk = 0; chunk < despawnChunks.Length /*&& dataStream.Length < TargetPacketSize*/; ++chunk) { var entities = despawnChunks[chunk].GetNativeArray(entityType); var ghosts = despawnChunks[chunk].GetNativeArray(ghostSystemStateType); for (var ent = 0; ent < entities.Length /*&& dataStream.Length < TargetPacketSize*/; ++ent) { if (ackTick == 0 || SequenceHelpers.IsNewer(ghosts[ent].despawnTick, ackTick)) { dataStream.WritePackedUInt((uint)ghosts[ent].ghostId, compressionModel); ++despawnLen; } } } uint updateLen = 0; var serialChunks = new NativeList <PrioChunk>(ghostChunks.Length + serialSpawnChunks.Length, Allocator.Temp); serialChunks.AddRange(serialSpawnChunks); var existingChunks = new NativeHashMap <ArchetypeChunk, int>(ghostChunks.Length, Allocator.Temp); // TODO: LZ: // temp hack, fix me int maxCount = serialSpawnChunks.Length; for (int chunk = 0; chunk < ghostChunks.Length; ++chunk) { SerializationState chunkState; var addNew = !chunkSerializationData.TryGetValue(ghostChunks[chunk], out chunkState); // FIXME: should be using chunk sequence number instead of this hack if (!addNew && chunkState.arch != ghostChunks[chunk].Archetype) { UnsafeUtility.Free(chunkState.snapshotData, Allocator.Persistent); chunkSerializationData.Remove(ghostChunks[chunk]); addNew = true; } if (addNew) { chunkState.lastUpdate = currentTick - 1; chunkState.startIndex = 0; chunkState.ghostType = serializers.FindSerializer(ghostChunks[chunk].Archetype); chunkState.arch = ghostChunks[chunk].Archetype; chunkState.snapshotWriteIndex = 0; int serializerDataSize = serializers.GetSnapshotSize(chunkState.ghostType); chunkState.snapshotData = (byte *)UnsafeUtility.Malloc(UnsafeUtility.SizeOf <int>() * GhostSystemConstants.SnapshotHistorySize + GhostSystemConstants.SnapshotHistorySize * ghostChunks[chunk].Capacity * (UnsafeUtility.SizeOf <Entity>() + serializerDataSize), 16, Allocator.Persistent); // Just clear snapshot index UnsafeUtility.MemClear(chunkState.snapshotData, UnsafeUtility.SizeOf <int>() * GhostSystemConstants.SnapshotHistorySize); chunkSerializationData.TryAdd(ghostChunks[chunk], chunkState); } existingChunks.TryAdd(ghostChunks[chunk], 1); // FIXME: only if modified or force sync var ghostType = chunkState.ghostType; var pc = new PrioChunk { chunk = ghostChunks[chunk], ghostState = null, priority = serializers.CalculateImportance(ghostType, ghostChunks[chunk]) * (int)(currentTick - chunkState.lastUpdate), startIndex = chunkState.startIndex, ghostType = ghostType }; serialChunks.Add(pc); if (ghostChunks[chunk].Count > maxCount) { maxCount = ghostChunks[chunk].Count; } } var oldChunks = chunkSerializationData.GetKeyArray(Allocator.Temp); for (int i = 0; i < oldChunks.Length; ++i) { int val; if (!existingChunks.TryGetValue(oldChunks[i], out val)) { SerializationState chunkState; chunkSerializationData.TryGetValue(oldChunks[i], out chunkState); UnsafeUtility.Free(chunkState.snapshotData, Allocator.Persistent); chunkSerializationData.Remove(oldChunks[i]); } } NativeArray <PrioChunk> serialChunkArray = serialChunks; serialChunkArray.Sort(); var availableBaselines = new NativeList <SnapshotBaseline>(GhostSystemConstants.SnapshotHistorySize, Allocator.Temp); var baselinePerEntity = new NativeArray <int>(maxCount * 3, Allocator.Temp); for (int pc = 0; pc < serialChunks.Length && dataStream.Length < TargetPacketSize; ++pc) { var chunk = serialChunks[pc].chunk; var ghostType = serialChunks[pc].ghostType; Entity * currentSnapshotEntity = null; byte * currentSnapshotData = null; SerializationState chunkState; int dataSize = 0; availableBaselines.Clear(); if (chunkSerializationData.TryGetValue(chunk, out chunkState)) { dataSize = serializers.GetSnapshotSize(chunkState.ghostType); uint *snapshotIndex = (uint *)chunkState.snapshotData; snapshotIndex[chunkState.snapshotWriteIndex] = currentTick; int baseline = (GhostSystemConstants.SnapshotHistorySize + chunkState.snapshotWriteIndex - 1) % GhostSystemConstants.SnapshotHistorySize; while (baseline != chunkState.snapshotWriteIndex) { if (snapshotAck.IsReceivedByRemote(snapshotIndex[baseline])) { byte *dataBase = chunkState.snapshotData + UnsafeUtility.SizeOf <int>() * GhostSystemConstants.SnapshotHistorySize + baseline * (dataSize + entitySize) * chunk.Capacity; availableBaselines.Add(new SnapshotBaseline { tick = snapshotIndex[baseline], snapshot = dataBase + entitySize * chunk.Capacity, entity = (Entity *)(dataBase) }); } baseline = (GhostSystemConstants.SnapshotHistorySize + baseline - 1) % GhostSystemConstants.SnapshotHistorySize; } // Find the acked snapshot to delta against, setup pointer to current and previous entity* and data* // Remember to bump writeIndex when done currentSnapshotData = chunkState.snapshotData + UnsafeUtility.SizeOf <int>() * GhostSystemConstants.SnapshotHistorySize; currentSnapshotData += chunkState.snapshotWriteIndex * (dataSize + entitySize) * chunk.Capacity; currentSnapshotEntity = (Entity *)currentSnapshotData; currentSnapshotData += entitySize * chunk.Capacity; } var ghosts = serialChunks[pc].ghostState; if (ghosts == null) { ghosts = (GhostSystemStateComponent *)chunk.GetNativeArray(ghostSystemStateType).GetUnsafeReadOnlyPtr(); } var ghostEntities = chunk.GetNativeArray(entityType); int ent; if (serialChunks[pc].startIndex < chunk.Count) { dataStream.WritePackedUInt((uint)ghostType, compressionModel); dataStream.WritePackedUInt((uint)(chunk.Count - serialChunks[pc].startIndex), compressionModel); } // First figure out the baselines to use per entity so they can be sent as baseline + maxCount instead of one per entity int targetBaselines = serializers.WantsPredictionDelta(ghostType) ? 3 : 1; for (ent = serialChunks[pc].startIndex; ent < chunk.Count; ++ent) { int foundBaselines = 0; for (int baseline = 0; baseline < availableBaselines.Length; ++baseline) { if (availableBaselines[baseline].entity[ent] == ghostEntities[ent]) { baselinePerEntity[ent * 3 + foundBaselines] = baseline; ++foundBaselines; if (foundBaselines == targetBaselines) { break; } } // Only way an entity can be missing from a snapshot but be available in an older is if last snapshot was partial else if (availableBaselines[baseline].entity[ent] != Entity.Null) { break; } } if (foundBaselines == 2) { foundBaselines = 1; } while (foundBaselines < 3) { baselinePerEntity[ent * 3 + foundBaselines] = -1; ++foundBaselines; } } ent = serializers.Serialize(ghostType, chunk, serialChunks[pc].startIndex, currentTick, currentSnapshotEntity, currentSnapshotData, ghosts, ghostEntities, baselinePerEntity, availableBaselines, dataStream, compressionModel); updateLen += (uint)(ent - serialChunks[pc].startIndex); // Spawn chunks are temporary and should not be added to the state data cache if (serialChunks[pc].ghostState == null) { // Only append chunks which contain data if (ent > serialChunks[pc].startIndex) { if (serialChunks[pc].startIndex > 0) { UnsafeUtility.MemClear(currentSnapshotEntity, entitySize * serialChunks[pc].startIndex); } if (ent < chunk.Capacity) { UnsafeUtility.MemClear(currentSnapshotEntity + ent, entitySize * (chunk.Capacity - ent)); } chunkState.snapshotWriteIndex = (chunkState.snapshotWriteIndex + 1) % GhostSystemConstants.SnapshotHistorySize; } if (ent >= chunk.Count) { chunkState.lastUpdate = currentTick; chunkState.startIndex = 0; } else { // TODO: should this always be run or should partial chunks only be allowed for the highest priority chunk? //if (pc == 0) chunkState.startIndex = ent; } chunkSerializationData.Remove(chunk); chunkSerializationData.TryAdd(chunk, chunkState); } } dataStream.Flush(); despawnLenWriter.Update(despawnLen); updateLenWriter.Update(updateLen); driver.Send(unreliablePipeline, connectionFromEntity[connectionEntity].Value, dataStream); }