forked from ppy/osu-framework
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove array/stopwatch access from TripleBuffer
- Loading branch information
Showing
4 changed files
with
74 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
@@ -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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters