GPU - Improve Memory Allocation (#1722)
* Implement TreeMap from scratch. Begin implementation of MemoryBlockManager * Implement GetFreePosition using MemoryBlocks * Implementation of Memory Management using a Tree. Still some issues to work around, but promising thus far. * Resolved invalid mapping issue. Performance appears promising. * Add tick metrics * Use the logger instead * Use debug loggin instead of info. * Remove unnecessary code. Add descriptions of added functions. * Improve memory allocation even further. As well as improve speed of position fetching. * Add TreeDictionary to Ryujinx Commons Removed Unnecessary Usigns * Add a Performance Profiler + Improve ReserveFixed * Begin transition to allocation in nvdrv * Create singleton nvmemallocator * Moved Allocation into Nv Related Files As requested by gdkchan, any allocation of memory has been moved into the driver files. Mapping remains in the GPU MemoryManager. * Remove unnecessary usings * Add missing descriptions * Correct descriptions * Fix formatting. * Remove unnecessary whitespace * Formatting / Convention Updates * Changes / Fixes Made syntax and convention changes as requested by gdkchan. Fixed an issue where IsRegionUsed would return the wrong boolean. Fixed an issue where GetFreePosition was asked for an address instead of a size. * Undo commenting of Assert in shader cache * Update Ryujinx.Common/Collections/TreeDictionary.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Resolved many suggestions * Implement Improved TreeDictionary Based off of Pseudo code and custom implementations. * Rename _set to _dictionary * Remove unused code * Remove unused code. * Remove unnecessary MapLow function. * Resolve data-structure based issues * Make adjustments to memory management. Deactive de-allocation for now, it causes more harm than good. * Minor refactorings + Re-implement deallocation Also cleaned up unnecessary code. * Add Tests for TreeDictionary * Update data structure to properly balance the tree * Experimental Implementation: 1. Reduce Time to Next Node to O(1) Runtime 2. Reduce While Loop Ct To 2 (In Most Cases) * Address issues w/ Deallocating Memory * Final Build + Fully Implement Dictionary Interface for new Data Structure + Cover All Memory Allocation Edge Cases, particularly w/ Games that De-Allocate a lot. * Minor Corrections Give TreeDictionary its own count (do not depend on inner dictionary) Properly remove adjacent allocations * Add AsList * Fix bug where internal dictionary wasn't being updated w/ new node for overwritten key. * Address comments in review. * Fix issue where block wouldn't break out (Fixes UE4 issues) * Update descriptions * Update descriptions * Reduce Node visibility to protect TreeDictionary Integrity + Remove usage of struct. * Update tests to use new TreeDictionary implementation. * Remove usage of dictionary in TreeDictionary * Refactoring / Renaming * Remove unneeded memoryblock class. * Add space for while * Add space for if * Formatting / descriptions * Clarified some descriptions * Reduce visibility of memory allocator * Edit method names to make more sense as memory blocks are no longer in use. * Make names consistent. * Protect against npe when sucessorof is called against keys that don't exist. (Not in use by memory manager, this is for other prs that might use this data structure) * Possible edge-case resolve * Update Ryujinx.Common/Collections/TreeDictionary.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Update Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs Co-authored-by: gdkchan <gab.dark.100@gmail.com> * Reduce # of unnecessary duplicate variables / Reduce visibility of variables only internally used. * Rename count to _count * Update Description of Add method. * Fix copypasta * Address comments * Address comments * Remove whitespace * Address comments, condense variables. * Consolidate vars * Fix whitespace. * Nit * Fix exception msg * Fix arrayIndex check * Fix arrayIndex check + indexer * Remove whitespace from cast Co-authored-by: gdkchan <gab.dark.100@gmail.com>
This commit is contained in:
parent
48278905d1
commit
06aa8a7578
10 changed files with 1577 additions and 202 deletions
987
Ryujinx.Common/Collections/TreeDictionary.cs
Normal file
987
Ryujinx.Common/Collections/TreeDictionary.cs
Normal file
|
@ -0,0 +1,987 @@
|
||||||
|
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> : IDictionary<K, V> where K : IComparable<K>
|
||||||
|
{
|
||||||
|
private const bool Black = true;
|
||||||
|
private const bool Red = false;
|
||||||
|
private Node<K, V> _root = null;
|
||||||
|
private int _count = 0;
|
||||||
|
public TreeDictionary() { }
|
||||||
|
|
||||||
|
#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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
if (null == value)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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.Count > 0)
|
||||||
|
{
|
||||||
|
Node<K, V> node = nodes.Dequeue();
|
||||||
|
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"/>.
|
||||||
|
/// <br></br>
|
||||||
|
/// The nodes will be added in Sorted by Key Order.
|
||||||
|
/// </summary>
|
||||||
|
public List<KeyValuePair<K, V>> AsList()
|
||||||
|
{
|
||||||
|
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.Count > 0)
|
||||||
|
{
|
||||||
|
Node<K, V> node = nodes.Dequeue();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region Private Methods (BST)
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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 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>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="node"/> is null</exception>
|
||||||
|
private static Node<K, V> Maximum(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
Node<K, V> 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>
|
||||||
|
private static Node<K, V> Minimum(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
Node<K, V> tmp = node;
|
||||||
|
while (tmp.Left != null)
|
||||||
|
{
|
||||||
|
tmp = tmp.Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the node with the key immediately greater than <paramref name="node"/>.Key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to find the successor of</param>
|
||||||
|
/// <returns>Successor of <paramref name="node"/></returns>
|
||||||
|
private static Node<K, V> SuccessorOf(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node.Right != null)
|
||||||
|
{
|
||||||
|
return Minimum(node.Right);
|
||||||
|
}
|
||||||
|
Node<K, V> parent = node.Parent;
|
||||||
|
while (parent != null && node == parent.Right)
|
||||||
|
{
|
||||||
|
node = parent;
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the node whose key immediately less than <paramref name="node"/>.Key.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="node">Node to find the predecessor of</param>
|
||||||
|
/// <returns>Predecessor of <paramref name="node"/></returns>
|
||||||
|
private static Node<K, V> PredecessorOf(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node.Left != null)
|
||||||
|
{
|
||||||
|
return Maximum(node.Left);
|
||||||
|
}
|
||||||
|
Node<K, V> parent = node.Parent;
|
||||||
|
while (parent != null && node == parent.Left)
|
||||||
|
{
|
||||||
|
node = parent;
|
||||||
|
parent = parent.Parent;
|
||||||
|
}
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
#region Private Methods (RBL)
|
||||||
|
|
||||||
|
private void RestoreBalanceAfterRemoval(Node<K, V> balanceNode)
|
||||||
|
{
|
||||||
|
Node<K, V> ptr = balanceNode;
|
||||||
|
|
||||||
|
while (ptr != _root && ColorOf(ptr) == Black)
|
||||||
|
{
|
||||||
|
if (ptr == LeftOf(ParentOf(ptr)))
|
||||||
|
{
|
||||||
|
Node<K, V> 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
|
||||||
|
{
|
||||||
|
Node<K, V> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreBalanceAfterInsertion(Node<K, V> balanceNode)
|
||||||
|
{
|
||||||
|
SetColor(balanceNode, Red);
|
||||||
|
while (balanceNode != null && balanceNode != _root && ColorOf(ParentOf(balanceNode)) == Red)
|
||||||
|
{
|
||||||
|
if (ParentOf(balanceNode) == LeftOf(ParentOf(ParentOf(balanceNode))))
|
||||||
|
{
|
||||||
|
Node<K, V> 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
|
||||||
|
{
|
||||||
|
Node<K, V> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateLeft(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
Node<K, V> right = RightOf(node);
|
||||||
|
node.Right = LeftOf(right);
|
||||||
|
if (LeftOf(right) != null)
|
||||||
|
{
|
||||||
|
LeftOf(right).Parent = node;
|
||||||
|
}
|
||||||
|
right.Parent = ParentOf(node);
|
||||||
|
if (ParentOf(node) == null)
|
||||||
|
{
|
||||||
|
_root = right;
|
||||||
|
}
|
||||||
|
else if (node == LeftOf(ParentOf(node)))
|
||||||
|
{
|
||||||
|
ParentOf(node).Left = right;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ParentOf(node).Right = right;
|
||||||
|
}
|
||||||
|
right.Left = node;
|
||||||
|
node.Parent = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateRight(Node<K, V> node)
|
||||||
|
{
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
Node<K, V> left = LeftOf(node);
|
||||||
|
node.Left = RightOf(left);
|
||||||
|
if (RightOf(left) != null)
|
||||||
|
{
|
||||||
|
RightOf(left).Parent = node;
|
||||||
|
}
|
||||||
|
left.Parent = node.Parent;
|
||||||
|
if (ParentOf(node) == null)
|
||||||
|
{
|
||||||
|
_root = left;
|
||||||
|
}
|
||||||
|
else if (node == RightOf(ParentOf(node)))
|
||||||
|
{
|
||||||
|
ParentOf(node).Right = left;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ParentOf(node).Left = left;
|
||||||
|
}
|
||||||
|
left.Right = node;
|
||||||
|
node.Parent = left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Safety-Methods
|
||||||
|
|
||||||
|
// These methods save memory by allowing us to forego sentinel nil nodes, as well as serve as protection against nullpointerexceptions.
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
private static bool ColorOf(Node<K, V> 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>
|
||||||
|
private static void SetColor(Node<K, V> 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>
|
||||||
|
private static Node<K, V> LeftOf(Node<K, V> 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>
|
||||||
|
private static Node<K, V> RightOf(Node<K, V> 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>
|
||||||
|
private static Node<K, V> ParentOf(Node<K, V> node)
|
||||||
|
{
|
||||||
|
return node?.Parent;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Interface Implementations
|
||||||
|
|
||||||
|
// Method descriptions are not provided as they are already included as part of the interface.
|
||||||
|
public bool ContainsKey(K key)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(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)
|
||||||
|
{
|
||||||
|
if (null == key)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(key));
|
||||||
|
}
|
||||||
|
Node<K, V> node = GetNode(key);
|
||||||
|
value = node != null ? node.Value : default;
|
||||||
|
return node != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(KeyValuePair<K, V> item)
|
||||||
|
{
|
||||||
|
if (item.Key == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(item.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_root = null;
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 int Count => _count;
|
||||||
|
|
||||||
|
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.Count > 0)
|
||||||
|
{
|
||||||
|
Node<K, V> node = queue.Dequeue();
|
||||||
|
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>
|
||||||
|
internal class Node<K, V>
|
||||||
|
{
|
||||||
|
internal bool Color = true;
|
||||||
|
internal Node<K, V> Left = null;
|
||||||
|
internal Node<K, V> Right = null;
|
||||||
|
internal Node<K, V> Parent = null;
|
||||||
|
internal K Key;
|
||||||
|
internal V Value;
|
||||||
|
|
||||||
|
public Node(K key, V value, Node<K, V> parent)
|
||||||
|
{
|
||||||
|
this.Key = key;
|
||||||
|
this.Value = value;
|
||||||
|
this.Parent = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -472,7 +472,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
|
ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
|
||||||
|
|
||||||
if (address == MemoryManager.BadAddress)
|
if (address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -533,7 +533,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
|
ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
|
||||||
|
|
||||||
if (address == MemoryManager.BadAddress)
|
if (address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -618,7 +618,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
|
ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
|
||||||
|
|
||||||
if (address == MemoryManager.BadAddress)
|
if (address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -983,7 +983,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
ulong address = _context.MemoryManager.Translate(cbp.DstAddress.Pack());
|
ulong address = _context.MemoryManager.Translate(cbp.DstAddress.Pack());
|
||||||
|
|
||||||
if (address == MemoryManager.BadAddress)
|
if (address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||||
|
|
||||||
// Bad address. We can't add a texture with a invalid address
|
// Bad address. We can't add a texture with a invalid address
|
||||||
// to the cache.
|
// to the cache.
|
||||||
if (info.Address == MemoryManager.BadAddress)
|
if (info.Address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,7 +401,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
|
|
||||||
ulong address = _context.MemoryManager.Translate(gpuVa);
|
ulong address = _context.MemoryManager.Translate(gpuVa);
|
||||||
|
|
||||||
if (address == MemoryManager.BadAddress)
|
if (address == MemoryManager.PteUnmapped)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MemoryManager
|
public class MemoryManager
|
||||||
{
|
{
|
||||||
private const ulong AddressSpaceSize = 1UL << 40;
|
|
||||||
|
|
||||||
public const ulong BadAddress = ulong.MaxValue;
|
|
||||||
|
|
||||||
private const int PtLvl0Bits = 14;
|
private const int PtLvl0Bits = 14;
|
||||||
private const int PtLvl1Bits = 14;
|
private const int PtLvl1Bits = 14;
|
||||||
public const int PtPageBits = 12;
|
public const int PtPageBits = 12;
|
||||||
|
@ -29,8 +25,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
|
private const int PtLvl0Bit = PtPageBits + PtLvl1Bits;
|
||||||
private const int PtLvl1Bit = PtPageBits;
|
private const int PtLvl1Bit = PtPageBits;
|
||||||
|
|
||||||
private const ulong PteUnmapped = 0xffffffff_ffffffff;
|
public const ulong PteUnmapped = 0xffffffff_ffffffff;
|
||||||
private const ulong PteReserved = 0xffffffff_fffffffe;
|
|
||||||
|
|
||||||
private readonly ulong[][] _pageTable;
|
private readonly ulong[][] _pageTable;
|
||||||
|
|
||||||
|
@ -136,116 +131,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
return va;
|
return va;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps a given range of pages to an allocated GPU virtual address.
|
|
||||||
/// The memory is automatically allocated by the memory manager.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pa">CPU virtual address to map into</param>
|
|
||||||
/// <param name="size">Size in bytes of the mapping</param>
|
|
||||||
/// <param name="alignment">Required alignment of the GPU virtual address in bytes</param>
|
|
||||||
/// <returns>GPU virtual address where the range was mapped, or an all ones mask in case of failure</returns>
|
|
||||||
public ulong MapAllocate(ulong pa, ulong size, ulong alignment)
|
|
||||||
{
|
|
||||||
lock (_pageTable)
|
|
||||||
{
|
|
||||||
ulong va = GetFreePosition(size, alignment);
|
|
||||||
|
|
||||||
if (va != PteUnmapped)
|
|
||||||
{
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
|
||||||
{
|
|
||||||
SetPte(va + offset, pa + offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return va;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maps a given range of pages to an allocated GPU virtual address.
|
|
||||||
/// The memory is automatically allocated by the memory manager.
|
|
||||||
/// This also ensures that the mapping is always done in the first 4GB of GPU address space.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="pa">CPU virtual address to map into</param>
|
|
||||||
/// <param name="size">Size in bytes of the mapping</param>
|
|
||||||
/// <returns>GPU virtual address where the range was mapped, or an all ones mask in case of failure</returns>
|
|
||||||
public ulong MapLow(ulong pa, ulong size)
|
|
||||||
{
|
|
||||||
lock (_pageTable)
|
|
||||||
{
|
|
||||||
ulong va = GetFreePosition(size, 1, PageSize);
|
|
||||||
|
|
||||||
if (va != PteUnmapped && va <= uint.MaxValue && (va + size) <= uint.MaxValue)
|
|
||||||
{
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
|
||||||
{
|
|
||||||
SetPte(va + offset, pa + offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
va = PteUnmapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
return va;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reserves memory at a fixed GPU memory location.
|
|
||||||
/// This prevents the reserved region from being used for memory allocation for map.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="va">GPU virtual address to reserve</param>
|
|
||||||
/// <param name="size">Size in bytes of the reservation</param>
|
|
||||||
/// <returns>GPU virtual address of the reservation, or an all ones mask in case of failure</returns>
|
|
||||||
public ulong ReserveFixed(ulong va, ulong size)
|
|
||||||
{
|
|
||||||
lock (_pageTable)
|
|
||||||
{
|
|
||||||
MemoryUnmapped?.Invoke(this, new UnmapEventArgs(va, size));
|
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
|
||||||
{
|
|
||||||
if (IsPageInUse(va + offset))
|
|
||||||
{
|
|
||||||
return PteUnmapped;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
|
||||||
{
|
|
||||||
SetPte(va + offset, PteReserved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return va;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reserves memory at any GPU memory location.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="size">Size in bytes of the reservation</param>
|
|
||||||
/// <param name="alignment">Reservation address alignment in bytes</param>
|
|
||||||
/// <returns>GPU virtual address of the reservation, or an all ones mask in case of failure</returns>
|
|
||||||
public ulong Reserve(ulong size, ulong alignment)
|
|
||||||
{
|
|
||||||
lock (_pageTable)
|
|
||||||
{
|
|
||||||
ulong address = GetFreePosition(size, alignment);
|
|
||||||
|
|
||||||
if (address != PteUnmapped)
|
|
||||||
{
|
|
||||||
for (ulong offset = 0; offset < size; offset += PageSize)
|
|
||||||
{
|
|
||||||
SetPte(address + offset, PteReserved);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Frees memory that was previously allocated by a map or reserved.
|
/// Frees memory that was previously allocated by a map or reserved.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -265,55 +150,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the address of an unused (free) region of the specified size.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="size">Size of the region in bytes</param>
|
|
||||||
/// <param name="alignment">Required alignment of the region address in bytes</param>
|
|
||||||
/// <param name="start">Start address of the search on the address space</param>
|
|
||||||
/// <returns>GPU virtual address of the allocation, or an all ones mask in case of failure</returns>
|
|
||||||
private ulong GetFreePosition(ulong size, ulong alignment = 1, ulong start = 1UL << 32)
|
|
||||||
{
|
|
||||||
// Note: Address 0 is not considered valid by the driver,
|
|
||||||
// when 0 is returned it's considered a mapping error.
|
|
||||||
ulong address = start;
|
|
||||||
ulong freeSize = 0;
|
|
||||||
|
|
||||||
if (alignment == 0)
|
|
||||||
{
|
|
||||||
alignment = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
alignment = (alignment + PageMask) & ~PageMask;
|
|
||||||
|
|
||||||
while (address + freeSize < AddressSpaceSize)
|
|
||||||
{
|
|
||||||
if (!IsPageInUse(address + freeSize))
|
|
||||||
{
|
|
||||||
freeSize += PageSize;
|
|
||||||
|
|
||||||
if (freeSize >= size)
|
|
||||||
{
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
address += freeSize + PageSize;
|
|
||||||
freeSize = 0;
|
|
||||||
|
|
||||||
ulong remainder = address % alignment;
|
|
||||||
|
|
||||||
if (remainder != 0)
|
|
||||||
{
|
|
||||||
address = (address - remainder) + alignment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return PteUnmapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks if a given page is mapped.
|
/// Checks if a given page is mapped.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -333,7 +169,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
{
|
{
|
||||||
ulong baseAddress = GetPte(gpuVa);
|
ulong baseAddress = GetPte(gpuVa);
|
||||||
|
|
||||||
if (baseAddress == PteUnmapped || baseAddress == PteReserved)
|
if (baseAddress == PteUnmapped)
|
||||||
{
|
{
|
||||||
return PteUnmapped;
|
return PteUnmapped;
|
||||||
}
|
}
|
||||||
|
@ -341,29 +177,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||||
return baseAddress + (gpuVa & PageMask);
|
return baseAddress + (gpuVa & PageMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a given memory page is mapped or reserved.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuVa">GPU virtual address of the page</param>
|
|
||||||
/// <returns>True if the page is mapped or reserved, false otherwise</returns>
|
|
||||||
private bool IsPageInUse(ulong gpuVa)
|
|
||||||
{
|
|
||||||
if (gpuVa >> PtLvl0Bits + PtLvl1Bits + PtPageBits != 0)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong l0 = (gpuVa >> PtLvl0Bit) & PtLvl0Mask;
|
|
||||||
ulong l1 = (gpuVa >> PtLvl1Bit) & PtLvl1Mask;
|
|
||||||
|
|
||||||
if (_pageTable[l0] == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _pageTable[l0][l1] != PteUnmapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the Page Table entry for a given GPU virtual address.
|
/// Gets the Page Table entry for a given GPU virtual address.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Collections;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu.Types;
|
||||||
|
@ -12,8 +13,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
class NvHostAsGpuDeviceFile : NvDeviceFile
|
class NvHostAsGpuDeviceFile : NvDeviceFile
|
||||||
{
|
{
|
||||||
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
|
private static ConcurrentDictionary<KProcess, AddressSpaceContext> _addressSpaceContextRegistry = new ConcurrentDictionary<KProcess, AddressSpaceContext>();
|
||||||
|
private NvMemoryAllocator _memoryAllocator;
|
||||||
|
|
||||||
public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, long owner) : base(context, owner) { }
|
public NvHostAsGpuDeviceFile(ServiceCtx context, IVirtualMemoryManager memory, long owner) : base(context, owner)
|
||||||
|
{
|
||||||
|
_memoryAllocator = context.Device.MemoryAllocator;
|
||||||
|
}
|
||||||
|
|
||||||
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
public override NvInternalResult Ioctl(NvIoctl command, Span<byte> arguments)
|
||||||
{
|
{
|
||||||
|
@ -92,11 +97,30 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
// the Offset field holds the alignment size instead.
|
// the Offset field holds the alignment size instead.
|
||||||
if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
|
if ((arguments.Flags & AddressSpaceFlags.FixedOffset) != 0)
|
||||||
{
|
{
|
||||||
arguments.Offset = (long)addressSpaceContext.Gmm.ReserveFixed((ulong)arguments.Offset, size);
|
bool regionInUse = _memoryAllocator.IsRegionInUse((ulong)arguments.Offset, size, out ulong freeAddressStartPosition);
|
||||||
|
ulong address;
|
||||||
|
|
||||||
|
if (!regionInUse)
|
||||||
|
{
|
||||||
|
_memoryAllocator.AllocateRange((ulong)arguments.Offset, size, freeAddressStartPosition);
|
||||||
|
address = freeAddressStartPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
address = NvMemoryAllocator.PteUnmapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.Offset = (long)address;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
arguments.Offset = (long)addressSpaceContext.Gmm.Reserve((ulong)size, (ulong)arguments.Offset);
|
ulong address = _memoryAllocator.GetFreeAddress((ulong)size, out ulong freeAddressStartPosition, (ulong)arguments.Offset);
|
||||||
|
if (address != NvMemoryAllocator.PteUnmapped)
|
||||||
|
{
|
||||||
|
_memoryAllocator.AllocateRange(address, (ulong)size, freeAddressStartPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments.Offset = unchecked((long)address);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments.Offset < 0)
|
if (arguments.Offset < 0)
|
||||||
|
@ -128,6 +152,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
|
|
||||||
if (addressSpaceContext.RemoveReservation(arguments.Offset))
|
if (addressSpaceContext.RemoveReservation(arguments.Offset))
|
||||||
{
|
{
|
||||||
|
_memoryAllocator.DeallocateRange((ulong)arguments.Offset, size);
|
||||||
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, size);
|
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -152,6 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
{
|
{
|
||||||
if (size != 0)
|
if (size != 0)
|
||||||
{
|
{
|
||||||
|
_memoryAllocator.DeallocateRange((ulong)arguments.Offset, (ulong)size);
|
||||||
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, (ulong)size);
|
addressSpaceContext.Gmm.Free((ulong)arguments.Offset, (ulong)size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +278,12 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
arguments.Offset = (long)addressSpaceContext.Gmm.MapAllocate((ulong)physicalAddress, (ulong)size, pageSize);
|
ulong va = _memoryAllocator.GetFreeAddress((ulong)size, out ulong freeAddressStartPosition, (ulong) pageSize);
|
||||||
|
if (va != NvMemoryAllocator.PteUnmapped)
|
||||||
|
{
|
||||||
|
_memoryAllocator.AllocateRange(va, (ulong)size, freeAddressStartPosition);
|
||||||
|
}
|
||||||
|
arguments.Offset = (long)addressSpaceContext.Gmm.Map((ulong)physicalAddress, va, (ulong)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments.Offset < 0)
|
if (arguments.Offset < 0)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Collections;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.Gpu.Memory;
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostAsGpu;
|
||||||
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel.Types;
|
||||||
|
@ -23,6 +24,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
private readonly Switch _device;
|
private readonly Switch _device;
|
||||||
|
|
||||||
private readonly IVirtualMemoryManager _memory;
|
private readonly IVirtualMemoryManager _memory;
|
||||||
|
private NvMemoryAllocator _memoryAllocator;
|
||||||
|
|
||||||
public enum ResourcePolicy
|
public enum ResourcePolicy
|
||||||
{
|
{
|
||||||
|
@ -45,6 +47,7 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
_timeout = 3000;
|
_timeout = 3000;
|
||||||
_submitTimeout = 0;
|
_submitTimeout = 0;
|
||||||
_timeslice = 0;
|
_timeslice = 0;
|
||||||
|
_memoryAllocator = _device.MemoryAllocator;
|
||||||
|
|
||||||
ChannelSyncpoints = new uint[MaxModuleSyncpoint];
|
ChannelSyncpoints = new uint[MaxModuleSyncpoint];
|
||||||
|
|
||||||
|
@ -245,7 +248,17 @@ namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostChannel
|
||||||
{
|
{
|
||||||
if (map.DmaMapAddress == 0)
|
if (map.DmaMapAddress == 0)
|
||||||
{
|
{
|
||||||
map.DmaMapAddress = (long)gmm.MapLow((ulong)map.Address, (uint)map.Size);
|
ulong va = _memoryAllocator.GetFreeAddress((ulong) map.Size, out ulong freeAddressStartPosition, 1, MemoryManager.PageSize);
|
||||||
|
|
||||||
|
if (va != NvMemoryAllocator.PteUnmapped && va <= uint.MaxValue && (va + (uint)map.Size) <= uint.MaxValue)
|
||||||
|
{
|
||||||
|
_memoryAllocator.AllocateRange(va, (uint)map.Size, freeAddressStartPosition);
|
||||||
|
map.DmaMapAddress = (long)gmm.Map((ulong)map.Address, va, (uint)map.Size);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
map.DmaMapAddress = unchecked((long)NvMemoryAllocator.PteUnmapped);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBufferEntry.MapAddress = (int)map.DmaMapAddress;
|
commandBufferEntry.MapAddress = (int)map.DmaMapAddress;
|
||||||
|
|
282
Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs
Normal file
282
Ryujinx.HLE/HOS/Services/Nv/NvMemoryAllocator.cs
Normal file
|
@ -0,0 +1,282 @@
|
||||||
|
using Ryujinx.Common.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using System;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices
|
||||||
|
{
|
||||||
|
class NvMemoryAllocator
|
||||||
|
{
|
||||||
|
private const ulong AddressSpaceSize = 1UL << 40;
|
||||||
|
|
||||||
|
private const ulong DefaultStart = 1UL << 32;
|
||||||
|
private const ulong InvalidAddress = 0;
|
||||||
|
|
||||||
|
private const ulong PageSize = MemoryManager.PageSize;
|
||||||
|
private const ulong PageMask = MemoryManager.PageMask;
|
||||||
|
|
||||||
|
public const ulong PteUnmapped = MemoryManager.PteUnmapped;
|
||||||
|
|
||||||
|
// Key --> Start Address of Region
|
||||||
|
// Value --> End Address of Region
|
||||||
|
private readonly TreeDictionary<ulong, ulong> _tree = new TreeDictionary<ulong, ulong>();
|
||||||
|
|
||||||
|
private readonly Dictionary<ulong, LinkedListNode<ulong>> _dictionary = new Dictionary<ulong, LinkedListNode<ulong>>();
|
||||||
|
private readonly LinkedList<ulong> _list = new LinkedList<ulong>();
|
||||||
|
|
||||||
|
public NvMemoryAllocator()
|
||||||
|
{
|
||||||
|
_tree.Add(PageSize, PageSize + AddressSpaceSize);
|
||||||
|
LinkedListNode<ulong> node = _list.AddFirst(PageSize);
|
||||||
|
_dictionary[PageSize] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a range of memory as consumed by removing it from the tree.
|
||||||
|
/// This function will split memory regions if there is available space.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address at which to allocate</param>
|
||||||
|
/// <param name="size">Size of the allocation in bytes</param>
|
||||||
|
/// <param name="referenceAddress">Reference to the address of memory where the allocation can take place</param>
|
||||||
|
#region Memory Allocation
|
||||||
|
public void AllocateRange(ulong va, ulong size, ulong referenceAddress = InvalidAddress)
|
||||||
|
{
|
||||||
|
lock (_tree)
|
||||||
|
{
|
||||||
|
if (referenceAddress != InvalidAddress)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
ulong referenceEndAddress = _tree.Get(referenceAddress);
|
||||||
|
if (va >= referenceAddress)
|
||||||
|
{
|
||||||
|
// Need Left Node
|
||||||
|
if (va > referenceAddress)
|
||||||
|
{
|
||||||
|
ulong leftEndAddress = va;
|
||||||
|
|
||||||
|
// Overwrite existing block with its new smaller range.
|
||||||
|
_tree.Add(referenceAddress, leftEndAddress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We need to get rid of the large chunk.
|
||||||
|
_tree.Remove(referenceAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong rightSize = referenceEndAddress - endAddress;
|
||||||
|
// If leftover space, create a right node.
|
||||||
|
if (rightSize > 0)
|
||||||
|
{
|
||||||
|
_tree.Add(endAddress, referenceEndAddress);
|
||||||
|
|
||||||
|
LinkedListNode<ulong> node = _list.AddAfter(_dictionary[referenceAddress], endAddress);
|
||||||
|
_dictionary[endAddress] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (va == referenceAddress)
|
||||||
|
{
|
||||||
|
_list.Remove(_dictionary[referenceAddress]);
|
||||||
|
_dictionary.Remove(referenceAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a range of memory as free by adding it to the tree.
|
||||||
|
/// This function will automatically compact the tree when it determines there are multiple ranges of free memory adjacent to each other.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address at which to deallocate</param>
|
||||||
|
/// <param name="size">Size of the allocation in bytes</param>
|
||||||
|
public void DeallocateRange(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
lock (_tree)
|
||||||
|
{
|
||||||
|
ulong freeAddressStartPosition = _tree.Floor(va);
|
||||||
|
if (freeAddressStartPosition != InvalidAddress)
|
||||||
|
{
|
||||||
|
LinkedListNode<ulong> node = _dictionary[freeAddressStartPosition];
|
||||||
|
ulong targetPrevAddress = _dictionary[freeAddressStartPosition].Previous != null ? _dictionary[_dictionary[freeAddressStartPosition].Previous.Value].Value : InvalidAddress;
|
||||||
|
ulong targetNextAddress = _dictionary[freeAddressStartPosition].Next != null ? _dictionary[_dictionary[freeAddressStartPosition].Next.Value].Value : InvalidAddress;
|
||||||
|
ulong expandedStart = va;
|
||||||
|
ulong expandedEnd = va + size;
|
||||||
|
|
||||||
|
while (targetPrevAddress != InvalidAddress)
|
||||||
|
{
|
||||||
|
ulong prevAddress = targetPrevAddress;
|
||||||
|
ulong prevEndAddress = _tree.Get(targetPrevAddress);
|
||||||
|
if (prevEndAddress >= expandedStart)
|
||||||
|
{
|
||||||
|
expandedStart = targetPrevAddress;
|
||||||
|
LinkedListNode<ulong> prevPtr = _dictionary[prevAddress];
|
||||||
|
if (prevPtr.Previous != null)
|
||||||
|
{
|
||||||
|
targetPrevAddress = prevPtr.Previous.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetPrevAddress = InvalidAddress;
|
||||||
|
}
|
||||||
|
node = node.Previous;
|
||||||
|
_tree.Remove(prevAddress);
|
||||||
|
_list.Remove(_dictionary[prevAddress]);
|
||||||
|
_dictionary.Remove(prevAddress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (targetNextAddress != InvalidAddress)
|
||||||
|
{
|
||||||
|
ulong nextAddress = targetNextAddress;
|
||||||
|
ulong nextEndAddress = _tree.Get(targetNextAddress);
|
||||||
|
if (nextAddress <= expandedEnd)
|
||||||
|
{
|
||||||
|
expandedEnd = Math.Max(expandedEnd, nextEndAddress);
|
||||||
|
LinkedListNode<ulong> nextPtr = _dictionary[nextAddress];
|
||||||
|
if (nextPtr.Next != null)
|
||||||
|
{
|
||||||
|
targetNextAddress = nextPtr.Next.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetNextAddress = InvalidAddress;
|
||||||
|
}
|
||||||
|
_tree.Remove(nextAddress);
|
||||||
|
_list.Remove(_dictionary[nextAddress]);
|
||||||
|
_dictionary.Remove(nextAddress);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_tree.Add(expandedStart, expandedEnd);
|
||||||
|
LinkedListNode<ulong> nodePtr = _list.AddAfter(node, expandedStart);
|
||||||
|
_dictionary[expandedStart] = nodePtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the address of an unused (free) region of the specified size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">Size of the region in bytes</param>
|
||||||
|
/// <param name="freeAddressStartPosition">Position at which memory can be allocated</param>
|
||||||
|
/// <param name="alignment">Required alignment of the region address in bytes</param>
|
||||||
|
/// <param name="start">Start address of the search on the address space</param>
|
||||||
|
/// <returns>GPU virtual address of the allocation, or an all ones mask in case of failure</returns>
|
||||||
|
public ulong GetFreeAddress(ulong size, out ulong freeAddressStartPosition, ulong alignment = 1, ulong start = DefaultStart)
|
||||||
|
{
|
||||||
|
// Note: Address 0 is not considered valid by the driver,
|
||||||
|
// when 0 is returned it's considered a mapping error.
|
||||||
|
lock (_tree)
|
||||||
|
{
|
||||||
|
ulong address = start;
|
||||||
|
|
||||||
|
if (alignment == 0)
|
||||||
|
{
|
||||||
|
alignment = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
alignment = (alignment + PageMask) & ~PageMask;
|
||||||
|
if (address < AddressSpaceSize)
|
||||||
|
{
|
||||||
|
bool completedFirstPass = false;
|
||||||
|
ulong targetAddress;
|
||||||
|
if(start == DefaultStart)
|
||||||
|
{
|
||||||
|
targetAddress = _list.Last.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetAddress = _tree.Floor(address);
|
||||||
|
if(targetAddress == InvalidAddress)
|
||||||
|
{
|
||||||
|
targetAddress = _tree.Ceiling(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (address < AddressSpaceSize)
|
||||||
|
{
|
||||||
|
if (targetAddress != InvalidAddress)
|
||||||
|
{
|
||||||
|
if (address >= targetAddress)
|
||||||
|
{
|
||||||
|
if (address + size <= _tree.Get(targetAddress))
|
||||||
|
{
|
||||||
|
freeAddressStartPosition = targetAddress;
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LinkedListNode<ulong> nextPtr = _dictionary[targetAddress];
|
||||||
|
if (nextPtr.Next != null)
|
||||||
|
{
|
||||||
|
targetAddress = nextPtr.Next.Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (completedFirstPass)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completedFirstPass = true;
|
||||||
|
address = start;
|
||||||
|
targetAddress = _tree.Floor(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
address += PageSize * (targetAddress / PageSize - (address / PageSize));
|
||||||
|
|
||||||
|
ulong remainder = address % alignment;
|
||||||
|
|
||||||
|
if (remainder != 0)
|
||||||
|
{
|
||||||
|
address = (address - remainder) + alignment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
freeAddressStartPosition = InvalidAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PteUnmapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a given memory region is mapped or reserved.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gpuVa">GPU virtual address of the page</param>
|
||||||
|
/// <param name="size">Size of the allocation in bytes</param>
|
||||||
|
/// <param name="freeAddressStartPosition">Nearest lower address that memory can be allocated</param>
|
||||||
|
/// <returns>True if the page is mapped or reserved, false otherwise</returns>
|
||||||
|
public bool IsRegionInUse(ulong gpuVa, ulong size, out ulong freeAddressStartPosition)
|
||||||
|
{
|
||||||
|
lock (_tree)
|
||||||
|
{
|
||||||
|
ulong floorAddress = _tree.Floor(gpuVa);
|
||||||
|
freeAddressStartPosition = floorAddress;
|
||||||
|
if (floorAddress != InvalidAddress)
|
||||||
|
{
|
||||||
|
return !(gpuVa >= floorAddress && ((gpuVa + size) < _tree.Get(floorAddress)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ using Ryujinx.HLE.HOS;
|
||||||
using Ryujinx.HLE.HOS.Services;
|
using Ryujinx.HLE.HOS.Services;
|
||||||
using Ryujinx.HLE.HOS.Services.Apm;
|
using Ryujinx.HLE.HOS.Services.Apm;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Nv.NvDrvServices;
|
||||||
using Ryujinx.HLE.HOS.SystemState;
|
using Ryujinx.HLE.HOS.SystemState;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
@ -26,6 +27,8 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
public GpuContext Gpu { get; private set; }
|
public GpuContext Gpu { get; private set; }
|
||||||
|
|
||||||
|
internal NvMemoryAllocator MemoryAllocator { get; private set; }
|
||||||
|
|
||||||
internal Host1xDevice Host1x { get; }
|
internal Host1xDevice Host1x { get; }
|
||||||
|
|
||||||
public VirtualFileSystem FileSystem { get; private set; }
|
public VirtualFileSystem FileSystem { get; private set; }
|
||||||
|
@ -69,6 +72,8 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
Gpu = new GpuContext(renderer);
|
Gpu = new GpuContext(renderer);
|
||||||
|
|
||||||
|
MemoryAllocator = new NvMemoryAllocator();
|
||||||
|
|
||||||
Host1x = new Host1xDevice(Gpu.Synchronization);
|
Host1x = new Host1xDevice(Gpu.Synchronization);
|
||||||
var nvdec = new NvdecDevice(Gpu.MemoryManager);
|
var nvdec = new NvdecDevice(Gpu.MemoryManager);
|
||||||
var vic = new VicDevice(Gpu.MemoryManager);
|
var vic = new VicDevice(Gpu.MemoryManager);
|
||||||
|
|
244
Ryujinx.Tests/TreeDictionaryTests.cs
Normal file
244
Ryujinx.Tests/TreeDictionaryTests.cs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
using NUnit.Framework;
|
||||||
|
using Ryujinx.Common.Collections;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Ryujinx.Tests.Collections
|
||||||
|
{
|
||||||
|
class TreeDictionaryTests
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void EnsureAddIntegrity()
|
||||||
|
{
|
||||||
|
TreeDictionary<int, int> dictionary = new TreeDictionary<int, int>();
|
||||||
|
|
||||||
|
Assert.AreEqual(dictionary.Count, 0);
|
||||||
|
|
||||||
|
dictionary.Add(2, 7);
|
||||||
|
dictionary.Add(1, 4);
|
||||||
|
dictionary.Add(10, 2);
|
||||||
|
dictionary.Add(4, 1);
|
||||||
|
dictionary.Add(3, 2);
|
||||||
|
dictionary.Add(11, 2);
|
||||||
|
dictionary.Add(5, 2);
|
||||||
|
|
||||||
|
Assert.AreEqual(dictionary.Count, 7);
|
||||||
|
|
||||||
|
List<KeyValuePair<int, int>> list = dictionary.AsLevelOrderList();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tree Should Look as Follows After Rotations
|
||||||
|
*
|
||||||
|
* 2
|
||||||
|
* 1 4
|
||||||
|
* 3 10
|
||||||
|
* 5 11
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
Assert.AreEqual(list.Count, dictionary.Count);
|
||||||
|
Assert.AreEqual(list[0].Key, 2);
|
||||||
|
Assert.AreEqual(list[1].Key, 1);
|
||||||
|
Assert.AreEqual(list[2].Key, 4);
|
||||||
|
Assert.AreEqual(list[3].Key, 3);
|
||||||
|
Assert.AreEqual(list[4].Key, 10);
|
||||||
|
Assert.AreEqual(list[5].Key, 5);
|
||||||
|
Assert.AreEqual(list[6].Key, 11);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EnsureRemoveIntegrity()
|
||||||
|
{
|
||||||
|
TreeDictionary<int, int> dictionary = new TreeDictionary<int, int>();
|
||||||
|
|
||||||
|
Assert.AreEqual(dictionary.Count, 0);
|
||||||
|
|
||||||
|
dictionary.Add(2, 7);
|
||||||
|
dictionary.Add(1, 4);
|
||||||
|
dictionary.Add(10, 2);
|
||||||
|
dictionary.Add(4, 1);
|
||||||
|
dictionary.Add(3, 2);
|
||||||
|
dictionary.Add(11, 2);
|
||||||
|
dictionary.Add(5, 2);
|
||||||
|
dictionary.Add(7, 2);
|
||||||
|
dictionary.Add(9, 2);
|
||||||
|
dictionary.Add(8, 2);
|
||||||
|
dictionary.Add(13, 2);
|
||||||
|
dictionary.Add(24, 2);
|
||||||
|
dictionary.Add(6, 2);
|
||||||
|
Assert.AreEqual(dictionary.Count, 13);
|
||||||
|
|
||||||
|
List<KeyValuePair<int, int>> list = dictionary.AsLevelOrderList();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tree Should Look as Follows After Rotations
|
||||||
|
*
|
||||||
|
* 4
|
||||||
|
* 2 10
|
||||||
|
* 1 3 7 13
|
||||||
|
* 5 9 11 24
|
||||||
|
* 6 8
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach (KeyValuePair<int, int> node in list)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{node.Key} -> {node.Value}");
|
||||||
|
}
|
||||||
|
Assert.AreEqual(list.Count, dictionary.Count);
|
||||||
|
Assert.AreEqual(list[0].Key, 4);
|
||||||
|
Assert.AreEqual(list[1].Key, 2);
|
||||||
|
Assert.AreEqual(list[2].Key, 10);
|
||||||
|
Assert.AreEqual(list[3].Key, 1);
|
||||||
|
Assert.AreEqual(list[4].Key, 3);
|
||||||
|
Assert.AreEqual(list[5].Key, 7);
|
||||||
|
Assert.AreEqual(list[6].Key, 13);
|
||||||
|
Assert.AreEqual(list[7].Key, 5);
|
||||||
|
Assert.AreEqual(list[8].Key, 9);
|
||||||
|
Assert.AreEqual(list[9].Key, 11);
|
||||||
|
Assert.AreEqual(list[10].Key, 24);
|
||||||
|
Assert.AreEqual(list[11].Key, 6);
|
||||||
|
Assert.AreEqual(list[12].Key, 8);
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
|
||||||
|
dictionary.Remove(7);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tree Should Look as Follows After Removal
|
||||||
|
*
|
||||||
|
* 4
|
||||||
|
* 2 10
|
||||||
|
* 1 3 6 13
|
||||||
|
* 5 9 11 24
|
||||||
|
* 8
|
||||||
|
*/
|
||||||
|
|
||||||
|
list = dictionary.AsLevelOrderList();
|
||||||
|
foreach (KeyValuePair<int, int> node in list)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{node.Key} -> {node.Value}");
|
||||||
|
}
|
||||||
|
Assert.AreEqual(list[0].Key, 4);
|
||||||
|
Assert.AreEqual(list[1].Key, 2);
|
||||||
|
Assert.AreEqual(list[2].Key, 10);
|
||||||
|
Assert.AreEqual(list[3].Key, 1);
|
||||||
|
Assert.AreEqual(list[4].Key, 3);
|
||||||
|
Assert.AreEqual(list[5].Key, 6);
|
||||||
|
Assert.AreEqual(list[6].Key, 13);
|
||||||
|
Assert.AreEqual(list[7].Key, 5);
|
||||||
|
Assert.AreEqual(list[8].Key, 9);
|
||||||
|
Assert.AreEqual(list[9].Key, 11);
|
||||||
|
Assert.AreEqual(list[10].Key, 24);
|
||||||
|
Assert.AreEqual(list[11].Key, 8);
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
|
||||||
|
dictionary.Remove(10);
|
||||||
|
|
||||||
|
list = dictionary.AsLevelOrderList();
|
||||||
|
/*
|
||||||
|
* Tree Should Look as Follows After Removal
|
||||||
|
*
|
||||||
|
* 4
|
||||||
|
* 2 9
|
||||||
|
* 1 3 6 13
|
||||||
|
* 5 8 11 24
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
foreach (KeyValuePair<int, int> node in list)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{node.Key} -> {node.Value}");
|
||||||
|
}
|
||||||
|
Assert.AreEqual(list[0].Key, 4);
|
||||||
|
Assert.AreEqual(list[1].Key, 2);
|
||||||
|
Assert.AreEqual(list[2].Key, 9);
|
||||||
|
Assert.AreEqual(list[3].Key, 1);
|
||||||
|
Assert.AreEqual(list[4].Key, 3);
|
||||||
|
Assert.AreEqual(list[5].Key, 6);
|
||||||
|
Assert.AreEqual(list[6].Key, 13);
|
||||||
|
Assert.AreEqual(list[7].Key, 5);
|
||||||
|
Assert.AreEqual(list[8].Key, 8);
|
||||||
|
Assert.AreEqual(list[9].Key, 11);
|
||||||
|
Assert.AreEqual(list[10].Key, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void EnsureOverwriteIntegrity()
|
||||||
|
{
|
||||||
|
TreeDictionary<int, int> dictionary = new TreeDictionary<int, int>();
|
||||||
|
|
||||||
|
Assert.AreEqual(dictionary.Count, 0);
|
||||||
|
|
||||||
|
dictionary.Add(2, 7);
|
||||||
|
dictionary.Add(1, 4);
|
||||||
|
dictionary.Add(10, 2);
|
||||||
|
dictionary.Add(4, 1);
|
||||||
|
dictionary.Add(3, 2);
|
||||||
|
dictionary.Add(11, 2);
|
||||||
|
dictionary.Add(5, 2);
|
||||||
|
dictionary.Add(7, 2);
|
||||||
|
dictionary.Add(9, 2);
|
||||||
|
dictionary.Add(8, 2);
|
||||||
|
dictionary.Add(13, 2);
|
||||||
|
dictionary.Add(24, 2);
|
||||||
|
dictionary.Add(6, 2);
|
||||||
|
Assert.AreEqual(dictionary.Count, 13);
|
||||||
|
|
||||||
|
List<KeyValuePair<int, int>> list = dictionary.AsLevelOrderList();
|
||||||
|
|
||||||
|
foreach (KeyValuePair<int, int> node in list)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{node.Key} -> {node.Value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tree Should Look as Follows After Rotations
|
||||||
|
*
|
||||||
|
* 4
|
||||||
|
* 2 10
|
||||||
|
* 1 3 7 13
|
||||||
|
* 5 9 11 24
|
||||||
|
* 6 8
|
||||||
|
*/
|
||||||
|
|
||||||
|
Assert.AreEqual(list.Count, dictionary.Count);
|
||||||
|
Assert.AreEqual(list[0].Key, 4);
|
||||||
|
Assert.AreEqual(list[1].Key, 2);
|
||||||
|
Assert.AreEqual(list[2].Key, 10);
|
||||||
|
Assert.AreEqual(list[3].Key, 1);
|
||||||
|
Assert.AreEqual(list[4].Key, 3);
|
||||||
|
Assert.AreEqual(list[5].Key, 7);
|
||||||
|
Assert.AreEqual(list[6].Key, 13);
|
||||||
|
Assert.AreEqual(list[7].Key, 5);
|
||||||
|
Assert.AreEqual(list[8].Key, 9);
|
||||||
|
Assert.AreEqual(list[9].Key, 11);
|
||||||
|
Assert.AreEqual(list[10].Key, 24);
|
||||||
|
Assert.AreEqual(list[11].Key, 6);
|
||||||
|
Assert.AreEqual(list[12].Key, 8);
|
||||||
|
|
||||||
|
Assert.AreEqual(list[4].Value, 2);
|
||||||
|
|
||||||
|
dictionary.Add(3, 4);
|
||||||
|
|
||||||
|
list = dictionary.AsLevelOrderList();
|
||||||
|
|
||||||
|
Assert.AreEqual(list[4].Value, 4);
|
||||||
|
|
||||||
|
|
||||||
|
// Assure that none of the nodes locations have been modified.
|
||||||
|
Assert.AreEqual(list[0].Key, 4);
|
||||||
|
Assert.AreEqual(list[1].Key, 2);
|
||||||
|
Assert.AreEqual(list[2].Key, 10);
|
||||||
|
Assert.AreEqual(list[3].Key, 1);
|
||||||
|
Assert.AreEqual(list[4].Key, 3);
|
||||||
|
Assert.AreEqual(list[5].Key, 7);
|
||||||
|
Assert.AreEqual(list[6].Key, 13);
|
||||||
|
Assert.AreEqual(list[7].Key, 5);
|
||||||
|
Assert.AreEqual(list[8].Key, 9);
|
||||||
|
Assert.AreEqual(list[9].Key, 11);
|
||||||
|
Assert.AreEqual(list[10].Key, 24);
|
||||||
|
Assert.AreEqual(list[11].Key, 6);
|
||||||
|
Assert.AreEqual(list[12].Key, 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue