265 lines
8.4 KiB
C#
265 lines
8.4 KiB
C#
using Prospect.Unreal.Net.Packets.Header.Sequence;
|
|
using Prospect.Unreal.Serialization;
|
|
using Serilog;
|
|
|
|
namespace Prospect.Unreal.Net.Packets.Header;
|
|
|
|
public delegate void HandlePacketNotification(PacketNotifyUpdateContext context, SequenceNumber ackedSequence, bool delivered);
|
|
|
|
public readonly ref struct PacketNotifyUpdateContext
|
|
{
|
|
public readonly HandlePacketNotification Func;
|
|
public readonly List<FChannelCloseInfo> ChannelToClose;
|
|
|
|
public PacketNotifyUpdateContext(HandlePacketNotification func, List<FChannelCloseInfo> channelToClose)
|
|
{
|
|
Func = func;
|
|
ChannelToClose = channelToClose;
|
|
}
|
|
}
|
|
|
|
public class FNetPacketNotify
|
|
{
|
|
private static readonly ILogger Logger = Log.ForContext<FNetPacketNotify>();
|
|
|
|
public const int SequenceNumberBits = 14;
|
|
public const int MaxSequenceHistoryLength = 256;
|
|
|
|
private readonly Queue<FSentAckData> _ackRecord = new Queue<FSentAckData>();
|
|
private uint _writtenHistoryWordCount;
|
|
private SequenceNumber _writtenInAckSeq;
|
|
|
|
private readonly SequenceHistory _inSeqHistory = new SequenceHistory();
|
|
private SequenceNumber _inSeq;
|
|
private SequenceNumber _inAckSeq;
|
|
private SequenceNumber _inAckSeqAck;
|
|
|
|
private SequenceNumber _outSeq;
|
|
private SequenceNumber _outAckSeq;
|
|
|
|
public void Init(SequenceNumber initialInSeq, SequenceNumber initialOutSeq)
|
|
{
|
|
_inSeqHistory.Reset();
|
|
_inSeq = initialInSeq;
|
|
_inAckSeq = initialInSeq;
|
|
_inAckSeqAck = initialInSeq;
|
|
_outSeq = initialOutSeq;
|
|
_outAckSeq = new SequenceNumber((ushort)(initialOutSeq.Value - 1));
|
|
}
|
|
|
|
public SequenceNumber GetInSeq()
|
|
{
|
|
return _inSeq;
|
|
}
|
|
|
|
public SequenceNumber GetInAckSeq()
|
|
{
|
|
return _inAckSeq;
|
|
}
|
|
|
|
public SequenceNumber GetOutSeq()
|
|
{
|
|
return _outSeq;
|
|
}
|
|
|
|
public SequenceNumber GetOutAckSeq()
|
|
{
|
|
return _outAckSeq;
|
|
}
|
|
|
|
/// <summary>
|
|
/// These methods must always write and read the exact same number of bits, that is the reason for not using WriteInt/WrittedWrappedInt
|
|
/// </summary>
|
|
public bool WriteHeader(FBitWriter writer, bool bRefresh)
|
|
{
|
|
// we always write at least 1 word
|
|
var currentHistoryWorkCount = Math.Clamp((GetCurrentSequenceHistoryLength() + SequenceHistory.BitsPerWord - 1) / SequenceHistory.BitsPerWord, 1, SequenceHistory.WordCount);
|
|
|
|
// We can only do a refresh if we do not need more space for the history
|
|
if (bRefresh && (currentHistoryWorkCount > _writtenHistoryWordCount))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// How many words of ack data should we write? If this is a refresh we must write the same size as the original header
|
|
_writtenHistoryWordCount = bRefresh ? _writtenHistoryWordCount : currentHistoryWorkCount;
|
|
|
|
// This is the last InAck we have acknowledged at this time
|
|
_writtenInAckSeq = _inAckSeq;
|
|
|
|
var seq = _outSeq;
|
|
var ackedSeq = _inAckSeq;
|
|
|
|
// Pack data into a uint
|
|
var packedHeader = FPackedHeader.Pack(seq, ackedSeq, _writtenHistoryWordCount - 1);
|
|
|
|
// Write packed header
|
|
writer.WriteUInt32(packedHeader);
|
|
|
|
// Write ack history
|
|
_inSeqHistory.Write(writer, _writtenHistoryWordCount);
|
|
|
|
return true;
|
|
}
|
|
|
|
private uint GetCurrentSequenceHistoryLength()
|
|
{
|
|
if (_inAckSeq.GreaterEq(_inAckSeqAck))
|
|
{
|
|
return (uint) SequenceNumber.Diff(_inAckSeq, _inAckSeqAck);
|
|
}
|
|
|
|
return SequenceHistory.Size;
|
|
}
|
|
|
|
public bool ReadHeader(ref FNotificationHeader data, FBitReader reader)
|
|
{
|
|
var packedHeader = reader.ReadUInt32();
|
|
|
|
data.Seq = FPackedHeader.GetSeq(packedHeader);
|
|
data.AckedSeq = FPackedHeader.GetAckedSeq(packedHeader);
|
|
data.HistoryWordCount = FPackedHeader.GetHistoryWordCount(packedHeader) + 1;
|
|
data.History = new SequenceHistory();
|
|
data.History.Read(reader, data.HistoryWordCount);
|
|
|
|
return !reader.IsError();
|
|
}
|
|
|
|
public int GetSequenceDelta(FNotificationHeader notificationData)
|
|
{
|
|
if (!notificationData.Seq.Greater(_inSeq))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!notificationData.AckedSeq.GreaterEq(_outAckSeq))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (!_outSeq.Greater(notificationData.AckedSeq))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return SequenceNumber.Diff(notificationData.Seq, _inSeq);
|
|
}
|
|
|
|
public int Update(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
|
|
{
|
|
var inSeqDelta = GetSequenceDelta(notificationData);
|
|
if (inSeqDelta > 0)
|
|
{
|
|
Logger.Verbose("Update - Seq {Seq}, InSeq {InSeq}", notificationData.Seq.Value, _inSeq.Value);
|
|
|
|
ProcessReceivedAcks(notificationData, context);
|
|
|
|
_inSeq = notificationData.Seq;
|
|
|
|
return inSeqDelta;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
private void ProcessReceivedAcks(FNotificationHeader notificationData, PacketNotifyUpdateContext context)
|
|
{
|
|
if (notificationData.AckedSeq.Greater(_outAckSeq))
|
|
{
|
|
Logger.Verbose("ProcessReceivedAcks - AckedSeq {AckedSeq}, OutAckSeq {OutAckSeq}", notificationData.AckedSeq.Value, _outAckSeq.Value);
|
|
|
|
var ackCount = SequenceNumber.Diff(notificationData.AckedSeq, _outAckSeq);
|
|
|
|
// Update InAckSeqAck used to track the needed number of bits to transmit our ack history
|
|
_inAckSeqAck = UpdateInAckSeqAck(ackCount, notificationData.AckedSeq);
|
|
|
|
// ExpectedAck = OutAckSeq + 1
|
|
var currentAck = new SequenceNumber(_outAckSeq.Value).IncrementAndGet();
|
|
|
|
if (ackCount > SequenceHistory.Size)
|
|
{
|
|
Logger.Warning("ProcessReceivedAcks - Missed Acks");
|
|
}
|
|
|
|
// Everything not found in the history buffer is treated as lost
|
|
while (ackCount > SequenceHistory.Size)
|
|
{
|
|
--ackCount;
|
|
context.Func(context, currentAck, false);
|
|
currentAck = currentAck.IncrementAndGet();
|
|
}
|
|
|
|
// For sequence numbers contained in the history we lookup the delivery status from the history
|
|
while (ackCount > 0)
|
|
{
|
|
--ackCount;
|
|
context.Func(context, currentAck, notificationData.History.IsDelivered(ackCount));
|
|
currentAck = currentAck.IncrementAndGet();
|
|
}
|
|
|
|
_outAckSeq = notificationData.AckedSeq;
|
|
}
|
|
}
|
|
|
|
private SequenceNumber UpdateInAckSeqAck(int ackCount, SequenceNumber ackedSeq)
|
|
{
|
|
if (ackCount <= _ackRecord.Count)
|
|
{
|
|
if (ackCount > 1)
|
|
{
|
|
for (var i = 0; i < ackCount - 1; i++)
|
|
{
|
|
_ackRecord.Dequeue();
|
|
}
|
|
}
|
|
|
|
var ackData = _ackRecord.Dequeue();
|
|
if (ackData.OutSeq.Equals(ackedSeq))
|
|
{
|
|
return ackData.InAckSeq;
|
|
}
|
|
}
|
|
|
|
return new SequenceNumber((ushort)(ackedSeq.Value - MaxSequenceHistoryLength));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mark Seq as received and update current InSeq, missing sequence numbers will be marked as lost
|
|
/// </summary>
|
|
public void AckSeq(SequenceNumber seq)
|
|
{
|
|
AckSeq(seq, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Explicitly mark Seq as not received and update current InSeq, additional missing sequence numbers will be marked as lost
|
|
/// </summary>
|
|
public void NakSeq(SequenceNumber seq)
|
|
{
|
|
AckSeq(seq, false);
|
|
}
|
|
|
|
private void AckSeq(SequenceNumber ackedSeq, bool isAck)
|
|
{
|
|
while (ackedSeq.Greater(_inAckSeq))
|
|
{
|
|
_inAckSeq = _inAckSeq.IncrementAndGet();
|
|
|
|
var bReportAcked = _inAckSeq.Equals(ackedSeq) ? isAck : false;
|
|
|
|
Logger.Verbose("AckSeq - AckedSeq: {Seq}, IsAck {IsAck}", _inAckSeq.Value, bReportAcked);
|
|
|
|
_inSeqHistory.AddDeliveryStatus(bReportAcked);
|
|
}
|
|
}
|
|
|
|
public SequenceNumber CommitAndIncrementOutSeq()
|
|
{
|
|
// Add entry to the ack-record so that we can update the InAckSeqAck when we received the ack for this OutSeq.
|
|
_ackRecord.Enqueue(new FSentAckData(_outSeq, _writtenInAckSeq));
|
|
_writtenHistoryWordCount = 0;
|
|
_outSeq = _outSeq.IncrementAndGet();
|
|
|
|
return _outSeq;
|
|
}
|
|
} |