ModernUO/Projects/UOContent/Accounting/Accounts.cs
Kamron Batman e1e30998ba
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
2022-10-11 22:17:22 -07:00

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;
}
}
}