Use plain structs for kvdb

This commit is contained in:
Alex Barney 2019-10-15 12:33:15 -05:00
parent be907ce4bb
commit dee7c93285
7 changed files with 186 additions and 105 deletions

View file

@ -7,7 +7,7 @@ using LibHac.Ncm;
namespace LibHac.Fs namespace LibHac.Fs
{ {
[StructLayout(LayoutKind.Explicit, Size = 0x40)] [StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataAttribute public struct SaveDataAttribute : IEquatable<SaveDataAttribute>, IComparable<SaveDataAttribute>
{ {
[FieldOffset(0x00)] public ulong TitleId; [FieldOffset(0x00)] public ulong TitleId;
[FieldOffset(0x08)] public UserId UserId; [FieldOffset(0x08)] public UserId UserId;
@ -15,6 +15,50 @@ namespace LibHac.Fs
[FieldOffset(0x20)] public SaveDataType Type; [FieldOffset(0x20)] public SaveDataType Type;
[FieldOffset(0x21)] public byte Rank; [FieldOffset(0x21)] public byte Rank;
[FieldOffset(0x22)] public short Index; [FieldOffset(0x22)] public short Index;
public override bool Equals(object obj)
{
return obj is SaveDataAttribute attribute && Equals(attribute);
}
public bool Equals(SaveDataAttribute other)
{
return TitleId == other.TitleId &&
Type == other.Type &&
UserId.Equals(other.UserId) &&
SaveDataId == other.SaveDataId &&
Rank == other.Rank &&
Index == other.Index;
}
public override int GetHashCode()
{
// ReSharper disable NonReadonlyMemberInGetHashCode
int hashCode = 487790375;
hashCode = hashCode * -1521134295 + TitleId.GetHashCode();
hashCode = hashCode * -1521134295 + Type.GetHashCode();
hashCode = hashCode * -1521134295 + UserId.GetHashCode();
hashCode = hashCode * -1521134295 + SaveDataId.GetHashCode();
hashCode = hashCode * -1521134295 + Rank.GetHashCode();
hashCode = hashCode * -1521134295 + Index.GetHashCode();
return hashCode;
// ReSharper restore NonReadonlyMemberInGetHashCode
}
public int CompareTo(SaveDataAttribute other)
{
int titleIdComparison = TitleId.CompareTo(other.TitleId);
if (titleIdComparison != 0) return titleIdComparison;
int typeComparison = Type.CompareTo(other.Type);
if (typeComparison != 0) return typeComparison;
int userIdComparison = UserId.CompareTo(other.UserId);
if (userIdComparison != 0) return userIdComparison;
int saveDataIdComparison = SaveDataId.CompareTo(other.SaveDataId);
if (saveDataIdComparison != 0) return saveDataIdComparison;
int rankComparison = Rank.CompareTo(other.Rank);
if (rankComparison != 0) return rankComparison;
return Index.CompareTo(other.Index);
}
} }
[StructLayout(LayoutKind.Explicit, Size = 0x48)] [StructLayout(LayoutKind.Explicit, Size = 0x48)]

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
using LibHac.Fs;
namespace LibHac.FsService
{
[StructLayout(LayoutKind.Explicit, Size = 0x40)]
public struct SaveDataIndexerEntry
{
[FieldOffset(0x00)] public ulong SaveDataId;
[FieldOffset(0x08)] public ulong Size;
[FieldOffset(0x10)] public ulong Field10;
[FieldOffset(0x18)] public SaveDataSpaceId SpaceId;
[FieldOffset(0x19)] public byte State;
}
}

View file

@ -1,40 +0,0 @@
using System;
using System.Buffers.Binary;
using LibHac.Kvdb;
namespace LibHac.FsService
{
public class SaveIndexerStruct : IExportable
{
public ulong SaveId { get; private set; }
public ulong Size { get; private set; }
public byte SpaceId { get; private set; }
public byte Field19 { get; private set; }
public int ExportSize => 0x40;
private bool _isFrozen;
public void ToBytes(Span<byte> output)
{
if(output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
BinaryPrimitives.WriteUInt64LittleEndian(output, SaveId);
BinaryPrimitives.WriteUInt64LittleEndian(output.Slice(8), Size);
output[0x18] = SpaceId;
output[0x19] = Field19;
}
public void FromBytes(ReadOnlySpan<byte> input)
{
if(_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
if (input.Length < ExportSize) throw new InvalidOperationException("Input data is too short.");
SaveId = BinaryPrimitives.ReadUInt64LittleEndian(input);
Size = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(8));
SpaceId = input[0x18];
Field19 = input[0x19];
}
public void Freeze() => _isFrozen = true;
}
}

View file

@ -1,31 +0,0 @@
using System;
namespace LibHac.Kvdb
{
/// <summary>
/// A class for handling any value used by <see cref="KeyValueDatabase{TKey,TValue}"/>
/// </summary>
public class GenericValue : IExportable
{
private bool _isFrozen;
private byte[] _value;
public int ExportSize => _value?.Length ?? 0;
public void ToBytes(Span<byte> output)
{
if (output.Length < ExportSize) throw new InvalidOperationException("Output buffer is too small.");
_value.CopyTo(output);
}
public void FromBytes(ReadOnlySpan<byte> input)
{
if (_isFrozen) throw new InvalidOperationException("Unable to modify frozen object.");
_value = input.ToArray();
}
public void Freeze() => _isFrozen = true;
}
}

View file

@ -5,7 +5,7 @@ namespace LibHac.Kvdb
{ {
public ref struct ImkvdbReader public ref struct ImkvdbReader
{ {
private ReadOnlySpan<byte> _data; private readonly ReadOnlySpan<byte> _data;
private int _position; private int _position;
public ImkvdbReader(ReadOnlySpan<byte> data) public ImkvdbReader(ReadOnlySpan<byte> data)
@ -18,13 +18,14 @@ namespace LibHac.Kvdb
{ {
entryCount = default; entryCount = default;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdb.InvalidKeyValue; if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
ref ImkvdbHeader header = ref Unsafe.As<byte, ImkvdbHeader>(ref Unsafe.AsRef(_data[_position])); ref ImkvdbHeader header = ref Unsafe.As<byte, ImkvdbHeader>(ref Unsafe.AsRef(_data[_position]));
if (header.Magic != ImkvdbHeader.ExpectedMagic) if (header.Magic != ImkvdbHeader.ExpectedMagic)
{ {
return ResultKvdb.InvalidKeyValue; return ResultKvdb.InvalidKeyValue.Log();
} }
entryCount = header.EntryCount; entryCount = header.EntryCount;
@ -38,13 +39,14 @@ namespace LibHac.Kvdb
keySize = default; keySize = default;
valueSize = default; valueSize = default;
if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length) return ResultKvdb.InvalidKeyValue; if (_position + Unsafe.SizeOf<ImkvdbHeader>() > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
ref ImkvdbEntryHeader header = ref Unsafe.As<byte, ImkvdbEntryHeader>(ref Unsafe.AsRef(_data[_position])); ref ImkvdbEntryHeader header = ref Unsafe.As<byte, ImkvdbEntryHeader>(ref Unsafe.AsRef(_data[_position]));
if (header.Magic != ImkvdbEntryHeader.ExpectedMagic) if (header.Magic != ImkvdbEntryHeader.ExpectedMagic)
{ {
return ResultKvdb.InvalidKeyValue; return ResultKvdb.InvalidKeyValue.Log();
} }
keySize = header.KeySize; keySize = header.KeySize;
@ -63,7 +65,8 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbEntryHeader>(); _position += Unsafe.SizeOf<ImkvdbEntryHeader>();
if (_position + keySize + valueSize > _data.Length) return ResultKvdb.InvalidKeyValue; if (_position + keySize + valueSize > _data.Length)
return ResultKvdb.InvalidKeyValue.Log();
key = _data.Slice(_position, keySize); key = _data.Slice(_position, keySize);
value = _data.Slice(_position + keySize, valueSize); value = _data.Slice(_position + keySize, valueSize);

View file

@ -5,7 +5,7 @@ namespace LibHac.Kvdb
{ {
public ref struct ImkvdbWriter public ref struct ImkvdbWriter
{ {
private Span<byte> _data; private readonly Span<byte> _data;
private int _position; private int _position;
public ImkvdbWriter(Span<byte> data) public ImkvdbWriter(Span<byte> data)
@ -27,9 +27,9 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbHeader>(); _position += Unsafe.SizeOf<ImkvdbHeader>();
} }
public void WriteEntry(IExportable key, IExportable value) public void WriteEntry(ReadOnlySpan<byte> key, ReadOnlySpan<byte> value)
{ {
WriteEntryHeader(key.ExportSize, value.ExportSize); WriteEntryHeader(key.Length, value.Length);
Write(key); Write(key);
Write(value); Write(value);
} }
@ -47,13 +47,13 @@ namespace LibHac.Kvdb
_position += Unsafe.SizeOf<ImkvdbEntryHeader>(); _position += Unsafe.SizeOf<ImkvdbEntryHeader>();
} }
private void Write(IExportable value) private void Write(ReadOnlySpan<byte> value)
{ {
int valueSize = value.ExportSize; int valueSize = value.Length;
if (_position + valueSize > _data.Length) throw new InvalidOperationException(); if (_position + valueSize > _data.Length) throw new InvalidOperationException();
Span<byte> dest = _data.Slice(_position, valueSize); Span<byte> dest = _data.Slice(_position, valueSize);
value.ToBytes(dest); value.CopyTo(dest);
_position += valueSize; _position += valueSize;
} }

View file

@ -1,33 +1,51 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using LibHac.Common;
using LibHac.Fs;
namespace LibHac.Kvdb namespace LibHac.Kvdb
{ {
// Todo: Save and load from file public class KeyValueDatabase<TKey> where TKey : unmanaged, IComparable<TKey>, IEquatable<TKey>
public class KeyValueDatabase<TKey, TValue>
where TKey : IComparable<TKey>, IEquatable<TKey>, IExportable, new()
where TValue : IExportable, new()
{ {
private Dictionary<TKey, TValue> KvDict { get; } = new Dictionary<TKey, TValue>(); public Dictionary<TKey, byte[]> KvDict { get; } = new Dictionary<TKey, byte[]>();
public int Count => KvDict.Count; private FileSystemClient FsClient { get; }
private string FileName { get; }
public Result Get(TKey key, out TValue value) public KeyValueDatabase() { }
public KeyValueDatabase(FileSystemClient fsClient, string fileName)
{
FsClient = fsClient;
FileName = fileName;
}
public Result Get(ref TKey key, Span<byte> valueBuffer)
{
Result rc = GetValue(ref key, out byte[] value);
if (rc.IsFailure()) return rc;
int size = Math.Min(valueBuffer.Length, value.Length);
value.AsSpan(0, size).CopyTo(valueBuffer);
return Result.Success;
}
public Result GetValue(ref TKey key, out byte[] value)
{ {
if (!KvDict.TryGetValue(key, out value)) if (!KvDict.TryGetValue(key, out value))
{ {
return ResultKvdb.KeyNotFound; return ResultKvdb.KeyNotFound.Log();
} }
return Result.Success; return Result.Success;
} }
public Result Set(TKey key, TValue value) public Result Set(ref TKey key, byte[] value)
{ {
key.Freeze();
KvDict[key] = value; KvDict[key] = value;
return Result.Success; return Result.Success;
@ -35,6 +53,8 @@ namespace LibHac.Kvdb
public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data) public Result ReadDatabaseFromBuffer(ReadOnlySpan<byte> data)
{ {
KvDict.Clear();
var reader = new ImkvdbReader(data); var reader = new ImkvdbReader(data);
Result rc = reader.ReadHeader(out int entryCount); Result rc = reader.ReadHeader(out int entryCount);
@ -45,13 +65,12 @@ namespace LibHac.Kvdb
rc = reader.ReadEntry(out ReadOnlySpan<byte> keyBytes, out ReadOnlySpan<byte> valueBytes); rc = reader.ReadEntry(out ReadOnlySpan<byte> keyBytes, out ReadOnlySpan<byte> valueBytes);
if (rc.IsFailure()) return rc; if (rc.IsFailure()) return rc;
Debug.Assert(keyBytes.Length == Unsafe.SizeOf<TKey>());
var key = new TKey(); var key = new TKey();
var value = new TValue(); keyBytes.CopyTo(SpanHelpers.AsByteSpan(ref key));
key.FromBytes(keyBytes); byte[] value = valueBytes.ToArray();
value.FromBytes(valueBytes);
key.Freeze();
KvDict.Add(key, value); KvDict.Add(key, value);
} }
@ -65,26 +84,97 @@ namespace LibHac.Kvdb
writer.WriteHeader(KvDict.Count); writer.WriteHeader(KvDict.Count);
foreach (KeyValuePair<TKey, TValue> entry in KvDict.OrderBy(x => x.Key)) foreach (KeyValuePair<TKey, byte[]> entry in KvDict.OrderBy(x => x.Key))
{ {
writer.WriteEntry(entry.Key, entry.Value); TKey key = entry.Key;
writer.WriteEntry(SpanHelpers.AsByteSpan(ref key), entry.Value);
} }
return Result.Success; return Result.Success;
} }
public Result ReadDatabaseFromFile()
{
if (FsClient == null || FileName == null)
return ResultFs.PreconditionViolation.Log();
Result rc = ReadFile(out byte[] data);
if (rc.IsFailure())
{
return rc == ResultFs.PathNotFound ? Result.Success : rc;
}
return ReadDatabaseFromBuffer(data);
}
public Result WriteDatabaseToFile()
{
if (FsClient == null || FileName == null)
return ResultFs.PreconditionViolation.Log();
var buffer = new byte[GetExportedSize()];
Result rc = WriteDatabaseToBuffer(buffer);
if (rc.IsFailure()) return rc;
return WriteFile(buffer);
}
public int GetExportedSize() public int GetExportedSize()
{ {
int size = Unsafe.SizeOf<ImkvdbHeader>(); int size = Unsafe.SizeOf<ImkvdbHeader>();
foreach (KeyValuePair<TKey, TValue> entry in KvDict) foreach (byte[] value in KvDict.Values)
{ {
size += Unsafe.SizeOf<ImkvdbEntryHeader>(); size += Unsafe.SizeOf<ImkvdbEntryHeader>();
size += entry.Key.ExportSize; size += Unsafe.SizeOf<TKey>();
size += entry.Value.ExportSize; size += value.Length;
} }
return size; return size;
} }
private Result ReadFile(out byte[] data)
{
Debug.Assert(FsClient != null);
Debug.Assert(!string.IsNullOrWhiteSpace(FileName));
data = default;
Result rc = FsClient.OpenFile(out FileHandle handle, FileName, OpenMode.Read);
if (rc.IsFailure()) return rc;
rc = FsClient.GetFileSize(out long fileSize, handle);
if (rc.IsSuccess())
{
data = new byte[fileSize];
rc = FsClient.ReadFile(handle, 0, data);
}
FsClient.CloseFile(handle);
return rc;
}
private Result WriteFile(ReadOnlySpan<byte> data)
{
Debug.Assert(FsClient != null);
Debug.Assert(!string.IsNullOrWhiteSpace(FileName));
FsClient.DeleteFile(FileName);
Result rc = FsClient.CreateFile(FileName, data.Length);
if (rc.IsFailure()) return rc;
rc = FsClient.OpenFile(out FileHandle handle, FileName, OpenMode.Write);
if (rc.IsFailure()) return rc;
rc = FsClient.WriteFile(handle, 0, data, WriteOption.Flush);
FsClient.CloseFile(handle);
return rc;
}
} }
} }