ace/Source/ACE.Server/WorldObjects/Player_Database.cs
gmriggs 73f9a4a20c
Adding rollback mitigation for players receiving items from NPCs (#1644)
* Adding rollback mitigation for players receiving items from NPCs

* updating design to prevent flooding, as per discussion w/ magnus

* Removing NPCReceiveChangesDetected
2019-04-04 11:51:32 -04:00

91 lines
4 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;
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();
private void SetPropertiesAtLogOut()
{
// These properties are used with offline players to determine passup rates
SetProperty(PropertyInt.CurrentLoyaltyAtLastLogoff, (int)GetCreatureSkill(Skill.Loyalty).Current);
SetProperty(PropertyInt.CurrentLeadershipAtLastLogoff, (int)GetCreatureSkill(Skill.Leadership).Current);
}
/// <summary>
/// This will make sure a player save happens no later than the current time + seconds
/// </summary>
public void RushNextPlayerSave(int seconds)
{
if (LastRequestedDatabaseSave + PlayerSaveInterval <= DateTime.UtcNow.AddSeconds(seconds))
return;
LastRequestedDatabaseSave = DateTime.UtcNow.AddSeconds(seconds) - PlayerSaveInterval;
}
/// <summary>
/// Saves the character to the persistent database. Includes Stats, Position, Skills, etc.<para />
/// Will also save any possessions that are marked with ChangesDetected.
/// </summary>
public void SavePlayerToDatabase()
{
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($"{Name} has been saved. It took {(DateTime.UtcNow - requestedTime).TotalMilliseconds:N0} ms to process the request."));
}
public void SaveCharacterToDatabase()
{
CharacterLastRequestedDatabaseSave = DateTime.UtcNow;
CharacterChangesDetected = false;
DatabaseManager.Shard.SaveCharacter(Character, CharacterDatabaseLock, null);
}
}
}