using System;
using System.Collections.Generic;
namespace Ryujinx.Memory.Range
{
///
/// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
///
public readonly struct MultiRange : IEquatable
{
private const ulong InvalidAddress = ulong.MaxValue;
private readonly MemoryRange _singleRange;
private readonly MemoryRange[] _ranges;
private bool HasSingleRange => _ranges == null;
///
/// Indicates that the range is fully unmapped.
///
public bool IsUnmapped => HasSingleRange && _singleRange.Address == InvalidAddress;
///
/// Total of physical sub-ranges on the virtual memory region.
///
public int Count => HasSingleRange ? 1 : _ranges.Length;
///
/// Creates a new multi-range with a single physical region.
///
/// Start address of the region
/// Size of the region in bytes
public MultiRange(ulong address, ulong size)
{
_singleRange = new MemoryRange(address, size);
_ranges = null;
}
///
/// Creates a new multi-range with multiple physical regions.
///
/// Array of physical regions
/// is null
public MultiRange(MemoryRange[] ranges)
{
ArgumentNullException.ThrowIfNull(ranges);
if (ranges.Length == 1)
{
_singleRange = ranges[0];
_ranges = null;
}
else
{
_singleRange = MemoryRange.Empty;
_ranges = ranges;
}
}
///
/// Gets a slice of the multi-range.
///
/// Offset of the slice into the multi-range in bytes
/// Size of the slice in bytes
/// A new multi-range representing the given slice of this one
public MultiRange Slice(ulong offset, ulong size)
{
if (HasSingleRange)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(size, _singleRange.Size - offset);
return new MultiRange(_singleRange.Address + offset, size);
}
else
{
var ranges = new List();
foreach (MemoryRange range in _ranges)
{
if ((long)offset <= 0)
{
ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size)));
size -= range.Size;
}
else if (offset < range.Size)
{
ulong sliceSize = Math.Min(size, range.Size - offset);
if (range.Address == InvalidAddress)
{
ranges.Add(new MemoryRange(range.Address, sliceSize));
}
else
{
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
}
size -= sliceSize;
}
if ((long)size <= 0)
{
break;
}
offset -= range.Size;
}
return ranges.Count == 1 ? new MultiRange(ranges[0].Address, ranges[0].Size) : new MultiRange(ranges.ToArray());
}
}
///
/// Gets the physical region at the specified index.
///
/// Index of the physical region
/// Region at the index specified
/// is invalid
public MemoryRange GetSubRange(int index)
{
if (HasSingleRange)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
return _singleRange;
}
else
{
if ((uint)index >= _ranges.Length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
return _ranges[index];
}
}
///
/// Gets the physical region at the specified index, without explicit bounds checking.
///
/// Index of the physical region
/// Region at the index specified
private MemoryRange GetSubRangeUnchecked(int index)
{
return HasSingleRange ? _singleRange : _ranges[index];
}
///
/// Check if two multi-ranges overlap with each other.
///
/// Other multi-range to check for overlap
/// True if any sub-range overlaps, false otherwise
public bool OverlapsWith(MultiRange other)
{
if (HasSingleRange && other.HasSingleRange)
{
return _singleRange.OverlapsWith(other._singleRange);
}
else
{
for (int i = 0; i < Count; i++)
{
MemoryRange currentRange = GetSubRangeUnchecked(i);
for (int j = 0; j < other.Count; j++)
{
if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j)))
{
return true;
}
}
}
}
return false;
}
///
/// Checks if a given multi-range is fully contained inside another.
///
/// Multi-range to be checked
/// True if all the sub-ranges on are contained inside the multi-range, with the same order, false otherwise
public bool Contains(MultiRange other)
{
return FindOffset(other) >= 0;
}
///
/// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained
/// inside the other multi-range, otherwise returns -1.
///
/// Multi-range that should be fully contained inside this one
/// Offset in bytes if fully contained, otherwise -1
public int FindOffset(MultiRange other)
{
int thisCount = Count;
int otherCount = other.Count;
if (thisCount == 1 && otherCount == 1)
{
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
MemoryRange currentFirstRange = GetSubRangeUnchecked(0);
if (otherFirstRange.Address >= currentFirstRange.Address &&
otherFirstRange.EndAddress <= currentFirstRange.EndAddress)
{
return (int)(otherFirstRange.Address - currentFirstRange.Address);
}
}
else if (thisCount >= otherCount)
{
ulong baseOffset = 0;
MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0);
MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1);
for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++)
{
MemoryRange currentFirstRange = GetSubRangeUnchecked(i);
MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1);
if (otherCount > 1)
{
if (otherFirstRange.Address < currentFirstRange.Address ||
otherFirstRange.EndAddress != currentFirstRange.EndAddress)
{
continue;
}
if (otherLastRange.Address != currentLastRange.Address ||
otherLastRange.EndAddress > currentLastRange.EndAddress)
{
continue;
}
bool fullMatch = true;
for (int j = 1; j < otherCount - 1; j++)
{
if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j)))
{
fullMatch = false;
break;
}
}
if (!fullMatch)
{
continue;
}
}
else if (currentFirstRange.Address > otherFirstRange.Address ||
currentFirstRange.EndAddress < otherFirstRange.EndAddress)
{
continue;
}
return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address));
}
}
return -1;
}
///
/// Gets the total size of all sub-ranges in bytes.
///
/// Total size in bytes
public ulong GetSize()
{
if (HasSingleRange)
{
return _singleRange.Size;
}
ulong sum = 0;
foreach (MemoryRange range in _ranges)
{
sum += range.Size;
}
return sum;
}
public override bool Equals(object obj)
{
return obj is MultiRange other && Equals(other);
}
public bool Equals(MultiRange other)
{
if (HasSingleRange && other.HasSingleRange)
{
return _singleRange.Equals(other._singleRange);
}
int thisCount = Count;
if (thisCount != other.Count)
{
return false;
}
for (int i = 0; i < thisCount; i++)
{
if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i)))
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
if (HasSingleRange)
{
return _singleRange.GetHashCode();
}
HashCode hash = new();
foreach (MemoryRange range in _ranges)
{
hash.Add(range);
}
return hash.ToHashCode();
}
///
/// Returns a string summary of the ranges contained in the MultiRange.
///
/// A string summary of the ranges contained within
public override string ToString()
{
return HasSingleRange ? _singleRange.ToString() : string.Join(", ", _ranges);
}
public static bool operator ==(MultiRange left, MultiRange right)
{
return left.Equals(right);
}
public static bool operator !=(MultiRange left, MultiRange right)
{
return !(left == right);
}
}
}