0.4.2: fix heels integration, potentially fix crashes, delay handling of transient resource loads, change transients to concurrent dictionary
This commit is contained in:
@@ -3,8 +3,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
@@ -40,7 +38,7 @@ public class CharacterDataFactory
|
||||
return playerPointer == IntPtr.Zero || ((Character*)playerPointer)->GameObject.GetDrawObject() == null;
|
||||
}
|
||||
|
||||
public CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer, CancellationToken token)
|
||||
public CharacterData BuildCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
||||
{
|
||||
if (!_ipcManager.Initialized)
|
||||
{
|
||||
@@ -50,20 +48,20 @@ public class CharacterDataFactory
|
||||
bool pointerIsZero = true;
|
||||
try
|
||||
{
|
||||
pointerIsZero = CheckForPointer(playerPointer);
|
||||
pointerIsZero = CheckForPointer(playerRelatedObject.Address);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Could not create data for " + objectKind);
|
||||
Logger.Warn("Could not create data for " + playerRelatedObject.ObjectKind);
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace ?? string.Empty);
|
||||
}
|
||||
|
||||
if (pointerIsZero)
|
||||
{
|
||||
Logger.Verbose("Pointer was zero for " + objectKind);
|
||||
previousData.FileReplacements.Remove(objectKind);
|
||||
previousData.GlamourerString.Remove(objectKind);
|
||||
Logger.Verbose("Pointer was zero for " + playerRelatedObject.ObjectKind);
|
||||
previousData.FileReplacements.Remove(playerRelatedObject.ObjectKind);
|
||||
previousData.GlamourerString.Remove(playerRelatedObject.ObjectKind);
|
||||
return previousData;
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ public class CharacterDataFactory
|
||||
|
||||
try
|
||||
{
|
||||
return CreateCharacterData(previousData, objectKind, playerPointer, token);
|
||||
return CreateCharacterData(previousData, playerRelatedObject, token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -80,7 +78,7 @@ public class CharacterDataFactory
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warn("Failed to create " + objectKind + " data");
|
||||
Logger.Warn("Failed to create " + playerRelatedObject.ObjectKind + " data");
|
||||
Logger.Warn(e.Message);
|
||||
Logger.Warn(e.StackTrace ?? string.Empty);
|
||||
}
|
||||
@@ -232,8 +230,17 @@ public class CharacterDataFactory
|
||||
cache.AddFileReplacement(objectKind, texDx11Replacement);
|
||||
}
|
||||
|
||||
private unsafe CharacterData CreateCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer, CancellationToken token)
|
||||
private unsafe CharacterData CreateCharacterData(CharacterData previousData, PlayerRelatedObject playerRelatedObject, CancellationToken token)
|
||||
{
|
||||
var objectKind = playerRelatedObject.ObjectKind;
|
||||
var charaPointer = playerRelatedObject.Address;
|
||||
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
|
||||
if (playerRelatedObject.HasUnprocessedUpdate)
|
||||
{
|
||||
Logger.Debug("Handling unprocessed update for " + objectKind);
|
||||
|
||||
if (previousData.FileReplacements.ContainsKey(objectKind))
|
||||
{
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
@@ -252,12 +259,6 @@ public class CharacterDataFactory
|
||||
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing(objectKind.ToString(), charaPointer);
|
||||
|
||||
Stopwatch st = Stopwatch.StartNew();
|
||||
|
||||
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||
|
||||
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
|
||||
|
||||
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
|
||||
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
||||
{
|
||||
@@ -291,7 +292,13 @@ public class CharacterDataFactory
|
||||
|
||||
previousData.FileReplacements[objectKind].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(charaPointer);
|
||||
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
||||
|
||||
Logger.Debug("Handling transient update for " + objectKind);
|
||||
ManageSemiTransientData(previousData, objectKind, charaPointer);
|
||||
|
||||
st.Stop();
|
||||
@@ -387,8 +394,6 @@ public class CharacterDataFactory
|
||||
{
|
||||
transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
}
|
||||
|
||||
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
|
||||
}
|
||||
|
||||
private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
|
||||
|
||||
@@ -117,6 +117,17 @@ public class CachedPlayer
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (objectKind == ObjectKind.Player)
|
||||
{
|
||||
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
|
||||
if (heelsOffsetDifferent)
|
||||
{
|
||||
Logger.Debug("Updating " + objectKind);
|
||||
charaDataToUpdate.Add(objectKind);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cachedData = characterData;
|
||||
@@ -324,6 +335,7 @@ public class CachedPlayer
|
||||
{
|
||||
_ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter);
|
||||
_ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter);
|
||||
_ipcManager.HeelsRestoreOffsetForPlayer(PlayerCharacter);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -207,16 +207,30 @@ namespace MareSynchronos.Managers
|
||||
});
|
||||
}
|
||||
|
||||
public void HeelsRestoreOffsetForPlayer(IntPtr character)
|
||||
{
|
||||
if (!CheckHeelsApi()) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
{
|
||||
Logger.Verbose("Restoring Heels data to " + character.ToString("X"));
|
||||
_heelsUnregisterPlayer.InvokeAction(gameObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void GlamourerApplyAll(string? customization, IntPtr obj)
|
||||
{
|
||||
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(obj);
|
||||
if (gameObj != null)
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer applying for " + gameObj);
|
||||
_glamourerApplyAll!.InvokeAction(customization, gameObj);
|
||||
Logger.Verbose("Glamourer applying for " + c.Address.ToString("X"));
|
||||
_glamourerApplyAll!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -227,10 +241,10 @@ namespace MareSynchronos.Managers
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only equipment to " + character.ToString("X"));
|
||||
_glamourerApplyOnlyEquipment!.InvokeAction(customization, gameObj);
|
||||
Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyEquipment!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -241,10 +255,10 @@ namespace MareSynchronos.Managers
|
||||
actionQueue.Enqueue(() =>
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
Logger.Verbose("Glamourer apply only customization to " + character.ToString("X"));
|
||||
_glamourerApplyOnlyCustomization!.InvokeAction(customization, gameObj);
|
||||
Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X"));
|
||||
_glamourerApplyOnlyCustomization!.InvokeAction(customization, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -255,9 +269,9 @@ namespace MareSynchronos.Managers
|
||||
try
|
||||
{
|
||||
var gameObj = _dalamudUtil.CreateGameObject(character);
|
||||
if (gameObj != null)
|
||||
if (gameObj is Character c)
|
||||
{
|
||||
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(gameObj);
|
||||
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c);
|
||||
byte[] bytes = Convert.FromBase64String(glamourerString);
|
||||
// ignore transparency
|
||||
bytes[88] = 128;
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace MareSynchronos.Managers
|
||||
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
|
||||
|
||||
private CancellationTokenSource? _playerChangedCts = new();
|
||||
private CancellationTokenSource _transientUpdateCts = new();
|
||||
|
||||
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
|
||||
|
||||
@@ -69,8 +70,19 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
|
||||
{
|
||||
obj.HasUnprocessedUpdate = true;
|
||||
_transientUpdateCts.Cancel();
|
||||
_transientUpdateCts = new CancellationTokenSource();
|
||||
var token = _transientUpdateCts.Token;
|
||||
Task.Run(async () =>
|
||||
{
|
||||
Logger.Debug("Delaying transient resource load update");
|
||||
await Task.Delay(750, token);
|
||||
if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
|
||||
Logger.Debug("Firing transient resource load update");
|
||||
obj.HasTransientsUpdate = true;
|
||||
OnPlayerOrAttachedObjectsChanged();
|
||||
}, token);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +94,7 @@ namespace MareSynchronos.Managers
|
||||
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
|
||||
{
|
||||
Logger.Debug("Heels offset changed to " + change);
|
||||
playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasUnprocessedUpdate = true;
|
||||
playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasTransientsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,14 +141,15 @@ namespace MareSynchronos.Managers
|
||||
|
||||
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
|
||||
{
|
||||
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList())
|
||||
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
|
||||
{
|
||||
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
|
||||
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address, token);
|
||||
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
unprocessedObject.HasUnprocessedUpdate = false;
|
||||
unprocessedObject.IsProcessing = false;
|
||||
unprocessedObject.HasTransientsUpdate = false;
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
@@ -165,7 +178,6 @@ namespace MareSynchronos.Managers
|
||||
if (address == item.Address)
|
||||
{
|
||||
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
||||
//_transientResourceManager.CleanSemiTransientResources(item.ObjectKind);
|
||||
item.HasUnprocessedUpdate = true;
|
||||
}
|
||||
}
|
||||
@@ -178,7 +190,7 @@ namespace MareSynchronos.Managers
|
||||
|
||||
private void OnPlayerOrAttachedObjectsChanged()
|
||||
{
|
||||
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate).ToList();
|
||||
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
|
||||
foreach (var unprocessedObject in unprocessedObjects)
|
||||
{
|
||||
unprocessedObject.IsProcessing = true;
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
using MareSynchronos.Models;
|
||||
using MareSynchronos.Utils;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
{
|
||||
@@ -16,8 +19,9 @@ namespace MareSynchronos.Managers
|
||||
|
||||
public event TransientResourceLoadedEvent? TransientResourceLoaded;
|
||||
|
||||
private Dictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||
private Dictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
||||
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
|
||||
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
|
||||
private CancellationTokenSource transientInvokeDelayCts = new CancellationTokenSource();
|
||||
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil)
|
||||
{
|
||||
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
|
||||
@@ -42,7 +46,7 @@ namespace MareSynchronos.Managers
|
||||
if (!dalamudUtil.IsGameObjectPresent(item.Key))
|
||||
{
|
||||
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
|
||||
TransientResources.Remove(item.Key);
|
||||
TransientResources.TryRemove(item.Key, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -138,15 +142,25 @@ namespace MareSynchronos.Managers
|
||||
SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant());
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == item.ToLowerInvariant()))
|
||||
{
|
||||
Logger.Debug("Persisting " + item.ToLowerInvariant());
|
||||
|
||||
var fileReplacement = createFileReplacement(item.ToLowerInvariant(), true);
|
||||
if (!fileReplacement.HasFileReplacement)
|
||||
fileReplacement = createFileReplacement(item.ToLowerInvariant(), false);
|
||||
SemiTransientResources[objectKind].Add(fileReplacement);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn("Issue during transient file persistence");
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
TransientResources[gameObject].Clear();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>0.4.1</Version>
|
||||
<Version>0.4.2</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace MareSynchronos.Models
|
||||
|
||||
public void SetResolvedPath(string path)
|
||||
{
|
||||
ResolvedPath = path.ToLowerInvariant().Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/');
|
||||
ResolvedPath = path.ToLowerInvariant();//.Replace('/', '\\').Replace(_penumbraDirectory, "").Replace('\\', '/');
|
||||
if (!HasFileReplacement || IsFileSwap) return;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
|
||||
@@ -7,7 +7,7 @@ using Penumbra.GameData.ByteString;
|
||||
|
||||
namespace MareSynchronos.Models
|
||||
{
|
||||
internal class PlayerRelatedObject
|
||||
public class PlayerRelatedObject
|
||||
{
|
||||
private readonly Func<IntPtr> getAddress;
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace MareSynchronos.Models
|
||||
public byte? HatState { get; set; }
|
||||
public byte? VisorWeaponState { get; set; }
|
||||
|
||||
public bool HasTransientsUpdate { get; set; } = false;
|
||||
public bool HasUnprocessedUpdate { get; set; } = false;
|
||||
public bool DoNotSendUpdate { get; set; } = false;
|
||||
public bool IsProcessing { get; set; } = false;
|
||||
|
||||
Reference in New Issue
Block a user