修改提交
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityTcp.Editor.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages long-running asynchronous operation states (compilation, UPM, baking, etc.)
|
||||
/// Provides operation ID generation, status tracking, and timeout management.
|
||||
/// </summary>
|
||||
public static class AsyncOperationTracker
|
||||
{
|
||||
/// <summary>
|
||||
/// Job status enum matching MCP protocol.
|
||||
/// </summary>
|
||||
public enum JobStatus
|
||||
{
|
||||
Pending,
|
||||
Complete,
|
||||
Error
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Job type enum for categorizing operations.
|
||||
/// </summary>
|
||||
public enum JobType
|
||||
{
|
||||
Compilation,
|
||||
UpmPackage,
|
||||
NavMeshBake,
|
||||
LightingBake,
|
||||
Custom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a tracked job/operation.
|
||||
/// </summary>
|
||||
public class Job
|
||||
{
|
||||
public string OpId { get; set; }
|
||||
public JobType Type { get; set; }
|
||||
public JobStatus Status { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public string Message { get; set; }
|
||||
public object Data { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public float Progress { get; set; } // 0.0 to 1.0
|
||||
}
|
||||
|
||||
// Job storage
|
||||
private static readonly Dictionary<string, Job> _jobs = new Dictionary<string, Job>();
|
||||
private static readonly object _jobsLock = new object();
|
||||
|
||||
// Default timeout in seconds
|
||||
private const int DefaultTimeoutSeconds = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new job with a unique op_id and registers it.
|
||||
/// </summary>
|
||||
public static Job CreateJob(JobType type, string message = null)
|
||||
{
|
||||
var job = new Job
|
||||
{
|
||||
OpId = GenerateOpId(),
|
||||
Type = type,
|
||||
Status = JobStatus.Pending,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Message = message ?? $"{type} operation started",
|
||||
Progress = 0.0f
|
||||
};
|
||||
|
||||
lock (_jobsLock)
|
||||
{
|
||||
_jobs[job.OpId] = job;
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a job by op_id.
|
||||
/// </summary>
|
||||
public static Job GetJob(string opId)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
return _jobs.TryGetValue(opId, out var job) ? job : null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates job status to Complete.
|
||||
/// </summary>
|
||||
public static void CompleteJob(string opId, string message = null, object data = null)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
if (_jobs.TryGetValue(opId, out var job))
|
||||
{
|
||||
job.Status = JobStatus.Complete;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.Progress = 1.0f;
|
||||
if (message != null) job.Message = message;
|
||||
if (data != null) job.Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates job status to Error.
|
||||
/// </summary>
|
||||
public static void FailJob(string opId, string errorMessage)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
if (_jobs.TryGetValue(opId, out var job))
|
||||
{
|
||||
job.Status = JobStatus.Error;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.ErrorMessage = errorMessage;
|
||||
job.Message = $"Operation failed: {errorMessage}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates job progress (0.0 to 1.0).
|
||||
/// </summary>
|
||||
public static void UpdateProgress(string opId, float progress, string message = null)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
if (_jobs.TryGetValue(opId, out var job))
|
||||
{
|
||||
job.Progress = Mathf.Clamp01(progress);
|
||||
if (message != null) job.Message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a job from tracking.
|
||||
/// </summary>
|
||||
public static void RemoveJob(string opId)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
_jobs.Remove(opId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all pending jobs of a specific type.
|
||||
/// </summary>
|
||||
public static List<Job> GetPendingJobs(JobType? type = null)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
return _jobs.Values
|
||||
.Where(j => j.Status == JobStatus.Pending && (!type.HasValue || j.Type == type.Value))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans up old jobs that have been completed or timed out.
|
||||
/// Should be called periodically.
|
||||
/// </summary>
|
||||
public static void CleanupOldJobs(int maxAgeSeconds = 3600)
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddSeconds(-maxAgeSeconds);
|
||||
|
||||
lock (_jobsLock)
|
||||
{
|
||||
var toRemove = _jobs
|
||||
.Where(kv => kv.Value.CompletedAt.HasValue && kv.Value.CompletedAt.Value < cutoff)
|
||||
.Select(kv => kv.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var opId in toRemove)
|
||||
{
|
||||
_jobs.Remove(opId);
|
||||
}
|
||||
|
||||
if (toRemove.Count > 0)
|
||||
{
|
||||
Debug.Log($"[AsyncOperationTracker] Cleaned up {toRemove.Count} old jobs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a job has timed out.
|
||||
/// </summary>
|
||||
public static bool IsJobTimedOut(string opId, int timeoutSeconds = DefaultTimeoutSeconds)
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
if (_jobs.TryGetValue(opId, out var job))
|
||||
{
|
||||
if (job.Status == JobStatus.Pending)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - job.CreatedAt).TotalSeconds;
|
||||
return elapsed > timeoutSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Pending async operation response for a job.
|
||||
/// Protocol: status/poll_interval/op_id
|
||||
/// </summary>
|
||||
public static object CreatePendingResponse(Job job, object stateDelta = null)
|
||||
{
|
||||
var response = new Dictionary<string, object>
|
||||
{
|
||||
["status"] = "pending",
|
||||
["poll_interval"] = 1.0, // Poll every 1 second
|
||||
["op_id"] = job.OpId,
|
||||
["success"] = true,
|
||||
["message"] = job.Message,
|
||||
["data"] = new
|
||||
{
|
||||
type = job.Type.ToString(),
|
||||
progress = job.Progress,
|
||||
createdAt = job.CreatedAt.ToString("o")
|
||||
}
|
||||
};
|
||||
|
||||
// Add operations state delta showing the new pending operation
|
||||
var opDelta = StateComposer.CreateOperationsDelta(new[] {
|
||||
new { id = job.OpId, type = job.Type.ToString(), progress = job.Progress, message = job.Message }
|
||||
});
|
||||
response["state_delta"] = stateDelta != null
|
||||
? StateComposer.MergeStateDeltas(opDelta, stateDelta)
|
||||
: opDelta;
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a Complete async operation response for a job.
|
||||
/// Protocol: status/op_id
|
||||
/// </summary>
|
||||
public static object CreateCompleteResponse(Job job, object stateDelta = null)
|
||||
{
|
||||
var response = new Dictionary<string, object>
|
||||
{
|
||||
["status"] = "complete",
|
||||
["op_id"] = job.OpId,
|
||||
["success"] = true,
|
||||
["message"] = job.Message,
|
||||
["data"] = job.Data
|
||||
};
|
||||
|
||||
// Include state_delta if provided
|
||||
if (stateDelta != null)
|
||||
{
|
||||
response["state_delta"] = stateDelta;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Error async operation response for a job.
|
||||
/// Protocol: status/op_id
|
||||
/// </summary>
|
||||
public static object CreateErrorResponse(Job job, object stateDelta = null)
|
||||
{
|
||||
var response = new Dictionary<string, object>
|
||||
{
|
||||
["status"] = "error",
|
||||
["op_id"] = job.OpId,
|
||||
["success"] = false,
|
||||
["message"] = job.Message,
|
||||
["error"] = job.ErrorMessage
|
||||
};
|
||||
|
||||
// Include state_delta if provided
|
||||
if (stateDelta != null)
|
||||
{
|
||||
response["state_delta"] = stateDelta;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a unique operation ID.
|
||||
/// </summary>
|
||||
private static string GenerateOpId()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets count of all jobs by status.
|
||||
/// </summary>
|
||||
public static Dictionary<JobStatus, int> GetJobCounts()
|
||||
{
|
||||
lock (_jobsLock)
|
||||
{
|
||||
return _jobs.Values
|
||||
.GroupBy(j => j.Status)
|
||||
.ToDictionary(g => g.Key, g => g.Count());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user