Skip to content

Commit

Permalink
Remove array/stopwatch access from TripleBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
smoogipoo committed Sep 10, 2024
1 parent 95bab7d commit 59bdcfd
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 49 deletions.
12 changes: 6 additions & 6 deletions osu.Framework.Tests/Graphics/TripleBufferTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void TestReadOnly()
var tripleBuffer = new TripleBuffer<TestObject>();

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer, Is.Null);
Assert.That(!buffer.IsValid);
}

[Test]
Expand All @@ -51,7 +51,7 @@ public void TestSameBufferIsNotWrittenTwiceInRowNoContestation()
}

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer!.Object!.ID, Is.EqualTo(lastWrite));
Assert.That(buffer.Object!.ID, Is.EqualTo(lastWrite));
}
}

Expand All @@ -72,7 +72,7 @@ public void TestSameBufferIsNotWrittenTwiceInRowContestation()
{
using (var read = tripleBuffer.GetForRead())
{
Assert.That(read!.Object!.ID, Is.Not.EqualTo(lastRead));
Assert.That(read.Object!.ID, Is.Not.EqualTo(lastRead));

for (int j = 0; j < 3; j++)
{
Expand Down Expand Up @@ -100,11 +100,11 @@ public void TestWriteThenRead()
write.Object = obj;

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer?.Object, Is.EqualTo(obj));
Assert.That(buffer.Object, Is.EqualTo(obj));
}

using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer, Is.Null);
Assert.That(!buffer.IsValid);
}

[Test]
Expand All @@ -121,7 +121,7 @@ public void TestReadSaturated()
{
resetEventSlim.Set();
using (var buffer = tripleBuffer.GetForRead())
Assert.That(buffer?.Object, Is.EqualTo(obj));
Assert.That(buffer.Object, Is.EqualTo(obj));
}, TaskCreationOptions.LongRunning);

Task.Factory.StartNew(() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ protected override void LoadComplete()
{
using (var read = tripleBuffer.GetForRead())
{
if (read != null)
if (read.IsValid)
reads[read.Index]++;
}
Expand Down
105 changes: 65 additions & 40 deletions osu.Framework/Allocation/TripleBuffer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;

namespace osu.Framework.Allocation
Expand All @@ -16,87 +16,112 @@ internal class TripleBuffer<T>
where T : class
{
private const int buffer_count = 3;
private const long read_timeout_milliseconds = 100;

private readonly Buffer[] buffers = new Buffer[buffer_count];

private readonly Stopwatch stopwatch = new Stopwatch();
private const int read_timeout_milliseconds = 100;

private BufferArray buffers;
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);
buffers[writeIndex] = new Buffer(writeIndex);
buffers[flipIndex] = new Buffer(flipIndex);
buffers[readIndex] = new Buffer(readIndex);
}

public Buffer GetForWrite()
public WriteUsage GetForWrite()
{
Buffer usage = buffers[writeIndex];
usage.LastUsage = UsageType.Write;
return usage;
ref Buffer buffer = ref buffers[writeIndex];
buffer.LastUsage = UsageType.Write;
return new WriteUsage(this, ref buffer);
}

public Buffer? GetForRead()
public ReadUsage GetForRead()
{
stopwatch.Restart();
const int estimate_nanoseconds_per_cycle = 5;
const int estimate_cycles_to_timeout = read_timeout_milliseconds * 1000 * 1000 / estimate_nanoseconds_per_cycle;

do
// This should really never happen, but prevents a potential infinite loop if the usage can never be retrieved.
for (int i = 0; i < estimate_cycles_to_timeout; i++)
{
flip(ref readIndex);

// This should really never happen, but prevents a potential infinite loop if the usage can never be retrieved.
if (stopwatch.ElapsedMilliseconds > read_timeout_milliseconds)
return null;
} while (buffers[readIndex].LastUsage == UsageType.Read);

Buffer usage = buffers[readIndex];

Debug.Assert(usage.LastUsage == UsageType.Write);
usage.LastUsage = UsageType.Read;
ref Buffer buffer = ref buffers[readIndex];
if (buffer.LastUsage == UsageType.Read)
continue;

return usage;
}
buffer.LastUsage = UsageType.Read;
return new ReadUsage(ref buffer);
}

private void finishUsage(Buffer usage)
{
if (usage.LastUsage == UsageType.Write)
flip(ref writeIndex);
return default;
}

private void flip(ref int localIndex)
{
localIndex = Interlocked.Exchange(ref flipIndex, localIndex);
}

public class Buffer : IDisposable
public readonly ref struct WriteUsage
{
public T? Object;
private readonly TripleBuffer<T> tripleBuffer;
private readonly ref Buffer buffer;

public volatile UsageType LastUsage;
public WriteUsage(TripleBuffer<T> tripleBuffer, ref Buffer buffer)
{
this.tripleBuffer = tripleBuffer;
this.buffer = ref buffer;
}

public T? Object
{
get => buffer.Object;
set => buffer.Object = value;
}

public readonly int Index;
public int Index => buffer.Index;

private readonly Action<Buffer>? finish;
public void Dispose() => tripleBuffer.flip(ref tripleBuffer.writeIndex);
}

public readonly ref struct ReadUsage
{
private readonly ref Buffer buffer;

public Buffer(int index, Action<Buffer>? finish)
public ReadUsage(ref Buffer buffer)
{
Index = index;
this.finish = finish;
this.buffer = ref buffer;
}

[MemberNotNullWhen(true, nameof(Object))]
public bool IsValid => !Unsafe.IsNullRef(ref buffer);

public T? Object => buffer.Object;

public int Index => buffer.Index;

public void Dispose()
{
finish?.Invoke(this);
}
}

public record struct Buffer(int Index)
{
public T? Object;
public volatile UsageType LastUsage;
}

public enum UsageType
{
Read,
Write
}

[InlineArray(buffer_count)]
private struct BufferArray
{
private Buffer buffer;
}
}
}
4 changes: 2 additions & 2 deletions osu.Framework/Platform/GameHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@ protected virtual void DrawFrame()

Renderer.AllowTearing = windowMode.Value == WindowMode.Fullscreen;

TripleBuffer<DrawNode>.Buffer buffer;
TripleBuffer<DrawNode>.ReadUsage buffer;

using (drawMonitor.BeginCollecting(PerformanceCollectionType.Sleep))
{
Expand All @@ -516,7 +516,7 @@ protected virtual void DrawFrame()
buffer = drawRoots.GetForRead();
}

if (buffer == null)
if (!buffer.IsValid)
return;

Debug.Assert(buffer.Object != null);
Expand Down

0 comments on commit 59bdcfd

Please sign in to comment.