using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Common.Extensions; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { /// /// Server state for a splitter. /// [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = Alignment)] public struct SplitterState { public const int Alignment = 0x10; /// /// The unique id of this . /// public int Id; /// /// Target sample rate to use on the splitter. /// public uint SampleRate; /// /// Count of splitter destinations (). /// public int DestinationCount; /// /// Set to true if the splitter has a new connection. /// [MarshalAs(UnmanagedType.I1)] public bool HasNewConnection; /// /// Linked list of . /// private unsafe SplitterDestination* _destinationsData; /// /// Span to the first element of the linked list of . /// public readonly Span Destinations { get { unsafe { return (IntPtr)_destinationsData != IntPtr.Zero ? new Span(_destinationsData, 1) : Span.Empty; } } } /// /// Create a new . /// /// The unique id of this . public SplitterState(int id) : this() { Id = id; } public readonly Span GetData(int index) { int i = 0; Span result = Destinations; while (i < index) { if (result.IsEmpty) { break; } result = result[0].Next; i++; } return result; } /// /// Clear the new connection flag. /// public void ClearNewConnectionFlag() { HasNewConnection = false; } /// /// Utility function to apply a given to all . /// /// The action to execute on each elements. private readonly void ForEachDestination(SpanAction action) { Span temp = Destinations; int i = 0; while (true) { if (temp.IsEmpty) { break; } Span next = temp[0].Next; action.Invoke(temp, i++); temp = next; } } /// /// Update the from user parameter. /// /// The splitter context. /// The user parameter. /// The raw input data after the . public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader input) { ClearLinks(); int destinationCount; if (context.IsBugFixed) { destinationCount = parameter.DestinationCount; } else { destinationCount = Math.Min(context.GetDestinationCountPerStateForCompatibility(), parameter.DestinationCount); } if (destinationCount > 0) { input.ReadLittleEndian(out int destinationId); Memory destination = context.GetDestinationMemory(destinationId); SetDestination(ref destination.Span[0]); DestinationCount = destinationCount; for (int i = 1; i < destinationCount; i++) { input.ReadLittleEndian(out destinationId); Memory nextDestination = context.GetDestinationMemory(destinationId); destination.Span[0].Link(ref nextDestination.Span[0]); destination = nextDestination; } } Debug.Assert(parameter.Id == Id); if (parameter.Id == Id) { SampleRate = parameter.SampleRate; HasNewConnection = true; } } /// /// Set the head of the linked list of . /// /// A reference to a . public void SetDestination(ref SplitterDestination newValue) { unsafe { fixed (SplitterDestination* newValuePtr = &newValue) { _destinationsData = newValuePtr; } } } /// /// Update the internal state of this instance. /// public readonly void UpdateInternalState() { ForEachDestination((destination, _) => destination[0].UpdateInternalState()); } /// /// Clear all links from the . /// public void ClearLinks() { ForEachDestination((destination, _) => destination[0].Unlink()); unsafe { _destinationsData = (SplitterDestination*)IntPtr.Zero; } } /// /// Initialize a given . /// /// All the to initialize. public static void InitializeSplitters(Span splitters) { foreach (ref SplitterState splitter in splitters) { unsafe { splitter._destinationsData = (SplitterDestination*)IntPtr.Zero; } splitter.DestinationCount = 0; } } } }