Editor/Core/Analysis/Inference/AutomaticHandRefiner.cs
#nullable enable annotations
using System.Threading.Tasks;
namespace HumanoidRigger;

public interface IHandRefiner
{
    HandRefinementReport Refine(ImportedCharacter character,Anatomy anatomy,string side);
}

/// <summary>Warms CPU inference during import; geometry remains usable when the optional prior is unavailable.</summary>
public sealed class AutomaticHandRefiner : IHandRefiner,IDisposable
{
    readonly object sync=new();
    readonly object inference=new();
    Task<NativeHandModel>? loading;
    bool disposed;
    int activeRefinements;
    public void Warmup()
    {
        lock(sync)
        {
            if(disposed||loading is not null)return;
            loading=Task.Run(()=>HandModelAssets.Load(HandModelAssets.CacheDirectory));
            // Observe download failures even if the window is closed before the hand stage.
            _=loading.ContinueWith(task=>{_ = task.Exception;},TaskContinuationOptions.OnlyOnFaulted);
        }
    }
    public HandRefinementReport Refine(ImportedCharacter character,Anatomy anatomy,string side)
    {
        Warmup();
        NativeHandModel model;
        lock(sync)
        {
            if(disposed)return new("Geometry fallback: inference closed",0,0,0);
            if(loading is null||!loading.IsCompleted)return new("Geometry fallback: hand model is warming",0,0,0);
            if(loading.IsFaulted)return new("Geometry fallback: hand model unavailable",0,0,0);
            model=loading.Result;activeRefinements++;
        }
        try
        {
            try{lock(inference)return HandPriorRefinement.Refine(character,anatomy,side,model);}
            catch(Exception e) when(e is InvalidOperationException or ArgumentException or ObjectDisposedException)
            {return new("Geometry fallback: "+e.Message,0,0,0);}
        }
        finally{lock(sync){activeRefinements--;if(disposed&&activeRefinements==0)ReleaseModel();}}
    }
    public void Dispose()
    {
        lock(sync)
        {
            if(disposed)return;disposed=true;
            if(activeRefinements==0)ReleaseModel();
        }
    }
    void ReleaseModel(){if(loading is not null)_=loading.ContinueWith(task=>{if(task.IsCompletedSuccessfully)task.Result.Dispose();},TaskScheduler.Default);}
}