using System.Diagnostics;
namespace AcDream.Core.Net.Transport;
///
/// The transport's monotonic time authority: an injectable timestamp source
/// (production default ) driving retail's
/// 0.5-second interval counter. is the value retail
/// writes into ProtoHeader::interval_ — our
/// — on rebuilt resend headers.
///
///
/// Retail oracle: ClientFlowQueue::IncrementLocalInterval @ 0x00547F10
/// (named-retail pseudo-C :338389) — the tail is
/// CurLocalInterval_.intervalID_ += elapsedIntervals, advanced by the
/// caller once per elapsed 0.5-s slice. Only the interval counter is in
/// N1 scope: the same function's every-6-intervals TimeSync/EchoRequest
/// (~3 s) and every-0xDC-intervals CICMD keepalive are campaign §5
/// deferrals (TS-58) — standalone-unsafe against ACE's watermark hole.
///
///
///
/// One clock owns every transport gate (campaign §5 AP-126 — retail's
/// cur/local clock split is immaterial to the gates we port). Single-threaded
/// like the rest of the transport: runs only from the
/// session sweep.
///
///
internal sealed class TransportClock
{
private readonly Func _timestampSource;
private readonly long _ticksPerInterval;
private long _intervalBaseTimestamp;
/// Timestamp ticks per second of the injected source.
public long Frequency { get; }
///
/// Retail's 0.5-s interval counter (CurLocalInterval_.intervalID_).
/// Starts at 1; wraps with natural ushort arithmetic.
///
public ushort IntervalId { get; private set; }
public TransportClock(
Func? timestampSource = null,
long? frequency = null)
{
_timestampSource = timestampSource ?? Stopwatch.GetTimestamp;
Frequency = frequency ?? Stopwatch.Frequency;
if (Frequency < 2)
{
throw new ArgumentOutOfRangeException(
nameof(frequency),
"the interval clock needs at least 2 ticks per second");
}
_ticksPerInterval = Frequency / 2;
_intervalBaseTimestamp = _timestampSource();
IntervalId = 1;
}
/// Current raw timestamp from the injected source.
public long GetTimestamp() => _timestampSource();
///
/// Advance by however many whole 0.5-s
/// intervals have elapsed since the last update. Called once per sweep.
///
public void Update()
{
long elapsed = _timestampSource() - _intervalBaseTimestamp;
if (elapsed < _ticksPerInterval)
return;
long steps = elapsed / _ticksPerInterval;
IntervalId = unchecked((ushort)(IntervalId + steps));
_intervalBaseTimestamp += steps * _ticksPerInterval;
}
}