some caching stuff and bug fixes #71
@@ -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<CharacterAnalyzer> 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<ObjectKind, Dictionary<string, FileDataEntry>> 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<ObjectKind, CharacterAnalysisObjectSummary>();
|
||||
|
||||
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;
|
||||
@@ -233,3 +269,23 @@ 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<ObjectKind, CharacterAnalysisObjectSummary>.Empty);
|
||||
|
||||
internal CharacterAnalysisSummary(IImmutableDictionary<ObjectKind, CharacterAnalysisObjectSummary> objects)
|
||||
{
|
||||
Objects = objects;
|
||||
}
|
||||
|
||||
public IImmutableDictionary<ObjectKind, CharacterAnalysisObjectSummary> Objects { get; }
|
||||
|
||||
public bool HasData => Objects.Any(kvp => kvp.Value.HasEntries);
|
||||
}
|
||||
@@ -56,7 +56,6 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
private readonly BroadcastService _broadcastService;
|
||||
|
||||
private List<IDrawFolder> _drawFolders;
|
||||
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _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<FileDownloadStatus> BuildCurrentDownloadSnapshot()
|
||||
private DownloadSummary GetDownloadSummary()
|
||||
{
|
||||
List<FileDownloadStatus> 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
|
||||
{
|
||||
|
||||
@@ -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<GroupFullInfoDto> 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<string>? groupDisplays = null;
|
||||
if (_syncedGroups.Count > 0)
|
||||
{
|
||||
groupDisplays = new List<string>(_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<string>.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<string> GroupDisplays)
|
||||
{
|
||||
public static TooltipSnapshot Empty { get; } =
|
||||
new(false, false, false, IndividualPairStatus.None, string.Empty, string.Empty, -1, -1, -1, false, ImmutableArray<string>.Empty);
|
||||
}
|
||||
|
||||
private void DrawPairedClientMenu()
|
||||
{
|
||||
DrawIndividualMenu();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user