mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2025-02-09 13:14:46 +01:00
Add FileSystemBufferManager
This commit is contained in:
parent
8e1eb0d057
commit
32a3750a92
@ -1,16 +1,28 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public readonly struct Buffer
|
||||
public readonly struct Buffer : IEquatable<Buffer>
|
||||
{
|
||||
public static Buffer Empty => default;
|
||||
public Memory<byte> Memory { get; }
|
||||
public Span<byte> Span => Memory.Span;
|
||||
public int Length => Memory.Length;
|
||||
public bool IsNull => Memory.IsEmpty;
|
||||
|
||||
public Buffer(Memory<byte> buffer)
|
||||
{
|
||||
Memory = buffer;
|
||||
}
|
||||
|
||||
public static bool operator ==(Buffer left, Buffer right) => left.Memory.Equals(right.Memory);
|
||||
public static bool operator !=(Buffer left, Buffer right) => !(left == right);
|
||||
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public override bool Equals(object obj) => obj is Buffer other && Equals(other);
|
||||
public bool Equals(Buffer other) => Memory.Equals(other.Memory);
|
||||
public override int GetHashCode() => Memory.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
@ -20,33 +20,33 @@ namespace LibHac.Fs
|
||||
|
||||
public const int BufferLevelMin = 0;
|
||||
|
||||
public (UIntPtr address, nuint size) AllocateBuffer(nuint size, BufferAttribute attribute) =>
|
||||
public Buffer AllocateBuffer(int size, BufferAttribute attribute) =>
|
||||
DoAllocateBuffer(size, attribute);
|
||||
|
||||
public void DeallocateBuffer(UIntPtr address, nuint size) => DoDeallocateBuffer(address, size);
|
||||
public void DeallocateBuffer(Buffer buffer) => DoDeallocateBuffer(buffer);
|
||||
|
||||
public CacheHandle RegisterCache(UIntPtr address, nuint size, BufferAttribute attribute) =>
|
||||
DoRegisterCache(address, size, attribute);
|
||||
public CacheHandle RegisterCache(Buffer buffer, BufferAttribute attribute) =>
|
||||
DoRegisterCache(buffer, attribute);
|
||||
|
||||
public (UIntPtr address, nuint size) AcquireCache(CacheHandle handle) => DoAcquireCache(handle);
|
||||
public nuint GetTotalSize() => DoGetTotalSize();
|
||||
public nuint GetFreeSize() => DoGetFreeSize();
|
||||
public nuint GetTotalAllocatableSize() => DoGetTotalAllocatableSize();
|
||||
public nuint GetFreeSizePeak() => DoGetFreeSizePeak();
|
||||
public nuint GetTotalAllocatableSizePeak() => DoGetTotalAllocatableSizePeak();
|
||||
public nuint GetRetriedCount() => DoGetRetriedCount();
|
||||
public Buffer AcquireCache(CacheHandle handle) => DoAcquireCache(handle);
|
||||
public int GetTotalSize() => DoGetTotalSize();
|
||||
public int GetFreeSize() => DoGetFreeSize();
|
||||
public int GetTotalAllocatableSize() => DoGetTotalAllocatableSize();
|
||||
public int GetFreeSizePeak() => DoGetFreeSizePeak();
|
||||
public int GetTotalAllocatableSizePeak() => DoGetTotalAllocatableSizePeak();
|
||||
public int GetRetriedCount() => DoGetRetriedCount();
|
||||
public void ClearPeak() => DoClearPeak();
|
||||
|
||||
protected abstract (UIntPtr address, nuint size) DoAllocateBuffer(nuint size, BufferAttribute attribute);
|
||||
protected abstract void DoDeallocateBuffer(UIntPtr address, nuint size);
|
||||
protected abstract CacheHandle DoRegisterCache(UIntPtr address, nuint size, BufferAttribute attribute);
|
||||
protected abstract (UIntPtr address, nuint size) DoAcquireCache(CacheHandle handle);
|
||||
protected abstract nuint DoGetTotalSize();
|
||||
protected abstract nuint DoGetFreeSize();
|
||||
protected abstract nuint DoGetTotalAllocatableSize();
|
||||
protected abstract nuint DoGetFreeSizePeak();
|
||||
protected abstract nuint DoGetTotalAllocatableSizePeak();
|
||||
protected abstract nuint DoGetRetriedCount();
|
||||
protected abstract Buffer DoAllocateBuffer(int size, BufferAttribute attribute);
|
||||
protected abstract void DoDeallocateBuffer(Buffer buffer);
|
||||
protected abstract CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute);
|
||||
protected abstract Buffer DoAcquireCache(CacheHandle handle);
|
||||
protected abstract int DoGetTotalSize();
|
||||
protected abstract int DoGetFreeSize();
|
||||
protected abstract int DoGetTotalAllocatableSize();
|
||||
protected abstract int DoGetFreeSizePeak();
|
||||
protected abstract int DoGetTotalAllocatableSizePeak();
|
||||
protected abstract int DoGetRetriedCount();
|
||||
protected abstract void DoClearPeak();
|
||||
|
||||
protected virtual void Dispose(bool disposing) { }
|
||||
|
@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
using Buffer = LibHac.Fs.Buffer;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.FsSystem
|
||||
@ -25,9 +26,6 @@ namespace LibHac.FsSystem
|
||||
private PageList* ExternalFreeLists { get; set; }
|
||||
private PageList[] InternalFreeLists { get; set; }
|
||||
|
||||
// Addition: Handle to allow initialization with a Memory<byte>
|
||||
private MemoryHandle PinnedMemoryHandle { get; set; }
|
||||
|
||||
private struct PageList
|
||||
{
|
||||
private PageEntry* FirstPageEntry { get; set; }
|
||||
@ -157,7 +155,8 @@ namespace LibHac.FsSystem
|
||||
FreeLists = null;
|
||||
ExternalFreeLists = null;
|
||||
InternalFreeLists = null;
|
||||
PinnedMemoryHandle.Dispose();
|
||||
PinnedHeapMemoryHandle.Dispose();
|
||||
PinnedWorkMemoryHandle.Dispose();
|
||||
}
|
||||
|
||||
public static int GetBlockCountFromOrder(int order)
|
||||
@ -192,16 +191,6 @@ namespace LibHac.FsSystem
|
||||
}
|
||||
}
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, uint blockSize, int orderMax)
|
||||
{
|
||||
PinnedMemoryHandle = heapBuffer.Pin();
|
||||
|
||||
var address = (UIntPtr)PinnedMemoryHandle.Pointer;
|
||||
var size = (nuint)heapBuffer.Length;
|
||||
|
||||
return Initialize(address, size, blockSize, orderMax);
|
||||
}
|
||||
|
||||
public Result Initialize(UIntPtr address, nuint size, nuint blockSize, void* workBuffer, nuint workBufferSize)
|
||||
{
|
||||
return Initialize(address, size, blockSize, QueryOrderMax(size, blockSize), workBuffer, workBufferSize);
|
||||
@ -250,14 +239,14 @@ namespace LibHac.FsSystem
|
||||
Assert.True(maxPageCount > 0);
|
||||
|
||||
// Setup the free lists
|
||||
if (ExternalFreeLists is not null)
|
||||
if (ExternalFreeLists != null)
|
||||
{
|
||||
Assert.Null(InternalFreeLists);
|
||||
FreeLists = ExternalFreeLists;
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalFreeLists = new PageList[OrderMax + 1];
|
||||
InternalFreeLists = GC.AllocateArray<PageList>(OrderMax + 1, true);
|
||||
FreeLists = (PageList*)Unsafe.AsPointer(ref MemoryMarshal.GetArrayDataReference(InternalFreeLists));
|
||||
if (InternalFreeLists == null)
|
||||
return ResultFs.AllocationFailureInFileSystemBuddyHeapA.Log();
|
||||
@ -405,20 +394,6 @@ namespace LibHac.FsSystem
|
||||
return BlockSize;
|
||||
}
|
||||
|
||||
// Not in original
|
||||
public int GetPageBlockCountMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return 1 << GetOrderMax();
|
||||
}
|
||||
|
||||
// Not in original
|
||||
public nuint GetPageSizeMax()
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
return (nuint)GetPageBlockCountMax() * GetBlockSize();
|
||||
}
|
||||
|
||||
private void DivideBuddies(PageEntry* pageEntry, int requiredOrder, int chosenOrder)
|
||||
{
|
||||
Assert.True(FreeLists != null);
|
||||
@ -591,5 +566,81 @@ namespace LibHac.FsSystem
|
||||
{
|
||||
return Alignment.IsAlignedPow2(GetIndexFromPageEntry(pageEntry), (uint)GetBlockCountFromOrder(order));
|
||||
}
|
||||
|
||||
// Addition: The below fields and methods allow using Memory<byte> with the class instead
|
||||
// of raw pointers.
|
||||
private MemoryHandle PinnedHeapMemoryHandle { get; set; }
|
||||
private Memory<byte> HeapBuffer { get; set; }
|
||||
private MemoryHandle PinnedWorkMemoryHandle { get; set; }
|
||||
private Memory<byte> WorkBuffer { get; set; }
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, int blockSize, Memory<byte> workBuffer)
|
||||
{
|
||||
return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize),
|
||||
workBuffer);
|
||||
}
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, int blockSize, int orderMax, Memory<byte> workBuffer)
|
||||
{
|
||||
PinnedWorkMemoryHandle = workBuffer.Pin();
|
||||
WorkBuffer = workBuffer;
|
||||
|
||||
PinnedHeapMemoryHandle = heapBuffer.Pin();
|
||||
HeapBuffer = heapBuffer;
|
||||
|
||||
var heapAddress = (UIntPtr)PinnedHeapMemoryHandle.Pointer;
|
||||
var heapSize = (nuint)heapBuffer.Length;
|
||||
|
||||
void* workAddress = PinnedHeapMemoryHandle.Pointer;
|
||||
var workSize = (nuint)heapBuffer.Length;
|
||||
|
||||
return Initialize(heapAddress, heapSize, (nuint)blockSize, orderMax, workAddress, workSize);
|
||||
}
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, int blockSize)
|
||||
{
|
||||
return Initialize(heapBuffer, blockSize, QueryOrderMax((nuint)heapBuffer.Length, (nuint)blockSize));
|
||||
}
|
||||
|
||||
public Result Initialize(Memory<byte> heapBuffer, int blockSize, int orderMax)
|
||||
{
|
||||
PinnedHeapMemoryHandle = heapBuffer.Pin();
|
||||
HeapBuffer = heapBuffer;
|
||||
|
||||
var address = (UIntPtr)PinnedHeapMemoryHandle.Pointer;
|
||||
var size = (nuint)heapBuffer.Length;
|
||||
|
||||
return Initialize(address, size, (nuint)blockSize, orderMax);
|
||||
}
|
||||
|
||||
public Buffer AllocateBufferByOrder(int order)
|
||||
{
|
||||
Assert.True(!HeapBuffer.IsEmpty);
|
||||
|
||||
void* address = AllocateByOrder(order);
|
||||
|
||||
if (address == null)
|
||||
return Buffer.Empty;
|
||||
|
||||
nuint size = GetBytesFromOrder(order);
|
||||
Assert.True(size <= int.MaxValue);
|
||||
|
||||
// Get the offset relative to the heap start
|
||||
nuint offset = (nuint)address - (nuint)PinnedHeapMemoryHandle.Pointer;
|
||||
Assert.True(offset <= (nuint)HeapBuffer.Length);
|
||||
|
||||
// Get a slice of the Memory<byte> containing the entire heap
|
||||
return new Buffer(HeapBuffer.Slice((int)offset, (int)size));
|
||||
}
|
||||
|
||||
public void Free(Buffer buffer)
|
||||
{
|
||||
Assert.True(!HeapBuffer.IsEmpty);
|
||||
Assert.True(!buffer.IsNull);
|
||||
|
||||
int order = GetOrderFromBytes((nuint)buffer.Length);
|
||||
void* pointer = Unsafe.AsPointer(ref MemoryMarshal.GetReference(buffer.Span));
|
||||
Free(pointer, order);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
562
src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs
Normal file
562
src/LibHac/FsSystem/Buffers/FileSystemBufferManager.cs
Normal file
@ -0,0 +1,562 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using LibHac.Diag;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Util;
|
||||
using Buffer = LibHac.Fs.Buffer;
|
||||
using CacheHandle = System.Int64;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace LibHac.FsSystem
|
||||
{
|
||||
public class FileSystemBufferManager : IBufferManager
|
||||
{
|
||||
private class CacheHandleTable
|
||||
{
|
||||
private struct Entry
|
||||
{
|
||||
private CacheHandle _handle;
|
||||
private Buffer _buffer;
|
||||
private BufferAttribute _attribute;
|
||||
|
||||
public void Initialize(CacheHandle handle, Buffer buffer, BufferAttribute attribute)
|
||||
{
|
||||
_handle = handle;
|
||||
_buffer = buffer;
|
||||
_attribute = attribute;
|
||||
}
|
||||
|
||||
public readonly CacheHandle GetHandle() => _handle;
|
||||
public readonly Buffer GetBuffer() => _buffer;
|
||||
public readonly int GetSize() => _buffer.Length;
|
||||
public readonly BufferAttribute GetBufferAttribute() => _attribute;
|
||||
}
|
||||
|
||||
private struct AttrInfo
|
||||
{
|
||||
private int _level;
|
||||
private int _cacheCount;
|
||||
private int _cacheSize;
|
||||
|
||||
public AttrInfo(int level, int cacheCount, int cacheSize)
|
||||
{
|
||||
_level = level;
|
||||
_cacheCount = cacheCount;
|
||||
_cacheSize = cacheSize;
|
||||
}
|
||||
|
||||
public int GetLevel() => _level;
|
||||
public int GetCacheCount() => _cacheCount;
|
||||
public void IncrementCacheCount() => _cacheCount++;
|
||||
public void DecrementCacheCount() => _cacheCount--;
|
||||
public int GetCacheSize() => _cacheSize;
|
||||
public void AddCacheSize(int diff) => _cacheSize += diff;
|
||||
public void SubtractCacheSize(int diff)
|
||||
{
|
||||
Assert.True(_cacheSize >= diff);
|
||||
_cacheSize -= diff;
|
||||
}
|
||||
}
|
||||
|
||||
private Entry[] Entries { get; set; }
|
||||
private int EntryCount { get; set; }
|
||||
private int EntryCountMax { get; set; }
|
||||
private LinkedList<AttrInfo> AttrList { get; set; } = new();
|
||||
private int CacheCountMin { get; set; }
|
||||
private int CacheSizeMin { get; set; }
|
||||
private int TotalCacheSize { get; set; }
|
||||
private CacheHandle CurrentHandle { get; set; }
|
||||
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
// We can't use an external buffer in C# without ensuring all allocated buffers are pinned.
|
||||
// This function is left here anyway for completion's sake.
|
||||
public static int QueryWorkBufferSize(int maxCacheCount)
|
||||
{
|
||||
Assert.True(maxCacheCount > 0);
|
||||
|
||||
int entryAlignment = sizeof(CacheHandle);
|
||||
int attrInfoAlignment = Unsafe.SizeOf<nuint>();
|
||||
|
||||
int entrySize = Unsafe.SizeOf<Entry>() * maxCacheCount;
|
||||
int attrListSize = Unsafe.SizeOf<AttrInfo>() * 0x100;
|
||||
return (int)Alignment.AlignUpPow2(
|
||||
(ulong)(entrySize + attrListSize + entryAlignment + attrInfoAlignment), 8);
|
||||
}
|
||||
|
||||
public Result Initialize(int maxCacheCount)
|
||||
{
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries == null);
|
||||
|
||||
// Note: We don't have the option of using an external Entry buffer like the original
|
||||
// because Entry includes managed references so we can't cast a byte* to Entry* without pinning.
|
||||
// If we don't have an external buffer, try to allocate an internal one.
|
||||
|
||||
Entries = new Entry[maxCacheCount];
|
||||
|
||||
if (Entries == null)
|
||||
{
|
||||
return ResultFs.AllocationFailureInFileSystemBufferManagerA.Log();
|
||||
}
|
||||
|
||||
// Set entries.
|
||||
EntryCount = 0;
|
||||
EntryCountMax = maxCacheCount;
|
||||
|
||||
Assert.True(Entries != null);
|
||||
|
||||
CacheCountMin = maxCacheCount / 16;
|
||||
CacheSizeMin = CacheCountMin * 0x100;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private int GetCacheCountMin(BufferAttribute attr)
|
||||
{
|
||||
return CacheCountMin;
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private int GetCacheSizeMin(BufferAttribute attr)
|
||||
{
|
||||
return CacheSizeMin;
|
||||
}
|
||||
|
||||
public bool Register(out CacheHandle handle, Buffer buffer, BufferAttribute attr)
|
||||
{
|
||||
Unsafe.SkipInit(out handle);
|
||||
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
Assert.True(!Unsafe.IsNullRef(ref handle));
|
||||
|
||||
// Get the entry.
|
||||
ref Entry entry = ref AcquireEntry(buffer, attr);
|
||||
|
||||
// If we don't have an entry, we can't register.
|
||||
if (Unsafe.IsNullRef(ref entry))
|
||||
return false;
|
||||
|
||||
// Get the attr info. If we have one, increment.
|
||||
ref AttrInfo attrInfo = ref FindAttrInfo(attr);
|
||||
if (!Unsafe.IsNullRef(ref attrInfo))
|
||||
{
|
||||
attrInfo.IncrementCacheCount();
|
||||
attrInfo.AddCacheSize(buffer.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make a new attr info and add it to the list.
|
||||
// Note: Not using attr info buffer
|
||||
var newInfo = new AttrInfo(attr.Level, 1, buffer.Length);
|
||||
AttrList.AddLast(newInfo);
|
||||
}
|
||||
|
||||
TotalCacheSize += buffer.Length;
|
||||
handle = entry.GetHandle();
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Unregister(out Buffer buffer, CacheHandle handle)
|
||||
{
|
||||
Unsafe.SkipInit(out buffer);
|
||||
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
Assert.True(!Unsafe.IsNullRef(ref buffer));
|
||||
|
||||
// Find the lower bound for the entry.
|
||||
for (int i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if (Entries[i].GetHandle() == handle)
|
||||
{
|
||||
UnregisterCore(out buffer, ref Entries[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ReSharper disable UnusedParameter.Local
|
||||
public bool UnregisterOldest(out Buffer buffer, BufferAttribute attr, int requiredSize = 0)
|
||||
// ReSharper restore UnusedParameter.Local
|
||||
{
|
||||
Unsafe.SkipInit(out buffer);
|
||||
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
Assert.True(!Unsafe.IsNullRef(ref buffer));
|
||||
|
||||
// If we have no entries, we can't unregister any.
|
||||
if (EntryCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool CanUnregister(CacheHandleTable table, ref Entry entry)
|
||||
{
|
||||
ref AttrInfo attrInfo = ref table.FindAttrInfo(entry.GetBufferAttribute());
|
||||
Assert.True(!Unsafe.IsNullRef(ref attrInfo));
|
||||
|
||||
int ccm = table.GetCacheCountMin(entry.GetBufferAttribute());
|
||||
int csm = table.GetCacheSizeMin(entry.GetBufferAttribute());
|
||||
|
||||
return ccm < attrInfo.GetCacheCount() && csm + entry.GetSize() <= attrInfo.GetCacheSize();
|
||||
}
|
||||
|
||||
// Find an entry, falling back to the first entry.
|
||||
ref Entry entry = ref Unsafe.NullRef<Entry>();
|
||||
for (int i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if (CanUnregister(this, ref Entries[i]))
|
||||
{
|
||||
entry = ref Entries[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (Unsafe.IsNullRef(ref entry))
|
||||
{
|
||||
entry = ref Entries[0];
|
||||
}
|
||||
|
||||
Assert.True(!Unsafe.IsNullRef(ref entry));
|
||||
UnregisterCore(out buffer, ref entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void UnregisterCore(out Buffer buffer, ref Entry entry)
|
||||
{
|
||||
Unsafe.SkipInit(out buffer);
|
||||
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
Assert.True(!Unsafe.IsNullRef(ref buffer));
|
||||
Assert.True(!Unsafe.IsNullRef(ref entry));
|
||||
|
||||
// Get the attribute info.
|
||||
ref AttrInfo attrInfo = ref FindAttrInfo(entry.GetBufferAttribute());
|
||||
Assert.True(!Unsafe.IsNullRef(ref attrInfo));
|
||||
Assert.True(attrInfo.GetCacheCount() > 0);
|
||||
Assert.True(attrInfo.GetCacheSize() >= entry.GetSize());
|
||||
|
||||
// Release from the attr info.
|
||||
attrInfo.DecrementCacheCount();
|
||||
attrInfo.SubtractCacheSize(entry.GetSize());
|
||||
|
||||
// Release from cached size.
|
||||
Assert.True(TotalCacheSize >= entry.GetSize());
|
||||
TotalCacheSize -= entry.GetSize();
|
||||
|
||||
// Release the entry.
|
||||
buffer = entry.GetBuffer();
|
||||
ReleaseEntry(ref entry);
|
||||
}
|
||||
|
||||
public CacheHandle PublishCacheHandle()
|
||||
{
|
||||
Assert.True(Entries != null);
|
||||
return ++CurrentHandle;
|
||||
}
|
||||
|
||||
public int GetTotalCacheSize()
|
||||
{
|
||||
return TotalCacheSize;
|
||||
}
|
||||
|
||||
private ref Entry AcquireEntry(Buffer buffer, BufferAttribute attr)
|
||||
{
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
|
||||
ref Entry entry = ref Unsafe.NullRef<Entry>();
|
||||
if (EntryCount < EntryCountMax)
|
||||
{
|
||||
entry = ref Entries[EntryCount];
|
||||
entry.Initialize(PublishCacheHandle(), buffer, attr);
|
||||
EntryCount++;
|
||||
Assert.True(EntryCount == 1 || Entries[EntryCount - 1].GetHandle() < entry.GetHandle());
|
||||
}
|
||||
|
||||
return ref entry;
|
||||
}
|
||||
|
||||
private void ReleaseEntry(ref Entry entry)
|
||||
{
|
||||
// Validate pre-conditions.
|
||||
Assert.True(Entries != null);
|
||||
Assert.True(!Unsafe.IsNullRef(ref entry));
|
||||
|
||||
// Ensure the entry is valid.
|
||||
Span<Entry> entryBuffer = Entries;
|
||||
Assert.True(Unsafe.IsAddressGreaterThan(ref entry, ref MemoryMarshal.GetReference(entryBuffer)));
|
||||
Assert.True(Unsafe.IsAddressLessThan(ref entry,
|
||||
ref Unsafe.Add(ref MemoryMarshal.GetReference(entryBuffer), entryBuffer.Length)));
|
||||
|
||||
// Get the index of the entry.
|
||||
int index = Unsafe.ByteOffset(ref MemoryMarshal.GetReference(entryBuffer), ref entry).ToInt32() /
|
||||
Unsafe.SizeOf<Entry>();
|
||||
|
||||
// Copy the entries back by one.
|
||||
Span<Entry> source = entryBuffer.Slice(index + 1, EntryCount - (index + 1));
|
||||
Span<Entry> dest = entryBuffer.Slice(index);
|
||||
source.CopyTo(dest);
|
||||
|
||||
// Decrement our entry count.
|
||||
EntryCount--;
|
||||
}
|
||||
|
||||
private ref AttrInfo FindAttrInfo(BufferAttribute attr)
|
||||
{
|
||||
LinkedListNode<AttrInfo> curNode = AttrList.First;
|
||||
|
||||
while (curNode != null)
|
||||
{
|
||||
if (curNode.ValueRef.GetLevel() == attr.Level)
|
||||
{
|
||||
return ref curNode.ValueRef;
|
||||
}
|
||||
|
||||
curNode = curNode.Next;
|
||||
}
|
||||
|
||||
return ref Unsafe.NullRef<AttrInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystemBuddyHeap BuddyHeap { get; } = new();
|
||||
private CacheHandleTable CacheTable { get; } = new();
|
||||
private int TotalSize { get; set; }
|
||||
private int PeakFreeSize { get; set; }
|
||||
private int PeakTotalAllocatableSize { get; set; }
|
||||
private int RetriedCount { get; set; }
|
||||
private object Locker { get; } = new();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
BuddyHeap.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public Result Initialize(int maxCacheCount, Memory<byte> heapBuffer, int blockSize, Memory<byte> workBuffer)
|
||||
{
|
||||
// Note: We can't use an external buffer for the cache handle table,
|
||||
// so pass the work buffer directly to the buddy heap.
|
||||
Result rc = CacheTable.Initialize(maxCacheCount);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
rc = BuddyHeap.Initialize(heapBuffer, blockSize, workBuffer);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
TotalSize = (int)BuddyHeap.GetTotalFreeSize();
|
||||
PeakFreeSize = TotalSize;
|
||||
PeakTotalAllocatableSize = TotalSize;
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Buffer DoAllocateBuffer(int size, BufferAttribute attribute)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return AllocateBufferImpl(size, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer AllocateBufferImpl(int size, BufferAttribute attribute)
|
||||
{
|
||||
int order = BuddyHeap.GetOrderFromBytes((nuint)size);
|
||||
Assert.True(order >= 0);
|
||||
|
||||
// Allocate space on the heap
|
||||
Buffer buffer;
|
||||
while ((buffer = BuddyHeap.AllocateBufferByOrder(order)).IsNull)
|
||||
{
|
||||
// Not enough space in heap. Deallocate cached buffer and try again.
|
||||
RetriedCount++;
|
||||
|
||||
if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute, size))
|
||||
{
|
||||
// No cached buffers left to deallocate.
|
||||
return Buffer.Empty;
|
||||
}
|
||||
DeallocateBufferImpl(deallocateBuffer);
|
||||
}
|
||||
|
||||
// Successfully allocated a buffer.
|
||||
int allocatedSize = (int)BuddyHeap.GetBytesFromOrder(order);
|
||||
Assert.True(size <= allocatedSize);
|
||||
|
||||
// Update heap stats
|
||||
int freeSize = (int)BuddyHeap.GetTotalFreeSize();
|
||||
PeakFreeSize = Math.Min(PeakFreeSize, freeSize);
|
||||
|
||||
int totalAllocatableSize = freeSize + CacheTable.GetTotalCacheSize();
|
||||
PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
protected override void DoDeallocateBuffer(Buffer buffer)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
DeallocateBufferImpl(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void DeallocateBufferImpl(Buffer buffer)
|
||||
{
|
||||
Assert.True(BitUtil.IsPowerOfTwo(buffer.Length));
|
||||
|
||||
BuddyHeap.Free(buffer);
|
||||
}
|
||||
|
||||
protected override CacheHandle DoRegisterCache(Buffer buffer, BufferAttribute attribute)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return RegisterCacheImpl(buffer, attribute);
|
||||
}
|
||||
}
|
||||
|
||||
private CacheHandle RegisterCacheImpl(Buffer buffer, BufferAttribute attribute)
|
||||
{
|
||||
CacheHandle handle;
|
||||
|
||||
// Try to register the handle.
|
||||
while (!CacheTable.Register(out handle, buffer, attribute))
|
||||
{
|
||||
// Unregister a buffer and try registering again.
|
||||
RetriedCount++;
|
||||
if (!CacheTable.UnregisterOldest(out Buffer deallocateBuffer, attribute))
|
||||
{
|
||||
// Can't unregister any existing buffers.
|
||||
// Register the input buffer to /dev/null.
|
||||
DeallocateBufferImpl(buffer);
|
||||
return CacheTable.PublishCacheHandle();
|
||||
}
|
||||
|
||||
// Deallocate the unregistered buffer.
|
||||
DeallocateBufferImpl(deallocateBuffer);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
protected override Buffer DoAcquireCache(CacheHandle handle)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return AcquireCacheImpl(handle);
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer AcquireCacheImpl(CacheHandle handle)
|
||||
{
|
||||
if (CacheTable.Unregister(out Buffer range, handle))
|
||||
{
|
||||
int totalAllocatableSize = (int)BuddyHeap.GetTotalFreeSize() + CacheTable.GetTotalCacheSize();
|
||||
PeakTotalAllocatableSize = Math.Min(PeakTotalAllocatableSize, totalAllocatableSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
range = Buffer.Empty;
|
||||
}
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
protected override int DoGetTotalSize()
|
||||
{
|
||||
return TotalSize;
|
||||
}
|
||||
|
||||
protected override int DoGetFreeSize()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return GetFreeSizeImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetFreeSizeImpl()
|
||||
{
|
||||
return (int)BuddyHeap.GetTotalFreeSize();
|
||||
}
|
||||
|
||||
protected override int DoGetTotalAllocatableSize()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return GetTotalAllocatableSizeImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTotalAllocatableSizeImpl()
|
||||
{
|
||||
return GetFreeSizeImpl() + CacheTable.GetTotalCacheSize();
|
||||
}
|
||||
|
||||
protected override int DoGetFreeSizePeak()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return GetFreeSizePeakImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetFreeSizePeakImpl()
|
||||
{
|
||||
return PeakFreeSize;
|
||||
}
|
||||
|
||||
protected override int DoGetTotalAllocatableSizePeak()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return GetTotalAllocatableSizePeakImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetTotalAllocatableSizePeakImpl()
|
||||
{
|
||||
return PeakTotalAllocatableSize;
|
||||
}
|
||||
|
||||
protected override int DoGetRetriedCount()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
return GetRetriedCountImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private int GetRetriedCountImpl()
|
||||
{
|
||||
return RetriedCount;
|
||||
}
|
||||
|
||||
protected override void DoClearPeak()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
ClearPeakImpl();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearPeakImpl()
|
||||
{
|
||||
PeakFreeSize = GetFreeSizeImpl();
|
||||
PeakTotalAllocatableSize = GetTotalAllocatableSizeImpl();
|
||||
RetriedCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,7 @@ using LibHac.Util;
|
||||
|
||||
namespace LibHac.FsSystem.RomFs
|
||||
{
|
||||
// todo: Change constraint to "unmanaged" after updating to
|
||||
// a newer SDK https://github.com/dotnet/csharplang/issues/1937
|
||||
internal class RomFsDictionary<T> where T : struct
|
||||
internal class RomFsDictionary<T> where T : unmanaged
|
||||
{
|
||||
private int _count;
|
||||
private int _length;
|
||||
@ -194,7 +192,7 @@ namespace LibHac.FsSystem.RomFs
|
||||
if (value != _capacity)
|
||||
{
|
||||
var newBuffer = new byte[value];
|
||||
Buffer.BlockCopy(Entries, 0, newBuffer, 0, _length);
|
||||
System.Buffer.BlockCopy(Entries, 0, newBuffer, 0, _length);
|
||||
|
||||
Entries = newBuffer;
|
||||
_capacity = value;
|
||||
|
Loading…
x
Reference in New Issue
Block a user