namespace TravelEar.Core;
///
/// The Helper's re-prime rule (ADR-0005): with the Sink fed from the encoder thread the stream
/// stops whenever the game stops encoding (mute, menu), so the playback ring drains. Playing the
/// next burst from an empty ring would spend every arrival jitter as an underrun click, so once
/// the ring has starved the reader emits silence until the backlog is back at the jitter target,
/// then resumes. At stream start the caller primes the ring instead, so this only acts after a
/// starve. Pure policy, no audio; the reader applies it.
///
public sealed class StarveGuard
{
private readonly int _targetBacklogSamples;
/// Number of times the ring ran dry and playback was held for a re-prime.
public long Starves { get; private set; }
/// True while playback is held, waiting for the backlog to reach the target.
public bool Holding { get; private set; }
public StarveGuard(int targetBacklogSamples)
{
_targetBacklogSamples = Math.Max(0, targetBacklogSamples);
}
// [impl->REQ-SINK-FORMAT]
///
/// Decides one read of samples against a ring holding
/// : true = output silence and leave the ring alone, false = read.
/// A read that would run the ring dry starts a hold; the hold ends when the backlog reaches
/// the target (or the requested size, whichever is larger).
///
public bool ShouldHold(int backlog, int requested)
{
var resumeAt = Math.Max(_targetBacklogSamples, requested);
if (Holding)
{
if (backlog < resumeAt) return true;
Holding = false;
return false;
}
if (backlog < requested)
{
Holding = true;
Starves++;
return true;
}
return false;
}
}