Files
osu-framework/osu.Framework/Graphics/Rendering/Deferred/EventList.cs
2024-02-26 15:22:08 +09:00

135 lines
4.7 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.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using osu.Framework.Graphics.Rendering.Deferred.Allocation;
using osu.Framework.Graphics.Rendering.Deferred.Events;
namespace osu.Framework.Graphics.Rendering.Deferred
{
internal class EventList
{
private readonly ResourceAllocator allocator;
private readonly List<MemoryReference> events = new List<MemoryReference>();
public EventList(ResourceAllocator allocator)
{
this.allocator = allocator;
}
/// <summary>
/// Prepares this <see cref="EventList"/> for a new frame.
/// </summary>
public void NewFrame()
=> events.Clear();
/// <summary>
/// Enqueues a render event to the list.
/// </summary>
/// <param name="renderEvent">The render event.</param>
/// <typeparam name="T">The event type.</typeparam>
public void Enqueue<T>(in T renderEvent)
where T : unmanaged, IRenderEvent
=> events.Add(createEvent(renderEvent));
private MemoryReference createEvent<T>(in T renderEvent)
where T : unmanaged, IRenderEvent
{
int requiredSize = Unsafe.SizeOf<T>() + 1;
MemoryReference reference = allocator.AllocateRegion(requiredSize);
Span<byte> buffer = allocator.GetRegion(reference);
buffer[0] = (byte)renderEvent.Type;
Unsafe.WriteUnaligned(ref buffer[1], renderEvent);
return reference;
}
/// <summary>
/// Creates a reader of this <see cref="EventList"/>.
/// </summary>
/// <returns>The <see cref="Enumerator"/>.</returns>
public Enumerator CreateEnumerator()
=> new Enumerator(this);
/// <summary>
/// Reads an <see cref="EventList"/>. Semantically, this is very similar to <see cref="IEnumerator{T}"/>.
/// </summary>
internal ref struct Enumerator
{
private readonly EventList list;
private int eventIndex;
private Span<byte> eventData = Span<byte>.Empty;
public Enumerator(EventList list)
{
this.list = list;
eventIndex = 0;
}
/// <summary>
/// Advances to the next (or first) event in the list.
/// </summary>
/// <returns>Whether an event can be read.</returns>
public bool Next()
{
if (eventIndex < list.events.Count)
{
eventData = list.allocator.GetRegion(list.events[eventIndex]);
eventIndex++;
return true;
}
eventData = Span<byte>.Empty;
return false;
}
/// <summary>
/// Reads the current event type.
/// </summary>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref RenderEventType CurrentType()
=> ref MemoryMarshal.AsRef<RenderEventType>(eventData);
/// <summary>
/// Reads the current event.
/// </summary>
/// <typeparam name="T">The expected event type.</typeparam>
/// <remarks>
/// Not valid for use if <see cref="Next"/> returns <c>false</c>.
/// </remarks>
public readonly ref T Current<T>()
where T : unmanaged, IRenderEvent
=> ref MemoryMarshal.AsRef<T>(eventData[1..]);
/// <summary>
/// Replaces the current event with a new one.
/// </summary>
/// <param name="newEvent">The new render event.</param>
/// <typeparam name="T">The new event type.</typeparam>
public void Replace<T>(T newEvent)
where T : unmanaged, IRenderEvent
{
if (Unsafe.SizeOf<T>() <= eventData.Length)
{
// Fast path where we can maintain contiguous data references.
eventData[0] = (byte)newEvent.Type;
Unsafe.WriteUnaligned(ref eventData[1], newEvent);
}
else
{
// Slow path.
eventData = list.allocator.GetRegion(list.events[eventIndex] = list.createEvent(newEvent));
}
}
}
}
}