using System.Collections.Concurrent;
using System.Collections.Generic;
namespace UnityTcp.Editor.Helpers
{
///
/// Thread-safe index of every in-flight runner job, keyed by job id and by the native request
/// id that started it. The request-id index is what lets a client detach a job it has not yet
/// received a job id for — it always knows the request id of the call it is stuck waiting on.
///
/// Runners register on enroll (and re-registers restored jobs
/// after a domain reload) and unregister when the job leaves their registry. The handle a
/// runner registers is what makes cross-thread detach possible: its
/// touches only thread-safe state (the
/// delivery gate, , the native response
/// queue), never the runner's main-thread-only job list.
///
public static class JobRegistry
{
/// A registered in-flight job, with the thread-safe detach entry point.
public interface IJobHandle
{
string JobId { get; }
ulong RequestId { get; }
string Name { get; }
/// Owning runner: "step", "task", or "coroutine".
string Runner { get; }
bool Detached { get; }
///
/// Detaches the job: answers its original request with a detach ack carrying the job
/// id, after which the outcome lands in for polling.
/// Callable from any thread. Returns false (with an explanation) when the job was
/// already detached or already answered.
///
bool TryDetach(string reason, out string error);
}
private static readonly ConcurrentDictionary _byJobId =
new ConcurrentDictionary();
private static readonly ConcurrentDictionary _jobIdByRequestId =
new ConcurrentDictionary();
internal static void Register(IJobHandle handle)
{
if (handle == null || string.IsNullOrEmpty(handle.JobId)) return;
_byJobId[handle.JobId] = handle;
if (handle.RequestId != 0)
_jobIdByRequestId[handle.RequestId] = handle.JobId;
}
internal static void Unregister(string jobId)
{
if (string.IsNullOrEmpty(jobId)) return;
if (_byJobId.TryRemove(jobId, out var handle) && handle.RequestId != 0)
{
// Only drop the request mapping if it still points at this job — a handed-off job
// re-registers under the same request id before the old runner unregisters.
if (_jobIdByRequestId.TryGetValue(handle.RequestId, out string mapped) && mapped == jobId)
_jobIdByRequestId.TryRemove(handle.RequestId, out _);
}
}
public static bool TryGet(string jobId, out IJobHandle handle)
{
handle = null;
return !string.IsNullOrEmpty(jobId) && _byJobId.TryGetValue(jobId, out handle);
}
public static bool TryGetByRequestId(ulong requestId, out IJobHandle handle)
{
handle = null;
return requestId != 0
&& _jobIdByRequestId.TryGetValue(requestId, out string jobId)
&& _byJobId.TryGetValue(jobId, out handle);
}
/// Snapshot of every in-flight job, for manage_job.list.
public static List Snapshot()
{
var result = new List();
foreach (var kv in _byJobId)
result.Add(kv.Value);
return result;
}
public static int Count => _byJobId.Count;
internal static void Clear()
{
_byJobId.Clear();
_jobIdByRequestId.Clear();
}
}
}