Skip to content

Commit e1e3099

Browse files
authored
fix: Adds ReadType/Write(Type) and improves type referencing (#1172)
## Changes * Improves type hashing by introducing xxHash3 (64bit) * Removes individual `tdb` files in favor of a single `SerializedTypes.db` file. This file is only used to identify a type that is being deserialized, which doesn't exist. * Adds duplicate type alias detection * Adds `AssemblyHandler.FindTypeByHash` View changed files whitespaces: https://github.com/modernuo/ModernUO/pull/1172/files?diff=split&w=1 ## SerializedTypes.db The serialized types file is used to get back the original name of a type in case it no longer exists in code. This can easily be necessary if a class is renamed in code and no `TypeAlias` is provided. ### Format byte[4] - version byte[4] - count --array-- byte[8] - xxHash byte[1] - flag, 0 - null, 1 - not null byte[n] - Full class name in UTF8 ### Example <img width="472" alt="SerializedTypes_Example" src="https://user-images.githubusercontent.com/3953314/195255429-31d24293-6bd1-419e-811b-07874dd0f78d.png"> ## Benchmarks Serialized 500 Type fields. The 8192bytes comes from the _ConcurrentQueue_ that would later be used for SerializedTypes. Note that the queue is never cleared, so it's size grew considerably. ```cs | Method | Mean | Error | StdDev | Allocated | |--------------------- |---------:|---------:|---------:|----------:| | BenchmarkXXHash | 18.44 us | 0.278 us | 0.260 us | 8192 B | | BenchmarkTypeStrings | 25.09 us | 0.292 us | 0.259 us | - | ``` TODO: * Add support in the Serialization Generator for `ReadType()` and `Write(Type)` * Remove `SetTypeRef` from Serialization Generator
1 parent f268d5d commit e1e3099

28 files changed

+613
-288
lines changed

Projects/Server/AssemblyHandler.cs

Lines changed: 95 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,22 @@
1414
*************************************************************************/
1515

1616
using System;
17+
using System.Collections.Concurrent;
1718
using System.Collections.Generic;
1819
using System.IO;
1920
using System.Linq;
2021
using System.Reflection;
2122
using System.Runtime.CompilerServices;
2223
using System.Runtime.Loader;
24+
using Server.Logging;
2325

2426
namespace Server;
2527

2628
public static class AssemblyHandler
2729
{
2830
private static readonly Dictionary<Assembly, TypeCache> m_TypeCaches = new();
2931
private static TypeCache m_NullCache;
32+
3033
public static Assembly[] Assemblies { get; set; }
3134

3235
internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args)
@@ -66,6 +69,8 @@ public static Assembly LoadAssemblyByAssemblyName(AssemblyName assemblyName)
6669
EnsureAssemblyDirectories();
6770
var assemblyDirectories = ServerConfiguration.AssemblyDirectories;
6871

72+
Assembly assembly = null;
73+
6974
foreach (var assemblyDir in assemblyDirectories)
7075
{
7176
var assemblyPath = PathUtility.GetFullPath(Path.Combine(assemblyDir, fileName), Core.BaseDirectory);
@@ -74,12 +79,17 @@ public static Assembly LoadAssemblyByAssemblyName(AssemblyName assemblyName)
7479
var assemblyNameCheck = AssemblyName.GetAssemblyName(assemblyPath);
7580
if (assemblyNameCheck.FullName == fullName)
7681
{
77-
return AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
82+
assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath);
83+
break;
7884
}
7985
}
8086
}
8187

82-
return null;
88+
// This forces the type caching to be generated.
89+
// We need this for world loading to find types by hash.
90+
GetTypeCache(assembly);
91+
92+
return assembly;
8393
}
8494

8595
public static Assembly LoadAssemblyByFileName(string assemblyFile)
@@ -154,6 +164,11 @@ private static void AddMethods(this Assembly assembly, string method, List<Metho
154164
}
155165
}
156166

167+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
168+
public static ulong GetTypeHash(Type type) => GetTypeHash(type.FullName);
169+
170+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
171+
public static ulong GetTypeHash(string key) => key == null ? 0 : HashUtility.ComputeHash64(key);
157172

158173
public static TypeCache GetTypeCache(Assembly asm)
159174
{
@@ -195,149 +210,171 @@ public static Type FindTypeByName(string name, bool fullName = false, bool ignor
195210

196211
return null;
197212
}
213+
214+
public static Type FindTypeByHash(ulong hash)
215+
{
216+
for (var i = 0; i < Assemblies.Length; i++)
217+
{
218+
foreach (var type in GetTypeCache(Assemblies[i]).GetTypesByHash(hash, true, false))
219+
{
220+
return type;
221+
}
222+
}
223+
224+
foreach(var type in GetTypeCache(Core.Assembly).GetTypesByHash(hash, true, false))
225+
{
226+
return type;
227+
}
228+
229+
return null;
230+
}
198231
}
199232

200233
public class TypeCache
201234
{
202-
private readonly Dictionary<string, int[]> _nameMap = new();
203-
private readonly Dictionary<string, int[]> _nameMapInsensitive = new();
204-
private readonly Dictionary<string, int[]> _fullNameMap = new();
205-
private readonly Dictionary<string, int[]> _fullNameMapInsensitive = new();
235+
private static ILogger logger = LogFactory.GetLogger(typeof(TypeCache));
236+
237+
private Dictionary<ulong, Type[]> _nameMap = new();
238+
private Dictionary<ulong, Type[]> _nameMapInsensitive = new();
239+
private Dictionary<ulong, Type[]> _fullNameMap = new();
240+
private Dictionary<ulong, Type[]> _fullNameMapInsensitive = new();
206241

207242
public TypeCache(Assembly asm)
208243
{
209244
Types = asm?.GetTypes() ?? Type.EmptyTypes;
210245

211-
var nameMap = new Dictionary<string, HashSet<int>>();
212-
var nameMapInsensitive = new Dictionary<string, HashSet<int>>();
213-
var fullNameMap = new Dictionary<string, HashSet<int>>();
214-
var fullNameMapInsensitive = new Dictionary<string, HashSet<int>>();
246+
var nameMap = new Dictionary<string, HashSet<Type>>();
247+
var nameMapInsensitive = new Dictionary<string, HashSet<Type>>();
248+
var fullNameMap = new Dictionary<string, HashSet<Type>>();
249+
var fullNameMapInsensitive = new Dictionary<string, HashSet<Type>>();
215250

216251
[MethodImpl(MethodImplOptions.AggressiveInlining)]
217-
void addTypeToRefs(int index, string fullTypeName)
252+
void addTypeToRefs(Type type, string typeName, string fullTypeName)
218253
{
219-
var typeName = fullTypeName[(fullTypeName.LastIndexOf('.') + 1)..];
220-
AddToRefs(index, typeName, nameMap);
221-
AddToRefs(index, typeName.ToLower(), nameMapInsensitive);
222-
AddToRefs(index, fullTypeName, fullNameMap);
223-
AddToRefs(index, fullTypeName.ToLower(), fullNameMapInsensitive);
254+
AddToRefs(type, typeName, nameMap);
255+
AddToRefs(type, typeName.ToLower(), nameMapInsensitive);
256+
AddToRefs(type, fullTypeName, fullNameMap);
257+
AddToRefs(type, fullTypeName.ToLower(), fullNameMapInsensitive);
224258
}
225259

226260
var aliasType = typeof(TypeAliasAttribute);
227261
for (var i = 0; i < Types.Length; i++)
228262
{
229263
var current = Types[i];
230-
addTypeToRefs(i, current.FullName);
264+
addTypeToRefs(current, current.Name, current.FullName ?? "");
231265
if (current.GetCustomAttribute(aliasType, false) is TypeAliasAttribute alias)
232266
{
233267
for (var j = 0; j < alias.Aliases.Length; j++)
234268
{
235-
addTypeToRefs(i, alias.Aliases[j]);
269+
var fullTypeName = alias.Aliases[j];
270+
var typeName = fullTypeName[(fullTypeName.LastIndexOf('.')+1)..];
271+
addTypeToRefs(current, typeName, fullTypeName);
236272
}
237273
}
238274
}
239275

240276
foreach (var (key, value) in nameMap)
241277
{
242-
_nameMap[key] = value.ToArray();
278+
_nameMap[HashUtility.ComputeHash64(key)] = value.ToArray();
243279
}
244280

245281
foreach (var (key, value) in nameMapInsensitive)
246282
{
247-
_nameMapInsensitive[key] = value.ToArray();
283+
_nameMapInsensitive[HashUtility.ComputeHash64(key)] = value.ToArray();
248284
}
249285

250286
foreach (var (key, value) in fullNameMap)
251287
{
252-
_fullNameMap[key] = value.ToArray();
288+
var values = value.ToArray();
289+
_fullNameMap[HashUtility.ComputeHash64(key)] = value.ToArray();
290+
#if DEBUG
291+
if (values.Length > 1)
292+
{
293+
for (var i = 0; i < values.Length; i++)
294+
{
295+
var type = values[i];
296+
logger.Warning(
297+
"Duplicate type {Type1} for {Name}.",
298+
type,
299+
key
300+
);
301+
}
302+
}
303+
#endif
253304
}
254305

255306
foreach (var (key, value) in fullNameMapInsensitive)
256307
{
257-
_fullNameMapInsensitive[key] = value.ToArray();
308+
_fullNameMapInsensitive[HashUtility.ComputeHash64(key)] = value.ToArray();
258309
}
259310
}
260311

261312
[MethodImpl(MethodImplOptions.AggressiveInlining)]
262-
private static void AddToRefs(int index, string key, Dictionary<string, HashSet<int>> map)
313+
private static void AddToRefs(Type type, string key, Dictionary<string, HashSet<Type>> map)
263314
{
264-
if (key == null)
315+
if (string.IsNullOrEmpty(key))
265316
{
266317
return;
267318
}
268319

269320
if (map.TryGetValue(key, out var refs))
270321
{
271-
refs.Add(index);
322+
refs.Add(type);
272323
}
273324
else
274325
{
275-
refs = new HashSet<int> { index };
326+
refs = new HashSet<Type> { type };
276327
map.Add(key, refs);
277328
}
278329
}
279330

280331
public Type[] Types { get; }
281332

282333
[MethodImpl(MethodImplOptions.AggressiveInlining)]
283-
public TypeEnumerable GetTypesByName(string name, bool full, bool ignoreCase) => new(name, this, full, ignoreCase);
284-
285-
public ref struct TypeEnumerable
286-
{
287-
private readonly TypeCache _cache;
288-
private readonly string _name;
289-
private readonly bool _ignoreCase;
290-
private readonly bool _full;
291-
292-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
293-
public TypeEnumerable(string name, TypeCache cache, bool full, bool ignoreCase)
294-
{
295-
_name = name;
296-
_cache = cache;
297-
_ignoreCase = ignoreCase;
298-
_full = full;
299-
}
334+
public TypeEnumerator GetTypesByName(string name, bool full, bool ignoreCase) => new(name, this, full, ignoreCase);
300335

301-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
302-
public TypeEnumerator GetEnumerator() => new(_name, _cache, _full, _ignoreCase);
303-
}
336+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
337+
public TypeEnumerator GetTypesByHash(ulong hash, bool full, bool ignoreCase) => new(hash, this, full, ignoreCase);
304338

305339
public ref struct TypeEnumerator
306340
{
307-
private readonly TypeCache _cache;
308-
private readonly int[] _values;
341+
private Type[] _values;
309342
private int _index;
310343
private Type _current;
311344

312345
[MethodImpl(MethodImplOptions.AggressiveInlining)]
313346
internal TypeEnumerator(string name, TypeCache cache, bool full, bool ignoreCase)
347+
: this(HashUtility.ComputeHash64(ignoreCase ? name.ToLower() : name), cache, full, ignoreCase)
314348
{
315-
_cache = cache;
349+
}
316350

351+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
352+
internal TypeEnumerator(ulong hash, TypeCache cache, bool full, bool ignoreCase)
353+
{
317354
if (ignoreCase)
318355
{
319-
var map = full ? _cache._fullNameMapInsensitive : _cache._nameMapInsensitive;
320-
_values = map.TryGetValue(name.ToLower(), out var values) ? values : Array.Empty<int>();
356+
var map = full ? cache._fullNameMapInsensitive : cache._nameMapInsensitive;
357+
_values = map.TryGetValue(hash, out var values) ? values : Array.Empty<Type>();
321358
}
322359
else
323360
{
324-
var map = full ? _cache._fullNameMap : _cache._nameMap;
325-
_values = map.TryGetValue(name, out var values) ? values : Array.Empty<int>();
361+
var map = full ? cache._fullNameMap : cache._nameMap;
362+
_values = map.TryGetValue(hash, out var values) ? values : Array.Empty<Type>();
326363
}
327364

328365
_index = 0;
329366
_current = default;
330367
}
331368

369+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
370+
public TypeEnumerator GetEnumerator() => this;
371+
332372
[MethodImpl(MethodImplOptions.AggressiveInlining)]
333373
public bool MoveNext()
334374
{
335-
int[] localList = _values;
336-
337-
if ((uint)_index < (uint)localList.Length)
375+
if ((uint)_index < (uint)_values.Length)
338376
{
339-
_current = _cache.Types[_values[_index++]];
340-
377+
_current = _values[_index++];
341378
return true;
342379
}
343380

Projects/Server/Guild.cs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,25 +31,15 @@ protected BaseGuild()
3131
{
3232
Serial = World.NewGuild;
3333
World.AddGuild(this);
34-
35-
SetTypeRef(GetType());
3634
}
3735

3836
protected BaseGuild(Serial serial)
3937
{
4038
Serial = serial;
41-
SetTypeRef(GetType());
4239
}
4340

4441
public void SetTypeRef(Type type)
4542
{
46-
TypeRef = World.GuildTypes.IndexOf(type);
47-
48-
if (TypeRef == -1)
49-
{
50-
World.GuildTypes.Add(type);
51-
TypeRef = World.GuildTypes.Count - 1;
52-
}
5343
}
5444

5545
public abstract string Abbreviation { get; set; }

Projects/Server/IEntity.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ public Entity(Serial serial, Point3D loc, Map map) : this(serial)
4343

4444
public Entity(Serial serial) => Serial = serial;
4545

46-
public void SetTypeRef(Type type)
47-
{
48-
}
49-
5046
DateTime ISerializable.Created { get; set; } = Core.Now;
5147

5248
DateTime ISerializable.LastSerialized { get; set; } = DateTime.MaxValue;

Projects/Server/Items/Container.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1809,8 +1809,7 @@ public ItemStackEntry(Item stack, Item drop)
18091809

18101810
public class ContainerData
18111811
{
1812-
private static ILogger _logger;
1813-
private static ILogger Logger => _logger ??= LogFactory.GetLogger(typeof(ContainerData));
1812+
private static ILogger logger = LogFactory.GetLogger(typeof(ContainerData));
18141813
private static readonly Dictionary<int, ContainerData> m_Table;
18151814

18161815
static ContainerData()
@@ -1875,7 +1874,7 @@ static ContainerData()
18751874

18761875
if (m_Table.ContainsKey(id))
18771876
{
1878-
Logger.Warning("double ItemID entry in Data\\containers.cfg");
1877+
logger.Warning("double ItemID entry in Data\\containers.cfg");
18791878
}
18801879
else
18811880
{

Projects/Server/Items/Item.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -217,24 +217,15 @@ public Item(int itemID = 0)
217217
SetLastMoved();
218218

219219
World.AddEntity(this);
220-
SetTypeRef(GetType());
221220
}
222221

223222
public Item(Serial serial)
224223
{
225224
Serial = serial;
226-
SetTypeRef(GetType());
227225
}
228226

229227
public void SetTypeRef(Type type)
230228
{
231-
TypeRef = World.ItemTypes.IndexOf(type);
232-
233-
if (TypeRef == -1)
234-
{
235-
World.ItemTypes.Add(type);
236-
TypeRef = World.ItemTypes.Count - 1;
237-
}
238229
}
239230

240231
public int TempFlags
@@ -786,8 +777,6 @@ public virtual void GetProperties(IPropertyList list)
786777
[CommandProperty(AccessLevel.Counselor)]
787778
public Serial Serial { get; }
788779

789-
public int TypeRef { get; private set; }
790-
791780
public virtual void Serialize(IGenericWriter writer)
792781
{
793782
writer.Write(9); // version

0 commit comments

Comments
 (0)