Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions RGB.NET.Core/Ids/IdGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ public static class IdGenerator

// ReSharper disable InconsistentNaming
private static readonly HashSet<string> _registeredIds = [];
private static readonly Dictionary<Assembly, Dictionary<string, string>> _idMappings = [];
private static readonly Dictionary<Assembly, Dictionary<string, int>> _counter = [];
// Keyed by the assembly's full name rather than the Assembly instance, so a reloaded copy of an assembly
// (e.g. a plugin in a new AssemblyLoadContext) keeps its ids and unloaded assemblies aren't kept alive.
private static readonly Dictionary<string, Dictionary<string, string>> _idMappings = [];
private static readonly Dictionary<string, Dictionary<string, int>> _counter = [];
// ReSharper restore InconsistentNaming

#endregion
Expand All @@ -31,13 +33,14 @@ public static class IdGenerator

internal static string MakeUnique(Assembly callingAssembly, string id)
{
if (!_idMappings.TryGetValue(callingAssembly, out Dictionary<string, string>? idMapping))
string assemblyKey = GetAssemblyKey(callingAssembly);
if (!_idMappings.TryGetValue(assemblyKey, out Dictionary<string, string>? idMapping))
{
_idMappings.Add(callingAssembly, idMapping = []);
_counter.Add(callingAssembly, []);
_idMappings.Add(assemblyKey, idMapping = []);
_counter.Add(assemblyKey, []);
}

Dictionary<string, int> counterMapping = _counter[callingAssembly];
Dictionary<string, int> counterMapping = _counter[assemblyKey];

if (!idMapping.TryGetValue(id, out string? mappedId))
{
Expand Down Expand Up @@ -65,9 +68,11 @@ internal static string MakeUnique(Assembly callingAssembly, string id)

internal static void ResetCounter(Assembly callingAssembly)
{
if (_counter.TryGetValue(callingAssembly, out Dictionary<string, int>? counter))
if (_counter.TryGetValue(GetAssemblyKey(callingAssembly), out Dictionary<string, int>? counter))
counter.Clear();
}

private static string GetAssemblyKey(Assembly assembly) => assembly.FullName ?? assembly.GetName().Name ?? string.Empty;

#endregion
}
66 changes: 66 additions & 0 deletions Tests/RGB.NET.Core.Tests/Ids/IdGeneratorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Loader;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace RGB.NET.Core.Tests.Ids;

[TestClass]
public class IdGeneratorTest
{
#region Helper

// Called through reflection from a reloaded copy of this assembly, so that copy is the calling assembly
[MethodImpl(MethodImplOptions.NoInlining)]
public static string MakeUniqueAndReset(string id)
{
string uniqueId = IdGenerator.MakeUnique(id);
IdGenerator.ResetCounter();
return uniqueId;
}

private static string MakeUniqueFromReloadedAssembly(string id)
{
AssemblyLoadContext context = new(nameof(IdGeneratorTest), true);
try
{
using MemoryStream stream = new(File.ReadAllBytes(typeof(IdGeneratorTest).Assembly.Location));
Assembly assembly = context.LoadFromStream(stream);
MethodInfo method = assembly.GetType(typeof(IdGeneratorTest).FullName!)!.GetMethod(nameof(MakeUniqueAndReset))!;
return (string)method.Invoke(null, [id])!;
}
finally
{
context.Unload();
}
}

#endregion

#region Tests

[TestMethod]
public void ReloadedAssemblyGetsTheSameIdAfterReset()
{
string id = $"Manufacturer Model {Guid.NewGuid()}";

string firstLoad = MakeUniqueFromReloadedAssembly(id);
string secondLoad = MakeUniqueFromReloadedAssembly(id);

Assert.AreEqual(id, firstLoad);
Assert.AreEqual(firstLoad, secondLoad);
}

[TestMethod]
public void DuplicateIdsWithinAnAssemblyAreStillUnique()
{
string id = $"Manufacturer Model {Guid.NewGuid()}";

Assert.AreEqual(id, IdGenerator.MakeUnique(id));
Assert.AreEqual($"{id} (2)", IdGenerator.MakeUnique(id));
}

#endregion
}