// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using EpicGames.Core;
namespace EpicGames.Horde.Compute.Buffers
{
///
/// In-process buffer used to store compute messages
///
public sealed class PooledBuffer : ComputeBuffer
{
///
/// Constructor
///
/// Total capacity of the buffer
public PooledBuffer(int capacity)
: this(2, capacity / 2)
{
}
///
/// Constructor
///
/// Number of chunks in the buffer
/// Length of each chunk
/// Number of readers for this buffer
public PooledBuffer(int numChunks, int chunkLength, int numReaders = 1)
: base(PooledBufferDetail.Create(numChunks, chunkLength, numReaders))
{
}
private PooledBuffer(ComputeBufferDetail resources)
: base(resources)
{
}
///
public override PooledBuffer AddRef()
{
_detail.AddRef();
return new PooledBuffer(_detail);
}
}
///
/// Core implementation of
///
class PooledBufferDetail : ComputeBufferDetail
{
GCHandle _headerHandle;
IMemoryOwner[] _chunkOwners;
readonly AsyncEvent _writerEvent = new AsyncEvent();
readonly AsyncEvent _readerEvent = new AsyncEvent();
internal PooledBufferDetail(HeaderPtr headerPtr, GCHandle headerHandle, Memory[] chunks, IMemoryOwner[] chunkOwners)
: base(headerPtr, chunks)
{
_headerHandle = headerHandle;
_chunkOwners = chunkOwners;
}
public static unsafe PooledBufferDetail Create(int numChunks, int chunkLength, int numReaders)
{
byte[] header = new byte[HeaderSize];
GCHandle headerHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
HeaderPtr headerPtr = new HeaderPtr((ulong*)headerHandle.AddrOfPinnedObject().ToPointer(), numReaders, numChunks, chunkLength);
Memory[] chunks = new Memory[numChunks];
IMemoryOwner[] chunkOwners = new IMemoryOwner[numChunks];
for (int idx = 0; idx < numChunks; idx++)
{
chunkOwners[idx] = MemoryPool.Shared.Rent(chunkLength);
chunks[idx] = chunkOwners[idx].Memory.Slice(0, chunkLength);
}
return new PooledBufferDetail(headerPtr, headerHandle, chunks, chunkOwners);
}
///
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
for (int idx = 0; idx < _chunkOwners.Length; idx++)
{
_chunkOwners[idx].Dispose();
}
_chunkOwners = Array.Empty>();
_headerHandle.Free();
_headerHandle = default;
}
}
///
public override void SetReadEvent(int readerIdx) => _readerEvent.Set();
///
public override Task WaitToReadAsync(int readerIdx, CancellationToken cancellationToken) => _readerEvent.Task.WaitAsync(cancellationToken);
///
public override void SetWriteEvent() => _writerEvent.Set();
///
public override Task WaitToWriteAsync(CancellationToken cancellationToken) => _writerEvent.Task.WaitAsync(cancellationToken);
}
}