Editor/Server/ProcessingEstimate.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace HumanoidMocapHost;

/// <summary>Learns how long captures take, as processing seconds per second of requested video,
/// from completed jobs. Starts from a measured default and moves towards recent jobs.</summary>
internal sealed class ProcessingEstimate(double secondsPerVideoSecond = ProcessingEstimate.DefaultSecondsPerVideoSecond)
{
    // 272 s for a 5 s clip on an RX 9070 with four lanes (2026-10-27); the host's own jobs replace it.
    internal const double DefaultSecondsPerVideoSecond = 60;
    // Jobs without an end time use this video length until the processor reports progress.
    internal const double UnknownVideoSeconds = 10;
    const double Weight = 0.3;
    double rate = secondsPerVideoSecond > 0 ? secondsPerVideoSecond
        : throw new ArgumentOutOfRangeException(nameof(secondsPerVideoSecond));

    internal double SecondsPerVideoSecond => rate;

    /// <summary>Expected processing time for a job covering <paramref name="videoSeconds"/>.</summary>
    internal double Expected(double? videoSeconds) => rate * (videoSeconds ?? UnknownVideoSeconds);

    /// <summary>Time left for a running job: extrapolated from reported progress once there is some,
    /// otherwise the expected time minus what has elapsed (never negative).</summary>
    internal double Remaining(double? videoSeconds, double elapsedSeconds, double? progress)
        => progress is > 0.02 and < 1 ? elapsedSeconds * (1 - progress.Value) / progress.Value
            : Math.Max(0, Expected(videoSeconds) - elapsedSeconds);

    /// <summary>Learns from a finished job. Jobs without a known video length are skipped.</summary>
    internal void Completed(double? videoSeconds, double elapsedSeconds)
    {
        if (videoSeconds is not > 0 || !double.IsFinite(elapsedSeconds) || elapsedSeconds <= 0) return;
        rate += Weight * (elapsedSeconds / videoSeconds.Value - rate);
    }
}