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; private delegate void SplitterDestinationAction(SplitterDestination destination, int index); /// /// 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 SplitterDestinationVersion1* _destinationDataV1; /// /// Linked list of . /// private unsafe SplitterDestinationVersion2* _destinationDataV2; /// /// First element of the linked list of splitter destinations data. /// public readonly SplitterDestination Destination { get { unsafe { return new SplitterDestination(_destinationDataV1, _destinationDataV2); } } } /// /// Create a new . /// /// The unique id of this . public SplitterState(int id) : this() { Id = id; } public readonly SplitterDestination GetData(int index) { int i = 0; SplitterDestination result = Destination; while (i < index) { if (result.IsNull) { break; } result = result.Next; i++; } return result; } /// /// Clear the new connection flag. /// public void ClearNewConnectionFlag() { HasNewConnection = false; } /// /// Utility function to apply an action to all . /// /// The action to execute on each elements. private readonly void ForEachDestination(SplitterDestinationAction action) { SplitterDestination temp = Destination; int i = 0; while (true) { if (temp.IsNull) { break; } SplitterDestination next = temp.Next; action(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); SplitterDestination destination = context.GetDestination(destinationId); SetDestination(destination); DestinationCount = destinationCount; for (int i = 1; i < destinationCount; i++) { input.ReadLittleEndian(out destinationId); SplitterDestination nextDestination = context.GetDestination(destinationId); destination.Link(nextDestination); destination = nextDestination; } } if (destinationCount < parameter.DestinationCount) { input.Advance((parameter.DestinationCount - destinationCount) * sizeof(int)); } Debug.Assert(parameter.Id == Id); if (parameter.Id == Id) { SampleRate = parameter.SampleRate; HasNewConnection = true; } } /// /// Set the head of the linked list of . /// /// New destination value. public void SetDestination(SplitterDestination newValue) { unsafe { fixed (SplitterDestinationVersion1* newValuePtr = &newValue.GetV1RefOrNull()) { _destinationDataV1 = newValuePtr; } fixed (SplitterDestinationVersion2* newValuePtr = &newValue.GetV2RefOrNull()) { _destinationDataV2 = newValuePtr; } } } /// /// Update the internal state of this instance. /// public readonly void UpdateInternalState() { ForEachDestination((destination, _) => destination.UpdateInternalState()); } /// /// Clear all links from the . /// public void ClearLinks() { ForEachDestination((destination, _) => destination.Unlink()); unsafe { _destinationDataV1 = null; _destinationDataV2 = null; } } /// /// Initialize a given . /// /// All the to initialize. public static void InitializeSplitters(Span splitters) { foreach (ref SplitterState splitter in splitters) { unsafe { splitter._destinationDataV1 = null; splitter._destinationDataV2 = null; } splitter.DestinationCount = 0; } } } }