mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-06-28 08:56:24 +02:00
Move solution and projects to src
This commit is contained in:
parent
cd124bda58
commit
cee7121058
3466 changed files with 55 additions and 55 deletions
100
src/Ryujinx.Common/AsyncWorkQueue.cs
Normal file
100
src/Ryujinx.Common/AsyncWorkQueue.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public sealed class AsyncWorkQueue<T> : IDisposable
|
||||
{
|
||||
private readonly Thread _workerThread;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private readonly Action<T> _workerAction;
|
||||
private readonly BlockingCollection<T> _queue;
|
||||
|
||||
public bool IsCancellationRequested => _cts.IsCancellationRequested;
|
||||
|
||||
public AsyncWorkQueue(Action<T> callback, string name = null) : this(callback, name, new BlockingCollection<T>())
|
||||
{
|
||||
}
|
||||
|
||||
public AsyncWorkQueue(Action<T> callback, string name, BlockingCollection<T> collection)
|
||||
{
|
||||
_cts = new CancellationTokenSource();
|
||||
_queue = collection;
|
||||
_workerAction = callback;
|
||||
_workerThread = new Thread(DoWork) { Name = name };
|
||||
|
||||
_workerThread.IsBackground = true;
|
||||
_workerThread.Start();
|
||||
}
|
||||
|
||||
private void DoWork()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var item in _queue.GetConsumingEnumerable(_cts.Token))
|
||||
{
|
||||
_workerAction(item);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
_cts.Cancel();
|
||||
}
|
||||
|
||||
public void CancelAfter(int millisecondsDelay)
|
||||
{
|
||||
_cts.CancelAfter(millisecondsDelay);
|
||||
}
|
||||
|
||||
public void CancelAfter(TimeSpan delay)
|
||||
{
|
||||
_cts.CancelAfter(delay);
|
||||
}
|
||||
|
||||
public void Add(T workItem)
|
||||
{
|
||||
_queue.Add(workItem);
|
||||
}
|
||||
|
||||
public void Add(T workItem, CancellationToken cancellationToken)
|
||||
{
|
||||
_queue.Add(workItem, cancellationToken);
|
||||
}
|
||||
|
||||
public bool TryAdd(T workItem)
|
||||
{
|
||||
return _queue.TryAdd(workItem);
|
||||
}
|
||||
|
||||
public bool TryAdd(T workItem, int millisecondsDelay)
|
||||
{
|
||||
return _queue.TryAdd(workItem, millisecondsDelay);
|
||||
}
|
||||
|
||||
public bool TryAdd(T workItem, int millisecondsDelay, CancellationToken cancellationToken)
|
||||
{
|
||||
return _queue.TryAdd(workItem, millisecondsDelay, cancellationToken);
|
||||
}
|
||||
|
||||
public bool TryAdd(T workItem, TimeSpan timeout)
|
||||
{
|
||||
return _queue.TryAdd(workItem, timeout);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_queue.CompleteAdding();
|
||||
_cts.Cancel();
|
||||
_workerThread.Join();
|
||||
|
||||
_queue.Dispose();
|
||||
_cts.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
499
src/Ryujinx.Common/Collections/IntervalTree.cs
Normal file
499
src/Ryujinx.Common/Collections/IntervalTree.cs
Normal file
|
@ -0,0 +1,499 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// An Augmented Interval Tree based off of the "TreeDictionary"'s Red-Black Tree. Allows fast overlap checking of ranges.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key</typeparam>
|
||||
/// <typeparam name="V">Value</typeparam>
|
||||
public class IntervalTree<K, V> : IntrusiveRedBlackTreeImpl<IntervalTreeNode<K, V>> where K : IComparable<K>
|
||||
{
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the values of the interval whose key is <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node value to get</param>
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <returns>Number of values found</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public int Get(K key, ref V[] overlaps)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
IntervalTreeNode<K, V> node = GetNode(key);
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node.Values.Count > overlaps.Length)
|
||||
{
|
||||
Array.Resize(ref overlaps, node.Values.Count);
|
||||
}
|
||||
|
||||
int overlapsCount = 0;
|
||||
foreach (RangeNode<K, V> value in node.Values)
|
||||
{
|
||||
overlaps[overlapsCount++] = value.Value;
|
||||
}
|
||||
|
||||
return overlapsCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the values of the intervals whose start and end keys overlap the given range.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range</param>
|
||||
/// <param name="end">End of the range</param>
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <param name="overlapCount">Index to start writing results into the array. Defaults to 0</param>
|
||||
/// <returns>Number of values found</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="start"/> or <paramref name="end"/> is null</exception>
|
||||
public int Get(K start, K end, ref V[] overlaps, int overlapCount = 0)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(start);
|
||||
ArgumentNullException.ThrowIfNull(end);
|
||||
|
||||
GetValues(Root, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
return overlapCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new interval into the tree whose start is <paramref name="start"/>, end is <paramref name="end"/> and value is <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range to add</param>
|
||||
/// <param name="end">End of the range to insert</param>
|
||||
/// <param name="value">Value to add</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="start"/>, <paramref name="end"/> or <paramref name="value"/> are null</exception>
|
||||
public void Add(K start, K end, V value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(start);
|
||||
ArgumentNullException.ThrowIfNull(end);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
Insert(start, end, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the given <paramref name="value"/> from the tree, searching for it with <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to remove</param>
|
||||
/// <param name="value">Value to remove</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
/// <returns>Number of deleted values</returns>
|
||||
public int Remove(K key, V value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
int removed = Delete(key, value);
|
||||
|
||||
Count -= removed;
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
|
||||
/// </summary>
|
||||
/// <returns>A list of all RangeNodes sorted by Key Order</returns>
|
||||
public List<RangeNode<K, V>> AsList()
|
||||
{
|
||||
List<RangeNode<K, V>> list = new List<RangeNode<K, V>>();
|
||||
|
||||
AddToList(Root, list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods (BST)
|
||||
|
||||
/// <summary>
|
||||
/// Adds all RangeNodes that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
|
||||
/// </summary>
|
||||
/// <param name="node">The node to search for RangeNodes within</param>
|
||||
/// <param name="list">The list to add RangeNodes to</param>
|
||||
private void AddToList(IntervalTreeNode<K, V> node, List<RangeNode<K, V>> list)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddToList(node.Left, list);
|
||||
|
||||
list.AddRange(node.Values);
|
||||
|
||||
AddToList(node.Right, list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to get</param>
|
||||
/// <returns>Node reference in the tree</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private IntervalTreeNode<K, V> GetNode(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
IntervalTreeNode<K, V> node = Root;
|
||||
while (node != null)
|
||||
{
|
||||
int cmp = key.CompareTo(node.Start);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all values that overlap the given start and end keys.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range</param>
|
||||
/// <param name="end">End of the range</param>
|
||||
/// <param name="overlaps">Overlaps array to place results in</param>
|
||||
/// <param name="overlapCount">Overlaps count to update</param>
|
||||
private void GetValues(IntervalTreeNode<K, V> node, K start, K end, ref V[] overlaps, ref int overlapCount)
|
||||
{
|
||||
if (node == null || start.CompareTo(node.Max) >= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GetValues(node.Left, start, end, ref overlaps, ref overlapCount);
|
||||
|
||||
bool endsOnRight = end.CompareTo(node.Start) > 0;
|
||||
if (endsOnRight)
|
||||
{
|
||||
if (start.CompareTo(node.End) < 0)
|
||||
{
|
||||
// Contains this node. Add overlaps to list.
|
||||
foreach (RangeNode<K,V> overlap in node.Values)
|
||||
{
|
||||
if (start.CompareTo(overlap.End) < 0)
|
||||
{
|
||||
if (overlaps.Length >= overlapCount)
|
||||
{
|
||||
Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize);
|
||||
}
|
||||
|
||||
overlaps[overlapCount++] = overlap.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GetValues(node.Right, start, end, ref overlaps, ref overlapCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new node into the tree with a given <paramref name="start"/>, <paramref name="end"/> and <paramref name="value"/>.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range to insert</param>
|
||||
/// <param name="end">End of the range to insert</param>
|
||||
/// <param name="value">Value to insert</param>
|
||||
private void Insert(K start, K end, V value)
|
||||
{
|
||||
IntervalTreeNode<K, V> newNode = BSTInsert(start, end, value);
|
||||
RestoreBalanceAfterInsertion(newNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propagate an increase in max value starting at the given node, heading up the tree.
|
||||
/// This should only be called if the max increases - not for rebalancing or removals.
|
||||
/// </summary>
|
||||
/// <param name="node">The node to start propagating from</param>
|
||||
private void PropagateIncrease(IntervalTreeNode<K, V> node)
|
||||
{
|
||||
K max = node.Max;
|
||||
IntervalTreeNode<K, V> ptr = node;
|
||||
|
||||
while ((ptr = ptr.Parent) != null)
|
||||
{
|
||||
if (max.CompareTo(ptr.Max) > 0)
|
||||
{
|
||||
ptr.Max = max;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propagate recalculating max value starting at the given node, heading up the tree.
|
||||
/// This fully recalculates the max value from all children when there is potential for it to decrease.
|
||||
/// </summary>
|
||||
/// <param name="node">The node to start propagating from</param>
|
||||
private void PropagateFull(IntervalTreeNode<K, V> node)
|
||||
{
|
||||
IntervalTreeNode<K, V> ptr = node;
|
||||
|
||||
do
|
||||
{
|
||||
K max = ptr.End;
|
||||
|
||||
if (ptr.Left != null && ptr.Left.Max.CompareTo(max) > 0)
|
||||
{
|
||||
max = ptr.Left.Max;
|
||||
}
|
||||
|
||||
if (ptr.Right != null && ptr.Right.Max.CompareTo(max) > 0)
|
||||
{
|
||||
max = ptr.Right.Max;
|
||||
}
|
||||
|
||||
ptr.Max = max;
|
||||
} while ((ptr = ptr.Parent) != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insertion Mechanism for the interval tree. Similar to a BST insert, with the start of the range as the key.
|
||||
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="start"/>, and all children in the right subtree are greater than <paramref name="start"/>.
|
||||
/// Each node can contain multiple values, and has an end address which is the maximum of all those values.
|
||||
/// Post insertion, the "max" value of the node and all parents are updated.
|
||||
/// </summary>
|
||||
/// <param name="start">Start of the range to insert</param>
|
||||
/// <param name="end">End of the range to insert</param>
|
||||
/// <param name="value">Value to insert</param>
|
||||
/// <returns>The inserted Node</returns>
|
||||
private IntervalTreeNode<K, V> BSTInsert(K start, K end, V value)
|
||||
{
|
||||
IntervalTreeNode<K, V> parent = null;
|
||||
IntervalTreeNode<K, V> node = Root;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
parent = node;
|
||||
int cmp = start.CompareTo(node.Start);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Values.Add(new RangeNode<K, V>(start, end, value));
|
||||
|
||||
if (end.CompareTo(node.End) > 0)
|
||||
{
|
||||
node.End = end;
|
||||
if (end.CompareTo(node.Max) > 0)
|
||||
{
|
||||
node.Max = end;
|
||||
PropagateIncrease(node);
|
||||
}
|
||||
}
|
||||
|
||||
Count++;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
IntervalTreeNode<K, V> newNode = new IntervalTreeNode<K, V>(start, end, value, parent);
|
||||
if (newNode.Parent == null)
|
||||
{
|
||||
Root = newNode;
|
||||
}
|
||||
else if (start.CompareTo(parent.Start) < 0)
|
||||
{
|
||||
parent.Left = newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = newNode;
|
||||
}
|
||||
|
||||
PropagateIncrease(newNode);
|
||||
Count++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes instances of <paramref name="value"> from the dictionary after searching for it with <paramref name="key">.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to search for</param>
|
||||
/// <param name="value">Value to delete</param>
|
||||
/// <returns>Number of deleted values</returns>
|
||||
private int Delete(K key, V value)
|
||||
{
|
||||
IntervalTreeNode<K, V> nodeToDelete = GetNode(key);
|
||||
|
||||
if (nodeToDelete == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int removed = nodeToDelete.Values.RemoveAll(node => node.Value.Equals(value));
|
||||
|
||||
if (nodeToDelete.Values.Count > 0)
|
||||
{
|
||||
if (removed > 0)
|
||||
{
|
||||
nodeToDelete.End = nodeToDelete.Values.Max(node => node.End);
|
||||
|
||||
// Recalculate max from children and new end.
|
||||
PropagateFull(nodeToDelete);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
IntervalTreeNode<K, V> replacementNode;
|
||||
|
||||
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
|
||||
{
|
||||
replacementNode = nodeToDelete;
|
||||
}
|
||||
else
|
||||
{
|
||||
replacementNode = PredecessorOf(nodeToDelete);
|
||||
}
|
||||
|
||||
IntervalTreeNode<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Parent = ParentOf(replacementNode);
|
||||
}
|
||||
|
||||
if (ParentOf(replacementNode) == null)
|
||||
{
|
||||
Root = tmp;
|
||||
}
|
||||
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
|
||||
{
|
||||
ParentOf(replacementNode).Left = tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentOf(replacementNode).Right = tmp;
|
||||
}
|
||||
|
||||
if (replacementNode != nodeToDelete)
|
||||
{
|
||||
nodeToDelete.Start = replacementNode.Start;
|
||||
nodeToDelete.Values = replacementNode.Values;
|
||||
nodeToDelete.End = replacementNode.End;
|
||||
nodeToDelete.Max = replacementNode.Max;
|
||||
}
|
||||
|
||||
PropagateFull(replacementNode);
|
||||
|
||||
if (tmp != null && ColorOf(replacementNode) == Black)
|
||||
{
|
||||
RestoreBalanceAfterRemoval(tmp);
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void RotateLeft(IntervalTreeNode<K, V> node)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
base.RotateLeft(node);
|
||||
|
||||
PropagateFull(node);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RotateRight(IntervalTreeNode<K, V> node)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
base.RotateRight(node);
|
||||
|
||||
PropagateFull(node);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsKey(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return GetNode(key) != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a value and its start and end keys.
|
||||
/// </summary>
|
||||
/// <typeparam name="K"></typeparam>
|
||||
/// <typeparam name="V"></typeparam>
|
||||
public readonly struct RangeNode<K, V>
|
||||
{
|
||||
public readonly K Start;
|
||||
public readonly K End;
|
||||
public readonly V Value;
|
||||
|
||||
public RangeNode(K start, K end, V value)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a node in the IntervalTree which contains start and end keys of type K, and a value of generic type V.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key type of the node</typeparam>
|
||||
/// <typeparam name="V">Value type of the node</typeparam>
|
||||
public class IntervalTreeNode<K, V> : IntrusiveRedBlackTreeNode<IntervalTreeNode<K, V>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The start of the range.
|
||||
/// </summary>
|
||||
internal K Start;
|
||||
|
||||
/// <summary>
|
||||
/// The end of the range - maximum of all in the Values list.
|
||||
/// </summary>
|
||||
internal K End;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum end value of this node and all its children.
|
||||
/// </summary>
|
||||
internal K Max;
|
||||
|
||||
/// <summary>
|
||||
/// Values contained on the node that shares a common Start value.
|
||||
/// </summary>
|
||||
internal List<RangeNode<K, V>> Values;
|
||||
|
||||
internal IntervalTreeNode(K start, K end, V value, IntervalTreeNode<K, V> parent)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
Max = end;
|
||||
Values = new List<RangeNode<K, V>> { new RangeNode<K, V>(start, end, value) };
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
285
src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs
Normal file
285
src/Ryujinx.Common/Collections/IntrusiveRedBlackTree.cs
Normal file
|
@ -0,0 +1,285 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Derived node type</typeparam>
|
||||
public class IntrusiveRedBlackTree<T> : IntrusiveRedBlackTreeImpl<T> where T : IntrusiveRedBlackTreeNode<T>, IComparable<T>
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new node into the tree.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to be added</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
public void Add(T node)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
Insert(node);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a node from the tree.
|
||||
/// </summary>
|
||||
/// <param name="node">Note to be removed</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
public void Remove(T node)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
if (Delete(node) != null)
|
||||
{
|
||||
Count--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the node that is considered equal to the specified node by the comparator.
|
||||
/// </summary>
|
||||
/// <param name="searchNode">Node to compare with</param>
|
||||
/// <returns>Node that is equal to <paramref name="searchNode"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="searchNode"/> is null</exception>
|
||||
public T GetNode(T searchNode)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(searchNode);
|
||||
|
||||
T node = Root;
|
||||
while (node != null)
|
||||
{
|
||||
int cmp = searchNode.CompareTo(node);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods (BST)
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new node into the tree.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to be inserted</param>
|
||||
private void Insert(T node)
|
||||
{
|
||||
T newNode = BSTInsert(node);
|
||||
RestoreBalanceAfterInsertion(newNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insertion Mechanism for a Binary Search Tree (BST).
|
||||
/// <br></br>
|
||||
/// Iterates the tree starting from the root and inserts a new node
|
||||
/// where all children in the left subtree are less than <paramref name="newNode"/>,
|
||||
/// and all children in the right subtree are greater than <paramref name="newNode"/>.
|
||||
/// </summary>
|
||||
/// <param name="newNode">Node to be inserted</param>
|
||||
/// <returns>The inserted Node</returns>
|
||||
private T BSTInsert(T newNode)
|
||||
{
|
||||
T parent = null;
|
||||
T node = Root;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
parent = node;
|
||||
int cmp = newNode.CompareTo(node);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
newNode.Parent = parent;
|
||||
if (parent == null)
|
||||
{
|
||||
Root = newNode;
|
||||
}
|
||||
else if (newNode.CompareTo(parent) < 0)
|
||||
{
|
||||
parent.Left = newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = newNode;
|
||||
}
|
||||
Count++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes <paramref name="nodeToDelete"/> from the tree, if it exists.
|
||||
/// </summary>
|
||||
/// <param name="nodeToDelete">Node to be removed</param>
|
||||
/// <returns>The deleted Node</returns>
|
||||
private T Delete(T nodeToDelete)
|
||||
{
|
||||
if (nodeToDelete == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
T old = nodeToDelete;
|
||||
T child;
|
||||
T parent;
|
||||
bool color;
|
||||
|
||||
if (LeftOf(nodeToDelete) == null)
|
||||
{
|
||||
child = RightOf(nodeToDelete);
|
||||
}
|
||||
else if (RightOf(nodeToDelete) == null)
|
||||
{
|
||||
child = LeftOf(nodeToDelete);
|
||||
}
|
||||
else
|
||||
{
|
||||
T element = Minimum(RightOf(nodeToDelete));
|
||||
|
||||
child = RightOf(element);
|
||||
parent = ParentOf(element);
|
||||
color = ColorOf(element);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
Root = child;
|
||||
}
|
||||
else if (element == LeftOf(parent))
|
||||
{
|
||||
parent.Left = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = child;
|
||||
}
|
||||
|
||||
if (ParentOf(element) == old)
|
||||
{
|
||||
parent = element;
|
||||
}
|
||||
|
||||
element.Color = old.Color;
|
||||
element.Left = old.Left;
|
||||
element.Right = old.Right;
|
||||
element.Parent = old.Parent;
|
||||
|
||||
if (ParentOf(old) == null)
|
||||
{
|
||||
Root = element;
|
||||
}
|
||||
else if (old == LeftOf(ParentOf(old)))
|
||||
{
|
||||
ParentOf(old).Left = element;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentOf(old).Right = element;
|
||||
}
|
||||
|
||||
LeftOf(old).Parent = element;
|
||||
|
||||
if (RightOf(old) != null)
|
||||
{
|
||||
RightOf(old).Parent = element;
|
||||
}
|
||||
|
||||
if (child != null && color == Black)
|
||||
{
|
||||
RestoreBalanceAfterRemoval(child);
|
||||
}
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
parent = ParentOf(nodeToDelete);
|
||||
color = ColorOf(nodeToDelete);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
child.Parent = parent;
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
Root = child;
|
||||
}
|
||||
else if (nodeToDelete == LeftOf(parent))
|
||||
{
|
||||
parent.Left = child;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = child;
|
||||
}
|
||||
|
||||
if (child != null && color == Black)
|
||||
{
|
||||
RestoreBalanceAfterRemoval(child);
|
||||
}
|
||||
|
||||
return old;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public static class IntrusiveRedBlackTreeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve the node that is considered equal to the key by the comparator.
|
||||
/// </summary>
|
||||
/// <param name="tree">Tree to search at</param>
|
||||
/// <param name="key">Key of the node to be found</param>
|
||||
/// <returns>Node that is equal to <paramref name="key"/></returns>
|
||||
public static N GetNodeByKey<N, K>(this IntrusiveRedBlackTree<N> tree, K key)
|
||||
where N : IntrusiveRedBlackTreeNode<N>, IComparable<N>, IComparable<K>
|
||||
where K : struct
|
||||
{
|
||||
N node = tree.RootNode;
|
||||
while (node != null)
|
||||
{
|
||||
int cmp = node.CompareTo(key);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
354
src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs
Normal file
354
src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeImpl.cs
Normal file
|
@ -0,0 +1,354 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Tree that provides the ability for O(logN) lookups for keys that exist in the tree, and O(logN) lookups for keys immediately greater than or less than a specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Derived node type</typeparam>
|
||||
public class IntrusiveRedBlackTreeImpl<T> where T : IntrusiveRedBlackTreeNode<T>
|
||||
{
|
||||
protected const bool Black = true;
|
||||
protected const bool Red = false;
|
||||
protected T Root = null;
|
||||
|
||||
internal T RootNode => Root;
|
||||
|
||||
/// <summary>
|
||||
/// Number of nodes on the tree.
|
||||
/// </summary>
|
||||
public int Count { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Removes all nodes on the tree.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Root = null;
|
||||
Count = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the node whose key is immediately greater than <paramref name="node"/>.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to find the successor of</param>
|
||||
/// <returns>Successor of <paramref name="node"/></returns>
|
||||
internal static T SuccessorOf(T node)
|
||||
{
|
||||
if (node.Right != null)
|
||||
{
|
||||
return Minimum(node.Right);
|
||||
}
|
||||
T parent = node.Parent;
|
||||
while (parent != null && node == parent.Right)
|
||||
{
|
||||
node = parent;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the node whose key is immediately less than <paramref name="node"/>.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to find the predecessor of</param>
|
||||
/// <returns>Predecessor of <paramref name="node"/></returns>
|
||||
internal static T PredecessorOf(T node)
|
||||
{
|
||||
if (node.Left != null)
|
||||
{
|
||||
return Maximum(node.Left);
|
||||
}
|
||||
T parent = node.Parent;
|
||||
while (parent != null && node == parent.Left)
|
||||
{
|
||||
node = parent;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node with the largest key where <paramref name="node"/> is considered the root node.
|
||||
/// </summary>
|
||||
/// <param name="node">Root node</param>
|
||||
/// <returns>Node with the maximum key in the tree of <paramref name="node"/></returns>
|
||||
protected static T Maximum(T node)
|
||||
{
|
||||
T tmp = node;
|
||||
while (tmp.Right != null)
|
||||
{
|
||||
tmp = tmp.Right;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node with the smallest key where <paramref name="node"/> is considered the root node.
|
||||
/// </summary>
|
||||
/// <param name="node">Root node</param>
|
||||
/// <returns>Node with the minimum key in the tree of <paramref name="node"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||
protected static T Minimum(T node)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(node);
|
||||
|
||||
T tmp = node;
|
||||
while (tmp.Left != null)
|
||||
{
|
||||
tmp = tmp.Left;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
protected void RestoreBalanceAfterRemoval(T balanceNode)
|
||||
{
|
||||
T ptr = balanceNode;
|
||||
|
||||
while (ptr != Root && ColorOf(ptr) == Black)
|
||||
{
|
||||
if (ptr == LeftOf(ParentOf(ptr)))
|
||||
{
|
||||
T sibling = RightOf(ParentOf(ptr));
|
||||
|
||||
if (ColorOf(sibling) == Red)
|
||||
{
|
||||
SetColor(sibling, Black);
|
||||
SetColor(ParentOf(ptr), Red);
|
||||
RotateLeft(ParentOf(ptr));
|
||||
sibling = RightOf(ParentOf(ptr));
|
||||
}
|
||||
if (ColorOf(LeftOf(sibling)) == Black && ColorOf(RightOf(sibling)) == Black)
|
||||
{
|
||||
SetColor(sibling, Red);
|
||||
ptr = ParentOf(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ColorOf(RightOf(sibling)) == Black)
|
||||
{
|
||||
SetColor(LeftOf(sibling), Black);
|
||||
SetColor(sibling, Red);
|
||||
RotateRight(sibling);
|
||||
sibling = RightOf(ParentOf(ptr));
|
||||
}
|
||||
SetColor(sibling, ColorOf(ParentOf(ptr)));
|
||||
SetColor(ParentOf(ptr), Black);
|
||||
SetColor(RightOf(sibling), Black);
|
||||
RotateLeft(ParentOf(ptr));
|
||||
ptr = Root;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
T sibling = LeftOf(ParentOf(ptr));
|
||||
|
||||
if (ColorOf(sibling) == Red)
|
||||
{
|
||||
SetColor(sibling, Black);
|
||||
SetColor(ParentOf(ptr), Red);
|
||||
RotateRight(ParentOf(ptr));
|
||||
sibling = LeftOf(ParentOf(ptr));
|
||||
}
|
||||
if (ColorOf(RightOf(sibling)) == Black && ColorOf(LeftOf(sibling)) == Black)
|
||||
{
|
||||
SetColor(sibling, Red);
|
||||
ptr = ParentOf(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ColorOf(LeftOf(sibling)) == Black)
|
||||
{
|
||||
SetColor(RightOf(sibling), Black);
|
||||
SetColor(sibling, Red);
|
||||
RotateLeft(sibling);
|
||||
sibling = LeftOf(ParentOf(ptr));
|
||||
}
|
||||
SetColor(sibling, ColorOf(ParentOf(ptr)));
|
||||
SetColor(ParentOf(ptr), Black);
|
||||
SetColor(LeftOf(sibling), Black);
|
||||
RotateRight(ParentOf(ptr));
|
||||
ptr = Root;
|
||||
}
|
||||
}
|
||||
}
|
||||
SetColor(ptr, Black);
|
||||
}
|
||||
|
||||
protected void RestoreBalanceAfterInsertion(T balanceNode)
|
||||
{
|
||||
SetColor(balanceNode, Red);
|
||||
while (balanceNode != null && balanceNode != Root && ColorOf(ParentOf(balanceNode)) == Red)
|
||||
{
|
||||
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
|
||||
{
|
||||
T sibling = RightOf(ParentOf(ParentOf(balanceNode)));
|
||||
|
||||
if (ColorOf(sibling) == Red)
|
||||
{
|
||||
SetColor(ParentOf(balanceNode), Black);
|
||||
SetColor(sibling, Black);
|
||||
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||
balanceNode = ParentOf(ParentOf(balanceNode));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (balanceNode == RightOf(ParentOf(balanceNode)))
|
||||
{
|
||||
balanceNode = ParentOf(balanceNode);
|
||||
RotateLeft(balanceNode);
|
||||
}
|
||||
SetColor(ParentOf(balanceNode), Black);
|
||||
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||
RotateRight(ParentOf(ParentOf(balanceNode)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
T sibling = LeftOf(ParentOf(ParentOf(balanceNode)));
|
||||
|
||||
if (ColorOf(sibling) == Red)
|
||||
{
|
||||
SetColor(ParentOf(balanceNode), Black);
|
||||
SetColor(sibling, Black);
|
||||
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||
balanceNode = ParentOf(ParentOf(balanceNode));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (balanceNode == LeftOf(ParentOf(balanceNode)))
|
||||
{
|
||||
balanceNode = ParentOf(balanceNode);
|
||||
RotateRight(balanceNode);
|
||||
}
|
||||
SetColor(ParentOf(balanceNode), Black);
|
||||
SetColor(ParentOf(ParentOf(balanceNode)), Red);
|
||||
RotateLeft(ParentOf(ParentOf(balanceNode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
SetColor(Root, Black);
|
||||
}
|
||||
|
||||
protected virtual void RotateLeft(T node)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
T right = RightOf(node);
|
||||
node.Right = LeftOf(right);
|
||||
if (node.Right != null)
|
||||
{
|
||||
node.Right.Parent = node;
|
||||
}
|
||||
T nodeParent = ParentOf(node);
|
||||
right.Parent = nodeParent;
|
||||
if (nodeParent == null)
|
||||
{
|
||||
Root = right;
|
||||
}
|
||||
else if (node == LeftOf(nodeParent))
|
||||
{
|
||||
nodeParent.Left = right;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeParent.Right = right;
|
||||
}
|
||||
right.Left = node;
|
||||
node.Parent = right;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void RotateRight(T node)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
T left = LeftOf(node);
|
||||
node.Left = RightOf(left);
|
||||
if (node.Left != null)
|
||||
{
|
||||
node.Left.Parent = node;
|
||||
}
|
||||
T nodeParent = ParentOf(node);
|
||||
left.Parent = nodeParent;
|
||||
if (nodeParent == null)
|
||||
{
|
||||
Root = left;
|
||||
}
|
||||
else if (node == RightOf(nodeParent))
|
||||
{
|
||||
nodeParent.Right = left;
|
||||
}
|
||||
else
|
||||
{
|
||||
nodeParent.Left = left;
|
||||
}
|
||||
left.Right = node;
|
||||
node.Parent = left;
|
||||
}
|
||||
}
|
||||
|
||||
#region Safety-Methods
|
||||
|
||||
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against NullReferenceExceptions.
|
||||
|
||||
/// <summary>
|
||||
/// Returns the color of <paramref name="node"/>, or Black if it is null.
|
||||
/// </summary>
|
||||
/// <param name="node">Node</param>
|
||||
/// <returns>The boolean color of <paramref name="node"/>, or black if null</returns>
|
||||
protected static bool ColorOf(T node)
|
||||
{
|
||||
return node == null || node.Color;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of <paramref name="node"/> node to <paramref name="color"/>.
|
||||
/// <br></br>
|
||||
/// This method does nothing if <paramref name="node"/> is null.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to set the color of</param>
|
||||
/// <param name="color">Color (Boolean)</param>
|
||||
protected static void SetColor(T node, bool color)
|
||||
{
|
||||
if (node != null)
|
||||
{
|
||||
node.Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method returns the left node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to retrieve the left child from</param>
|
||||
/// <returns>Left child of <paramref name="node"/></returns>
|
||||
protected static T LeftOf(T node)
|
||||
{
|
||||
return node?.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method returns the right node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to retrieve the right child from</param>
|
||||
/// <returns>Right child of <paramref name="node"/></returns>
|
||||
protected static T RightOf(T node)
|
||||
{
|
||||
return node?.Right;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the parent node of <paramref name="node"/>, or null if <paramref name="node"/> is null.
|
||||
/// </summary>
|
||||
/// <param name="node">Node to retrieve the parent from</param>
|
||||
/// <returns>Parent of <paramref name="node"/></returns>
|
||||
protected static T ParentOf(T node)
|
||||
{
|
||||
return node?.Parent;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
16
src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs
Normal file
16
src/Ryujinx.Common/Collections/IntrusiveRedBlackTreeNode.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Ryujinx.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a node in the Red-Black Tree.
|
||||
/// </summary>
|
||||
public class IntrusiveRedBlackTreeNode<T> where T : IntrusiveRedBlackTreeNode<T>
|
||||
{
|
||||
internal bool Color = true;
|
||||
internal T Left;
|
||||
internal T Right;
|
||||
internal T Parent;
|
||||
|
||||
public T Predecessor => IntrusiveRedBlackTreeImpl<T>.PredecessorOf((T)this);
|
||||
public T Successor => IntrusiveRedBlackTreeImpl<T>.SuccessorOf((T)this);
|
||||
}
|
||||
}
|
617
src/Ryujinx.Common/Collections/TreeDictionary.cs
Normal file
617
src/Ryujinx.Common/Collections/TreeDictionary.cs
Normal file
|
@ -0,0 +1,617 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Ryujinx.Common.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Dictionary that provides the ability for O(logN) Lookups for keys that exist in the Dictionary, and O(logN) lookups for keys immediately greater than or less than a specified key.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key</typeparam>
|
||||
/// <typeparam name="V">Value</typeparam>
|
||||
public class TreeDictionary<K, V> : IntrusiveRedBlackTreeImpl<Node<K, V>>, IDictionary<K, V> where K : IComparable<K>
|
||||
{
|
||||
#region Public Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value of the node whose key is <paramref name="key"/>, or the default value if no such node exists.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node value to get</param>
|
||||
/// <returns>Value associated w/ <paramref name="key"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public V Get(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = GetNode(key);
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
return node.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new node into the tree whose key is <paramref name="key"/> key and value is <paramref name="value"/>.
|
||||
/// <br></br>
|
||||
/// <b>Note:</b> Adding the same key multiple times will cause the value for that key to be overwritten.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to add</param>
|
||||
/// <param name="value">Value of the node to add</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> or <paramref name="value"/> are null</exception>
|
||||
public void Add(K key, V value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
Insert(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the node whose key is <paramref name="key"/> from the tree.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to remove</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public void Remove(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (Delete(key) != null)
|
||||
{
|
||||
Count--;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the value whose key is equal to or immediately less than <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for which to find the floor value of</param>
|
||||
/// <returns>Key of node immediately less than <paramref name="key"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public K Floor(K key)
|
||||
{
|
||||
Node<K, V> node = FloorNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
return node.Key;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node whose key is equal to or immediately greater than <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for which to find the ceiling node of</param>
|
||||
/// <returns>Key of node immediately greater than <paramref name="key"/></returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
public K Ceiling(K key)
|
||||
{
|
||||
Node<K, V> node = CeilingNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
return node.Key;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the value whose key is immediately greater than <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to find the successor of</param>
|
||||
/// <returns>Value</returns>
|
||||
public K SuccessorOf(K key)
|
||||
{
|
||||
Node<K, V> node = GetNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
Node<K, V> successor = SuccessorOf(node);
|
||||
|
||||
return successor != null ? successor.Key : default;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the value whose key is immediately less than <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to find the predecessor of</param>
|
||||
/// <returns>Value</returns>
|
||||
public K PredecessorOf(K key)
|
||||
{
|
||||
Node<K, V> node = GetNode(key);
|
||||
if (node != null)
|
||||
{
|
||||
Node<K, V> predecessor = PredecessorOf(node);
|
||||
|
||||
return predecessor != null ? predecessor.Key : default;
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the nodes in the dictionary as key/value pairs into <paramref name="list"/>.
|
||||
/// <br></br>
|
||||
/// The key/value pairs will be added in Level Order.
|
||||
/// </summary>
|
||||
/// <param name="list">List to add the tree pairs into</param>
|
||||
public List<KeyValuePair<K, V>> AsLevelOrderList()
|
||||
{
|
||||
List<KeyValuePair<K, V>> list = new List<KeyValuePair<K, V>>();
|
||||
|
||||
Queue<Node<K, V>> nodes = new Queue<Node<K, V>>();
|
||||
|
||||
if (this.Root != null)
|
||||
{
|
||||
nodes.Enqueue(this.Root);
|
||||
}
|
||||
while (nodes.TryDequeue(out Node<K, V> node))
|
||||
{
|
||||
list.Add(new KeyValuePair<K, V>(node.Key, node.Value));
|
||||
if (node.Left != null)
|
||||
{
|
||||
nodes.Enqueue(node.Left);
|
||||
}
|
||||
if (node.Right != null)
|
||||
{
|
||||
nodes.Enqueue(node.Right);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the nodes in the dictionary into <paramref name="list"/>.
|
||||
/// </summary>
|
||||
/// <returns>A list of all KeyValuePairs sorted by Key Order</returns>
|
||||
public List<KeyValuePair<K, V>> AsList()
|
||||
{
|
||||
List<KeyValuePair<K, V>> list = new List<KeyValuePair<K, V>>();
|
||||
|
||||
AddToList(Root, list);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods (BST)
|
||||
|
||||
/// <summary>
|
||||
/// Adds all nodes that are children of or contained within <paramref name="node"/> into <paramref name="list"/>, in Key Order.
|
||||
/// </summary>
|
||||
/// <param name="node">The node to search for nodes within</param>
|
||||
/// <param name="list">The list to add node to</param>
|
||||
private void AddToList(Node<K, V> node, List<KeyValuePair<K, V>> list)
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AddToList(node.Left, list);
|
||||
|
||||
list.Add(new KeyValuePair<K, V>(node.Key, node.Value));
|
||||
|
||||
AddToList(node.Right, list);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve the node reference whose key is <paramref name="key"/>, or null if no such node exists.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to get</param>
|
||||
/// <returns>Node reference in the tree</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> GetNode(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = Root;
|
||||
while (node != null)
|
||||
{
|
||||
int cmp = key.CompareTo(node.Key);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return node;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new node into the tree whose key is <paramref name="key"/> and value is <paramref name="value"/>.
|
||||
/// <br></br>
|
||||
/// Adding the same key multiple times will overwrite the previous value.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to insert</param>
|
||||
/// <param name="value">Value of the node to insert</param>
|
||||
private void Insert(K key, V value)
|
||||
{
|
||||
Node<K, V> newNode = BSTInsert(key, value);
|
||||
RestoreBalanceAfterInsertion(newNode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Insertion Mechanism for a Binary Search Tree (BST).
|
||||
/// <br></br>
|
||||
/// Iterates the tree starting from the root and inserts a new node where all children in the left subtree are less than <paramref name="key"/>, and all children in the right subtree are greater than <paramref name="key"/>.
|
||||
/// <br></br>
|
||||
/// <b>Note: </b> If a node whose key is <paramref name="key"/> already exists, it's value will be overwritten.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to insert</param>
|
||||
/// <param name="value">Value of the node to insert</param>
|
||||
/// <returns>The inserted Node</returns>
|
||||
private Node<K, V> BSTInsert(K key, V value)
|
||||
{
|
||||
Node<K, V> parent = null;
|
||||
Node<K, V> node = Root;
|
||||
|
||||
while (node != null)
|
||||
{
|
||||
parent = node;
|
||||
int cmp = key.CompareTo(node.Key);
|
||||
if (cmp < 0)
|
||||
{
|
||||
node = node.Left;
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
node = node.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
node.Value = value;
|
||||
return node;
|
||||
}
|
||||
}
|
||||
Node<K, V> newNode = new Node<K, V>(key, value, parent);
|
||||
if (newNode.Parent == null)
|
||||
{
|
||||
Root = newNode;
|
||||
}
|
||||
else if (key.CompareTo(parent.Key) < 0)
|
||||
{
|
||||
parent.Left = newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.Right = newNode;
|
||||
}
|
||||
Count++;
|
||||
return newNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes <paramref name="key"/> from the dictionary, if it exists.
|
||||
/// </summary>
|
||||
/// <param name="key">Key of the node to delete</param>
|
||||
/// <returns>The deleted Node</returns>
|
||||
private Node<K, V> Delete(K key)
|
||||
{
|
||||
// O(1) Retrieval
|
||||
Node<K, V> nodeToDelete = GetNode(key);
|
||||
|
||||
if (nodeToDelete == null) return null;
|
||||
|
||||
Node<K, V> replacementNode;
|
||||
|
||||
if (LeftOf(nodeToDelete) == null || RightOf(nodeToDelete) == null)
|
||||
{
|
||||
replacementNode = nodeToDelete;
|
||||
}
|
||||
else
|
||||
{
|
||||
replacementNode = PredecessorOf(nodeToDelete);
|
||||
}
|
||||
|
||||
Node<K, V> tmp = LeftOf(replacementNode) ?? RightOf(replacementNode);
|
||||
|
||||
if (tmp != null)
|
||||
{
|
||||
tmp.Parent = ParentOf(replacementNode);
|
||||
}
|
||||
|
||||
if (ParentOf(replacementNode) == null)
|
||||
{
|
||||
Root = tmp;
|
||||
}
|
||||
else if (replacementNode == LeftOf(ParentOf(replacementNode)))
|
||||
{
|
||||
ParentOf(replacementNode).Left = tmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentOf(replacementNode).Right = tmp;
|
||||
}
|
||||
|
||||
if (replacementNode != nodeToDelete)
|
||||
{
|
||||
nodeToDelete.Key = replacementNode.Key;
|
||||
nodeToDelete.Value = replacementNode.Value;
|
||||
}
|
||||
|
||||
if (tmp != null && ColorOf(replacementNode) == Black)
|
||||
{
|
||||
RestoreBalanceAfterRemoval(tmp);
|
||||
}
|
||||
|
||||
return replacementNode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node whose key immediately less than or equal to <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for which to find the floor node of</param>
|
||||
/// <returns>Node whose key is immediately less than or equal to <paramref name="key"/>, or null if no such node is found.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> FloorNode(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> tmp = Root;
|
||||
|
||||
while (tmp != null)
|
||||
{
|
||||
int cmp = key.CompareTo(tmp.Key);
|
||||
if (cmp > 0)
|
||||
{
|
||||
if (tmp.Right != null)
|
||||
{
|
||||
tmp = tmp.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
else if (cmp < 0)
|
||||
{
|
||||
if (tmp.Left != null)
|
||||
{
|
||||
tmp = tmp.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
Node<K, V> parent = tmp.Parent;
|
||||
Node<K, V> ptr = tmp;
|
||||
while (parent != null && ptr == parent.Left)
|
||||
{
|
||||
ptr = parent;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the node whose key is immediately greater than or equal to than <paramref name="key"/>.
|
||||
/// </summary>
|
||||
/// <param name="key">Key for which to find the ceiling node of</param>
|
||||
/// <returns>Node whose key is immediately greater than or equal to <paramref name="key"/>, or null if no such node is found.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="key"/> is null</exception>
|
||||
private Node<K, V> CeilingNode(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> tmp = Root;
|
||||
|
||||
while (tmp != null)
|
||||
{
|
||||
int cmp = key.CompareTo(tmp.Key);
|
||||
if (cmp < 0)
|
||||
{
|
||||
if (tmp.Left != null)
|
||||
{
|
||||
tmp = tmp.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
else if (cmp > 0)
|
||||
{
|
||||
if (tmp.Right != null)
|
||||
{
|
||||
tmp = tmp.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
Node<K, V> parent = tmp.Parent;
|
||||
Node<K, V> ptr = tmp;
|
||||
while (parent != null && ptr == parent.Right)
|
||||
{
|
||||
ptr = parent;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Interface Implementations
|
||||
|
||||
// Method descriptions are not provided as they are already included as part of the interface.
|
||||
public bool ContainsKey(K key)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return GetNode(key) != null;
|
||||
}
|
||||
|
||||
bool IDictionary<K, V>.Remove(K key)
|
||||
{
|
||||
int count = Count;
|
||||
Remove(key);
|
||||
return count > Count;
|
||||
}
|
||||
|
||||
public bool TryGetValue(K key, [MaybeNullWhen(false)] out V value)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
Node<K, V> node = GetNode(key);
|
||||
value = node != null ? node.Value : default;
|
||||
return node != null;
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<K, V> item)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(item.Key);
|
||||
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<K, V> item)
|
||||
{
|
||||
if (item.Key == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Node<K, V> node = GetNode(item.Key);
|
||||
if (node != null)
|
||||
{
|
||||
return node.Key.Equals(item.Key) && node.Value.Equals(item.Value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
|
||||
{
|
||||
if (arrayIndex < 0 || array.Length - arrayIndex < this.Count)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
SortedList<K, V> list = GetKeyValues();
|
||||
|
||||
int offset = 0;
|
||||
|
||||
for (int i = arrayIndex; i < array.Length && offset < list.Count; i++)
|
||||
{
|
||||
array[i] = new KeyValuePair<K, V>(list.Keys[i], list.Values[i]);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<K, V> item)
|
||||
{
|
||||
Node<K, V> node = GetNode(item.Key);
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node.Value.Equals(item.Value))
|
||||
{
|
||||
int count = Count;
|
||||
Remove(item.Key);
|
||||
return count > Count;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
|
||||
{
|
||||
return GetKeyValues().GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetKeyValues().GetEnumerator();
|
||||
}
|
||||
|
||||
public ICollection<K> Keys => GetKeyValues().Keys;
|
||||
|
||||
public ICollection<V> Values => GetKeyValues().Values;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public V this[K key]
|
||||
{
|
||||
get => Get(key);
|
||||
set => Add(key, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Interface Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sorted list of all the node keys / values in the tree.
|
||||
/// </summary>
|
||||
/// <returns>List of node keys</returns>
|
||||
private SortedList<K, V> GetKeyValues()
|
||||
{
|
||||
SortedList<K, V> set = new SortedList<K, V>();
|
||||
Queue<Node<K, V>> queue = new Queue<Node<K, V>>();
|
||||
if (Root != null)
|
||||
{
|
||||
queue.Enqueue(Root);
|
||||
}
|
||||
|
||||
while (queue.TryDequeue(out Node<K, V> node))
|
||||
{
|
||||
set.Add(node.Key, node.Value);
|
||||
if (null != node.Left)
|
||||
{
|
||||
queue.Enqueue(node.Left);
|
||||
}
|
||||
if (null != node.Right)
|
||||
{
|
||||
queue.Enqueue(node.Right);
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a node in the TreeDictionary which contains a key and value of generic type K and V, respectively.
|
||||
/// </summary>
|
||||
/// <typeparam name="K">Key of the node</typeparam>
|
||||
/// <typeparam name="V">Value of the node</typeparam>
|
||||
public class Node<K, V> : IntrusiveRedBlackTreeNode<Node<K, V>> where K : IComparable<K>
|
||||
{
|
||||
internal K Key;
|
||||
internal V Value;
|
||||
|
||||
internal Node(K key, V value, Node<K, V> parent)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
Parent = parent;
|
||||
}
|
||||
}
|
||||
}
|
16
src/Ryujinx.Common/Configuration/AntiAliasing.cs
Normal file
16
src/Ryujinx.Common/Configuration/AntiAliasing.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AntiAliasing>))]
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
Fxaa,
|
||||
SmaaLow,
|
||||
SmaaMedium,
|
||||
SmaaHigh,
|
||||
SmaaUltra
|
||||
}
|
||||
}
|
149
src/Ryujinx.Common/Configuration/AppDataManager.cs
Normal file
149
src/Ryujinx.Common/Configuration/AppDataManager.cs
Normal file
|
@ -0,0 +1,149 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public static class AppDataManager
|
||||
{
|
||||
public const string DefaultBaseDir = "Ryujinx";
|
||||
public const string DefaultPortableDir = "portable";
|
||||
|
||||
// The following 3 are always part of Base Directory
|
||||
private const string GamesDir = "games";
|
||||
private const string ProfilesDir = "profiles";
|
||||
private const string KeysDir = "system";
|
||||
|
||||
public enum LaunchMode
|
||||
{
|
||||
UserProfile,
|
||||
Portable,
|
||||
Custom
|
||||
}
|
||||
|
||||
public static LaunchMode Mode { get; private set; }
|
||||
|
||||
public static string BaseDirPath { get; private set; }
|
||||
public static string GamesDirPath { get; private set; }
|
||||
public static string ProfilesDirPath { get; private set; }
|
||||
public static string KeysDirPath { get; private set; }
|
||||
public static string KeysDirPathUser { get; }
|
||||
|
||||
public const string DefaultNandDir = "bis";
|
||||
public const string DefaultSdcardDir = "sdcard";
|
||||
private const string DefaultModsDir = "mods";
|
||||
|
||||
public static string CustomModsPath { get; set; }
|
||||
public static string CustomSdModsPath {get; set; }
|
||||
public static string CustomNandPath { get; set; } // TODO: Actually implement this into VFS
|
||||
public static string CustomSdCardPath { get; set; } // TODO: Actually implement this into VFS
|
||||
|
||||
static AppDataManager()
|
||||
{
|
||||
KeysDirPathUser = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".switch");
|
||||
}
|
||||
|
||||
public static void Initialize(string baseDirPath)
|
||||
{
|
||||
string appDataPath;
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support");
|
||||
}
|
||||
else
|
||||
{
|
||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
|
||||
if (appDataPath.Length == 0)
|
||||
{
|
||||
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
}
|
||||
|
||||
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||
|
||||
if (Directory.Exists(portablePath))
|
||||
{
|
||||
BaseDirPath = portablePath;
|
||||
Mode = LaunchMode.Portable;
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseDirPath = userProfilePath;
|
||||
Mode = LaunchMode.UserProfile;
|
||||
}
|
||||
|
||||
if (baseDirPath != null && baseDirPath != userProfilePath)
|
||||
{
|
||||
if (!Directory.Exists(baseDirPath))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Custom Data Directory '{baseDirPath}' does not exist. Falling back to {Mode}...");
|
||||
}
|
||||
else
|
||||
{
|
||||
BaseDirPath = baseDirPath;
|
||||
Mode = LaunchMode.Custom;
|
||||
}
|
||||
}
|
||||
|
||||
BaseDirPath = Path.GetFullPath(BaseDirPath); // convert relative paths
|
||||
|
||||
// NOTE: Moves the Ryujinx folder in `~/.config` to `~/Library/Application Support` if one is found
|
||||
// and a Ryujinx folder does not already exist in Application Support.
|
||||
// Also creates a symlink from `~/.config/Ryujinx` to `~/Library/Application Support/Ryujinx` to preserve backwards compatibility.
|
||||
// This should be removed in the future.
|
||||
if (OperatingSystem.IsMacOS() && Mode == LaunchMode.UserProfile)
|
||||
{
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.Delete(oldConfigPath, true);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
}
|
||||
|
||||
SetupBasePaths();
|
||||
}
|
||||
|
||||
private static void SetupBasePaths()
|
||||
{
|
||||
Directory.CreateDirectory(BaseDirPath);
|
||||
Directory.CreateDirectory(GamesDirPath = Path.Combine(BaseDirPath, GamesDir));
|
||||
Directory.CreateDirectory(ProfilesDirPath = Path.Combine(BaseDirPath, ProfilesDir));
|
||||
Directory.CreateDirectory(KeysDirPath = Path.Combine(BaseDirPath, KeysDir));
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
if (file.Name == ".DS_Store")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo subDir in subDirs)
|
||||
{
|
||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
||||
}
|
||||
}
|
63
src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs
Normal file
63
src/Ryujinx.Common/Configuration/AspectRatioExtensions.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<AspectRatio>))]
|
||||
public enum AspectRatio
|
||||
{
|
||||
Fixed4x3,
|
||||
Fixed16x9,
|
||||
Fixed16x10,
|
||||
Fixed21x9,
|
||||
Fixed32x9,
|
||||
Stretched
|
||||
}
|
||||
|
||||
public static class AspectRatioExtensions
|
||||
{
|
||||
public static float ToFloat(this AspectRatio aspectRatio)
|
||||
{
|
||||
return aspectRatio.ToFloatX() / aspectRatio.ToFloatY();
|
||||
}
|
||||
|
||||
public static float ToFloatX(this AspectRatio aspectRatio)
|
||||
{
|
||||
return aspectRatio switch
|
||||
{
|
||||
AspectRatio.Fixed4x3 => 4.0f,
|
||||
AspectRatio.Fixed16x9 => 16.0f,
|
||||
AspectRatio.Fixed16x10 => 16.0f,
|
||||
AspectRatio.Fixed21x9 => 21.0f,
|
||||
AspectRatio.Fixed32x9 => 32.0f,
|
||||
_ => 16.0f
|
||||
};
|
||||
}
|
||||
|
||||
public static float ToFloatY(this AspectRatio aspectRatio)
|
||||
{
|
||||
return aspectRatio switch
|
||||
{
|
||||
AspectRatio.Fixed4x3 => 3.0f,
|
||||
AspectRatio.Fixed16x9 => 9.0f,
|
||||
AspectRatio.Fixed16x10 => 10.0f,
|
||||
AspectRatio.Fixed21x9 => 9.0f,
|
||||
AspectRatio.Fixed32x9 => 9.0f,
|
||||
_ => 9.0f
|
||||
};
|
||||
}
|
||||
|
||||
public static string ToText(this AspectRatio aspectRatio)
|
||||
{
|
||||
return aspectRatio switch
|
||||
{
|
||||
AspectRatio.Fixed4x3 => "4:3",
|
||||
AspectRatio.Fixed16x9 => "16:9",
|
||||
AspectRatio.Fixed16x10 => "16:10",
|
||||
AspectRatio.Fixed21x9 => "21:9",
|
||||
AspectRatio.Fixed32x9 => "32:9",
|
||||
_ => "Stretched"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
13
src/Ryujinx.Common/Configuration/BackendThreading.cs
Normal file
13
src/Ryujinx.Common/Configuration/BackendThreading.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<BackendThreading>))]
|
||||
public enum BackendThreading
|
||||
{
|
||||
Auto,
|
||||
Off,
|
||||
On
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct DownloadableContentContainer
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string ContainerPath { get; set; }
|
||||
[JsonPropertyName("dlc_nca_list")]
|
||||
public List<DownloadableContentNca> DownloadableContentNcaList { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(List<DownloadableContentContainer>))]
|
||||
public partial class DownloadableContentJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
14
src/Ryujinx.Common/Configuration/DownloadableContentNca.cs
Normal file
14
src/Ryujinx.Common/Configuration/DownloadableContentNca.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct DownloadableContentNca
|
||||
{
|
||||
[JsonPropertyName("path")]
|
||||
public string FullPath { get; set; }
|
||||
[JsonPropertyName("title_id")]
|
||||
public ulong TitleId { get; set; }
|
||||
[JsonPropertyName("is_enabled")]
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
12
src/Ryujinx.Common/Configuration/GraphicsBackend.cs
Normal file
12
src/Ryujinx.Common/Configuration/GraphicsBackend.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsBackend>))]
|
||||
public enum GraphicsBackend
|
||||
{
|
||||
Vulkan,
|
||||
OpenGl
|
||||
}
|
||||
}
|
14
src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs
Normal file
14
src/Ryujinx.Common/Configuration/GraphicsDebugLevel.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GraphicsDebugLevel>))]
|
||||
public enum GraphicsDebugLevel
|
||||
{
|
||||
None,
|
||||
Error,
|
||||
Slowdowns,
|
||||
All
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<GamepadInputId>))]
|
||||
public enum GamepadInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
A,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LeftStick,
|
||||
RightStick,
|
||||
LeftShoulder,
|
||||
RightShoulder,
|
||||
|
||||
// Likely axis
|
||||
LeftTrigger,
|
||||
// Likely axis
|
||||
RightTrigger,
|
||||
|
||||
DpadUp,
|
||||
DpadDown,
|
||||
DpadLeft,
|
||||
DpadRight,
|
||||
|
||||
// Special buttons
|
||||
|
||||
Minus,
|
||||
Plus,
|
||||
|
||||
Back = Minus,
|
||||
Start = Plus,
|
||||
|
||||
Guide,
|
||||
Misc1,
|
||||
|
||||
// Xbox Elite paddle
|
||||
Paddle1,
|
||||
Paddle2,
|
||||
Paddle3,
|
||||
Paddle4,
|
||||
|
||||
// PS5 touchpad button
|
||||
Touchpad,
|
||||
|
||||
// Virtual buttons for single joycon
|
||||
SingleLeftTrigger0,
|
||||
SingleRightTrigger0,
|
||||
|
||||
SingleLeftTrigger1,
|
||||
SingleRightTrigger1,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
public class GenericControllerInputConfig<Button, Stick> : GenericInputConfigurationCommon<Button> where Button : unmanaged where Stick : unmanaged
|
||||
{
|
||||
[JsonIgnore]
|
||||
private float _deadzoneLeft;
|
||||
[JsonIgnore]
|
||||
private float _deadzoneRight;
|
||||
[JsonIgnore]
|
||||
private float _triggerThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Left JoyCon Controller Stick Bindings
|
||||
/// </summary>
|
||||
public JoyconConfigControllerStick<Button, Stick> LeftJoyconStick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Right JoyCon Controller Stick Bindings
|
||||
/// </summary>
|
||||
public JoyconConfigControllerStick<Button, Stick> RightJoyconStick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller Left Analog Stick Deadzone
|
||||
/// </summary>
|
||||
public float DeadzoneLeft
|
||||
{
|
||||
get => _deadzoneLeft; set
|
||||
{
|
||||
_deadzoneLeft = MathF.Round(value, 3);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controller Right Analog Stick Deadzone
|
||||
/// </summary>
|
||||
public float DeadzoneRight
|
||||
{
|
||||
get => _deadzoneRight; set
|
||||
{
|
||||
_deadzoneRight = MathF.Round(value, 3);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controller Left Analog Stick Range
|
||||
/// </summary>
|
||||
public float RangeLeft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller Right Analog Stick Range
|
||||
/// </summary>
|
||||
public float RangeRight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller Trigger Threshold
|
||||
/// </summary>
|
||||
public float TriggerThreshold
|
||||
{
|
||||
get => _triggerThreshold; set
|
||||
{
|
||||
_triggerThreshold = MathF.Round(value, 3);
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Controller Motion Settings
|
||||
/// </summary>
|
||||
public MotionConfigController Motion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller Rumble Settings
|
||||
/// </summary>
|
||||
public RumbleConfigController Rumble { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
public class JoyconConfigControllerStick<Button, Stick> where Button: unmanaged where Stick: unmanaged
|
||||
{
|
||||
public Stick Joystick { get; set; }
|
||||
public bool InvertStickX { get; set; }
|
||||
public bool InvertStickY { get; set; }
|
||||
public bool Rotate90CW { get; set; }
|
||||
public Button StickButton { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
public class CemuHookMotionConfigController : MotionConfigController
|
||||
{
|
||||
/// <summary>
|
||||
/// Motion Controller Slot
|
||||
/// </summary>
|
||||
public int Slot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Motion Controller Alternative Slot, for RightJoyCon in Pair mode
|
||||
/// </summary>
|
||||
public int AltSlot { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Mirror motion input in Pair mode
|
||||
/// </summary>
|
||||
public bool MirrorInput { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host address of the DSU Server
|
||||
/// </summary>
|
||||
public string DsuServerHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Port of the DSU Server
|
||||
/// </summary>
|
||||
public int DsuServerPort { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
class JsonMotionConfigControllerConverter : JsonConverter<MotionConfigController>
|
||||
{
|
||||
private static readonly MotionConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static MotionInputBackendType GetMotionInputBackendType(ref Utf8JsonReader reader)
|
||||
{
|
||||
// Temporary reader to get the backend type
|
||||
Utf8JsonReader tempReader = reader;
|
||||
|
||||
MotionInputBackendType result = MotionInputBackendType.Invalid;
|
||||
|
||||
while (tempReader.Read())
|
||||
{
|
||||
// NOTE: We scan all properties ignoring the depth entirely on purpose.
|
||||
// The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
|
||||
// As such, this code will try to parse very field named "motion_backend" to the correct enum.
|
||||
if (tempReader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
string propertyName = tempReader.GetString();
|
||||
|
||||
if (propertyName.Equals("motion_backend"))
|
||||
{
|
||||
tempReader.Read();
|
||||
|
||||
if (tempReader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
string backendTypeRaw = tempReader.GetString();
|
||||
|
||||
if (!Enum.TryParse(backendTypeRaw, out result))
|
||||
{
|
||||
result = MotionInputBackendType.Invalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override MotionConfigController Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
MotionInputBackendType motionBackendType = GetMotionInputBackendType(ref reader);
|
||||
|
||||
return motionBackendType switch
|
||||
{
|
||||
MotionInputBackendType.GamepadDriver => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardMotionConfigController),
|
||||
MotionInputBackendType.CemuHook => JsonSerializer.Deserialize(ref reader, SerializerContext.CemuHookMotionConfigController),
|
||||
_ => throw new InvalidOperationException($"Unknown backend type {motionBackendType}"),
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, MotionConfigController value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (value.MotionBackend)
|
||||
{
|
||||
case MotionInputBackendType.GamepadDriver:
|
||||
JsonSerializer.Serialize(writer, value as StandardMotionConfigController, SerializerContext.StandardMotionConfigController);
|
||||
break;
|
||||
case MotionInputBackendType.CemuHook:
|
||||
JsonSerializer.Serialize(writer, value as CemuHookMotionConfigController, SerializerContext.CemuHookMotionConfigController);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown motion backend type {value.MotionBackend}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(JsonMotionConfigControllerConverter))]
|
||||
public class MotionConfigController
|
||||
{
|
||||
public MotionInputBackendType MotionBackend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gyro Sensitivity
|
||||
/// </summary>
|
||||
public int Sensitivity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gyro Deadzone
|
||||
/// </summary>
|
||||
public double GyroDeadzone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable Motion Controls
|
||||
/// </summary>
|
||||
public bool EnableMotion { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(MotionConfigController))]
|
||||
[JsonSerializable(typeof(CemuHookMotionConfigController))]
|
||||
[JsonSerializable(typeof(StandardMotionConfigController))]
|
||||
public partial class MotionConfigJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MotionInputBackendType>))]
|
||||
public enum MotionInputBackendType : byte
|
||||
{
|
||||
Invalid,
|
||||
GamepadDriver,
|
||||
CemuHook
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Controller.Motion
|
||||
{
|
||||
public class StandardMotionConfigController : MotionConfigController { }
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
public class RumbleConfigController
|
||||
{
|
||||
/// <summary>
|
||||
/// Controller Strong Rumble Multiplier
|
||||
/// </summary>
|
||||
public float StrongRumble { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller Weak Rumble Multiplier
|
||||
/// </summary>
|
||||
public float WeakRumble { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Enable Rumble
|
||||
/// </summary>
|
||||
public bool EnableRumble { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
public class StandardControllerInputConfig : GenericControllerInputConfig<GamepadInputId, StickInputId> { }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid.Controller
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<StickInputId>))]
|
||||
public enum StickInputId : byte
|
||||
{
|
||||
Unbound,
|
||||
Left,
|
||||
Right,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
23
src/Ryujinx.Common/Configuration/Hid/ControllerType.cs
Normal file
23
src/Ryujinx.Common/Configuration/Hid/ControllerType.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[Flags]
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ControllerType>))]
|
||||
public enum ControllerType : int
|
||||
{
|
||||
None,
|
||||
ProController = 1 << 0,
|
||||
Handheld = 1 << 1,
|
||||
JoyconPair = 1 << 2,
|
||||
JoyconLeft = 1 << 3,
|
||||
JoyconRight = 1 << 4,
|
||||
Invalid = 1 << 5,
|
||||
Pokeball = 1 << 6,
|
||||
SystemExternal = 1 << 29,
|
||||
System = 1 << 30
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class GenericInputConfigurationCommon<Button> : InputConfig where Button : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Left JoyCon Controller Bindings
|
||||
/// </summary>
|
||||
public LeftJoyconCommonConfig<Button> LeftJoycon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Right JoyCon Controller Bindings
|
||||
/// </summary>
|
||||
public RightJoyconCommonConfig<Button> RightJoycon { get; set; }
|
||||
}
|
||||
}
|
13
src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
Normal file
13
src/Ryujinx.Common/Configuration/Hid/InputBackendType.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<InputBackendType>))]
|
||||
public enum InputBackendType
|
||||
{
|
||||
Invalid,
|
||||
WindowKeyboard,
|
||||
GamepadSDL2,
|
||||
}
|
||||
}
|
41
src/Ryujinx.Common/Configuration/Hid/InputConfig.cs
Normal file
41
src/Ryujinx.Common/Configuration/Hid/InputConfig.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(JsonInputConfigConverter))]
|
||||
public class InputConfig : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// The current version of the input file format
|
||||
/// </summary>
|
||||
public const int CurrentVersion = 1;
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
public InputBackendType Backend { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller id
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Controller's Type
|
||||
/// </summary>
|
||||
public ControllerType ControllerType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Player's Index for the controller
|
||||
/// </summary>
|
||||
public PlayerIndex PlayerIndex { get; set; }
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(InputConfig))]
|
||||
[JsonSerializable(typeof(StandardKeyboardInputConfig))]
|
||||
[JsonSerializable(typeof(StandardControllerInputConfig))]
|
||||
public partial class InputConfigJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using Ryujinx.Common.Configuration.Hid.Keyboard;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class JsonInputConfigConverter : JsonConverter<InputConfig>
|
||||
{
|
||||
private static readonly InputConfigJsonSerializerContext SerializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
private static InputBackendType GetInputBackendType(ref Utf8JsonReader reader)
|
||||
{
|
||||
// Temporary reader to get the backend type
|
||||
Utf8JsonReader tempReader = reader;
|
||||
|
||||
InputBackendType result = InputBackendType.Invalid;
|
||||
|
||||
while (tempReader.Read())
|
||||
{
|
||||
// NOTE: We scan all properties ignoring the depth entirely on purpose.
|
||||
// The reason behind this is that we cannot track in a reliable way the depth of the object because Utf8JsonReader never emit the first TokenType == StartObject if the json start with an object.
|
||||
// As such, this code will try to parse very field named "backend" to the correct enum.
|
||||
if (tempReader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
string propertyName = tempReader.GetString();
|
||||
|
||||
if (propertyName.Equals("backend"))
|
||||
{
|
||||
tempReader.Read();
|
||||
|
||||
if (tempReader.TokenType == JsonTokenType.String)
|
||||
{
|
||||
string backendTypeRaw = tempReader.GetString();
|
||||
|
||||
if (!Enum.TryParse(backendTypeRaw, out result))
|
||||
{
|
||||
result = InputBackendType.Invalid;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override InputConfig Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
InputBackendType backendType = GetInputBackendType(ref reader);
|
||||
|
||||
return backendType switch
|
||||
{
|
||||
InputBackendType.WindowKeyboard => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardKeyboardInputConfig),
|
||||
InputBackendType.GamepadSDL2 => JsonSerializer.Deserialize(ref reader, SerializerContext.StandardControllerInputConfig),
|
||||
_ => throw new InvalidOperationException($"Unknown backend type {backendType}"),
|
||||
};
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, InputConfig value, JsonSerializerOptions options)
|
||||
{
|
||||
switch (value.Backend)
|
||||
{
|
||||
case InputBackendType.WindowKeyboard:
|
||||
JsonSerializer.Serialize(writer, value as StandardKeyboardInputConfig, SerializerContext.StandardKeyboardInputConfig);
|
||||
break;
|
||||
case InputBackendType.GamepadSDL2:
|
||||
JsonSerializer.Serialize(writer, value as StandardControllerInputConfig, SerializerContext.StandardControllerInputConfig);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Unknown backend type {value.Backend}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
143
src/Ryujinx.Common/Configuration/Hid/Key.cs
Normal file
143
src/Ryujinx.Common/Configuration/Hid/Key.cs
Normal file
|
@ -0,0 +1,143 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<Key>))]
|
||||
public enum Key
|
||||
{
|
||||
Unknown,
|
||||
ShiftLeft,
|
||||
ShiftRight,
|
||||
ControlLeft,
|
||||
ControlRight,
|
||||
AltLeft,
|
||||
AltRight,
|
||||
WinLeft,
|
||||
WinRight,
|
||||
Menu,
|
||||
F1,
|
||||
F2,
|
||||
F3,
|
||||
F4,
|
||||
F5,
|
||||
F6,
|
||||
F7,
|
||||
F8,
|
||||
F9,
|
||||
F10,
|
||||
F11,
|
||||
F12,
|
||||
F13,
|
||||
F14,
|
||||
F15,
|
||||
F16,
|
||||
F17,
|
||||
F18,
|
||||
F19,
|
||||
F20,
|
||||
F21,
|
||||
F22,
|
||||
F23,
|
||||
F24,
|
||||
F25,
|
||||
F26,
|
||||
F27,
|
||||
F28,
|
||||
F29,
|
||||
F30,
|
||||
F31,
|
||||
F32,
|
||||
F33,
|
||||
F34,
|
||||
F35,
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right,
|
||||
Enter,
|
||||
Escape,
|
||||
Space,
|
||||
Tab,
|
||||
BackSpace,
|
||||
Insert,
|
||||
Delete,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
NumLock,
|
||||
Clear,
|
||||
Keypad0,
|
||||
Keypad1,
|
||||
Keypad2,
|
||||
Keypad3,
|
||||
Keypad4,
|
||||
Keypad5,
|
||||
Keypad6,
|
||||
Keypad7,
|
||||
Keypad8,
|
||||
Keypad9,
|
||||
KeypadDivide,
|
||||
KeypadMultiply,
|
||||
KeypadSubtract,
|
||||
KeypadAdd,
|
||||
KeypadDecimal,
|
||||
KeypadEnter,
|
||||
A,
|
||||
B,
|
||||
C,
|
||||
D,
|
||||
E,
|
||||
F,
|
||||
G,
|
||||
H,
|
||||
I,
|
||||
J,
|
||||
K,
|
||||
L,
|
||||
M,
|
||||
N,
|
||||
O,
|
||||
P,
|
||||
Q,
|
||||
R,
|
||||
S,
|
||||
T,
|
||||
U,
|
||||
V,
|
||||
W,
|
||||
X,
|
||||
Y,
|
||||
Z,
|
||||
Number0,
|
||||
Number1,
|
||||
Number2,
|
||||
Number3,
|
||||
Number4,
|
||||
Number5,
|
||||
Number6,
|
||||
Number7,
|
||||
Number8,
|
||||
Number9,
|
||||
Tilde,
|
||||
Grave,
|
||||
Minus,
|
||||
Plus,
|
||||
BracketLeft,
|
||||
BracketRight,
|
||||
Semicolon,
|
||||
Quote,
|
||||
Comma,
|
||||
Period,
|
||||
Slash,
|
||||
BackSlash,
|
||||
Unbound,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class GenericKeyboardInputConfig<Key> : GenericInputConfigurationCommon<Key> where Key : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Left JoyCon Controller Stick Bindings
|
||||
/// </summary>
|
||||
public JoyconConfigKeyboardStick<Key> LeftJoyconStick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Right JoyCon Controller Stick Bindings
|
||||
/// </summary>
|
||||
public JoyconConfigKeyboardStick<Key> RightJoyconStick { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class JoyconConfigKeyboardStick<Key> where Key: unmanaged
|
||||
{
|
||||
public Key StickUp { get; set; }
|
||||
public Key StickDown { get; set; }
|
||||
public Key StickLeft { get; set; }
|
||||
public Key StickRight { get; set; }
|
||||
public Key StickButton { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid.Keyboard
|
||||
{
|
||||
public class StandardKeyboardInputConfig : GenericKeyboardInputConfig<Key> { }
|
||||
}
|
17
src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
Normal file
17
src/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// NOTE: Please don't change this to struct.
|
||||
// This breaks Avalonia's TwoWay binding, which makes us unable to save new KeyboardHotkeys.
|
||||
public class KeyboardHotkeys
|
||||
{
|
||||
public Key ToggleVsync { get; set; }
|
||||
public Key Screenshot { get; set; }
|
||||
public Key ShowUi { get; set; }
|
||||
public Key Pause { get; set; }
|
||||
public Key ToggleMute { get; set; }
|
||||
public Key ResScaleUp { get; set; }
|
||||
public Key ResScaleDown { get; set; }
|
||||
public Key VolumeUp { get; set; }
|
||||
public Key VolumeDown { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class LeftJoyconCommonConfig<Button>
|
||||
{
|
||||
public Button ButtonMinus { get; set; }
|
||||
public Button ButtonL { get; set; }
|
||||
public Button ButtonZl { get; set; }
|
||||
public Button ButtonSl { get; set; }
|
||||
public Button ButtonSr { get; set; }
|
||||
public Button DpadUp { get; set; }
|
||||
public Button DpadDown { get; set; }
|
||||
public Button DpadLeft { get; set; }
|
||||
public Button DpadRight { get; set; }
|
||||
}
|
||||
}
|
22
src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs
Normal file
22
src/Ryujinx.Common/Configuration/Hid/PlayerIndex.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
// This enum was duplicated from Ryujinx.HLE.HOS.Services.Hid.PlayerIndex and should be kept identical
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<PlayerIndex>))]
|
||||
public enum PlayerIndex : int
|
||||
{
|
||||
Player1 = 0,
|
||||
Player2 = 1,
|
||||
Player3 = 2,
|
||||
Player4 = 3,
|
||||
Player5 = 4,
|
||||
Player6 = 5,
|
||||
Player7 = 6,
|
||||
Player8 = 7,
|
||||
Handheld = 8,
|
||||
Unknown = 9,
|
||||
Auto = 10 // Shouldn't be used directly
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Ryujinx.Common.Configuration.Hid
|
||||
{
|
||||
public class RightJoyconCommonConfig<Button>
|
||||
{
|
||||
public Button ButtonPlus { get; set; }
|
||||
public Button ButtonR { get; set; }
|
||||
public Button ButtonZr { get; set; }
|
||||
public Button ButtonSl { get; set; }
|
||||
public Button ButtonSr { get; set; }
|
||||
public Button ButtonX { get; set; }
|
||||
public Button ButtonB { get; set; }
|
||||
public Button ButtonY { get; set; }
|
||||
public Button ButtonA { get; set; }
|
||||
}
|
||||
}
|
13
src/Ryujinx.Common/Configuration/MemoryManagerMode.cs
Normal file
13
src/Ryujinx.Common/Configuration/MemoryManagerMode.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<MemoryManagerMode>))]
|
||||
public enum MemoryManagerMode : byte
|
||||
{
|
||||
SoftwarePageTable,
|
||||
HostMapped,
|
||||
HostMappedUnsafe
|
||||
}
|
||||
}
|
13
src/Ryujinx.Common/Configuration/ScalingFilter.cs
Normal file
13
src/Ryujinx.Common/Configuration/ScalingFilter.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<ScalingFilter>))]
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
Nearest,
|
||||
Fsr
|
||||
}
|
||||
}
|
10
src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs
Normal file
10
src/Ryujinx.Common/Configuration/TitleUpdateMetadata.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct TitleUpdateMetadata
|
||||
{
|
||||
public string Selected { get; set; }
|
||||
public List<string> Paths { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(TitleUpdateMetadata))]
|
||||
public partial class TitleUpdateMetadataJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
16
src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs
Normal file
16
src/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class BinaryReaderExtensions
|
||||
{
|
||||
public unsafe static T ReadStruct<T>(this BinaryReader reader)
|
||||
where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||
}
|
||||
}
|
||||
}
|
28
src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
28
src/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class BinaryWriterExtensions
|
||||
{
|
||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||
where T : unmanaged
|
||||
{
|
||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||
|
||||
writer.Write(data);
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||
{
|
||||
writer.Write((ulong)value);
|
||||
writer.Write((ulong)(value >> 64));
|
||||
}
|
||||
|
||||
public static void Write(this BinaryWriter writer, MemoryStream stream)
|
||||
{
|
||||
stream.CopyTo(writer.BaseStream);
|
||||
}
|
||||
}
|
||||
}
|
138
src/Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
138
src/Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
|
@ -0,0 +1,138 @@
|
|||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class StreamExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
|
||||
///
|
||||
/// This default implementation converts each buffer value to a stack-allocated
|
||||
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="buffer">The buffer of values to be written</param>
|
||||
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
|
||||
{
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (BitConverter.IsLittleEndian)
|
||||
{
|
||||
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
|
||||
|
||||
foreach (int value in buffer)
|
||||
{
|
||||
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
|
||||
stream.Write(byteBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a four-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, int value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte signed integer to this stream. The current position
|
||||
/// of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, long value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
// Writes a four-byte unsigned integer to this stream. The current position
|
||||
// of the stream is advanced by four.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, uint value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an eight-byte unsigned integer to this stream. The current
|
||||
/// position of the stream is advanced by eight.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="value">The value to be written</param>
|
||||
public static void Write(this Stream stream, ulong value)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of source to stream by calling source.CopyTo(stream).
|
||||
/// Provides consistency with other Stream.Write methods.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to</param>
|
||||
/// <param name="source">The stream to be read from</param>
|
||||
public static void Write(this Stream stream, Stream source)
|
||||
{
|
||||
source.CopyTo(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a sequence of bytes to the Stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to be written to.</param>
|
||||
/// <param name="value">The byte to be written</param>
|
||||
/// <param name="count">The number of times the value should be written</param>
|
||||
public static void WriteByte(this Stream stream, byte value, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int BlockSize = 16;
|
||||
|
||||
int blockCount = count / BlockSize;
|
||||
if (blockCount > 0)
|
||||
{
|
||||
Span<byte> span = stackalloc byte[BlockSize];
|
||||
span.Fill(value);
|
||||
for (int x = 0; x < blockCount; x++)
|
||||
{
|
||||
stream.Write(span);
|
||||
}
|
||||
}
|
||||
|
||||
int nonBlockBytes = count % BlockSize;
|
||||
for (int x = 0; x < nonBlockBytes; x++)
|
||||
{
|
||||
stream.WriteByte(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs
Normal file
22
src/Ryujinx.Common/GraphicsDriver/DriverUtilities.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver
|
||||
{
|
||||
public static class DriverUtilities
|
||||
{
|
||||
public static void ToggleOGLThreading(bool enabled)
|
||||
{
|
||||
Environment.SetEnvironmentVariable("mesa_glthread", enabled.ToString().ToLower());
|
||||
Environment.SetEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", enabled ? "1" : "0");
|
||||
|
||||
try
|
||||
{
|
||||
NVThreadedOptimization.SetThreadedOptimization(enabled);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// NVAPI is not available, or couldn't change the application profile.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs
Normal file
11
src/Ryujinx.Common/GraphicsDriver/NVAPI/Nvapi.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
namespace Ryujinx.Common.GraphicsDriver.NVAPI
|
||||
{
|
||||
enum Nvapi : uint
|
||||
{
|
||||
OglThreadControlId = 0x20C1221E,
|
||||
|
||||
OglThreadControlDefault = 0,
|
||||
OglThreadControlEnable = 1,
|
||||
OglThreadControlDisable = 2
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver.NVAPI
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public unsafe struct NvapiUnicodeString
|
||||
{
|
||||
private fixed byte _data[4096];
|
||||
|
||||
public NvapiUnicodeString(string text)
|
||||
{
|
||||
Set(text);
|
||||
}
|
||||
|
||||
public string Get()
|
||||
{
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
string text = Encoding.Unicode.GetString(data, 4096);
|
||||
|
||||
int index = text.IndexOf('\0');
|
||||
if (index > -1)
|
||||
{
|
||||
text = text.Remove(index);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public void Set(string text)
|
||||
{
|
||||
text += '\0';
|
||||
fixed (char* textPtr = text)
|
||||
fixed (byte* data = _data)
|
||||
{
|
||||
int written = Encoding.Unicode.GetBytes(textPtr, text.Length, data, 4096);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver.NVAPI
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
unsafe struct NvdrsApplicationV4
|
||||
{
|
||||
public uint Version;
|
||||
public uint IsPredefined;
|
||||
public NvapiUnicodeString AppName;
|
||||
public NvapiUnicodeString UserFriendlyName;
|
||||
public NvapiUnicodeString Launcher;
|
||||
public NvapiUnicodeString FileInFolder;
|
||||
public uint Flags;
|
||||
public NvapiUnicodeString CommandLine;
|
||||
}
|
||||
}
|
15
src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs
Normal file
15
src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsProfile.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver.NVAPI
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
unsafe struct NvdrsProfile
|
||||
{
|
||||
public uint Version;
|
||||
public NvapiUnicodeString ProfileName;
|
||||
public uint GpuSupport;
|
||||
public uint IsPredefined;
|
||||
public uint NumOfApps;
|
||||
public uint NumOfSettings;
|
||||
}
|
||||
}
|
49
src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs
Normal file
49
src/Ryujinx.Common/GraphicsDriver/NVAPI/NvdrsSetting.cs
Normal file
|
@ -0,0 +1,49 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver.NVAPI
|
||||
{
|
||||
enum NvdrsSettingType : uint
|
||||
{
|
||||
NvdrsDwordType,
|
||||
NvdrsBinaryType,
|
||||
NvdrsStringType,
|
||||
NvdrsWstringType,
|
||||
}
|
||||
|
||||
enum NvdrsSettingLocation : uint
|
||||
{
|
||||
NvdrsCurrentProfileLocation,
|
||||
NvdrsGlobalProfileLocation,
|
||||
NvdrsBaseProfileLocation,
|
||||
NvdrsDefaultProfileLocation,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit, Size = 0x3020)]
|
||||
unsafe struct NvdrsSetting
|
||||
{
|
||||
[FieldOffset(0x0)]
|
||||
public uint Version;
|
||||
[FieldOffset(0x4)]
|
||||
public NvapiUnicodeString SettingName;
|
||||
[FieldOffset(0x1004)]
|
||||
public Nvapi SettingId;
|
||||
[FieldOffset(0x1008)]
|
||||
public NvdrsSettingType SettingType;
|
||||
[FieldOffset(0x100C)]
|
||||
public NvdrsSettingLocation SettingLocation;
|
||||
[FieldOffset(0x1010)]
|
||||
public uint IsCurrentPredefined;
|
||||
[FieldOffset(0x1014)]
|
||||
public uint IsPredefinedValid;
|
||||
|
||||
[FieldOffset(0x1018)]
|
||||
public uint PredefinedValue;
|
||||
[FieldOffset(0x1018)]
|
||||
public NvapiUnicodeString PredefinedString;
|
||||
|
||||
[FieldOffset(0x201C)]
|
||||
public uint CurrentValue;
|
||||
[FieldOffset(0x201C)]
|
||||
public NvapiUnicodeString CurrentString;
|
||||
}
|
||||
}
|
163
src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs
Normal file
163
src/Ryujinx.Common/GraphicsDriver/NVThreadedOptimization.cs
Normal file
|
@ -0,0 +1,163 @@
|
|||
using Ryujinx.Common.GraphicsDriver.NVAPI;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.GraphicsDriver
|
||||
{
|
||||
static partial class NVThreadedOptimization
|
||||
{
|
||||
private const string ProfileName = "Ryujinx Nvidia Profile";
|
||||
|
||||
private const uint NvAPI_Initialize_ID = 0x0150E828;
|
||||
private const uint NvAPI_DRS_CreateSession_ID = 0x0694D52E;
|
||||
private const uint NvAPI_DRS_LoadSettings_ID = 0x375DBD6B;
|
||||
private const uint NvAPI_DRS_FindProfileByName_ID = 0x7E4A9A0B;
|
||||
private const uint NvAPI_DRS_CreateProfile_ID = 0x0CC176068;
|
||||
private const uint NvAPI_DRS_CreateApplication_ID = 0x4347A9DE;
|
||||
private const uint NvAPI_DRS_SetSetting_ID = 0x577DD202;
|
||||
private const uint NvAPI_DRS_SaveSettings_ID = 0xFCBC7E14;
|
||||
private const uint NvAPI_DRS_DestroySession_ID = 0x0DAD9CFF8;
|
||||
|
||||
[LibraryImport("nvapi64")]
|
||||
private static partial IntPtr nvapi_QueryInterface(uint id);
|
||||
|
||||
private delegate int NvAPI_InitializeDelegate();
|
||||
private static NvAPI_InitializeDelegate NvAPI_Initialize;
|
||||
|
||||
private delegate int NvAPI_DRS_CreateSessionDelegate(out IntPtr handle);
|
||||
private static NvAPI_DRS_CreateSessionDelegate NvAPI_DRS_CreateSession;
|
||||
|
||||
private delegate int NvAPI_DRS_LoadSettingsDelegate(IntPtr handle);
|
||||
private static NvAPI_DRS_LoadSettingsDelegate NvAPI_DRS_LoadSettings;
|
||||
|
||||
private delegate int NvAPI_DRS_FindProfileByNameDelegate(IntPtr handle, NvapiUnicodeString profileName, out IntPtr profileHandle);
|
||||
private static NvAPI_DRS_FindProfileByNameDelegate NvAPI_DRS_FindProfileByName;
|
||||
|
||||
private delegate int NvAPI_DRS_CreateProfileDelegate(IntPtr handle, ref NvdrsProfile profileInfo, out IntPtr profileHandle);
|
||||
private static NvAPI_DRS_CreateProfileDelegate NvAPI_DRS_CreateProfile;
|
||||
|
||||
private delegate int NvAPI_DRS_CreateApplicationDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsApplicationV4 app);
|
||||
private static NvAPI_DRS_CreateApplicationDelegate NvAPI_DRS_CreateApplication;
|
||||
|
||||
private delegate int NvAPI_DRS_SetSettingDelegate(IntPtr handle, IntPtr profileHandle, ref NvdrsSetting setting);
|
||||
private static NvAPI_DRS_SetSettingDelegate NvAPI_DRS_SetSetting;
|
||||
|
||||
private delegate int NvAPI_DRS_SaveSettingsDelegate(IntPtr handle);
|
||||
private static NvAPI_DRS_SaveSettingsDelegate NvAPI_DRS_SaveSettings;
|
||||
|
||||
private delegate int NvAPI_DRS_DestroySessionDelegate(IntPtr handle);
|
||||
private static NvAPI_DRS_DestroySessionDelegate NvAPI_DRS_DestroySession;
|
||||
|
||||
private static bool _initialized;
|
||||
|
||||
private static void Check(int status)
|
||||
{
|
||||
if (status != 0)
|
||||
{
|
||||
throw new Exception($"NVAPI Error: {status}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Initialize()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
NvAPI_Initialize = NvAPI_Delegate<NvAPI_InitializeDelegate>(NvAPI_Initialize_ID);
|
||||
|
||||
Check(NvAPI_Initialize());
|
||||
|
||||
NvAPI_DRS_CreateSession = NvAPI_Delegate<NvAPI_DRS_CreateSessionDelegate>(NvAPI_DRS_CreateSession_ID);
|
||||
NvAPI_DRS_LoadSettings = NvAPI_Delegate<NvAPI_DRS_LoadSettingsDelegate>(NvAPI_DRS_LoadSettings_ID);
|
||||
NvAPI_DRS_FindProfileByName = NvAPI_Delegate<NvAPI_DRS_FindProfileByNameDelegate>(NvAPI_DRS_FindProfileByName_ID);
|
||||
NvAPI_DRS_CreateProfile = NvAPI_Delegate<NvAPI_DRS_CreateProfileDelegate>(NvAPI_DRS_CreateProfile_ID);
|
||||
NvAPI_DRS_CreateApplication = NvAPI_Delegate<NvAPI_DRS_CreateApplicationDelegate>(NvAPI_DRS_CreateApplication_ID);
|
||||
NvAPI_DRS_SetSetting = NvAPI_Delegate<NvAPI_DRS_SetSettingDelegate>(NvAPI_DRS_SetSetting_ID);
|
||||
NvAPI_DRS_SaveSettings = NvAPI_Delegate<NvAPI_DRS_SaveSettingsDelegate>(NvAPI_DRS_SaveSettings_ID);
|
||||
NvAPI_DRS_DestroySession = NvAPI_Delegate<NvAPI_DRS_DestroySessionDelegate>(NvAPI_DRS_DestroySession_ID);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static uint MakeVersion<T>(uint version) where T : unmanaged
|
||||
{
|
||||
return (uint)Unsafe.SizeOf<T>() | version << 16;
|
||||
}
|
||||
|
||||
public static void SetThreadedOptimization(bool enabled)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
uint targetValue = (uint)(enabled ? Nvapi.OglThreadControlEnable : Nvapi.OglThreadControlDisable);
|
||||
|
||||
Check(NvAPI_Initialize());
|
||||
|
||||
Check(NvAPI_DRS_CreateSession(out IntPtr handle));
|
||||
|
||||
Check(NvAPI_DRS_LoadSettings(handle));
|
||||
|
||||
IntPtr profileHandle;
|
||||
|
||||
// Check if the profile already exists.
|
||||
|
||||
int status = NvAPI_DRS_FindProfileByName(handle, new NvapiUnicodeString(ProfileName), out profileHandle);
|
||||
|
||||
if (status != 0)
|
||||
{
|
||||
NvdrsProfile profile = new NvdrsProfile {
|
||||
Version = MakeVersion<NvdrsProfile>(1),
|
||||
IsPredefined = 0,
|
||||
GpuSupport = uint.MaxValue
|
||||
};
|
||||
profile.ProfileName.Set(ProfileName);
|
||||
Check(NvAPI_DRS_CreateProfile(handle, ref profile, out profileHandle));
|
||||
|
||||
NvdrsApplicationV4 application = new NvdrsApplicationV4
|
||||
{
|
||||
Version = MakeVersion<NvdrsApplicationV4>(4),
|
||||
IsPredefined = 0,
|
||||
Flags = 3 // IsMetro, IsCommandLine
|
||||
};
|
||||
application.AppName.Set("Ryujinx.exe");
|
||||
application.UserFriendlyName.Set("Ryujinx");
|
||||
application.Launcher.Set("");
|
||||
application.FileInFolder.Set("");
|
||||
|
||||
Check(NvAPI_DRS_CreateApplication(handle, profileHandle, ref application));
|
||||
}
|
||||
|
||||
NvdrsSetting setting = new NvdrsSetting
|
||||
{
|
||||
Version = MakeVersion<NvdrsSetting>(1),
|
||||
SettingId = Nvapi.OglThreadControlId,
|
||||
SettingType = NvdrsSettingType.NvdrsDwordType,
|
||||
SettingLocation = NvdrsSettingLocation.NvdrsCurrentProfileLocation,
|
||||
IsCurrentPredefined = 0,
|
||||
IsPredefinedValid = 0,
|
||||
CurrentValue = targetValue,
|
||||
PredefinedValue = targetValue
|
||||
};
|
||||
|
||||
Check(NvAPI_DRS_SetSetting(handle, profileHandle, ref setting));
|
||||
|
||||
Check(NvAPI_DRS_SaveSettings(handle));
|
||||
|
||||
NvAPI_DRS_DestroySession(handle);
|
||||
}
|
||||
|
||||
private static T NvAPI_Delegate<T>(uint id) where T : class
|
||||
{
|
||||
IntPtr ptr = nvapi_QueryInterface(id);
|
||||
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(ptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
src/Ryujinx.Common/Hash128.cs
Normal file
48
src/Ryujinx.Common/Hash128.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Hash128 : IEquatable<Hash128>
|
||||
{
|
||||
public ulong Low;
|
||||
public ulong High;
|
||||
|
||||
public Hash128(ulong low, ulong high)
|
||||
{
|
||||
Low = low;
|
||||
High = high;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{High:x16}{Low:x16}";
|
||||
}
|
||||
|
||||
public static bool operator ==(Hash128 x, Hash128 y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public static bool operator !=(Hash128 x, Hash128 y)
|
||||
{
|
||||
return !x.Equals(y);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Hash128 hash128 && Equals(hash128);
|
||||
}
|
||||
|
||||
public bool Equals(Hash128 cmpObj)
|
||||
{
|
||||
return Low == cmpObj.Low && High == cmpObj.High;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Low, High);
|
||||
}
|
||||
}
|
||||
}
|
42
src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
Normal file
42
src/Ryujinx.Common/Logging/Formatters/DefaultLogFormatter.cs
Normal file
|
@ -0,0 +1,42 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DefaultLogFormatter : ILogFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public string Format(LogEventArgs args)
|
||||
{
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
sb.Clear();
|
||||
|
||||
sb.Append($@"{args.Time:hh\:mm\:ss\.fff}");
|
||||
sb.Append($" |{args.Level.ToString()[0]}| ");
|
||||
|
||||
if (args.ThreadName != null)
|
||||
{
|
||||
sb.Append(args.ThreadName);
|
||||
sb.Append(' ');
|
||||
}
|
||||
|
||||
sb.Append(args.Message);
|
||||
|
||||
if (args.Data is not null)
|
||||
{
|
||||
sb.Append(' ');
|
||||
DynamicObjectFormatter.Format(sb, args.Data);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
#nullable enable
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class DynamicObjectFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
public static string? Format(object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
try
|
||||
{
|
||||
Format(sb, dynamicObject);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
finally
|
||||
{
|
||||
StringBuilderPool.Release(sb);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Format(StringBuilder sb, object? dynamicObject)
|
||||
{
|
||||
if (dynamicObject is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PropertyInfo[] props = dynamicObject.GetType().GetProperties();
|
||||
|
||||
sb.Append('{');
|
||||
|
||||
foreach (var prop in props)
|
||||
{
|
||||
sb.Append(prop.Name);
|
||||
sb.Append(": ");
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(prop.PropertyType))
|
||||
{
|
||||
Array? array = (Array?) prop.GetValue(dynamicObject);
|
||||
|
||||
if (array is not null)
|
||||
{
|
||||
foreach (var item in array)
|
||||
{
|
||||
sb.Append(item);
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
if (array.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(prop.GetValue(dynamicObject));
|
||||
}
|
||||
|
||||
sb.Append(" ; ");
|
||||
}
|
||||
|
||||
// We remove the final ';' from the string
|
||||
if (props.Length > 0)
|
||||
{
|
||||
sb.Remove(sb.Length - 3, 3);
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
}
|
||||
}
|
7
src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
Normal file
7
src/Ryujinx.Common/Logging/Formatters/ILogFormatter.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
interface ILogFormatter
|
||||
{
|
||||
string Format(LogEventArgs args);
|
||||
}
|
||||
}
|
76
src/Ryujinx.Common/Logging/LogClass.cs
Normal file
76
src/Ryujinx.Common/Logging/LogClass.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogClass>))]
|
||||
public enum LogClass
|
||||
{
|
||||
Application,
|
||||
Audio,
|
||||
AudioRenderer,
|
||||
Configuration,
|
||||
Cpu,
|
||||
Emulation,
|
||||
FFmpeg,
|
||||
Font,
|
||||
Gpu,
|
||||
Hid,
|
||||
Host1x,
|
||||
Kernel,
|
||||
KernelIpc,
|
||||
KernelScheduler,
|
||||
KernelSvc,
|
||||
Loader,
|
||||
ModLoader,
|
||||
Nvdec,
|
||||
Ptc,
|
||||
Service,
|
||||
ServiceAcc,
|
||||
ServiceAm,
|
||||
ServiceApm,
|
||||
ServiceAudio,
|
||||
ServiceBcat,
|
||||
ServiceBsd,
|
||||
ServiceBtm,
|
||||
ServiceCaps,
|
||||
ServiceFatal,
|
||||
ServiceFriend,
|
||||
ServiceFs,
|
||||
ServiceHid,
|
||||
ServiceIrs,
|
||||
ServiceLdn,
|
||||
ServiceLdr,
|
||||
ServiceLm,
|
||||
ServiceMii,
|
||||
ServiceMm,
|
||||
ServiceMnpp,
|
||||
ServiceNfc,
|
||||
ServiceNfp,
|
||||
ServiceNgct,
|
||||
ServiceNifm,
|
||||
ServiceNim,
|
||||
ServiceNs,
|
||||
ServiceNsd,
|
||||
ServiceNtc,
|
||||
ServiceNv,
|
||||
ServiceOlsc,
|
||||
ServicePctl,
|
||||
ServicePcv,
|
||||
ServicePl,
|
||||
ServicePrepo,
|
||||
ServicePsm,
|
||||
ServicePtm,
|
||||
ServiceSet,
|
||||
ServiceSfdnsres,
|
||||
ServiceSm,
|
||||
ServiceSsl,
|
||||
ServiceSss,
|
||||
ServiceTime,
|
||||
ServiceVi,
|
||||
SurfaceFlinger,
|
||||
TamperMachine,
|
||||
Ui,
|
||||
Vic
|
||||
}
|
||||
}
|
23
src/Ryujinx.Common/Logging/LogEventArgs.cs
Normal file
23
src/Ryujinx.Common/Logging/LogEventArgs.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class LogEventArgs : EventArgs
|
||||
{
|
||||
public readonly LogLevel Level;
|
||||
public readonly TimeSpan Time;
|
||||
public readonly string ThreadName;
|
||||
|
||||
public readonly string Message;
|
||||
public readonly object Data;
|
||||
|
||||
public LogEventArgs(LogLevel level, TimeSpan time, string threadName, string message, object data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
30
src/Ryujinx.Common/Logging/LogEventArgsJson.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
internal class LogEventArgsJson
|
||||
{
|
||||
public LogLevel Level { get; }
|
||||
public TimeSpan Time { get; }
|
||||
public string ThreadName { get; }
|
||||
|
||||
public string Message { get; }
|
||||
public string Data { get; }
|
||||
|
||||
[JsonConstructor]
|
||||
public LogEventArgsJson(LogLevel level, TimeSpan time, string threadName, string message, string data = null)
|
||||
{
|
||||
Level = level;
|
||||
Time = time;
|
||||
ThreadName = threadName;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public static LogEventArgsJson FromLogEventArgs(LogEventArgs args)
|
||||
{
|
||||
return new LogEventArgsJson(args.Level, args.Time, args.ThreadName, args.Message, DynamicObjectFormatter.Format(args.Data));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonSerializable(typeof(LogEventArgsJson))]
|
||||
internal partial class LogEventJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
19
src/Ryujinx.Common/Logging/LogLevel.cs
Normal file
19
src/Ryujinx.Common/Logging/LogLevel.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
[JsonConverter(typeof(TypedStringEnumConverter<LogLevel>))]
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Stub,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Guest,
|
||||
AccessLog,
|
||||
Notice,
|
||||
Trace
|
||||
}
|
||||
}
|
224
src/Ryujinx.Common/Logging/Logger.cs
Normal file
224
src/Ryujinx.Common/Logging/Logger.cs
Normal file
|
@ -0,0 +1,224 @@
|
|||
using Ryujinx.Common.SystemInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly Stopwatch m_Time;
|
||||
|
||||
private static readonly bool[] m_EnabledClasses;
|
||||
|
||||
private static readonly List<ILogTarget> m_LogTargets;
|
||||
|
||||
private static readonly StdErrAdapter _stdErrAdapter;
|
||||
|
||||
public static event EventHandler<LogEventArgs> Updated;
|
||||
|
||||
public readonly struct Log
|
||||
{
|
||||
internal readonly LogLevel Level;
|
||||
|
||||
internal Log(LogLevel level)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintMsg(LogClass logClass, string message)
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, "", message)));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Print(LogClass logClass, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message)));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Print(LogClass logClass, string message, object data, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), data));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message)));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintStub(LogClass logClass, object data, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed."), data));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintStub(LogClass logClass, string message, object data, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintRawMsg(string message)
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}";
|
||||
}
|
||||
|
||||
public static Log? Debug { get; private set; }
|
||||
public static Log? Info { get; private set; }
|
||||
public static Log? Warning { get; private set; }
|
||||
public static Log? Error { get; private set; }
|
||||
public static Log? Guest { get; private set; }
|
||||
public static Log? AccessLog { get; private set; }
|
||||
public static Log? Stub { get; private set; }
|
||||
public static Log? Trace { get; private set; }
|
||||
public static Log Notice { get; } // Always enabled
|
||||
|
||||
static Logger()
|
||||
{
|
||||
m_EnabledClasses = new bool[Enum.GetNames<LogClass>().Length];
|
||||
|
||||
for (int index = 0; index < m_EnabledClasses.Length; index++)
|
||||
{
|
||||
m_EnabledClasses[index] = true;
|
||||
}
|
||||
|
||||
m_LogTargets = new List<ILogTarget>();
|
||||
|
||||
m_Time = Stopwatch.StartNew();
|
||||
|
||||
// Logger should log to console by default
|
||||
AddTarget(new AsyncLogTargetWrapper(
|
||||
new ConsoleLogTarget("console"),
|
||||
1000,
|
||||
AsyncLogTargetOverflowAction.Discard));
|
||||
|
||||
Notice = new Log(LogLevel.Notice);
|
||||
|
||||
// Enable important log levels before configuration is loaded
|
||||
Error = new Log(LogLevel.Error);
|
||||
Warning = new Log(LogLevel.Warning);
|
||||
Info = new Log(LogLevel.Info);
|
||||
Trace = new Log(LogLevel.Trace);
|
||||
|
||||
_stdErrAdapter = new StdErrAdapter();
|
||||
}
|
||||
|
||||
public static void RestartTime()
|
||||
{
|
||||
m_Time.Restart();
|
||||
}
|
||||
|
||||
private static ILogTarget GetTarget(string targetName)
|
||||
{
|
||||
foreach (var target in m_LogTargets)
|
||||
{
|
||||
if (target.Name.Equals(targetName))
|
||||
{
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void AddTarget(ILogTarget target)
|
||||
{
|
||||
m_LogTargets.Add(target);
|
||||
|
||||
Updated += target.Log;
|
||||
}
|
||||
|
||||
public static void RemoveTarget(string target)
|
||||
{
|
||||
ILogTarget logTarget = GetTarget(target);
|
||||
|
||||
if (logTarget != null)
|
||||
{
|
||||
Updated -= logTarget.Log;
|
||||
|
||||
m_LogTargets.Remove(logTarget);
|
||||
|
||||
logTarget.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Shutdown()
|
||||
{
|
||||
Updated = null;
|
||||
|
||||
_stdErrAdapter.Dispose();
|
||||
|
||||
foreach (var target in m_LogTargets)
|
||||
{
|
||||
target.Dispose();
|
||||
}
|
||||
|
||||
m_LogTargets.Clear();
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<LogLevel> GetEnabledLevels()
|
||||
{
|
||||
var logs = new Log?[] { Debug, Info, Warning, Error, Guest, AccessLog, Stub, Trace };
|
||||
List<LogLevel> levels = new List<LogLevel>(logs.Length);
|
||||
foreach (var log in logs)
|
||||
{
|
||||
if (log.HasValue)
|
||||
{
|
||||
levels.Add(log.Value.Level);
|
||||
}
|
||||
}
|
||||
|
||||
return levels;
|
||||
}
|
||||
|
||||
public static void SetEnable(LogLevel logLevel, bool enabled)
|
||||
{
|
||||
switch (logLevel)
|
||||
{
|
||||
case LogLevel.Debug : Debug = enabled ? new Log(LogLevel.Debug) : new Log?(); break;
|
||||
case LogLevel.Info : Info = enabled ? new Log(LogLevel.Info) : new Log?(); break;
|
||||
case LogLevel.Warning : Warning = enabled ? new Log(LogLevel.Warning) : new Log?(); break;
|
||||
case LogLevel.Error : Error = enabled ? new Log(LogLevel.Error) : new Log?(); break;
|
||||
case LogLevel.Guest : Guest = enabled ? new Log(LogLevel.Guest) : new Log?(); break;
|
||||
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog): new Log?(); break;
|
||||
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
|
||||
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
|
||||
default: throw new ArgumentException("Unknown Log Level");
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetEnable(LogClass logClass, bool enabled)
|
||||
{
|
||||
m_EnabledClasses[(int)logClass] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
79
src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
Normal file
79
src/Ryujinx.Common/Logging/Targets/AsyncLogTargetWrapper.cs
Normal file
|
@ -0,0 +1,79 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public enum AsyncLogTargetOverflowAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Block until there's more room in the queue
|
||||
/// </summary>
|
||||
Block = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Discard the overflowing item
|
||||
/// </summary>
|
||||
Discard = 1
|
||||
}
|
||||
|
||||
public class AsyncLogTargetWrapper : ILogTarget
|
||||
{
|
||||
private ILogTarget _target;
|
||||
|
||||
private Thread _messageThread;
|
||||
|
||||
private BlockingCollection<LogEventArgs> _messageQueue;
|
||||
|
||||
private readonly int _overflowTimeout;
|
||||
|
||||
string ILogTarget.Name { get => _target.Name; }
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target)
|
||||
: this(target, -1, AsyncLogTargetOverflowAction.Block)
|
||||
{ }
|
||||
|
||||
public AsyncLogTargetWrapper(ILogTarget target, int queueLimit, AsyncLogTargetOverflowAction overflowAction)
|
||||
{
|
||||
_target = target;
|
||||
_messageQueue = new BlockingCollection<LogEventArgs>(queueLimit);
|
||||
_overflowTimeout = overflowAction == AsyncLogTargetOverflowAction.Block ? -1 : 0;
|
||||
|
||||
_messageThread = new Thread(() => {
|
||||
while (!_messageQueue.IsCompleted)
|
||||
{
|
||||
try
|
||||
{
|
||||
_target.Log(this, _messageQueue.Take());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// IOE means that Take() was called on a completed collection.
|
||||
// Some other thread can call CompleteAdding after we pass the
|
||||
// IsCompleted check but before we call Take.
|
||||
// We can simply catch the exception since the loop will break
|
||||
// on the next iteration.
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_messageThread.Name = "Logger.MessageThread";
|
||||
_messageThread.IsBackground = true;
|
||||
_messageThread.Start();
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
if (!_messageQueue.IsAddingCompleted)
|
||||
{
|
||||
_messageQueue.TryAdd(e, _overflowTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_messageQueue.CompleteAdding();
|
||||
_messageThread.Join();
|
||||
}
|
||||
}
|
||||
}
|
41
src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
Normal file
41
src/Ryujinx.Common/Logging/Targets/ConsoleLogTarget.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class ConsoleLogTarget : ILogTarget
|
||||
{
|
||||
private readonly ILogFormatter _formatter;
|
||||
|
||||
private readonly string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
private static ConsoleColor GetLogColor(LogLevel level) => level switch {
|
||||
LogLevel.Info => ConsoleColor.White,
|
||||
LogLevel.Warning => ConsoleColor.Yellow,
|
||||
LogLevel.Error => ConsoleColor.Red,
|
||||
LogLevel.Stub => ConsoleColor.DarkGray,
|
||||
LogLevel.Notice => ConsoleColor.Cyan,
|
||||
LogLevel.Trace => ConsoleColor.DarkCyan,
|
||||
_ => ConsoleColor.Gray,
|
||||
};
|
||||
|
||||
public ConsoleLogTarget(string name)
|
||||
{
|
||||
_formatter = new DefaultLogFormatter();
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
{
|
||||
Console.ForegroundColor = GetLogColor(args.Level);
|
||||
Console.WriteLine(_formatter.Format(args));
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
55
src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
Normal file
55
src/Ryujinx.Common/Logging/Targets/FileLogTarget.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class FileLogTarget : ILogTarget
|
||||
{
|
||||
private readonly StreamWriter _logWriter;
|
||||
private readonly ILogFormatter _formatter;
|
||||
private readonly string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public FileLogTarget(string path, string name)
|
||||
: this(path, name, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
|
||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
||||
{
|
||||
// Ensure directory is present
|
||||
DirectoryInfo logDir = new DirectoryInfo(Path.Combine(path, "Logs"));
|
||||
logDir.Create();
|
||||
|
||||
// Clean up old logs, should only keep 3
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||
for (int i = 0; i < files.Length - 2; i++)
|
||||
{
|
||||
files[i].Delete();
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.GetVersion();
|
||||
|
||||
// Get path for the current time
|
||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.log");
|
||||
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_formatter = new DefaultLogFormatter();
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
{
|
||||
_logWriter.WriteLine(_formatter.Format(args));
|
||||
_logWriter.Flush();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_logWriter.WriteLine("---- End of Log ----");
|
||||
_logWriter.Flush();
|
||||
_logWriter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
11
src/Ryujinx.Common/Logging/Targets/ILogTarget.cs
Normal file
11
src/Ryujinx.Common/Logging/Targets/ILogTarget.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public interface ILogTarget : IDisposable
|
||||
{
|
||||
void Log(object sender, LogEventArgs args);
|
||||
|
||||
string Name { get; }
|
||||
}
|
||||
}
|
40
src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
Normal file
40
src/Ryujinx.Common/Logging/Targets/JsonLogTarget.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using Ryujinx.Common.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
{
|
||||
public class JsonLogTarget : ILogTarget
|
||||
{
|
||||
private Stream _stream;
|
||||
private bool _leaveOpen;
|
||||
private string _name;
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public JsonLogTarget(Stream stream, string name)
|
||||
{
|
||||
_stream = stream;
|
||||
_name = name;
|
||||
}
|
||||
|
||||
public JsonLogTarget(Stream stream, bool leaveOpen)
|
||||
{
|
||||
_stream = stream;
|
||||
_leaveOpen = leaveOpen;
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs e)
|
||||
{
|
||||
var logEventArgsJson = LogEventArgsJson.FromLogEventArgs(e);
|
||||
JsonHelper.SerializeToStream(_stream, logEventArgsJson, LogEventJsonSerializerContext.Default.LogEventArgsJson);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_leaveOpen)
|
||||
{
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
src/Ryujinx.Common/Memory/ArrayPtr.cs
Normal file
123
src/Ryujinx.Common/Memory/ArrayPtr.cs
Normal file
|
@ -0,0 +1,123 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an array of unmanaged resources.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Array element type</typeparam>
|
||||
public unsafe struct ArrayPtr<T> : IEquatable<ArrayPtr<T>>, IArray<T> where T : unmanaged
|
||||
{
|
||||
private IntPtr _ptr;
|
||||
|
||||
/// <summary>
|
||||
/// Null pointer.
|
||||
/// </summary>
|
||||
public static ArrayPtr<T> Null => new ArrayPtr<T>() { _ptr = IntPtr.Zero };
|
||||
|
||||
/// <summary>
|
||||
/// True if the pointer is null, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsNull => _ptr == IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Number of elements on the array.
|
||||
/// </summary>
|
||||
public int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the item at the given index.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// No bounds checks are performed, this allows negative indexing,
|
||||
/// but care must be taken if the index may be out of bounds.
|
||||
/// </remarks>
|
||||
/// <param name="index">Index of the element</param>
|
||||
/// <returns>Reference to the element at the given index</returns>
|
||||
public ref T this[int index] => ref Unsafe.AsRef<T>((T*)_ptr + index);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new array from a given reference.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For data on the heap, proper pinning is necessary during
|
||||
/// use. Failure to do so will result in memory corruption and crashes.
|
||||
/// </remarks>
|
||||
/// <param name="value">Reference of the first array element</param>
|
||||
/// <param name="length">Number of elements on the array</param>
|
||||
public ArrayPtr(ref T value, int length)
|
||||
{
|
||||
_ptr = (IntPtr)Unsafe.AsPointer(ref value);
|
||||
Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new array from a given pointer.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Array base pointer</param>
|
||||
/// <param name="length">Number of elements on the array</param>
|
||||
public ArrayPtr(T* ptr, int length)
|
||||
{
|
||||
_ptr = (IntPtr)ptr;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new array from a given pointer.
|
||||
/// </summary>
|
||||
/// <param name="ptr">Array base pointer</param>
|
||||
/// <param name="length">Number of elements on the array</param>
|
||||
public ArrayPtr(IntPtr ptr, int length)
|
||||
{
|
||||
_ptr = ptr;
|
||||
Length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the array starting at the specified position.
|
||||
/// </summary>
|
||||
/// <param name="start">Index where the new array should start</param>
|
||||
/// <returns>New array starting at the specified position</returns>
|
||||
public ArrayPtr<T> Slice(int start) => new ArrayPtr<T>(ref this[start], Length - start);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a span from the array.
|
||||
/// </summary>
|
||||
/// <returns>Span of the array</returns>
|
||||
public Span<T> AsSpan() => Length == 0 ? Span<T>.Empty : MemoryMarshal.CreateSpan(ref this[0], Length);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the array base pointer.
|
||||
/// </summary>
|
||||
/// <returns>Base pointer</returns>
|
||||
public T* ToPointer() => (T*)_ptr;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ArrayPtr<T> other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals([AllowNull] ArrayPtr<T> other)
|
||||
{
|
||||
return _ptr == other._ptr && Length == other.Length;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(_ptr, Length);
|
||||
}
|
||||
|
||||
public static bool operator ==(ArrayPtr<T> left, ArrayPtr<T> right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ArrayPtr<T> left, ArrayPtr<T> right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Common/Memory/Box.cs
Normal file
12
src/Ryujinx.Common/Memory/Box.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public class Box<T> where T : unmanaged
|
||||
{
|
||||
public T Data;
|
||||
|
||||
public Box()
|
||||
{
|
||||
Data = new T();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public sealed partial class ByteMemoryPool
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="IMemoryOwner{Byte}"/> that wraps an array rented from
|
||||
/// <see cref="ArrayPool{Byte}.Shared"/> and exposes it as <see cref="Memory{Byte}"/>
|
||||
/// with a length of the requested size.
|
||||
/// </summary>
|
||||
private sealed class ByteMemoryPoolBuffer : IMemoryOwner<byte>
|
||||
{
|
||||
private byte[] _array;
|
||||
private readonly int _length;
|
||||
|
||||
public ByteMemoryPoolBuffer(int length)
|
||||
{
|
||||
_array = ArrayPool<byte>.Shared.Rent(length);
|
||||
_length = length;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <see cref="Memory{Byte}"/> belonging to this owner.
|
||||
/// </summary>
|
||||
public Memory<byte> Memory
|
||||
{
|
||||
get
|
||||
{
|
||||
byte[] array = _array;
|
||||
|
||||
ObjectDisposedException.ThrowIf(array is null, this);
|
||||
|
||||
return new Memory<byte>(array, 0, _length);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var array = Interlocked.Exchange(ref _array, null);
|
||||
|
||||
if (array != null)
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
108
src/Ryujinx.Common/Memory/ByteMemoryPool.cs
Normal file
108
src/Ryujinx.Common/Memory/ByteMemoryPool.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a pool of re-usable byte array instances.
|
||||
/// </summary>
|
||||
public sealed partial class ByteMemoryPool
|
||||
{
|
||||
private static readonly ByteMemoryPool _shared = new ByteMemoryPool();
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a <see cref="ByteMemoryPool"/> instance. Private to force access through
|
||||
/// the <see cref="ByteMemoryPool.Shared"/> instance.
|
||||
/// </summary>
|
||||
private ByteMemoryPool()
|
||||
{
|
||||
// No implementation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a shared <see cref="ByteMemoryPool"/> instance.
|
||||
/// </summary>
|
||||
public static ByteMemoryPool Shared => _shared;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum buffer size supported by this pool.
|
||||
/// </summary>
|
||||
public int MaxBufferSize => Array.MaxLength;
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> Rent(long length)
|
||||
=> RentImpl(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> Rent(ulong length)
|
||||
=> RentImpl(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer may contain data from a prior use.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> Rent(int length)
|
||||
=> RentImpl(length);
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> RentCleared(long length)
|
||||
=> RentCleared(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> RentCleared(ulong length)
|
||||
=> RentCleared(checked((int)length));
|
||||
|
||||
/// <summary>
|
||||
/// Rents a byte memory buffer from <see cref="ArrayPool{Byte}.Shared"/>.
|
||||
/// The buffer's contents are cleared (set to all 0s) before returning.
|
||||
/// </summary>
|
||||
/// <param name="length">The buffer's required length in bytes</param>
|
||||
/// <returns>A <see cref="IMemoryOwner{Byte}"/> wrapping the rented memory</returns>
|
||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
||||
public IMemoryOwner<byte> RentCleared(int length)
|
||||
{
|
||||
var buffer = RentImpl(length);
|
||||
|
||||
buffer.Memory.Span.Clear();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static ByteMemoryPoolBuffer RentImpl(int length)
|
||||
{
|
||||
if ((uint)length > Array.MaxLength)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(length), length, null);
|
||||
}
|
||||
|
||||
return new ByteMemoryPoolBuffer(length);
|
||||
}
|
||||
}
|
||||
}
|
21
src/Ryujinx.Common/Memory/IArray.cs
Normal file
21
src/Ryujinx.Common/Memory/IArray.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Array interface.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Element type</typeparam>
|
||||
public interface IArray<T> where T : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to index the array.
|
||||
/// </summary>
|
||||
/// <param name="index">Element index</param>
|
||||
/// <returns>Element at the specified index</returns>
|
||||
ref T this[int index] { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of elements on the array.
|
||||
/// </summary>
|
||||
int Length { get; }
|
||||
}
|
||||
}
|
99
src/Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
99
src/Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
|
@ -0,0 +1,99 @@
|
|||
using Microsoft.IO;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public static class MemoryStreamManager
|
||||
{
|
||||
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
|
||||
|
||||
/// <summary>
|
||||
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
|
||||
/// </summary>
|
||||
public static class Shared
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
|
||||
/// </summary>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream()
|
||||
=> new RecyclableMemoryStream(_shared);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(byte[] buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
|
||||
=> GetStream(Guid.NewGuid(), null, buffer);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||
stream.Write(buffer);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||
/// </summary>
|
||||
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
|
||||
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||
/// <param name="offset">The offset from the start of the buffer to copy from</param>
|
||||
/// <param name="count">The number of bytes to copy from the buffer</param>
|
||||
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
|
||||
{
|
||||
RecyclableMemoryStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||
stream.Write(buffer, offset, count);
|
||||
stream.Position = 0;
|
||||
return stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream?.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
|
||||
|
||||
namespace Ryujinx.Common.Memory.PartialUnmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple implementation of a ReaderWriterLock which can be used from native code.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct NativeReaderWriterLock
|
||||
{
|
||||
public int WriteLock;
|
||||
public int ReaderCount;
|
||||
|
||||
public static int WriteLockOffset;
|
||||
public static int ReaderCountOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Populates the field offsets for use when emitting native code.
|
||||
/// </summary>
|
||||
static NativeReaderWriterLock()
|
||||
{
|
||||
NativeReaderWriterLock instance = new NativeReaderWriterLock();
|
||||
|
||||
WriteLockOffset = OffsetOf(ref instance, ref instance.WriteLock);
|
||||
ReaderCountOffset = OffsetOf(ref instance, ref instance.ReaderCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires the reader lock.
|
||||
/// </summary>
|
||||
public void AcquireReaderLock()
|
||||
{
|
||||
// Must take write lock for a very short time to become a reader.
|
||||
|
||||
while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) { }
|
||||
|
||||
Interlocked.Increment(ref ReaderCount);
|
||||
|
||||
Interlocked.Exchange(ref WriteLock, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the reader lock.
|
||||
/// </summary>
|
||||
public void ReleaseReaderLock()
|
||||
{
|
||||
Interlocked.Decrement(ref ReaderCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Upgrades to a writer lock. The reader lock is temporarily released while obtaining the writer lock.
|
||||
/// </summary>
|
||||
public void UpgradeToWriterLock()
|
||||
{
|
||||
// Prevent any more threads from entering reader.
|
||||
// If the write lock is already taken, wait for it to not be taken.
|
||||
|
||||
Interlocked.Decrement(ref ReaderCount);
|
||||
|
||||
while (Interlocked.CompareExchange(ref WriteLock, 1, 0) != 0) { }
|
||||
|
||||
// Wait for reader count to drop to 0, then take the lock again as the only reader.
|
||||
|
||||
while (Interlocked.CompareExchange(ref ReaderCount, 1, 0) != 0) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downgrades from a writer lock, back to a reader one.
|
||||
/// </summary>
|
||||
public void DowngradeFromWriterLock()
|
||||
{
|
||||
// Release the WriteLock.
|
||||
|
||||
Interlocked.Exchange(ref WriteLock, 0);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory.PartialUnmaps
|
||||
{
|
||||
static class PartialUnmapHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Calculates a byte offset of a given field within a struct.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Struct type</typeparam>
|
||||
/// <typeparam name="T2">Field type</typeparam>
|
||||
/// <param name="storage">Parent struct</param>
|
||||
/// <param name="target">Field</param>
|
||||
/// <returns>The byte offset of the given field in the given struct</returns>
|
||||
public static int OffsetOf<T, T2>(ref T2 storage, ref T target)
|
||||
{
|
||||
return (int)Unsafe.ByteOffset(ref Unsafe.As<T2, T>(ref storage), ref target);
|
||||
}
|
||||
}
|
||||
}
|
163
src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs
Normal file
163
src/Ryujinx.Common/Memory/PartialUnmaps/PartialUnmapState.cs
Normal file
|
@ -0,0 +1,163 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.Marshalling;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
|
||||
|
||||
namespace Ryujinx.Common.Memory.PartialUnmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// State for partial unmaps. Intended to be used on Windows.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public partial struct PartialUnmapState
|
||||
{
|
||||
public NativeReaderWriterLock PartialUnmapLock;
|
||||
public int PartialUnmapsCount;
|
||||
public ThreadLocalMap<int> LocalCounts;
|
||||
|
||||
public readonly static int PartialUnmapLockOffset;
|
||||
public readonly static int PartialUnmapsCountOffset;
|
||||
public readonly static int LocalCountsOffset;
|
||||
|
||||
public readonly static IntPtr GlobalState;
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll")]
|
||||
private static partial int GetCurrentThreadId();
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
private static partial IntPtr OpenThread(int dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwThreadId);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs (UnmanagedType.Bool)]
|
||||
private static partial bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[SupportedOSPlatform("windows")]
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a global static PartialUnmapState and populates the field offsets.
|
||||
/// </summary>
|
||||
static unsafe PartialUnmapState()
|
||||
{
|
||||
PartialUnmapState instance = new PartialUnmapState();
|
||||
|
||||
PartialUnmapLockOffset = OffsetOf(ref instance, ref instance.PartialUnmapLock);
|
||||
PartialUnmapsCountOffset = OffsetOf(ref instance, ref instance.PartialUnmapsCount);
|
||||
LocalCountsOffset = OffsetOf(ref instance, ref instance.LocalCounts);
|
||||
|
||||
int size = Unsafe.SizeOf<PartialUnmapState>();
|
||||
GlobalState = Marshal.AllocHGlobal(size);
|
||||
Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the global state.
|
||||
/// </summary>
|
||||
public static unsafe void Reset()
|
||||
{
|
||||
int size = Unsafe.SizeOf<PartialUnmapState>();
|
||||
Unsafe.InitBlockUnaligned((void*)GlobalState, 0, (uint)size);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the global state.
|
||||
/// </summary>
|
||||
/// <returns>A reference to the global state</returns>
|
||||
public static unsafe ref PartialUnmapState GetRef()
|
||||
{
|
||||
return ref Unsafe.AsRef<PartialUnmapState>((void*)GlobalState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an access violation handler should retry execution due to a fault caused by partial unmap.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Due to Windows limitations, <see cref="UnmapView"/> might need to unmap more memory than requested.
|
||||
/// The additional memory that was unmapped is later remapped, however this leaves a time gap where the
|
||||
/// memory might be accessed but is unmapped. Users of the API must compensate for that by catching the
|
||||
/// access violation and retrying if it happened between the unmap and remap operation.
|
||||
/// This method can be used to decide if retrying in such cases is necessary or not.
|
||||
///
|
||||
/// This version of the function is not used, but serves as a reference for the native
|
||||
/// implementation in ARMeilleure.
|
||||
/// </remarks>
|
||||
/// <returns>True if execution should be retried, false otherwise</returns>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public bool RetryFromAccessViolation()
|
||||
{
|
||||
PartialUnmapLock.AcquireReaderLock();
|
||||
|
||||
int threadID = GetCurrentThreadId();
|
||||
int threadIndex = LocalCounts.GetOrReserve(threadID, 0);
|
||||
|
||||
if (threadIndex == -1)
|
||||
{
|
||||
// Out of thread local space... try again later.
|
||||
|
||||
PartialUnmapLock.ReleaseReaderLock();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ref int threadLocalPartialUnmapsCount = ref LocalCounts.GetValue(threadIndex);
|
||||
|
||||
bool retry = threadLocalPartialUnmapsCount != PartialUnmapsCount;
|
||||
if (retry)
|
||||
{
|
||||
threadLocalPartialUnmapsCount = PartialUnmapsCount;
|
||||
}
|
||||
|
||||
PartialUnmapLock.ReleaseReaderLock();
|
||||
|
||||
return retry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Iterates and trims threads in the thread -> count map that
|
||||
/// are no longer active.
|
||||
/// </summary>
|
||||
[SupportedOSPlatform("windows")]
|
||||
public void TrimThreads()
|
||||
{
|
||||
const uint ExitCodeStillActive = 259;
|
||||
const int ThreadQueryInformation = 0x40;
|
||||
|
||||
Span<int> ids = LocalCounts.ThreadIds.AsSpan();
|
||||
|
||||
for (int i = 0; i < ids.Length; i++)
|
||||
{
|
||||
int id = ids[i];
|
||||
|
||||
if (id != 0)
|
||||
{
|
||||
IntPtr handle = OpenThread(ThreadQueryInformation, false, (uint)id);
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
Interlocked.CompareExchange(ref ids[i], 0, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetExitCodeThread(handle, out uint exitCode);
|
||||
|
||||
if (exitCode != ExitCodeStillActive)
|
||||
{
|
||||
Interlocked.CompareExchange(ref ids[i], 0, id);
|
||||
}
|
||||
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs
Normal file
92
src/Ryujinx.Common/Memory/PartialUnmaps/ThreadLocalMap.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Common.Memory.PartialUnmaps.PartialUnmapHelpers;
|
||||
|
||||
namespace Ryujinx.Common.Memory.PartialUnmaps
|
||||
{
|
||||
/// <summary>
|
||||
/// A simple fixed size thread safe map that can be used from native code.
|
||||
/// Integer thread IDs map to corresponding structs.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The value type for the map</typeparam>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct ThreadLocalMap<T> where T : unmanaged
|
||||
{
|
||||
public const int MapSize = 20;
|
||||
|
||||
public Array20<int> ThreadIds;
|
||||
public Array20<T> Structs;
|
||||
|
||||
public static int ThreadIdsOffset;
|
||||
public static int StructsOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Populates the field offsets for use when emitting native code.
|
||||
/// </summary>
|
||||
static ThreadLocalMap()
|
||||
{
|
||||
ThreadLocalMap<T> instance = new ThreadLocalMap<T>();
|
||||
|
||||
ThreadIdsOffset = OffsetOf(ref instance, ref instance.ThreadIds);
|
||||
StructsOffset = OffsetOf(ref instance, ref instance.Structs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of a given thread ID in the map, or reserves one.
|
||||
/// When reserving a struct, its value is set to the given initial value.
|
||||
/// Returns -1 when there is no space to reserve a new entry.
|
||||
/// </summary>
|
||||
/// <param name="threadId">Thread ID to use as a key</param>
|
||||
/// <param name="initial">Initial value of the associated struct.</param>
|
||||
/// <returns>The index of the entry, or -1 if none</returns>
|
||||
public int GetOrReserve(int threadId, T initial)
|
||||
{
|
||||
// Try get a match first.
|
||||
|
||||
for (int i = 0; i < MapSize; i++)
|
||||
{
|
||||
int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, threadId);
|
||||
|
||||
if (compare == threadId)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// Try get a free entry. Since the id is assumed to be unique to this thread, we know it doesn't exist yet.
|
||||
|
||||
for (int i = 0; i < MapSize; i++)
|
||||
{
|
||||
int compare = Interlocked.CompareExchange(ref ThreadIds[i], threadId, 0);
|
||||
|
||||
if (compare == 0)
|
||||
{
|
||||
Structs[i] = initial;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the struct value for a given map entry.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the entry</param>
|
||||
/// <returns>A reference to the struct value</returns>
|
||||
public ref T GetValue(int index)
|
||||
{
|
||||
return ref Structs[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases an entry from the map.
|
||||
/// </summary>
|
||||
/// <param name="index">Index of the entry to release</param>
|
||||
public void Release(int index)
|
||||
{
|
||||
Interlocked.Exchange(ref ThreadIds[index], 0);
|
||||
}
|
||||
}
|
||||
}
|
68
src/Ryujinx.Common/Memory/Ptr.cs
Normal file
68
src/Ryujinx.Common/Memory/Ptr.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a pointer to an unmanaged resource.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the unmanaged resource</typeparam>
|
||||
public unsafe struct Ptr<T> : IEquatable<Ptr<T>> where T : unmanaged
|
||||
{
|
||||
private IntPtr _ptr;
|
||||
|
||||
/// <summary>
|
||||
/// Null pointer.
|
||||
/// </summary>
|
||||
public static Ptr<T> Null => new Ptr<T>() { _ptr = IntPtr.Zero };
|
||||
|
||||
/// <summary>
|
||||
/// True if the pointer is null, false otherwise.
|
||||
/// </summary>
|
||||
public bool IsNull => _ptr == IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the value.
|
||||
/// </summary>
|
||||
public ref T Value => ref Unsafe.AsRef<T>((void*)_ptr);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new pointer to an unmanaged resource.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For data on the heap, proper pinning is necessary during
|
||||
/// use. Failure to do so will result in memory corruption and crashes.
|
||||
/// </remarks>
|
||||
/// <param name="value">Reference to the unmanaged resource</param>
|
||||
public Ptr(ref T value)
|
||||
{
|
||||
_ptr = (IntPtr)Unsafe.AsPointer(ref value);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Ptr<T> other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals([AllowNull] Ptr<T> other)
|
||||
{
|
||||
return _ptr == other._ptr;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _ptr.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(Ptr<T> left, Ptr<T> right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Ptr<T> left, Ptr<T> right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
||||
}
|
89
src/Ryujinx.Common/Memory/SpanOrArray.cs
Normal file
89
src/Ryujinx.Common/Memory/SpanOrArray.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
/// <summary>
|
||||
/// A struct that can represent both a Span and Array.
|
||||
/// This is useful to keep the Array representation when possible to avoid copies.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Element Type</typeparam>
|
||||
public readonly ref struct SpanOrArray<T> where T : unmanaged
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly ReadOnlySpan<T> Span;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from an array.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to store</param>
|
||||
public SpanOrArray(T[] array)
|
||||
{
|
||||
Array = array;
|
||||
Span = ReadOnlySpan<T>.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new SpanOrArray from a readonly span.
|
||||
/// </summary>
|
||||
/// <param name="array">Span to store</param>
|
||||
public SpanOrArray(ReadOnlySpan<T> span)
|
||||
{
|
||||
Array = null;
|
||||
Span = span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the contained array, or convert the span if necessary.
|
||||
/// </summary>
|
||||
/// <returns>An array containing the data</returns>
|
||||
public T[] ToArray()
|
||||
{
|
||||
return Array ?? Span.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a ReadOnlySpan from either the array or ReadOnlySpan.
|
||||
/// </summary>
|
||||
/// <returns>A ReadOnlySpan containing the data</returns>
|
||||
public ReadOnlySpan<T> AsSpan()
|
||||
{
|
||||
return Array ?? Span;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast an array to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="array">Source array</param>
|
||||
public static implicit operator SpanOrArray<T>(T[] array)
|
||||
{
|
||||
return new SpanOrArray<T>(array);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a ReadOnlySpan to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source ReadOnlySpan</param>
|
||||
public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a Span to a SpanOrArray.
|
||||
/// </summary>
|
||||
/// <param name="span">Source Span</param>
|
||||
public static implicit operator SpanOrArray<T>(Span<T> span)
|
||||
{
|
||||
return new SpanOrArray<T>(span);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cast a SpanOrArray to a ReadOnlySpan
|
||||
/// </summary>
|
||||
/// <param name="spanOrArray">Source SpanOrArray</param>
|
||||
public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray)
|
||||
{
|
||||
return spanOrArray.AsSpan();
|
||||
}
|
||||
}
|
||||
}
|
56
src/Ryujinx.Common/Memory/SpanReader.cs
Normal file
56
src/Ryujinx.Common/Memory/SpanReader.cs
Normal file
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public ref struct SpanReader
|
||||
{
|
||||
private ReadOnlySpan<byte> _input;
|
||||
|
||||
public int Length => _input.Length;
|
||||
|
||||
public SpanReader(ReadOnlySpan<byte> input)
|
||||
{
|
||||
_input = input;
|
||||
}
|
||||
|
||||
public T Read<T>() where T : unmanaged
|
||||
{
|
||||
T value = MemoryMarshal.Cast<byte, T>(_input)[0];
|
||||
|
||||
_input = _input.Slice(Unsafe.SizeOf<T>());
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetSpan(int size)
|
||||
{
|
||||
ReadOnlySpan<byte> data = _input.Slice(0, size);
|
||||
|
||||
_input = _input.Slice(size);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetSpanSafe(int size)
|
||||
{
|
||||
return GetSpan((int)Math.Min((uint)_input.Length, (uint)size));
|
||||
}
|
||||
|
||||
public T ReadAt<T>(int offset) where T : unmanaged
|
||||
{
|
||||
return MemoryMarshal.Cast<byte, T>(_input.Slice(offset))[0];
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> GetSpanAt(int offset, int size)
|
||||
{
|
||||
return _input.Slice(offset, size);
|
||||
}
|
||||
|
||||
public void Skip(int size)
|
||||
{
|
||||
_input = _input.Slice(size);
|
||||
}
|
||||
}
|
||||
}
|
45
src/Ryujinx.Common/Memory/SpanWriter.cs
Normal file
45
src/Ryujinx.Common/Memory/SpanWriter.cs
Normal file
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public ref struct SpanWriter
|
||||
{
|
||||
private Span<byte> _output;
|
||||
|
||||
public int Length => _output.Length;
|
||||
|
||||
public SpanWriter(Span<byte> output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
public void Write<T>(T value) where T : unmanaged
|
||||
{
|
||||
MemoryMarshal.Cast<byte, T>(_output)[0] = value;
|
||||
_output = _output.Slice(Unsafe.SizeOf<T>());
|
||||
}
|
||||
|
||||
public void Write(ReadOnlySpan<byte> data)
|
||||
{
|
||||
data.CopyTo(_output.Slice(0, data.Length));
|
||||
_output = _output.Slice(data.Length);
|
||||
}
|
||||
|
||||
public void WriteAt<T>(int offset, T value) where T : unmanaged
|
||||
{
|
||||
MemoryMarshal.Cast<byte, T>(_output.Slice(offset))[0] = value;
|
||||
}
|
||||
|
||||
public void WriteAt(int offset, ReadOnlySpan<byte> data)
|
||||
{
|
||||
data.CopyTo(_output.Slice(offset, data.Length));
|
||||
}
|
||||
|
||||
public void Skip(int size)
|
||||
{
|
||||
_output = _output.Slice(size);
|
||||
}
|
||||
}
|
||||
}
|
654
src/Ryujinx.Common/Memory/StructArrayHelpers.cs
Normal file
654
src/Ryujinx.Common/Memory/StructArrayHelpers.cs
Normal file
|
@ -0,0 +1,654 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public struct Array1<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
public int Length => 1;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 1);
|
||||
}
|
||||
public struct Array2<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array1<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 2;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 2);
|
||||
}
|
||||
public struct Array3<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array2<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 3;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 3);
|
||||
}
|
||||
public struct Array4<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array3<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 4;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 4);
|
||||
}
|
||||
public struct Array5<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array4<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 5;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 5);
|
||||
}
|
||||
public struct Array6<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array5<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 6;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 6);
|
||||
}
|
||||
public struct Array7<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array6<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 7;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 7);
|
||||
}
|
||||
public struct Array8<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array7<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 8;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 8);
|
||||
}
|
||||
public struct Array9<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array8<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 9;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 9);
|
||||
}
|
||||
public struct Array10<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array9<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 10;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 10);
|
||||
}
|
||||
public struct Array11<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array10<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 11;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 11);
|
||||
}
|
||||
public struct Array12<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array11<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 12;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 12);
|
||||
}
|
||||
public struct Array13<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array12<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 13;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 13);
|
||||
}
|
||||
public struct Array14<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array13<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 14;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 14);
|
||||
}
|
||||
public struct Array15<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array14<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 15;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 15);
|
||||
}
|
||||
public struct Array16<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array15<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 16;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 16);
|
||||
}
|
||||
public struct Array17<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array16<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 17;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 17);
|
||||
}
|
||||
public struct Array18<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array17<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 18;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 18);
|
||||
}
|
||||
public struct Array19<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array18<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 19;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 19);
|
||||
}
|
||||
public struct Array20<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array19<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 20;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 20);
|
||||
}
|
||||
public struct Array21<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array20<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 21;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 21);
|
||||
}
|
||||
public struct Array22<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array21<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 22;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 22);
|
||||
}
|
||||
public struct Array23<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array22<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 23;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 23);
|
||||
}
|
||||
public struct Array24<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array23<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 24;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 24);
|
||||
}
|
||||
public struct Array25<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array24<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 25;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 25);
|
||||
}
|
||||
public struct Array26<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array25<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 26;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 26);
|
||||
}
|
||||
public struct Array27<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array26<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 27;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 27);
|
||||
}
|
||||
public struct Array28<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array27<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 28;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 28);
|
||||
}
|
||||
public struct Array29<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array28<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 29;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 29);
|
||||
}
|
||||
public struct Array30<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array29<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 30;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 30);
|
||||
}
|
||||
public struct Array31<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array30<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 31;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 31);
|
||||
}
|
||||
public struct Array32<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array31<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 32;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 32);
|
||||
}
|
||||
public struct Array33<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array32<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 33;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 33);
|
||||
}
|
||||
public struct Array34<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array33<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 34;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 34);
|
||||
}
|
||||
public struct Array35<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array34<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 35;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 35);
|
||||
}
|
||||
public struct Array36<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array35<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 36;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 36);
|
||||
}
|
||||
public struct Array37<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array36<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 37;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 37);
|
||||
}
|
||||
public struct Array38<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array37<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 38;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 38);
|
||||
}
|
||||
public struct Array39<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array38<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 39;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 39);
|
||||
}
|
||||
public struct Array40<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array39<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 40;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 40);
|
||||
}
|
||||
public struct Array41<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array40<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 41;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 41);
|
||||
}
|
||||
public struct Array42<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array41<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 42;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 42);
|
||||
}
|
||||
public struct Array43<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array42<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 43;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 43);
|
||||
}
|
||||
public struct Array44<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array43<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 44;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 44);
|
||||
}
|
||||
public struct Array45<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array44<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 45;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 45);
|
||||
}
|
||||
public struct Array46<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array45<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 46;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 46);
|
||||
}
|
||||
public struct Array47<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array46<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 47;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 47);
|
||||
}
|
||||
public struct Array48<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array47<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 48;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 48);
|
||||
}
|
||||
public struct Array49<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array48<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 49;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 49);
|
||||
}
|
||||
public struct Array50<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array49<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 50;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 50);
|
||||
}
|
||||
public struct Array51<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array50<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 51;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 51);
|
||||
}
|
||||
public struct Array52<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array51<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 52;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 52);
|
||||
}
|
||||
public struct Array53<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array52<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 53;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 53);
|
||||
}
|
||||
public struct Array54<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array53<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 54;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 54);
|
||||
}
|
||||
public struct Array55<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array54<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 55;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 55);
|
||||
}
|
||||
public struct Array56<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array55<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 56;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 56);
|
||||
}
|
||||
public struct Array57<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array56<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 57;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 57);
|
||||
}
|
||||
public struct Array58<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array57<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 58;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 58);
|
||||
}
|
||||
public struct Array59<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array58<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 59;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 59);
|
||||
}
|
||||
public struct Array60<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array59<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 60;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 60);
|
||||
}
|
||||
public struct Array61<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array60<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 61;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 61);
|
||||
}
|
||||
public struct Array62<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array61<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 62;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 62);
|
||||
}
|
||||
public struct Array63<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array62<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 63;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 63);
|
||||
}
|
||||
public struct Array64<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array63<T> _other;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 64;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 64);
|
||||
}
|
||||
public struct Array73<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
#pragma warning disable CS0169
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
Array8<T> _other2;
|
||||
#pragma warning restore CS0169
|
||||
public int Length => 73;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, 73);
|
||||
}
|
||||
}
|
77
src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs
Normal file
77
src/Ryujinx.Common/Memory/StructByteArrayHelpers.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray128 : IArray<byte>
|
||||
{
|
||||
private const int Size = 128;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray256 : IArray<byte>
|
||||
{
|
||||
private const int Size = 256;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray512 : IArray<byte>
|
||||
{
|
||||
private const int Size = 512;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray1024 : IArray<byte>
|
||||
{
|
||||
private const int Size = 1024;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray2048 : IArray<byte>
|
||||
{
|
||||
private const int Size = 2048;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray4096 : IArray<byte>
|
||||
{
|
||||
private const int Size = 4096;
|
||||
|
||||
byte _element;
|
||||
|
||||
public int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
}
|
82
src/Ryujinx.Common/PerformanceCounter.cs
Normal file
82
src/Ryujinx.Common/PerformanceCounter.cs
Normal file
|
@ -0,0 +1,82 @@
|
|||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class PerformanceCounter
|
||||
{
|
||||
private static double _ticksToNs;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of ticks in 1 day.
|
||||
/// </summary>
|
||||
public static long TicksPerDay { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of ticks in 1 hour.
|
||||
/// </summary>
|
||||
public static long TicksPerHour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of ticks in 1 minute.
|
||||
/// </summary>
|
||||
public static long TicksPerMinute { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of ticks in 1 second.
|
||||
/// </summary>
|
||||
public static long TicksPerSecond { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Represents the number of ticks in 1 millisecond.
|
||||
/// </summary>
|
||||
public static long TicksPerMillisecond { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of ticks elapsed since the system started.
|
||||
/// </summary>
|
||||
public static long ElapsedTicks
|
||||
{
|
||||
get
|
||||
{
|
||||
return Stopwatch.GetTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of milliseconds elapsed since the system started.
|
||||
/// </summary>
|
||||
public static long ElapsedMilliseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
long timestamp = Stopwatch.GetTimestamp();
|
||||
|
||||
return timestamp / TicksPerMillisecond;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of nanoseconds elapsed since the system started.
|
||||
/// </summary>
|
||||
public static long ElapsedNanoseconds
|
||||
{
|
||||
get
|
||||
{
|
||||
long timestamp = Stopwatch.GetTimestamp();
|
||||
|
||||
return (long)(timestamp * _ticksToNs);
|
||||
}
|
||||
}
|
||||
|
||||
static PerformanceCounter()
|
||||
{
|
||||
TicksPerMillisecond = Stopwatch.Frequency / 1000;
|
||||
TicksPerSecond = Stopwatch.Frequency;
|
||||
TicksPerMinute = TicksPerSecond * 60;
|
||||
TicksPerHour = TicksPerMinute * 60;
|
||||
TicksPerDay = TicksPerHour * 24;
|
||||
|
||||
_ticksToNs = 1000000000.0 / Stopwatch.Frequency;
|
||||
}
|
||||
}
|
||||
}
|
75
src/Ryujinx.Common/Pools/ObjectPool.cs
Normal file
75
src/Ryujinx.Common/Pools/ObjectPool.cs
Normal file
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class ObjectPool<T>
|
||||
where T : class
|
||||
{
|
||||
private T _firstItem;
|
||||
private readonly T[] _items;
|
||||
|
||||
private readonly Func<T> _factory;
|
||||
|
||||
public ObjectPool(Func<T> factory, int size)
|
||||
{
|
||||
_items = new T[size - 1];
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public T Allocate()
|
||||
{
|
||||
T instance = _firstItem;
|
||||
|
||||
if (instance == null || instance != Interlocked.CompareExchange(ref _firstItem, null, instance))
|
||||
{
|
||||
instance = AllocateInternal();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private T AllocateInternal()
|
||||
{
|
||||
T[] items = _items;
|
||||
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
T instance = items[i];
|
||||
|
||||
if (instance != null && instance == Interlocked.CompareExchange(ref items[i], null, instance))
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
return _factory();
|
||||
}
|
||||
|
||||
public void Release(T obj)
|
||||
{
|
||||
if (_firstItem == null)
|
||||
{
|
||||
_firstItem = obj;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReleaseInternal(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReleaseInternal(T obj)
|
||||
{
|
||||
T[] items = _items;
|
||||
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (items[i] == null)
|
||||
{
|
||||
items[i] = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Common/Pools/SharedPools.cs
Normal file
17
src/Ryujinx.Common/Pools/SharedPools.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Ryujinx.Common
|
||||
{
|
||||
public static class SharedPools
|
||||
{
|
||||
private static class DefaultPool<T>
|
||||
where T : class, new()
|
||||
{
|
||||
public static readonly ObjectPool<T> Instance = new ObjectPool<T>(() => new T(), 20);
|
||||
}
|
||||
|
||||
public static ObjectPool<T> Default<T>()
|
||||
where T : class, new()
|
||||
{
|
||||
return DefaultPool<T>.Instance;
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ryujinx.Common/Pools/ThreadStaticArray.cs
Normal file
20
src/Ryujinx.Common/Pools/ThreadStaticArray.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Pools
|
||||
{
|
||||
public static class ThreadStaticArray<T>
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static T[] _array;
|
||||
|
||||
public static ref T[] Get()
|
||||
{
|
||||
if (_array == null)
|
||||
{
|
||||
_array = new T[1];
|
||||
}
|
||||
|
||||
return ref _array;
|
||||
}
|
||||
}
|
||||
}
|
61
src/Ryujinx.Common/ReactiveObject.cs
Normal file
61
src/Ryujinx.Common/ReactiveObject.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class ReactiveObject<T>
|
||||
{
|
||||
private ReaderWriterLock _readerWriterLock = new ReaderWriterLock();
|
||||
private bool _isInitialized = false;
|
||||
private T _value;
|
||||
|
||||
public event EventHandler<ReactiveEventArgs<T>> Event;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
_readerWriterLock.AcquireReaderLock(Timeout.Infinite);
|
||||
T value = _value;
|
||||
_readerWriterLock.ReleaseReaderLock();
|
||||
|
||||
return value;
|
||||
}
|
||||
set
|
||||
{
|
||||
_readerWriterLock.AcquireWriterLock(Timeout.Infinite);
|
||||
|
||||
T oldValue = _value;
|
||||
|
||||
bool oldIsInitialized = _isInitialized;
|
||||
|
||||
_isInitialized = true;
|
||||
_value = value;
|
||||
|
||||
_readerWriterLock.ReleaseWriterLock();
|
||||
|
||||
if (!oldIsInitialized || oldValue == null || !oldValue.Equals(_value))
|
||||
{
|
||||
Event?.Invoke(this, new ReactiveEventArgs<T>(oldValue, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator T(ReactiveObject<T> obj)
|
||||
{
|
||||
return obj.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public class ReactiveEventArgs<T>
|
||||
{
|
||||
public T OldValue { get; }
|
||||
public T NewValue { get; }
|
||||
|
||||
public ReactiveEventArgs(T oldValue, T newValue)
|
||||
{
|
||||
OldValue = oldValue;
|
||||
NewValue = newValue;
|
||||
}
|
||||
}
|
||||
}
|
19
src/Ryujinx.Common/ReferenceEqualityComparer.cs
Normal file
19
src/Ryujinx.Common/ReferenceEqualityComparer.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
public class ReferenceEqualityComparer<T> : IEqualityComparer<T>
|
||||
where T : class
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
return x == y;
|
||||
}
|
||||
|
||||
public int GetHashCode([DisallowNull] T obj)
|
||||
{
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
60
src/Ryujinx.Common/ReleaseInformation.cs
Normal file
60
src/Ryujinx.Common/ReleaseInformation.cs
Normal file
|
@ -0,0 +1,60 @@
|
|||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
{
|
||||
// DO NOT EDIT, filled by CI
|
||||
public static class ReleaseInformation
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public static string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public static string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public static string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
public static string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||
public static string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||
|
||||
public static bool IsValid()
|
||||
{
|
||||
return !BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
return BuildVersion;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
}
|
||||
|
||||
#if FORCE_EXTERNAL_BASE_DIR
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
#else
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
15
src/Ryujinx.Common/Ryujinx.Common.csproj
Normal file
15
src/Ryujinx.Common/Ryujinx.Common.csproj
Normal file
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||
<PackageReference Include="MsgPack.Cli" />
|
||||
<PackageReference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
80
src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs
Normal file
80
src/Ryujinx.Common/SystemInfo/LinuxSystemInfo.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
class LinuxSystemInfo : SystemInfo
|
||||
{
|
||||
internal LinuxSystemInfo()
|
||||
{
|
||||
string cpuName = GetCpuidCpuName();
|
||||
|
||||
if (cpuName == null)
|
||||
{
|
||||
var cpuDict = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["model name"] = null,
|
||||
["Processor"] = null,
|
||||
["Hardware"] = null
|
||||
};
|
||||
|
||||
ParseKeyValues("/proc/cpuinfo", cpuDict);
|
||||
|
||||
cpuName = cpuDict["model name"] ?? cpuDict["Processor"] ?? cpuDict["Hardware"] ?? "Unknown";
|
||||
}
|
||||
|
||||
var memDict = new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["MemTotal"] = null,
|
||||
["MemAvailable"] = null
|
||||
};
|
||||
|
||||
ParseKeyValues("/proc/meminfo", memDict);
|
||||
|
||||
// Entries are in KiB
|
||||
ulong.TryParse(memDict["MemTotal"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong totalKiB);
|
||||
ulong.TryParse(memDict["MemAvailable"]?.Split(' ')[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong availableKiB);
|
||||
|
||||
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
||||
RamTotal = totalKiB * 1024;
|
||||
RamAvailable = availableKiB * 1024;
|
||||
}
|
||||
|
||||
private static void ParseKeyValues(string filePath, Dictionary<string, string> itemDict)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"File \"{filePath}\" not found");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int count = itemDict.Count;
|
||||
|
||||
using (StreamReader file = new StreamReader(filePath))
|
||||
{
|
||||
string line;
|
||||
while ((line = file.ReadLine()) != null)
|
||||
{
|
||||
string[] kvPair = line.Split(':', 2, StringSplitOptions.TrimEntries);
|
||||
|
||||
if (kvPair.Length < 2) continue;
|
||||
|
||||
string key = kvPair[0];
|
||||
|
||||
if (itemDict.TryGetValue(key, out string value) && value == null)
|
||||
{
|
||||
itemDict[key] = kvPair[1];
|
||||
|
||||
if (--count <= 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs
Normal file
157
src/Ryujinx.Common/SystemInfo/MacOSSystemInfo.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
partial class MacOSSystemInfo : SystemInfo
|
||||
{
|
||||
internal MacOSSystemInfo()
|
||||
{
|
||||
string cpuName = GetCpuidCpuName();
|
||||
|
||||
if (cpuName == null && sysctlbyname("machdep.cpu.brand_string", out cpuName) != 0)
|
||||
{
|
||||
cpuName = "Unknown";
|
||||
}
|
||||
|
||||
ulong totalRAM = 0;
|
||||
|
||||
if (sysctlbyname("hw.memsize", ref totalRAM) != 0) // Bytes
|
||||
{
|
||||
totalRAM = 0;
|
||||
}
|
||||
|
||||
CpuName = $"{cpuName} ; {LogicalCoreCount} logical";
|
||||
RamTotal = totalRAM;
|
||||
RamAvailable = GetVMInfoAvailableMemory();
|
||||
}
|
||||
|
||||
static ulong GetVMInfoAvailableMemory()
|
||||
{
|
||||
var port = mach_host_self();
|
||||
|
||||
uint pageSize = 0;
|
||||
var result = host_page_size(port, ref pageSize);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_page_size() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int flavor = 4; // HOST_VM_INFO64
|
||||
uint count = (uint)(Marshal.SizeOf<VMStatistics64>() / sizeof(int)); // HOST_VM_INFO64_COUNT
|
||||
VMStatistics64 stats = new();
|
||||
result = host_statistics64(port, flavor, ref stats, ref count);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to query Available RAM. host_statistics64() error = {result}");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (ulong)(stats.FreeCount + stats.InactiveCount) * pageSize;
|
||||
}
|
||||
|
||||
private const string SystemLibraryName = "libSystem.dylib";
|
||||
|
||||
[LibraryImport(SystemLibraryName, SetLastError = true)]
|
||||
private static partial int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string name, IntPtr oldValue, ref ulong oldSize, IntPtr newValue, ulong newValueSize);
|
||||
|
||||
private static int sysctlbyname(string name, IntPtr oldValue, ref ulong oldSize)
|
||||
{
|
||||
if (sysctlbyname(name, oldValue, ref oldSize, IntPtr.Zero, 0) == -1)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
|
||||
Logger.Error?.Print(LogClass.Application, $"Cannot retrieve '{name}'. Error Code {err}");
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int sysctlbyname<T>(string name, ref T oldValue)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
ulong oldValueSize = (ulong)Unsafe.SizeOf<T>();
|
||||
|
||||
return sysctlbyname(name, (IntPtr)Unsafe.AsPointer(ref oldValue), ref oldValueSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static int sysctlbyname(string name, out string oldValue)
|
||||
{
|
||||
oldValue = default;
|
||||
|
||||
ulong strSize = 0;
|
||||
|
||||
int res = sysctlbyname(name, IntPtr.Zero, ref strSize);
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
byte[] rawData = new byte[strSize];
|
||||
|
||||
unsafe
|
||||
{
|
||||
fixed (byte* rawDataPtr = rawData)
|
||||
{
|
||||
res = sysctlbyname(name, (IntPtr)rawDataPtr, ref strSize);
|
||||
}
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
oldValue = Encoding.ASCII.GetString(rawData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
[LibraryImport(SystemLibraryName, SetLastError = true)]
|
||||
private static partial uint mach_host_self();
|
||||
|
||||
[LibraryImport(SystemLibraryName, SetLastError = true)]
|
||||
private static partial int host_page_size(uint host, ref uint out_page_size);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 8)]
|
||||
struct VMStatistics64
|
||||
{
|
||||
public uint FreeCount;
|
||||
public uint ActiveCount;
|
||||
public uint InactiveCount;
|
||||
public uint WireCount;
|
||||
public ulong ZeroFillCount;
|
||||
public ulong Reactivations;
|
||||
public ulong Pageins;
|
||||
public ulong Pageouts;
|
||||
public ulong Faults;
|
||||
public ulong CowFaults;
|
||||
public ulong Lookups;
|
||||
public ulong Hits;
|
||||
public ulong Purges;
|
||||
public uint PurgeableCount;
|
||||
public uint SpeculativeCount;
|
||||
public ulong Decompressions;
|
||||
public ulong Compressions;
|
||||
public ulong Swapins;
|
||||
public ulong Swapouts;
|
||||
public uint CompressorPageCount;
|
||||
public uint ThrottledCount;
|
||||
public uint ExternalPageCount;
|
||||
public uint InternalPageCount;
|
||||
public ulong TotalUncompressedPagesInCompressor;
|
||||
}
|
||||
|
||||
[LibraryImport(SystemLibraryName, SetLastError = true)]
|
||||
private static partial int host_statistics64(uint host_priv, int host_flavor, ref VMStatistics64 host_info64_out, ref uint host_info64_outCnt);
|
||||
}
|
||||
}
|
80
src/Ryujinx.Common/SystemInfo/SystemInfo.cs
Normal file
80
src/Ryujinx.Common/SystemInfo/SystemInfo.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
public class SystemInfo
|
||||
{
|
||||
public string OsDescription { get; protected set; }
|
||||
public string CpuName { get; protected set; }
|
||||
public ulong RamTotal { get; protected set; }
|
||||
public ulong RamAvailable { get; protected set; }
|
||||
protected static int LogicalCoreCount => Environment.ProcessorCount;
|
||||
|
||||
protected SystemInfo()
|
||||
{
|
||||
OsDescription = $"{RuntimeInformation.OSDescription} ({RuntimeInformation.OSArchitecture})";
|
||||
CpuName = "Unknown";
|
||||
}
|
||||
|
||||
private static string ToMiBString(ulong bytesValue) => (bytesValue == 0) ? "Unknown" : $"{bytesValue / 1024 / 1024} MiB";
|
||||
|
||||
public void Print()
|
||||
{
|
||||
Logger.Notice.Print(LogClass.Application, $"Operating System: {OsDescription}");
|
||||
Logger.Notice.Print(LogClass.Application, $"CPU: {CpuName}");
|
||||
Logger.Notice.Print(LogClass.Application, $"RAM: Total {ToMiBString(RamTotal)} ; Available {ToMiBString(RamAvailable)}");
|
||||
}
|
||||
|
||||
public static SystemInfo Gather()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
return new WindowsSystemInfo();
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
return new LinuxSystemInfo();
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
return new MacOSSystemInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "SystemInfo unsupported on this platform");
|
||||
|
||||
return new SystemInfo();
|
||||
}
|
||||
}
|
||||
|
||||
// x86 exposes a 48 byte ASCII "CPU brand" string via CPUID leaves 0x80000002-0x80000004.
|
||||
internal static string GetCpuidCpuName()
|
||||
{
|
||||
if (!X86Base.IsSupported)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if CPU supports the query
|
||||
if ((uint)X86Base.CpuId(unchecked((int)0x80000000), 0).Eax < 0x80000004)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int[] regs = new int[12];
|
||||
|
||||
for (uint i = 0; i < 3; ++i)
|
||||
{
|
||||
(regs[4 * i], regs[4 * i + 1], regs[4 * i + 2], regs[4 * i + 3]) = X86Base.CpuId((int)(0x80000002 + i), 0);
|
||||
}
|
||||
|
||||
string name = Encoding.ASCII.GetString(MemoryMarshal.Cast<int, byte>(regs)).Replace('\0', ' ').Trim();
|
||||
|
||||
return string.IsNullOrEmpty(name) ? null : name;
|
||||
}
|
||||
}
|
||||
}
|
89
src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs
Normal file
89
src/Ryujinx.Common/SystemInfo/WindowsSystemInfo.cs
Normal file
|
@ -0,0 +1,89 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
partial class WindowsSystemInfo : SystemInfo
|
||||
{
|
||||
internal WindowsSystemInfo()
|
||||
{
|
||||
CpuName = $"{GetCpuidCpuName() ?? GetCpuNameWMI()} ; {LogicalCoreCount} logical"; // WMI is very slow
|
||||
(RamTotal, RamAvailable) = GetMemoryStats();
|
||||
}
|
||||
|
||||
private static (ulong Total, ulong Available) GetMemoryStats()
|
||||
{
|
||||
MemoryStatusEx memStatus = new MemoryStatusEx();
|
||||
if (GlobalMemoryStatusEx(ref memStatus))
|
||||
{
|
||||
return (memStatus.TotalPhys, memStatus.AvailPhys); // Bytes
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"GlobalMemoryStatusEx failed. Error {Marshal.GetLastWin32Error():X}");
|
||||
}
|
||||
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
private static string GetCpuNameWMI()
|
||||
{
|
||||
ManagementObjectCollection cpuObjs = GetWMIObjects("root\\CIMV2", "SELECT * FROM Win32_Processor");
|
||||
|
||||
if (cpuObjs != null)
|
||||
{
|
||||
foreach (var cpuObj in cpuObjs)
|
||||
{
|
||||
return cpuObj["Name"].ToString().Trim();
|
||||
}
|
||||
}
|
||||
|
||||
return Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER").Trim();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct MemoryStatusEx
|
||||
{
|
||||
public uint Length;
|
||||
public uint MemoryLoad;
|
||||
public ulong TotalPhys;
|
||||
public ulong AvailPhys;
|
||||
public ulong TotalPageFile;
|
||||
public ulong AvailPageFile;
|
||||
public ulong TotalVirtual;
|
||||
public ulong AvailVirtual;
|
||||
public ulong AvailExtendedVirtual;
|
||||
|
||||
public MemoryStatusEx()
|
||||
{
|
||||
Length = (uint)Marshal.SizeOf<MemoryStatusEx>();
|
||||
}
|
||||
}
|
||||
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool GlobalMemoryStatusEx(ref MemoryStatusEx lpBuffer);
|
||||
|
||||
private static ManagementObjectCollection GetWMIObjects(string scope, string query)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new ManagementObjectSearcher(scope, query).Get();
|
||||
}
|
||||
catch (PlatformNotSupportedException ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"WMI isn't available : {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
35
src/Ryujinx.Common/SystemInterop/DisplaySleep.cs
Normal file
35
src/Ryujinx.Common/SystemInterop/DisplaySleep.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
public partial class DisplaySleep
|
||||
{
|
||||
[Flags]
|
||||
enum EXECUTION_STATE : uint
|
||||
{
|
||||
ES_CONTINUOUS = 0x80000000,
|
||||
ES_DISPLAY_REQUIRED = 0x00000002,
|
||||
ES_SYSTEM_REQUIRED = 0x00000001
|
||||
}
|
||||
|
||||
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||
private static partial EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
|
||||
|
||||
static public void Prevent()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS | EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_DISPLAY_REQUIRED);
|
||||
}
|
||||
}
|
||||
|
||||
static public void Restore()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
96
src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs
Normal file
96
src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs
Normal file
|
@ -0,0 +1,96 @@
|
|||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
public static partial class ForceDpiAware
|
||||
{
|
||||
[LibraryImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static partial bool SetProcessDPIAware();
|
||||
|
||||
private const string X11LibraryName = "libX11.so.6";
|
||||
|
||||
[LibraryImport(X11LibraryName)]
|
||||
private static partial IntPtr XOpenDisplay([MarshalAs(UnmanagedType.LPStr)] string display);
|
||||
|
||||
[LibraryImport(X11LibraryName)]
|
||||
private static partial IntPtr XGetDefault(IntPtr display, [MarshalAs(UnmanagedType.LPStr)] string program, [MarshalAs(UnmanagedType.LPStr)] string option);
|
||||
|
||||
[LibraryImport(X11LibraryName)]
|
||||
private static partial int XDisplayWidth(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(X11LibraryName)]
|
||||
private static partial int XDisplayWidthMM(IntPtr display, int screenNumber);
|
||||
|
||||
[LibraryImport(X11LibraryName)]
|
||||
private static partial int XCloseDisplay(IntPtr display);
|
||||
|
||||
private static readonly double _standardDpiScale = 96.0;
|
||||
private static readonly double _maxScaleFactor = 1.25;
|
||||
|
||||
/// <summary>
|
||||
/// Marks the application as DPI-Aware when running on the Windows operating system.
|
||||
/// </summary>
|
||||
public static void Windows()
|
||||
{
|
||||
// Make process DPI aware for proper window sizing on high-res screens.
|
||||
if (OperatingSystem.IsWindowsVersionAtLeast(6))
|
||||
{
|
||||
SetProcessDPIAware();
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetActualScaleFactor()
|
||||
{
|
||||
double userDpiScale = 96.0;
|
||||
|
||||
try
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
userDpiScale = GdiPlusHelper.GetDpiX(IntPtr.Zero);
|
||||
}
|
||||
else if (OperatingSystem.IsLinux())
|
||||
{
|
||||
string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower();
|
||||
|
||||
if (xdgSessionType == null || xdgSessionType == "x11")
|
||||
{
|
||||
IntPtr display = XOpenDisplay(null);
|
||||
string dpiString = Marshal.PtrToStringAnsi(XGetDefault(display, "Xft", "dpi"));
|
||||
if (dpiString == null || !double.TryParse(dpiString, NumberStyles.Any, CultureInfo.InvariantCulture, out userDpiScale))
|
||||
{
|
||||
userDpiScale = (double)XDisplayWidth(display, 0) * 25.4 / (double)XDisplayWidthMM(display, 0);
|
||||
}
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
else if (xdgSessionType == "wayland")
|
||||
{
|
||||
// TODO
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Wayland not yet supported");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: Unrecognised XDG_SESSION_TYPE: {xdgSessionType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI: {e.Message}");
|
||||
}
|
||||
|
||||
return userDpiScale;
|
||||
}
|
||||
|
||||
public static double GetWindowScaleFactor()
|
||||
{
|
||||
double userDpiScale = GetActualScaleFactor();
|
||||
|
||||
return Math.Min(userDpiScale / _standardDpiScale, _maxScaleFactor);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue