Files
AIGame/AIClient/Packages/com.unity.pipeline/Runtime/Common/CliProgress.cs
T

235 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
namespace Unity.Pipeline
{
/// <summary>
/// Per-server progress-reporting state. Owned one-per-<see cref="BasePipelineServer"/> (the
/// same pattern as <c>Dispatcher</c>) so a test server's execution can never cross-attribute
/// progress with the live server's. See <see cref="CliProgress"/> for the public static
/// façade that arbitrary command code calls.
/// </summary>
internal sealed class CliProgressState
{
/// <summary>Immutable copy of the current progress state (thread-safe to hand across threads).</summary>
internal readonly struct Snapshot
{
public readonly bool HasReport;
public readonly string Title;
public readonly string Info;
public readonly long? Current;
public readonly long? Total;
public readonly double? Progress01;
public Snapshot(string title, string info, long? current, long? total, double? progress01)
{
HasReport = true;
Title = title;
Info = info;
Current = current;
Total = total;
Progress01 = progress01;
}
}
readonly object m_Gate = new object();
Snapshot m_Explicit;
string m_ExplicitOwnerId;
string m_CurrentOwnerId;
Snapshot m_Ambient;
int m_ActiveCount;
/// <summary>Whether any execution is currently in flight. Read by /api/progress.</summary>
internal bool IsActive
{
get
{
lock (m_Gate)
{
return m_ActiveCount > 0;
}
}
}
/// <summary>Marks one more execution in flight (see <see cref="EndExecutionCount"/>).</summary>
internal void BeginExecutionCount()
{
lock (m_Gate)
{
m_ActiveCount++;
}
}
/// <summary>
/// Marks one execution finished, resetting the explicit-report state when this was the
/// last one in flight. The decrement and the reset-on-zero check happen under the same
/// lock as <see cref="BeginExecution"/>, so a new execution starting in the gap can never
/// have its fresh BeginExecution/Report wiped by this reset — the previous implementation
/// decremented a separate counter and reset separately, leaving a window where a finishing
/// execution could decrement to zero, get preempted, and then wipe a new execution's
/// owner id and early report once it resumed.
/// </summary>
internal void EndExecutionCount()
{
lock (m_Gate)
{
m_ActiveCount--;
if (m_ActiveCount == 0)
{
m_Explicit = default;
m_ExplicitOwnerId = null;
m_CurrentOwnerId = null;
}
}
}
/// <summary>
/// Report the current task's progress so a connected CLI can render it live.
/// Safe to call from any thread, at any frequency (the server samples on poll).
/// </summary>
/// <param name="title">Short task title, e.g. "Generating World".</param>
/// <param name="info">Detail line, e.g. "Processing 42/100".</param>
/// <param name="current">Current step, when the task has countable steps.</param>
/// <param name="total">Total steps, when the task has countable steps.</param>
/// <param name="progress">Completion in the 0–1 range; omit for indeterminate tasks
/// (when omitted and both <paramref name="current"/> and <paramref name="total"/> are
/// present, the CLI derives a percentage from them).</param>
internal void Report(string title, string info = null, long? current = null, long? total = null, double? progress = null)
{
var clamped = progress.HasValue ? Math.Max(0d, Math.Min(1d, progress.Value)) : (double?)null;
lock (m_Gate)
{
m_Explicit = new Snapshot(title, info, current, total, clamped);
m_ExplicitOwnerId = m_CurrentOwnerId;
}
}
/// <summary>Clear an explicit <see cref="Report"/> (e.g. when the reported task finishes).</summary>
internal void Clear()
{
lock (m_Gate)
{
m_Explicit = default;
m_ExplicitOwnerId = null;
}
}
/// <summary>Ambient-source mirror (UnityEditor.Progress items). Explicit reports win.</summary>
internal void ReportAmbient(string title, string info, long? current, long? total, double? progress01)
{
lock (m_Gate)
{
m_Ambient = new Snapshot(title, info, current, total, progress01);
}
}
/// <summary>Clear the ambient mirror (no running UnityEditor.Progress items remain).</summary>
internal void ClearAmbient()
{
lock (m_Gate)
{
m_Ambient = default;
}
}
/// <summary>
/// Marks the start of the execution identified by <paramref name="ownerId"/> (a job id,
/// or a synthetic id for a plain <c>/api/exec</c>). Clears any leftover explicit report
/// from whatever ran before it and becomes the only owner <see cref="Current"/> will
/// accept explicit reports from until <see cref="EndExecution"/> is called — so a queued
/// execution can never surface the previous one's progress before reporting its own.
/// </summary>
internal void BeginExecution(string ownerId)
{
lock (m_Gate)
{
m_CurrentOwnerId = ownerId;
m_Explicit = default;
m_ExplicitOwnerId = null;
}
}
/// <summary>
/// Marks the end of the execution identified by <paramref name="ownerId"/>. A no-op if
/// some other execution is already current (e.g. called after a swallowed exception),
/// so callers can call this unconditionally in a <c>finally</c>.
/// </summary>
internal void EndExecution(string ownerId)
{
lock (m_Gate)
{
if (m_CurrentOwnerId == ownerId)
{
m_CurrentOwnerId = null;
}
}
}
/// <summary>
/// The snapshot the /api/progress endpoint serves: the explicit report, but only when it
/// was made by the currently executing owner (see <see cref="BeginExecution"/>); falls
/// back to the ambient mirror otherwise.
/// </summary>
internal Snapshot Current
{
get
{
lock (m_Gate)
{
var explicitValid = m_Explicit.HasReport && m_ExplicitOwnerId == m_CurrentOwnerId;
return explicitValid ? m_Explicit : m_Ambient;
}
}
}
}
/// <summary>
/// Structured progress reporting for long-running pipeline commands — the server side of the
/// CLI's terminal progress bars (CLI-488, coordinated with CLI-335).
///
/// While a command executes over <c>/api/exec</c>, the CLI polls <c>GET /api/progress</c> and
/// renders whatever is reported here (a progress bar in a terminal, NDJSON progress frames in
/// machine formats). Command authors call <see cref="Report"/> from any thread — including a
/// main thread that is blocked inside a long synchronous command, which is exactly the
/// <c>EditorUtility.DisplayProgressBar</c> scenario this exists for. The state is read by the
/// HTTP listener thread, never the main thread, so progress stays visible even while the
/// Editor is busy.
///
/// This is a thin static façade over the executing server's own <see cref="CliProgressState"/>
/// (resolved via <see cref="BasePipelineServer.CurrentServer"/>) so command code — which has
/// no reference to "which server is running me" — can keep calling this the same way
/// regardless of which server dispatched it.
///
/// Ambient <c>UnityEditor.Progress</c> items are mirrored in by the Editor assembly
/// (<c>EditorProgressMirror</c>), targeting the live server directly; an explicit
/// <see cref="Report"/> always wins over the ambient mirror. Every execution (a plain
/// <c>/api/exec</c> or a detached job) is bracketed with BeginExecution/EndExecution on the
/// server's own <see cref="CliProgressState"/>, which tags who the current explicit report is
/// allowed to belong to — so a job queued behind one that just finished never inherits its
/// stale progress before making its own first report, no matter how long that job takes to
/// get around to reporting anything.
/// </summary>
public static class CliProgress
{
/// <summary>
/// Report the current task's progress so a connected CLI can render it live.
/// Safe to call from any thread, at any frequency (the server samples on poll). A no-op
/// when called outside a command's own execution (no server to attribute it to).
/// </summary>
/// <param name="title">Short title for the operation.</param>
/// <param name="info">Optional detail text (e.g. current step).</param>
/// <param name="current">Current progress amount, if determinate.</param>
/// <param name="total">Total amount, if determinate.</param>
/// <param name="progress">Progress in [0, 1], used instead of current/total when set.</param>
public static void Report(string title, string info = null, long? current = null, long? total = null, double? progress = null)
{
BasePipelineServer.CurrentServer?.Progress.Report(title, info, current, total, progress);
}
/// <summary>Clear an explicit <see cref="Report"/> (e.g. when the reported task finishes).</summary>
public static void Clear()
{
BasePipelineServer.CurrentServer?.Progress.Clear();
}
}
}