diff --git a/CHANGELOG.md b/CHANGELOG.md index 201e9b8..e477cd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Changelog +### 0.9.84 - 20/09/2024 +* Fixed a bug with Tuple3 values that had a reference in the first position. +* Added a user accessible remap function for values + ### 0.9.83 - 20/09/2024 * Optimized the interface with RocksDB used all throughout the library. Results in a 30% speedup on search operations inside RocksDB. diff --git a/src/NexusMods.MnemonicDB.Abstractions/PartitionId.cs b/src/NexusMods.MnemonicDB.Abstractions/PartitionId.cs index 021814c..128b5ad 100644 --- a/src/NexusMods.MnemonicDB.Abstractions/PartitionId.cs +++ b/src/NexusMods.MnemonicDB.Abstractions/PartitionId.cs @@ -51,7 +51,20 @@ public static PartitionId User(byte id) public EntityId MaxValue => EntityId.From(((ulong)Value << 56) | 0x00FFFFFFFFFFFFFF); /// - public override string ToString() => $"PartId:{Value:x}"; + public override string ToString() + { + if (Value == Attribute) + return $"PartId:({Attribute.Value:x})Attribute"; + if (Value == Transactions) + return $"PartId:({Transactions.Value:x})Transactions"; + if (Value == Entity) + return $"PartId:({Entity.Value:x})Entity"; + if (Value == Temp) + return $"PartId:({Temp.Value:x})Temp"; + if (Value > Temp) + return $"PartId:({Value:x})User"; + return $"PartId:{Value:x}"; + } /// /// Encode a partition id and entity id pair diff --git a/src/NexusMods.MnemonicDB.Abstractions/ValueHelpers.cs b/src/NexusMods.MnemonicDB.Abstractions/ValueHelpers.cs new file mode 100644 index 0000000..5ed5e28 --- /dev/null +++ b/src/NexusMods.MnemonicDB.Abstractions/ValueHelpers.cs @@ -0,0 +1,66 @@ +using System; +using System.Runtime.InteropServices; +using NexusMods.MnemonicDB.Abstractions.ElementComparers; +using NexusMods.MnemonicDB.Abstractions.Internals; +using Reloaded.Memory.Extensions; + +namespace NexusMods.MnemonicDB.Abstractions; + +/// +/// Static methods that help with reading, writing and formatting values +/// +public static class ValueHelpers +{ + /// + /// Remaps the values of this attribute, if required. + /// + public static void Remap(Func remapFn, in KeyPrefix prefix, Span valueSpan) + { + switch (prefix.ValueTag) + { + case ValueTags.Reference: + var oldId = MemoryMarshal.Read(valueSpan); + var newId = remapFn(oldId); + MemoryMarshal.Write(valueSpan, newId); + break; + case ValueTags.Tuple2: + { + var tag1 = (ValueTags)valueSpan[0]; + var tag2 = (ValueTags)valueSpan[1]; + if (tag1 == ValueTags.Reference) + { + var entityId = MemoryMarshal.Read(valueSpan.SliceFast(2)); + var newEntityId = remapFn(entityId); + MemoryMarshal.Write(valueSpan.SliceFast(2), newEntityId); + } + if (tag2 == ValueTags.Reference) + { + throw new NotSupportedException("This attribute does not support remapping of the second element."); + } + break; + } + case ValueTags.Tuple3: + { + var tag1 = (ValueTags)valueSpan[0]; + var tag2 = (ValueTags)valueSpan[1]; + var tag3 = (ValueTags)valueSpan[2]; + if (tag1 == ValueTags.Reference) + { + var entityId = MemoryMarshal.Read(valueSpan.SliceFast(3)); + var newEntityId = remapFn(entityId); + MemoryMarshal.Write(valueSpan.SliceFast(3), newEntityId); + } + if (tag2 == ValueTags.Reference) + { + throw new NotSupportedException("This attribute does not support remapping of the second element."); + } + if (tag3 == ValueTags.Reference) + { + throw new NotSupportedException("This attribute does not support remapping of the third element."); + } + break; + } + } + } + +} diff --git a/src/NexusMods.MnemonicDB/Storage/DatomStore.cs b/src/NexusMods.MnemonicDB/Storage/DatomStore.cs index b7f0669..88fc684 100644 --- a/src/NexusMods.MnemonicDB/Storage/DatomStore.cs +++ b/src/NexusMods.MnemonicDB/Storage/DatomStore.cs @@ -52,6 +52,11 @@ public class DatomStore : IDatomStore private IDb? _currentDb = null; private static readonly TimeSpan TransactionTimeout = TimeSpan.FromMinutes(120); + + /// + /// Cached function to remap temporary entity ids to real entity ids + /// + private readonly Func _remapFunc; /// /// Used to remap temporary entity ids to real entity ids, this is cleared after each transaction @@ -76,6 +81,7 @@ public class DatomStore : IDatomStore /// public DatomStore(ILogger logger, DatomStoreSettings settings, IStoreBackend backend) { + _remapFunc = Remap; _attributeCache = backend.AttributeCache; _pendingTransactions = new BlockingCollection(new ConcurrentQueue()); @@ -400,7 +406,7 @@ private void Log(PendingTransaction pendingTransaction, out StoreResult result) var valueSpan = datom.ValueSpan; var span = _writer.GetSpan(valueSpan.Length); valueSpan.CopyTo(span); - Remap(in keyPrefix, span); + ValueHelpers.Remap(_remapFunc, in keyPrefix, span); _writer.Advance(valueSpan.Length); } @@ -450,35 +456,6 @@ private void Log(PendingTransaction pendingTransaction, out StoreResult result) }; } - private void Remap(in KeyPrefix prefix, Span valueSpan) - { - switch (prefix.ValueTag) - { - case ValueTags.Reference: - var oldId = MemoryMarshal.Read(valueSpan); - var newId = Remap(oldId); - MemoryMarshal.Write(valueSpan, newId); - break; - case ValueTags.Tuple2: - { - var tag1 = (ValueTags)valueSpan[0]; - var tag2 = (ValueTags)valueSpan[1]; - if (tag1 == ValueTags.Reference) - { - var entityId = MemoryMarshal.Read(valueSpan.SliceFast(2)); - var newEntityId = Remap(entityId); - MemoryMarshal.Write(valueSpan.SliceFast(2), newEntityId); - } - if (tag2 == ValueTags.Reference) - { - throw new NotSupportedException("This attribute does not support remapping of the second element."); - } - break; - } - } - - } - /// /// Updates the data in _prevWriter to be a retraction of the data in that write. /// diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/Int3Attribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/Int3Attribute.cs deleted file mode 100644 index 75e784e..0000000 --- a/tests/NexusMods.MnemonicDB.TestModel/Attributes/Int3Attribute.cs +++ /dev/null @@ -1,18 +0,0 @@ -using NexusMods.MnemonicDB.Abstractions; -using NexusMods.MnemonicDB.Abstractions.Attributes; -using NexusMods.MnemonicDB.Abstractions.ElementComparers; - -namespace NexusMods.MnemonicDB.TestModel.Attributes; - -public class Int3Attribute(string ns, string name) : TupleAttribute(ValueTags.Int32, ValueTags.Int32, ValueTags.Ascii, ns, name) -{ - protected override (int, int, string) FromLowLevel((int, int, string) value) - { - return value; - } - - protected override (int, int, string) ToLowLevel((int, int, string) value) - { - return value; - } -} diff --git a/tests/NexusMods.MnemonicDB.TestModel/Attributes/Tuple3TestAttribute.cs b/tests/NexusMods.MnemonicDB.TestModel/Attributes/Tuple3TestAttribute.cs new file mode 100644 index 0000000..91f4d4c --- /dev/null +++ b/tests/NexusMods.MnemonicDB.TestModel/Attributes/Tuple3TestAttribute.cs @@ -0,0 +1,18 @@ +using NexusMods.MnemonicDB.Abstractions; +using NexusMods.MnemonicDB.Abstractions.Attributes; +using NexusMods.MnemonicDB.Abstractions.ElementComparers; + +namespace NexusMods.MnemonicDB.TestModel.Attributes; + +public class Tuple3TestAttribute(string ns, string name) : TupleAttribute(ValueTags.Reference, ValueTags.Int32, ValueTags.Ascii, ns, name) +{ + protected override (EntityId, int, string) FromLowLevel((ulong, int, string) value) + { + return (EntityId.From(value.Item1), value.Item2, value.Item3); + } + + protected override (ulong, int, string) ToLowLevel((EntityId, int, string) value) + { + return (value.Item1.Value, value.Item2, value.Item3); + } +} diff --git a/tests/NexusMods.MnemonicDB.TestModel/File.cs b/tests/NexusMods.MnemonicDB.TestModel/File.cs index 165d1f9..edb7ec8 100644 --- a/tests/NexusMods.MnemonicDB.TestModel/File.cs +++ b/tests/NexusMods.MnemonicDB.TestModel/File.cs @@ -20,5 +20,5 @@ public partial class File : IModelDefinition /// /// Tuple3 test /// - public static readonly Int3Attribute TupleTest = new(Namespace, nameof(TupleTest)) { IsIndexed = true, IsOptional = true}; + public static readonly Tuple3TestAttribute TupleTest = new(Namespace, nameof(TupleTest)) { IsIndexed = true, IsOptional = true}; } diff --git a/tests/NexusMods.MnemonicDB.Tests/DbTests.CanUseTuple3Attributes.verified.txt b/tests/NexusMods.MnemonicDB.Tests/DbTests.CanUseTuple3Attributes.verified.txt index 94dd6f1..c404f22 100644 --- a/tests/NexusMods.MnemonicDB.Tests/DbTests.CanUseTuple3Attributes.verified.txt +++ b/tests/NexusMods.MnemonicDB.Tests/DbTests.CanUseTuple3Attributes.verified.txt @@ -1,27 +1,27 @@ -+ | 0200000000000001 | TupleTest | (1, 1, 1) | 0100000000000003 -+ | 0200000000000002 | TupleTest | (1, 1, 2) | 0100000000000003 -+ | 0200000000000003 | TupleTest | (1, 1, 3) | 0100000000000003 -+ | 0200000000000004 | TupleTest | (1, 2, 1) | 0100000000000003 -+ | 0200000000000005 | TupleTest | (1, 2, 2) | 0100000000000003 -+ | 0200000000000006 | TupleTest | (1, 2, 3) | 0100000000000003 -+ | 0200000000000007 | TupleTest | (1, 3, 1) | 0100000000000003 -+ | 0200000000000008 | TupleTest | (1, 3, 2) | 0100000000000003 -+ | 0200000000000009 | TupleTest | (1, 3, 3) | 0100000000000003 -+ | 020000000000000A | TupleTest | (2, 1, 1) | 0100000000000003 -+ | 020000000000000B | TupleTest | (2, 1, 2) | 0100000000000003 -+ | 020000000000000C | TupleTest | (2, 1, 3) | 0100000000000003 -+ | 020000000000000D | TupleTest | (2, 2, 1) | 0100000000000003 -+ | 020000000000000E | TupleTest | (2, 2, 2) | 0100000000000003 -+ | 020000000000000F | TupleTest | (2, 2, 3) | 0100000000000003 -+ | 0200000000000010 | TupleTest | (2, 3, 1) | 0100000000000003 -+ | 0200000000000011 | TupleTest | (2, 3, 2) | 0100000000000003 -+ | 0200000000000012 | TupleTest | (2, 3, 3) | 0100000000000003 -+ | 0200000000000013 | TupleTest | (3, 1, 1) | 0100000000000003 -+ | 0200000000000014 | TupleTest | (3, 1, 2) | 0100000000000003 -+ | 0200000000000015 | TupleTest | (3, 1, 3) | 0100000000000003 -+ | 0200000000000016 | TupleTest | (3, 2, 1) | 0100000000000003 -+ | 0200000000000017 | TupleTest | (3, 2, 2) | 0100000000000003 -+ | 0200000000000018 | TupleTest | (3, 2, 3) | 0100000000000003 -+ | 0200000000000019 | TupleTest | (3, 3, 1) | 0100000000000003 -+ | 020000000000001A | TupleTest | (3, 3, 2) | 0100000000000003 -+ | 020000000000001B | TupleTest | (3, 3, 3) | 0100000000000003 ++ | 0200000000000001 | TupleTest | (EId:200000000000001, 1, 1) | 0100000000000003 ++ | 0200000000000002 | TupleTest | (EId:200000000000002, 1, 2) | 0100000000000003 ++ | 0200000000000003 | TupleTest | (EId:200000000000003, 1, 3) | 0100000000000003 ++ | 0200000000000004 | TupleTest | (EId:200000000000004, 2, 1) | 0100000000000003 ++ | 0200000000000005 | TupleTest | (EId:200000000000005, 2, 2) | 0100000000000003 ++ | 0200000000000006 | TupleTest | (EId:200000000000006, 2, 3) | 0100000000000003 ++ | 0200000000000007 | TupleTest | (EId:200000000000007, 3, 1) | 0100000000000003 ++ | 0200000000000008 | TupleTest | (EId:200000000000008, 3, 2) | 0100000000000003 ++ | 0200000000000009 | TupleTest | (EId:200000000000009, 3, 3) | 0100000000000003 ++ | 020000000000000A | TupleTest | (EId:20000000000000A, 1, 1) | 0100000000000003 ++ | 020000000000000B | TupleTest | (EId:20000000000000B, 1, 2) | 0100000000000003 ++ | 020000000000000C | TupleTest | (EId:20000000000000C, 1, 3) | 0100000000000003 ++ | 020000000000000D | TupleTest | (EId:20000000000000D, 2, 1) | 0100000000000003 ++ | 020000000000000E | TupleTest | (EId:20000000000000E, 2, 2) | 0100000000000003 ++ | 020000000000000F | TupleTest | (EId:20000000000000F, 2, 3) | 0100000000000003 ++ | 0200000000000010 | TupleTest | (EId:200000000000010, 3, 1) | 0100000000000003 ++ | 0200000000000011 | TupleTest | (EId:200000000000011, 3, 2) | 0100000000000003 ++ | 0200000000000012 | TupleTest | (EId:200000000000012, 3, 3) | 0100000000000003 ++ | 0200000000000013 | TupleTest | (EId:200000000000013, 1, 1) | 0100000000000003 ++ | 0200000000000014 | TupleTest | (EId:200000000000014, 1, 2) | 0100000000000003 ++ | 0200000000000015 | TupleTest | (EId:200000000000015, 1, 3) | 0100000000000003 ++ | 0200000000000016 | TupleTest | (EId:200000000000016, 2, 1) | 0100000000000003 ++ | 0200000000000017 | TupleTest | (EId:200000000000017, 2, 2) | 0100000000000003 ++ | 0200000000000018 | TupleTest | (EId:200000000000018, 2, 3) | 0100000000000003 ++ | 0200000000000019 | TupleTest | (EId:200000000000019, 3, 1) | 0100000000000003 ++ | 020000000000001A | TupleTest | (EId:20000000000001A, 3, 2) | 0100000000000003 ++ | 020000000000001B | TupleTest | (EId:20000000000001B, 3, 3) | 0100000000000003 diff --git a/tests/NexusMods.MnemonicDB.Tests/DbTests.cs b/tests/NexusMods.MnemonicDB.Tests/DbTests.cs index b7d4047..7d7cea5 100644 --- a/tests/NexusMods.MnemonicDB.Tests/DbTests.cs +++ b/tests/NexusMods.MnemonicDB.Tests/DbTests.cs @@ -15,6 +15,7 @@ using NexusMods.MnemonicDB.Abstractions.TxFunctions; using NexusMods.MnemonicDB.TestModel; using NexusMods.MnemonicDB.TestModel.Analyzers; +using NexusMods.MnemonicDB.TestModel.Attributes; using NexusMods.Paths; using File = NexusMods.MnemonicDB.TestModel.File; @@ -1002,12 +1003,17 @@ public async Task CanUseTuple3Attributes() foreach (var c in new[]{1, 2, 3}) { var tmpId = tx.TempId(); - tx.Add(tmpId, File.TupleTest, (a, b, c.ToString())); + tx.Add(tmpId, File.TupleTest, (tmpId, b, c.ToString())); } var results = await tx.Commit(); + + var resolved = results.Db.Datoms(File.TupleTest).Resolved(Connection).ToArray(); + + resolved.Select(v => ((Tuple3TestAttribute.ReadDatom)v).V.Item1) + .Should().AllSatisfy(id => id.Partition.Should().NotBe(PartitionId.Temp)); - await VerifyTable(results.Db.Datoms(File.TupleTest).Resolved(Connection)); + await VerifyTable(resolved); } [Fact]