Editor/TextToAnimation/Engine/EngineThread.cs

Editor helper for ensuring code runs on the editor main thread. It provides an awaitable SwitchToMainThread, a DelayOnMain helper that delays then hops to main, a MainThreadProgress that marshals IProgress callbacks to the main thread, and a Try<T> wrapper that catches exceptions and returns default.

Native Interop
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Sandbox;

namespace TextToAnimation.Editor.Engine;

/// <summary>
/// Main-thread helpers. Engine objects (AssetSystem, Model, SceneModel, widgets) may only be touched on the
/// editor main thread - doing so from a pool thread is a native crash, not an exception. Every async flow
/// awaits <see cref="SwitchToMainThread"/> before touching them (pattern from humanoid-retargeter).
/// </summary>
public static class EngineThread
{
	/// <summary>Awaitable hop to the editor main thread (completes synchronously when already there).</summary>
	public static MainThreadAwaitable SwitchToMainThread() => default;

	public readonly struct MainThreadAwaitable : INotifyCompletion
	{
		public MainThreadAwaitable GetAwaiter() => this;
		public bool IsCompleted => ThreadSafe.IsMainThread;
		public void OnCompleted( Action continuation ) => MainThread.Queue( continuation );
		public void GetResult() { }
	}

	/// <summary>Waits on a timer, then resumes on the main thread.</summary>
	public static async Task DelayOnMain( int milliseconds )
	{
		await Task.Delay( milliseconds );
		await SwitchToMainThread();
	}

	/// <summary>An <see cref="IProgress{T}"/> whose callback always runs on the main thread (the editor has no
	/// SynchronizationContext, so <see cref="Progress{T}"/> would call back on the worker).</summary>
	public sealed class MainThreadProgress<T> : IProgress<T>
	{
		readonly Action<T> _report;
		public MainThreadProgress( Action<T> report ) => _report = report;
		public void Report( T value )
		{
			if ( ThreadSafe.IsMainThread ) _report( value );
			else MainThread.Queue( () => _report( value ) );
		}
	}

	public static T Try<T>( Func<T> getter )
	{
		try { return getter(); }
		catch { return default; }
	}
}