using System;
using System.Collections.Generic;
using System.Linq;
namespace Unity.Pipeline
{
/// Snapshot of one dialog shown/dismissed event, as relayed by EditorDialogEvents.
internal readonly struct DialogInfo
{
public readonly int Id;
public readonly string Source;
public readonly string Title;
public readonly string Message;
public readonly string[] Buttons;
public readonly string Level;
public readonly DateTime OpenedAtUtc;
public readonly DateTime? DismissedAtUtc;
public DialogInfo(int id, string source, string title, string message, string[] buttons, string level, DateTime openedAtUtc, DateTime? dismissedAtUtc)
{
Id = id;
Source = source;
Title = title;
Message = message;
Buttons = buttons;
Level = level;
OpenedAtUtc = openedAtUtc;
DismissedAtUtc = dismissedAtUtc;
}
public DialogInfo WithDismissed(DateTime dismissedAtUtc) =>
new DialogInfo(Id, Source, Title, Message, Buttons, Level, OpenedAtUtc, dismissedAtUtc);
}
///
/// Per-server modal-dialog state (the same one-instance-per-
/// ownership as CliProgressState/Progress, so a test server's dialog state never
/// crosses into the live server's). Unlike progress, dialogs are not scoped to a currently
/// executing command — they can be raised by anything (a human, a background job, an unrelated
/// command) at any time, so there is no owner-id concept here.
///
internal sealed class DialogStateTracker
{
const int MaxRecentEvents = 50;
readonly object m_Gate = new object();
readonly Dictionary m_Open = new Dictionary();
readonly LinkedList m_Recent = new LinkedList();
internal bool IsDialogOpen
{
get { lock (m_Gate) return m_Open.Count > 0; }
}
///
/// Ordered oldest-opened-first: callers (the dialog busy-gate, /api/status) treat index 0
/// as "the first open dialog", and Dictionary<,>'s enumeration order isn't guaranteed
/// to match insertion order once entries have been removed and re-added.
///
internal List CurrentlyOpen
{
get { lock (m_Gate) return m_Open.Values.OrderBy(e => e.OpenedAtUtc).ToList(); }
}
internal void OnShown(int id, string source, string title, string message, string[] buttons, string level, DateTime openedAtUtc)
{
var info = new DialogInfo(id, source, title, message, buttons, level, openedAtUtc, null);
lock (m_Gate)
{
m_Open[id] = info;
}
}
internal void OnDismissed(int id, DateTime dismissedAtUtc)
{
lock (m_Gate)
{
if (!m_Open.TryGetValue(id, out var info))
return;
m_Open.Remove(id);
m_Recent.AddLast(info.WithDismissed(dismissedAtUtc));
while (m_Recent.Count > MaxRecentEvents)
m_Recent.RemoveFirst();
}
}
///
/// Dialogs opened at or after — still-open ones and recently
/// dismissed ones. Used to attach dialogsDuringExecution to a command's exec response.
///
internal List EventsSince(DateTime utc)
{
lock (m_Gate)
{
var result = m_Open.Values.Where(e => e.OpenedAtUtc >= utc).ToList();
result.AddRange(m_Recent.Where(e => e.OpenedAtUtc >= utc));
return result;
}
}
}
}