Editor/HotCodeEditor/Navigation/LatestOnlyWorker.cs
using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;
/// <summary>
/// Runs one background job at a time and only cares about the newest request: if more come in
/// while a job runs, the last one runs next and the ones in between are skipped. Results are
/// handed back on the main thread by <see cref="Pump"/> (call it every frame).
///
/// For things like autocomplete, where only the answer for the current keystroke matters.
/// </summary>
public class LatestOnlyWorker<T>
{
private bool _busy;
private (Func<T> Work, Action<T> Apply)? _next;
private readonly ConcurrentQueue<(T Result, Exception Error, Action<T> Apply)> _done = new();
/// <summary>
/// Called on the main thread when a job throws.
/// </summary>
public Action<Exception> OnError;
public void Request( Func<T> work, Action<T> apply )
{
if ( _busy )
{
_next = (work, apply);
return;
}
Start( work, apply );
}
private void Start( Func<T> work, Action<T> apply )
{
_busy = true;
Task.Run( work ).ContinueWith( t => _done.Enqueue( t.IsFaulted
? (default, t.Exception?.InnerException ?? t.Exception, apply)
: (t.Result, null, apply) ) );
}
public void Pump()
{
while ( _done.TryDequeue( out var done ) )
{
_busy = false;
// A newer request is waiting, so this result is already out of date
if ( _next is { } next )
{
_next = null;
Start( next.Work, next.Apply );
continue;
}
if ( done.Error is not null ) OnError?.Invoke( done.Error );
else done.Apply( done.Result );
}
}
}