Merge branch '2.0.0' into dotnet10-api14-migration

This commit is contained in:
defnotken
2025-11-26 16:20:38 -06:00
116 changed files with 20468 additions and 3311 deletions

View File

@@ -1,5 +1,6 @@
using Dalamud.Bindings.ImGui;
using Dalamud.Game.Text;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@@ -16,8 +17,10 @@ using LightlessSync.LightlessConfiguration.Models;
using LightlessSync.PlayerData.Handlers;
using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services;
using LightlessSync.Services.ActorTracking;
using LightlessSync.Services.Mediator;
using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Services;
using LightlessSync.UI.Style;
using LightlessSync.Utils;
using LightlessSync.UtilsEnum.Enum;
@@ -25,10 +28,12 @@ using LightlessSync.WebAPI;
using LightlessSync.WebAPI.Files;
using LightlessSync.WebAPI.Files.Models;
using LightlessSync.WebAPI.SignalR.Utils;
using DalamudObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@@ -37,6 +42,9 @@ using System.Net.Http.Json;
using System.Numerics;
using System.Text;
using System.Text.Json;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FfxivCharacter = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
using FfxivCharacterBase = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.CharacterBase;
namespace LightlessSync.UI;
@@ -54,7 +62,8 @@ public class SettingsUi : WindowMediatorSubscriberBase
private readonly FileUploadManager _fileTransferManager;
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
private readonly IpcManager _ipcManager;
private readonly PairManager _pairManager;
private readonly ActorObjectService _actorObjectService;
private readonly PairUiService _pairUiService;
private readonly PerformanceCollectorService _performanceCollector;
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
private readonly PairProcessingLimiter _pairProcessingLimiter;
@@ -94,7 +103,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
public SettingsUi(ILogger<SettingsUi> logger,
UiSharedService uiShared, LightlessConfigService configService, UiThemeConfigService themeConfigService,
PairManager pairManager,
PairUiService pairUiService,
ServerConfigurationManager serverConfigurationManager,
PlayerPerformanceConfigService playerPerformanceConfigService,
PairProcessingLimiter pairProcessingLimiter,
@@ -106,12 +115,13 @@ public class SettingsUi : WindowMediatorSubscriberBase
IpcManager ipcManager, CacheMonitor cacheMonitor,
DalamudUtilService dalamudUtilService, HttpClient httpClient,
NameplateService nameplateService,
NameplateHandler nameplateHandler) : base(logger, mediator, "Lightless Sync Settings",
NameplateHandler nameplateHandler,
ActorObjectService actorObjectService) : base(logger, mediator, "Lightless Sync Settings",
performanceCollector)
{
_configService = configService;
_themeConfigService = themeConfigService;
_pairManager = pairManager;
_pairUiService = pairUiService;
_serverConfigurationManager = serverConfigurationManager;
_playerPerformanceConfigService = playerPerformanceConfigService;
_pairProcessingLimiter = pairProcessingLimiter;
@@ -128,13 +138,15 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared = uiShared;
_nameplateService = nameplateService;
_nameplateHandler = nameplateHandler;
_actorObjectService = actorObjectService;
AllowClickthrough = false;
AllowPinning = true;
_validationProgress = new Progress<(int, int, FileCacheEntity)>(v => _currentProgress = v);
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(800, 400), MaximumSize = new Vector2(800, 2000),
MinimumSize = new Vector2(850f, 400f),
MaximumSize = new Vector2(850f, 2000f),
};
TitleBarButtons = new()
@@ -449,6 +461,74 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
}
private void DrawTextureDownscaleCounters()
{
HashSet<Pair> trackedPairs = new();
var snapshot = _pairUiService.GetSnapshot();
foreach (var pair in snapshot.DirectPairs)
{
trackedPairs.Add(pair);
}
foreach (var group in snapshot.GroupPairs.Values)
{
foreach (var pair in group)
{
trackedPairs.Add(pair);
}
}
long totalOriginalBytes = 0;
long totalEffectiveBytes = 0;
var hasData = false;
foreach (var pair in trackedPairs)
{
if (!pair.IsVisible)
continue;
var original = pair.LastAppliedApproximateVRAMBytes;
var effective = pair.LastAppliedApproximateEffectiveVRAMBytes;
if (original >= 0)
{
hasData = true;
totalOriginalBytes += original;
}
if (effective >= 0)
{
hasData = true;
totalEffectiveBytes += effective;
}
}
if (!hasData)
{
ImGui.TextDisabled("VRAM usage has not been calculated yet.");
return;
}
var savedBytes = Math.Max(0L, totalOriginalBytes - totalEffectiveBytes);
var originalText = UiSharedService.ByteToString(totalOriginalBytes, addSuffix: true);
var effectiveText = UiSharedService.ByteToString(totalEffectiveBytes, addSuffix: true);
var savedText = UiSharedService.ByteToString(savedBytes, addSuffix: true);
ImGui.TextUnformatted($"Total VRAM usage (original): {originalText}");
ImGui.TextUnformatted($"Total VRAM usage (effective): {effectiveText}");
if (savedBytes > 0)
{
UiSharedService.ColorText($"VRAM saved by downscaling: {savedText}", UIColors.Get("LightlessGreen"));
}
else
{
ImGui.TextUnformatted($"VRAM saved by downscaling: {savedText}");
}
}
private void DrawThemeVectorRow(MainStyle.StyleVector2Option option)
{
ImGui.TableNextRow();
@@ -1380,6 +1460,22 @@ public class SettingsUi : WindowMediatorSubscriberBase
_logger.LogWarning(ex, "Could not delete file {file} because it is in use.", file);
}
}
foreach (var directory in Directory.GetDirectories(_configService.Current.CacheFolder))
{
try
{
Directory.Delete(directory, recursive: true);
}
catch (IOException ex)
{
_logger.LogWarning(ex, "Could not delete directory {Directory} because it is in use.", directory);
}
catch (UnauthorizedAccessException ex)
{
_logger.LogWarning(ex, "Could not delete directory {Directory} due to access restrictions.", directory);
}
}
});
}
@@ -1417,8 +1513,9 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
if (_uiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
{
ImGui.SetClipboardText(UiSharedService.GetNotes(_pairManager.DirectPairs
.UnionBy(_pairManager.GroupPairs.SelectMany(p => p.Value), p => p.UserData,
var snapshot = _pairUiService.GetSnapshot();
ImGui.SetClipboardText(UiSharedService.GetNotes(snapshot.DirectPairs
.UnionBy(snapshot.GroupPairs.SelectMany(p => p.Value), p => p.UserData,
UserDataComparer.Instance).ToList()));
}
@@ -2383,6 +2480,22 @@ public class SettingsUi : WindowMediatorSubscriberBase
_uiShared.DrawHelpText(
"Will show a performance indicator when players exceed defined thresholds in Lightless UI." +
Environment.NewLine + "Will use warning thresholds.");
using (ImRaii.Disabled(!showPerformanceIndicator))
{
using var indent = ImRaii.PushIndent();
bool showCompactStats = _playerPerformanceConfigService.Current.ShowPerformanceUsageNextToName;
if (ImGui.Checkbox("Show performance stats next to alias", ref showCompactStats))
{
_playerPerformanceConfigService.Current.ShowPerformanceUsageNextToName = showCompactStats;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText(
"Adds a text with approx. VRAM usage and triangle count to the right of pairs alias." +
Environment.NewLine + "Requires performance indicator to be enabled.");
}
bool warnOnExceedingThresholds = _playerPerformanceConfigService.Current.WarnOnExceedingThresholds;
if (ImGui.Checkbox("Warn on loading in players exceeding performance thresholds",
ref warnOnExceedingThresholds))
@@ -2547,6 +2660,102 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TreePop();
}
ImGui.Separator();
if (_uiShared.MediumTreeNode("Texture Optimization", UIColors.Get("LightlessYellow")))
{
_uiShared.MediumText("Warning", UIColors.Get("DimRed"));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Texture compression and downscaling is potentially a "),
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("This feature is encouraged to help "),
new SeStringUtils.RichTextEntry("lower-end systems with limited VRAM", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry(" and for use in "),
new SeStringUtils.RichTextEntry("performance-critical scenarios", UIColors.Get("LightlessYellow"), true),
new SeStringUtils.RichTextEntry("."));
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("Runtime downscaling "),
new SeStringUtils.RichTextEntry("MAY", UIColors.Get("DimRed"), true),
new SeStringUtils.RichTextEntry(" cause higher load on the system when processing downloads."));
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
new SeStringUtils.RichTextEntry("When enabled, we cannot provide support for appearance issues caused by this setting!", UIColors.Get("DimRed"), true));
var textureConfig = _playerPerformanceConfigService.Current;
var trimNonIndex = textureConfig.EnableNonIndexTextureMipTrim;
if (ImGui.Checkbox("Trim mip levels for textures", ref trimNonIndex))
{
textureConfig.EnableNonIndexTextureMipTrim = trimNonIndex;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When enabled, Lightless will remove high-resolution mip levels from textures (not index) that exceed the size limit and are not compressed with any kind compression.");
var downscaleIndex = textureConfig.EnableIndexTextureDownscale;
if (ImGui.Checkbox("Downscale index textures above limit", ref downscaleIndex))
{
textureConfig.EnableIndexTextureDownscale = downscaleIndex;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("Controls whether Lightless reduces index textures that exceed the size limit.");
var dimensionOptions = new[] { 512, 1024, 2048, 4096 };
var optionLabels = dimensionOptions.Select(static value => value.ToString()).ToArray();
var currentDimension = textureConfig.TextureDownscaleMaxDimension;
var selectedIndex = Array.IndexOf(dimensionOptions, currentDimension);
if (selectedIndex < 0)
{
selectedIndex = Array.IndexOf(dimensionOptions, 2048);
}
ImGui.SetNextItemWidth(140 * ImGuiHelpers.GlobalScale);
if (ImGui.Combo("Maximum texture dimension", ref selectedIndex, optionLabels, optionLabels.Length))
{
textureConfig.TextureDownscaleMaxDimension = dimensionOptions[selectedIndex];
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText($"Textures above this size will be reduced until their largest dimension is at or below the limit. Block-compressed textures are skipped when \"Only downscale uncompressed\" is enabled.{UiSharedService.TooltipSeparator}Default: 2048");
var keepOriginalTextures = textureConfig.KeepOriginalTextureFiles;
if (ImGui.Checkbox("Keep original texture files", ref keepOriginalTextures))
{
textureConfig.KeepOriginalTextureFiles = keepOriginalTextures;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("When disabled, Lightless removes the original texture after a downscaled copy is created.");
ImGui.SameLine();
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow")));
if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale)
{
UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed"));
}
ImGui.Dummy(new Vector2(5));
_uiShared.ColoredSeparator(UIColors.Get("DimRed"), 3f);
var onlyUncompressed = textureConfig.OnlyDownscaleUncompressedTextures;
if (ImGui.Checkbox("Only downscale uncompressed textures", ref onlyUncompressed))
{
textureConfig.OnlyDownscaleUncompressedTextures = onlyUncompressed;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("If disabled, compressed textures will be targeted for downscaling too.");
_uiShared.ColoredSeparator(UIColors.Get("DimRed"), 3f);
ImGui.Dummy(new Vector2(5));
DrawTextureDownscaleCounters();
ImGui.Dummy(new Vector2(5));
_uiShared.ColoredSeparator(UIColors.Get("LightlessYellow"), 1.5f);
ImGui.TreePop();
}
ImGui.Separator();
ImGui.Dummy(new Vector2(10));
@@ -3505,7 +3714,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
// Lightless notification locations
var lightlessLocations = GetLightlessNotificationLocations();
var downloadLocations = GetDownloadNotificationLocations();
if (ImGui.BeginTable("##NotificationLocationTable", 3, ImGuiTableFlags.Borders | ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit))
{
ImGui.TableSetupColumn("Notification Type", ImGuiTableColumnFlags.WidthFixed, 200f * ImGuiHelpers.GlobalScale);
@@ -3668,7 +3877,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.EndTable();
}
ImGuiHelpers.ScaledDummy(5);
if (_uiShared.IconTextButton(FontAwesomeIcon.Trash, "Clear All Notifications"))
{
@@ -3786,7 +3995,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Spacing();
ImGui.TextUnformatted("Size & Layout");
float notifWidth = _configService.Current.NotificationWidth;
if (ImGui.SliderFloat("Notification Width", ref notifWidth, 250f, 600f, "%.0f"))
{
@@ -3819,7 +4028,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Spacing();
ImGui.TextUnformatted("Position");
var currentCorner = _configService.Current.NotificationCorner;
if (ImGui.BeginCombo("Notification Position", GetNotificationCornerLabel(currentCorner)))
{
@@ -3837,7 +4046,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.EndCombo();
}
_uiShared.DrawHelpText("Choose which corner of the screen notifications appear in.");
int offsetY = _configService.Current.NotificationOffsetY;
if (ImGui.SliderInt("Vertical Offset", ref offsetY, -2500, 2500))
{
@@ -4130,7 +4339,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.Separator();
// Location descriptions removed - information is now inline with each setting
}
}
@@ -4250,7 +4459,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.TableSetColumnIndex(2);
var availableWidth = ImGui.GetContentRegionAvail().X;
var buttonWidth = (availableWidth - ImGui.GetStyle().ItemSpacing.X * 2) / 3;
// Play button
using var playId = ImRaii.PushId($"Play_{typeIndex}");
using (ImRaii.Disabled(isDisabled))
@@ -4271,7 +4480,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
}
UiSharedService.AttachToolTip("Test this sound");
// Disable toggle button
ImGui.SameLine();
using var disableId = ImRaii.PushId($"Disable_{typeIndex}");
@@ -4279,11 +4488,11 @@ public class SettingsUi : WindowMediatorSubscriberBase
{
var icon = isDisabled ? FontAwesomeIcon.VolumeOff : FontAwesomeIcon.VolumeUp;
var color = isDisabled ? UIColors.Get("DimRed") : UIColors.Get("LightlessGreen");
ImGui.PushStyleColor(ImGuiCol.Button, color);
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, color * new Vector4(1.2f, 1.2f, 1.2f, 1f));
ImGui.PushStyleColor(ImGuiCol.ButtonActive, color * new Vector4(0.8f, 0.8f, 0.8f, 1f));
if (ImGui.Button(icon.ToIconString(), new Vector2(buttonWidth, 0)))
{
bool newDisabled = !isDisabled;
@@ -4297,16 +4506,16 @@ public class SettingsUi : WindowMediatorSubscriberBase
}
_configService.Save();
}
ImGui.PopStyleColor(3);
}
UiSharedService.AttachToolTip(isDisabled ? "Sound is disabled - click to enable" : "Sound is enabled - click to disable");
// Reset button
ImGui.SameLine();
using var resetId = ImRaii.PushId($"Reset_{typeIndex}");
bool isDefault = currentSoundId == defaultSoundId;
using (ImRaii.Disabled(isDefault))
{
using (ImRaii.PushFont(UiBuilder.IconFont))
@@ -4331,6 +4540,4 @@ public class SettingsUi : WindowMediatorSubscriberBase
ImGui.EndTable();
}
}
}
}