mirror of
https://github.com/SK-la/osu-framework.git
synced 2026-03-15 03:20:30 +00:00
117 lines
3.3 KiB
C#
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
|
|
}
|
|
}
|
|
}
|