Editor/Core/RigWork.cs
using System.Threading;
using System.Threading.Tasks;
namespace HumanoidRigger;

/// <summary>Leave CPU capacity for the editor and other applications. Scheduling
/// changes never reduce solver iterations, candidates, or validation coverage.</summary>
internal static class RigWork
{
    internal static int WorkerCount(int vertexCount)
        =>vertexCount<8192?1:Math.Clamp(Environment.ProcessorCount/4,1,4);

    internal static Task Run(Action action)=>Run(()=>{action();return 0;});
    internal static Task<T> Run<T>(Func<T> action)=>Task.Run(()=>
    {
        using var priority=new WorkerPriority();
        return action();
    });

    internal static void For(int count,int workers,Action<int> body)
        =>For(count,workers,()=>0,(i,_)=>body(i));

    internal static void For<T>(int count,int workers,Func<T> initialize,Action<int,T> body)
    {
        if(workers==1)
        {
            using var priority=new WorkerPriority();
            var local=initialize();
            for(int i=0;i<count;i++)body(i,local);
            return;
        }
        // Set priority once per participating worker, including the caller that
        // Parallel.For may use. Restore it even when initialization or work fails.
        Parallel.For(0,count,new ParallelOptions{MaxDegreeOfParallelism=workers},
            ()=>
            {
                var priority=new WorkerPriority();
                try{return(Priority:priority,Value:initialize());}
                catch{priority.Dispose();throw;}
            },
            (i,_,local)=>{body(i,local.Value);return local;},
            local=>local.Priority.Dispose());
    }

    internal readonly struct WorkerPriority : IDisposable
    {
        readonly Thread thread;
        readonly ThreadPriority original;
        readonly bool changed;
        public WorkerPriority()
        {
            thread=Thread.CurrentThread;
            original=thread.Priority;
            changed=original>ThreadPriority.BelowNormal;
            if(changed)thread.Priority=ThreadPriority.BelowNormal;
        }
        public void Dispose(){if(changed)thread.Priority=original;}
    }
}