From 833fbd0e253c307a907eca56981fceb1f142eb93 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 20 Jan 2025 10:29:51 +0100 Subject: [PATCH] GPose Together fixes safeguard pose/world data generation loops safeguard more fix gpose together charas getting their collection nuked going outside gpose --- .../CharaData/CharaDataCharacterHandler.cs | 14 +- .../CharaDataGposeTogetherManager.cs | 220 ++++++++++-------- .../Services/CharaData/CharaDataManager.cs | 2 +- 3 files changed, 131 insertions(+), 105 deletions(-) diff --git a/MareSynchronos/Services/CharaData/CharaDataCharacterHandler.cs b/MareSynchronos/Services/CharaData/CharaDataCharacterHandler.cs index e76d287..f9eee31 100644 --- a/MareSynchronos/Services/CharaData/CharaDataCharacterHandler.cs +++ b/MareSynchronos/Services/CharaData/CharaDataCharacterHandler.cs @@ -29,7 +29,7 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase { foreach (var chara in _handledCharaData) { - _ = RevertHandledChara(chara, reapplyPose: false); + _ = RevertHandledChara(chara); } }); @@ -56,11 +56,11 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase base.Dispose(disposing); foreach (var chara in _handledCharaData) { - _ = RevertHandledChara(chara, reapplyPose: false); + _ = RevertHandledChara(chara); } } - public async Task RevertChara(string name, Guid? cPlusId, bool reapplyPose = true) + public async Task RevertChara(string name, Guid? cPlusId) { Guid applicationId = Guid.NewGuid(); await _ipcManager.Glamourer.RevertByNameAsync(Logger, name, applicationId).ConfigureAwait(false); @@ -75,20 +75,20 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase await _ipcManager.Penumbra.RedrawAsync(Logger, handler, applicationId, CancellationToken.None).ConfigureAwait(false); } - public async Task RevertHandledChara(string name, bool reapplyPose = true) + public async Task RevertHandledChara(string name) { var handled = _handledCharaData.FirstOrDefault(f => string.Equals(f.Name, name, StringComparison.Ordinal)); if (handled == null) return false; _handledCharaData.Remove(handled); - await _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus, reapplyPose)).ConfigureAwait(false); + await _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus)).ConfigureAwait(false); return true; } - public Task RevertHandledChara(HandledCharaDataEntry? handled, bool reapplyPose = true) + public Task RevertHandledChara(HandledCharaDataEntry? handled) { if (handled == null) return Task.CompletedTask; _handledCharaData.Remove(handled); - return _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus, reapplyPose)); + return _dalamudUtilService.RunOnFrameworkThread(() => RevertChara(handled.Name, handled.CustomizePlus)); } internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry) diff --git a/MareSynchronos/Services/CharaData/CharaDataGposeTogetherManager.cs b/MareSynchronos/Services/CharaData/CharaDataGposeTogetherManager.cs index 3d13987..351f2f8 100644 --- a/MareSynchronos/Services/CharaData/CharaDataGposeTogetherManager.cs +++ b/MareSynchronos/Services/CharaData/CharaDataGposeTogetherManager.cs @@ -131,8 +131,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase _lastCreatedCharaData = (playerData, charaDataDownloadDto); } - _lastFullPoseData = null; - _lastWorldData = null; + ForceResendOwnData(); if (_lastCreatedCharaData != null) await _apiController.GposeLobbyPushCharacterData(_lastCreatedCharaData.Value.Dto) @@ -251,8 +250,6 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase node["OffHand"]![bone.Key]!["Rotation"] = $"{bone.Value.RotationX.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationY.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationZ.ToString(CultureInfo.InvariantCulture)}, {bone.Value.RotationW.ToString(CultureInfo.InvariantCulture)}"; } - Logger.LogTrace(node.ToJsonString(new System.Text.Json.JsonSerializerOptions() { WriteIndented = true })); - return node.ToJsonString(); } @@ -370,34 +367,47 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase if (!_dalamudUtil.IsInGpose) continue; if (_usersInLobby.Count == 0) continue; - var chara = await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false); - if (_dalamudUtil.IsInGpose) + try { - chara = (IPlayerCharacter?)(await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(chara.Name.TextValue, _dalamudUtil.IsInGpose).ConfigureAwait(false)); + var chara = await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false); + if (_dalamudUtil.IsInGpose) + { + chara = (IPlayerCharacter?)(await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(chara.Name.TextValue, _dalamudUtil.IsInGpose).ConfigureAwait(false)); + } + if (chara == null || chara.Address == nint.Zero) continue; + + var poseJson = await _brio.GetPoseAsync(chara.Address).ConfigureAwait(false); + if (string.IsNullOrEmpty(poseJson)) continue; + + var lastFullData = _poseGenerationExecutions++ >= 12 ? null : _lastFullPoseData; + lastFullData = _forceResendFullPose ? _lastFullPoseData : lastFullData; + + var poseData = CreatePoseDataFromJson(poseJson, lastFullData); + if (!poseData.IsDelta) + { + _lastFullPoseData = poseData; + _lastDeltaPoseData = null; + _poseGenerationExecutions = 0; + } + + bool deltaIsSame = _lastDeltaPoseData != null && + (poseData.Bones.Keys.All(k => _lastDeltaPoseData.Value.Bones.ContainsKey(k) + && poseData.Bones.Values.All(k => _lastDeltaPoseData.Value.Bones.ContainsValue(k)))); + + if (_forceResendFullPose || ((poseData.Bones.Any() || poseData.MainHand.Any() || poseData.OffHand.Any()) + && (!poseData.IsDelta || (poseData.IsDelta && !deltaIsSame)))) + { + _forceResendFullPose = false; + await _apiController.GposeLobbyPushPoseData(poseData).ConfigureAwait(false); + } + + if (poseData.IsDelta) + _lastDeltaPoseData = poseData; } - if (chara == null || chara.Address == nint.Zero) continue; - - var poseJson = await _brio.GetPoseAsync(chara.Address).ConfigureAwait(false); - if (string.IsNullOrEmpty(poseJson)) continue; - - var poseData = CreatePoseDataFromJson(poseJson, _poseGenerationExecutions++ >= 12 ? null : _lastFullPoseData); - if (!poseData.IsDelta) + catch (Exception ex) { - _lastFullPoseData = poseData; - _lastDeltaPoseData = null; - _poseGenerationExecutions = 0; + Logger.LogWarning(ex, "Error during Pose Data Generation"); } - - bool deltaIsSame = _lastDeltaPoseData != null && - (poseData.Bones.Keys.All(k => _lastDeltaPoseData.Value.Bones.ContainsKey(k) - && poseData.Bones.Values.All(k => _lastDeltaPoseData.Value.Bones.ContainsValue(k)))); - - if ((poseData.Bones.Any() || poseData.MainHand.Any() || poseData.OffHand.Any()) - && (!poseData.IsDelta || (poseData.IsDelta && !deltaIsSame))) - await _apiController.GposeLobbyPushPoseData(poseData).ConfigureAwait(false); - - if (poseData.IsDelta) - _lastDeltaPoseData = poseData; } } @@ -410,78 +420,86 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase // if there are no players in lobby, don't do anything if (_usersInLobby.Count == 0) continue; - // get own player data - var player = (Dalamud.Game.ClientState.Objects.Types.ICharacter?)(await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false)); - if (player == null) continue; - WorldData worldData; - if (_dalamudUtil.IsInGpose) + try { - player = await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(player.Name.TextValue, true).ConfigureAwait(false); + // get own player data + var player = (Dalamud.Game.ClientState.Objects.Types.ICharacter?)(await _dalamudUtil.GetPlayerCharacterAsync().ConfigureAwait(false)); if (player == null) continue; - worldData = (await _brio.GetTransformAsync(player.Address).ConfigureAwait(false)); - } - else - { - var rotQuaternion = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), player.Rotation); - worldData = new() + WorldData worldData; + if (_dalamudUtil.IsInGpose) { - PositionX = player.Position.X, - PositionY = player.Position.Y, - PositionZ = player.Position.Z, - RotationW = rotQuaternion.W, - RotationX = rotQuaternion.X, - RotationY = rotQuaternion.Y, - RotationZ = rotQuaternion.Z, - ScaleX = 1, - ScaleY = 1, - ScaleZ = 1 - }; - } - - var loc = await _dalamudUtil.GetMapDataAsync().ConfigureAwait(false); - worldData.LocationInfo = loc; - - if (worldData != _lastWorldData) - { - await _apiController.GposeLobbyPushWorldData(worldData).ConfigureAwait(false); - _lastWorldData = worldData; - Logger.LogTrace("WorldData (gpose: {gpose}): {data}", _dalamudUtil.IsInGpose, worldData); - } - - foreach (var entry in _usersInLobby) - { - if (!entry.Value.HasWorldDataUpdate || _dalamudUtil.IsInGpose) continue; - - var entryWorldData = entry.Value.WorldData!.Value; - - if (worldData.LocationInfo.MapId == entryWorldData.LocationInfo.MapId && worldData.LocationInfo.DivisionId == entryWorldData.LocationInfo.DivisionId - && (worldData.LocationInfo.HouseId != entryWorldData.LocationInfo.HouseId - || worldData.LocationInfo.WardId != entryWorldData.LocationInfo.WardId - || entryWorldData.LocationInfo.ServerId != worldData.LocationInfo.ServerId)) - { - if (entry.Value.SpawnedVfxId == null) - { - // spawn if it doesn't exist yet - entry.Value.LastWorldPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ); - entry.Value.SpawnedVfxId = await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.SpawnObject(entry.Value.LastWorldPosition.Value, - Quaternion.Identity, Vector3.One, 0.5f, 0.1f, 0.5f, 0.9f)).ConfigureAwait(false); - } - else - { - // move object via lerp if it does exist - var newPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ); - if (newPosition != entry.Value.LastWorldPosition) - { - entry.Value.UpdateStart = DateTime.UtcNow; - entry.Value.TargetWorldPosition = newPosition; - } - } + player = await _dalamudUtil.GetGposeCharacterFromObjectTableByNameAsync(player.Name.TextValue, true).ConfigureAwait(false); + if (player == null) continue; + worldData = (await _brio.GetTransformAsync(player.Address).ConfigureAwait(false)); } else { - await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.DespawnObject(entry.Value.SpawnedVfxId)).ConfigureAwait(false); - entry.Value.SpawnedVfxId = null; + var rotQuaternion = Quaternion.CreateFromAxisAngle(new Vector3(0, 1, 0), player.Rotation); + worldData = new() + { + PositionX = player.Position.X, + PositionY = player.Position.Y, + PositionZ = player.Position.Z, + RotationW = rotQuaternion.W, + RotationX = rotQuaternion.X, + RotationY = rotQuaternion.Y, + RotationZ = rotQuaternion.Z, + ScaleX = 1, + ScaleY = 1, + ScaleZ = 1 + }; } + + var loc = await _dalamudUtil.GetMapDataAsync().ConfigureAwait(false); + worldData.LocationInfo = loc; + + if (_forceResendWorldData || worldData != _lastWorldData) + { + _forceResendWorldData = false; + await _apiController.GposeLobbyPushWorldData(worldData).ConfigureAwait(false); + _lastWorldData = worldData; + Logger.LogTrace("WorldData (gpose: {gpose}): {data}", _dalamudUtil.IsInGpose, worldData); + } + + foreach (var entry in _usersInLobby) + { + if (!entry.Value.HasWorldDataUpdate || _dalamudUtil.IsInGpose || entry.Value.WorldData == null) continue; + + var entryWorldData = entry.Value.WorldData!.Value; + + if (worldData.LocationInfo.MapId == entryWorldData.LocationInfo.MapId && worldData.LocationInfo.DivisionId == entryWorldData.LocationInfo.DivisionId + && (worldData.LocationInfo.HouseId != entryWorldData.LocationInfo.HouseId + || worldData.LocationInfo.WardId != entryWorldData.LocationInfo.WardId + || entryWorldData.LocationInfo.ServerId != worldData.LocationInfo.ServerId)) + { + if (entry.Value.SpawnedVfxId == null) + { + // spawn if it doesn't exist yet + entry.Value.LastWorldPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ); + entry.Value.SpawnedVfxId = await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.SpawnObject(entry.Value.LastWorldPosition.Value, + Quaternion.Identity, Vector3.One, 0.5f, 0.1f, 0.5f, 0.9f)).ConfigureAwait(false); + } + else + { + // move object via lerp if it does exist + var newPosition = new Vector3(entryWorldData.PositionX, entryWorldData.PositionY, entryWorldData.PositionZ); + if (newPosition != entry.Value.LastWorldPosition) + { + entry.Value.UpdateStart = DateTime.UtcNow; + entry.Value.TargetWorldPosition = newPosition; + } + } + } + else + { + await _dalamudUtil.RunOnFrameworkThread(() => _vfxSpawnManager.DespawnObject(entry.Value.SpawnedVfxId)).ConfigureAwait(false); + entry.Value.SpawnedVfxId = null; + } + } + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Error during World Data Generation"); } } } @@ -523,6 +541,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase private void OnEnterGpose() { + ForceResendOwnData(); ResetOwnData(); foreach (var data in _usersInLobby.Values) { @@ -533,6 +552,7 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase private void OnExitGpose() { + ForceResendOwnData(); ResetOwnData(); foreach (var data in _usersInLobby.Values) { @@ -540,10 +560,18 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase } } + + private bool _forceResendFullPose = false; + private bool _forceResendWorldData = false; + + private void ForceResendOwnData() + { + _forceResendFullPose = true; + _forceResendWorldData = true; + } + private void ResetOwnData() { - _lastFullPoseData = null; - _lastDeltaPoseData = null; _poseGenerationExecutions = 0; _lastCreatedCharaData = null; } @@ -654,8 +682,6 @@ public class CharaDataGposeTogetherManager : DisposableMediatorSubscriberBase if (_usersInLobby.ContainsKey(userData.UID)) OnUserLeaveLobby(userData); _usersInLobby[userData.UID] = new(userData); - _lastFullPoseData = null; - _lastWorldData = null; _ = PushCharacterDownloadDto(); } diff --git a/MareSynchronos/Services/CharaData/CharaDataManager.cs b/MareSynchronos/Services/CharaData/CharaDataManager.cs index ac145c1..8a6a62f 100644 --- a/MareSynchronos/Services/CharaData/CharaDataManager.cs +++ b/MareSynchronos/Services/CharaData/CharaDataManager.cs @@ -1013,7 +1013,7 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase if (string.IsNullOrEmpty(handledActor)) return; UiBlockingComputation = Task.Run(async () => { - await _characterHandler.RevertHandledChara(handledActor, false).ConfigureAwait(false); + await _characterHandler.RevertHandledChara(handledActor).ConfigureAwait(false); var gposeChara = await _dalamudUtilService.GetGposeCharacterFromObjectTableByNameAsync(handledActor, true).ConfigureAwait(false); if (gposeChara != null) await _ipcManager.Brio.DespawnActorAsync(gposeChara.Address).ConfigureAwait(false);