## 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
139 lines
4 KiB
C#
139 lines
4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Xml;
|
|
using Server.Logging;
|
|
|
|
namespace Server.Accounting
|
|
{
|
|
public static class Accounts
|
|
{
|
|
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
|
|
|
|
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
|
|
private static Dictionary<Serial, Account> _accountsById = new(32);
|
|
private static Serial _lastAccount;
|
|
|
|
private static void OutOfMemory(string message) => throw new OutOfMemoryException(message);
|
|
|
|
public static Serial NewAccount
|
|
{
|
|
get
|
|
{
|
|
var last = _lastAccount;
|
|
|
|
for (uint i = 0; i < uint.MaxValue; i++)
|
|
{
|
|
last++;
|
|
|
|
if (FindAccount(last) == null)
|
|
{
|
|
return _lastAccount = last;
|
|
}
|
|
}
|
|
|
|
OutOfMemory("No serials left to allocate for accounts");
|
|
return Serial.MinusOne;
|
|
}
|
|
}
|
|
|
|
public static int Count => _accountsByName.Count;
|
|
|
|
public static void Configure() =>
|
|
Persistence.Register("Accounts", Serialize, WriteSnapshot, Deserialize);
|
|
|
|
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, basePath,World.SerializedTypes, out _);
|
|
}
|
|
|
|
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
|
|
|
|
public static Account GetAccount(string username)
|
|
{
|
|
_accountsByName.TryGetValue(username, out var a);
|
|
return a;
|
|
}
|
|
|
|
public static void Add(Account a)
|
|
{
|
|
_accountsByName[a.Username] = a;
|
|
_accountsById[a.Serial] = a;
|
|
}
|
|
|
|
public static void Remove(Account a)
|
|
{
|
|
_accountsByName.Remove(a.Username);
|
|
_accountsById.Remove(a.Serial);
|
|
}
|
|
|
|
internal static void Deserialize(string path, Dictionary<ulong, string> typesDb)
|
|
{
|
|
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
|
|
|
|
// Backward Compatibility
|
|
if (File.Exists(filePath))
|
|
{
|
|
DeserializeXml(filePath);
|
|
return;
|
|
}
|
|
|
|
IIndexInfo<Serial> indexInfo = new EntityTypeIndex("Accounts");
|
|
|
|
_accountsById = EntityPersistence.LoadIndex(path, indexInfo, typesDb, out List<EntitySpan<Account>> accounts);
|
|
|
|
if (_accountsById.Count > 0)
|
|
{
|
|
_lastAccount = _accountsById.Keys.Max();
|
|
}
|
|
|
|
EntityPersistence.LoadData(path, indexInfo, typesDb, accounts);
|
|
|
|
foreach (var a in _accountsById.Values)
|
|
{
|
|
_accountsByName[a.Username] = a;
|
|
}
|
|
}
|
|
|
|
private static void DeserializeXml(string filePath)
|
|
{
|
|
var doc = new XmlDocument();
|
|
doc.Load(filePath);
|
|
|
|
var root = doc["accounts"];
|
|
|
|
if (root == null)
|
|
{
|
|
throw new FileLoadException("Unable to load xml file");
|
|
}
|
|
|
|
foreach (XmlElement account in root.GetElementsByTagName("account"))
|
|
{
|
|
try
|
|
{
|
|
new Account(account);
|
|
}
|
|
catch
|
|
{
|
|
logger.Warning("Account instance load failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
public static IAccount FindAccount(Serial serial)
|
|
{
|
|
_accountsById.TryGetValue(serial, out var account);
|
|
return account;
|
|
}
|
|
}
|
|
}
|