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
100 lines
4.1 KiB
C#
100 lines
4.1 KiB
C#
using System;
|
|
using System.Collections.ObjectModel;
|
|
using System.Threading;
|
|
|
|
using ACE.Database;
|
|
using ACE.Database.Models.Shard;
|
|
using ACE.Entity.Enum;
|
|
using ACE.Entity.Enum.Properties;
|
|
using ACE.Server.Entity.Actions;
|
|
|
|
namespace ACE.Server.WorldObjects
|
|
{
|
|
partial class Player
|
|
{
|
|
public static TimeSpan PlayerSaveInterval = TimeSpan.FromMinutes(5);
|
|
|
|
public DateTime CharacterLastRequestedDatabaseSave { get; protected set; }
|
|
|
|
/// <summary>
|
|
/// This variable is set to true when a change is made, and set to false before a save is requested.<para />
|
|
/// The primary use for this is to trigger save on add/modify/remove of properties.
|
|
/// </summary>
|
|
public bool CharacterChangesDetected { get; set; }
|
|
|
|
/// <summary>
|
|
/// Best practice says you should use this lock any time you read/write the Character.<para />
|
|
/// <para />
|
|
/// For absolute maximum performance, if you're willing to assume (and risk) the following:<para />
|
|
/// - that the character in the database will not be modified (in a way that adds or removes properties) outside of ACE while ACE is running with a reference to that character<para />
|
|
/// - that the character will only be read/modified by a single thread in ACE<para />
|
|
/// You can remove the lock usage for any Get/GetAll Property functions. You would simply use it for Set/Remove Property functions because each of these could end up adding/removing to the collections.<para />
|
|
/// The critical thing is that the collections are not added to or removed from while Entity Framework is iterating over them.<para />
|
|
/// Mag-nus 2018-08-19
|
|
/// </summary>
|
|
public readonly ReaderWriterLockSlim CharacterDatabaseLock = new ReaderWriterLockSlim();
|
|
|
|
/// <summary>
|
|
/// Gets the ActionChain to save a character
|
|
/// </summary>
|
|
public ActionChain GetSaveChain()
|
|
{
|
|
return new ActionChain(this, SavePlayer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates and Enqueues an ActionChain to save a character
|
|
/// </summary>
|
|
public void EnqueueSaveChain()
|
|
{
|
|
GetSaveChain().EnqueueChain();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Internal save character functionality<para />
|
|
/// Saves the character to the persistent database. Includes Stats, Position, Skills, etc.<para />
|
|
/// Will also save any possessions that are marked with ChangesDetected.
|
|
/// </summary>
|
|
private void SavePlayer()
|
|
{
|
|
if (CharacterChangesDetected)
|
|
SaveCharacterToDatabase();
|
|
|
|
var biotas = new Collection<(Biota biota, ReaderWriterLockSlim rwLock)>();
|
|
|
|
SaveBiotaToDatabase(false);
|
|
biotas.Add((Biota, BiotaDatabaseLock));
|
|
|
|
var allPosessions = GetAllPossessions();
|
|
|
|
foreach (var possession in allPosessions)
|
|
{
|
|
if (possession.ChangesDetected)
|
|
{
|
|
possession.SaveBiotaToDatabase(false);
|
|
biotas.Add((possession.Biota, possession.BiotaDatabaseLock));
|
|
}
|
|
}
|
|
|
|
var requestedTime = DateTime.UtcNow;
|
|
|
|
DatabaseManager.Shard.SaveBiotasInParallel(biotas, result => log.Debug($"{Session.Player.Name} has been saved. It took {(DateTime.UtcNow - requestedTime).TotalMilliseconds:N0} ms to process the request."));
|
|
}
|
|
|
|
public void SaveCharacterToDatabase()
|
|
{
|
|
// Make sure our IsPlussed value is up to date
|
|
bool isPlussed = (GetProperty(PropertyBool.IsAdmin) ?? false) || (GetProperty(PropertyBool.IsArch) ?? false) || (GetProperty(PropertyBool.IsPsr) ?? false) || (GetProperty(PropertyBool.IsSentinel) ?? false);
|
|
|
|
if (WeenieType == WeenieType.Admin || WeenieType == WeenieType.Sentinel)
|
|
isPlussed = true;
|
|
|
|
Character.IsPlussed = isPlussed;
|
|
|
|
CharacterLastRequestedDatabaseSave = DateTime.UtcNow;
|
|
CharacterChangesDetected = false;
|
|
|
|
DatabaseManager.Shard.SaveCharacter(Character, CharacterDatabaseLock, null);
|
|
}
|
|
}
|
|
}
|