Пример #1
0
        /// <summary>
        /// Store the packet for possible later resends, and fill in the header we'll use to send it (populate with
        /// sequence ID, last acknowledged ID from remote with ackmask.
        /// </summary>
        /// <param name="context">Pipeline context, the reliability shared state is used here.</param>
        /// <param name="inboundBuffer">Buffer with packet data.</param>
        /// <param name="header">Packet header which will be populated.</param>
        /// <returns>Sequence ID assigned to this packet.</returns>
        public static unsafe int Write(NetworkPipelineContext context, InboundBufferVec inboundBuffer, ref PacketHeader header)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();

            var sequence = (ushort)reliable->SentPackets.Sequence;

            if (!TryAquire(context.internalProcessBuffer, sequence))
            {
                reliable->errorCode = ErrorCodes.OutgoingQueueIsFull;
                return((int)ErrorCodes.OutgoingQueueIsFull);
            }
            reliable->stats.PacketsSent++;

            header.SequenceId      = sequence;
            header.AckedSequenceId = (ushort)reliable->ReceivedPackets.Sequence;
            header.AckMask         = reliable->ReceivedPackets.AckMask;

            reliable->ReceivedPackets.Acked = reliable->ReceivedPackets.Sequence;

            // Attach our processing time of the packet we're acknowledging (time between receiving it and sending this ack)
            header.ProcessingTime =
                CalculateProcessingTime(context.internalSharedProcessBuffer, header.AckedSequenceId, context.timestamp);

            reliable->SentPackets.Sequence = (ushort)(reliable->SentPackets.Sequence + 1);
            SetHeaderAndPacket(context.internalProcessBuffer, sequence, header, inboundBuffer, context.timestamp);

            StoreTimestamp(context.internalSharedProcessBuffer, sequence, context.timestamp);

            return(sequence);
        }
Пример #2
0
        /// <summary>
        /// Resume or play back a packet we had received earlier out of order. When an out of order packet is received
        /// it is stored since we need to first return the packet with the next sequence ID. When that packet finally
        /// arrives it is returned but a pipeline resume is requested since we already have the next packet stored
        /// and it can be processed immediately after.
        /// </summary>
        /// <param name="context">Pipeline context, we'll use both the shared reliability context and receive context.</param>
        /// <param name="startSequence">The first packet which we need to retrieve now, there could be more after that.</param>
        /// <param name="needsResume">Indicates if we need the pipeline to resume again.</param>
        /// <returns></returns>
        public static unsafe NativeSlice <byte> ResumeReceive(NetworkPipelineContext context, int startSequence, ref bool needsResume)
        {
            if (startSequence == NullEntry)
            {
                return(default(NativeSlice <byte>));
            }

            SharedContext *shared   = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();
            Context *      reliable = (Context *)context.internalProcessBuffer.GetUnsafePtr();

            reliable->Resume = NullEntry;

            PacketInformation *info  = GetPacketInformation(context.internalProcessBuffer, startSequence);
            var latestReceivedPacket = shared->ReceivedPackets.Sequence;

            if (info->SequenceId == startSequence)
            {
                var offset = reliable->DataPtrOffset + ((startSequence % reliable->Capacity) * reliable->DataStride);
                NativeSlice <byte> slice = new NativeSlice <byte>(context.internalProcessBuffer, offset, info->Size);
                reliable->Delivered = startSequence;

                if ((ushort)(startSequence + 1) <= latestReceivedPacket)
                {
                    reliable->Resume = (ushort)(startSequence + 1);
                    needsResume      = true;
                }
                return(slice);
            }
            return(default(NativeSlice <byte>));
        }
Пример #3
0
        public static unsafe SharedContext InitializeContext(NativeSlice <byte> sharedBuffer, NativeSlice <byte> sendBuffer, NativeSlice <byte> recvBuffer, Parameters param)
        {
            InitializeProcessContext(sendBuffer, param);
            InitializeProcessContext(recvBuffer, param);

            SharedContext *notifier = (SharedContext *)sharedBuffer.GetUnsafePtr();

            *notifier = new SharedContext
            {
                WindowSize  = param.WindowSize,
                SentPackets = new SequenceBufferContext {
                    Acked = NullEntry
                },
                MinimumResendTime = DefaultMinimumResendTime,
                ReceivedPackets   = new SequenceBufferContext {
                    Sequence = NullEntry
                },
                RttInfo = new RTTInfo {
                    SmoothedVariance = 5, SmoothedRtt = 50, ResendTimeout = 50, LastRtt = 50
                },
                TimerDataOffset       = UnsafeUtility.SizeOf <SharedContext>(),
                TimerDataStride       = UnsafeUtility.SizeOf <PacketTimers>(),
                RemoteTimerDataOffset = UnsafeUtility.SizeOf <SharedContext>() + UnsafeUtility.SizeOf <PacketTimers>() * param.WindowSize,
                RemoteTimerDataStride = UnsafeUtility.SizeOf <PacketTimers>()
            };
            return(*notifier);
        }
Пример #4
0
        public static unsafe SharedContext InitializeContext(byte *sharedBuffer, int sharedBufferLength,
                                                             byte *sendBuffer, int sendBufferLength, byte *recvBuffer, int recvBufferLength, Parameters param)
        {
            InitializeProcessContext(sendBuffer, sendBufferLength, param);
            InitializeProcessContext(recvBuffer, recvBufferLength, param);

            SharedContext *notifier = (SharedContext *)sharedBuffer;

            *notifier = new SharedContext
            {
                WindowSize  = param.WindowSize,
                SentPackets = new SequenceBufferContext {
                    Acked = NullEntry
                },
                MinimumResendTime = DefaultMinimumResendTime,
                ReceivedPackets   = new SequenceBufferContext {
                    Sequence = NullEntry
                },
                RttInfo = new RTTInfo {
                    SmoothedVariance = 5, SmoothedRtt = 50, ResendTimeout = 50, LastRtt = 50
                },
                TimerDataOffset       = AlignedSizeOf <SharedContext>(),
                TimerDataStride       = AlignedSizeOf <PacketTimers>(),
                RemoteTimerDataOffset = AlignedSizeOf <SharedContext>() + AlignedSizeOf <PacketTimers>() * param.WindowSize,
                RemoteTimerDataStride = AlignedSizeOf <PacketTimers>()
            };
            return(*notifier);
        }
Пример #5
0
        /// <summary>
        /// Write an ack packet, only the packet header is used and this doesn't advance the sequence ID.
        /// The packet is not stored away for resend routine.
        /// </summary>
        /// <param name="context">Pipeline context, the reliability shared state is used here.</param>
        /// <param name="header">Packet header which will be populated.</param>
        /// <returns></returns>
        public static unsafe void WriteAckPacket(NetworkPipelineContext context, ref PacketHeader header)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();

            header.Type            = (ushort)PacketType.Ack;
            header.AckedSequenceId = (ushort)reliable->ReceivedPackets.Sequence;
            header.AckMask         = reliable->ReceivedPackets.AckMask;
            header.ProcessingTime  =
                CalculateProcessingTime(context.internalSharedProcessBuffer, header.AckedSequenceId, context.timestamp);
            reliable->ReceivedPackets.Acked = reliable->ReceivedPackets.Sequence;
        }
Пример #6
0
        /// <summary>
        /// Resend a packet which we have not received an acknowledgement for in time. Pipeline resume
        /// will be enabled if there are more packets which we need to resend. The send reliability context
        /// will then also be updated to track the next packet we need to resume.
        /// </summary>
        /// <param name="context">Pipeline context, we'll use both the shared reliability context and send context.</param>
        /// <param name="header">Packet header for the packet payload we're resending.</param>
        /// <param name="needsResume">Indicates if a pipeline resume is needed again.</param>
        /// <returns>Buffer slice to packet payload.</returns>
        /// <exception cref="ApplicationException"></exception>
        public static unsafe NativeSlice <byte> ResumeSend(NetworkPipelineContext context, out PacketHeader header, ref bool needsResume)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();
            Context *      ctx      = (Context *)context.internalProcessBuffer.GetUnsafePtr();

#if ENABLE_UNITY_COLLECTIONS_CHECKS
            if (ctx->Resume == NullEntry)
            {
                throw new ApplicationException("This function should not be called unless there is data in resume");
            }
#endif

            var sequence = (ushort)ctx->Resume;

            PacketInformation *information;
            information = GetPacketInformation(context.internalProcessBuffer, sequence);
            // Reset the resend timer
            information->SendTime = context.timestamp;

            Packet *packet = GetPacket(context.internalProcessBuffer, sequence);
            header = packet->Header;

            // Update acked/ackmask to latest values
            header.AckedSequenceId = (ushort)reliable->ReceivedPackets.Sequence;
            header.AckMask         = reliable->ReceivedPackets.AckMask;

            var offset = (ctx->DataPtrOffset + ((sequence % ctx->Capacity) * ctx->DataStride)) + UnsafeUtility.SizeOf <PacketHeader>();

            NativeSlice <byte> slice = new NativeSlice <byte>(context.internalProcessBuffer, offset, information->Size);
            reliable->stats.PacketsResent++;

            needsResume = false;
            ctx->Resume = -1;

            // Check if another packet needs to be resent right after this one
            for (int i = sequence + 1; i < reliable->ReceivedPackets.Sequence + 1; i++)
            {
                var timeToResend = CurrentResendTime(context.internalSharedProcessBuffer);
                information = GetPacketInformation(context.internalProcessBuffer, i);
                if (information->SequenceId >= 0 && information->SendTime + timeToResend > context.timestamp)
                {
                    needsResume = true;
                    ctx->Resume = i;
                }
            }
            return(slice);
        }
Пример #7
0
        public static unsafe void ReadAckPacket(NetworkPipelineContext context, PacketHeader header)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();

            // Store receive timestamp for our acked sequence ID with remote processing time
            StoreReceiveTimestamp(context.internalSharedProcessBuffer, header.AckedSequenceId, context.timestamp, header.ProcessingTime);

            // Check the distance of the acked seqId in the header, if it's too far away from last acked packet we
            // can't process it and add it to the ack mask
            if (!SequenceHelpers.GreaterThan16(header.AckedSequenceId, (ushort)reliable->SentPackets.Acked))
            {
                // No new acks;
                return;
            }

            reliable->SentPackets.Acked   = header.AckedSequenceId;
            reliable->SentPackets.AckMask = header.AckMask;
        }
Пример #8
0
        /// <summary>
        /// Acknowledge the reception of packets which have been sent. The reliability
        /// shared context/state is updated when packets are received from the other end
        /// of the connection. The other side will update it's ackmask with which packets
        /// have been received (starting from last received sequence ID) each time it sends
        /// a packet back. This checks the resend timers on each non-acknowledged packet
        /// and notifies if it's time to resend yet.
        /// </summary>
        /// <param name="context">Pipeline context, contains the buffer slices this pipeline connection owns.</param>
        /// <returns></returns>
        public static unsafe bool ReleaseOrResumePackets(NetworkPipelineContext context)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();
            Context *      ctx      = (Context *)context.internalProcessBuffer.GetUnsafePtr();

            // Last sequence ID and ackmask we received from the remote peer, these are confirmed delivered packets
            var lastReceivedAckMask            = reliable->SentPackets.AckMask;
            var lastOwnSequenceIdAckedByRemote = (ushort)reliable->SentPackets.Acked;

            // To deal with wrapping, chop off the upper half of the sequence ID and multiply by window size, it
            // will then never wrap but will map to the correct index in the packet storage, wrapping happens when
            // sending low sequence IDs (since it checks sequence IDs backwards in time).
            var sequence = (ushort)(reliable->WindowSize * ((1 - lastOwnSequenceIdAckedByRemote) >> 15));

            // Check each slot in the window, starting from the sequence ID calculated above (this isn't the
            // latest sequence ID though as it was adjusted to avoid wrapping)
            for (int i = 0; i < reliable->WindowSize; i++)
            {
                var info = GetPacketInformation(context.internalProcessBuffer, sequence);
                if (info->SequenceId >= 0)
                {
                    // Check the bit for this sequence ID against the ackmask. Bit 0 in the ackmask is the latest
                    // ackedSeqId, bit 1 latest ackedSeqId - 1 (one older) and so on. If bit X is 1 then ackedSeqId-X is acknowledged
                    var ackBits = 1 << (lastOwnSequenceIdAckedByRemote - info->SequenceId);

                    // Release if this seqId has been flipped on in the ackmask (so it's acknowledged)
                    // Ignore if sequence ID is out of window range of the last acknowledged id
                    if (SequenceHelpers.AbsDistance((ushort)lastOwnSequenceIdAckedByRemote, (ushort)info->SequenceId) < reliable->WindowSize && (ackBits & lastReceivedAckMask) != 0)
                    {
                        Release(context.internalProcessBuffer, info->SequenceId);
                        info->SendTime = -1;
                        sequence       = (ushort)(sequence - 1);
                        continue;
                    }
                    var timeToResend = CurrentResendTime(context.internalSharedProcessBuffer);
                    if (context.timestamp > info->SendTime + timeToResend)
                    {
                        ctx->Resume = info->SequenceId;
                    }
                }
                sequence = (ushort)(sequence - 1);
            }
            return(ctx->Resume != NullEntry);
        }
Пример #9
0
        /// <summary>
        /// Read header data and update reliability tracking information in the shared context.
        /// - If the packets sequence ID is lower than the last received ID+1, then it's stale
        /// - If the packets sequence ID is higher, then we'll process it and update tracking info in the shared context
        /// </summary>
        /// <param name="context">Pipeline context, the reliability shared state is used here.</param>
        /// <param name="header">Packet header of a new received packet.</param>
        /// <returns>Sequence ID of the received packet.</returns>
        public static unsafe int Read(NetworkPipelineContext context, PacketHeader header)
        {
            SharedContext *reliable = (SharedContext *)context.internalSharedProcessBuffer.GetUnsafePtr();

            reliable->stats.PacketsReceived++;
            if (SequenceHelpers.StalePacket(
                    header.SequenceId,
                    (ushort)(reliable->ReceivedPackets.Sequence + 1),
                    (ushort)reliable->WindowSize))
            {
                reliable->stats.PacketsStale++;
                return((int)ErrorCodes.Stale_Packet);
            }

            var window = reliable->WindowSize - 1;

            if (SequenceHelpers.GreaterThan16((ushort)(header.SequenceId + 1), (ushort)reliable->ReceivedPackets.Sequence))
            {
                int distance = SequenceHelpers.AbsDistance(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence);

                for (var i = 0; i < Math.Min(distance, window); ++i)
                {
                    if ((reliable->ReceivedPackets.AckMask & 1 << (window - i)) == 0)
                    {
                        reliable->stats.PacketsDropped++;
                    }
                }

                if (distance > window)
                {
                    reliable->stats.PacketsDropped   += distance - window;
                    reliable->ReceivedPackets.AckMask = 1;
                }
                else
                {
                    reliable->ReceivedPackets.AckMask <<= distance;
                    reliable->ReceivedPackets.AckMask  |= 1;
                }

                reliable->ReceivedPackets.Sequence = header.SequenceId;
            }
            else if (SequenceHelpers.LessThan16(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence))
            {
                int distance = SequenceHelpers.AbsDistance(header.SequenceId, (ushort)reliable->ReceivedPackets.Sequence);
                // If this is a resent packet the distance will seem very big and needs to be calculated again with adjustment for wrapping
                if (distance >= ushort.MaxValue - reliable->WindowSize)
                {
                    distance = reliable->ReceivedPackets.Sequence - header.SequenceId;
                }

                var ackBit = 1 << distance;
                if ((ackBit & reliable->ReceivedPackets.AckMask) != 0)
                {
                    reliable->stats.PacketsDuplicated++;
                    return((int)ErrorCodes.Duplicated_Packet);
                }

                reliable->stats.PacketsOutOfOrder++;
                reliable->ReceivedPackets.AckMask |= (uint)ackBit;
            }

            // Store receive timestamp for remote sequence ID we just received
            StoreRemoteReceiveTimestamp(context.internalSharedProcessBuffer, header.SequenceId, context.timestamp);

            ReadAckPacket(context, header);

            return(header.SequenceId);
        }