ace/Source/ACE.Database/ShardDatabase.cs
Mag-nus 4dab146548
Code cleanup (#1681)
* Flags attribute

* empty statement

* Fix Formatting

* Fix OrderBy -> ThenBy

* use OfType

* Replace with asingle call to Count(...)

* Replace with a single call to FirstOrDefault

* Merge cast with type check

* use String.IsNullOrEmpty

* use collections count property

* use format specifier (results in shorter loc)

* use Any() instead of Count() > 0

* revert ThenBy with comment
2019-04-10 20:48:34 -05:00

815 lines
36 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using log4net;
using ACE.Common.Extensions;
using ACE.Database.Entity;
using ACE.Database.Models.Shard;
using ACE.Entity.Enum;
using ACE.Entity.Enum.Properties;
namespace ACE.Database
{
public class ShardDatabase
{
private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public bool Exists(bool retryUntilFound)
{
var config = Common.ConfigManager.Config.MySql.Shard;
for (; ; )
{
using (var context = new ShardDbContext())
{
if (((RelationalDatabaseCreator)context.Database.GetService<IDatabaseCreator>()).Exists())
{
log.Debug($"Successfully connected to {config.Database} database on {config.Host}:{config.Port}.");
return true;
}
}
log.Error($"Attempting to reconnect to {config.Database} database on {config.Host}:{config.Port} in 5 seconds...");
if (retryUntilFound)
Thread.Sleep(5000);
else
return false;
}
}
/// <summary>
/// Will return uint.MaxValue if no records were found within the range provided.
/// </summary>
public uint GetMaxGuidFoundInRange(uint min, uint max)
{
using (var context = new ShardDbContext())
{
var results = context.Biota
.AsNoTracking()
.Where(r => r.Id >= min && r.Id <= max)
.ToList();
if (!results.Any())
return uint.MaxValue;
var maxId = min;
foreach (var result in results)
{
if (result.Id > maxId)
maxId = result.Id;
}
return maxId;
}
}
/// <summary>
/// This will return available id's, in the form of sequence gaps starting from min.<para />
/// If a gap is just 1 value wide, then both start and end will be the same number.
/// </summary>
public List<(uint start, uint end)> GetSequenceGaps(uint min, uint limitAvailableIDsReturned)
{
// References:
// https://stackoverflow.com/questions/4340793/how-to-find-gaps-in-sequential-numbering-in-mysql/29736658#29736658
// https://stackoverflow.com/questions/50402015/how-to-execute-sqlquery-with-entity-framework-core-2-1
// This query is ugly, but very fast.
var sql = "SELECT" + Environment.NewLine +
" z.gap_starts_at, z.gap_ends_at_not_inclusive, @available_ids:=@available_ids+(z.gap_ends_at_not_inclusive - z.gap_starts_at) as running_total_available_ids" + Environment.NewLine +
"FROM (" + Environment.NewLine +
" SELECT" + Environment.NewLine +
" @rownum:=@rownum+1 AS gap_starts_at," + Environment.NewLine +
" @available_ids:=0," + Environment.NewLine +
" IF(@rownum=id, 0, @rownum:=id) AS gap_ends_at_not_inclusive" + Environment.NewLine +
" FROM" + Environment.NewLine +
" (SELECT @rownum:=(SELECT MIN(id)-1 FROM biota WHERE id > " + min + ")) AS a" + Environment.NewLine +
" JOIN biota" + Environment.NewLine +
" WHERE id > " + min + Environment.NewLine +
" ORDER BY id" + Environment.NewLine +
" ) AS z" + Environment.NewLine +
"WHERE z.gap_ends_at_not_inclusive!=0 AND @available_ids<" + limitAvailableIDsReturned + "; ";
using (var context = new ShardDbContext())
{
var connection = context.Database.GetDbConnection();
connection.Open();
var command = connection.CreateCommand();
command.CommandText = sql;
var reader = command.ExecuteReader();
var gaps = new List<(uint start, uint end)>();
while (reader.Read())
{
var gap_starts_at = reader.GetFieldValue<double>(0);
var gap_ends_at_not_inclusive = reader.GetFieldValue<decimal>(1);
//var running_total_available_ids = reader.GetFieldValue<double>(2);
gaps.Add(((uint)gap_starts_at, (uint)gap_ends_at_not_inclusive - 1));
}
return gaps;
}
}
public int GetBiotaCount()
{
using (var context = new ShardDbContext())
return context.Biota.Count();
}
[Flags]
enum PopulatedCollectionFlags
{
BiotaPropertiesAnimPart = 0x1,
BiotaPropertiesAttribute = 0x2,
BiotaPropertiesAttribute2nd = 0x4,
BiotaPropertiesBodyPart = 0x8,
BiotaPropertiesBook = 0x10,
BiotaPropertiesBookPageData = 0x20,
BiotaPropertiesBool = 0x40,
BiotaPropertiesCreateList = 0x80,
BiotaPropertiesDID = 0x100,
BiotaPropertiesEmote = 0x200,
BiotaPropertiesEnchantmentRegistry = 0x400,
BiotaPropertiesEventFilter = 0x800,
BiotaPropertiesFloat = 0x1000,
BiotaPropertiesGenerator = 0x2000,
BiotaPropertiesIID = 0x4000,
BiotaPropertiesInt = 0x8000,
BiotaPropertiesInt64 = 0x10000,
BiotaPropertiesPalette = 0x20000,
BiotaPropertiesPosition = 0x40000,
BiotaPropertiesSkill = 0x80000,
BiotaPropertiesSpellBook = 0x100000,
BiotaPropertiesString = 0x200000,
BiotaPropertiesTextureMap = 0x400000,
HousePermission = 0x800000,
}
private static void SetBiotaPopulatedCollections(Biota biota)
{
PopulatedCollectionFlags populatedCollectionFlags = 0;
if (biota.BiotaPropertiesAnimPart != null && biota.BiotaPropertiesAnimPart.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesAnimPart;
if (biota.BiotaPropertiesAttribute != null && biota.BiotaPropertiesAttribute.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesAttribute;
if (biota.BiotaPropertiesAttribute2nd != null && biota.BiotaPropertiesAttribute2nd.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesAttribute2nd;
if (biota.BiotaPropertiesBodyPart != null && biota.BiotaPropertiesBodyPart.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesBodyPart;
if (biota.BiotaPropertiesBook != null) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesBook;
if (biota.BiotaPropertiesBookPageData != null && biota.BiotaPropertiesBookPageData.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesBookPageData;
if (biota.BiotaPropertiesBool != null && biota.BiotaPropertiesBool.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesBool;
if (biota.BiotaPropertiesCreateList != null && biota.BiotaPropertiesCreateList.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesCreateList;
if (biota.BiotaPropertiesDID != null && biota.BiotaPropertiesDID.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesDID;
if (biota.BiotaPropertiesEmote != null && biota.BiotaPropertiesEmote.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesEmote;
if (biota.BiotaPropertiesEnchantmentRegistry != null && biota.BiotaPropertiesEnchantmentRegistry.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesEnchantmentRegistry;
if (biota.BiotaPropertiesEventFilter != null && biota.BiotaPropertiesEventFilter.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesEventFilter;
if (biota.BiotaPropertiesFloat != null && biota.BiotaPropertiesFloat.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesFloat;
if (biota.BiotaPropertiesGenerator != null && biota.BiotaPropertiesGenerator.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesGenerator;
if (biota.BiotaPropertiesIID != null && biota.BiotaPropertiesIID.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesIID;
if (biota.BiotaPropertiesInt != null && biota.BiotaPropertiesInt.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesInt;
if (biota.BiotaPropertiesInt64 != null && biota.BiotaPropertiesInt64.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesInt64;
if (biota.BiotaPropertiesPalette != null && biota.BiotaPropertiesPalette.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesPalette;
if (biota.BiotaPropertiesPosition != null && biota.BiotaPropertiesPosition.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesPosition;
if (biota.BiotaPropertiesSkill != null && biota.BiotaPropertiesSkill.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesSkill;
if (biota.BiotaPropertiesSpellBook != null && biota.BiotaPropertiesSpellBook.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesSpellBook;
if (biota.BiotaPropertiesString != null && biota.BiotaPropertiesString.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesString;
if (biota.BiotaPropertiesTextureMap != null && biota.BiotaPropertiesTextureMap.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.BiotaPropertiesTextureMap;
if (biota.HousePermission != null && biota.HousePermission.Count > 0) populatedCollectionFlags |= PopulatedCollectionFlags.HousePermission;
biota.PopulatedCollectionFlags = (uint)populatedCollectionFlags;
}
private static readonly ConditionalWeakTable<Biota, ShardDbContext> BiotaContexts = new ConditionalWeakTable<Biota, ShardDbContext>();
private static Biota GetBiota(ShardDbContext context, uint id)
{
var biota = context.Biota
.FirstOrDefault(r => r.Id == id);
if (biota == null)
return null;
PopulatedCollectionFlags populatedCollectionFlags = (PopulatedCollectionFlags)biota.PopulatedCollectionFlags;
// todo: There are gains to be had here if we can conditionally perform mulitple .Include (.Where) statements in a single query.
// todo: Until I figure out how to do that, this is still pretty good. Mag-nus 2018-08-10
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesAnimPart)) biota.BiotaPropertiesAnimPart = context.BiotaPropertiesAnimPart.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesAttribute)) biota.BiotaPropertiesAttribute = context.BiotaPropertiesAttribute.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesAttribute2nd)) biota.BiotaPropertiesAttribute2nd = context.BiotaPropertiesAttribute2nd.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesBodyPart)) biota.BiotaPropertiesBodyPart = context.BiotaPropertiesBodyPart.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesBook)) biota.BiotaPropertiesBook = context.BiotaPropertiesBook.FirstOrDefault(r => r.ObjectId == biota.Id);
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesBookPageData)) biota.BiotaPropertiesBookPageData = context.BiotaPropertiesBookPageData.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesBool)) biota.BiotaPropertiesBool = context.BiotaPropertiesBool.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesCreateList)) biota.BiotaPropertiesCreateList = context.BiotaPropertiesCreateList.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesDID)) biota.BiotaPropertiesDID = context.BiotaPropertiesDID.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesEmote)) biota.BiotaPropertiesEmote = context.BiotaPropertiesEmote.Include(r => r.BiotaPropertiesEmoteAction).Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesEnchantmentRegistry)) biota.BiotaPropertiesEnchantmentRegistry = context.BiotaPropertiesEnchantmentRegistry.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesEventFilter)) biota.BiotaPropertiesEventFilter = context.BiotaPropertiesEventFilter.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesFloat)) biota.BiotaPropertiesFloat = context.BiotaPropertiesFloat.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesGenerator)) biota.BiotaPropertiesGenerator = context.BiotaPropertiesGenerator.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesIID)) biota.BiotaPropertiesIID = context.BiotaPropertiesIID.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesInt)) biota.BiotaPropertiesInt = context.BiotaPropertiesInt.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesInt64)) biota.BiotaPropertiesInt64 = context.BiotaPropertiesInt64.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesPalette)) biota.BiotaPropertiesPalette = context.BiotaPropertiesPalette.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesPosition)) biota.BiotaPropertiesPosition = context.BiotaPropertiesPosition.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesSkill)) biota.BiotaPropertiesSkill = context.BiotaPropertiesSkill.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesSpellBook)) biota.BiotaPropertiesSpellBook = context.BiotaPropertiesSpellBook.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesString)) biota.BiotaPropertiesString = context.BiotaPropertiesString.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.BiotaPropertiesTextureMap)) biota.BiotaPropertiesTextureMap = context.BiotaPropertiesTextureMap.Where(r => r.ObjectId == biota.Id).ToList();
if (populatedCollectionFlags.HasFlag(PopulatedCollectionFlags.HousePermission)) biota.HousePermission = context.HousePermission.Where(r => r.HouseId == biota.Id).ToList();
return biota;
}
public Biota GetBiota(uint id)
{
var context = new ShardDbContext();
var biota = GetBiota(context, id);
if (biota != null)
BiotaContexts.Add(biota, context);
return biota;
}
public List<Biota> GetBiotasByWcid(uint wcid)
{
using (var context = new ShardDbContext())
{
var results = context.Biota.Where(r => r.WeenieClassId == wcid);
var biotas = new List<Biota>();
foreach (var result in results)
biotas.Add(GetBiota(context, result.Id));
return biotas;
}
}
public bool SaveBiota(Biota biota, ReaderWriterLockSlim rwLock)
{
if (BiotaContexts.TryGetValue(biota, out var cachedContext))
{
rwLock.EnterReadLock();
try
{
SetBiotaPopulatedCollections(biota);
Exception firstException = null;
retry:
try
{
cachedContext.SaveChanges();
if (firstException != null)
log.Debug($"SaveBiota retry succeeded after initial exception of: {firstException.GetFullMessage()}");
return true;
}
catch (Exception ex)
{
if (firstException == null)
{
firstException = ex;
goto retry;
}
// Character name might be in use or some other fault
log.Error($"SaveBiota failed first attempt with exception: {firstException}");
log.Error($"SaveBiota failed second attempt with exception: {ex}");
return false;
}
}
finally
{
rwLock.ExitReadLock();
}
}
var context = new ShardDbContext();
BiotaContexts.Add(biota, context);
rwLock.EnterReadLock();
try
{
SetBiotaPopulatedCollections(biota);
context.Biota.Add(biota);
Exception firstException = null;
retry:
try
{
context.SaveChanges();
if (firstException != null)
log.Debug($"SaveBiota retry succeeded after initial exception of: {firstException.GetFullMessage()}");
return true;
}
catch (Exception ex)
{
if (firstException == null)
{
firstException = ex;
goto retry;
}
// Character name might be in use or some other fault
log.Error($"SaveBiota failed first attempt with exception: {firstException}");
log.Error($"SaveBiota failed second attempt with exception: {ex}");
return false;
}
}
finally
{
rwLock.ExitReadLock();
}
}
public bool SaveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas)
{
var result = true;
Parallel.ForEach(biotas, biota =>
{
if (!SaveBiota(biota.biota, biota.rwLock))
result = false;
});
return result;
}
public bool RemoveBiota(Biota biota, ReaderWriterLockSlim rwLock)
{
if (BiotaContexts.TryGetValue(biota, out var cachedContext))
{
BiotaContexts.Remove(biota);
rwLock.EnterReadLock();
try
{
cachedContext.Biota.Remove(biota);
Exception firstException = null;
retry:
try
{
cachedContext.SaveChanges();
if (firstException != null)
log.Debug($"RemoveBiota retry succeeded after initial exception of: {firstException.GetFullMessage()}");
return true;
}
catch (Exception ex)
{
if (firstException == null)
{
firstException = ex;
goto retry;
}
// Character name might be in use or some other fault
log.Error($"RemoveBiota failed first attempt with exception: {firstException}");
log.Error($"RemoveBiota failed second attempt with exception: {ex}");
return false;
}
}
finally
{
rwLock.ExitReadLock();
}
}
// If we got here, the biota didn't come from the database through this class.
// Most likely, it doesn't exist in the database, so, no need to remove.
return true;
}
public bool RemoveBiotasInParallel(IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> biotas)
{
var result = true;
Parallel.ForEach(biotas, biota =>
{
if (!RemoveBiota(biota.biota, biota.rwLock))
result = false;
});
return result;
}
public void FreeBiotaAndDisposeContext(Biota biota)
{
if (BiotaContexts.TryGetValue(biota, out var context))
{
BiotaContexts.Remove(biota);
context.Dispose();
}
}
public void FreeBiotaAndDisposeContexts(IEnumerable<Biota> biotas)
{
foreach (var biota in biotas)
FreeBiotaAndDisposeContext(biota);
}
public PossessedBiotas GetPossessedBiotasInParallel(uint id)
{
var inventory = GetInventoryInParallel(id, true);
var wieldedItems = GetWieldedItemsInParallel(id);
return new PossessedBiotas(inventory, wieldedItems);
}
public List<Biota> GetInventoryInParallel(uint parentId, bool includedNestedItems)
{
var inventory = new ConcurrentBag<Biota>();
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.BiotaPropertiesIID
.Where(r => r.Type == (ushort)PropertyInstanceId.Container && r.Value == parentId)
.ToList();
Parallel.ForEach(results, result =>
{
var biota = GetBiota(result.ObjectId);
if (biota != null)
{
inventory.Add(biota);
if (includedNestedItems && biota.WeenieType == (int)WeenieType.Container)
{
var subItems = GetInventoryInParallel(biota.Id, false);
foreach (var subItem in subItems)
inventory.Add(subItem);
}
}
});
}
return inventory.ToList();
}
public List<Biota> GetWieldedItemsInParallel(uint parentId)
{
var wieldedItems = new ConcurrentBag<Biota>();
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.BiotaPropertiesIID
.Where(r => r.Type == (ushort)PropertyInstanceId.Wielder && r.Value == parentId)
.ToList();
Parallel.ForEach(results, result =>
{
var biota = GetBiota(result.ObjectId);
if (biota != null)
wieldedItems.Add(biota);
});
}
return wieldedItems.ToList();
}
public List<Biota> GetStaticObjectsByLandblock(ushort landblockId)
{
var staticObjects = new List<Biota>();
var staticLandblockId = 0x70000 | landblockId;
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.Biota.Where(b => b.Id >> 12 == staticLandblockId).ToList();
foreach (var result in results)
{
var biota = GetBiota(result.Id);
staticObjects.Add(biota);
}
}
return staticObjects;
}
public List<Biota> GetStaticObjectsByLandblockInParallel(ushort landblockId)
{
var staticObjects = new ConcurrentBag<Biota>();
var staticLandblockId = 0x70000 | landblockId;
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.Biota.Where(b => b.Id >> 12 == staticLandblockId).ToList();
Parallel.ForEach(results, result =>
{
var biota = GetBiota(result.Id);
staticObjects.Add(biota);
});
}
return staticObjects.ToList();
}
public List<Biota> GetDynamicObjectsByLandblock(ushort landblockId)
{
var dynamics = new List<Biota>();
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.BiotaPropertiesPosition
.Where(p => p.PositionType == 1 && p.ObjCellId >> 16 == landblockId && p.ObjectId >= 0x80000000)
.ToList();
foreach (var result in results)
{
var biota = GetBiota(result.ObjectId);
// Filter out objects that are in a container
if (biota.BiotaPropertiesIID.FirstOrDefault(r => r.Type == 2 && r.Value != 0) != null)
continue;
// Filter out wielded objects
if (biota.BiotaPropertiesIID.FirstOrDefault(r => r.Type == 3 && r.Value != 0) != null)
continue;
dynamics.Add(biota);
}
}
return dynamics;
}
public List<Biota> GetDynamicObjectsByLandblockInParallel(ushort landblockId)
{
var dynamics = new ConcurrentBag<Biota>();
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.BiotaPropertiesPosition
.Where(p => p.PositionType == 1 && p.ObjCellId >> 16 == landblockId && p.ObjectId >= 0x80000000)
.ToList();
Parallel.ForEach(results, result =>
{
var biota = GetBiota(result.ObjectId);
// Filter out objects that are in a container
if (biota.BiotaPropertiesIID.FirstOrDefault(r => r.Type == 2 && r.Value != 0) != null)
return;
// Filter out wielded objects
if (biota.BiotaPropertiesIID.FirstOrDefault(r => r.Type == 3 && r.Value != 0) != null)
return;
dynamics.Add(biota);
});
}
return dynamics.ToList();
}
public List<Biota> GetHousesOwned()
{
using (var context = new ShardDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
var results = context.Biota.Where(i => i.WeenieType == (int)WeenieType.SlumLord).ToList();
return results;
}
}
public bool IsCharacterNameAvailable(string name)
{
using (var context = new ShardDbContext())
{
var result = context.Character
.AsNoTracking()
.Where(r => !r.IsDeleted)
.Where(r => !(r.DeleteTime > 0))
.FirstOrDefault(r => r.Name == name);
return result == null;
}
}
private static readonly ConditionalWeakTable<Character, ShardDbContext> CharacterContexts = new ConditionalWeakTable<Character, ShardDbContext>();
public List<Character> GetCharacters(uint accountId, bool includeDeleted)
{
var context = new ShardDbContext();
var results = context.Character
.Include(r => r.CharacterPropertiesContract)
.Include(r => r.CharacterPropertiesFillCompBook)
.Include(r => r.CharacterPropertiesFriendList)
.Include(r => r.CharacterPropertiesQuestRegistry)
.Include(r => r.CharacterPropertiesShortcutBar)
.Include(r => r.CharacterPropertiesSpellBar)
.Include(r => r.CharacterPropertiesTitleBook)
.Where(r => r.AccountId == accountId && (includeDeleted || !r.IsDeleted))
.ToList();
foreach (var result in results)
CharacterContexts.Add(result, context);
return results;
}
public Character GetCharacterByName(string name) // When searching by name, only non-deleted characters matter
{
var context = new ShardDbContext();
var result = context.Character
//.Include(r => r.CharacterPropertiesContract)
//.Include(r => r.CharacterPropertiesFillCompBook)
//.Include(r => r.CharacterPropertiesFriendList)
//.Include(r => r.CharacterPropertiesQuestRegistry)
//.Include(r => r.CharacterPropertiesShortcutBar)
//.Include(r => r.CharacterPropertiesSpellBar)
//.Include(r => r.CharacterPropertiesTitleBook)
.FirstOrDefault(r => r.Name == name.ToLower() && !r.IsDeleted);
if (result != null)
CharacterContexts.Add(result, context);
return result;
}
public bool SaveCharacter(Character character, ReaderWriterLockSlim rwLock)
{
if (CharacterContexts.TryGetValue(character, out var cachedContext))
{
rwLock.EnterReadLock();
try
{
Exception firstException = null;
retry:
try
{
cachedContext.SaveChanges();
if (firstException != null)
log.Debug($"SaveCharacter retry succeeded after initial exception of: {firstException.GetFullMessage()}");
return true;
}
catch (Exception ex)
{
if (firstException == null)
{
firstException = ex;
goto retry;
}
// Character name might be in use or some other fault
log.Error($"SaveCharacter failed first attempt with exception: {firstException}");
log.Error($"SaveCharacter failed second attempt with exception: {ex}");
return false;
}
}
finally
{
rwLock.ExitReadLock();
}
}
var context = new ShardDbContext();
CharacterContexts.Add(character, context);
rwLock.EnterReadLock();
try
{
context.Character.Add(character);
Exception firstException = null;
retry:
try
{
context.SaveChanges();
if (firstException != null)
log.Debug($"SaveCharacter retry succeeded after initial exception of: {firstException.GetFullMessage()}");
return true;
}
catch (Exception ex)
{
if (firstException == null)
{
firstException = ex;
goto retry;
}
// Character name might be in use or some other fault
log.Error($"SaveCharacter failed first attempt with exception: {firstException}");
log.Error($"SaveCharacter failed second attempt with exception: {ex}");
return false;
}
}
finally
{
rwLock.ExitReadLock();
}
}
public bool AddCharacterInParallel(Biota biota, ReaderWriterLockSlim biotaLock, IEnumerable<(Biota biota, ReaderWriterLockSlim rwLock)> possessions, Character character, ReaderWriterLockSlim characterLock)
{
if (!SaveBiota(biota, biotaLock))
return false; // Biota save failed which mean Character fails.
if (!SaveBiotasInParallel(possessions))
return false;
if (!SaveCharacter(character, characterLock))
return false;
return true;
}
/// <summary>
/// This will get all player biotas that are backed by characters that are not deleted.
/// </summary>
public List<Biota> GetAllPlayerBiotasInParallel()
{
var biotas = new ConcurrentBag<Biota>();
using (var context = new ShardDbContext())
{
var results = context.Character
.Where(r => !r.IsDeleted)
.AsNoTracking()
.ToList();
Parallel.ForEach(results, result =>
{
var biota = GetBiota(result.Id);
biotas.Add(biota);
});
}
return biotas.ToList();
}
public uint? GetAllegianceID(uint monarchID)
{
using (var context = new ShardDbContext())
{
var query = from biota in context.Biota
join iid in context.BiotaPropertiesIID on biota.Id equals iid.ObjectId
where biota.WeenieType == (int)WeenieType.Allegiance && iid.Type == (int)PropertyInstanceId.Monarch && iid.Value == monarchID
select biota.Id;
return query.FirstOrDefault();
}
}
}
}