From 1a89c2caeef874c00b816faea4bbd31fedfa248c Mon Sep 17 00:00:00 2001 From: azyges <229218900+azyges@users.noreply.github.com> Date: Wed, 22 Oct 2025 03:20:13 +0900 Subject: [PATCH 1/2] some caching stuff and bug fixes --- LightlessSync/Services/CharacterAnalyzer.cs | 58 +++++- LightlessSync/UI/CompactUI.cs | 144 +++++++------- LightlessSync/UI/Components/DrawUserPair.cs | 187 ++++++++++++++---- LightlessSync/UI/Handlers/IdDisplayHandler.cs | 2 +- LightlessSync/Utils/SeStringUtils.cs | 44 ++++- 5 files changed, 316 insertions(+), 119 deletions(-) diff --git a/LightlessSync/Services/CharacterAnalyzer.cs b/LightlessSync/Services/CharacterAnalyzer.cs index c35fd01..27235f6 100644 --- a/LightlessSync/Services/CharacterAnalyzer.cs +++ b/LightlessSync/Services/CharacterAnalyzer.cs @@ -6,7 +6,11 @@ using LightlessSync.UI; using LightlessSync.Utils; using Lumina.Data.Files; using Microsoft.Extensions.Logging; - +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace LightlessSync.Services; public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable @@ -16,6 +20,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable private CancellationTokenSource? _analysisCts; private CancellationTokenSource _baseAnalysisCts = new(); private string _lastDataHash = string.Empty; + private CharacterAnalysisSummary _latestSummary = CharacterAnalysisSummary.Empty; public CharacterAnalyzer(ILogger logger, LightlessMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer) : base(logger, mediator) @@ -34,6 +39,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable public bool IsAnalysisRunning => _analysisCts != null; public int TotalFiles { get; internal set; } internal Dictionary> LastAnalysis { get; } = []; + public CharacterAnalysisSummary LatestSummary => _latestSummary; public void CancelAnalyze() { @@ -80,6 +86,8 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable } } + RecalculateSummary(); + Mediator.Publish(new CharacterDataAnalyzedMessage()); _analysisCts.CancelDispose(); @@ -137,11 +145,39 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable LastAnalysis[obj.Key] = data; } + RecalculateSummary(); + Mediator.Publish(new CharacterDataAnalyzedMessage()); _lastDataHash = charaData.DataHash.Value; } + private void RecalculateSummary() + { + var builder = ImmutableDictionary.CreateBuilder(); + + foreach (var (objectKind, entries) in LastAnalysis) + { + long totalTriangles = 0; + long texOriginalBytes = 0; + long texCompressedBytes = 0; + + foreach (var entry in entries.Values) + { + totalTriangles += entry.Triangles; + if (string.Equals(entry.FileType, "tex", StringComparison.OrdinalIgnoreCase)) + { + texOriginalBytes += entry.OriginalSize; + texCompressedBytes += entry.CompressedSize; + } + } + + builder[objectKind] = new CharacterAnalysisObjectSummary(entries.Count, totalTriangles, texOriginalBytes, texCompressedBytes); + } + + _latestSummary = new CharacterAnalysisSummary(builder.ToImmutable()); + } + private void PrintAnalysis() { if (LastAnalysis.Count == 0) return; @@ -232,4 +268,24 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable } }); } +} + +public readonly record struct CharacterAnalysisObjectSummary(int EntryCount, long TotalTriangles, long TexOriginalBytes, long TexCompressedBytes) +{ + public bool HasEntries => EntryCount > 0; +} + +public sealed class CharacterAnalysisSummary +{ + public static CharacterAnalysisSummary Empty { get; } = + new(ImmutableDictionary.Empty); + + internal CharacterAnalysisSummary(IImmutableDictionary objects) + { + Objects = objects; + } + + public IImmutableDictionary Objects { get; } + + public bool HasData => Objects.Any(kvp => kvp.Value.HasEntries); } \ No newline at end of file diff --git a/LightlessSync/UI/CompactUI.cs b/LightlessSync/UI/CompactUI.cs index c264681..0700de3 100644 --- a/LightlessSync/UI/CompactUI.cs +++ b/LightlessSync/UI/CompactUI.cs @@ -56,7 +56,6 @@ public class CompactUi : WindowMediatorSubscriberBase private readonly BroadcastService _broadcastService; private List _drawFolders; - private Dictionary>? _cachedAnalysis; private Pair? _lastAddedUser; private string _lastAddedUserComment = string.Empty; private Vector2 _lastPosition = Vector2.One; @@ -382,15 +381,26 @@ public class CompactUi : WindowMediatorSubscriberBase _uiSharedService.IconText(FontAwesomeIcon.Upload); ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); - if (currentUploads.Any()) + if (currentUploads.Count > 0) { - var totalUploads = currentUploads.Count; + int totalUploads = currentUploads.Count; + int doneUploads = 0; + long totalUploaded = 0; + long totalToUpload = 0; - var doneUploads = currentUploads.Count(c => c.IsTransferred); - var activeUploads = currentUploads.Count(c => !c.IsTransferred); + foreach (var upload in currentUploads) + { + if (upload.IsTransferred) + { + doneUploads++; + } + + totalUploaded += upload.Transferred; + totalToUpload += upload.Total; + } + + int activeUploads = totalUploads - doneUploads; var uploadSlotLimit = Math.Clamp(_configService.Current.ParallelUploads, 1, 8); - var totalUploaded = currentUploads.Sum(c => c.Transferred); - var totalToUpload = currentUploads.Sum(c => c.Total); ImGui.TextUnformatted($"{doneUploads}/{totalUploads} (slots {activeUploads}/{uploadSlotLimit})"); var uploadText = $"({UiSharedService.ByteToString(totalUploaded)}/{UiSharedService.ByteToString(totalToUpload)})"; @@ -405,17 +415,17 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.TextUnformatted("No uploads in progress"); } - var currentDownloads = BuildCurrentDownloadSnapshot(); + var downloadSummary = GetDownloadSummary(); ImGui.AlignTextToFramePadding(); _uiSharedService.IconText(FontAwesomeIcon.Download); ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); - if (currentDownloads.Any()) + if (downloadSummary.HasDownloads) { - var totalDownloads = currentDownloads.Sum(c => c.TotalFiles); - var doneDownloads = currentDownloads.Sum(c => c.TransferredFiles); - var totalDownloaded = currentDownloads.Sum(c => c.TransferredBytes); - var totalToDownload = currentDownloads.Sum(c => c.TotalBytes); + var totalDownloads = downloadSummary.TotalFiles; + var doneDownloads = downloadSummary.TransferredFiles; + var totalDownloaded = downloadSummary.TransferredBytes; + var totalToDownload = downloadSummary.TotalBytes; ImGui.TextUnformatted($"{doneDownloads}/{totalDownloads}"); var downloadText = @@ -433,27 +443,35 @@ public class CompactUi : WindowMediatorSubscriberBase } - private List BuildCurrentDownloadSnapshot() + private DownloadSummary GetDownloadSummary() { - List snapshot = new(); + long totalBytes = 0; + long transferredBytes = 0; + int totalFiles = 0; + int transferredFiles = 0; foreach (var kvp in _currentDownloads.ToArray()) { - var value = kvp.Value; - if (value == null || value.Count == 0) + if (kvp.Value is not { Count: > 0 } statuses) + { continue; - - try - { - snapshot.AddRange(value.Values.ToArray()); } - catch (System.ArgumentException) + + foreach (var status in statuses.Values) { - // skibidi + totalBytes += status.TotalBytes; + transferredBytes += status.TransferredBytes; + totalFiles += status.TotalFiles; + transferredFiles += status.TransferredFiles; } } - return snapshot; + return new DownloadSummary(totalFiles, transferredFiles, transferredBytes, totalBytes); + } + + private readonly record struct DownloadSummary(int TotalFiles, int TransferredFiles, long TransferredBytes, long TotalBytes) + { + public bool HasDownloads => TotalFiles > 0 || TotalBytes > 0; } private void DrawUIDHeader() @@ -480,7 +498,7 @@ public class CompactUi : WindowMediatorSubscriberBase } //Getting information of character and triangles threshold to show overlimit status in UID bar. - _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); + var analysisSummary = _characterAnalyzer.LatestSummary; Vector2 uidTextSize, iconSize; using (_uiSharedService.UidFont.Push()) @@ -509,6 +527,7 @@ public class CompactUi : WindowMediatorSubscriberBase if (ImGui.IsItemHovered()) { ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 32f); ImGui.PushStyleColor(ImGuiCol.Text, UIColors.Get("PairBlue")); ImGui.Text("Lightfinder"); @@ -556,6 +575,7 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.PopStyleColor(); } + ImGui.PopTextWrapPos(); ImGui.EndTooltip(); } @@ -574,7 +594,7 @@ public class CompactUi : WindowMediatorSubscriberBase var seString = SeStringUtils.BuildFormattedPlayerName(uidText, vanityTextColor, vanityGlowColor); var cursorPos = ImGui.GetCursorScreenPos(); var fontPtr = ImGui.GetFont(); - SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr); + SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-header"); } else { @@ -591,56 +611,40 @@ public class CompactUi : WindowMediatorSubscriberBase UiSharedService.AttachToolTip("Click to copy"); - if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected) + if (_apiController.ServerState is ServerState.Connected && analysisSummary.HasData) { - var firstEntry = _cachedAnalysis.FirstOrDefault(); - var valueDict = firstEntry.Value; - if (valueDict != null && valueDict.Count > 0) + var objectSummary = analysisSummary.Objects.Values.FirstOrDefault(summary => summary.HasEntries); + if (objectSummary.HasEntries) { - var groupedfiles = valueDict - .Select(v => v.Value) - .Where(v => v != null) - .GroupBy(f => f.FileType, StringComparer.Ordinal) - .OrderBy(k => k.Key, StringComparer.Ordinal) - .ToList(); + var actualVramUsage = objectSummary.TexOriginalBytes; + var actualTriCount = objectSummary.TotalTriangles; - var actualTriCount = valueDict - .Select(v => v.Value) - .Where(v => v != null) - .Sum(f => f.Triangles); + var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage; + var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000); - if (groupedfiles != null) + if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds) { - //Checking of VRAM threshhold - var texGroup = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)); - var actualVramUsage = texGroup != null ? texGroup.Sum(f => f.OriginalSize) : 0L; - var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage; - var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000); + ImGui.SameLine(); + ImGui.SetCursorPosY(cursorY + 15f); + _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); - if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds) + string warningMessage = ""; + if (isOverTriHold) { - ImGui.SameLine(); - ImGui.SetCursorPosY(cursorY + 15f); - _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); + warningMessage += $"You exceed your own triangles threshold by " + + $"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles."; + warningMessage += Environment.NewLine; - string warningMessage = ""; - if (isOverTriHold) - { - warningMessage += $"You exceed your own triangles threshold by " + - $"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles."; - warningMessage += Environment.NewLine; - - } - if (isOverVRAMUsage) - { - warningMessage += $"You exceed your own VRAM threshold by " + - $"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}."; - } - UiSharedService.AttachToolTip(warningMessage); - if (ImGui.IsItemClicked()) - { - _lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); - } + } + if (isOverVRAMUsage) + { + warningMessage += $"You exceed your own VRAM threshold by " + + $"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}."; + } + UiSharedService.AttachToolTip(warningMessage); + if (ImGui.IsItemClicked()) + { + _lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); } } } @@ -663,7 +667,7 @@ public class CompactUi : WindowMediatorSubscriberBase var seString = SeStringUtils.BuildFormattedPlayerName(_apiController.UID, vanityTextColor, vanityGlowColor); var cursorPos = ImGui.GetCursorScreenPos(); var fontPtr = ImGui.GetFont(); - SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr); + SeStringUtils.RenderSeStringWithHitbox(seString, cursorPos, fontPtr, "uid-footer"); } else { @@ -921,4 +925,4 @@ public class CompactUi : WindowMediatorSubscriberBase _wasOpen = IsOpen; IsOpen = false; } -} \ No newline at end of file +} diff --git a/LightlessSync/UI/Components/DrawUserPair.cs b/LightlessSync/UI/Components/DrawUserPair.cs index fa5022e..4c4c1d4 100644 --- a/LightlessSync/UI/Components/DrawUserPair.cs +++ b/LightlessSync/UI/Components/DrawUserPair.cs @@ -2,6 +2,7 @@ using Dalamud.Interface; using Dalamud.Interface.Utility; using Dalamud.Interface.Utility.Raii; +using LightlessSync.API.Data.Enum; using LightlessSync.API.Data.Extensions; using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.User; @@ -13,6 +14,9 @@ using LightlessSync.Services.ServerConfiguration; using LightlessSync.UI.Handlers; using LightlessSync.Utils; using LightlessSync.WebAPI; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; namespace LightlessSync.UI.Components; @@ -32,6 +36,8 @@ public class DrawUserPair private readonly CharaDataManager _charaDataManager; private float _menuWidth = -1; private bool _wasHovered = false; + private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty; + private string _cachedTooltip = string.Empty; public DrawUserPair(string id, Pair entry, List syncedGroups, GroupFullInfoDto? currentGroup, @@ -190,15 +196,12 @@ public class DrawUserPair private void DrawLeftSide() { - string userPairText = string.Empty; - ImGui.AlignTextToFramePadding(); if (_pair.IsPaused) { using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")); _uiSharedService.IconText(FontAwesomeIcon.PauseCircle); - userPairText = _pair.UserData.AliasOrUID + " is paused"; } else if (!_pair.IsOnline) { @@ -207,12 +210,10 @@ public class DrawUserPair ? FontAwesomeIcon.ArrowsLeftRight : (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional ? FontAwesomeIcon.User : FontAwesomeIcon.Users)); - userPairText = _pair.UserData.AliasOrUID + " is offline"; } else if (_pair.IsVisible) { _uiSharedService.IconText(FontAwesomeIcon.Eye, UIColors.Get("LightlessBlue")); - userPairText = _pair.UserData.AliasOrUID + " is visible: " + _pair.PlayerName + Environment.NewLine + "Click to target this player"; if (ImGui.IsItemClicked()) { _mediator.Publish(new TargetPairMessage(_pair)); @@ -223,46 +224,9 @@ public class DrawUserPair using var _ = ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("PairBlue")); _uiSharedService.IconText(_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional ? FontAwesomeIcon.User : FontAwesomeIcon.Users); - userPairText = _pair.UserData.AliasOrUID + " is online"; } - if (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.OneSided) - { - userPairText += UiSharedService.TooltipSeparator + "User has not added you back"; - } - else if (_pair.IndividualPairStatus == API.Data.Enum.IndividualPairStatus.Bidirectional) - { - userPairText += UiSharedService.TooltipSeparator + "You are directly Paired"; - } - - if (_pair.LastAppliedDataBytes >= 0) - { - userPairText += UiSharedService.TooltipSeparator; - userPairText += ((!_pair.IsPaired) ? "(Last) " : string.Empty) + "Mods Info" + Environment.NewLine; - userPairText += "Files Size: " + UiSharedService.ByteToString(_pair.LastAppliedDataBytes, true); - if (_pair.LastAppliedApproximateVRAMBytes >= 0) - { - userPairText += Environment.NewLine + "Approx. VRAM Usage: " + UiSharedService.ByteToString(_pair.LastAppliedApproximateVRAMBytes, true); - } - if (_pair.LastAppliedDataTris >= 0) - { - userPairText += Environment.NewLine + "Approx. Triangle Count (excl. Vanilla): " - + (_pair.LastAppliedDataTris > 1000 ? (_pair.LastAppliedDataTris / 1000d).ToString("0.0'k'") : _pair.LastAppliedDataTris); - } - } - - if (_syncedGroups.Any()) - { - userPairText += UiSharedService.TooltipSeparator + string.Join(Environment.NewLine, - _syncedGroups.Select(g => - { - var groupNote = _serverConfigurationManager.GetNoteForGid(g.GID); - var groupString = string.IsNullOrEmpty(groupNote) ? g.GroupAliasOrGID : $"{groupNote} ({g.GroupAliasOrGID})"; - return "Paired through " + groupString; - })); - } - - UiSharedService.AttachToolTip(userPairText); + UiSharedService.AttachToolTip(GetUserTooltip()); if (_performanceConfigService.Current.ShowPerformanceIndicator && !_performanceConfigService.Current.UIDsToIgnore @@ -327,6 +291,143 @@ public class DrawUserPair _displayHandler.DrawPairText(_id, _pair, leftSide, () => rightSide - leftSide); } + private string GetUserTooltip() + { + List? groupDisplays = null; + if (_syncedGroups.Count > 0) + { + groupDisplays = new List(_syncedGroups.Count); + foreach (var group in _syncedGroups) + { + var groupNote = _serverConfigurationManager.GetNoteForGid(group.GID); + groupDisplays.Add(string.IsNullOrEmpty(groupNote) ? group.GroupAliasOrGID : $"{groupNote} ({group.GroupAliasOrGID})"); + } + } + + var snapshot = new TooltipSnapshot( + _pair.IsPaused, + _pair.IsOnline, + _pair.IsVisible, + _pair.IndividualPairStatus, + _pair.UserData.AliasOrUID, + _pair.PlayerName ?? string.Empty, + _pair.LastAppliedDataBytes, + _pair.LastAppliedApproximateVRAMBytes, + _pair.LastAppliedDataTris, + _pair.IsPaired, + groupDisplays is null ? ImmutableArray.Empty : ImmutableArray.CreateRange(groupDisplays)); + + if (!_tooltipSnapshot.Equals(snapshot)) + { + _cachedTooltip = BuildTooltip(snapshot); + _tooltipSnapshot = snapshot; + } + + return _cachedTooltip; + } + + private static string BuildTooltip(in TooltipSnapshot snapshot) + { + var builder = new StringBuilder(256); + + if (snapshot.IsPaused) + { + builder.Append(snapshot.AliasOrUid); + builder.Append(" is paused"); + } + else if (!snapshot.IsOnline) + { + builder.Append(snapshot.AliasOrUid); + builder.Append(" is offline"); + } + else if (snapshot.IsVisible) + { + builder.Append(snapshot.AliasOrUid); + builder.Append(" is visible: "); + builder.Append(snapshot.PlayerName); + builder.Append(Environment.NewLine); + builder.Append("Click to target this player"); + } + else + { + builder.Append(snapshot.AliasOrUid); + builder.Append(" is online"); + } + + if (snapshot.PairStatus == IndividualPairStatus.OneSided) + { + builder.Append(UiSharedService.TooltipSeparator); + builder.Append("User has not added you back"); + } + else if (snapshot.PairStatus == IndividualPairStatus.Bidirectional) + { + builder.Append(UiSharedService.TooltipSeparator); + builder.Append("You are directly Paired"); + } + + if (snapshot.LastAppliedDataBytes >= 0) + { + builder.Append(UiSharedService.TooltipSeparator); + if (!snapshot.IsPaired) + { + builder.Append("(Last) "); + } + builder.Append("Mods Info"); + builder.Append(Environment.NewLine); + builder.Append("Files Size: "); + builder.Append(UiSharedService.ByteToString(snapshot.LastAppliedDataBytes, true)); + + if (snapshot.LastAppliedApproximateVRAMBytes >= 0) + { + builder.Append(Environment.NewLine); + builder.Append("Approx. VRAM Usage: "); + builder.Append(UiSharedService.ByteToString(snapshot.LastAppliedApproximateVRAMBytes, true)); + } + + if (snapshot.LastAppliedDataTris >= 0) + { + builder.Append(Environment.NewLine); + builder.Append("Approx. Triangle Count (excl. Vanilla): "); + builder.Append(snapshot.LastAppliedDataTris > 1000 + ? (snapshot.LastAppliedDataTris / 1000d).ToString("0.0'k'") + : snapshot.LastAppliedDataTris); + } + } + + if (!snapshot.GroupDisplays.IsEmpty) + { + builder.Append(UiSharedService.TooltipSeparator); + for (int i = 0; i < snapshot.GroupDisplays.Length; i++) + { + if (i > 0) + { + builder.Append(Environment.NewLine); + } + builder.Append("Paired through "); + builder.Append(snapshot.GroupDisplays[i]); + } + } + + return builder.ToString(); + } + + private readonly record struct TooltipSnapshot( + bool IsPaused, + bool IsOnline, + bool IsVisible, + IndividualPairStatus PairStatus, + string AliasOrUid, + string PlayerName, + long LastAppliedDataBytes, + long LastAppliedApproximateVRAMBytes, + long LastAppliedDataTris, + bool IsPaired, + ImmutableArray GroupDisplays) + { + public static TooltipSnapshot Empty { get; } = + new(false, false, false, IndividualPairStatus.None, string.Empty, string.Empty, -1, -1, -1, false, ImmutableArray.Empty); + } + private void DrawPairedClientMenu() { DrawIndividualMenu(); diff --git a/LightlessSync/UI/Handlers/IdDisplayHandler.cs b/LightlessSync/UI/Handlers/IdDisplayHandler.cs index 01f0df6..4d362a9 100644 --- a/LightlessSync/UI/Handlers/IdDisplayHandler.cs +++ b/LightlessSync/UI/Handlers/IdDisplayHandler.cs @@ -157,7 +157,7 @@ public class IdDisplayHandler Vector2 textSize; using (ImRaii.PushFont(font, textIsUid)) { - SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font); + SeStringUtils.RenderSeStringWithHitbox(seString, rowStart, font, pair.UserData.UID); itemMin = ImGui.GetItemRectMin(); itemMax = ImGui.GetItemRectMax(); //textSize = itemMax - itemMin; diff --git a/LightlessSync/Utils/SeStringUtils.cs b/LightlessSync/Utils/SeStringUtils.cs index a19a343..7507515 100644 --- a/LightlessSync/Utils/SeStringUtils.cs +++ b/LightlessSync/Utils/SeStringUtils.cs @@ -7,6 +7,7 @@ using Dalamud.Interface.Utility; using Lumina.Text; using System; using System.Numerics; +using System.Threading; using DalamudSeString = Dalamud.Game.Text.SeStringHandling.SeString; using DalamudSeStringBuilder = Dalamud.Game.Text.SeStringHandling.SeStringBuilder; using LuminaSeStringBuilder = Lumina.Text.SeStringBuilder; @@ -15,6 +16,9 @@ namespace LightlessSync.Utils; public static class SeStringUtils { + private static int _seStringHitboxCounter; + private static int _iconHitboxCounter; + public static DalamudSeString BuildFormattedPlayerName(string text, Vector4? textColor, Vector4? glowColor) { var b = new DalamudSeStringBuilder(); @@ -119,7 +123,7 @@ public static class SeStringUtils ImGui.Dummy(new Vector2(0f, textSize.Y)); } - public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null) + public static Vector2 RenderSeStringWithHitbox(DalamudSeString seString, Vector2 position, ImFontPtr? font = null, string? id = null) { var drawList = ImGui.GetWindowDrawList(); @@ -137,12 +141,28 @@ public static class SeStringUtils var textSize = ImGui.CalcTextSize(seString.TextValue); ImGui.SetCursorScreenPos(position); - ImGui.InvisibleButton($"##hitbox_{Guid.NewGuid()}", textSize); + if (id is not null) + { + ImGui.PushID(id); + } + else + { + ImGui.PushID(Interlocked.Increment(ref _seStringHitboxCounter)); + } + + try + { + ImGui.InvisibleButton("##hitbox", textSize); + } + finally + { + ImGui.PopID(); + } return textSize; } - public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null) + public static Vector2 RenderIconWithHitbox(int iconId, Vector2 position, ImFontPtr? font = null, string? id = null) { var drawList = ImGui.GetWindowDrawList(); @@ -158,7 +178,23 @@ public static class SeStringUtils var drawResult = ImGuiHelpers.CompileSeStringWrapped(iconMacro, drawParams); ImGui.SetCursorScreenPos(position); - ImGui.InvisibleButton($"##iconHitbox_{Guid.NewGuid()}", drawResult.Size); + if (id is not null) + { + ImGui.PushID(id); + } + else + { + ImGui.PushID(Interlocked.Increment(ref _iconHitboxCounter)); + } + + try + { + ImGui.InvisibleButton("##iconHitbox", drawResult.Size); + } + finally + { + ImGui.PopID(); + } return drawResult.Size; } -- 2.49.1 From 6bb00c50d81cfdf21ac9ab1c4b0a021081845064 Mon Sep 17 00:00:00 2001 From: azyges <229218900+azyges@users.noreply.github.com> Date: Wed, 22 Oct 2025 03:33:51 +0900 Subject: [PATCH 2/2] improve logging fallback --- .../WebAPI/Files/FileDownloadManager.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/LightlessSync/WebAPI/Files/FileDownloadManager.cs b/LightlessSync/WebAPI/Files/FileDownloadManager.cs index cc82d04..b8f81f2 100644 --- a/LightlessSync/WebAPI/Files/FileDownloadManager.cs +++ b/LightlessSync/WebAPI/Files/FileDownloadManager.cs @@ -215,6 +215,26 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase await Task.Delay(retryDelay, ct).ConfigureAwait(false); } + catch (TaskCanceledException ex) when (!ct.IsCancellationRequested) + { + response?.Dispose(); + retryCount++; + + Logger.LogWarning(ex, "Cancellation/timeout during download of {requestUrl}. Attempt {attempt} of {maxRetries}", requestUrl, retryCount, maxRetries); + + if (retryCount >= maxRetries) + { + Logger.LogError("Max retries reached for {requestUrl} after TaskCanceledException", requestUrl); + throw; + } + + await Task.Delay(retryDelay, ct).ConfigureAwait(false); + } + catch (OperationCanceledException) when (ct.IsCancellationRequested) + { + response?.Dispose(); + throw; + } catch (HttpRequestException ex) { response?.Dispose(); -- 2.49.1