add UI for Mare Data Analysis
This commit is contained in:
@@ -520,6 +520,11 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
ImGui.Text("No downloads in progress");
|
||||
}
|
||||
|
||||
if (UiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Mare Character Data Analysis", WindowContentWidth))
|
||||
{
|
||||
Mediator.Publish(new OpenDataAnalysisUiMessage());
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
|
||||
268
MareSynchronos/UI/DataAnalysisUi.cs
Normal file
268
MareSynchronos/UI/DataAnalysisUi.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Raii;
|
||||
using ImGuiNET;
|
||||
using MareSynchronos.API.Data.Enum;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
|
||||
public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
{
|
||||
private readonly CharacterAnalyzer _characterAnalyzer;
|
||||
private bool _hasUpdate = false;
|
||||
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
|
||||
private string _selectedHash = string.Empty;
|
||||
private ObjectKind _selectedTab;
|
||||
|
||||
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer) : base(logger, mediator, "Mare Character Data Analysis")
|
||||
{
|
||||
_characterAnalyzer = characterAnalyzer;
|
||||
|
||||
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
|
||||
{
|
||||
_hasUpdate = true;
|
||||
});
|
||||
Mediator.Subscribe<OpenDataAnalysisUiMessage>(this, (_) => Toggle());
|
||||
SizeConstraints = new()
|
||||
{
|
||||
MinimumSize = new()
|
||||
{
|
||||
X = 800,
|
||||
Y = 600
|
||||
},
|
||||
MaximumSize = new()
|
||||
{
|
||||
X = 3840,
|
||||
Y = 2160
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnOpen()
|
||||
{
|
||||
_hasUpdate = true;
|
||||
_selectedHash = string.Empty;
|
||||
}
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
if (_hasUpdate)
|
||||
{
|
||||
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
|
||||
_hasUpdate = false;
|
||||
}
|
||||
|
||||
UiSharedService.TextWrapped("This window shows you all files and their sizes that are currently in use through your character and associated entities in Mare");
|
||||
|
||||
if (_cachedAnalysis!.Count == 0) return;
|
||||
|
||||
if (_cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)))
|
||||
{
|
||||
bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
|
||||
if (isAnalyzing)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped($"Analyzing {_characterAnalyzer.CurrentFile}/{_characterAnalyzer.TotalFiles}",
|
||||
ImGuiColors.DalamudYellow);
|
||||
if (UiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
|
||||
{
|
||||
_characterAnalyzer.CancelAnalyze();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to analyze your current data",
|
||||
ImGuiColors.DalamudYellow);
|
||||
if (UiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis"))
|
||||
{
|
||||
_ = _characterAnalyzer.ComputeAnalysis(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
ImGui.TextUnformatted("Total files:");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_cachedAnalysis!.Values.Sum(c => c.Values.Count).ToString());
|
||||
ImGui.SameLine();
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
string text = "";
|
||||
var groupedfiles = _cachedAnalysis.Values.SelectMany(f => f.Values).GroupBy(f => f.FileType, StringComparer.Ordinal);
|
||||
text = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
|
||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
||||
ImGui.SetTooltip(text);
|
||||
}
|
||||
ImGui.TextUnformatted("Total size (uncompressed):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
|
||||
ImGui.TextUnformatted("Total size (compressed):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
using var tabbar = ImRaii.TabBar("objectSelection");
|
||||
foreach (var kvp in _cachedAnalysis)
|
||||
{
|
||||
using var id = ImRaii.PushId(kvp.Key.ToString());
|
||||
string tabText = kvp.Key.ToString();
|
||||
if (kvp.Value.Any(f => !f.Value.IsComputed)) tabText += " (!)";
|
||||
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
|
||||
if (tab.Success)
|
||||
{
|
||||
ImGui.TextUnformatted("Files for " + kvp.Key);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(kvp.Value.Count.ToString());
|
||||
ImGui.SameLine();
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TextUnformatted(FontAwesomeIcon.InfoCircle.ToIconString());
|
||||
}
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
string text = "";
|
||||
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal);
|
||||
text = string.Join(Environment.NewLine, groupedfiles.OrderBy(f => f.Key, StringComparer.Ordinal)
|
||||
.Select(f => f.Key + ": " + f.Count() + " files, size: " + UiSharedService.ByteToString(f.Sum(v => v.OriginalSize))
|
||||
+ ", compressed: " + UiSharedService.ByteToString(f.Sum(v => v.CompressedSize))));
|
||||
ImGui.SetTooltip(text);
|
||||
}
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (uncompressed):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
|
||||
ImGui.TextUnformatted($"{kvp.Key} size (compressed):");
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
|
||||
|
||||
ImGui.Separator();
|
||||
if (_selectedTab != kvp.Key)
|
||||
{
|
||||
_selectedHash = string.Empty;
|
||||
_selectedTab = kvp.Key;
|
||||
}
|
||||
|
||||
using var table = ImRaii.Table("Analysis", 6, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
|
||||
new Vector2(0, 300));
|
||||
if (!table.Success) continue;
|
||||
ImGui.TableSetupColumn("Type");
|
||||
ImGui.TableSetupColumn("Hash");
|
||||
ImGui.TableSetupColumn("Filepaths");
|
||||
ImGui.TableSetupColumn("Gamepaths");
|
||||
ImGui.TableSetupColumn("Original Size");
|
||||
ImGui.TableSetupColumn("Compressed Size");
|
||||
ImGui.TableSetupScrollFreeze(0, 1);
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var sortSpecs = ImGui.TableGetSortSpecs();
|
||||
if (sortSpecs.SpecsDirty)
|
||||
{
|
||||
var idx = sortSpecs.Specs.ColumnIndex;
|
||||
|
||||
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.FileType, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.FileType, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.FilePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
if (idx == 5 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
|
||||
_cachedAnalysis[kvp.Key] = kvp.Value.OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
|
||||
|
||||
sortSpecs.SpecsDirty = false;
|
||||
}
|
||||
|
||||
foreach (var item in kvp.Value)
|
||||
{
|
||||
using var text = ImRaii.PushColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1), string.Equals(item.Key, _selectedHash));
|
||||
using var text2 = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 1), !item.Value.IsComputed);
|
||||
ImGui.TableNextColumn();
|
||||
if (!item.Value.IsComputed)
|
||||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudRed));
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudRed));
|
||||
}
|
||||
if (string.Equals(_selectedHash, item.Key, StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow));
|
||||
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow));
|
||||
}
|
||||
ImGui.TextUnformatted(item.Value.FileType);
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(item.Key);
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(item.Value.FilePaths.Count.ToString());
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(item.Value.GamePaths.Count.ToString());
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(item.Value.OriginalSize));
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(UiSharedService.ByteToString(item.Value.CompressedSize));
|
||||
if (ImGui.IsItemClicked()) _selectedHash = item.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.Separator();
|
||||
ImGui.Text("Selected file:");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
|
||||
if (_cachedAnalysis[_selectedTab].ContainsKey(_selectedHash))
|
||||
{
|
||||
var filePaths = _cachedAnalysis[_selectedTab][_selectedHash].FilePaths;
|
||||
ImGui.TextUnformatted("Local file path:");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.TextWrapped(filePaths[0]);
|
||||
if (filePaths.Count > 1)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"(and {filePaths.Count - 1} more)");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.FontText(FontAwesomeIcon.InfoCircle.ToIconString(), UiBuilder.IconFont);
|
||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, filePaths.Skip(1)));
|
||||
}
|
||||
|
||||
var gamepaths = _cachedAnalysis[_selectedTab][_selectedHash].GamePaths;
|
||||
ImGui.TextUnformatted("Used by game path:");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.TextWrapped(gamepaths[0]);
|
||||
if (gamepaths.Count > 1)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted($"(and {gamepaths.Count - 1} more)");
|
||||
ImGui.SameLine();
|
||||
UiSharedService.FontText(FontAwesomeIcon.InfoCircle.ToIconString(), UiBuilder.IconFont);
|
||||
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,11 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_entry.Value.Dispose();
|
||||
if (_entry.IsValueCreated)
|
||||
{
|
||||
_logger.LogDebug("Disposing DtrEntry");
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -51,15 +55,6 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
||||
catch (OperationCanceledException) { }
|
||||
finally
|
||||
{
|
||||
_logger.LogDebug("Disposing DtrEntry");
|
||||
if (_entry.IsValueCreated)
|
||||
{
|
||||
Clear();
|
||||
|
||||
_entry.Value.Remove();
|
||||
_entry.Value.Dispose();
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
|
||||
private readonly FileUploadManager _fileTransferManager;
|
||||
private readonly FileTransferOrchestrator _fileTransferOrchestrator;
|
||||
private readonly CharacterAnalyzer _characterAnalyzer;
|
||||
private readonly MareCharaFileManager _mareCharaFileManager;
|
||||
private readonly PairManager _pairManager;
|
||||
private readonly PerformanceCollectorService _performanceCollector;
|
||||
@@ -51,8 +50,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
ServerConfigurationManager serverConfigurationManager,
|
||||
MareMediator mediator, PerformanceCollectorService performanceCollector,
|
||||
FileUploadManager fileTransferManager,
|
||||
FileTransferOrchestrator fileTransferOrchestrator,
|
||||
CharacterAnalyzer characterAnalyzer) : base(logger, mediator, "Mare Synchronos Settings")
|
||||
FileTransferOrchestrator fileTransferOrchestrator) : base(logger, mediator, "Mare Synchronos Settings")
|
||||
{
|
||||
_configService = configService;
|
||||
_mareCharaFileManager = mareCharaFileManager;
|
||||
@@ -61,7 +59,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_performanceCollector = performanceCollector;
|
||||
_fileTransferManager = fileTransferManager;
|
||||
_fileTransferOrchestrator = fileTransferOrchestrator;
|
||||
_characterAnalyzer = characterAnalyzer;
|
||||
_uiShared = uiShared;
|
||||
|
||||
SizeConstraints = new WindowSizeConstraints()
|
||||
@@ -334,23 +331,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
}
|
||||
UiSharedService.AttachToolTip("Use this when reporting mods being rejected from the server.");
|
||||
|
||||
var isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
|
||||
if (isAnalyzing) ImGui.BeginDisabled();
|
||||
if (UiSharedService.IconTextButton(FontAwesomeIcon.QuestionCircle, "[DEBUG] Analyze current character composition to /xllog"))
|
||||
{
|
||||
_ = _characterAnalyzer.Analyze();
|
||||
}
|
||||
UiSharedService.AttachToolTip("This will compute your current \"Mare load\" and print it to the /xllog");
|
||||
if (isAnalyzing) ImGui.EndDisabled();
|
||||
ImGui.SameLine();
|
||||
if (!isAnalyzing) ImGui.BeginDisabled();
|
||||
if (UiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
|
||||
{
|
||||
_characterAnalyzer.CancelAnalyze();
|
||||
}
|
||||
UiSharedService.AttachToolTip("Cancels the current analysis of your character composition");
|
||||
if (!isAnalyzing) ImGui.EndDisabled();
|
||||
|
||||
_uiShared.DrawCombo("Log Level", Enum.GetValues<LogLevel>(), (l) => l.ToString(), (l) =>
|
||||
{
|
||||
_configService.Current.LogLevel = l;
|
||||
|
||||
@@ -313,7 +313,7 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
return ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X;
|
||||
}
|
||||
|
||||
public static bool IconTextButton(FontAwesomeIcon icon, string text)
|
||||
public static bool IconTextButton(FontAwesomeIcon icon, string text, float? width = null)
|
||||
{
|
||||
var buttonClicked = false;
|
||||
|
||||
@@ -322,9 +322,18 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
var padding = ImGui.GetStyle().FramePadding;
|
||||
var spacing = ImGui.GetStyle().ItemSpacing;
|
||||
|
||||
var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X;
|
||||
Vector2 buttonSize;
|
||||
var buttonSizeY = (iconSize.Y > textSize.Y ? iconSize.Y : textSize.Y) + padding.Y * 2;
|
||||
var buttonSize = new Vector2(buttonSizeX, buttonSizeY);
|
||||
|
||||
if (width == null)
|
||||
{
|
||||
var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X;
|
||||
buttonSize = new Vector2(buttonSizeX, buttonSizeY);
|
||||
}
|
||||
else
|
||||
{
|
||||
buttonSize = new Vector2(width.Value, buttonSizeY);
|
||||
}
|
||||
|
||||
if (ImGui.Button("###" + icon.ToIconString() + text, buttonSize))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user