mirror of
https://github.com/ACEmulator/ACE
synced 2026-08-17 12:26:06 -04:00
* RateMonitor created * NetworkSession Update foreach change to help profiling This just removes the lambda pattern for the foreach so profilers can monitor each line individually. Functionality is unchanged. * Reduce Landblock ctor async work to a single thread This helps reduce ACE thread starvation * NetworkSession cosmetic * NetworkSession cosmetic * NetworkSession currentBundles ConcurrentDictionary to Array This significantly improves the performance of NetworkSession. Arrays are faster than Dictionaries, and we manage concurrency to the elements via currentBundleLocks. We're also able to get away with using an array because the number of elements is only 12. Accessing the indexes of currentBundles should be atomic as it's an array of reference values, compiled as 64 bit. * Add InParallel to database functions that do their underlying work in parallel * DoSessionWork tick outbound messages in series, not in parallel. * NetworkSession should not be interfacing with ActionChains * Don't load landblock ctor resources in parallel Save the threads in the pool for more important work * LandblockManager removed check/recheck This is legacy code from a pattern no longer used. It is no longer required. * Cosmetic
332 lines
12 KiB
C#
332 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Concurrent;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
using ACE.Database.Entity;
|
|
using ACE.Database.Models.Shard;
|
|
using ACE.Entity.Enum;
|
|
|
|
namespace ACE.Database
|
|
{
|
|
public class SerializedShardDatabase
|
|
{
|
|
private readonly ShardDatabase _wrappedDatabase;
|
|
|
|
private readonly BlockingCollection<Task> _queue = new BlockingCollection<Task>();
|
|
|
|
private Thread _workerThread;
|
|
|
|
internal SerializedShardDatabase(ShardDatabase shardDatabase)
|
|
{
|
|
_wrappedDatabase = shardDatabase;
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
_workerThread = new Thread(DoWork);
|
|
_workerThread.Name = "Serialized Shard Database";
|
|
_workerThread.Start();
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
_queue.CompleteAdding();
|
|
_workerThread.Join();
|
|
}
|
|
|
|
private void DoWork()
|
|
{
|
|
while (!_queue.IsAddingCompleted)
|
|
{
|
|
try
|
|
{
|
|
Task t = _queue.Take();
|
|
|
|
try
|
|
{
|
|
t.Start();
|
|
t.Wait();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// log eventually, perhaps add failure callbacks?
|
|
// swallow for now. can't block other db work because 1 fails.
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
// the _queue has been disposed, we're good
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// _queue is empty and CompleteForAdding has been called -- we're done here
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public int QueueCount => _queue.Count;
|
|
|
|
public void GetCurrentQueueWaitTime(Action<TimeSpan> callback)
|
|
{
|
|
var initialCallTime = DateTime.UtcNow;
|
|
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
callback?.Invoke(DateTime.UtcNow - initialCallTime);
|
|
}));
|
|
}
|
|
|
|
|
|
public void GetMaxGuidFoundInRange(uint min, uint max, Action<uint> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.GetMaxGuidFoundInRange(min, max);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
|
|
public Biota GetBiota(uint id)
|
|
{
|
|
return _wrappedDatabase.GetBiota(id);
|
|
}
|
|
|
|
public void GetBiota(uint id, Action<Biota> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var c = _wrappedDatabase.GetBiota(id);
|
|
callback?.Invoke(c);
|
|
}));
|
|
}
|
|
|
|
public void SaveBiota(Biota biota, ReaderWriterLockSlim rwLock, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.SaveBiota(biota, rwLock);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void SaveBiota(Biota biota, ReaderWriterLockSlim rwLock, Action<bool> callback, Action<TimeSpan, TimeSpan> performanceResults)
|
|
{
|
|
var initialCallTime = DateTime.UtcNow;
|
|
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var taskStartTime = DateTime.UtcNow;
|
|
var result = _wrappedDatabase.SaveBiota(biota, rwLock);
|
|
var taskCompletedTime = DateTime.UtcNow;
|
|
callback?.Invoke(result);
|
|
performanceResults?.Invoke(taskStartTime - initialCallTime, taskCompletedTime - taskStartTime);
|
|
}));
|
|
}
|
|
|
|
public void SaveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.SaveBiotasInParallel(biotas);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void SaveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas, Action<bool> callback, Action<TimeSpan, TimeSpan> performanceResults)
|
|
{
|
|
var initialCallTime = DateTime.UtcNow;
|
|
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var taskStartTime = DateTime.UtcNow;
|
|
var result = _wrappedDatabase.SaveBiotasInParallel(biotas);
|
|
var taskCompletedTime = DateTime.UtcNow;
|
|
callback?.Invoke(result);
|
|
performanceResults?.Invoke(taskStartTime - initialCallTime, taskCompletedTime - taskStartTime);
|
|
}));
|
|
}
|
|
|
|
public void RemoveBiota(Biota biota, ReaderWriterLockSlim rwLock, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.RemoveBiota(biota, rwLock);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void RemoveBiota(Biota biota, ReaderWriterLockSlim rwLock, Action<bool> callback, Action<TimeSpan, TimeSpan> performanceResults)
|
|
{
|
|
var initialCallTime = DateTime.UtcNow;
|
|
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var taskStartTime = DateTime.UtcNow;
|
|
var result = _wrappedDatabase.RemoveBiota(biota, rwLock);
|
|
var taskCompletedTime = DateTime.UtcNow;
|
|
callback?.Invoke(result);
|
|
performanceResults?.Invoke(taskStartTime - initialCallTime, taskCompletedTime - taskStartTime);
|
|
}));
|
|
}
|
|
|
|
public void RemoveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.RemoveBiotasInParallel(biotas);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void RemoveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas, Action<bool> callback, Action<TimeSpan, TimeSpan> performanceResults)
|
|
{
|
|
var initialCallTime = DateTime.UtcNow;
|
|
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var taskStartTime = DateTime.UtcNow;
|
|
var result = _wrappedDatabase.RemoveBiotasInParallel(biotas);
|
|
var taskCompletedTime = DateTime.UtcNow;
|
|
callback?.Invoke(result);
|
|
performanceResults?.Invoke(taskStartTime - initialCallTime, taskCompletedTime - taskStartTime);
|
|
}));
|
|
}
|
|
|
|
public void FreeBiotaAndDisposeContext(Biota biota)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
_wrappedDatabase.FreeBiotaAndDisposeContext(biota);
|
|
}));
|
|
}
|
|
|
|
public void FreeBiotaAndDisposeContexts(IEnumerable<Biota> biotas)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
_wrappedDatabase.FreeBiotaAndDisposeContexts(biotas);
|
|
}));
|
|
}
|
|
|
|
|
|
public void GetPlayerBiotasInParallel(uint id, Action<PlayerBiotas> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var c = _wrappedDatabase.GetPlayerBiotasInParallel(id);
|
|
callback?.Invoke(c);
|
|
}));
|
|
}
|
|
|
|
public void GetInventoryInParallel(uint parentId, bool includedNestedItems, Action<List<Biota>> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var c = _wrappedDatabase.GetInventoryInParallel(parentId, includedNestedItems);
|
|
callback?.Invoke(c);
|
|
}));
|
|
|
|
}
|
|
|
|
public void GetWieldedItemsInParallel(uint parentId, Action<List<Biota>> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var c = _wrappedDatabase.GetWieldedItemsInParallel(parentId);
|
|
callback?.Invoke(c);
|
|
}));
|
|
|
|
}
|
|
|
|
public List<Biota> GetDecayableObjectsByLandblock(ushort landblockId)
|
|
{
|
|
return _wrappedDatabase.GetDecayableObjectsByLandblock(landblockId);
|
|
}
|
|
|
|
public List<Biota> GetDecayableObjectsByLandblockInParallel(ushort landblockId)
|
|
{
|
|
return _wrappedDatabase.GetDecayableObjectsByLandblockInParallel(landblockId);
|
|
}
|
|
|
|
public List<Biota> GetStaticObjectsByLandblock(ushort landblockId)
|
|
{
|
|
return _wrappedDatabase.GetStaticObjectsByLandblock(landblockId);
|
|
}
|
|
|
|
public List<Biota> GetStaticObjectsByLandblockInParallel(ushort landblockId)
|
|
{
|
|
return _wrappedDatabase.GetStaticObjectsByLandblockInParallel(landblockId);
|
|
}
|
|
|
|
|
|
public void IsCharacterNameAvailable(string name, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.IsCharacterNameAvailable(name);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void GetCharacters(uint accountId, bool includeDeleted, Action<List<Character>> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.GetCharacters(accountId, includeDeleted);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
public void SaveCharacter(Character character, ReaderWriterLockSlim rwLock, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.SaveCharacter(character, rwLock);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
|
|
public void AddCharacterInParallel(Biota biota, ReaderWriterLockSlim biotaLock, IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> possessions, Character character, ReaderWriterLockSlim characterLock, Action<bool> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.AddCharacterInParallel(biota, biotaLock, possessions, character, characterLock);
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
|
|
public void GetAllCharacters(Action<List<Character>> callback)
|
|
{
|
|
_queue.Add(new Task(() =>
|
|
{
|
|
var result = _wrappedDatabase.GetAllCharacters();
|
|
callback?.Invoke(result);
|
|
}));
|
|
}
|
|
|
|
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
// ******************************************************************* OLD CODE BELOW ********************************
|
|
|
|
public void RenameCharacter(string currentName, string newName, Action<uint> callback)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public void SetCharacterAccessLevelByName(string name, AccessLevel accessLevel, Action<uint> callback)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|