Lumi/companion/plugins/Lumi.Companion.Overlay/OverlayLifecycleQueue.cs
2026-07-26 22:06:34 +02:00

91 lines
3.6 KiB
C#

namespace Lumi.Companion.Overlay;
public enum OverlayItemState { Queued, Entering, Visible, Exiting, Removed }
public sealed class OverlayQueueItem(string id, string kind, object payload, DateTimeOffset queuedAt)
{
public string Id { get; } = id;
public string Kind { get; } = kind;
public object Payload { get; } = payload;
public DateTimeOffset QueuedAt { get; } = queuedAt;
public DateTimeOffset StateAt { get; internal set; } = queuedAt;
public OverlayItemState State { get; internal set; } = OverlayItemState.Queued;
}
public sealed class OverlayLifecycleQueue(Func<OverlayContainerSettings> settings, Func<DateTimeOffset>? now = null)
{
private readonly List<OverlayQueueItem> _items = [];
private readonly Func<DateTimeOffset> _now = now ?? (() => DateTimeOffset.UtcNow);
public IReadOnlyList<OverlayQueueItem> Items => _items;
public event Action? Changed;
public bool Add(string id, string kind, object payload)
{
if (_items.Any(item => item.Id == id && item.State != OverlayItemState.Removed)) return false;
var value = settings();
if (_items.Count(item => item.State != OverlayItemState.Removed) >= Math.Max(value.QueueLimit + 1, value.QueueLimit * 2))
return false;
var active = _items.Where(item => item.State is not OverlayItemState.Exiting and not OverlayItemState.Removed).ToList();
while (active.Count >= value.QueueLimit)
{
var oldest = active[0];
oldest.State = OverlayItemState.Exiting;
oldest.StateAt = _now();
active.RemoveAt(0);
}
var created = new OverlayQueueItem(id, kind, payload, _now()) { State = OverlayItemState.Entering, StateAt = _now() };
_items.Add(created);
Changed?.Invoke();
return true;
}
public void Advance()
{
var current = _now();
var value = settings();
var changed = false;
foreach (var item in _items)
{
var elapsed = current - item.StateAt;
if (item.State == OverlayItemState.Entering && elapsed.TotalMilliseconds >= value.EntryDurationMs)
{
item.State = OverlayItemState.Visible; item.StateAt = current; changed = true;
}
else if (item.State == OverlayItemState.Visible && elapsed.TotalMilliseconds >= value.LifetimeMs)
{
item.State = OverlayItemState.Exiting; item.StateAt = current; changed = true;
}
else if (item.State == OverlayItemState.Exiting && elapsed.TotalMilliseconds >= value.ExitDurationMs)
{
item.State = OverlayItemState.Removed; changed = true;
}
}
if (_items.RemoveAll(item => item.State == OverlayItemState.Removed) > 0) changed = true;
if (changed) Changed?.Invoke();
}
public void Clear()
{
var current = _now();
foreach (var item in _items.Where(item => item.State is not OverlayItemState.Exiting and not OverlayItemState.Removed))
{
item.State = OverlayItemState.Exiting;
item.StateAt = current;
}
Changed?.Invoke();
}
public void ExitWhere(Func<OverlayQueueItem, bool> predicate)
{
var current = _now();
var changed = false;
foreach (var item in _items.Where(item => item.State is not OverlayItemState.Exiting and not OverlayItemState.Removed && predicate(item)))
{
item.State = OverlayItemState.Exiting;
item.StateAt = current;
changed = true;
}
if (changed) Changed?.Invoke();
}
}