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]