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
This commit is contained in:
Kamron Batman 2022-10-11 22:17:22 -07:00 • committed by GitHub
parent f268d5d4e2
commit e1e30998ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 616 additions and 291 deletions

View file

@ -15,6 +15,10 @@ namespace Server.Accounting
[SerializationGenerator(4)]
public partial class Account : IAccount, IComparable<Account>, ISerializable
{
public void SetTypeRef(Type type)
{
}
public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0);
public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0);
public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0);
@ -145,7 +149,6 @@ namespace Server.Accounting
public Account(XmlElement node)
{
Serial = Accounts.NewAccount;
SetTypeRef(GetType());
_username = Utility.GetText(node["username"], "empty");
@ -218,17 +221,6 @@ namespace Server.Accounting
this.MarkDirty();
}
public void SetTypeRef(Type type)
{
TypeRef = Accounts.Types.IndexOf(type);
if (TypeRef == -1)
{
Accounts.Types.Add(type);
TypeRef = Accounts.Types.Count - 1;
}
}
/// <summary>
/// Object detailing information about the hardware of the last person to log into this account
/// </summary>
@ -305,8 +297,6 @@ namespace Server.Accounting
[CommandProperty(AccessLevel.GameMaster)]
DateTime ISerializable.LastSerialized { get; set; } = Core.Now;
public int TypeRef { get; private set; }
public Serial Serial { get; set; }
public void BeforeSerialize()

View file

@ -14,7 +14,6 @@ namespace Server.Accounting
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
private static Dictionary<Serial, Account> _accountsById = new(32);
private static Serial _lastAccount;
internal static List<Type> Types { get; } = new();
private static void OutOfMemory(string message) => throw new OutOfMemoryException(message);
@ -44,13 +43,18 @@ namespace Server.Accounting
public static void Configure() =>
Persistence.Register("Accounts", Serialize, WriteSnapshot, Deserialize);
internal static void Serialize() =>
EntityPersistence.SaveEntities(_accountsById.Values, account => ((ISerializable)account).Serialize());
internal static void Serialize()
{
EntityPersistence.SaveEntities(
_accountsById.Values,
account => ((ISerializable)account).Serialize(World.SerializedTypes)
);
}
internal static void WriteSnapshot(string basePath)
{
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
EntityPersistence.WriteEntities(indexInfo, _accountsById, Types, basePath, out _);
EntityPersistence.WriteEntities(indexInfo, _accountsById, basePath,World.SerializedTypes, out _);
}
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
@ -73,7 +77,7 @@ namespace Server.Accounting
_accountsById.Remove(a.Serial);
}
internal static void Deserialize(string path)
internal static void Deserialize(string path, Dictionary<ulong, string> typesDb)
{
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
@ -86,14 +90,14 @@ namespace Server.Accounting
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List<EntitySpan<Account>> accounts);
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, typesDb, out List<EntitySpan<Account>> accounts);
if (_accountsById.Count > 0)
{
_lastAccount = _accountsById.Keys.Max();
}
EntityPersistence.LoadData(path, indexInfo, accounts);
EntityPersistence.LoadData(path, indexInfo, typesDb, accounts);
foreach (var a in _accountsById.Values)
{

View file

@ -3,7 +3,6 @@ using ModernUO.Serialization;
namespace Server.Items
{
[SerializationGenerator(0)]
[TypeAlias("Server.Items.GargishClothKilt", "Server.Items.GargishClothKiltArmor")]
public partial class GargishClothKiltType2 : BaseArmor
{
[Constructible]

View file

@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace Server.Mobiles
{
public class SkeletalDragon : BaseCreature

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using Server.Collections;
using Server.Gumps;
using Server.Items;