Files
osu-framework/osu.Framework/Allocation/TripleBuffer.cs
2024-12-10 23:58:03 +09:00

117 lines
3.3 KiB
C#

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using System.Threading;
namespace osu.Framework.Allocation
{
/// <summary>
/// Handles triple-buffering of any object type.
/// Thread safety assumes at most one writer and one reader.
/// Comes with the added assurance that the most recent <see cref="GetForRead"/> object is not written to.
/// </summary>
internal class TripleBuffer<T>
where T : class
{
/// <summary>
/// The default amount of time (in milliseconds) to wait for a write to occur during <see cref="GetForRead"/>.
/// </summary>
public const int DEFAULT_READ_TIMEOUT = 100;
private const int buffer_count = 3;
private readonly Buffer[] buffers = new Buffer[buffer_count];
private readonly Stopwatch stopwatch = new Stopwatch();
private int writeIndex;
private int flipIndex = 1;
private int readIndex = 2;
public TripleBuffer()
{
for (int i = 0; i < buffer_count; i++)
buffers[i] = new Buffer(i, finishUsage);
}
/// <summary>
/// Retrieves a buffer to be written to.
/// </summary>
/// <returns>The buffer.</returns>
public Buffer GetForWrite()
{
Buffer usage = buffers[writeIndex];
usage.LastUsage = UsageType.Write;
return usage;
}
/// <summary>
/// Attempts to retrieve a buffer to read.
/// </summary>
/// <param name="timeout">Amount of time (in milliseconds) to wait for a buffer to be written.</param>
/// <returns>An available buffer to be read, or <c>null</c> if no buffer has been written.</returns>
public Buffer? GetForRead(int timeout = DEFAULT_READ_TIMEOUT)
{
stopwatch.Restart();
while (true)
{
flip(ref readIndex);
if (buffers[readIndex].LastUsage != UsageType.Read)
break;
if (timeout == 0 || stopwatch.ElapsedMilliseconds > timeout)
return null;
}
Buffer usage = buffers[readIndex];
Debug.Assert(usage.LastUsage == UsageType.Write);
usage.LastUsage = UsageType.Read;
return usage;
}
private void finishUsage(Buffer usage)
{
if (usage.LastUsage == UsageType.Write)
flip(ref writeIndex);
}
private void flip(ref int localIndex)
{
localIndex = Interlocked.Exchange(ref flipIndex, localIndex);
}
public class Buffer : IDisposable
{
public T? Object;
public volatile UsageType LastUsage;
public readonly int Index;
private readonly Action<Buffer>? finish;
public Buffer(int index, Action<Buffer>? finish)
{
Index = index;
this.finish = finish;
}
public void Dispose()
{
finish?.Invoke(this);
}
}
public enum UsageType
{
Read,
Write
}
}
}