rework cache creation conditions

This commit is contained in:
Stanley Dimant
2025-02-23 03:04:08 +01:00
parent 3a481f606b
commit 17ae9633e8
11 changed files with 264 additions and 271 deletions

View File

@@ -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);
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)); Mediator.Publish(new TransientResourceChangedMessage(gameObject));
}
}
}); });
} }

View File

@@ -15,6 +15,32 @@ public class CharacterData
public string MoodlesData { get; set; } = string.Empty; public string MoodlesData { get; set; } = string.Empty;
public string PetNamesData { get; set; } = string.Empty; public string PetNamesData { 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 =

View 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;
}

View File

@@ -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;
}

View File

@@ -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 = fileReplacements.Where(f => !f.IsFileSwap).ToArray(); var toCompute = fragment.FileReplacements.Where(f => !f.IsFileSwap).ToArray();
_logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length); _logger.LogDebug("Getting Hashes for {amount} Files", toCompute.Length);
var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray()); var computedPaths = _fileCacheManager.GetFileCachesByPaths(toCompute.Select(c => c.ResolvedPath).ToArray());
foreach (var file in toCompute) foreach (var file in toCompute)
{ {
ct.ThrowIfCancellationRequested();
file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty; file.Hash = computedPaths[file.ResolvedPath]?.Hash ?? string.Empty;
} }
var removed = fileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash)); var removed = fragment.FileReplacements.RemoveWhere(f => !f.IsFileSwap && string.IsNullOrEmpty(f.Hash));
if (removed > 0) if (removed > 0)
{ {
_logger.LogDebug("Removed {amount} of invalid files", removed); _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);
} }
} }

View File

@@ -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;
@@ -16,10 +15,8 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
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 Task? _delayedZoningTask; private Task? _delayedZoningTask;
private bool _haltProcessing = false; private bool _haltProcessing = false;
private bool _ignoreSendAfterRedraw = false;
private int _ptrNullCounter = 0; private int _ptrNullCounter = 0;
private byte _classJob = 0; private byte _classJob = 0;
private CancellationTokenSource _zoningCts = new(); private CancellationTokenSource _zoningCts = new();
@@ -76,12 +73,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,9 +81,10 @@ 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,
@@ -112,11 +104,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private ushort[] MainHandData { get; set; } = new ushort[3]; private ushort[] MainHandData { get; set; } = new ushort[3];
private ushort[] OffHandData { get; set; } = new ushort[3]; private ushort[] OffHandData { get; set; } = new ushort[3];
public DrawCondition CurrentDrawCondition { get; set; } = DrawCondition.None;
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.ICharacter> act, CancellationToken token) public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.ICharacter> act, CancellationToken token)
{ {
while (await _dalamudUtil.RunOnFrameworkThread(() => while (await _dalamudUtil.RunOnFrameworkThread(() =>
{ {
if (IsBeingDrawn()) return true; 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)
{ {
@@ -188,12 +182,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
_ptrNullCounter = 0; _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 +199,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 +236,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,24 +280,13 @@ 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();
_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)); Mediator.Publish(new ClearCacheForObjectMessage(this));
_clearCts = null; }
}
} }
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData) private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
@@ -387,7 +368,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject; return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject;
} }
private bool IsBeingDrawn() public bool IsBeingDrawn()
{ {
var curPtr = _getAddress(); var curPtr = _getAddress();
Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr: {ptr}", this, curPtr.ToString("X")); Logger.LogTrace("[{this}] IsBeingDrawn, CurPtr: {ptr}", this, curPtr.ToString("X"));
@@ -414,27 +395,22 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
return true; return true;
} }
var drawObj = GetDrawObjUnsafe(curPtr); return CurrentDrawCondition != DrawCondition.None;
Logger.LogTrace("[{this}] IsBeingDrawn, DrawObjPtr: {ptr}", this, drawObj.ToString("X"));
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 +418,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 +436,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);

View File

@@ -8,37 +8,24 @@ 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 CancellationTokenSource _runtimeCts = new();
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 CancellationTokenSource _debounceCts = new();
private CancellationTokenSource _honorificCts = new(); private readonly HashSet<ObjectKind> _debouncedObjectCache = [];
private CancellationTokenSource _moodlesCts = new();
private CancellationTokenSource _petNicknamesCts = new();
private bool _isZoning = false; private bool _isZoning = false;
private bool _haltCharaDataCreation; private bool _haltCharaDataCreation;
private readonly Dictionary<ObjectKind, CancellationTokenSource> _glamourerCts = new();
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);
@@ -56,70 +43,58 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
_playerRelatedObjects[ObjectKind.Companion] = gameObjectHandlerFactory.Create(ObjectKind.Companion, () => dalamudUtil.GetCompanion(), isWatched: true) _playerRelatedObjects[ObjectKind.Companion] = gameObjectHandlerFactory.Create(ObjectKind.Companion, () => dalamudUtil.GetCompanion(), isWatched: true)
.GetAwaiter().GetResult(); .GetAwaiter().GetResult();
Mediator.Subscribe<ClassJobChangedMessage>(this, (msg) => Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
{ {
if (msg.GameObjectHandler != _playerRelatedObjects[ObjectKind.Player]) return; Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor);
AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind);
Logger.LogTrace("Removing pet data for {obj}", msg.GameObjectHandler);
_playerData.FileReplacements.Remove(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 Logger.LogDebug("Clearing cache for {obj}", msg.ObjectToCreateFor);
if (msg.ObjectToCreateFor == _playerRelatedObjects[ObjectKind.Pet]) return; AddCacheToCreate(msg.ObjectToCreateFor.ObjectKind);
_ = Task.Run(() =>
{
Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor);
_playerData.FileReplacements.Remove(msg.ObjectToCreateFor.ObjectKind);
_playerData.GlamourerString.Remove(msg.ObjectToCreateFor.ObjectKind);
_playerData.CustomizePlusScale.Remove(msg.ObjectToCreateFor.ObjectKind);
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
});
}); });
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => Mediator.Subscribe<CustomizePlusMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
_ = Task.Run(async () =>
{
foreach (var item in _playerRelatedObjects foreach (var item in _playerRelatedObjects
.Where(item => msg.Address == null .Where(item => msg.Address == null
|| item.Value.Address == msg.Address).Select(k => k.Key)) || item.Value.Address == msg.Address).Select(k => k.Key))
{ {
Logger.LogDebug("Received CustomizePlus change, updating {obj}", item); Logger.LogDebug("Received CustomizePlus change, updating {obj}", item);
await AddPlayerCacheToCreate(item).ConfigureAwait(false); AddCacheToCreate(item);
} }
}); });
});
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 (!default(KeyValuePair<ObjectKind, GameObjectHandler>).Equals(changedType)) if (!default(KeyValuePair<ObjectKind, GameObjectHandler>).Equals(changedType))
{ {
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<MoodlesMessage>(this, (msg) => Mediator.Subscribe<MoodlesMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
@@ -127,25 +102,31 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
if (!default(KeyValuePair<ObjectKind, GameObjectHandler>).Equals(changedType) && changedType.Key == ObjectKind.Player) if (!default(KeyValuePair<ObjectKind, GameObjectHandler>).Equals(changedType) && changedType.Key == ObjectKind.Player)
{ {
Logger.LogDebug("Received Moodles change, updating player"); Logger.LogDebug("Received Moodles change, updating player");
MoodlesChanged(); AddCacheToCreate(ObjectKind.Player);
} }
}); });
Mediator.Subscribe<PetNamesMessage>(this, (msg) => Mediator.Subscribe<PetNamesMessage>(this, (msg) =>
{ {
if (_isZoning) return; if (_isZoning) return;
if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal)) if (!string.Equals(msg.PetNicknamesData, _playerData.PetNamesData, StringComparison.Ordinal))
{ {
Logger.LogDebug("Received Pet Nicknames change, updating player"); Logger.LogDebug("Received Pet Nicknames change, updating player");
PetNicknamesChanged(); AddCacheToCreate(ObjectKind.Player);
} }
}); });
Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (msg) => Mediator.Subscribe<PenumbraModSettingChangedMessage>(this, (msg) =>
{ {
Logger.LogDebug("Received Penumbra Mod settings change, updating player"); Logger.LogDebug("Received Penumbra Mod settings change, updating everything");
_ = AddPlayerCacheToCreate(); AddCacheToCreate(ObjectKind.Player);
AddCacheToCreate(ObjectKind.Pet);
AddCacheToCreate(ObjectKind.MinionOrMount);
AddCacheToCreate(ObjectKind.Companion);
}); });
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation()); Mediator.Subscribe<FrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
} }
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)
@@ -153,96 +134,90 @@ 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.FromSeconds(1), token).ConfigureAwait(false); await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
await AddPlayerCacheToCreate(kind).ConfigureAwait(false); Logger.LogWarning("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() private readonly HashSet<ObjectKind> _currentlyCreating = [];
{ private CancellationTokenSource _creationCts = new();
_honorificCts?.Cancel();
_honorificCts?.Dispose();
_honorificCts = new();
var token = _honorificCts.Token;
_ = Task.Run(async () =>
{
await Task.Delay(TimeSpan.FromSeconds(3), 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(2), 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(3), 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;
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(); _cacheCreateLock.Wait();
var toCreate = _cachesToCreate.ToList(); var objectKindsToCreate = _cachesToCreate.ToList();
foreach (var creationObj in objectKindsToCreate)
{
_currentlyCreating.Add(creationObj);
}
_cachesToCreate.Clear(); _cachesToCreate.Clear();
_cacheCreateLock.Release(); _cacheCreateLock.Release();
_cacheCreationTask = Task.Run(async () => _ = Task.Run(async () =>
{ {
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_creationCts.Token, _runtimeCts.Token);
Logger.LogDebug("Creating Caches for {objectKinds}", string.Join(", ", objectKindsToCreate));
try try
{ {
foreach (var obj in toCreate) Dictionary<ObjectKind, CharacterDataFragment?> createdData = [];
foreach (var objectKind in objectKindsToCreate)
{ {
await _characterDataFactory.BuildCharacterData(_playerData, obj.Value, _cts.Token).ConfigureAwait(false); createdData[objectKind] = await _characterDataFactory.BuildCharacterData(_playerRelatedObjects[objectKind], linkedCts.Token).ConfigureAwait(false);
}
foreach (var kvp in createdData)
{
_playerData.SetFragment(kvp.Key, kvp.Value);
} }
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI())); Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
_currentlyCreating.Clear();
}
catch (OperationCanceledException)
{
Logger.LogDebug("Cache Creation cancelled");
linkedCts.Dispose();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -252,12 +227,6 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
{ {
Logger.LogDebug("Cache Creation complete"); Logger.LogDebug("Cache Creation complete");
} }
}, _cts.Token); });
}
else if (_cachesToCreate.Any())
{
Logger.LogDebug("Cache Creation stored until previous creation finished");
} }
} }
}
#pragma warning restore MA0040

View File

@@ -112,6 +112,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<TagHandler>(); collection.AddSingleton<TagHandler>();
collection.AddSingleton<IdDisplayHandler>(); collection.AddSingleton<IdDisplayHandler>();
collection.AddSingleton<PlayerPerformanceService>(); collection.AddSingleton<PlayerPerformanceService>();
collection.AddSingleton<TransientResourceManager>();
collection.AddSingleton<CharaDataManager>(); collection.AddSingleton<CharaDataManager>();
collection.AddSingleton<CharaDataFileHandler>(); collection.AddSingleton<CharaDataFileHandler>();
@@ -211,7 +212,6 @@ public sealed class Plugin : IDalamudPlugin
collection.AddScoped<IPopupHandler, BanUserPopupHandler>(); collection.AddScoped<IPopupHandler, BanUserPopupHandler>();
collection.AddScoped<IPopupHandler, CensusPopupHandler>(); collection.AddScoped<IPopupHandler, CensusPopupHandler>();
collection.AddScoped<CacheCreationService>(); collection.AddScoped<CacheCreationService>();
collection.AddScoped<TransientResourceManager>();
collection.AddScoped<PlayerDataFactory>(); collection.AddScoped<PlayerDataFactory>();
collection.AddScoped<OnlinePlayerManager>(); collection.AddScoped<OnlinePlayerManager>();
collection.AddScoped((s) => new UiService(s.GetRequiredService<ILogger<UiService>>(), pluginInterface.UiBuilder, s.GetRequiredService<MareConfigService>(), collection.AddScoped((s) => new UiService(s.GetRequiredService<ILogger<UiService>>(), pluginInterface.UiBuilder, s.GetRequiredService<MareConfigService>(),

View File

@@ -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))

View File

@@ -461,15 +461,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
{ {

View File

@@ -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;