rebuild PlayerManager to CacheCreationService and optimize creation of the local file cache
This commit is contained in:
@@ -11,6 +11,7 @@ using MareSynchronos.Utils;
|
||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
||||
using Penumbra.String;
|
||||
using Weapon = MareSynchronos.Interop.Weapon;
|
||||
using MareSynchronos.FileCache;
|
||||
|
||||
namespace MareSynchronos.Factories;
|
||||
|
||||
@@ -19,23 +20,23 @@ public class CharacterDataFactory
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly TransientResourceManager _transientResourceManager;
|
||||
private readonly FileReplacementFactory _fileReplacementFactory;
|
||||
private readonly FileCacheManager _fileCacheManager;
|
||||
|
||||
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager, FileReplacementFactory fileReplacementFactory)
|
||||
public CharacterDataFactory(DalamudUtil dalamudUtil, IpcManager ipcManager, TransientResourceManager transientResourceManager, FileCacheManager fileReplacementFactory)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(CharacterDataFactory));
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_ipcManager = ipcManager;
|
||||
_transientResourceManager = transientResourceManager;
|
||||
_fileReplacementFactory = fileReplacementFactory;
|
||||
_fileCacheManager = fileReplacementFactory;
|
||||
}
|
||||
|
||||
private unsafe bool CheckForPointer(IntPtr playerPointer)
|
||||
private unsafe bool CheckForNullDrawObject(IntPtr playerPointer)
|
||||
{
|
||||
return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null;
|
||||
return ((Character*)playerPointer)->GameObject.DrawObject == null;
|
||||
}
|
||||
|
||||
public CharacterData BuildCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
||||
public CharacterData BuildCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||
{
|
||||
if (!_ipcManager.Initialized)
|
||||
{
|
||||
@@ -45,7 +46,16 @@ public class CharacterDataFactory
|
||||
bool pointerIsZero = true;
|
||||
try
|
||||
{
|
||||
pointerIsZero = CheckForPointer(playerRelatedObject.Address);
|
||||
pointerIsZero = playerRelatedObject.Address == IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
pointerIsZero = CheckForNullDrawObject(playerRelatedObject.Address);
|
||||
}
|
||||
catch
|
||||
{
|
||||
pointerIsZero = true;
|
||||
Logger.Debug("NullRef for " + playerRelatedObject.ObjectKind);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -67,17 +77,20 @@ public class CharacterDataFactory
|
||||
|
||||
try
|
||||
{
|
||||
pathsToForwardResolve.Clear();
|
||||
pathsToReverseResolve.Clear();
|
||||
return CreateCharacterData(previousData, playerRelatedObject, token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Logger.Debug("Cancelled creating Character data");
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Failed to create " + playerRelatedObject.ObjectKind + " data");
|
||||
Logger.Warn(e.Message);
|
||||
Logger.Warn(e.StackTrace ?? string.Empty);
|
||||
Logger.Debug("Failed to create " + playerRelatedObject.ObjectKind + " data");
|
||||
Logger.Debug(e.Message);
|
||||
Logger.Debug(e.StackTrace ?? string.Empty);
|
||||
}
|
||||
|
||||
previousData.FileReplacements = previousFileReplacements;
|
||||
@@ -85,23 +98,7 @@ public class CharacterDataFactory
|
||||
return previousData;
|
||||
}
|
||||
|
||||
private (string, string) GetIndentationForInheritanceLevel(int inheritanceLevel)
|
||||
{
|
||||
return (string.Join("", Enumerable.Repeat("\t", inheritanceLevel)), string.Join("", Enumerable.Repeat("\t", inheritanceLevel + 2)));
|
||||
}
|
||||
|
||||
private void DebugPrint(FileReplacement fileReplacement, ObjectKind objectKind, string resourceType, int inheritanceLevel)
|
||||
{
|
||||
var indentation = GetIndentationForInheritanceLevel(inheritanceLevel);
|
||||
|
||||
if (fileReplacement.HasFileReplacement)
|
||||
{
|
||||
Logger.Verbose(indentation.Item1 + objectKind + resourceType + " [" + string.Join(", ", fileReplacement.GamePaths) + "]");
|
||||
Logger.Verbose(indentation.Item2 + "=> " + fileReplacement.ResolvedPath);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
||||
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl)
|
||||
{
|
||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||
{
|
||||
@@ -115,26 +112,23 @@ public class CharacterDataFactory
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warn("Could not get model data for " + objectKind);
|
||||
Logger.Warn("Could not get model data");
|
||||
return;
|
||||
}
|
||||
Logger.Verbose("Checking File Replacement for Model " + mdlPath);
|
||||
|
||||
FileReplacement mdlFileReplacement = CreateFileReplacement(mdlPath);
|
||||
DebugPrint(mdlFileReplacement, objectKind, "Model", inheritanceLevel);
|
||||
|
||||
cache.AddFileReplacement(objectKind, mdlFileReplacement);
|
||||
AddResolvePath(mdlPath);
|
||||
|
||||
for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
||||
{
|
||||
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
||||
if (mtrl == null) continue;
|
||||
|
||||
AddReplacementsFromMaterial(mtrl, objectKind, cache, inheritanceLevel + 1);
|
||||
AddReplacementsFromMaterial(mtrl);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void AddReplacementsFromMaterial(Material* mtrl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
||||
private unsafe void AddReplacementsFromMaterial(Material* mtrl)
|
||||
{
|
||||
string fileName;
|
||||
try
|
||||
@@ -144,25 +138,15 @@ public class CharacterDataFactory
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warn("Could not get material data for " + objectKind);
|
||||
Logger.Warn("Could not get material data");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.Verbose("Checking File Replacement for Material " + fileName);
|
||||
var mtrlPath = fileName.Split("|")[2];
|
||||
|
||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath, StringComparison.Ordinal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var mtrlFileReplacement = CreateFileReplacement(mtrlPath);
|
||||
DebugPrint(mtrlFileReplacement, objectKind, "Material", inheritanceLevel);
|
||||
|
||||
cache.AddFileReplacement(objectKind, mtrlFileReplacement);
|
||||
AddResolvePath(mtrlPath);
|
||||
|
||||
var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
|
||||
for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
|
||||
@@ -181,14 +165,14 @@ public class CharacterDataFactory
|
||||
|
||||
Logger.Verbose("Checking File Replacement for Texture " + texPath);
|
||||
|
||||
AddReplacementsFromTexture(texPath, objectKind, cache, inheritanceLevel + 1);
|
||||
AddReplacementsFromTexture(texPath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var shpkPath = "shader/sm5/shpk/" + new ByteString(mtrlResourceHandle->ShpkString).ToString();
|
||||
Logger.Verbose("Checking File Replacement for Shader " + shpkPath);
|
||||
AddReplacementsFromShader(shpkPath, objectKind, cache, inheritanceLevel + 1);
|
||||
AddReplacementsFromShader(shpkPath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -196,130 +180,92 @@ public class CharacterDataFactory
|
||||
}
|
||||
}
|
||||
|
||||
private void AddReplacement(string varPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = false)
|
||||
private void AddReplacement(string varPath, bool doNotReverseResolve = false)
|
||||
{
|
||||
if (varPath.IsNullOrEmpty()) return;
|
||||
|
||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath, StringComparer.Ordinal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var variousReplacement = CreateFileReplacement(varPath, doNotReverseResolve);
|
||||
DebugPrint(variousReplacement, objectKind, "Various", inheritanceLevel);
|
||||
|
||||
cache.AddFileReplacement(objectKind, variousReplacement);
|
||||
AddResolvePath(varPath, doNotReverseResolve);
|
||||
}
|
||||
|
||||
private void AddReplacementsFromShader(string shpkPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
||||
private void AddReplacementsFromShader(string shpkPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(shpkPath)) return;
|
||||
|
||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(shpkPath, StringComparer.Ordinal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var shpkFileReplacement = CreateFileReplacement(shpkPath, doNotReverseResolve: true);
|
||||
DebugPrint(shpkFileReplacement, objectKind, "Shader", inheritanceLevel);
|
||||
cache.AddFileReplacement(objectKind, shpkFileReplacement);
|
||||
AddResolvePath(shpkPath, doNotReverseResolve: true);
|
||||
}
|
||||
|
||||
private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true)
|
||||
private void AddReplacementsFromTexture(string texPath, bool doNotReverseResolve = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(texPath)) return;
|
||||
|
||||
if (cache.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath, StringComparer.Ordinal)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var texFileReplacement = CreateFileReplacement(texPath, doNotReverseResolve);
|
||||
DebugPrint(texFileReplacement, objectKind, "Texture", inheritanceLevel);
|
||||
|
||||
cache.AddFileReplacement(objectKind, texFileReplacement);
|
||||
AddResolvePath(texPath, doNotReverseResolve);
|
||||
|
||||
if (texPath.Contains("/--", StringComparison.Ordinal)) return;
|
||||
|
||||
var texDx11Replacement =
|
||||
CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
|
||||
|
||||
DebugPrint(texDx11Replacement, objectKind, "Texture (DX11)", inheritanceLevel);
|
||||
|
||||
cache.AddFileReplacement(objectKind, texDx11Replacement);
|
||||
AddResolvePath(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
|
||||
}
|
||||
|
||||
private unsafe CharacterData CreateCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
||||
private unsafe CharacterData CreateCharacterData(CharacterData previousData, GameObjectHandler playerRelatedObject, CancellationToken token)
|
||||
{
|
||||
var objectKind = playerRelatedObject.ObjectKind;
|
||||
var charaPointer = playerRelatedObject.Address;
|
||||
|
||||
if (!previousData.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
previousData.FileReplacements[objectKind] = new(FileReplacementComparer.Instance);
|
||||
}
|
||||
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(playerRelatedObject.ObjectKind.ToString(), playerRelatedObject.Address, ct: token);
|
||||
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
|
||||
if (playerRelatedObject.HasUnprocessedUpdate)
|
||||
Logger.Debug("Handling unprocessed update for " + objectKind);
|
||||
|
||||
if (previousData.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
Logger.Debug("Handling unprocessed update for " + objectKind);
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
}
|
||||
|
||||
if (previousData.FileReplacements.ContainsKey(objectKind))
|
||||
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
||||
while (!DalamudUtil.IsObjectPresent(chara))
|
||||
{
|
||||
Logger.Verbose("Character is null but it shouldn't be, waiting");
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
var human = (Human*)((Character*)charaPointer)->GameObject.DrawObject;
|
||||
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
||||
{
|
||||
var mdl = (RenderModel*)human->CharacterBase.ModelArray[mdlIdx];
|
||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||
{
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
previousData.FileReplacements.Add(objectKind, new());
|
||||
continue;
|
||||
}
|
||||
|
||||
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
||||
while (!DalamudUtil.IsObjectPresent(chara))
|
||||
{
|
||||
Logger.Verbose("Character is null but it shouldn't be, waiting");
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
|
||||
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
||||
{
|
||||
var mdl = (RenderModel*)human->CharacterBase.ModelArray[mdlIdx];
|
||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
AddReplacementsFromRenderModel(mdl);
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0);
|
||||
}
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
AddPlayerSpecificReplacements(objectKind, charaPointer, human);
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Pet)
|
||||
{
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
_transientResourceManager.AddSemiTransientResource(objectKind, item.GamePaths.First());
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
AddPlayerSpecificReplacements(previousData, objectKind, charaPointer, human);
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Pet)
|
||||
{
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
_transientResourceManager.AddSemiTransientResource(objectKind, item);
|
||||
}
|
||||
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
}
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
}
|
||||
|
||||
|
||||
Dictionary<string, List<string>> resolvedPaths = GetFileReplacementsFromPaths();
|
||||
previousData.FileReplacements[objectKind] = new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)));
|
||||
|
||||
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
|
||||
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
||||
@@ -327,52 +273,75 @@ public class CharacterDataFactory
|
||||
previousData.PalettePlusPalette = _ipcManager.PalettePlusBuildPalette();
|
||||
|
||||
Logger.Debug("Handling transient update for " + objectKind);
|
||||
ManageSemiTransientData(previousData, objectKind, charaPointer);
|
||||
_transientResourceManager.ClearTransientPaths(charaPointer, previousData.FileReplacements[objectKind].SelectMany(c => c.GamePaths).ToList());
|
||||
|
||||
pathsToForwardResolve.Clear();
|
||||
pathsToReverseResolve.Clear();
|
||||
|
||||
ManageSemiTransientData(objectKind, charaPointer);
|
||||
|
||||
var resolvedTransientPaths = GetFileReplacementsFromPaths();
|
||||
foreach (var replacement in resolvedTransientPaths.Select(c => new FileReplacement(c.Value, c.Key, _fileCacheManager)))
|
||||
{
|
||||
previousData.FileReplacements[objectKind].Add(replacement);
|
||||
}
|
||||
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
Logger.Debug(item.ToString());
|
||||
}
|
||||
|
||||
st.Stop();
|
||||
Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed);
|
||||
Logger.Verbose("Building " + objectKind + " Data took " + st.ElapsedMilliseconds + "ms");
|
||||
return previousData;
|
||||
}
|
||||
|
||||
private unsafe void ManageSemiTransientData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer)
|
||||
private Dictionary<string, List<string>> GetFileReplacementsFromPaths()
|
||||
{
|
||||
_transientResourceManager.PersistTransientResources(charaPointer, objectKind, CreateFileReplacement);
|
||||
|
||||
// get rid of items that have no file replacements anymore
|
||||
foreach (var entry in previousData.FileReplacements.ToList())
|
||||
var forwardPaths = pathsToForwardResolve.ToArray();
|
||||
var reversePaths = pathsToReverseResolve.ToArray();
|
||||
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
||||
var result = _ipcManager.PenumbraResolvePaths(pathsToForwardResolve.ToArray(), pathsToReverseResolve.ToArray());
|
||||
for (int i = 0; i < forwardPaths.Length; i++)
|
||||
{
|
||||
foreach (var item in entry.Value.ToList())
|
||||
var filePath = result.forward[i].ToLowerInvariant();
|
||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||
{
|
||||
if (!item.HasFileReplacement) previousData.FileReplacements[entry.Key].Remove(item);
|
||||
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedPaths[filePath] = new List<string> { forwardPaths[i].ToLowerInvariant() };
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < reversePaths.Length; i++)
|
||||
{
|
||||
var filePath = reversePaths[i].ToLowerInvariant();
|
||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||
{
|
||||
list.AddRange(result.reverse[i].Select(c => c.ToLowerInvariant()));
|
||||
}
|
||||
else
|
||||
{
|
||||
resolvedPaths[filePath] = new List<string>(result.reverse[i].Select(c => c.ToLowerInvariant()).ToList());
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedPaths;
|
||||
}
|
||||
|
||||
private unsafe void ManageSemiTransientData(ObjectKind objectKind, IntPtr charaPointer)
|
||||
{
|
||||
_transientResourceManager.PersistTransientResources(charaPointer, objectKind);
|
||||
|
||||
foreach (var item in _transientResourceManager.GetSemiTransientResources(objectKind))
|
||||
{
|
||||
if (!previousData.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
previousData.FileReplacements.Add(objectKind, new());
|
||||
}
|
||||
|
||||
if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Contains(p, StringComparer.OrdinalIgnoreCase))))
|
||||
{
|
||||
var penumResolve = _ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant();
|
||||
var gamePath = item.GamePaths.First().ToLowerInvariant();
|
||||
if (string.Equals(penumResolve, gamePath, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Verbose("PenumResolve was same as GamePath, not adding " + item);
|
||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Verbose("Found semi transient resource: " + item);
|
||||
previousData.FileReplacements[objectKind].Add(item);
|
||||
}
|
||||
}
|
||||
AddResolvePath(item, true);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void AddPlayerSpecificReplacements(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer, Human* human)
|
||||
private unsafe void AddPlayerSpecificReplacements(ObjectKind objectKind, IntPtr charaPointer, Human* human)
|
||||
{
|
||||
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
|
||||
|
||||
@@ -380,42 +349,32 @@ public class CharacterDataFactory
|
||||
{
|
||||
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
|
||||
|
||||
AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0);
|
||||
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
}
|
||||
AddReplacementsFromRenderModel(mainHandWeapon);
|
||||
|
||||
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject))
|
||||
{
|
||||
Logger.Verbose("Found transient weapon resource: " + item);
|
||||
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
|
||||
AddReplacement(item, doNotReverseResolve: true);
|
||||
}
|
||||
|
||||
if (weaponObject->NextSibling != (IntPtr)weaponObject)
|
||||
{
|
||||
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
|
||||
|
||||
AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1);
|
||||
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
_transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item);
|
||||
}
|
||||
AddReplacementsFromRenderModel(offHandWeapon);
|
||||
|
||||
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
|
||||
{
|
||||
Logger.Verbose("Found transient offhand weapon resource: " + item);
|
||||
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
|
||||
AddReplacement(item, doNotReverseResolve: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
|
||||
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId);
|
||||
try
|
||||
{
|
||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
|
||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), doNotReverseResolve: false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -423,43 +382,29 @@ public class CharacterDataFactory
|
||||
}
|
||||
try
|
||||
{
|
||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
|
||||
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), doNotReverseResolve: false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warn("Could not get Legacy Body Decal Data");
|
||||
}
|
||||
|
||||
foreach (var item in previousData.FileReplacements[objectKind])
|
||||
{
|
||||
_transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
|
||||
private void AddReplacementSkeleton(ushort raceSexId)
|
||||
{
|
||||
string raceSexIdString = raceSexId.ToString("0000");
|
||||
|
||||
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
|
||||
|
||||
var replacement = CreateFileReplacement(skeletonPath, doNotReverseResolve: true);
|
||||
cache.AddFileReplacement(objectKind, replacement);
|
||||
|
||||
DebugPrint(replacement, objectKind, "SKLB", 0);
|
||||
AddResolvePath(skeletonPath, doNotReverseResolve: true);
|
||||
}
|
||||
|
||||
private FileReplacement CreateFileReplacement(string path, bool doNotReverseResolve = false)
|
||||
private void AddResolvePath(string path, bool doNotReverseResolve = false)
|
||||
{
|
||||
var fileReplacement = _fileReplacementFactory.Create();
|
||||
if (!doNotReverseResolve)
|
||||
{
|
||||
fileReplacement.ReverseResolvePath(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
fileReplacement.ResolvePath(path);
|
||||
}
|
||||
|
||||
return fileReplacement;
|
||||
if (doNotReverseResolve) pathsToForwardResolve.Add(path.ToLowerInvariant());
|
||||
else pathsToReverseResolve.Add(path.ToLowerInvariant());
|
||||
}
|
||||
|
||||
private HashSet<string> pathsToForwardResolve = new(StringComparer.Ordinal);
|
||||
private HashSet<string> pathsToReverseResolve = new(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Managers;
|
||||
using MareSynchronos.Models;
|
||||
|
||||
namespace MareSynchronos.Factories;
|
||||
|
||||
public class FileReplacementFactory
|
||||
{
|
||||
private readonly FileCacheManager fileCacheManager;
|
||||
private readonly IpcManager ipcManager;
|
||||
|
||||
public FileReplacementFactory(FileCacheManager fileCacheManager, IpcManager ipcManager)
|
||||
{
|
||||
this.fileCacheManager = fileCacheManager;
|
||||
this.ipcManager = ipcManager;
|
||||
}
|
||||
|
||||
public FileReplacement Create()
|
||||
{
|
||||
return new FileReplacement(fileCacheManager, ipcManager);
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,8 @@ public class FileCacheManager : IDisposable
|
||||
|
||||
public FileCacheEntity? GetFileCacheByPath(string path)
|
||||
{
|
||||
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
|
||||
var entry = _fileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)).Value;
|
||||
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
|
||||
var entry = _fileCaches.Values.FirstOrDefault(f => f.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
@@ -140,8 +140,8 @@ public class FileCacheManager : IDisposable
|
||||
FileInfo fi = new(path);
|
||||
if (!fi.Exists) return null;
|
||||
var fullName = fi.FullName.ToLowerInvariant();
|
||||
if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
|
||||
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||
if (!fullName.Contains(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
|
||||
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
|
||||
return CreateFileCacheEntity(fi, prefixedPath);
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ public class FileCacheManager : IDisposable
|
||||
{
|
||||
if (fileCache.PrefixedFilePath.StartsWith(_penumbraPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal));
|
||||
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory, StringComparison.Ordinal));
|
||||
}
|
||||
else if (fileCache.PrefixedFilePath.StartsWith(_cachePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@@ -150,7 +150,7 @@ public class PeriodicFileScanner : MediatorSubscriberBase, IDisposable
|
||||
private void PeriodicFileScan(CancellationToken ct)
|
||||
{
|
||||
TotalFiles = 1;
|
||||
var penumbraDir = _ipcManager.PenumbraModDirectory();
|
||||
var penumbraDir = _ipcManager.PenumbraModDirectory;
|
||||
bool penDirExists = true;
|
||||
bool cacheDirExists = true;
|
||||
if (string.IsNullOrEmpty(penumbraDir) || !Directory.Exists(penumbraDir))
|
||||
|
||||
114
MareSynchronos/Managers/CacheCreationService.cs
Normal file
114
MareSynchronos/Managers/CacheCreationService.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.Factories;
|
||||
using MareSynchronos.Mediator;
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public class CacheCreationService : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
private readonly CharacterDataFactory _characterDataFactory;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly ApiController _apiController;
|
||||
private Task? _cacheCreationTask;
|
||||
private Dictionary<ObjectKind, GameObjectHandler> _cachesToCreate = new();
|
||||
private CharacterData _lastCreatedData = new();
|
||||
private CancellationTokenSource cts = new();
|
||||
private List<GameObjectHandler> _playerRelatedObjects;
|
||||
|
||||
public unsafe CacheCreationService(MareMediator mediator, CharacterDataFactory characterDataFactory, IpcManager ipcManager,
|
||||
ApiController apiController, DalamudUtil dalamudUtil) : base(mediator)
|
||||
{
|
||||
_characterDataFactory = characterDataFactory;
|
||||
_ipcManager = ipcManager;
|
||||
_apiController = apiController;
|
||||
|
||||
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
|
||||
{
|
||||
var actualMsg = (CreateCacheForObjectMessage)msg;
|
||||
_cachesToCreate[actualMsg.ObjectToCreateFor.ObjectKind] = actualMsg.ObjectToCreateFor;
|
||||
});
|
||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
|
||||
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
|
||||
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
|
||||
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
|
||||
|
||||
_playerRelatedObjects = new List<GameObjectHandler>()
|
||||
{
|
||||
new(Mediator, ObjectKind.Player, () => dalamudUtil.PlayerPointer),
|
||||
new(Mediator, ObjectKind.MinionOrMount, () => (IntPtr)((Character*)dalamudUtil.PlayerPointer)->CompanionObject),
|
||||
new(Mediator, ObjectKind.Pet, () => dalamudUtil.GetPet()),
|
||||
new(Mediator, ObjectKind.Companion, () => dalamudUtil.GetCompanion()),
|
||||
};
|
||||
}
|
||||
|
||||
private void PalettePlusChanged(PalettePlusMessage msg)
|
||||
{
|
||||
if (!string.Equals(msg.Data, _lastCreatedData.PalettePlusPalette, StringComparison.Ordinal))
|
||||
{
|
||||
_lastCreatedData.PalettePlusPalette = msg.Data ?? string.Empty;
|
||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||
}
|
||||
}
|
||||
|
||||
private void HeelsOffsetChanged(HeelsOffsetMessage msg)
|
||||
{
|
||||
if (msg.Offset != _lastCreatedData.HeelsOffset)
|
||||
{
|
||||
_lastCreatedData.HeelsOffset = msg.Offset;
|
||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||
}
|
||||
}
|
||||
|
||||
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
||||
{
|
||||
if (!string.Equals(msg.Data, _lastCreatedData.CustomizePlusScale, StringComparison.Ordinal))
|
||||
{
|
||||
_lastCreatedData.CustomizePlusScale = msg.Data ?? string.Empty;
|
||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessCacheCreation()
|
||||
{
|
||||
if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true))
|
||||
{
|
||||
var toCreate = _cachesToCreate.ToList();
|
||||
_cachesToCreate.Clear();
|
||||
_cacheCreationTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var obj in toCreate)
|
||||
{
|
||||
var data = _characterDataFactory.BuildCharacterData(_lastCreatedData, obj.Value, cts.Token);
|
||||
}
|
||||
Mediator.Publish(new CharacterDataCreatedMessage(_lastCreatedData));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error("Error during Cache Creation Processing", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Logger.Debug("Cache Creation complete");
|
||||
|
||||
}
|
||||
}, cts.Token);
|
||||
}
|
||||
else if (_cachesToCreate.Any())
|
||||
{
|
||||
Logger.Debug("Cache Creation stored until previous creation finished");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
_playerRelatedObjects.ForEach(p => p.Dispose());
|
||||
cts.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private API.Data.CharacterData _cachedData = new();
|
||||
private PlayerRelatedObject? _currentCharacterEquipment;
|
||||
private GameObjectHandler? _currentCharacterEquipment;
|
||||
private CancellationTokenSource? _downloadCancellationTokenSource = new();
|
||||
private bool _isVisible;
|
||||
|
||||
@@ -65,6 +65,18 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
Logger.Debug("Checking for files to download for player " + PlayerName);
|
||||
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
|
||||
|
||||
if (!_ipcManager.CheckPenumbraApi())
|
||||
{
|
||||
Mediator.Publish(new NotificationMessage("Penumbra inactive", "Your Penumbra installation is not active or out of date. Update Penumbra and/or the Enable Mods setting in Penumbra to continue to use Mare.", NotificationType.Error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ipcManager.CheckGlamourerApi())
|
||||
{
|
||||
Mediator.Publish(new NotificationMessage("Glamourer inactive", "Your Glamourer installation is not active or out of date. Update Glamourer to continue to use Mare.", NotificationType.Error));
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal) && !forced) return;
|
||||
|
||||
bool updateModdedPaths = false;
|
||||
@@ -116,6 +128,14 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
bool manipDataDifferent = !string.Equals(_cachedData.ManipulationData, characterData.ManipulationData, StringComparison.Ordinal);
|
||||
if (manipDataDifferent)
|
||||
{
|
||||
Logger.Debug("Updating " + objectKind);
|
||||
charaDataToUpdate.Add(objectKind);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
|
||||
if (heelsOffsetDifferent)
|
||||
{
|
||||
@@ -189,8 +209,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentCharacterEquipment?.CheckAndUpdateObject();
|
||||
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
|
||||
if (_currentCharacterEquipment?.CheckAndUpdateObject() ?? false)
|
||||
{
|
||||
OnPlayerChanged();
|
||||
}
|
||||
@@ -247,8 +266,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
|
||||
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManagerOnPenumbraRedrawEvent(((PenumbraRedrawMessage)msg)));
|
||||
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
|
||||
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
|
||||
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
|
||||
_currentCharacterEquipment = new GameObjectHandler(Mediator, ObjectKind.Player, () => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero, false);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
@@ -437,7 +455,7 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
PlayerCharacter = msg.Address;
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(10));
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
|
||||
cts.Dispose();
|
||||
cts = new CancellationTokenSource();
|
||||
@@ -460,7 +478,6 @@ public class CachedPlayer : MediatorSubscriberBase, IDisposable
|
||||
private void OnPlayerChanged()
|
||||
{
|
||||
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
|
||||
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
|
||||
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
|
||||
{
|
||||
Logger.Debug($"Saving new Glamourer data");
|
||||
|
||||
@@ -35,8 +35,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
private readonly FuncSubscriber<string, string> _penumbraResolvePlayer;
|
||||
private readonly FuncSubscriber<string, string[]> _reverseResolvePlayer;
|
||||
private readonly FuncSubscriber<string, string, Dictionary<string, string>, string, int, PenumbraApiEc> _penumbraAddTemporaryMod;
|
||||
private readonly FuncSubscriber<string[], string[], (string[], string[][])> _penumbraResolvePaths;
|
||||
private readonly FuncSubscriber<bool> _penumbraEnabled;
|
||||
private readonly EventSubscriber<nint, string, string> _penumbraGameObjectResourcePathResolved;
|
||||
private readonly EventSubscriber<ModSettingChange, string, string, bool> _penumbraModSettingChanged;
|
||||
|
||||
private readonly ICallGateSubscriber<string> _heelsGetApiVersion;
|
||||
private readonly ICallGateSubscriber<float> _heelsGetOffset;
|
||||
@@ -81,9 +82,10 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
_penumbraRemoveTemporaryCollection = Penumbra.Api.Ipc.RemoveTemporaryCollectionByName.Subscriber(pi);
|
||||
_penumbraRemoveTemporaryMod = Penumbra.Api.Ipc.RemoveTemporaryMod.Subscriber(pi);
|
||||
_penumbraAssignTemporaryCollection = Penumbra.Api.Ipc.AssignTemporaryCollection.Subscriber(pi);
|
||||
_penumbraResolvePaths = Penumbra.Api.Ipc.ResolvePlayerPaths.Subscriber(pi);
|
||||
_penumbraEnabled = Penumbra.Api.Ipc.GetEnabledState.Subscriber(pi);
|
||||
|
||||
_penumbraGameObjectResourcePathResolved = Penumbra.Api.Ipc.GameObjectResourcePathResolved.Subscriber(pi, (ptr, arg1, arg2) => ResourceLoaded((IntPtr)ptr, arg1, arg2));
|
||||
_penumbraModSettingChanged = Penumbra.Api.Ipc.ModSettingChanged.Subscriber(pi, (modsetting, a, b, c) => PenumbraModSettingChangedHandler());
|
||||
|
||||
_glamourerApiVersion = pi.GetIpcSubscriber<int>("Glamourer.ApiVersion");
|
||||
_glamourerGetAllCustomization = pi.GetIpcSubscriber<GameObject?, string>("Glamourer.GetAllCustomizationFromCharacter");
|
||||
@@ -125,6 +127,12 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => HandleActionQueue());
|
||||
Mediator.Subscribe<GposeFrameworkUpdateMessage>(this, (_) => HandleGposeActionQueue());
|
||||
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (_) => ClearActionQueue());
|
||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => CheckPenumbraModPath());
|
||||
}
|
||||
|
||||
private void CheckPenumbraModPath()
|
||||
{
|
||||
PenumbraModDirectory = GetPenumbraModDirectory();
|
||||
}
|
||||
|
||||
private void HandleGposeActionQueue()
|
||||
@@ -142,11 +150,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
_inGposeQueueMode = on;
|
||||
}
|
||||
|
||||
private void PenumbraModSettingChangedHandler()
|
||||
{
|
||||
Mediator.Publish(new PenumbraModSettingChangedMessage());
|
||||
}
|
||||
|
||||
private void ClearActionQueue()
|
||||
{
|
||||
ActionQueue.Clear();
|
||||
@@ -191,7 +194,7 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 17 };
|
||||
return _penumbraApiVersion.Invoke() is { Item1: 4, Item2: >= 19 } && _penumbraEnabled.Invoke();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -259,7 +262,6 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
_penumbraDispose.Dispose();
|
||||
_penumbraInit.Dispose();
|
||||
_penumbraObjectIsRedrawn.Dispose();
|
||||
_penumbraModSettingChanged.Dispose();
|
||||
_heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange);
|
||||
}
|
||||
|
||||
@@ -411,7 +413,9 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
return _penumbraGetMetaManipulations.Invoke();
|
||||
}
|
||||
|
||||
public string? PenumbraModDirectory()
|
||||
public string? PenumbraModDirectory;
|
||||
|
||||
public string? GetPenumbraModDirectory()
|
||||
{
|
||||
if (!CheckPenumbraApi()) return null;
|
||||
return _penumbraResolveModDir!.Invoke().ToLowerInvariant();
|
||||
@@ -495,6 +499,11 @@ public class IpcManager : MediatorSubscriberBase, IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
public (string[] forward, string[][] reverse) PenumbraResolvePaths(string[] forward, string[] reverse)
|
||||
{
|
||||
return _penumbraResolvePaths.Invoke(forward, reverse);
|
||||
}
|
||||
|
||||
private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
|
||||
{
|
||||
Mediator.Publish(new PenumbraRedrawMessage(objectAddress, objectTableIndex));
|
||||
|
||||
@@ -97,7 +97,7 @@ public class NotificationService : MediatorSubscriberBase
|
||||
|
||||
private void PrintErrorChat(string? message)
|
||||
{
|
||||
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Error: ", 534).AddItalicsOn().AddUiForeground(message ?? string.Empty, 534).AddUiForegroundOff().AddItalicsOff();
|
||||
_chatGui.Print(se.BuiltString);
|
||||
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Error: " + message);
|
||||
_chatGui.PrintError(se.BuiltString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,17 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
private readonly ApiController _apiController;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly PlayerManager _playerManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly PairManager _pairManager;
|
||||
private CharacterData? _lastSentData;
|
||||
|
||||
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, PlayerManager playerManager,
|
||||
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil,
|
||||
FileCacheManager fileDbManager, PairManager pairManager, MareMediator mediator) : base(mediator)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
|
||||
|
||||
_apiController = apiController;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_playerManager = playerManager;
|
||||
_fileDbManager = fileDbManager;
|
||||
_pairManager = pairManager;
|
||||
|
||||
@@ -30,6 +29,20 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
||||
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => DalamudUtilOnLogIn());
|
||||
Mediator.Subscribe<DalamudLogoutMessage>(this, (_) => DalamudUtilOnLogOut());
|
||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => FrameworkOnUpdate());
|
||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) =>
|
||||
{
|
||||
var newData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI();
|
||||
if (_lastSentData == null || _lastSentData != null && !string.Equals(newData.DataHash.Value, _lastSentData.DataHash.Value, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("Pushing data for visible players");
|
||||
_lastSentData = newData;
|
||||
PushCharacterData(_pairManager.VisibleUsers);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Not sending data for " + newData.DataHash.Value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void PlayerManagerOnPlayerHasChanged(PlayerChangedMessage msg)
|
||||
@@ -78,11 +91,11 @@ public class OnlinePlayerManager : MediatorSubscriberBase, IDisposable
|
||||
|
||||
private void PushCharacterData(List<UserData> visiblePlayers)
|
||||
{
|
||||
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
|
||||
if (visiblePlayers.Any() && _lastSentData != null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, visiblePlayers).ConfigureAwait(false);
|
||||
await _apiController.PushCharacterData(_lastSentData, visiblePlayers).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Dalamud.Utility;
|
||||
using MareSynchronos.API.Data;
|
||||
|
||||
@@ -1,279 +0,0 @@
|
||||
using MareSynchronos.Factories;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.WebAPI;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.Mediator;
|
||||
#if DEBUG
|
||||
#endif
|
||||
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public class PlayerManager : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CharacterDataFactory _characterDataFactory;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private readonly IpcManager _ipcManager;
|
||||
public API.Data.CharacterData? LastCreatedCharacterData { get; private set; }
|
||||
public Models.CharacterData PermanentDataCache { get; private set; } = new();
|
||||
private readonly Dictionary<ObjectKind, Func<bool>> _objectKindsToUpdate = new();
|
||||
|
||||
private CancellationTokenSource? _playerChangedCts = new();
|
||||
private CancellationTokenSource _transientUpdateCts = new();
|
||||
|
||||
private readonly List<PlayerRelatedObject> _playerRelatedObjects = new();
|
||||
|
||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil,
|
||||
MareMediator mediator) : base(mediator)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(PlayerManager));
|
||||
|
||||
_apiController = apiController;
|
||||
_ipcManager = ipcManager;
|
||||
_characterDataFactory = characterDataFactory;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
|
||||
Mediator.Subscribe<CustomizePlusMessage>(this, (msg) => CustomizePlusChanged((CustomizePlusMessage)msg));
|
||||
Mediator.Subscribe<HeelsOffsetMessage>(this, (msg) => HeelsOffsetChanged((HeelsOffsetMessage)msg));
|
||||
Mediator.Subscribe<PalettePlusMessage>(this, (msg) => PalettePlusChanged((PalettePlusMessage)msg));
|
||||
Mediator.Subscribe<ConnectedMessage>(this, (_) => ApiControllerOnConnected());
|
||||
Mediator.Subscribe<DisconnectedMessage>(this, (_) => ApiController_Disconnected());
|
||||
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (_) => DalamudUtilOnDelayedFrameworkUpdate());
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtilOnFrameworkUpdate());
|
||||
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) => HandleTransientResourceLoad((TransientResourceChangedMessage)msg));
|
||||
|
||||
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
||||
if (_apiController.IsConnected)
|
||||
{
|
||||
ApiControllerOnConnected();
|
||||
}
|
||||
|
||||
_playerRelatedObjects = new List<PlayerRelatedObject>()
|
||||
{
|
||||
new(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
|
||||
new(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
|
||||
new(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
|
||||
new(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
|
||||
};
|
||||
}
|
||||
|
||||
private void DalamudUtilOnFrameworkUpdate()
|
||||
{
|
||||
Mediator.Publish(new PlayerRelatedObjectPointerUpdateMessage(_playerRelatedObjects.Select(f => f.CurrentAddress).ToArray()));
|
||||
}
|
||||
|
||||
public void HandleTransientResourceLoad(TransientResourceChangedMessage msg)
|
||||
{
|
||||
foreach (var obj in _playerRelatedObjects)
|
||||
{
|
||||
if (obj.Address == msg.Address && !obj.HasUnprocessedUpdate)
|
||||
{
|
||||
_transientUpdateCts.Cancel();
|
||||
_transientUpdateCts = new CancellationTokenSource();
|
||||
var token = _transientUpdateCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.Debug("Delaying transient resource load update");
|
||||
await Task.Delay(750, token).ConfigureAwait(false);
|
||||
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
|
||||
Logger.Debug("Firing transient resource load update");
|
||||
obj.HasTransientsUpdate = true;
|
||||
}, token);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HeelsOffsetChanged(HeelsOffsetMessage change)
|
||||
{
|
||||
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
||||
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change.Offset && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("Heels offset changed to " + change.Offset);
|
||||
player.HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void CustomizePlusChanged(CustomizePlusMessage msg)
|
||||
{
|
||||
var change = msg.Data ?? string.Empty;
|
||||
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
||||
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.CustomizePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("CustomizePlus data changed to " + change);
|
||||
player.HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void PalettePlusChanged(PalettePlusMessage msg)
|
||||
{
|
||||
var change = msg.Data ?? string.Empty;
|
||||
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
|
||||
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.PalettePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("PalettePlus data changed to " + change);
|
||||
player.HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
|
||||
_playerChangedCts?.Cancel();
|
||||
}
|
||||
|
||||
private unsafe void DalamudUtilOnDelayedFrameworkUpdate()
|
||||
{
|
||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
|
||||
|
||||
_playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
|
||||
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApiControllerOnConnected()
|
||||
{
|
||||
Logger.Debug("ApiController Connected");
|
||||
|
||||
Mediator.Subscribe<PenumbraRedrawMessage>(this, (msg) => IpcManager_PenumbraRedrawEvent((PenumbraRedrawMessage)msg));
|
||||
}
|
||||
|
||||
private void ApiController_Disconnected()
|
||||
{
|
||||
Logger.Debug(nameof(ApiController_Disconnected));
|
||||
|
||||
Mediator.Unsubscribe<PenumbraRedrawMessage>(this);
|
||||
}
|
||||
|
||||
private async Task<API.Data.CharacterData?> CreateFullCharacterCacheDto(CancellationToken token)
|
||||
{
|
||||
foreach (var unprocessedObject in _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
|
||||
{
|
||||
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
|
||||
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
unprocessedObject.HasUnprocessedUpdate = false;
|
||||
unprocessedObject.IsProcessing = false;
|
||||
unprocessedObject.HasTransientsUpdate = false;
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
int timeOut = 10000;
|
||||
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested && timeOut >= 0)
|
||||
{
|
||||
Logger.Verbose("Waiting until cache is ready (Timeout: " + TimeSpan.FromMilliseconds(timeOut) + ")");
|
||||
await Task.Delay(50, token).ConfigureAwait(false);
|
||||
timeOut -= 50;
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested || timeOut <= 0) return null;
|
||||
|
||||
Logger.Verbose("Cache creation complete");
|
||||
|
||||
var cache = PermanentDataCache.ToAPI();
|
||||
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void IpcManager_PenumbraRedrawEvent(PenumbraRedrawMessage msg)
|
||||
{
|
||||
Logger.Verbose("RedrawEvent for addr " + msg.Address);
|
||||
|
||||
foreach (var item in _playerRelatedObjects)
|
||||
{
|
||||
if (msg.Address == item.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
||||
item.HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
|
||||
{
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerOrAttachedObjectsChanged()
|
||||
{
|
||||
var unprocessedObjects = _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
|
||||
foreach (var unprocessedObject in unprocessedObjects)
|
||||
{
|
||||
unprocessedObject.IsProcessing = true;
|
||||
}
|
||||
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
|
||||
bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate);
|
||||
unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false);
|
||||
_playerChangedCts?.Cancel();
|
||||
_playerChangedCts = new CancellationTokenSource();
|
||||
var token = _playerChangedCts.Token;
|
||||
|
||||
// fix for redraw from anamnesis
|
||||
while ((!_dalamudUtil.IsPlayerPresent || string.Equals(_dalamudUtil.PlayerName, "--", StringComparison.Ordinal)) && !token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Waiting Until Player is Present");
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
Logger.Debug("Cancelled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_ipcManager.Initialized)
|
||||
{
|
||||
Logger.Warn("Penumbra not active, doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
API.Data.CharacterData? cacheData = null;
|
||||
try
|
||||
{
|
||||
Mediator.Publish(new HaltScanMessage("Character creation"));
|
||||
foreach (var item in unprocessedObjects)
|
||||
{
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, item.ObjectKind == ObjectKind.MinionOrMount ? 1000 : 10000, token);
|
||||
}
|
||||
|
||||
cacheData = (await CreateFullCharacterCacheDto(token).ConfigureAwait(false));
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
Mediator.Publish(new ResumeScanMessage("Character creation"));
|
||||
}
|
||||
if (cacheData == null || token.IsCancellationRequested) return;
|
||||
|
||||
#if DEBUG
|
||||
//var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented);
|
||||
//Logger.Verbose(json);
|
||||
#endif
|
||||
|
||||
if (string.Equals(LastCreatedCharacterData?.DataHash.Value ?? string.Empty, cacheData.DataHash.Value, StringComparison.Ordinal))
|
||||
{
|
||||
Logger.Debug("Not sending data, already sent");
|
||||
return;
|
||||
}
|
||||
|
||||
LastCreatedCharacterData = cacheData;
|
||||
|
||||
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
|
||||
{
|
||||
Logger.Verbose("Invoking PlayerHasChanged");
|
||||
Mediator.Publish(new PlayerChangedMessage(cacheData));
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.Factories;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.Mediator;
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.Utils;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
@@ -16,13 +14,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
|
||||
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
|
||||
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
|
||||
[Obsolete]
|
||||
private string PersistentDataCache => Path.Combine(_configurationService.ConfigurationDirectory, "PersistentTransientData.lst");
|
||||
private string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
|
||||
|
||||
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
||||
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory, MareMediator mediator) : base(mediator)
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<string>> SemiTransientResources { get; } = new();
|
||||
public TransientResourceManager(ConfigurationService configurationService, DalamudUtil dalamudUtil, MareMediator mediator) : base(mediator)
|
||||
{
|
||||
_configurationService = configurationService;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
@@ -32,16 +28,8 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => DalamudUtil_FrameworkUpdate());
|
||||
Mediator.Subscribe<ClassJobChangedMessage>(this, (_) => DalamudUtil_ClassJobChanged());
|
||||
Mediator.Subscribe<PlayerRelatedObjectPointerUpdateMessage>(this, (msg) => PlayerRelatedPointers = ((PlayerRelatedObjectPointerUpdateMessage)msg).RelatedObjects);
|
||||
// migrate obsolete data to new format
|
||||
if (File.Exists(PersistentDataCache))
|
||||
{
|
||||
var persistentEntities = File.ReadAllLines(PersistentDataCache).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = persistentEntities;
|
||||
_configurationService.Save();
|
||||
File.Delete(PersistentDataCache);
|
||||
}
|
||||
|
||||
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
|
||||
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<string>(StringComparer.Ordinal));
|
||||
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
|
||||
{
|
||||
int restored = 0;
|
||||
@@ -49,14 +37,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileReplacement = fileReplacementFactory.Create();
|
||||
fileReplacement.ResolvePath(file);
|
||||
if (fileReplacement.HasFileReplacement)
|
||||
{
|
||||
Logger.Debug("Loaded persistent transient resource " + file);
|
||||
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
|
||||
restored++;
|
||||
}
|
||||
Logger.Debug("Loaded persistent transient resource " + file);
|
||||
SemiTransientResources[ObjectKind.Player].Add(file);
|
||||
restored++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -70,20 +53,12 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
|
||||
private void Manager_PenumbraModSettingChanged()
|
||||
{
|
||||
bool successfulValidation = true;
|
||||
Task.Run(() =>
|
||||
{
|
||||
Logger.Debug("Penumbra Mod Settings changed, verifying SemiTransientResources");
|
||||
foreach (var item in SemiTransientResources)
|
||||
{
|
||||
item.Value.RemoveWhere(p =>
|
||||
{
|
||||
var verified = p.Verify();
|
||||
successfulValidation &= verified;
|
||||
return !verified;
|
||||
});
|
||||
if (!successfulValidation)
|
||||
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
|
||||
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.PlayerPointer));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -126,19 +101,19 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
public List<FileReplacement> GetSemiTransientResources(ObjectKind objectKind)
|
||||
public HashSet<string> GetSemiTransientResources(ObjectKind objectKind)
|
||||
{
|
||||
if (SemiTransientResources.TryGetValue(objectKind, out var result))
|
||||
{
|
||||
return result.ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
return new List<FileReplacement>();
|
||||
return new HashSet<string>();
|
||||
}
|
||||
|
||||
private void Manager_PenumbraResourceLoadEvent(PenumbraResourceLoadMessage msg)
|
||||
{
|
||||
var gamePath = msg.GamePath;
|
||||
var gamePath = msg.GamePath.ToLowerInvariant();
|
||||
var gameObject = msg.GameObject;
|
||||
var filePath = msg.FilePath;
|
||||
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
|
||||
@@ -166,14 +141,9 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
if (string.Equals(filePath, replacedGamePath, StringComparison.OrdinalIgnoreCase)) return;
|
||||
|
||||
if (TransientResources[gameObject].Contains(replacedGamePath) ||
|
||||
SemiTransientResources.Any(r => r.Value.Any(f =>
|
||||
string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))
|
||||
))
|
||||
SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f, gamePath, StringComparison.OrdinalIgnoreCase))))
|
||||
{
|
||||
Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath);
|
||||
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,19 +153,11 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement)
|
||||
{
|
||||
if (TransientResources.ContainsKey(gameObject))
|
||||
{
|
||||
TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => string.Equals(g, f, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
|
||||
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func<string, bool, FileReplacement> createFileReplacement)
|
||||
public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if (!TransientResources.TryGetValue(gameObject, out var resources))
|
||||
@@ -203,47 +165,16 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
SemiTransientResources[objectKind].RemoveWhere(p => !p.Verify());
|
||||
|
||||
var transientResources = resources.ToList();
|
||||
Logger.Debug("Persisting " + transientResources.Count + " transient resources");
|
||||
foreach (var gamePath in transientResources)
|
||||
{
|
||||
var existingResource = SemiTransientResources[objectKind].Any(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingResource)
|
||||
{
|
||||
Logger.Debug("Semi Transient resource replaced: " + gamePath);
|
||||
SemiTransientResources[objectKind].RemoveWhere(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: true);
|
||||
if (!fileReplacement.HasFileReplacement)
|
||||
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: false);
|
||||
if (fileReplacement.HasFileReplacement)
|
||||
{
|
||||
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
|
||||
if (SemiTransientResources[objectKind].Add(fileReplacement))
|
||||
{
|
||||
Logger.Debug("Added " + fileReplacement);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Debug("Not added " + fileReplacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue during transient file persistence", ex);
|
||||
}
|
||||
SemiTransientResources[objectKind].Add(gamePath);
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
|
||||
{
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
||||
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = fileReplacements;
|
||||
_configurationService.Save();
|
||||
}
|
||||
TransientResources[gameObject].Clear();
|
||||
@@ -256,22 +187,26 @@ public class TransientResourceManager : MediatorSubscriberBase, IDisposable
|
||||
SemiTransientResources.Clear();
|
||||
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
|
||||
{
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
|
||||
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = SemiTransientResources[ObjectKind.Player];
|
||||
_configurationService.Save();
|
||||
}
|
||||
}
|
||||
|
||||
internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item)
|
||||
internal void AddSemiTransientResource(ObjectKind objectKind, string item)
|
||||
{
|
||||
if (!SemiTransientResources.ContainsKey(objectKind))
|
||||
{
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
SemiTransientResources[objectKind] = new HashSet<string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if (!SemiTransientResources[objectKind].Any(f => string.Equals(f.ResolvedPath, item.ResolvedPath, StringComparison.OrdinalIgnoreCase)))
|
||||
SemiTransientResources[objectKind].Add(item.ToLowerInvariant());
|
||||
}
|
||||
|
||||
internal void ClearTransientPaths(IntPtr ptr, List<string> list)
|
||||
{
|
||||
if (TransientResources.TryGetValue(ptr, out var set))
|
||||
{
|
||||
SemiTransientResources[objectKind].Add(item);
|
||||
set.RemoveWhere(p => list.Contains(p, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,8 +94,8 @@ public class MarePlugin : MediatorSubscriberBase, IDisposable
|
||||
|
||||
_runtimeServiceScope?.Dispose();
|
||||
_runtimeServiceScope = _serviceProvider.CreateScope();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<CacheCreationService>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<TransientResourceManager>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<PlayerManager>();
|
||||
_runtimeServiceScope.ServiceProvider.GetRequiredService<OnlinePlayerManager>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>0.7.13</Version>
|
||||
<Version>0.7.14</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||
@@ -33,7 +33,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.2" />
|
||||
<PackageReference Include="Penumbra.Api" Version="1.0.6" />
|
||||
<PackageReference Include="Penumbra.Api" Version="1.0.7" />
|
||||
<PackageReference Include="Penumbra.String" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using MareSynchronos.Models;
|
||||
|
||||
namespace MareSynchronos.Mediator;
|
||||
|
||||
@@ -35,4 +36,7 @@ public record ResumeScanMessage(string Source) : IMessage;
|
||||
|
||||
public record NotificationMessage
|
||||
(string Title, string Message, NotificationType Type, uint TimeShownOnScreen = 3000) : IMessage;
|
||||
public record CreateCacheForObjectMessage(GameObjectHandler ObjectToCreateFor) : IMessage;
|
||||
public record CharacterDataCreatedMessage(CharacterData CharacterData) : IMessage;
|
||||
|
||||
#pragma warning restore MA0048 // File name must match type name
|
||||
@@ -1,6 +1,5 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.API.Data;
|
||||
|
||||
@@ -10,7 +9,7 @@ namespace MareSynchronos.Models;
|
||||
public class CharacterData
|
||||
{
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
||||
public Dictionary<ObjectKind, HashSet<FileReplacement>> FileReplacements { get; set; } = new();
|
||||
|
||||
[JsonProperty]
|
||||
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||
@@ -33,7 +32,7 @@ public class CharacterData
|
||||
{
|
||||
if (!fileReplacement.HasFileReplacement) return;
|
||||
|
||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new HashSet<FileReplacement>());
|
||||
|
||||
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, StringComparison.OrdinalIgnoreCase));
|
||||
if (existingReplacement != null)
|
||||
@@ -57,16 +56,9 @@ public class CharacterData
|
||||
};
|
||||
}).ToList());
|
||||
|
||||
Logger.Debug("Adding fileSwaps");
|
||||
foreach (var item in FileReplacements)
|
||||
{
|
||||
Logger.Debug("Checking fileSwaps for " + item.Key);
|
||||
var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto());
|
||||
Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps");
|
||||
foreach (var swap in fileSwapsToAdd)
|
||||
{
|
||||
Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath);
|
||||
}
|
||||
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,79 +1,31 @@
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Managers;
|
||||
using MareSynchronos.Utils;
|
||||
using MareSynchronos.API.Data;
|
||||
|
||||
namespace MareSynchronos.Models;
|
||||
|
||||
public class FileReplacement
|
||||
{
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly IpcManager _ipcManager;
|
||||
|
||||
public FileReplacement(FileCacheManager fileDbManager, IpcManager ipcManager)
|
||||
public FileReplacement(List<string> gamePaths, string filePath, FileCacheManager fileDbManager)
|
||||
{
|
||||
_fileDbManager = fileDbManager;
|
||||
_ipcManager = ipcManager;
|
||||
GamePaths = gamePaths.Select(g => g.Replace('\\', '/')).ToList();
|
||||
ResolvedPath = filePath.Replace('\\', '/');
|
||||
HashLazy = new(() => !IsFileSwap ? fileDbManager.GetFileCacheByPath(ResolvedPath)?.Hash ?? string.Empty : string.Empty);
|
||||
}
|
||||
|
||||
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
|
||||
|
||||
public List<string> GamePaths { get; set; } = new();
|
||||
public List<string> GamePaths { get; init; } = new();
|
||||
|
||||
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, StringComparison.Ordinal));
|
||||
|
||||
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths[0], ResolvedPath, StringComparison.Ordinal);
|
||||
|
||||
public string Hash { get; private set; } = string.Empty;
|
||||
public string Hash => HashLazy.Value;
|
||||
|
||||
public string ResolvedPath { get; set; } = string.Empty;
|
||||
private Lazy<string> HashLazy;
|
||||
|
||||
private void SetResolvedPath(string path)
|
||||
{
|
||||
ResolvedPath = path.ToLowerInvariant().Replace('\\', '/');
|
||||
if (!HasFileReplacement || IsFileSwap) return;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath)!;
|
||||
Hash = cache.Hash;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Could not set Hash for " + ResolvedPath + ", resetting to original", ex);
|
||||
ResolvedPath = GamePaths[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public bool Verify()
|
||||
{
|
||||
if (!IsFileSwap)
|
||||
{
|
||||
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath);
|
||||
if (cache == null)
|
||||
{
|
||||
Logger.Warn("Replacement Failed verification: " + GamePaths[0]);
|
||||
return false;
|
||||
}
|
||||
Hash = cache.Hash;
|
||||
return true;
|
||||
}
|
||||
|
||||
ResolvePath(GamePaths[0]);
|
||||
|
||||
var success = IsFileSwap;
|
||||
if (!success)
|
||||
{
|
||||
Logger.Warn("FileSwap Failed verification: " + GamePaths[0]);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
public string ResolvedPath { get; init; } = string.Empty;
|
||||
|
||||
public FileReplacementData ToFileReplacementDto()
|
||||
{
|
||||
@@ -87,20 +39,6 @@ public class FileReplacement
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal void ReverseResolvePath(string path)
|
||||
{
|
||||
GamePaths = _ipcManager.PenumbraReverseResolvePlayer(path).ToList();
|
||||
SetResolvedPath(path);
|
||||
}
|
||||
|
||||
internal void ResolvePath(string path)
|
||||
{
|
||||
GamePaths = new List<string> { path };
|
||||
SetResolvedPath(_ipcManager.PenumbraResolvePath(path));
|
||||
return $"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,15 @@ using System.Runtime.InteropServices;
|
||||
using MareSynchronos.Utils;
|
||||
using Penumbra.String;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.Mediator;
|
||||
|
||||
namespace MareSynchronos.Models;
|
||||
|
||||
public class PlayerRelatedObject
|
||||
public class GameObjectHandler : MediatorSubscriberBase
|
||||
{
|
||||
private readonly MareMediator _mediator;
|
||||
private readonly Func<IntPtr> getAddress;
|
||||
private readonly bool _sendUpdates;
|
||||
|
||||
public unsafe Character* Character => (Character*)Address;
|
||||
|
||||
@@ -31,26 +34,31 @@ public class PlayerRelatedObject
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
||||
public GameObjectHandler(MareMediator mediator, ObjectKind objectKind, Func<IntPtr> getAddress, bool sendUpdates = true) : base(mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
ObjectKind = objectKind;
|
||||
Address = address;
|
||||
DrawObjectAddress = drawObjectAddress;
|
||||
this.getAddress = getAddress;
|
||||
_sendUpdates = sendUpdates;
|
||||
_name = string.Empty;
|
||||
|
||||
Mediator.Subscribe<TransientResourceChangedMessage>(this, (msg) =>
|
||||
{
|
||||
var actualMsg = (TransientResourceChangedMessage)msg;
|
||||
if (actualMsg.Address != Address || !sendUpdates) return;
|
||||
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||
});
|
||||
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => CheckAndUpdateObject());
|
||||
}
|
||||
|
||||
public byte[] EquipSlotData { get; set; } = new byte[40];
|
||||
public byte[] CustomizeData { get; set; } = new byte[26];
|
||||
public byte? HatState { get; set; }
|
||||
public byte? VisorWeaponState { get; set; }
|
||||
private bool _doNotSendUpdate;
|
||||
|
||||
public bool HasTransientsUpdate { get; set; } = false;
|
||||
public bool HasUnprocessedUpdate { get; set; } = false;
|
||||
public bool DoNotSendUpdate { get; set; } = false;
|
||||
public bool IsProcessing { get; set; } = false;
|
||||
|
||||
public unsafe void CheckAndUpdateObject()
|
||||
public unsafe bool CheckAndUpdateObject()
|
||||
{
|
||||
var curPtr = CurrentAddress;
|
||||
if (curPtr != IntPtr.Zero)
|
||||
@@ -68,7 +76,10 @@ public class PlayerRelatedObject
|
||||
|
||||
Address = curPtr;
|
||||
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||
HasUnprocessedUpdate = true;
|
||||
if (_sendUpdates && !_doNotSendUpdate)
|
||||
Mediator.Publish(new CreateCacheForObjectMessage(this));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
||||
@@ -77,12 +88,14 @@ public class PlayerRelatedObject
|
||||
DrawObjectAddress = IntPtr.Zero;
|
||||
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
DoNotSendUpdate = false;
|
||||
_doNotSendUpdate = false;
|
||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||
{
|
||||
var data = Marshal.ReadByte((IntPtr)equipSlotData, i);
|
||||
@@ -107,10 +120,10 @@ public class PlayerRelatedObject
|
||||
var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0);
|
||||
if (newHatState != HatState)
|
||||
{
|
||||
if (HatState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
if (HatState != null && !hasChanges)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Hat changed");
|
||||
DoNotSendUpdate = true;
|
||||
_doNotSendUpdate = true;
|
||||
}
|
||||
HatState = newHatState;
|
||||
hasChanges = true;
|
||||
@@ -120,10 +133,10 @@ public class PlayerRelatedObject
|
||||
|
||||
if (newWeaponOrVisorState != VisorWeaponState)
|
||||
{
|
||||
if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate)
|
||||
if (VisorWeaponState != null && !hasChanges)
|
||||
{
|
||||
Logger.Debug("Not Sending Update, only Visor/Weapon changed");
|
||||
DoNotSendUpdate = true;
|
||||
_doNotSendUpdate = true;
|
||||
}
|
||||
VisorWeaponState = newWeaponOrVisorState;
|
||||
hasChanges = true;
|
||||
@@ -59,7 +59,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<PairManager>();
|
||||
collection.AddSingleton<ApiController>();
|
||||
collection.AddSingleton<PeriodicFileScanner>();
|
||||
collection.AddSingleton<FileReplacementFactory>();
|
||||
collection.AddSingleton<MareCharaFileManager>();
|
||||
collection.AddSingleton<NotificationService>();
|
||||
|
||||
@@ -70,9 +69,9 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<IntroUi>();
|
||||
collection.AddSingleton<DownloadUi>();
|
||||
|
||||
collection.AddScoped<CacheCreationService>();
|
||||
collection.AddScoped<TransientResourceManager>();
|
||||
collection.AddScoped<CharacterDataFactory>();
|
||||
collection.AddScoped<PlayerManager>();
|
||||
collection.AddScoped<OnlinePlayerManager>();
|
||||
|
||||
var serviceProvider = collection.BuildServiceProvider(new ServiceProviderOptions() { ValidateOnBuild = true, ValidateScopes = true });
|
||||
|
||||
@@ -57,7 +57,7 @@ public class SettingsUi : WindowMediatorSubscriberBase, IDisposable
|
||||
Mediator.Subscribe<SwitchToIntroUiMessage>(this, (_) => IsOpen = false);
|
||||
Mediator.Subscribe<GposeStartMessage>(this, (_) => UiShared_GposeStart());
|
||||
Mediator.Subscribe<GposeEndMessage>(this, (_) => UiShared_GposeEnd());
|
||||
Mediator.Subscribe<PlayerChangedMessage>(this, (msg) => LastCreatedCharacterData = ((PlayerChangedMessage)msg).Data);
|
||||
Mediator.Subscribe<CharacterDataCreatedMessage>(this, (msg) => LastCreatedCharacterData = ((CharacterDataCreatedMessage)msg).CharacterData.ToAPI());
|
||||
|
||||
windowSystem.AddWindow(this);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public partial class UiShared : IDisposable
|
||||
public string PlayerName => _dalamudUtil.PlayerName;
|
||||
public uint WorldId => _dalamudUtil.WorldId;
|
||||
public Dictionary<ushort, string> WorldData => _dalamudUtil.WorldData.Value;
|
||||
public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory() ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory());
|
||||
public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory);
|
||||
public bool EditTrackerPosition { get; set; }
|
||||
public ImFontPtr UidFont { get; private set; }
|
||||
public bool UidFontBuilt { get; private set; }
|
||||
@@ -468,7 +468,7 @@ public partial class UiShared : IDisposable
|
||||
{
|
||||
if (!success) return;
|
||||
|
||||
_isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(), StringComparison.Ordinal);
|
||||
_isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory?.ToLowerInvariant(), StringComparison.Ordinal);
|
||||
_isDirectoryWritable = IsDirectoryWritable(path);
|
||||
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40);
|
||||
_cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript);
|
||||
|
||||
@@ -226,11 +226,13 @@ public class DalamudUtil : IDisposable
|
||||
{
|
||||
if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
|
||||
|
||||
Logger.Verbose($"Starting wait for {name} to draw");
|
||||
|
||||
var obj = (GameObject*)characterAddress;
|
||||
const int tick = 250;
|
||||
int curWaitTime = 0;
|
||||
// ReSharper disable once LoopVariableIsNeverChangedInsideLoop
|
||||
while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
||||
while ((obj->DrawObject == null || (obj->RenderFlags & 0b100000000000) == 0b100000000000) && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
|
||||
{
|
||||
Logger.Verbose($"Waiting for {name} to finish drawing");
|
||||
curWaitTime += tick;
|
||||
|
||||
45
MareSynchronos/Utils/FileReplacementComparer.cs
Normal file
45
MareSynchronos/Utils/FileReplacementComparer.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using MareSynchronos.Models;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public class FileReplacementComparer : IEqualityComparer<FileReplacement>
|
||||
{
|
||||
public static FileReplacementComparer Instance => _instance;
|
||||
private static FileReplacementComparer _instance = new();
|
||||
private FileReplacementComparer() { }
|
||||
public bool Equals(FileReplacement? x, FileReplacement? y)
|
||||
{
|
||||
if (x == null || y == null) return false;
|
||||
return x.ResolvedPath.Equals(y.ResolvedPath) && CompareLists(x.GamePaths, y.GamePaths);
|
||||
}
|
||||
|
||||
public int GetHashCode(FileReplacement obj)
|
||||
{
|
||||
return HashCode.Combine(obj.ResolvedPath.GetHashCode(StringComparison.OrdinalIgnoreCase), GetOrderIndependentHashCode(obj.GamePaths));
|
||||
}
|
||||
|
||||
private static int GetOrderIndependentHashCode<T>(IEnumerable<T> source)
|
||||
{
|
||||
int hash = 0;
|
||||
foreach (T element in source)
|
||||
{
|
||||
hash = unchecked(hash +
|
||||
EqualityComparer<T>.Default.GetHashCode(element));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
private bool CompareLists(List<string> list1, List<string> list2)
|
||||
{
|
||||
if (list1.Count != list2.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < list1.Count; i++)
|
||||
{
|
||||
if (!string.Equals(list1[i], list2[i], StringComparison.OrdinalIgnoreCase))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user