namespace AcDream.App.Rendering;
/// Retryable one-owner slot for a disposable runtime resource.
internal sealed class OwnedResourceSlot
where T : class, IDisposable
{
private T? _resource;
private bool _operationActive;
public bool HasResource => _resource is not null;
public T Acquire(Func factory)
{
ArgumentNullException.ThrowIfNull(factory);
if (_operationActive || _resource is not null)
throw new InvalidOperationException($"A {typeof(T).Name} is already owned.");
_operationActive = true;
try
{
T resource = factory()
?? throw new InvalidOperationException($"The {typeof(T).Name} factory returned null.");
_resource = resource;
return resource;
}
finally
{
_operationActive = false;
}
}
public T Borrow()
{
if (_operationActive)
throw new InvalidOperationException($"The {typeof(T).Name} owner is changing state.");
return _resource
?? throw new InvalidOperationException($"No {typeof(T).Name} is currently owned.");
}
public void Release()
{
T? resource = _resource;
if (resource is null)
return;
if (_operationActive)
throw new InvalidOperationException($"The {typeof(T).Name} owner is changing state.");
_operationActive = true;
try
{
resource.Dispose();
if (ReferenceEquals(_resource, resource))
_resource = null;
}
finally
{
_operationActive = false;
}
}
internal void TransferOwnership(T expected)
{
ArgumentNullException.ThrowIfNull(expected);
if (_operationActive)
throw new InvalidOperationException($"The {typeof(T).Name} owner is changing state.");
if (!ReferenceEquals(_resource, expected))
throw new InvalidOperationException(
$"The expected {typeof(T).Name} is not owned by this slot.");
_resource = null;
}
}
///
/// Owns a fallback resource until a complete destination owner factory
/// returns. A throwing factory leaves fallback ownership unchanged.
///
internal sealed class TransferableResourceSlot
where T : class, IDisposable
{
private readonly OwnedResourceSlot _fallback = new();
private bool _operationActive;
private bool _prepared;
public bool HasFallback => _fallback.HasResource;
public T Acquire(Func factory) => Execute(() =>
{
T resource = _fallback.Acquire(factory);
_prepared = true;
return resource;
});
public T AcquirePrepared(Func factory, Action prepare) => Execute(() =>
{
ArgumentNullException.ThrowIfNull(factory);
ArgumentNullException.ThrowIfNull(prepare);
T resource;
if (_fallback.HasResource)
{
resource = _fallback.Borrow();
}
else
{
resource = _fallback.Acquire(factory);
_prepared = false;
}
if (!_prepared)
{
prepare(resource);
_prepared = true;
}
return resource;
});
public T Borrow()
{
if (_operationActive)
throw new InvalidOperationException("The transferable resource owner is changing state.");
return _fallback.Borrow();
}
public TOwner Transfer(Func destinationFactory)
where TOwner : class
{
return Execute(() =>
{
ArgumentNullException.ThrowIfNull(destinationFactory);
if (!_prepared)
throw new InvalidOperationException(
"The transferable resource has not completed preparation.");
T resource = _fallback.Borrow();
TOwner owner = destinationFactory(resource)
?? throw new InvalidOperationException("The destination owner factory returned null.");
// The destination factory contract transfers ownership of resource.
// Clear without disposing only after the complete owner exists.
_fallback.TransferOwnership(resource);
_prepared = false;
return owner;
});
}
public void ReleaseFallback() => Execute(
() =>
{
_fallback.Release();
if (!_fallback.HasResource)
_prepared = false;
return true;
});
private TResult Execute(Func operation)
{
ArgumentNullException.ThrowIfNull(operation);
if (_operationActive)
throw new InvalidOperationException("The transferable resource owner is changing state.");
_operationActive = true;
try
{
return operation();
}
finally
{
_operationActive = false;
}
}
}