rework cache creation conditions
clean up some stuff revert pet clearing fix initial cache creation not happening without changes/redraws fix draw conditions when framework inactive
This commit is contained in:
@@ -246,7 +246,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void DalamudUtil_FrameworkUpdate()
|
private void DalamudUtil_FrameworkUpdate()
|
||||||
{
|
{
|
||||||
_cachedFrameAddresses = new(_playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.CurrentAddress(), c => c.ObjectKind));
|
_cachedFrameAddresses = new(_playerRelatedPointers.Where(k => k.Address != nint.Zero).ToDictionary(c => c.Address, c => c.ObjectKind));
|
||||||
lock (_cacheAdditionLock)
|
lock (_cacheAdditionLock)
|
||||||
{
|
{
|
||||||
_cachedHandledPaths.Clear();
|
_cachedHandledPaths.Clear();
|
||||||
@@ -371,7 +371,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
if (isAdded)
|
if (isAdded)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath);
|
Logger.LogDebug("Adding {replacedGamePath} for {gameObject} ({filePath})", replacedGamePath, owner?.ToString() ?? gameObjectAddress.ToString("X"), filePath);
|
||||||
SendTransients(gameObjectAddress);
|
SendTransients(gameObjectAddress, objectKind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -382,7 +382,7 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendTransients(nint gameObject)
|
private void SendTransients(nint gameObject, ObjectKind objectKind)
|
||||||
{
|
{
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -391,7 +391,14 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
|
|||||||
_sendTransientCts = new();
|
_sendTransientCts = new();
|
||||||
var token = _sendTransientCts.Token;
|
var token = _sendTransientCts.Token;
|
||||||
await Task.Delay(TimeSpan.FromSeconds(5), token).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(5), token).ConfigureAwait(false);
|
||||||
Mediator.Publish(new TransientResourceChangedMessage(gameObject));
|
foreach (var kvp in TransientResources)
|
||||||
|
{
|
||||||
|
if (TransientResources.TryGetValue(objectKind, out var values) && values.Any())
|
||||||
|
{
|
||||||
|
Logger.LogTrace("Sending Transients for {kind}", objectKind);
|
||||||
|
Mediator.Publish(new TransientResourceChangedMessage(gameObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,32 @@ public class CharacterData
|
|||||||
public string PetNamesData { get; set; } = string.Empty;
|
public string PetNamesData { get; set; } = string.Empty;
|
||||||
public string MoodlesData { get; set; } = string.Empty;
|
public string MoodlesData { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public void SetFragment(ObjectKind kind, CharacterDataFragment? fragment)
|
||||||
|
{
|
||||||
|
if (kind == ObjectKind.Player)
|
||||||
|
{
|
||||||
|
var playerFragment = (fragment as CharacterDataFragmentPlayer);
|
||||||
|
HeelsData = playerFragment?.HeelsData ?? string.Empty;
|
||||||
|
HonorificData = playerFragment?.HonorificData ?? string.Empty;
|
||||||
|
ManipulationString = playerFragment?.ManipulationString ?? string.Empty;
|
||||||
|
MoodlesData = playerFragment?.MoodlesData ?? string.Empty;
|
||||||
|
PetNamesData = playerFragment?.PetNamesData ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragment is null)
|
||||||
|
{
|
||||||
|
CustomizePlusScale.Remove(kind);
|
||||||
|
FileReplacements.Remove(kind);
|
||||||
|
GlamourerString.Remove(kind);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CustomizePlusScale[kind] = fragment.CustomizePlusScale;
|
||||||
|
FileReplacements[kind] = fragment.FileReplacements;
|
||||||
|
GlamourerString[kind] = fragment.GlamourerString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public API.Data.CharacterData ToAPI()
|
public API.Data.CharacterData ToAPI()
|
||||||
{
|
{
|
||||||
Dictionary<ObjectKind, List<FileReplacementData>> fileReplacements =
|
Dictionary<ObjectKind, List<FileReplacementData>> fileReplacements =
|
||||||
|
|||||||
8
MareSynchronos/PlayerData/Data/CharacterDataFragment.cs
Normal file
8
MareSynchronos/PlayerData/Data/CharacterDataFragment.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace MareSynchronos.PlayerData.Data;
|
||||||
|
|
||||||
|
public class CharacterDataFragment
|
||||||
|
{
|
||||||
|
public string CustomizePlusScale { get; set; } = string.Empty;
|
||||||
|
public HashSet<FileReplacement> FileReplacements { get; set; } = [];
|
||||||
|
public string GlamourerString { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace MareSynchronos.PlayerData.Data;
|
||||||
|
|
||||||
|
public class CharacterDataFragmentPlayer : CharacterDataFragment
|
||||||
|
{
|
||||||
|
public string HeelsData { get; set; } = string.Empty;
|
||||||
|
public string HonorificData { get; set; } = string.Empty;
|
||||||
|
public string ManipulationString { get; set; } = string.Empty;
|
||||||
|
public string MoodlesData { get; set; } = string.Empty;
|
||||||
|
public string PetNamesData { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -38,14 +38,14 @@ public class PlayerDataFactory
|
|||||||
_logger.LogTrace("Creating {this}", nameof(PlayerDataFactory));
|
_logger.LogTrace("Creating {this}", nameof(PlayerDataFactory));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
public async Task<CharacterDataFragment?> BuildCharacterData(GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||||
{
|
{
|
||||||
if (!_ipcManager.Initialized)
|
if (!_ipcManager.Initialized)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Penumbra or Glamourer is not connected");
|
throw new InvalidOperationException("Penumbra or Glamourer is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerRelatedObject == null) return;
|
if (playerRelatedObject == null) return null;
|
||||||
|
|
||||||
bool pointerIsZero = true;
|
bool pointerIsZero = true;
|
||||||
try
|
try
|
||||||
@@ -69,23 +69,15 @@ public class PlayerDataFactory
|
|||||||
if (pointerIsZero)
|
if (pointerIsZero)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Pointer was zero for {objectKind}", playerRelatedObject.ObjectKind);
|
_logger.LogTrace("Pointer was zero for {objectKind}", playerRelatedObject.ObjectKind);
|
||||||
previousData.FileReplacements.Remove(playerRelatedObject.ObjectKind);
|
return null;
|
||||||
previousData.GlamourerString.Remove(playerRelatedObject.ObjectKind);
|
|
||||||
previousData.CustomizePlusScale.Remove(playerRelatedObject.ObjectKind);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var previousFileReplacements = previousData.FileReplacements.ToDictionary(d => d.Key, d => d.Value);
|
|
||||||
var previousGlamourerData = previousData.GlamourerString.ToDictionary(d => d.Key, d => d.Value);
|
|
||||||
var previousCustomize = previousData.CustomizePlusScale.ToDictionary(d => d.Key, d => d.Value);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _performanceCollector.LogPerformance(this, $"CreateCharacterData>{playerRelatedObject.ObjectKind}", async () =>
|
return await _performanceCollector.LogPerformance(this, $"CreateCharacterData>{playerRelatedObject.ObjectKind}", async () =>
|
||||||
{
|
{
|
||||||
await CreateCharacterData(previousData, playerRelatedObject, token).ConfigureAwait(false);
|
return await CreateCharacterData(playerRelatedObject, token).ConfigureAwait(false);
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -97,9 +89,7 @@ public class PlayerDataFactory
|
|||||||
_logger.LogWarning(e, "Failed to create {object} data", playerRelatedObject);
|
_logger.LogWarning(e, "Failed to create {object} data", playerRelatedObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
previousData.FileReplacements = previousFileReplacements;
|
return null;
|
||||||
previousData.GlamourerString = previousGlamourerData;
|
|
||||||
previousData.CustomizePlusScale = previousCustomize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer)
|
private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer)
|
||||||
@@ -112,32 +102,25 @@ public class PlayerDataFactory
|
|||||||
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CharacterData> CreateCharacterData(CharacterData data, GameObjectHandler playerRelatedObject, CancellationToken token)
|
private async Task<CharacterDataFragment> CreateCharacterData(GameObjectHandler playerRelatedObject, CancellationToken ct)
|
||||||
{
|
{
|
||||||
var objectKind = playerRelatedObject.ObjectKind;
|
var objectKind = playerRelatedObject.ObjectKind;
|
||||||
|
CharacterDataFragment fragment = objectKind == ObjectKind.Player ? new CharacterDataFragmentPlayer() : new();
|
||||||
|
|
||||||
_logger.LogDebug("Building character data for {obj}", playerRelatedObject);
|
_logger.LogDebug("Building character data for {obj}", playerRelatedObject);
|
||||||
|
|
||||||
if (!data.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? value))
|
|
||||||
{
|
|
||||||
data.FileReplacements[objectKind] = new(FileReplacementComparer.Instance);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
value.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
data.CustomizePlusScale.Remove(objectKind);
|
|
||||||
|
|
||||||
// wait until chara is not drawing and present so nothing spontaneously explodes
|
// wait until chara is not drawing and present so nothing spontaneously explodes
|
||||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: token).ConfigureAwait(false);
|
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct).ConfigureAwait(false);
|
||||||
int totalWaitTime = 10000;
|
int totalWaitTime = 10000;
|
||||||
while (!await _dalamudUtil.IsObjectPresentAsync(await _dalamudUtil.CreateGameObjectAsync(playerRelatedObject.Address).ConfigureAwait(false)).ConfigureAwait(false) && totalWaitTime > 0)
|
while (!await _dalamudUtil.IsObjectPresentAsync(await _dalamudUtil.CreateGameObjectAsync(playerRelatedObject.Address).ConfigureAwait(false)).ConfigureAwait(false) && totalWaitTime > 0)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Character is null but it shouldn't be, waiting");
|
_logger.LogTrace("Character is null but it shouldn't be, waiting");
|
||||||
await Task.Delay(50, token).ConfigureAwait(false);
|
await Task.Delay(50, ct).ConfigureAwait(false);
|
||||||
totalWaitTime -= 50;
|
totalWaitTime -= 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
Dictionary<string, List<ushort>>? boneIndices =
|
Dictionary<string, List<ushort>>? boneIndices =
|
||||||
objectKind != ObjectKind.Player
|
objectKind != ObjectKind.Player
|
||||||
? null
|
? null
|
||||||
@@ -151,24 +134,29 @@ public class PlayerDataFactory
|
|||||||
resolvedPaths = await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false);
|
resolvedPaths = await _ipcManager.Penumbra.GetCharacterData(_logger, playerRelatedObject).ConfigureAwait(false);
|
||||||
if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data");
|
if (resolvedPaths == null) throw new InvalidOperationException("Penumbra returned null data");
|
||||||
|
|
||||||
data.FileReplacements[objectKind] =
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
fragment.FileReplacements =
|
||||||
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
||||||
.Where(p => p.HasFileReplacement).ToHashSet();
|
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||||
data.FileReplacements[objectKind].RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !CacheMonitor.AllowedFileExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
_logger.LogDebug("== Static Replacements ==");
|
_logger.LogDebug("== Static Replacements ==");
|
||||||
foreach (var replacement in data.FileReplacements[objectKind].Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
foreach (var replacement in fragment.FileReplacements.Where(i => i.HasFileReplacement).OrderBy(i => i.GamePaths.First(), StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("=> {repl}", replacement);
|
_logger.LogDebug("=> {repl}", replacement);
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _transientResourceManager.WaitForRecording(token).ConfigureAwait(false);
|
await _transientResourceManager.WaitForRecording(ct).ConfigureAwait(false);
|
||||||
|
|
||||||
// if it's pet then it's summoner, if it's summoner we actually want to keep all filereplacements alive at all times
|
// if it's pet then it's summoner, if it's summoner we actually want to keep all filereplacements alive at all times
|
||||||
// or we get into redraw city for every change and nothing works properly
|
// or we get into redraw city for every change and nothing works properly
|
||||||
if (objectKind == ObjectKind.Pet)
|
if (objectKind == ObjectKind.Pet)
|
||||||
{
|
{
|
||||||
foreach (var item in data.FileReplacements[ObjectKind.Pet].Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths))
|
foreach (var item in fragment.FileReplacements.Where(i => i.HasFileReplacement).SelectMany(p => p.GamePaths))
|
||||||
{
|
{
|
||||||
if (_transientResourceManager.AddTransientResource(objectKind, item))
|
if (_transientResourceManager.AddTransientResource(objectKind, item))
|
||||||
{
|
{
|
||||||
@@ -176,14 +164,16 @@ public class PlayerDataFactory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogTrace("Clearing {count} Static Replacements for Pet", data.FileReplacements[ObjectKind.Pet].Count);
|
_logger.LogTrace("Clearing {count} Static Replacements for Pet", fragment.FileReplacements.Count);
|
||||||
data.FileReplacements[ObjectKind.Pet].Clear();
|
fragment.FileReplacements.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
_logger.LogDebug("Handling transient update for {obj}", playerRelatedObject);
|
_logger.LogDebug("Handling transient update for {obj}", playerRelatedObject);
|
||||||
|
|
||||||
// remove all potentially gathered paths from the transient resource manager that are resolved through static resolving
|
// remove all potentially gathered paths from the transient resource manager that are resolved through static resolving
|
||||||
_transientResourceManager.ClearTransientPaths(objectKind, data.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
|
_transientResourceManager.ClearTransientPaths(objectKind, fragment.FileReplacements.SelectMany(c => c.GamePaths).ToList());
|
||||||
|
|
||||||
// get all remaining paths and resolve them
|
// get all remaining paths and resolve them
|
||||||
var transientPaths = ManageSemiTransientData(objectKind);
|
var transientPaths = ManageSemiTransientData(objectKind);
|
||||||
@@ -193,63 +183,67 @@ public class PlayerDataFactory
|
|||||||
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal))
|
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement([.. c.Value], c.Key)).OrderBy(f => f.ResolvedPath, StringComparer.Ordinal))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("=> {repl}", replacement);
|
_logger.LogDebug("=> {repl}", replacement);
|
||||||
data.FileReplacements[objectKind].Add(replacement);
|
fragment.FileReplacements.Add(replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean up all semi transient resources that don't have any file replacement (aka null resolve)
|
// clean up all semi transient resources that don't have any file replacement (aka null resolve)
|
||||||
_transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. data.FileReplacements[objectKind]]);
|
_transientResourceManager.CleanUpSemiTransientResources(objectKind, [.. fragment.FileReplacements]);
|
||||||
|
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
// make sure we only return data that actually has file replacements
|
// make sure we only return data that actually has file replacements
|
||||||
foreach (var item in data.FileReplacements)
|
fragment.FileReplacements = new HashSet<FileReplacement>(fragment.FileReplacements.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance);
|
||||||
{
|
|
||||||
data.FileReplacements[item.Key] = new HashSet<FileReplacement>(item.Value.Where(v => v.HasFileReplacement).OrderBy(v => v.ResolvedPath, StringComparer.Ordinal), FileReplacementComparer.Instance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// gather up data from ipc
|
// gather up data from ipc
|
||||||
data.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations();
|
|
||||||
Task<string> getHeelsOffset = _ipcManager.Heels.GetOffsetAsync();
|
Task<string> getHeelsOffset = _ipcManager.Heels.GetOffsetAsync();
|
||||||
Task<string> getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address);
|
Task<string> getGlamourerData = _ipcManager.Glamourer.GetCharacterCustomizationAsync(playerRelatedObject.Address);
|
||||||
Task<string?> getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address);
|
Task<string?> getCustomizeData = _ipcManager.CustomizePlus.GetScaleAsync(playerRelatedObject.Address);
|
||||||
Task<string> getHonorificTitle = _ipcManager.Honorific.GetTitle();
|
Task<string> getHonorificTitle = _ipcManager.Honorific.GetTitle();
|
||||||
data.GlamourerString[playerRelatedObject.ObjectKind] = await getGlamourerData.ConfigureAwait(false);
|
fragment.GlamourerString = await getGlamourerData.ConfigureAwait(false);
|
||||||
_logger.LogDebug("Glamourer is now: {data}", data.GlamourerString[playerRelatedObject.ObjectKind]);
|
_logger.LogDebug("Glamourer is now: {data}", fragment.GlamourerString);
|
||||||
var customizeScale = await getCustomizeData.ConfigureAwait(false);
|
var customizeScale = await getCustomizeData.ConfigureAwait(false);
|
||||||
data.CustomizePlusScale[playerRelatedObject.ObjectKind] = customizeScale ?? string.Empty;
|
fragment.CustomizePlusScale = customizeScale ?? string.Empty;
|
||||||
_logger.LogDebug("Customize is now: {data}", data.CustomizePlusScale[playerRelatedObject.ObjectKind]);
|
_logger.LogDebug("Customize is now: {data}", fragment.CustomizePlusScale);
|
||||||
data.HonorificData = await getHonorificTitle.ConfigureAwait(false);
|
|
||||||
_logger.LogDebug("Honorific is now: {data}", data.HonorificData);
|
|
||||||
data.HeelsData = await getHeelsOffset.ConfigureAwait(false);
|
|
||||||
_logger.LogDebug("Heels is now: {heels}", data.HeelsData);
|
|
||||||
if (objectKind == ObjectKind.Player)
|
if (objectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
data.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty;
|
var playerFragment = (fragment as CharacterDataFragmentPlayer)!;
|
||||||
_logger.LogDebug("Moodles is now: {moodles}", data.MoodlesData);
|
playerFragment.ManipulationString = _ipcManager.Penumbra.GetMetaManipulations();
|
||||||
|
|
||||||
data.PetNamesData = _ipcManager.PetNames.GetLocalNames();
|
playerFragment!.HonorificData = await getHonorificTitle.ConfigureAwait(false);
|
||||||
_logger.LogDebug("Pet Nicknames is now: {petnames}", data.PetNamesData);
|
_logger.LogDebug("Honorific is now: {data}", playerFragment!.HonorificData);
|
||||||
|
|
||||||
|
playerFragment!.HeelsData = await getHeelsOffset.ConfigureAwait(false);
|
||||||
|
_logger.LogDebug("Heels is now: {heels}", playerFragment!.HeelsData);
|
||||||
|
|
||||||
|
playerFragment!.MoodlesData = await _ipcManager.Moodles.GetStatusAsync(playerRelatedObject.Address).ConfigureAwait(false) ?? string.Empty;
|
||||||
|
_logger.LogDebug("Moodles is now: {moodles}", playerFragment!.MoodlesData);
|
||||||
|
|
||||||
|
playerFragment!.PetNamesData = _ipcManager.PetNames.GetLocalNames();
|
||||||
|
_logger.LogDebug("Pet Nicknames is now: {petnames}", playerFragment!.PetNamesData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.FileReplacements.TryGetValue(objectKind, out HashSet<FileReplacement>? fileReplacements))
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var toCompute = fragment.FileReplacements.Where(f => !f.IsFileSwap).ToArray();
|
||||||
|
_logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length);
|
||||||
|
var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray());
|
||||||
|
foreach (var file in toCompute)
|
||||||
{
|
{
|
||||||
var toCompute = fileReplacements.Where(f => !f.IsFileSwap).ToArray();
|
ct.ThrowIfCancellationRequested();
|
||||||
_logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length);
|
file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty;
|
||||||
var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray());
|
}
|
||||||
foreach (var file in toCompute)
|
var removed = fragment.FileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash));
|
||||||
{
|
if (removed > 0)
|
||||||
file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty;
|
{
|
||||||
}
|
_logger.LogDebug("Removed {amount} of invalid files", removed);
|
||||||
var removed = fileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash));
|
|
||||||
if (removed > 0)
|
|
||||||
{
|
|
||||||
_logger.LogDebug("Removed {amount} of invalid files", removed);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Player)
|
if (objectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await VerifyPlayerAnimationBones(boneIndices, data, objectKind).ConfigureAwait(false);
|
await VerifyPlayerAnimationBones(boneIndices, (fragment as CharacterDataFragmentPlayer)!, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -259,10 +253,10 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
_logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds);
|
_logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(DateTime.UtcNow.Ticks - start.Ticks).TotalMilliseconds);
|
||||||
|
|
||||||
return data;
|
return fragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task VerifyPlayerAnimationBones(Dictionary<string, List<ushort>>? boneIndices, CharacterData previousData, ObjectKind objectKind)
|
private async Task VerifyPlayerAnimationBones(Dictionary<string, List<ushort>>? boneIndices, CharacterDataFragmentPlayer fragment, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (boneIndices == null) return;
|
if (boneIndices == null) return;
|
||||||
|
|
||||||
@@ -274,8 +268,10 @@ public class PlayerDataFactory
|
|||||||
if (boneIndices.All(u => u.Value.Count == 0)) return;
|
if (boneIndices.All(u => u.Value.Count == 0)) return;
|
||||||
|
|
||||||
int noValidationFailed = 0;
|
int noValidationFailed = 0;
|
||||||
foreach (var file in previousData.FileReplacements[objectKind].Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList())
|
foreach (var file in fragment.FileReplacements.Where(f => !f.IsFileSwap && f.GamePaths.First().EndsWith("pap", StringComparison.OrdinalIgnoreCase)).ToList())
|
||||||
{
|
{
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var skeletonIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false);
|
var skeletonIndices = await _dalamudUtil.RunOnFrameworkThread(() => _modelAnalyzer.GetBoneIndicesFromPap(file.Hash)).ConfigureAwait(false);
|
||||||
bool validationFailed = false;
|
bool validationFailed = false;
|
||||||
if (skeletonIndices != null)
|
if (skeletonIndices != null)
|
||||||
@@ -305,10 +301,10 @@ public class PlayerDataFactory
|
|||||||
{
|
{
|
||||||
noValidationFailed++;
|
noValidationFailed++;
|
||||||
_logger.LogDebug("Removing {file} from sent file replacements and transient data", file.ResolvedPath);
|
_logger.LogDebug("Removing {file} from sent file replacements and transient data", file.ResolvedPath);
|
||||||
previousData.FileReplacements[objectKind].Remove(file);
|
fragment.FileReplacements.Remove(file);
|
||||||
foreach (var gamePath in file.GamePaths)
|
foreach (var gamePath in file.GamePaths)
|
||||||
{
|
{
|
||||||
_transientResourceManager.RemoveTransientResource(objectKind, gamePath);
|
_transientResourceManager.RemoveTransientResource(ObjectKind.Player, gamePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using MareSynchronos.Services;
|
using MareSynchronos.Services;
|
||||||
using MareSynchronos.Services.Mediator;
|
using MareSynchronos.Services.Mediator;
|
||||||
using MareSynchronos.Utils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
||||||
@@ -10,18 +9,15 @@ using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
|
|||||||
|
|
||||||
namespace MareSynchronos.PlayerData.Handlers;
|
namespace MareSynchronos.PlayerData.Handlers;
|
||||||
|
|
||||||
public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighPriorityMediatorSubscriber
|
||||||
{
|
{
|
||||||
private readonly DalamudUtilService _dalamudUtil;
|
private readonly DalamudUtilService _dalamudUtil;
|
||||||
private readonly Func<IntPtr> _getAddress;
|
private readonly Func<IntPtr> _getAddress;
|
||||||
private readonly bool _isOwnedObject;
|
private readonly bool _isOwnedObject;
|
||||||
private readonly PerformanceCollectorService _performanceCollector;
|
private readonly PerformanceCollectorService _performanceCollector;
|
||||||
private CancellationTokenSource? _clearCts = new();
|
private byte _classJob = 0;
|
||||||
private Task? _delayedZoningTask;
|
private Task? _delayedZoningTask;
|
||||||
private bool _haltProcessing = false;
|
private bool _haltProcessing = false;
|
||||||
private bool _ignoreSendAfterRedraw = false;
|
|
||||||
private int _ptrNullCounter = 0;
|
|
||||||
private byte _classJob = 0;
|
|
||||||
private CancellationTokenSource _zoningCts = new();
|
private CancellationTokenSource _zoningCts = new();
|
||||||
|
|
||||||
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector,
|
public GameObjectHandler(ILogger<GameObjectHandler> logger, PerformanceCollectorService performanceCollector,
|
||||||
@@ -76,12 +72,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
if (msg.Address == Address)
|
if (msg.Address == Address)
|
||||||
{
|
{
|
||||||
_haltProcessing = false;
|
_haltProcessing = false;
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
_ignoreSendAfterRedraw = true;
|
|
||||||
await Task.Delay(500).ConfigureAwait(false);
|
|
||||||
_ignoreSendAfterRedraw = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -90,22 +80,23 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult();
|
_dalamudUtil.RunOnFrameworkThread(CheckAndUpdateObject).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum DrawCondition
|
public enum DrawCondition
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
|
ObjectZero,
|
||||||
DrawObjectZero,
|
DrawObjectZero,
|
||||||
RenderFlags,
|
RenderFlags,
|
||||||
ModelInSlotLoaded,
|
ModelInSlotLoaded,
|
||||||
ModelFilesInSlotLoaded
|
ModelFilesInSlotLoaded
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte RaceId { get; private set; }
|
|
||||||
public byte Gender { get; private set; }
|
|
||||||
public byte TribeId { get; private set; }
|
|
||||||
|
|
||||||
public IntPtr Address { get; private set; }
|
public IntPtr Address { get; private set; }
|
||||||
|
public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None;
|
||||||
|
public byte Gender { get; private set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
public ObjectKind ObjectKind { get; }
|
public ObjectKind ObjectKind { get; }
|
||||||
|
public byte RaceId { get; private set; }
|
||||||
|
public byte TribeId { get; private set; }
|
||||||
private byte[] CustomizeData { get; set; } = new byte[26];
|
private byte[] CustomizeData { get; set; } = new byte[26];
|
||||||
private IntPtr DrawObjectAddress { get; set; }
|
private IntPtr DrawObjectAddress { get; set; }
|
||||||
private byte[] EquipSlotData { get; set; } = new byte[40];
|
private byte[] EquipSlotData { get; set; } = new byte[40];
|
||||||
@@ -116,7 +107,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
{
|
{
|
||||||
while (await _dalamudUtil.RunOnFrameworkThread(() =>
|
while (await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||||
{
|
{
|
||||||
if (IsBeingDrawn()) return true;
|
if (_haltProcessing) CheckAndUpdateObject();
|
||||||
|
if (CurrentDrawCondition != DrawCondition.None) return true;
|
||||||
var gameObj = _dalamudUtil.CreateGameObject(Address);
|
var gameObj = _dalamudUtil.CreateGameObject(Address);
|
||||||
if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara)
|
if (gameObj is Dalamud.Game.ClientState.Objects.Types.ICharacter chara)
|
||||||
{
|
{
|
||||||
@@ -141,12 +133,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntPtr CurrentAddress()
|
|
||||||
{
|
|
||||||
_dalamudUtil.EnsureIsOnFramework();
|
|
||||||
return _getAddress.Invoke();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject()
|
public Dalamud.Game.ClientState.Objects.Types.IGameObject? GetGameObject()
|
||||||
{
|
{
|
||||||
return _dalamudUtil.CreateGameObject(Address);
|
return _dalamudUtil.CreateGameObject(Address);
|
||||||
@@ -185,15 +171,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
Address = _getAddress();
|
Address = _getAddress();
|
||||||
if (Address != IntPtr.Zero)
|
if (Address != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
_ptrNullCounter = 0;
|
|
||||||
var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject;
|
var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject;
|
||||||
DrawObjectAddress = drawObjAddr;
|
DrawObjectAddress = drawObjAddr;
|
||||||
|
CurrentDrawCondition = DrawCondition.None;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DrawObjectAddress = IntPtr.Zero;
|
DrawObjectAddress = IntPtr.Zero;
|
||||||
|
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
||||||
|
|
||||||
if (_haltProcessing) return;
|
if (_haltProcessing) return;
|
||||||
|
|
||||||
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
||||||
@@ -201,12 +190,6 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
if (_clearCts != null)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("[{this}] Cancelling Clear Task", this);
|
|
||||||
_clearCts.CancelDispose();
|
|
||||||
_clearCts = null;
|
|
||||||
}
|
|
||||||
var chara = (Character*)Address;
|
var chara = (Character*)Address;
|
||||||
var name = chara->GameObject.NameString;
|
var name = chara->GameObject.NameString;
|
||||||
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
|
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
|
||||||
@@ -244,7 +227,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
|
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (equipDiff && !_isOwnedObject && !_ignoreSendAfterRedraw) // send the message out immediately and cancel out, no reason to continue if not self
|
if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self
|
||||||
{
|
{
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
Mediator.Publish(new CharacterChangedMessage(this));
|
Mediator.Publish(new CharacterChangedMessage(this));
|
||||||
@@ -288,26 +271,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
else if (addrDiff || drawObjDiff)
|
else if (addrDiff || drawObjDiff)
|
||||||
{
|
{
|
||||||
|
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
||||||
{
|
{
|
||||||
_clearCts?.CancelDispose();
|
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
||||||
_clearCts = new();
|
|
||||||
var token = _clearCts.Token;
|
|
||||||
_ = Task.Run(() => ClearAsync(token), token);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ClearAsync(CancellationToken token)
|
|
||||||
{
|
|
||||||
Logger.LogDebug("[{this}] Running Clear Task", this);
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
|
||||||
Logger.LogDebug("[{this}] Sending ClearCachedForObjectMessage", this);
|
|
||||||
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
|
||||||
_clearCts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
||||||
{
|
{
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
@@ -382,31 +354,9 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe IntPtr GetDrawObjUnsafe(nint curPtr)
|
|
||||||
{
|
|
||||||
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsBeingDrawn()
|
private bool IsBeingDrawn()
|
||||||
{
|
{
|
||||||
var curPtr = _getAddress();
|
if (_haltProcessing) CheckAndUpdateObject();
|
||||||
Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr: {ptr}", this, curPtr.ToString("X"));
|
|
||||||
|
|
||||||
if (curPtr == IntPtr.Zero && _ptrNullCounter < 2)
|
|
||||||
{
|
|
||||||
Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr is ZERO, counter is {cnt}", this, _ptrNullCounter);
|
|
||||||
_ptrNullCounter++;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curPtr == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr is ZERO, returning", this);
|
|
||||||
|
|
||||||
Address = IntPtr.Zero;
|
|
||||||
DrawObjectAddress = IntPtr.Zero;
|
|
||||||
throw new ArgumentNullException($"CurPtr for {this} turned ZERO");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_dalamudUtil.IsAnythingDrawing)
|
if (_dalamudUtil.IsAnythingDrawing)
|
||||||
{
|
{
|
||||||
@@ -414,27 +364,23 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var drawObj = GetDrawObjUnsafe(curPtr);
|
Logger.LogTrace("[{this}] IsBeingDrawn, Condition: {cond}", this, CurrentDrawCondition);
|
||||||
Logger.LogTrace("[{this}] IsBeingDrawn, DrawObjPtr: {ptr}", this, drawObj.ToString("X"));
|
return CurrentDrawCondition != DrawCondition.None;
|
||||||
var isDrawn = IsBeingDrawnUnsafe(drawObj, curPtr);
|
|
||||||
Logger.LogTrace("[{this}] IsBeingDrawn, Condition: {cond}", this, isDrawn);
|
|
||||||
return isDrawn != DrawCondition.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe DrawCondition IsBeingDrawnUnsafe(IntPtr drawObj, IntPtr curPtr)
|
private unsafe DrawCondition IsBeingDrawnUnsafe()
|
||||||
{
|
{
|
||||||
var drawObjZero = drawObj == IntPtr.Zero;
|
if (Address == IntPtr.Zero) return DrawCondition.ObjectZero;
|
||||||
if (drawObjZero) return DrawCondition.DrawObjectZero;
|
if (DrawObjectAddress == IntPtr.Zero) return DrawCondition.DrawObjectZero;
|
||||||
var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags) != 0x0;
|
var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->RenderFlags) != 0x0;
|
||||||
if (renderFlags) return DrawCondition.RenderFlags;
|
if (renderFlags) return DrawCondition.RenderFlags;
|
||||||
|
|
||||||
if (ObjectKind == ObjectKind.Player)
|
if (ObjectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
var modelInSlotLoaded = (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0);
|
var modelInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelInSlotLoaded != 0);
|
||||||
if (modelInSlotLoaded) return DrawCondition.ModelInSlotLoaded;
|
if (modelInSlotLoaded) return DrawCondition.ModelInSlotLoaded;
|
||||||
var modelFilesInSlotLoaded = (((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0);
|
var modelFilesInSlotLoaded = (((CharacterBase*)DrawObjectAddress)->HasModelFilesInSlotLoaded != 0);
|
||||||
if (modelFilesInSlotLoaded) return DrawCondition.ModelFilesInSlotLoaded;
|
if (modelFilesInSlotLoaded) return DrawCondition.ModelFilesInSlotLoaded;
|
||||||
return DrawCondition.None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DrawCondition.None;
|
return DrawCondition.None;
|
||||||
@@ -442,11 +388,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void ZoneSwitchEnd()
|
private void ZoneSwitchEnd()
|
||||||
{
|
{
|
||||||
if (!_isOwnedObject || _haltProcessing) return;
|
if (!_isOwnedObject) return;
|
||||||
|
|
||||||
_clearCts?.Cancel();
|
|
||||||
_clearCts?.Dispose();
|
|
||||||
_clearCts = null;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_zoningCts?.CancelAfter(2500);
|
_zoningCts?.CancelAfter(2500);
|
||||||
@@ -463,7 +406,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
private void ZoneSwitchStart()
|
private void ZoneSwitchStart()
|
||||||
{
|
{
|
||||||
if (!_isOwnedObject || _haltProcessing) return;
|
if (!_isOwnedObject) return;
|
||||||
|
|
||||||
_zoningCts = new();
|
_zoningCts = new();
|
||||||
Logger.LogDebug("[{obj}] Starting Delay After Zoning", this);
|
Logger.LogDebug("[{obj}] Starting Delay After Zoning", this);
|
||||||
|
|||||||
@@ -8,37 +8,26 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace MareSynchronos.PlayerData.Services;
|
namespace MareSynchronos.PlayerData.Services;
|
||||||
|
|
||||||
#pragma warning disable MA0040
|
|
||||||
|
|
||||||
public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _cacheCreateLock = new(1);
|
private readonly SemaphoreSlim _cacheCreateLock = new(1);
|
||||||
private readonly Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = [];
|
private readonly HashSet<ObjectKind> _cachesToCreate = [];
|
||||||
private readonly PlayerDataFactory _characterDataFactory;
|
private readonly PlayerDataFactory _characterDataFactory;
|
||||||
private readonly CancellationTokenSource _cts = new();
|
private readonly HashSet<ObjectKind> _currentlyCreating = [];
|
||||||
|
private readonly HashSet<ObjectKind> _debouncedObjectCache = [];
|
||||||
private readonly CharacterData _playerData = new();
|
private readonly CharacterData _playerData = new();
|
||||||
private readonly Dictionary<ObjectKind, GameObjectHandler> _playerRelatedObjects = [];
|
private readonly Dictionary<ObjectKind, GameObjectHandler> _playerRelatedObjects = [];
|
||||||
private Task? _cacheCreationTask;
|
private readonly CancellationTokenSource _runtimeCts = new();
|
||||||
private CancellationTokenSource _honorificCts = new();
|
private CancellationTokenSource _creationCts = new();
|
||||||
private CancellationTokenSource _petNicknamesCts = new();
|
private CancellationTokenSource _debounceCts = new();
|
||||||
private CancellationTokenSource _moodlesCts = new();
|
|
||||||
private bool _isZoning = false;
|
|
||||||
private bool _haltCharaDataCreation;
|
private bool _haltCharaDataCreation;
|
||||||
private readonly Dictionary<ObjectKind, CancellationTokenSource> _glamourerCts = new();
|
private bool _isZoning = false;
|
||||||
|
|
||||||
public CacheCreationService(ILogger<CacheCreationService> logger, MareMediator mediator, GameObjectHandlerFactory gameObjectHandlerFactory,
|
public CacheCreationService(ILogger<CacheCreationService> logger, MareMediator mediator, GameObjectHandlerFactory gameObjectHandlerFactory,
|
||||||
PlayerDataFactory characterDataFactory, DalamudUtilService dalamudUtil) : base(logger, mediator)
|
PlayerDataFactory characterDataFactory, DalamudUtilService dalamudUtil) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_characterDataFactory = characterDataFactory;
|
_characterDataFactory = characterDataFactory;
|
||||||
|
|
||||||
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
|
|
||||||
{
|
|
||||||
Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor);
|
|
||||||
_cacheCreateLock.Wait();
|
|
||||||
_cachesToCreate[msg.ObjectToCreateFor.ObjectKind] = msg.ObjectToCreateFor;
|
|
||||||
_cacheCreateLock.Release();
|
|
||||||
});
|
|
||||||
|
|
||||||
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (msg) => _isZoning = true);
|
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (msg) => _isZoning = true);
|
||||||
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (msg) => _isZoning = false);
|
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (msg) => _isZoning = false);
|
||||||
|
|
||||||
@@ -47,6 +36,12 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
|||||||
_haltCharaDataCreation = !msg.Resume;
|
_haltCharaDataCreation = !msg.Resume;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor);
|
||||||
|
AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind);
|
||||||
|
});
|
||||||
|
|
||||||
_playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, dalamudUtil.GetPlayerPointer, isWatched: true)
|
_playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, dalamudUtil.GetPlayerPointer, isWatched: true)
|
||||||
.GetAwaiter().GetResult();
|
.GetAwaiter().GetResult();
|
||||||
_playerRelatedObjects[ObjectKind.MinionOrMount] = gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => dalamudUtil.GetMinionOrMount(), isWatched: true)
|
_playerRelatedObjects[ObjectKind.MinionOrMount] = gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => dalamudUtil.GetMinionOrMount(), isWatched: true)
|
||||||
@@ -58,77 +53,64 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
|
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (msg.GameObjectHandler != _playerRelatedObjects[ObjectKind.Player]) return;
|
if (msg.GameObjectHandler == _playerRelatedObjects[ObjectKind.Player])
|
||||||
|
{
|
||||||
Logger.LogTrace("Removing pet data for {obj}", msg.GameObjectHandler);
|
AddCacheToCreate(ObjectKind.Player);
|
||||||
_playerData.FileReplacements.Remove(ObjectKind.Pet);
|
AddCacheToCreate(ObjectKind.Pet);
|
||||||
_playerData.GlamourerString.Remove(ObjectKind.Pet);
|
}
|
||||||
_playerData.CustomizePlusScale.Remove(ObjectKind.Pet);
|
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<ClearCacheForObjectMessage>(this, (msg) =>
|
Mediator.Subscribe<ClearCacheForObjectMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
// ignore pets
|
if (msg.ObjectToCreateFor.ObjectKind == ObjectKind.Pet)
|
||||||
if (msg.ObjectToCreateFor == _playerRelatedObjects[ObjectKind.Pet]) return;
|
|
||||||
_ = Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor);
|
Logger.LogTrace("Received clear cache for {obj}, ignoring", msg.ObjectToCreateFor);
|
||||||
_playerData.FileReplacements.Remove(msg.ObjectToCreateFor.ObjectKind);
|
return;
|
||||||
_playerData.GlamourerString.Remove(msg.ObjectToCreateFor.ObjectKind);
|
}
|
||||||
_playerData.CustomizePlusScale.Remove(msg.ObjectToCreateFor.ObjectKind);
|
Logger.LogDebug("Clearing cache for {obj}", msg.ObjectToCreateFor);
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) =>
|
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_isZoning) return;
|
if (_isZoning) return;
|
||||||
_ = Task.Run(async () =>
|
foreach (var item in _playerRelatedObjects
|
||||||
|
.Where(item => msg.Address == null
|
||||||
|
|| item.Value.Address == msg.Address).Select(k => k.Key))
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug("Received CustomizePlus change, updating {obj}", item);
|
||||||
foreach (var item in _playerRelatedObjects
|
AddCacheToCreate(item);
|
||||||
.Where(item => msg.Address == null
|
}
|
||||||
|| item.Value.Address == msg.Address).Select(k => k.Key))
|
|
||||||
{
|
|
||||||
Logger.LogDebug("Received CustomizePlus change, updating {obj}", item);
|
|
||||||
await AddPlayerCacheToCreate(item).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) =>
|
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_isZoning) return;
|
if (_isZoning) return;
|
||||||
Logger.LogDebug("Received Heels Offset change, updating player");
|
Logger.LogDebug("Received Heels Offset change, updating player");
|
||||||
_ = AddPlayerCacheToCreate();
|
AddCacheToCreate();
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<GlamourerChangedMessage>(this, (msg) =>
|
Mediator.Subscribe<GlamourerChangedMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_isZoning) return;
|
if (_isZoning) return;
|
||||||
var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address);
|
var changedType = _playerRelatedObjects.FirstOrDefault(f => f.Value.Address == msg.Address);
|
||||||
if (changedType.Key != default || changedType.Value != default)
|
if (changedType.Key != default || changedType.Value != default)
|
||||||
{
|
{
|
||||||
GlamourerChanged(changedType.Key);
|
Logger.LogDebug("Received GlamourerChangedMessage for {kind}", changedType);
|
||||||
|
AddCacheToCreate(changedType.Key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<HonorificMessage>(this, (msg) =>
|
Mediator.Subscribe<HonorificMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_isZoning) return;
|
if (_isZoning) return;
|
||||||
if (!string.Equals(msg.NewHonorificTitle, _playerData.HonorificData, StringComparison.Ordinal))
|
if (!string.Equals(msg.NewHonorificTitle, _playerData.HonorificData, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Received Honorific change, updating player");
|
Logger.LogDebug("Received Honorific change, updating player");
|
||||||
HonorificChanged();
|
AddCacheToCreate(ObjectKind.Player);
|
||||||
}
|
|
||||||
});
|
|
||||||
Mediator.Subscribe<PetNamesMessage>(this, (msg) =>
|
|
||||||
{
|
|
||||||
if (_isZoning) return;
|
|
||||||
if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
Logger.LogDebug("Received Pet Nicknames change, updating player");
|
|
||||||
PetNicknamesChanged();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<MoodlesMessage>(this, (msg) =>
|
Mediator.Subscribe<MoodlesMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
if (_isZoning) return;
|
if (_isZoning) return;
|
||||||
@@ -136,16 +118,30 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
|||||||
if (changedType.Key == ObjectKind.Player && changedType.Value != default)
|
if (changedType.Key == ObjectKind.Player && changedType.Value != default)
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Received Moodles change, updating player");
|
Logger.LogDebug("Received Moodles change, updating player");
|
||||||
MoodlesChanged();
|
AddCacheToCreate(ObjectKind.Player);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (msg) =>
|
|
||||||
|
Mediator.Subscribe<PetNamesMessage>(this, (msg) =>
|
||||||
{
|
{
|
||||||
Logger.LogDebug("Received Penumbra Mod settings change, updating player");
|
if (_isZoning) return;
|
||||||
AddPlayerCacheToCreate().GetAwaiter().GetResult();
|
if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Received Pet Nicknames change, updating player");
|
||||||
|
AddCacheToCreate(ObjectKind.Player);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Received Penumbra Mod settings change, updating everything");
|
||||||
|
AddCacheToCreate(ObjectKind.Player);
|
||||||
|
AddCacheToCreate(ObjectKind.Pet);
|
||||||
|
AddCacheToCreate(ObjectKind.MinionOrMount);
|
||||||
|
AddCacheToCreate(ObjectKind.Companion);
|
||||||
|
});
|
||||||
|
|
||||||
|
Mediator.Subscribe<FrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -153,111 +149,95 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
|
|||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
|
|
||||||
_playerRelatedObjects.Values.ToList().ForEach(p => p.Dispose());
|
_playerRelatedObjects.Values.ToList().ForEach(p => p.Dispose());
|
||||||
_cts.Dispose();
|
_runtimeCts.Cancel();
|
||||||
|
_runtimeCts.Dispose();
|
||||||
|
_creationCts.Cancel();
|
||||||
|
_creationCts.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddPlayerCacheToCreate(ObjectKind kind = ObjectKind.Player)
|
private void AddCacheToCreate(ObjectKind kind = ObjectKind.Player)
|
||||||
{
|
{
|
||||||
await _cacheCreateLock.WaitAsync().ConfigureAwait(false);
|
_debounceCts.Cancel();
|
||||||
_cachesToCreate[kind] = _playerRelatedObjects[kind];
|
_debounceCts.Dispose();
|
||||||
|
_debounceCts = new();
|
||||||
|
var token = _debounceCts.Token;
|
||||||
|
_cacheCreateLock.Wait();
|
||||||
|
_debouncedObjectCache.Add(kind);
|
||||||
_cacheCreateLock.Release();
|
_cacheCreateLock.Release();
|
||||||
}
|
|
||||||
|
|
||||||
private void GlamourerChanged(ObjectKind kind)
|
|
||||||
{
|
|
||||||
if (_glamourerCts.TryGetValue(kind, out var cts))
|
|
||||||
{
|
|
||||||
_glamourerCts[kind]?.Cancel();
|
|
||||||
_glamourerCts[kind]?.Dispose();
|
|
||||||
}
|
|
||||||
_glamourerCts[kind] = new();
|
|
||||||
var token = _glamourerCts[kind].Token;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await Task.Delay(TimeSpan.FromMilliseconds(500), token).ConfigureAwait(false);
|
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
||||||
await AddPlayerCacheToCreate(kind).ConfigureAwait(false);
|
Logger.LogTrace("Debounce complete, inserting objects to create for: {obj}", string.Join(", ", _debouncedObjectCache));
|
||||||
|
await _cacheCreateLock.WaitAsync(token).ConfigureAwait(false);
|
||||||
|
foreach (var item in _debouncedObjectCache)
|
||||||
|
{
|
||||||
|
_cachesToCreate.Add(item);
|
||||||
|
}
|
||||||
|
_debouncedObjectCache.Clear();
|
||||||
|
_cacheCreateLock.Release();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HonorificChanged()
|
|
||||||
{
|
|
||||||
_honorificCts?.Cancel();
|
|
||||||
_honorificCts?.Dispose();
|
|
||||||
_honorificCts = new();
|
|
||||||
var token = _honorificCts.Token;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
|
||||||
await AddPlayerCacheToCreate().ConfigureAwait(false);
|
|
||||||
}, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PetNicknamesChanged()
|
|
||||||
{
|
|
||||||
_petNicknamesCts?.Cancel();
|
|
||||||
_petNicknamesCts?.Dispose();
|
|
||||||
_petNicknamesCts = new();
|
|
||||||
var token = _petNicknamesCts.Token;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
|
||||||
await AddPlayerCacheToCreate().ConfigureAwait(false);
|
|
||||||
}, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void MoodlesChanged()
|
|
||||||
{
|
|
||||||
_moodlesCts?.Cancel();
|
|
||||||
_moodlesCts?.Dispose();
|
|
||||||
_moodlesCts = new();
|
|
||||||
var token = _moodlesCts.Token;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
|
|
||||||
await AddPlayerCacheToCreate().ConfigureAwait(false);
|
|
||||||
}, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessCacheCreation()
|
private void ProcessCacheCreation()
|
||||||
{
|
{
|
||||||
if (_isZoning || _haltCharaDataCreation) return;
|
if (_isZoning || _haltCharaDataCreation) return;
|
||||||
|
|
||||||
if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true))
|
if (_cachesToCreate.Count == 0) return;
|
||||||
{
|
|
||||||
_cacheCreateLock.Wait();
|
|
||||||
var toCreate = _cachesToCreate.ToList();
|
|
||||||
_cachesToCreate.Clear();
|
|
||||||
_cacheCreateLock.Release();
|
|
||||||
|
|
||||||
_cacheCreationTask = Task.Run(async () =>
|
if (_playerRelatedObjects.Any(p => p.Value.CurrentDrawCondition is
|
||||||
|
not (GameObjectHandler.DrawCondition.None or GameObjectHandler.DrawCondition.DrawObjectZero or GameObjectHandler.DrawCondition.ObjectZero)))
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Waiting for draw to finish before executing cache creation");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_creationCts.Cancel();
|
||||||
|
_creationCts.Dispose();
|
||||||
|
_creationCts = new();
|
||||||
|
_cacheCreateLock.Wait();
|
||||||
|
var objectKindsToCreate = _cachesToCreate.ToList();
|
||||||
|
foreach (var creationObj in objectKindsToCreate)
|
||||||
|
{
|
||||||
|
_currentlyCreating.Add(creationObj);
|
||||||
|
}
|
||||||
|
_cachesToCreate.Clear();
|
||||||
|
_cacheCreateLock.Release();
|
||||||
|
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_creationCts.Token, _runtimeCts.Token);
|
||||||
|
|
||||||
|
Logger.LogDebug("Creating Caches for {objectKinds}", string.Join(", ", objectKindsToCreate));
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
Dictionary<ObjectKind, CharacterDataFragment?> createdData = [];
|
||||||
|
foreach (var objectKind in objectKindsToCreate)
|
||||||
{
|
{
|
||||||
foreach (var obj in toCreate)
|
createdData[objectKind] = await _characterDataFactory.BuildCharacterData(_playerRelatedObjects[objectKind], linkedCts.Token).ConfigureAwait(false);
|
||||||
{
|
}
|
||||||
await _characterDataFactory.BuildCharacterData(_playerData, obj.Value, _cts.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
foreach (var kvp in createdData)
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
Logger.LogCritical(ex, "Error during Cache Creation Processing");
|
_playerData.SetFragment(kvp.Key, kvp.Value);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
|
||||||
Logger.LogDebug("Cache Creation complete");
|
_currentlyCreating.Clear();
|
||||||
}
|
}
|
||||||
}, _cts.Token);
|
catch (OperationCanceledException)
|
||||||
}
|
{
|
||||||
else if (_cachesToCreate.Any())
|
Logger.LogDebug("Cache Creation cancelled");
|
||||||
{
|
}
|
||||||
Logger.LogDebug("Cache Creation stored until previous creation finished");
|
catch (Exception ex)
|
||||||
}
|
{
|
||||||
|
Logger.LogCritical(ex, "Error during Cache Creation Processing");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Logger.LogDebug("Cache Creation complete");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#pragma warning restore MA0040
|
|
||||||
@@ -150,6 +150,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddSingleton<UidDisplayHandler>();
|
collection.AddSingleton<UidDisplayHandler>();
|
||||||
collection.AddSingleton<PluginWatcherService>();
|
collection.AddSingleton<PluginWatcherService>();
|
||||||
collection.AddSingleton<PlayerPerformanceService>();
|
collection.AddSingleton<PlayerPerformanceService>();
|
||||||
|
collection.AddSingleton<TransientResourceManager>();
|
||||||
|
|
||||||
collection.AddSingleton<CharaDataManager>();
|
collection.AddSingleton<CharaDataManager>();
|
||||||
collection.AddSingleton<CharaDataFileHandler>();
|
collection.AddSingleton<CharaDataFileHandler>();
|
||||||
@@ -224,7 +225,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
collection.AddScoped<IPopupHandler, ReportPopupHandler>();
|
collection.AddScoped<IPopupHandler, ReportPopupHandler>();
|
||||||
collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
|
||||||
collection.AddScoped<CacheCreationService>();
|
collection.AddScoped<CacheCreationService>();
|
||||||
collection.AddScoped<TransientResourceManager>();
|
|
||||||
collection.AddScoped<PlayerDataFactory>();
|
collection.AddScoped<PlayerDataFactory>();
|
||||||
collection.AddScoped<OnlinePlayerManager>();
|
collection.AddScoped<OnlinePlayerManager>();
|
||||||
collection.AddScoped<UiService>();
|
collection.AddScoped<UiService>();
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ public sealed class CharaDataFileHandler : IDisposable
|
|||||||
using var tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player,
|
using var tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Player,
|
||||||
() => _dalamudUtilService.GetCharacterFromObjectTableByIndex(chara.ObjectIndex)?.Address ?? IntPtr.Zero, isWatched: false).ConfigureAwait(false);
|
() => _dalamudUtilService.GetCharacterFromObjectTableByIndex(chara.ObjectIndex)?.Address ?? IntPtr.Zero, isWatched: false).ConfigureAwait(false);
|
||||||
PlayerData.Data.CharacterData newCdata = new();
|
PlayerData.Data.CharacterData newCdata = new();
|
||||||
await _playerDataFactory.BuildCharacterData(newCdata, tempHandler, CancellationToken.None).ConfigureAwait(false);
|
var fragment = await _playerDataFactory.BuildCharacterData(tempHandler, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
newCdata.SetFragment(ObjectKind.Player, fragment);
|
||||||
if (newCdata.FileReplacements.TryGetValue(ObjectKind.Player, out var playerData) && playerData != null)
|
if (newCdata.FileReplacements.TryGetValue(ObjectKind.Player, out var playerData) && playerData != null)
|
||||||
{
|
{
|
||||||
foreach (var data in playerData.Select(g => g.GamePaths))
|
foreach (var data in playerData.Select(g => g.GamePaths))
|
||||||
|
|||||||
@@ -493,15 +493,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
{
|
{
|
||||||
if (!_clientState.IsLoggedIn) return;
|
if (!_clientState.IsLoggedIn) return;
|
||||||
|
|
||||||
|
if (ct == null)
|
||||||
|
ct = CancellationToken.None;
|
||||||
|
|
||||||
const int tick = 250;
|
const int tick = 250;
|
||||||
int curWaitTime = 0;
|
int curWaitTime = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler);
|
logger.LogTrace("[{redrawId}] Starting wait for {handler} to draw", redrawId, handler);
|
||||||
await Task.Delay(tick).ConfigureAwait(true);
|
await Task.Delay(tick, ct.Value).ConfigureAwait(true);
|
||||||
curWaitTime += tick;
|
curWaitTime += tick;
|
||||||
|
|
||||||
while ((!ct?.IsCancellationRequested ?? true)
|
while ((!ct.Value.IsCancellationRequested)
|
||||||
&& curWaitTime < timeOut
|
&& curWaitTime < timeOut
|
||||||
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
|
&& await handler.IsBeingDrawnRunOnFrameworkAsync().ConfigureAwait(false)) // 0b100000000000 is "still rendering" or something
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace MareSynchronos.Services.Mediator;
|
||||||
|
|
||||||
|
public interface IHighPriorityMediatorSubscriber : IMediatorSubscriber { }
|
||||||
@@ -91,6 +91,7 @@ public sealed class MareMediator : IHostedService
|
|||||||
{
|
{
|
||||||
_messageQueue.Clear();
|
_messageQueue.Clear();
|
||||||
_loopCts.Cancel();
|
_loopCts.Cancel();
|
||||||
|
_loopCts.Dispose();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ public sealed class MareMediator : IHostedService
|
|||||||
List<SubscriberAction> subscribersCopy = [];
|
List<SubscriberAction> subscribersCopy = [];
|
||||||
lock (_addRemoveLock)
|
lock (_addRemoveLock)
|
||||||
{
|
{
|
||||||
subscribersCopy = subscribers?.Where(s => s.Subscriber != null).ToList() ?? [];
|
subscribersCopy = subscribers?.Where(s => s.Subscriber != null).OrderBy(k => k.Subscriber is IHighPriorityMediatorSubscriber ? 0 : 1).ToList() ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||||
@@ -165,7 +166,7 @@ public sealed class MareMediator : IHostedService
|
|||||||
if (!_genericExecuteMethods.TryGetValue((msgType, message.SubscriberKey), out var methodInfo))
|
if (!_genericExecuteMethods.TryGetValue((msgType, message.SubscriberKey), out var methodInfo))
|
||||||
{
|
{
|
||||||
_genericExecuteMethods[(msgType, message.SubscriberKey)] = methodInfo = GetType()
|
_genericExecuteMethods[(msgType, message.SubscriberKey)] = methodInfo = GetType()
|
||||||
.GetMethod(nameof(ExecuteReflected), System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)?
|
.GetMethod(nameof(ExecuteReflected), BindingFlags.NonPublic | BindingFlags.Instance)?
|
||||||
.MakeGenericMethod(msgType);
|
.MakeGenericMethod(msgType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ public record HaltScanMessage(string Source) : MessageBase;
|
|||||||
public record ResumeScanMessage(string Source) : MessageBase;
|
public record ResumeScanMessage(string Source) : MessageBase;
|
||||||
public record NotificationMessage
|
public record NotificationMessage
|
||||||
(string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase;
|
(string Title, string Message, NotificationType Type, TimeSpan? TimeShownOnScreen = null) : MessageBase;
|
||||||
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase;
|
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
||||||
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : MessageBase;
|
public record ClearCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : SameThreadMessage;
|
||||||
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
|
public record CharacterDataCreatedMessage(CharacterData CharacterData) : SameThreadMessage;
|
||||||
public record CharacterDataAnalyzedMessage : MessageBase;
|
public record CharacterDataAnalyzedMessage : MessageBase;
|
||||||
public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase;
|
public record PenumbraStartRedrawMessage(IntPtr Address) : MessageBase;
|
||||||
|
|||||||
Reference in New Issue
Block a user