From 8cef433c81095dec3dad80513f0ea3ee6f06c5ea Mon Sep 17 00:00:00 2001 From: sven-n Date: Mon, 5 Aug 2024 19:05:34 +0200 Subject: [PATCH 1/4] Implemented UUID V7 for primary key values --- src/GameLogic/MiniGames/MiniGameContext.cs | 2 +- .../EntityFramework/EntityDataContext.cs | 1 + .../EntityFramework/GuidV7ValueGenerator.cs | 23 +++++++++++ src/Persistence/GuidV7.cs | 39 +++++++++++++++++++ src/Persistence/InMemory/InMemoryContext.cs | 12 ++++-- 5 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/Persistence/EntityFramework/GuidV7ValueGenerator.cs create mode 100644 src/Persistence/GuidV7.cs diff --git a/src/GameLogic/MiniGames/MiniGameContext.cs b/src/GameLogic/MiniGames/MiniGameContext.cs index edec59d13..3c4acb0fe 100644 --- a/src/GameLogic/MiniGames/MiniGameContext.cs +++ b/src/GameLogic/MiniGames/MiniGameContext.cs @@ -494,7 +494,7 @@ protected async ValueTask SaveRankingAsync(IEnumerable<(int Rank, Character Char try { using var context = this._gameContext.PersistenceContextProvider.CreateNewTypedContext(false); - var instanceId = Guid.NewGuid(); + var instanceId = GuidV7.NewGuid(); var timestamp = DateTime.UtcNow; foreach (var score in scoreEntries) { diff --git a/src/Persistence/EntityFramework/EntityDataContext.cs b/src/Persistence/EntityFramework/EntityDataContext.cs index bb30c9324..70cb0ca53 100644 --- a/src/Persistence/EntityFramework/EntityDataContext.cs +++ b/src/Persistence/EntityFramework/EntityDataContext.cs @@ -79,6 +79,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) if (key != null) { key.ValueGenerated = Microsoft.EntityFrameworkCore.Metadata.ValueGenerated.OnAdd; + key.SetValueGeneratorFactory((_, _) => new GuidV7ValueGenerator()); } } diff --git a/src/Persistence/EntityFramework/GuidV7ValueGenerator.cs b/src/Persistence/EntityFramework/GuidV7ValueGenerator.cs new file mode 100644 index 000000000..4c0c0d90f --- /dev/null +++ b/src/Persistence/EntityFramework/GuidV7ValueGenerator.cs @@ -0,0 +1,23 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.EntityFramework; + +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.ValueGeneration; + +/// +/// A value generator for UUID V7 used as primary key. +/// +public class GuidV7ValueGenerator : ValueGenerator +{ + /// + public override bool GeneratesTemporaryValues => false; + + /// + public override Guid Next(EntityEntry entry) + { + return GuidV7.NewGuid(); + } +} \ No newline at end of file diff --git a/src/Persistence/GuidV7.cs b/src/Persistence/GuidV7.cs new file mode 100644 index 000000000..93f4f257f --- /dev/null +++ b/src/Persistence/GuidV7.cs @@ -0,0 +1,39 @@ +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence; + +using System.Security.Cryptography; + +/// +/// A generator for version 7 GUIDs. Layout: +/// The first 6 bytes is a unix timestamp in milliseconds. +/// The 7th byte contains the version number (7) in the upper 4 bits. +/// The rest is random. +/// +public static class GuidV7 +{ + public static Guid NewGuid() => NewGuid(DateTimeOffset.UtcNow); + + public static Guid NewGuid(DateTimeOffset dateTimeOffset) + { + // We create a buffer which is two bytes bigger than the Guid, + // because we don't need the first two bytes of the timestamp. + Span buffer = stackalloc byte[18]; + var uuidAsBytes = buffer[2..]; + var currentTimestamp = dateTimeOffset.ToUnixTimeMilliseconds(); + Span timestampBytes = stackalloc byte[sizeof(long)]; + if (!BitConverter.TryWriteBytes(buffer, currentTimestamp)) + { + throw new InvalidOperationException("Could not convert the timestamp to bytes."); + } + + RandomNumberGenerator.Fill(uuidAsBytes[6..]); + + uuidAsBytes[6] &= 0x0F; + uuidAsBytes[6] |= 0x70; + + return new Guid(uuidAsBytes, true); + } +} \ No newline at end of file diff --git a/src/Persistence/InMemory/InMemoryContext.cs b/src/Persistence/InMemory/InMemoryContext.cs index 4665375a1..b808fcc8a 100644 --- a/src/Persistence/InMemory/InMemoryContext.cs +++ b/src/Persistence/InMemory/InMemoryContext.cs @@ -5,6 +5,7 @@ namespace MUnique.OpenMU.Persistence.InMemory; using System.Collections; +using System.Threading; using Nito.AsyncEx.Synchronous; /// @@ -43,7 +44,10 @@ public void Dispose() this.SavedChanges = null; } - /// + /// + /// Saves the changes of the context. + /// + /// True, if the saving was successful; false, otherwise. public bool SaveChanges() { foreach (var repository in this.Provider.MemoryRepositories) @@ -55,7 +59,7 @@ public bool SaveChanges() } /// - public async ValueTask SaveChangesAsync() + public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = default) { var result = this.SaveChanges(); if (result) @@ -97,7 +101,7 @@ public T CreateNew(params object?[] args) { if (identifiable.Id == Guid.Empty) { - identifiable.Id = Guid.NewGuid(); + identifiable.Id = GuidV7.NewGuid(); } var repository = this.Provider.GetRepository() as IMemoryRepository; @@ -115,7 +119,7 @@ public object CreateNew(Type type, params object?[] args) { if (identifiable.Id == Guid.Empty) { - identifiable.Id = Guid.NewGuid(); + identifiable.Id = GuidV7.NewGuid(); } var repository = this.Provider.GetRepository(type) as IMemoryRepository; From 14070fef3e5bdd0907b5e5cf94464ee5938ff61a Mon Sep 17 00:00:00 2001 From: sven-n Date: Mon, 5 Aug 2024 20:18:24 +0200 Subject: [PATCH 2/4] fixed build --- src/Persistence/InMemory/InMemoryContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Persistence/InMemory/InMemoryContext.cs b/src/Persistence/InMemory/InMemoryContext.cs index b808fcc8a..6ace5156e 100644 --- a/src/Persistence/InMemory/InMemoryContext.cs +++ b/src/Persistence/InMemory/InMemoryContext.cs @@ -59,7 +59,7 @@ public bool SaveChanges() } /// - public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = default) + public async ValueTask SaveChangesAsync() { var result = this.SaveChanges(); if (result) From 5155688db2e9f6571c9f227ca1c6589c5a894911 Mon Sep 17 00:00:00 2001 From: sven-n Date: Mon, 5 Aug 2024 20:35:25 +0200 Subject: [PATCH 3/4] Fixed code style, added comments --- src/Persistence/GuidV7.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Persistence/GuidV7.cs b/src/Persistence/GuidV7.cs index 93f4f257f..aad89783c 100644 --- a/src/Persistence/GuidV7.cs +++ b/src/Persistence/GuidV7.cs @@ -1,4 +1,4 @@ -// +// // Licensed under the MIT License. See LICENSE file in the project root for full license information. // @@ -14,8 +14,16 @@ namespace MUnique.OpenMU.Persistence; /// public static class GuidV7 { + /// + /// Creates a new random guid. + /// + /// The new guid. public static Guid NewGuid() => NewGuid(DateTimeOffset.UtcNow); + /// + /// Creates a new random guid for the specified date. + /// + /// The new guid. public static Guid NewGuid(DateTimeOffset dateTimeOffset) { // We create a buffer which is two bytes bigger than the Guid, @@ -23,7 +31,7 @@ public static Guid NewGuid(DateTimeOffset dateTimeOffset) Span buffer = stackalloc byte[18]; var uuidAsBytes = buffer[2..]; var currentTimestamp = dateTimeOffset.ToUnixTimeMilliseconds(); - Span timestampBytes = stackalloc byte[sizeof(long)]; + if (!BitConverter.TryWriteBytes(buffer, currentTimestamp)) { throw new InvalidOperationException("Could not convert the timestamp to bytes."); From 234dfde4c387583b4729f70d1013b764bc0459dd Mon Sep 17 00:00:00 2001 From: sven-n Date: Tue, 6 Aug 2024 07:12:35 +0200 Subject: [PATCH 4/4] added parameter comment --- src/Persistence/GuidV7.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Persistence/GuidV7.cs b/src/Persistence/GuidV7.cs index aad89783c..b1ca3aafa 100644 --- a/src/Persistence/GuidV7.cs +++ b/src/Persistence/GuidV7.cs @@ -23,6 +23,7 @@ public static class GuidV7 /// /// Creates a new random guid for the specified date. /// + /// The date time offset which is the prefix of the id. /// The new guid. public static Guid NewGuid(DateTimeOffset dateTimeOffset) {