various fixes and improvements for drawing checks and data sending

This commit is contained in:
rootdarkarchon
2023-05-03 21:59:44 +02:00
parent ae5f1e9527
commit 3448601b6f
10 changed files with 242 additions and 147 deletions

View File

@@ -185,9 +185,9 @@ public sealed class TransientResourceManager : DisposableMediatorSubscriberBase
Task.Run(() =>
{
Logger.LogDebug("Penumbra Mod Settings changed, verifying SemiTransientResources");
foreach (var item in SemiTransientResources)
foreach (var item in _playerRelatedPointers)
{
Mediator.Publish(new TransientResourceChangedMessage(_dalamudUtil.GetPlayerPointer()));
Mediator.Publish(new TransientResourceChangedMessage(item.Address));
}
});
}

View File

@@ -170,24 +170,30 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
public async Task CustomizePlusRevertAsync(IntPtr character)
{
if (!CheckCustomizePlusApi()) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is Character c)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _customizePlusRevert!.InvokeAction(c)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
{
Logger.LogTrace("CustomizePlus reverting for {chara}", c.Address.ToString("X"));
_customizePlusRevert!.InvokeAction(c);
}
}).ConfigureAwait(false);
}
public async Task CustomizePlusSetBodyScaleAsync(IntPtr character, string scale)
{
if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is Character c)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale));
Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
{
string decodedScale = Encoding.UTF8.GetString(Convert.FromBase64String(scale));
Logger.LogTrace("CustomizePlus applying for {chara}", c.Address.ToString("X"));
_customizePlusSetBodyScaleToCharacter!.InvokeAction(decodedScale, c);
}
}).ConfigureAwait(false);
}
public async Task<string> GetCustomizePlusScaleAsync()
@@ -198,10 +204,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
}
public float GetHeelsOffset()
public async Task<float> GetHeelsOffsetAsync()
{
if (!CheckHeelsApi()) return 0.0f;
return _heelsGetOffset.InvokeFunc();
return await _dalamudUtil.RunOnFrameworkThread(_heelsGetOffset.InvokeFunc).ConfigureAwait(false);
}
public async Task GlamourerApplyAllAsync(ILogger logger, GameObjectHandler handler, string? customization, Guid applicationId, CancellationToken token, bool fireAndForget = false)
@@ -210,15 +216,12 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
try
{
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
if (gameObj is Character c)
await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
{
await PenumbraRedrawAsync(logger, handler, applicationId, () =>
{
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId);
_glamourerApplyAll!.InvokeAction(customization, c);
}).ConfigureAwait(false);
}
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyAll", applicationId);
_glamourerApplyAll!.InvokeAction(customization, chara);
}).ConfigureAwait(false);
}
finally
{
@@ -232,20 +235,16 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
try
{
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
if (gameObj is Character c)
await PenumbraRedrawInternalAsync(logger, handler, applicationid, (chara) =>
{
await PenumbraRedrawAsync(logger, handler, applicationid, () =>
{
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid);
_glamourerApplyOnlyCustomization!.InvokeAction(customization, c);
}).ConfigureAwait(false);
await PenumbraRedrawAsync(logger, handler, applicationid, () =>
{
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid);
_glamourerApplyOnlyEquipment!.InvokeAction(equipment, c);
}).ConfigureAwait(false);
}
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyCustomization", applicationid);
_glamourerApplyOnlyCustomization!.InvokeAction(customization, chara);
}).ConfigureAwait(false);
await PenumbraRedrawInternalAsync(logger, handler, applicationid, (chara) =>
{
logger.LogDebug("[{appid}] Calling on IPC: GlamourerApplyOnlyEquipment", applicationid);
_glamourerApplyOnlyEquipment!.InvokeAction(equipment, chara);
}).ConfigureAwait(false);
}
finally
{
@@ -258,17 +257,20 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
if (!CheckGlamourerApi()) return string.Empty;
try
{
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is Character c)
return await _dalamudUtil.RunOnFrameworkThread(() =>
{
var glamourerString = await _dalamudUtil.RunOnFrameworkThread(() => _glamourerGetAllCustomization!.InvokeFunc(c)).ConfigureAwait(false);
byte[] bytes = Convert.FromBase64String(glamourerString);
// ignore transparency
bytes[88] = 128;
bytes[89] = 63;
return Convert.ToBase64String(bytes);
}
return string.Empty;
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
{
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c);
byte[] bytes = Convert.FromBase64String(glamourerString);
// ignore transparency
bytes[88] = 128;
bytes[89] = 63;
return Convert.ToBase64String(bytes);
}
return string.Empty;
}).ConfigureAwait(false);
}
catch
{
@@ -279,34 +281,43 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
public async Task HeelsRestoreOffsetForPlayerAsync(IntPtr character)
{
if (!CheckHeelsApi()) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj != null)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _heelsUnregisterPlayer.InvokeAction(gameObj)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
Logger.LogTrace("Restoring Heels data to {chara}", character.ToString("X"));
_heelsUnregisterPlayer.InvokeAction(gameObj);
}
}).ConfigureAwait(false);
}
public async Task HeelsSetOffsetForPlayerAsync(IntPtr character, float offset)
{
if (!CheckHeelsApi()) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj != null)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _heelsRegisterPlayer.InvokeAction(gameObj, offset)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
{
Logger.LogTrace("Applying Heels data to {chara}", character.ToString("X"));
_heelsRegisterPlayer.InvokeAction(gameObj, offset);
}
}).ConfigureAwait(false);
}
public async Task HonorificClearTitleAsync(nint character)
{
if (!CheckHonorificApi()) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is PlayerCharacter c)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _honorificClearCharacterTitle!.InvokeAction(c)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is PlayerCharacter c)
{
Logger.LogTrace("Honorific removing for {addr}", c.Address.ToString("X"));
_honorificClearCharacterTitle!.InvokeAction(c);
}
}).ConfigureAwait(false);
}
public string HonorificGetTitle()
@@ -320,10 +331,10 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
{
if (!CheckHonorificApi()) return;
Logger.LogTrace("Applying Honorific data to {chara}", character.ToString("X"));
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is PlayerCharacter pc)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
await _dalamudUtil.RunOnFrameworkThread(() =>
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is PlayerCharacter pc)
{
if (string.IsNullOrEmpty(honorificData))
{
@@ -333,8 +344,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
{
_honorificSetCharacterTitle!.InvokeAction(pc, honorificData[1..], honorificData[0] == '1');
}
}).ConfigureAwait(false);
}
}
}).ConfigureAwait(false);
}
public async Task<string> PalettePlusBuildPaletteAsync()
@@ -348,22 +359,25 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
public async Task PalettePlusRemovePaletteAsync(IntPtr character)
{
if (!CheckPalettePlusApi()) return;
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is Character c)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
Logger.LogTrace("PalettePlus removing for {addr}", c.Address.ToString("X"));
await _dalamudUtil.RunOnFrameworkThread(() => _palettePlusRemoveCharaPalette!.InvokeAction(c)).ConfigureAwait(false);
}
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
{
Logger.LogTrace("PalettePlus removing for {addr}", c.Address.ToString("X"));
_palettePlusRemoveCharaPalette!.InvokeAction(c);
}
}).ConfigureAwait(false);
}
public async Task PalettePlusSetPaletteAsync(IntPtr character, string palette)
{
if (!CheckPalettePlusApi()) return;
string decodedPalette = Encoding.UTF8.GetString(Convert.FromBase64String(palette));
var gameObj = await _dalamudUtil.CreateGameObjectAsync(character).ConfigureAwait(false);
if (gameObj is Character c)
await _dalamudUtil.RunOnFrameworkThread(() =>
{
await _dalamudUtil.RunOnFrameworkThread(() =>
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
{
if (string.IsNullOrEmpty(decodedPalette))
{
@@ -375,8 +389,8 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
Logger.LogTrace("PalettePlus applying for {addr}", c.Address.ToString("X"));
_palettePlusSetCharaPalette!.InvokeAction(c, decodedPalette);
}
}).ConfigureAwait(false);
}
}
}).ConfigureAwait(false);
}
public string PenumbraGetMetaManipulations()
@@ -385,21 +399,17 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
return _penumbraGetMetaManipulations.Invoke();
}
public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token, bool fireAndForget = false)
public async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, CancellationToken token)
{
if (!CheckPenumbraApi() || _dalamudUtil.IsZoning) return;
try
{
await _redrawSemaphore.WaitAsync(token).ConfigureAwait(false);
var gameObj = await _dalamudUtil.CreateGameObjectAsync(handler.Address).ConfigureAwait(false);
if (gameObj is Character c)
await PenumbraRedrawInternalAsync(logger, handler, applicationId, (chara) =>
{
await PenumbraRedrawAsync(logger, handler, applicationId, () =>
{
logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId);
_penumbraRedrawObject!.Invoke(c, RedrawType.Redraw);
}).ConfigureAwait(false);
}
logger.LogDebug("[{appid}] Calling on IPC: PenumbraRedraw", applicationId);
_penumbraRedrawObject!.Invoke(chara, RedrawType.Redraw);
}).ConfigureAwait(false);
}
finally
{
@@ -442,6 +452,7 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
logger.LogTrace("[{applicationId}] Change: {from} => {to}", applicationId, mod.Key, mod.Value);
}
logger.LogTrace("[{applicationId}] Manip: {data}", applicationId, manipulationData);
var ret2 = _penumbraAddTemporaryMod.Invoke("MareChara", collName, modPaths, manipulationData, 0);
logger.LogTrace("[{applicationId}] Setting temp mods for {collName}, Success: {ret2}", applicationId, collName, ret2);
}).ConfigureAwait(false);
@@ -650,29 +661,27 @@ public sealed class IpcManager : DisposableMediatorSubscriberBase
_penumbraRedraw!.Invoke("self", RedrawType.Redraw);
}
private async Task PenumbraRedrawAsync(ILogger logger, GameObjectHandler obj, Guid applicationId, Action action)
private async Task PenumbraRedrawInternalAsync(ILogger logger, GameObjectHandler handler, Guid applicationId, Action<Character> action)
{
Mediator.Publish(new PenumbraStartRedrawMessage(obj.Address));
Mediator.Publish(new PenumbraStartRedrawMessage(handler.Address));
_penumbraRedrawRequests[obj.Address] = true;
_penumbraRedrawRequests[handler.Address] = true;
try
{
CancellationTokenSource cancelToken = new CancellationTokenSource();
cancelToken.CancelAfter(TimeSpan.FromSeconds(15));
await obj.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token);
await Task.Delay(TimeSpan.FromSeconds(1), _disposalCts.Token).ConfigureAwait(false);
await handler.ActOnFrameworkAfterEnsureNoDrawAsync(action, cancelToken.Token).ConfigureAwait(false);
if (!_disposalCts.Token.IsCancellationRequested)
await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, obj, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false);
await _dalamudUtil.WaitWhileCharacterIsDrawing(logger, handler, applicationId, 30000, _disposalCts.Token).ConfigureAwait(false);
}
finally
{
_penumbraRedrawRequests[obj.Address] = false;
_penumbraRedrawRequests[handler.Address] = false;
}
Mediator.Publish(new PenumbraEndRedrawMessage(obj.Address));
Mediator.Publish(new PenumbraEndRedrawMessage(handler.Address));
}
private void PeriodicApiStateCheck()

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Authors></Authors>
<Company></Company>
<Version>0.8.32</Version>
<Version>0.8.33</Version>
<Description></Description>
<Copyright></Copyright>
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>

View File

@@ -375,7 +375,7 @@ public class PlayerDataFactory
// gather up data from ipc
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
previousData.HeelsOffset = _ipcManager.GetHeelsOffset();
Task<float> getHeelsOffset = _ipcManager.GetHeelsOffsetAsync();
Task<string> getGlamourerData = _ipcManager.GlamourerGetCharacterCustomizationAsync(playerRelatedObject.Address);
Task<string> getCustomizeData = _ipcManager.GetCustomizePlusScaleAsync();
Task<string> getPalettePlusData = _ipcManager.PalettePlusBuildPaletteAsync();
@@ -387,6 +387,8 @@ public class PlayerDataFactory
_logger.LogDebug("Palette is now: {data}", previousData.PalettePlusPalette);
previousData.HonorificData = _ipcManager.HonorificGetTitle();
_logger.LogDebug("Honorific is now: {data}", previousData.HonorificData);
previousData.HeelsOffset = await getHeelsOffset.ConfigureAwait(false);
_logger.LogDebug("Heels is now: {heels}", previousData.HeelsOffset);
st.Stop();
_logger.LogInformation("Building character data for {obj} took {time}ms", objectKind, TimeSpan.FromTicks(st.ElapsedTicks).TotalMilliseconds);

View File

@@ -1,6 +1,4 @@
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Logging;
@@ -61,6 +59,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
Mediator.Subscribe<CutsceneEndMessage>(this, (_) =>
{
_haltProcessing = false;
ZoneSwitchEnd();
});
Mediator.Subscribe<PenumbraStartRedrawMessage>(this, (msg) =>
{
@@ -93,12 +92,16 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private IntPtr DrawObjectAddress { get; set; }
private byte[] EquipSlotData { get; set; } = new byte[40];
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action act, CancellationToken token)
public async Task ActOnFrameworkAfterEnsureNoDrawAsync(Action<Dalamud.Game.ClientState.Objects.Types.Character> act, CancellationToken token)
{
while (await _dalamudUtil.RunOnFrameworkThread(() =>
{
if (IsBeingDrawn()) return true;
act();
var gameObj = _dalamudUtil.CreateGameObject(Address);
if (gameObj is Dalamud.Game.ClientState.Objects.Types.Character chara)
{
act.Invoke(chara);
}
return false;
}).ConfigureAwait(false))
{
@@ -117,6 +120,12 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
return _dalamudUtil.CreateGameObject(Address);
}
public void Invalidate()
{
Address = IntPtr.Zero;
DrawObjectAddress = IntPtr.Zero;
}
public async Task<bool> IsBeingDrawnRunOnFrameworkAsync()
{
return await _dalamudUtil.RunOnFrameworkThread(IsBeingDrawn).ConfigureAwait(false);
@@ -146,7 +155,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
Address = _getAddress();
if (Address != IntPtr.Zero)
{
var drawObjAddr = (IntPtr)((GameObject*)Address)->DrawObject;
var drawObjAddr = (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address)->DrawObject;
DrawObjectAddress = drawObjAddr;
}
else
@@ -165,7 +174,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
_clearCts?.Cancel();
_clearCts = null;
}
var chara = (Character*)Address;
var chara = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Address;
var name = new ByteString(chara->GameObject.Name).ToString();
bool nameChange = !string.Equals(name, Name, StringComparison.Ordinal);
Name = name;
@@ -259,11 +268,17 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
private unsafe IntPtr GetDrawObj(nint curPtr)
{
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Getting new DrawObject", this);
return (IntPtr)((GameObject*)curPtr)->DrawObject;
return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->DrawObject;
}
private bool IsBeingDrawn()
{
if (_dalamudUtil.IsAnythingDrawing)
{
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Global draw block", this);
return true;
}
var curPtr = _getAddress();
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, CurPtr: {ptr}", this, curPtr.ToString("X"));
@@ -289,7 +304,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
var drawObjZero = drawObj == IntPtr.Zero;
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition IsDrawObjZero: {cond}", this, drawObjZero);
if (drawObjZero) return true;
var renderFlags = (((GameObject*)curPtr)->RenderFlags) != 0x0;
var renderFlags = (((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags) != 0x0;
Logger.LogTrace("[{this}] IsBeingDrawnRunOnFramework, Condition RenderFlags: {cond}", this, renderFlags);
if (renderFlags) return true;
var modelInSlotLoaded = (((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0);
@@ -303,7 +318,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase
}
return drawObj == IntPtr.Zero
|| ((GameObject*)curPtr)->RenderFlags != 0x0;
|| ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)curPtr)->RenderFlags != 0x0;
}
private void ZoneSwitchEnd()

View File

@@ -14,10 +14,8 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Files;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
using ObjectKind = MareSynchronos.API.Data.Enum.ObjectKind;
namespace MareSynchronos.PlayerData.Pairs;
@@ -55,6 +53,12 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
_lifetime = lifetime;
_fileDbManager = fileDbManager;
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (_) =>
{
_charaHandler?.Invalidate();
IsVisible = false;
}
);
_pluginWarnings ??= new()
{
ShownCustomizePlusWarning = mareConfigService.Current.DisableOptionalPluginWarnings,
@@ -445,16 +449,14 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
if (updateModdedPaths && (moddedPaths.Any() || !string.IsNullOrEmpty(charaData.ManipulationData)))
{
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() => _ipcManager
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync((_) => _ipcManager
.PenumbraRemoveTemporaryCollectionAsync(Logger, _applicationId, PlayerName!)
.ConfigureAwait(true).GetAwaiter().GetResult(), token).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync(() =>
await _charaHandler!.ActOnFrameworkAfterEnsureNoDrawAsync((chara) =>
{
var gameObj = _charaHandler!.GetGameObject();
if (gameObj == null) return;
var objTableIndex = gameObj.ObjectTableIndex();
var objTableIndex = chara.ObjectTableIndex();
_ipcManager.PenumbraSetTemporaryModsAsync(Logger, _applicationId, PlayerName!, objTableIndex, moddedPaths, charaData.ManipulationData)
.ConfigureAwait(true).GetAwaiter().GetResult();
}, token).ConfigureAwait(false);
@@ -510,6 +512,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
_framesSinceNotVisible++;
if (_framesSinceNotVisible > 30)
{
_framesSinceNotVisible = 30;
IsVisible = false;
Logger.LogTrace("{this} visibility changed, now: {visi}", this, IsVisible);
}
@@ -627,7 +630,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
if (minionOrMount != IntPtr.Zero)
{
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => minionOrMount, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);
}
}
else if (objectKind == ObjectKind.Pet)
@@ -636,7 +639,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
if (pet != IntPtr.Zero)
{
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => pet, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);
}
}
else if (objectKind == ObjectKind.Companion)
@@ -645,7 +648,7 @@ public sealed class CachedPlayer : DisposableMediatorSubscriberBase
if (companion != IntPtr.Zero)
{
using GameObjectHandler tempHandler = await _gameObjectHandlerFactory.Create(ObjectKind.Pet, () => companion, false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token, fireAndForget: false).ConfigureAwait(false);
await _ipcManager.PenumbraRedrawAsync(Logger, tempHandler, applicationId, cancelToken.Token).ConfigureAwait(false);
}
}
}

View File

@@ -41,6 +41,7 @@ public class OnlinePlayerManager : DisposableMediatorSubscriberBase
}
});
Mediator.Subscribe<CachedPlayerVisibleMessage>(this, (msg) => _newVisiblePlayers.Add(msg.Player));
Mediator.Subscribe<ConnectedMessage>(this, (_) => PushCharacterData(_pairManager.GetVisibleUsers()));
}
private void FrameworkOnUpdate()

View File

@@ -18,6 +18,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
private readonly Dictionary<ObjectKind, GameObjectHandler> _playerRelatedObjects = new();
private Task? _cacheCreationTask;
private CancellationTokenSource _honorificCts = new();
private bool _isZoning = false;
private CancellationTokenSource _palettePlusCts = new();
public CacheCreationService(ILogger<CacheCreationService> logger, MareMediator mediator, GameObjectHandlerFactory gameObjectHandlerFactory,
@@ -27,7 +28,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
Mediator.Subscribe<CreateCacheForObjectMessage>(this, (msg) =>
{
Logger.LogDebug("Received CreateCacheForObject for {handler}, updating player", msg.ObjectToCreateFor);
Logger.LogDebug("Received CreateCacheForObject for {handler}, updating", msg.ObjectToCreateFor);
_cacheCreateLock.Wait();
_cachesToCreate[msg.ObjectToCreateFor.ObjectKind] = msg.ObjectToCreateFor;
_cacheCreateLock.Release();
@@ -37,25 +38,32 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
{
Task.Run(() =>
{
Logger.LogTrace("Clearing cache for {obj}", msg.ObjectToCreateFor);
_playerData.FileReplacements.Remove(msg.ObjectToCreateFor.ObjectKind);
_playerData.GlamourerString.Remove(msg.ObjectToCreateFor.ObjectKind);
Mediator.Publish(new CharacterDataCreatedMessage(_playerData.ToAPI()));
});
});
Mediator.Subscribe<ZoneSwitchStartMessage>(this, (msg) => _isZoning = true);
Mediator.Subscribe<ZoneSwitchEndMessage>(this, (msg) => _isZoning = false);
Mediator.Subscribe<DelayedFrameworkUpdateMessage>(this, (msg) => ProcessCacheCreation());
Mediator.Subscribe<CustomizePlusMessage>(this, async (_) =>
{
if (_isZoning) return;
Logger.LogDebug("Received CustomizePlus change, updating player");
await AddPlayerCacheToCreate().ConfigureAwait(false);
});
Mediator.Subscribe<HeelsOffsetMessage>(this, async (_) =>
{
if (_isZoning) return;
Logger.LogDebug("Received Heels Offset change, updating player");
await AddPlayerCacheToCreate().ConfigureAwait(false);
});
Mediator.Subscribe<PalettePlusMessage>(this, (msg) =>
{
if (_isZoning) return;
if (msg.Character.Address == _playerRelatedObjects[ObjectKind.Player].Address)
{
Logger.LogDebug("Received PalettePlus change, updating player");
@@ -64,6 +72,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
});
Mediator.Subscribe<HonorificMessage>(this, (msg) =>
{
if (_isZoning) return;
if (!string.Equals(msg.NewHonorificTitle, _playerData.HonorificData, StringComparison.Ordinal))
{
Logger.LogDebug("Received Honorific change, updating player");
@@ -76,7 +85,7 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
await AddPlayerCacheToCreate().ConfigureAwait(false);
});
_playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, () => dalamudUtil.GetPlayerPointer(), true)
_playerRelatedObjects[ObjectKind.Player] = gameObjectHandlerFactory.Create(ObjectKind.Player, dalamudUtil.GetPlayerPointer, true)
.GetAwaiter().GetResult();
_playerRelatedObjects[ObjectKind.MinionOrMount] = gameObjectHandlerFactory.Create(ObjectKind.MinionOrMount, () => dalamudUtil.GetMinionOrMount(), true)
.GetAwaiter().GetResult();
@@ -131,6 +140,8 @@ public sealed class CacheCreationService : DisposableMediatorSubscriberBase
private void ProcessCacheCreation()
{
if (_isZoning) return;
if (_cachesToCreate.Any() && (_cacheCreationTask?.IsCompleted ?? true))
{
_cacheCreateLock.Wait();

View File

@@ -6,12 +6,14 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.Gui;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Numerics;
using System.Runtime.CompilerServices;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Services;
@@ -53,6 +55,7 @@ public class DalamudUtilService : IHostedService
public unsafe GameObject* GposeTarget => TargetSystem.Instance()->GPoseTarget;
public unsafe Dalamud.Game.ClientState.Objects.Types.GameObject? GposeTargetGameObject => GposeTarget == null ? null : _objectTable[GposeTarget->ObjectIndex];
public bool IsAnythingDrawing { get; private set; } = false;
public bool IsInCutscene { get; private set; } = false;
public bool IsInGpose { get; private set; } = false;
public bool IsLoggedIn { get; private set; }
@@ -171,6 +174,11 @@ public class DalamudUtilService : IHostedService
return _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
}
public async Task<IntPtr> GetPlayerPointerAsync()
{
return await RunOnFrameworkThread(GetPlayerPointer).ConfigureAwait(false);
}
public uint GetWorldId()
{
EnsureIsOnFramework();
@@ -198,35 +206,43 @@ public class DalamudUtilService : IHostedService
return await RunOnFrameworkThread(() => IsObjectPresent(obj)).ConfigureAwait(false);
}
public async Task RunOnFrameworkThread(Action act)
public async Task RunOnFrameworkThread(Action act, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
{
if (!_framework.IsInFrameworkUpdateThread)
var fileName = Path.GetFileNameWithoutExtension(callerFilePath);
await _performanceCollector.LogPerformance(this, "RunOnFramework:Act/" + fileName + ">" + callerMember + ":" + callerLineNumber, async () =>
{
await _framework.RunOnFrameworkThread(act).ContinueWith((_) => Task.CompletedTask).ConfigureAwait(false);
while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered
if (!_framework.IsInFrameworkUpdateThread)
{
_logger.LogTrace("Still on framework");
await Task.Delay(1).ConfigureAwait(false);
await _framework.RunOnFrameworkThread(act).ContinueWith((_) => Task.CompletedTask).ConfigureAwait(false);
while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered
{
_logger.LogTrace("Still on framework");
await Task.Delay(1).ConfigureAwait(false);
}
}
}
else
act();
else
act();
}).ConfigureAwait(false);
}
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
public async Task<T> RunOnFrameworkThread<T>(Func<T> func, [CallerMemberName] string callerMember = "", [CallerFilePath] string callerFilePath = "", [CallerLineNumber] int callerLineNumber = 0)
{
if (!_framework.IsInFrameworkUpdateThread)
var fileName = Path.GetFileNameWithoutExtension(callerFilePath);
return await _performanceCollector.LogPerformance(this, "RunOnFramework:Func<" + typeof(T) + ">/" + fileName + ">" + callerMember + ":" + callerLineNumber, async () =>
{
var result = await _framework.RunOnFrameworkThread(func).ContinueWith((task) => task.Result).ConfigureAwait(false);
while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered
if (!_framework.IsInFrameworkUpdateThread)
{
_logger.LogTrace("Still on framework");
await Task.Delay(1).ConfigureAwait(false);
var result = await _framework.RunOnFrameworkThread(func).ContinueWith((task) => task.Result).ConfigureAwait(false);
while (_framework.IsInFrameworkUpdateThread) // yield the thread again, should technically never be triggered
{
_logger.LogTrace("Still on framework");
await Task.Delay(1).ConfigureAwait(false);
}
return result;
}
return result;
}
return func.Invoke();
return func.Invoke();
}).ConfigureAwait(false);
}
public Task StartAsync(CancellationToken cancellationToken)
@@ -318,9 +334,35 @@ public class DalamudUtilService : IHostedService
{
if (_clientState.LocalPlayer?.IsDead ?? false) return;
IsAnythingDrawing = false;
_playerCharas = _performanceCollector.LogPerformance(this, "ObjTableToCharas",
() => _objectTable.OfType<PlayerCharacter>().Where(o => o.ObjectIndex < 240)
.ToDictionary(p => p.GetHash256(), p => (p.Name.ToString(), p.Address), StringComparer.Ordinal));
.ToDictionary(p => p.GetHash256(), p =>
{
if (!IsAnythingDrawing)
{
var gameObj = (GameObject*)p.Address;
bool isDrawing = gameObj->RenderFlags == 0b100000000000;
if (!isDrawing)
{
var drawObj = gameObj->DrawObject;
if ((nint)drawObj != IntPtr.Zero)
{
isDrawing = ((CharacterBase*)drawObj)->HasModelInSlotLoaded != 0;
if (!isDrawing)
{
isDrawing = ((CharacterBase*)drawObj)->HasModelFilesInSlotLoaded != 0;
}
}
}
if (isDrawing)
{
IsAnythingDrawing = true;
}
}
return (p.Name.ToString(), p.Address);
}, StringComparer.Ordinal));
if (GposeTarget != null && !IsInGpose)
{

View File

@@ -168,7 +168,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
await _mareHub.StartAsync(token).ConfigureAwait(false);
await InitializeData().ConfigureAwait(false);
InitializeApiHooks();
await LoadIninitialPairs().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
@@ -198,6 +199,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
$"Please keep your Mare Synchronos client up-to-date.",
Dalamud.Interface.Internal.Notifications.NotificationType.Error));
}
await LoadOnlinePairs().ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
@@ -283,7 +286,7 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
ServerState = ServerState.Offline;
}
private async Task InitializeData()
private void InitializeApiHooks()
{
if (_mareHub == null) return;
@@ -311,6 +314,16 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
OnGroupSendFullInfo((dto) => Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => Client_GroupSendInfo(dto));
_healthCheckTokenSource?.Cancel();
_healthCheckTokenSource?.Dispose();
_healthCheckTokenSource = new CancellationTokenSource();
_ = ClientHealthCheck(_healthCheckTokenSource.Token);
_initialized = true;
}
private async Task LoadIninitialPairs()
{
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
Logger.LogDebug("Individual Pair: {userPair}", userPair);
@@ -330,18 +343,15 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
_pairManager.AddGroupPair(user);
}
}
}
private async Task LoadOnlinePairs()
{
foreach (var entry in await UserGetOnlinePairs().ConfigureAwait(false))
{
Logger.LogDebug("Pair online: {pair}", entry);
_pairManager.MarkPairOnline(entry, sendNotif: false);
}
_healthCheckTokenSource?.Cancel();
_healthCheckTokenSource?.Dispose();
_healthCheckTokenSource = new CancellationTokenSource();
_ = ClientHealthCheck(_healthCheckTokenSource.Token);
_initialized = true;
}
private void MareHubOnClosed(Exception? arg)
@@ -364,13 +374,15 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM
ServerState = ServerState.Connecting;
try
{
await InitializeData().ConfigureAwait(false);
InitializeApiHooks();
await LoadIninitialPairs().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
{
await StopConnection(ServerState.VersionMisMatch).ConfigureAwait(false);
return;
}
await LoadOnlinePairs().ConfigureAwait(false);
ServerState = ServerState.Connected;
}
catch (Exception ex)