mirror of
https://github.com/ACEmulator/ACE
synced 2026-08-17 12:26:06 -04:00
175 lines
8 KiB
C#
175 lines
8 KiB
C#
using System;
|
|
using System.Threading;
|
|
|
|
using ACE.Common;
|
|
using ACE.Database;
|
|
using ACE.Entity.Enum;
|
|
using ACE.Entity.Models;
|
|
using ACE.Server.Network.GameMessages.Messages;
|
|
|
|
namespace ACE.Server.WorldObjects
|
|
{
|
|
partial class WorldObject
|
|
{
|
|
private readonly bool biotaOriginatedFromDatabase;
|
|
|
|
public DateTime LastRequestedDatabaseSave { 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 ChangesDetected { get; set; }
|
|
|
|
/// <summary>
|
|
/// Best practice says you should use this lock any time you read/write the Biota.<para />
|
|
/// However, it's only a requirement to do this for properties/collections that will be modified after the initial biota has been created.<para />
|
|
/// There are several properties/collections of the biota that are simply duplicates of the original weenie and are never changed. You wouldn't need to use this lock to read those collections.<para />
|
|
/// <para />
|
|
/// For absolute maximum performance, if you're willing to assume (and risk) the following:<para />
|
|
/// - that the biota 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 biota<para />
|
|
/// - that the biota 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 BiotaDatabaseLock = new ReaderWriterLockSlim();
|
|
|
|
public bool BiotaOriginatedFromOrHasBeenSavedToDatabase()
|
|
{
|
|
return biotaOriginatedFromDatabase || LastRequestedDatabaseSave != DateTime.MinValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will set the LastRequestedDatabaseSave to UtcNow and ChangesDetected to false.<para />
|
|
/// If enqueueSave is set to true, DatabaseManager.Shard.SaveBiota() will be called for the biota.<para />
|
|
/// Set enqueueSave to false if you want to perform all the normal routines for a save but not the actual save. This is useful if you're going to collect biotas in bulk for bulk saving.
|
|
/// </summary>
|
|
public virtual void SaveBiotaToDatabase(bool enqueueSave = true)
|
|
{
|
|
// Make sure all of our positions in the biota are up to date with our current cached values.
|
|
foreach (var kvp in positionCache)
|
|
{
|
|
if (kvp.Value != null)
|
|
Biota.SetPosition(kvp.Key, kvp.Value, BiotaDatabaseLock);
|
|
}
|
|
|
|
LastRequestedDatabaseSave = DateTime.UtcNow;
|
|
ChangesDetected = false;
|
|
|
|
if (enqueueSave)
|
|
{
|
|
CheckpointTimestamp = Time.GetUnixTime();
|
|
//DatabaseManager.Shard.SaveBiota(Biota, BiotaDatabaseLock, null);
|
|
DatabaseManager.Shard.SaveBiota(Biota, BiotaDatabaseLock, result =>
|
|
{
|
|
if (!result)
|
|
{
|
|
if (this is Player player)
|
|
{
|
|
// This will trigger a boot on next player tick
|
|
player.BiotaSaveFailed = true;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will set the LastRequestedDatabaseSave to MinValue and ChangesDetected to true.<para />
|
|
/// If enqueueRemove is set to true, DatabaseManager.Shard.RemoveBiota() will be called for the biota.<para />
|
|
/// Set enqueueRemove to false if you want to perform all the normal routines for a remove but not the actual removal. This is useful if you're going to collect biotas in bulk for bulk removing.
|
|
/// </summary>
|
|
public void RemoveBiotaFromDatabase(bool enqueueRemove = true)
|
|
{
|
|
// If this entity doesn't exist in the database, let's not queue up work unnecessary database work.
|
|
if (!BiotaOriginatedFromOrHasBeenSavedToDatabase())
|
|
{
|
|
ChangesDetected = true;
|
|
return;
|
|
}
|
|
|
|
LastRequestedDatabaseSave = DateTime.MinValue;
|
|
ChangesDetected = true;
|
|
|
|
if (enqueueRemove)
|
|
DatabaseManager.Shard.RemoveBiota(Biota.Id, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A static that should persist to the shard may be a hook with an item, or a house that's been purchased, or a housing chest that isn't empty, etc...<para />
|
|
/// If the world object originated from the database or has been saved to the database, this will also return true.
|
|
/// </summary>
|
|
public bool IsStaticThatShouldPersistToShard()
|
|
{
|
|
if (!Guid.IsStatic())
|
|
return false;
|
|
|
|
if (BiotaOriginatedFromOrHasBeenSavedToDatabase())
|
|
return true;
|
|
|
|
if (WeenieType == WeenieType.SlumLord && this is SlumLord slumlord)
|
|
{
|
|
if (slumlord.House != null && slumlord.House.HouseOwner.HasValue && slumlord.House.HouseOwner != 0)
|
|
return true;
|
|
}
|
|
|
|
if (WeenieType == WeenieType.House && this is House house)
|
|
{
|
|
if (house.HouseOwner.HasValue && house.HouseOwner != 0)
|
|
return true;
|
|
}
|
|
|
|
if ((WeenieType == WeenieType.Hook || WeenieType == WeenieType.Storage) && this is Container container)
|
|
{
|
|
if (container.Inventory.Count > 0)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// This will filter out the following:<para />
|
|
/// Ammunition and Spell projectiles.<para />
|
|
/// Monster corpses.<para />
|
|
/// Missiles that haven't been saved to the shard yet.<para />
|
|
/// If the world object originated from the database or has been saved to the database, this will also return true.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool IsDynamicThatShouldPersistToShard()
|
|
{
|
|
if (!Guid.IsDynamic())
|
|
return false;
|
|
|
|
if (BiotaOriginatedFromOrHasBeenSavedToDatabase())
|
|
return true;
|
|
|
|
// Don't save generators, and items that were generated by a generator
|
|
// If the item was generated by a generator and then picked up by a player, the wo.Generator property would be set to null.
|
|
if (IsGenerator || Generator != null)
|
|
return false;
|
|
|
|
if (WeenieType == WeenieType.Missile || WeenieType == WeenieType.Ammunition || WeenieType == WeenieType.ProjectileSpell || WeenieType == WeenieType.GamePiece
|
|
|| WeenieType == WeenieType.Pet || WeenieType == WeenieType.CombatPet)
|
|
return false;
|
|
|
|
if (WeenieType == WeenieType.Corpse && this is Corpse corpse && corpse.IsMonster)
|
|
return false;
|
|
|
|
if (WeenieType == WeenieType.Portal && this is Portal portal && portal.IsGateway)
|
|
return false;
|
|
|
|
// Missiles are unique. The only missiles that are persistable are ones that already exist in the database.
|
|
// TODO: See if we can remove this check by catching the WeenieType above.
|
|
var missile = Missile;
|
|
if (missile.HasValue && missile.Value)
|
|
{
|
|
log.Warn($"Missile: WeenieClassId: {WeenieClassId}, Name: {Name}, WeenieType: {WeenieType}, detected in IsDynamicThatShouldPersistToShard() that wasn't caught by prior check.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|