mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-23 22:17:52 -05:00
104 lines
4.1 KiB
C#
104 lines
4.1 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
|
|
#nullable enable
|
|
namespace DataLayer;
|
|
|
|
/// <summary> Notifies clients that the object is being disposed. </summary>
|
|
public interface INotifyDisposed : IDisposable
|
|
{
|
|
/// <summary> Event raised when the object is disposed. </summary>
|
|
event EventHandler? ObjectDisposed;
|
|
}
|
|
|
|
/// <summary> Creates a single instance of <typeparamref name="TDisposable"/> at a time, blocking subsequent creations until the previous creations are disposed. </summary>
|
|
public static class InstanceQueue<TDisposable> where TDisposable : INotifyDisposed
|
|
{
|
|
/// <summary> Synchronization object for access to <see cref="LastInLine"/>"/> </summary>
|
|
private static Lock Locker { get; } = new();
|
|
/// <summary> Ticket holder for the last instance creator in line. </summary>
|
|
private static Ticket? LastInLine { get; set; }
|
|
|
|
/// <summary> Waits for all previously created instances of <typeparamref name="TDisposable"/> to be disposed, then creates and returns a new instance of <typeparamref name="TDisposable"/> using the provided <paramref name="creator"/> factory. </summary>
|
|
public static TDisposable WaitToCreateInstance(Func<TDisposable> creator)
|
|
{
|
|
Ticket ticket;
|
|
lock (Locker)
|
|
{
|
|
ticket = LastInLine = new Ticket(creator, LastInLine);
|
|
}
|
|
|
|
return ticket.Fulfill();
|
|
}
|
|
|
|
/// <summary> A queue ticket for an instance creator. </summary>
|
|
/// <param name="creator">Factory to create a new instance of <typeparamref name="TDisposable"/></param>
|
|
/// <param name="inFront">The ticket immediately in preceding this new ticket. This new ticket must wait for <paramref name="inFront"/> to signal its instance has been disposed before creating a new instance of <typeparamref name="TDisposable"/></param>
|
|
private class Ticket(Func<TDisposable> creator, Ticket? inFront) : IDisposable
|
|
{
|
|
/// <summary> Factory to create a new instance of <typeparamref name="TDisposable"/> </summary>
|
|
private Func<TDisposable> Creator { get; } = creator;
|
|
/// <summary> Ticket immediately in front of this one. This instance must wait for <see cref="InFront"/> to signal its instance has been disposed before creating a new instance of <typeparamref name="TDisposable"/></summary>
|
|
private Ticket? InFront { get; } = inFront;
|
|
/// <summary> Wait handle to signal when this ticket's created instance is disposed </summary>
|
|
private EventWaitHandle WaitHandle { get; } = new(false, EventResetMode.ManualReset);
|
|
/// <summary> This ticket's created instance of <typeparamref name="TDisposable"/> </summary>
|
|
private TDisposable? CreatedInstance { get; set; }
|
|
|
|
/// <summary> Disposes of this ticket and every ticket queued in front of it. </summary>
|
|
public void Dispose()
|
|
{
|
|
WaitHandle.Dispose();
|
|
InFront?.Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Waits for the <see cref="InFront"/> ticket's instance to be disposed, then creates and returns a new instance of <typeparamref name="TDisposable"/> using the <see cref="Creator"/> factory.
|
|
/// </summary>
|
|
public TDisposable Fulfill()
|
|
{
|
|
#if DEBUG
|
|
var sw = Stopwatch.StartNew();
|
|
#endif
|
|
//Wait for the previous ticket's instance to be disposed, then dispose of the previous ticket.
|
|
InFront?.WaitHandle.WaitOne(Timeout.Infinite);
|
|
InFront?.Dispose();
|
|
#if DEBUG
|
|
sw.Stop();
|
|
Debug.WriteLine($"Waited {sw.ElapsedMilliseconds}ms to create instance of {typeof(TDisposable).Name}");
|
|
#endif
|
|
CreatedInstance = Creator();
|
|
CreatedInstance.ObjectDisposed += CreatedInstance_ObjectDisposed;
|
|
return CreatedInstance;
|
|
}
|
|
|
|
private void CreatedInstance_ObjectDisposed(object? sender, EventArgs e)
|
|
{
|
|
Debug.WriteLine($"{typeof(TDisposable).Name} Disposed");
|
|
if (CreatedInstance is not null)
|
|
{
|
|
CreatedInstance.ObjectDisposed -= CreatedInstance_ObjectDisposed;
|
|
CreatedInstance = default;
|
|
}
|
|
|
|
lock (Locker)
|
|
{
|
|
if (this == LastInLine)
|
|
{
|
|
//There are no ticket holders waiting after this one.
|
|
//This ticket is fulfilled and will never be waited on.
|
|
LastInLine = null;
|
|
Dispose();
|
|
}
|
|
else
|
|
{
|
|
//Signal the that this ticket has been fulfilled so that
|
|
//the next ticket in line may proceed.
|
|
WaitHandle.Set();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|