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 );
		}
	}
}