fix: Cleans up entity persistence. Generalizes Mobiles/Items/Guilds (#1528)

### Summary
- [X] Fixed a bug where entity persistence was serialized out of order, causing world corruption.
- [X] Fixed LastSerialized not being utilized properly and dangling references still becoming an issue.
- [X] Added a new `GenericEntityPersistence<T>` type to encapsulate `ISerializable` serialization.
- [X] Removing the custom logic and moved Items, Mobiles, Guilds, and Accounts  to GenericEntityPersistence.
- [X] Changed serialization to use the singleton pattern to reduce calling methods from stored variables.
This commit is contained in:
Kamron Batman 2023-10-01 22:10:47 -07:00 • committed by GitHub
parent e0225c6e59
commit f2de2fbb77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 721 additions and 970 deletions

View file

@ -1,139 +1,95 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using Server.Logging;
namespace Server.Accounting
namespace Server.Accounting;
public class Accounts : GenericEntityPersistence<Account>
{
public static class Accounts
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
public static int Count => _accountsByName.Count;
private static Accounts _accountsPersistence;
public static Serial NewAccount => _accountsPersistence.NewEntity;
public static void Configure()
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts));
_accountsPersistence = new Accounts();
}
private static readonly Dictionary<string, Account> _accountsByName = new(32, StringComparer.OrdinalIgnoreCase);
private static Dictionary<Serial, Account> _accountsById = new(32);
private static Serial _lastAccount;
public Accounts() : base("Accounts", 3, 0x1, 0x7FFFFFFF)
{
}
private static void OutOfMemory(string message) => throw new OutOfMemoryException(message);
public static IEnumerable<IAccount> GetAccounts() => _accountsByName.Values;
public static Serial NewAccount
public static Account GetAccount(string username)
{
_accountsByName.TryGetValue(username, out var a);
return a;
}
public static void Add(Account a)
{
_accountsByName[a.Username] = a;
_accountsPersistence.AddEntity(a);
}
public static void Remove(Account a)
{
_accountsByName.Remove(a.Username);
_accountsPersistence.RemoveEntity(a);
}
public override void Deserialize(string path, Dictionary<ulong, string> typesDb)
{
var filePath = Path.Combine(path, "Accounts", "accounts.xml");
// Backward Compatibility
if (File.Exists(filePath))
{
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;
}
DeserializeXml(filePath);
return;
}
public static int Count => _accountsByName.Count;
base.Deserialize(path, typesDb);
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)
foreach (var a in EntitiesBySerial.Values)
{
_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;
}
}
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) => _accountsPersistence.FindEntity<Account>(serial);
}