Add Pair Character Analysis for funsies

This commit is contained in:
Loporrit
2025-02-22 11:59:22 +00:00
parent def13858f4
commit eaaded1ed5
19 changed files with 701 additions and 32 deletions

View File

@@ -141,12 +141,13 @@ public sealed class FileCacheManager : IHostedService
public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) public async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{ {
var fileCache = GetFileCacheByHash(fileHash)!.ResolvedFilepath; var fileCache = GetFileCacheByHash(fileHash)!;
using var fs = File.OpenRead(fileCache); using var fs = File.OpenRead(fileCache.ResolvedFilepath);
var ms = new MemoryStream(64 * 1024); var ms = new MemoryStream(64 * 1024);
using var encstream = LZ4Stream.Encode(ms, new LZ4EncoderSettings(){CompressionLevel=K4os.Compression.LZ4.LZ4Level.L09_HC}); using var encstream = LZ4Stream.Encode(ms, new LZ4EncoderSettings(){CompressionLevel=K4os.Compression.LZ4.LZ4Level.L09_HC});
await fs.CopyToAsync(encstream, uploadToken).ConfigureAwait(false); await fs.CopyToAsync(encstream, uploadToken).ConfigureAwait(false);
encstream.Close(); encstream.Close();
fileCache.CompressedSize = encstream.Length;
return (fileHash, ms.ToArray()); return (fileHash, ms.ToArray());
} }

View File

@@ -0,0 +1,30 @@
using MareSynchronos.FileCache;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.PlayerData.Factories;
public class PairAnalyzerFactory
{
private readonly ILoggerFactory _loggerFactory;
private readonly MareMediator _mareMediator;
private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _modelAnalyzer;
public PairAnalyzerFactory(ILoggerFactory loggerFactory, MareMediator mareMediator,
FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
{
_loggerFactory = loggerFactory;
_fileCacheManager = fileCacheManager;
_mareMediator = mareMediator;
_modelAnalyzer = modelAnalyzer;
}
public PairAnalyzer Create(Pair pair)
{
return new PairAnalyzer(_loggerFactory.CreateLogger<PairAnalyzer>(), pair, _mareMediator,
_fileCacheManager, _modelAnalyzer);
}
}

View File

@@ -23,12 +23,13 @@ public class PairHandlerFactory
private readonly PlayerPerformanceService _playerPerformanceService; private readonly PlayerPerformanceService _playerPerformanceService;
private readonly ServerConfigurationManager _serverConfigManager; private readonly ServerConfigurationManager _serverConfigManager;
private readonly PluginWarningNotificationService _pluginWarningNotificationManager; private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
ServerConfigurationManager serverConfigManager) ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory)
{ {
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
@@ -41,11 +42,12 @@ public class PairHandlerFactory
_mareMediator = mareMediator; _mareMediator = mareMediator;
_playerPerformanceService = playerPerformanceService; _playerPerformanceService = playerPerformanceService;
_serverConfigManager = serverConfigManager; _serverConfigManager = serverConfigManager;
_pairAnalyzerFactory = pairAnalyzerFactory;
} }
public PairHandler Create(Pair pair) public PairHandler Create(Pair pair)
{ {
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _gameObjectHandlerFactory, return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager); _fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager);
} }

View File

@@ -43,7 +43,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
private Guid _penumbraCollection = Guid.Empty; private Guid _penumbraCollection = Guid.Empty;
private bool _redrawOnNextApplication = false; private bool _redrawOnNextApplication = false;
public PairHandler(ILogger<PairHandler> logger, Pair pair, public PairHandler(ILogger<PairHandler> logger, Pair pair, PairAnalyzer pairAnalyzer,
GameObjectHandlerFactory gameObjectHandlerFactory, GameObjectHandlerFactory gameObjectHandlerFactory,
IpcManager ipcManager, FileDownloadManager transferManager, IpcManager ipcManager, FileDownloadManager transferManager,
PluginWarningNotificationService pluginWarningNotificationManager, PluginWarningNotificationService pluginWarningNotificationManager,
@@ -53,6 +53,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
ServerConfigurationManager serverConfigManager) : base(logger, mediator) ServerConfigurationManager serverConfigManager) : base(logger, mediator)
{ {
Pair = pair; Pair = pair;
PairAnalyzer = pairAnalyzer;
_gameObjectHandlerFactory = gameObjectHandlerFactory; _gameObjectHandlerFactory = gameObjectHandlerFactory;
_ipcManager = ipcManager; _ipcManager = ipcManager;
_downloadManager = transferManager; _downloadManager = transferManager;
@@ -129,6 +130,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
public long LastAppliedDataBytes { get; private set; } public long LastAppliedDataBytes { get; private set; }
public Pair Pair { get; private set; } public Pair Pair { get; private set; }
public PairAnalyzer PairAnalyzer { get; private init; }
public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero; public nint PlayerCharacter => _charaHandler?.Address ?? nint.Zero;
public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero public unsafe uint PlayerCharacterId => (_charaHandler?.Address ?? nint.Zero) == nint.Zero
? uint.MaxValue ? uint.MaxValue
@@ -159,6 +161,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
.Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles));
_forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null);
_cachedData = characterData; _cachedData = characterData;
Mediator.Publish(new PairDataAppliedMessage(Pair.UserData.UID, characterData));
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods); Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
return; return;
} }
@@ -176,6 +179,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
.Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles));
_forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null);
_cachedData = characterData; _cachedData = characterData;
Mediator.Publish(new PairDataAppliedMessage(Pair.UserData.UID, characterData));
Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods); Logger.LogDebug("[BASE-{appBase}] Setting data: {hash}, forceApplyMods: {force}", applicationBase, _cachedData.DataHash.Value, _forceApplyMods);
return; return;
} }
@@ -274,6 +278,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
{ {
PlayerName = null; PlayerName = null;
_cachedData = null; _cachedData = null;
Mediator.Publish(new PairDataAppliedMessage(Pair.UserData.UID, null));
Logger.LogDebug("Disposing {name} complete", name); Logger.LogDebug("Disposing {name} complete", name);
} }
} }
@@ -581,6 +586,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
} }
_cachedData = charaData; _cachedData = charaData;
Mediator.Publish(new PairDataAppliedMessage(Pair.UserData.UID, charaData));
Logger.LogDebug("[{applicationId}] Application finished", _applicationId); Logger.LogDebug("[{applicationId}] Application finished", _applicationId);
} }
@@ -591,6 +597,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
IsVisible = false; IsVisible = false;
_forceApplyMods = true; _forceApplyMods = true;
_cachedData = charaData; _cachedData = charaData;
Mediator.Publish(new PairDataAppliedMessage(Pair.UserData.UID, charaData));
Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId); Logger.LogDebug("[{applicationId}] Cancelled, player turned null during application", _applicationId);
} }
else else

View File

@@ -9,6 +9,7 @@ using MareSynchronos.API.Dto.User;
using MareSynchronos.MareConfiguration; using MareSynchronos.MareConfiguration;
using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Factories;
using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Handlers;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator; using MareSynchronos.Services.Mediator;
using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Services.ServerConfiguration;
using MareSynchronos.Utils; using MareSynchronos.Utils;
@@ -67,6 +68,7 @@ public class Pair : DisposableMediatorSubscriberBase
public long LastAppliedDataTris { get; set; } = -1; public long LastAppliedDataTris { get; set; } = -1;
public long LastAppliedApproximateVRAMBytes { get; set; } = -1; public long LastAppliedApproximateVRAMBytes { get; set; } = -1;
public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty; public string Ident => _onlineUserIdentDto?.Ident ?? string.Empty;
public PairAnalyzer? PairAnalyzer => CachedPlayer?.PairAnalyzer;
public UserData UserData { get; init; } public UserData UserData { get; init; }

View File

@@ -79,6 +79,7 @@ public sealed class Plugin : IDalamudPlugin
collection.AddSingleton<GameObjectHandlerFactory>(); collection.AddSingleton<GameObjectHandlerFactory>();
collection.AddSingleton<FileDownloadManagerFactory>(); collection.AddSingleton<FileDownloadManagerFactory>();
collection.AddSingleton<PairHandlerFactory>(); collection.AddSingleton<PairHandlerFactory>();
collection.AddSingleton<PairAnalyzerFactory>();
collection.AddSingleton<PairFactory>(); collection.AddSingleton<PairFactory>();
collection.AddSingleton<XivDataAnalyzer>(); collection.AddSingleton<XivDataAnalyzer>();
collection.AddSingleton<CharacterAnalyzer>(); collection.AddSingleton<CharacterAnalyzer>();

View File

@@ -9,7 +9,7 @@ using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services; namespace MareSynchronos.Services;
public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable public sealed class CharacterAnalyzer : DisposableMediatorSubscriberBase
{ {
private readonly FileCacheManager _fileCacheManager; private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer; private readonly XivDataAnalyzer _xivDataAnalyzer;
@@ -63,7 +63,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
foreach (var file in remaining) foreach (var file in remaining)
{ {
Logger.LogDebug("Computing file {file}", file.FilePaths[0]); Logger.LogDebug("Computing file {file}", file.FilePaths[0]);
await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); await file.ComputeSizes(_fileCacheManager, cancelToken, ignoreCacheEntries: true).ConfigureAwait(false);
CurrentFile++; CurrentFile++;
} }
@@ -88,9 +88,14 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
if (print) PrintAnalysis(); if (print) PrintAnalysis();
} }
public void Dispose() protected override void Dispose(bool disposing)
{ {
_analysisCts.CancelDispose(); base.Dispose(disposing);
if (!disposing) return;
_analysisCts?.CancelDispose();
_baseAnalysisCts.CancelDispose();
} }
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token) private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
@@ -191,11 +196,11 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize, long Triangles) internal sealed record FileDataEntry(string Hash, string FileType, List<string> GamePaths, List<string> FilePaths, long OriginalSize, long CompressedSize, long Triangles)
{ {
public bool IsComputed => OriginalSize > 0 && CompressedSize > 0; public bool IsComputed => OriginalSize > 0 && CompressedSize > 0;
public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token) public async Task ComputeSizes(FileCacheManager fileCacheManager, CancellationToken token, bool ignoreCacheEntries = true)
{ {
var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false); var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false);
var normalSize = new FileInfo(FilePaths[0]).Length; var normalSize = new FileInfo(FilePaths[0]).Length;
var entries = fileCacheManager.GetAllFileCachesByHash(Hash, ignoreCacheEntries: true, validate: false); var entries = fileCacheManager.GetAllFileCachesByHash(Hash, ignoreCacheEntries: ignoreCacheEntries, validate: false);
foreach (var entry in entries) foreach (var entry in entries)
{ {
entry.Size = normalSize; entry.Size = normalSize;
@@ -220,7 +225,9 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable
using var reader = new BinaryReader(stream); using var reader = new BinaryReader(stream);
reader.BaseStream.Position = 4; reader.BaseStream.Position = 4;
var format = (TexFile.TextureFormat)reader.ReadInt32(); var format = (TexFile.TextureFormat)reader.ReadInt32();
return format.ToString(); var width = reader.ReadInt16();
var height = reader.ReadInt16();
return $"{format} ({width}x{height})";
} }
catch catch
{ {

View File

@@ -109,7 +109,7 @@ public sealed class MareMediator : IHostedService
throw new InvalidOperationException("Already subscribed"); throw new InvalidOperationException("Already subscribed");
} }
_logger.LogDebug("Subscriber added for message {message}: {sub}", typeof(T).Name, subscriber.GetType().Name); _logger.LogTrace("Subscriber added for message {message}: {sub}", typeof(T).Name, subscriber.GetType().Name);
} }
} }
@@ -124,7 +124,7 @@ public sealed class MareMediator : IHostedService
throw new InvalidOperationException("Already subscribed"); throw new InvalidOperationException("Already subscribed");
} }
_logger.LogDebug("Subscriber added for message {message}:{key}: {sub}", typeof(T).Name, key, subscriber.GetType().Name); _logger.LogTrace("Subscriber added for message {message}:{key}: {sub}", typeof(T).Name, key, subscriber.GetType().Name);
} }
} }

View File

@@ -78,6 +78,7 @@ public record OpenReportPopupMessage(Pair PairToReport) : MessageBase;
public record OpenBanUserPopupMessage(Pair PairToBan, GroupFullInfoDto GroupFullInfoDto) : MessageBase; public record OpenBanUserPopupMessage(Pair PairToBan, GroupFullInfoDto GroupFullInfoDto) : MessageBase;
public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase; public record OpenSyncshellAdminPanel(GroupFullInfoDto GroupInfo) : MessageBase;
public record OpenPermissionWindow(Pair Pair) : MessageBase; public record OpenPermissionWindow(Pair Pair) : MessageBase;
public record OpenPairAnalysisWindow(Pair Pair) : MessageBase;
public record DownloadLimitChangedMessage() : SameThreadMessage; public record DownloadLimitChangedMessage() : SameThreadMessage;
public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase; public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase;
public record TargetPairMessage(Pair Pair) : MessageBase; public record TargetPairMessage(Pair Pair) : MessageBase;
@@ -94,5 +95,7 @@ public record HoldPairApplicationMessage(string UID, string Source) : KeyedMessa
public record UnholdPairApplicationMessage(string UID, string Source) : KeyedMessage(UID); public record UnholdPairApplicationMessage(string UID, string Source) : KeyedMessage(UID);
public record HoldPairDownloadsMessage(string UID, string Source) : KeyedMessage(UID); public record HoldPairDownloadsMessage(string UID, string Source) : KeyedMessage(UID);
public record UnholdPairDownloadsMessage(string UID, string Source) : KeyedMessage(UID); public record UnholdPairDownloadsMessage(string UID, string Source) : KeyedMessage(UID);
public record PairDataAppliedMessage(string UID, CharacterData? CharacterData) : KeyedMessage(UID);
public record PairDataAnalyzedMessage(string UID) : KeyedMessage(UID);
#pragma warning restore S2094 #pragma warning restore S2094
#pragma warning restore MA0048 // File name must match type name #pragma warning restore MA0048 // File name must match type name

View File

@@ -0,0 +1,211 @@
using Lumina.Data.Files;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.FileCache;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services.Mediator;
using MareSynchronos.UI;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Services;
public sealed class PairAnalyzer : DisposableMediatorSubscriberBase
{
private readonly FileCacheManager _fileCacheManager;
private readonly XivDataAnalyzer _xivDataAnalyzer;
private CancellationTokenSource? _analysisCts;
private CancellationTokenSource _baseAnalysisCts = new();
private string _lastDataHash = string.Empty;
public PairAnalyzer(ILogger<PairAnalyzer> logger, Pair pair, MareMediator mediator, FileCacheManager fileCacheManager, XivDataAnalyzer modelAnalyzer)
: base(logger, mediator)
{
Pair = pair;
Mediator.SubscribeKeyed<PairDataAppliedMessage>(this, pair.UserData.UID, (msg) =>
{
_baseAnalysisCts = _baseAnalysisCts.CancelRecreate();
var token = _baseAnalysisCts.Token;
if (msg.CharacterData != null)
{
_ = BaseAnalysis(msg.CharacterData, token);
}
else
{
LastAnalysis.Clear();
_lastDataHash = string.Empty;
}
});
_fileCacheManager = fileCacheManager;
_xivDataAnalyzer = modelAnalyzer;
var lastReceivedData = pair.LastReceivedCharacterData;
if (lastReceivedData != null)
_ = BaseAnalysis(lastReceivedData, _baseAnalysisCts.Token);
}
public Pair Pair { get; init; }
public int CurrentFile { get; internal set; }
public bool IsAnalysisRunning => _analysisCts != null;
public int TotalFiles { get; internal set; }
internal Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>> LastAnalysis { get; } = [];
internal string LastPlayerName { get; set; } = string.Empty;
public void CancelAnalyze()
{
_analysisCts?.CancelDispose();
_analysisCts = null;
}
public async Task ComputeAnalysis(bool print = true, bool recalculate = false)
{
Logger.LogDebug("=== Calculating Character Analysis ===");
_analysisCts = _analysisCts?.CancelRecreate() ?? new();
var cancelToken = _analysisCts.Token;
var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList();
if (allFiles.Exists(c => !c.IsComputed || recalculate))
{
var remaining = allFiles.Where(c => !c.IsComputed || recalculate).ToList();
TotalFiles = remaining.Count;
CurrentFile = 1;
Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count);
Mediator.Publish(new HaltScanMessage(nameof(PairAnalyzer)));
try
{
foreach (var file in remaining)
{
Logger.LogDebug("Computing file {file}", file.FilePaths[0]);
await file.ComputeSizes(_fileCacheManager, cancelToken, ignoreCacheEntries: false).ConfigureAwait(false);
CurrentFile++;
}
_fileCacheManager.WriteOutFullCsv();
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Failed to analyze files");
}
finally
{
Mediator.Publish(new ResumeScanMessage(nameof(PairAnalyzer)));
}
}
LastPlayerName = Pair.PlayerName ?? string.Empty;
Mediator.Publish(new PairDataAnalyzedMessage(Pair.UserData.UID));
_analysisCts.CancelDispose();
_analysisCts = null;
if (print) PrintAnalysis();
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing) return;
_analysisCts?.CancelDispose();
_baseAnalysisCts.CancelDispose();
}
private async Task BaseAnalysis(CharacterData charaData, CancellationToken token)
{
if (string.Equals(charaData.DataHash.Value, _lastDataHash, StringComparison.Ordinal)) return;
LastAnalysis.Clear();
foreach (var obj in charaData.FileReplacements)
{
Dictionary<string, CharacterAnalyzer.FileDataEntry> data = new(StringComparer.OrdinalIgnoreCase);
foreach (var fileEntry in obj.Value)
{
token.ThrowIfCancellationRequested();
var fileCacheEntries = _fileCacheManager.GetAllFileCachesByHash(fileEntry.Hash, ignoreCacheEntries: false, validate: false).ToList();
if (fileCacheEntries.Count == 0) continue;
var filePath = fileCacheEntries[0].ResolvedFilepath;
FileInfo fi = new(filePath);
string ext = "unk?";
try
{
ext = fi.Extension[1..];
}
catch (Exception ex)
{
Logger.LogWarning(ex, "Could not identify extension for {path}", filePath);
}
var tris = await _xivDataAnalyzer.GetTrianglesByHash(fileEntry.Hash).ConfigureAwait(false);
foreach (var entry in fileCacheEntries)
{
data[fileEntry.Hash] = new CharacterAnalyzer.FileDataEntry(fileEntry.Hash, ext,
[.. fileEntry.GamePaths],
fileCacheEntries.Select(c => c.ResolvedFilepath).Distinct().ToList(),
entry.Size > 0 ? entry.Size.Value : 0,
entry.CompressedSize > 0 ? entry.CompressedSize.Value : 0,
tris);
}
}
LastAnalysis[obj.Key] = data;
}
Mediator.Publish(new PairDataAnalyzedMessage(Pair.UserData.UID));
_lastDataHash = charaData.DataHash.Value;
}
private void PrintAnalysis()
{
if (LastAnalysis.Count == 0) return;
foreach (var kvp in LastAnalysis)
{
int fileCounter = 1;
int totalFiles = kvp.Value.Count;
Logger.LogInformation("=== Analysis for {uid}:{obj} ===", Pair.UserData.UID, kvp.Key);
foreach (var entry in kvp.Value.OrderBy(b => b.Value.GamePaths.OrderBy(p => p, StringComparer.Ordinal).First(), StringComparer.Ordinal))
{
Logger.LogInformation("File {x}/{y}: {hash}", fileCounter++, totalFiles, entry.Key);
foreach (var path in entry.Value.GamePaths)
{
Logger.LogInformation(" Game Path: {path}", path);
}
if (entry.Value.FilePaths.Count > 1) Logger.LogInformation(" Multiple fitting files detected for {key}", entry.Key);
foreach (var filePath in entry.Value.FilePaths)
{
Logger.LogInformation(" File Path: {path}", filePath);
}
Logger.LogInformation(" Size: {size}, Compressed: {compressed}", UiSharedService.ByteToString(entry.Value.OriginalSize),
UiSharedService.ByteToString(entry.Value.CompressedSize));
}
}
foreach (var kvp in LastAnalysis)
{
Logger.LogInformation("=== Detailed summary by file type for {obj} ===", kvp.Key);
foreach (var entry in kvp.Value.Select(v => v.Value).GroupBy(v => v.FileType, StringComparer.Ordinal))
{
Logger.LogInformation("{ext} files: {count}, size extracted: {size}, size compressed: {sizeComp}", entry.Key, entry.Count(),
UiSharedService.ByteToString(entry.Sum(v => v.OriginalSize)), UiSharedService.ByteToString(entry.Sum(v => v.CompressedSize)));
}
Logger.LogInformation("=== Total summary for {obj} ===", kvp.Key);
Logger.LogInformation("Total files: {count}, size extracted: {size}, size compressed: {sizeComp}", kvp.Value.Count,
UiSharedService.ByteToString(kvp.Value.Sum(v => v.Value.OriginalSize)), UiSharedService.ByteToString(kvp.Value.Sum(v => v.Value.CompressedSize)));
}
Logger.LogInformation("=== Total summary for all currently present objects ===");
Logger.LogInformation("Total files: {count}, size extracted: {size}, size compressed: {sizeComp}",
LastAnalysis.Values.Sum(v => v.Values.Count),
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.OriginalSize))),
UiSharedService.ByteToString(LastAnalysis.Values.Sum(c => c.Values.Sum(v => v.CompressedSize))));
}
}

View File

@@ -51,4 +51,10 @@ public class UiFactory
return new PermissionWindowUI(_loggerFactory.CreateLogger<PermissionWindowUI>(), pair, return new PermissionWindowUI(_loggerFactory.CreateLogger<PermissionWindowUI>(), pair,
_mareMediator, _uiSharedService, _apiController, _performanceCollectorService); _mareMediator, _uiSharedService, _apiController, _performanceCollectorService);
} }
public PlayerAnalysisUI CreatePlayerAnalysisUi(Pair pair)
{
return new PlayerAnalysisUI(_loggerFactory.CreateLogger<PlayerAnalysisUI>(), pair,
_mareMediator, _uiSharedService, _performanceCollectorService);
}
} }

View File

@@ -76,6 +76,17 @@ public sealed class UiService : DisposableMediatorSubscriberBase
} }
}); });
Mediator.Subscribe<OpenPairAnalysisWindow>(this, (msg) =>
{
if (!_createdWindows.Exists(p => p is PlayerAnalysisUI ui
&& msg.Pair == ui.Pair))
{
var window = _uiFactory.CreatePlayerAnalysisUi(msg.Pair);
_createdWindows.Add(window);
_windowSystem.AddWindow(window);
}
});
Mediator.Subscribe<RemoveWindowMessage>(this, (msg) => Mediator.Subscribe<RemoveWindowMessage>(this, (msg) =>
{ {
_windowSystem.RemoveWindow(msg.Window); _windowSystem.RemoveWindow(msg.Window);

View File

@@ -335,6 +335,11 @@ public class DrawGroupPair : DrawPairBase
} }
if (_pair.IsVisible) if (_pair.IsVisible)
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Open Analysis"))
{
_displayHandler.OpenAnalysis(_pair);
ImGui.CloseCurrentPopup();
}
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data"))
{ {
_pair.ApplyLastReceivedData(forced: true); _pair.ApplyLastReceivedData(forced: true);

View File

@@ -225,6 +225,11 @@ public class DrawUserPair : DrawPairBase
} }
if (entry.IsVisible) if (entry.IsVisible)
{ {
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Open Analysis"))
{
_displayHandler.OpenAnalysis(_pair);
ImGui.CloseCurrentPopup();
}
if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data")) if (_uiSharedService.IconTextButton(FontAwesomeIcon.Sync, "Reload last data"))
{ {
entry.ApplyLastReceivedData(forced: true); entry.ApplyLastReceivedData(forced: true);

View File

@@ -26,6 +26,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
private Task? _conversionTask; private Task? _conversionTask;
private bool _enableBc7ConversionMode = false; private bool _enableBc7ConversionMode = false;
private bool _hasUpdate = false; private bool _hasUpdate = false;
private bool _sortDirty = true;
private bool _modalOpen = false; private bool _modalOpen = false;
private string _selectedFileTypeTab = string.Empty; private string _selectedFileTypeTab = string.Empty;
private string _selectedHash = string.Empty; private string _selectedHash = string.Empty;
@@ -102,11 +103,12 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
{ {
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
_hasUpdate = false; _hasUpdate = false;
_sortDirty = true;
} }
UiSharedService.TextWrapped("This window shows you all files and their sizes that are currently in use through your character and associated entities"); UiSharedService.TextWrapped("This window shows you all files and their sizes that are currently in use through your character and associated entities");
if (_cachedAnalysis!.Count == 0) return; if (_cachedAnalysis == null || _cachedAnalysis.Count == 0) return;
bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning; bool isAnalyzing = _characterAnalyzer.IsAnalysisRunning;
bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed)); bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed));
@@ -161,7 +163,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted("Total size (actual):"); ImGui.TextUnformatted("Total size (actual):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize)))); ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
ImGui.TextUnformatted("Total size (compressed for up/download only):"); ImGui.TextUnformatted("Total size (download size):");
ImGui.SameLine(); ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis)) using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
{ {
@@ -182,7 +184,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
{ {
using var id = ImRaii.PushId(kvp.Key.ToString()); using var id = ImRaii.PushId(kvp.Key.ToString());
string tabText = 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()); using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
if (tab.Success) if (tab.Success)
{ {
@@ -209,7 +210,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.TextUnformatted($"{kvp.Key} size (actual):"); ImGui.TextUnformatted($"{kvp.Key} size (actual):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
ImGui.TextUnformatted($"{kvp.Key} size (compressed for up/download only):"); ImGui.TextUnformatted($"{kvp.Key} size (download size):");
ImGui.SameLine(); ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis)) using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
{ {
@@ -248,10 +249,6 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]"; string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]";
var requiresCompute = fileGroup.Any(k => !k.IsComputed); var requiresCompute = fileGroup.Any(k => !k.IsComputed);
using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute); using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute);
if (requiresCompute)
{
fileGroupText += " (!)";
}
ImRaii.IEndObject fileTab; ImRaii.IEndObject fileTab;
using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)), using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)),
requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal))) requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal)))
@@ -277,7 +274,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
ImGui.TextUnformatted($"{fileGroup.Key} files size (compressed for up/download only):"); ImGui.TextUnformatted($"{fileGroup.Key} files size (download size):");
ImGui.SameLine(); ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize))); ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
@@ -376,10 +373,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
new Vector2(0, 300)); new Vector2(0, 300));
if (!table.Success) return; if (!table.Success) return;
ImGui.TableSetupColumn("Hash"); ImGui.TableSetupColumn("Hash");
ImGui.TableSetupColumn("Filepaths"); ImGui.TableSetupColumn("Filepaths", ImGuiTableColumnFlags.PreferSortDescending);
ImGui.TableSetupColumn("Gamepaths"); ImGui.TableSetupColumn("Gamepaths", ImGuiTableColumnFlags.PreferSortDescending);
ImGui.TableSetupColumn("Original Size"); ImGui.TableSetupColumn("File Size", ImGuiTableColumnFlags.DefaultSort | ImGuiTableColumnFlags.PreferSortDescending);
ImGui.TableSetupColumn("Compressed Size"); ImGui.TableSetupColumn("Download Size", ImGuiTableColumnFlags.PreferSortDescending);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)) if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
{ {
ImGui.TableSetupColumn("Format"); ImGui.TableSetupColumn("Format");
@@ -387,13 +384,13 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
} }
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal)) if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
{ {
ImGui.TableSetupColumn("Triangles"); ImGui.TableSetupColumn("Triangles", ImGuiTableColumnFlags.PreferSortDescending);
} }
ImGui.TableSetupScrollFreeze(0, 1); ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
var sortSpecs = ImGui.TableGetSortSpecs(); var sortSpecs = ImGui.TableGetSortSpecs();
if (sortSpecs.SpecsDirty) if (sortSpecs.SpecsDirty || _sortDirty)
{ {
var idx = sortSpecs.Specs.ColumnIndex; var idx = sortSpecs.Specs.ColumnIndex;
@@ -427,6 +424,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal); _cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
sortSpecs.SpecsDirty = false; sortSpecs.SpecsDirty = false;
_sortDirty = false;
} }
foreach (var item in fileGroup) foreach (var item in fileGroup)
@@ -462,7 +460,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
if (_enableBc7ConversionMode) if (_enableBc7ConversionMode)
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (string.Equals(item.Format.Value, "BC7", StringComparison.Ordinal)) if (item.Format.Value.StartsWith("BC") || item.Format.Value.StartsWith("DXT")
|| item.Format.Value.StartsWith("24864")) // BC4
{ {
ImGui.TextUnformatted(""); ImGui.TextUnformatted("");
continue; continue;

View File

@@ -194,6 +194,11 @@ public class UidDisplayHandler
_mediator.Publish(new ProfileOpenStandaloneMessage(entry)); _mediator.Publish(new ProfileOpenStandaloneMessage(entry));
} }
internal void OpenAnalysis(Pair entry)
{
_mediator.Publish(new OpenPairAnalysisWindow(entry));
}
private bool ShowUidInsteadOfName(Pair pair) private bool ShowUidInsteadOfName(Pair pair)
{ {
_showUidForEntry.TryGetValue(pair.UserData.UID, out var showUidInsteadOfName); _showUidForEntry.TryGetValue(pair.UserData.UID, out var showUidInsteadOfName);

View File

@@ -0,0 +1,370 @@
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using ImGuiNET;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.Interop.Ipc;
using MareSynchronos.PlayerData.Pairs;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using Microsoft.Extensions.Logging;
using System.Numerics;
namespace MareSynchronos.UI;
public class PlayerAnalysisUI : WindowMediatorSubscriberBase
{
private readonly UiSharedService _uiSharedService;
private Dictionary<ObjectKind, Dictionary<string, CharacterAnalyzer.FileDataEntry>>? _cachedAnalysis;
private bool _hasUpdate = true;
private bool _sortDirty = true;
private string _selectedFileTypeTab = string.Empty;
private string _selectedHash = string.Empty;
private ObjectKind _selectedObjectTab;
public PlayerAnalysisUI(ILogger<PlayerAnalysisUI> logger, Pair pair, MareMediator mediator, UiSharedService uiSharedService,
PerformanceCollectorService performanceCollectorService)
: base(logger, mediator, "Character Data Analysis for " + pair.UserData.AliasOrUID + "###LoporritPairAnalysis" + pair.UserData.UID, performanceCollectorService)
{
Pair = pair;
_uiSharedService = uiSharedService;
Mediator.SubscribeKeyed<PairDataAnalyzedMessage>(this, Pair.UserData.UID, (_) =>
{
_logger.LogInformation("PairDataAnalyzedMessage received for {uid}", Pair.UserData.UID);
_hasUpdate = true;
});
SizeConstraints = new()
{
MinimumSize = new()
{
X = 800,
Y = 600
},
MaximumSize = new()
{
X = 3840,
Y = 2160
}
};
IsOpen = true;
}
public Pair Pair { get; private init; }
public PairAnalyzer? PairAnalyzer => Pair.PairAnalyzer;
public override void OnClose()
{
Mediator.Publish(new RemoveWindowMessage(this));
}
protected override void DrawInternal()
{
if (PairAnalyzer == null) return;
PairAnalyzer analyzer = PairAnalyzer!;
if (_hasUpdate)
{
_cachedAnalysis = analyzer.LastAnalysis.DeepClone();
_hasUpdate = false;
_sortDirty = true;
}
UiSharedService.TextWrapped($"This window shows you all files and their sizes that are currently in use by {Pair.UserData.AliasOrUID} and associated entities");
if (_cachedAnalysis == null || _cachedAnalysis.Count == 0) return;
bool isAnalyzing = analyzer.IsAnalysisRunning;
bool needAnalysis = _cachedAnalysis!.Any(c => c.Value.Any(f => !f.Value.IsComputed));
if (isAnalyzing)
{
UiSharedService.ColorTextWrapped($"Analyzing {analyzer.CurrentFile}/{analyzer.TotalFiles}",
ImGuiColors.DalamudYellow);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.StopCircle, "Cancel analysis"))
{
analyzer.CancelAnalyze();
}
}
else
{
if (needAnalysis)
{
UiSharedService.ColorTextWrapped("Some entries in the analysis have file size not determined yet, press the button below to compute missing data",
ImGuiColors.DalamudYellow);
if (_uiSharedService.IconTextButton(FontAwesomeIcon.PlayCircle, "Start analysis (missing entries)"))
{
_ = analyzer.ComputeAnalysis(print: 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 (actual):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.OriginalSize))));
ImGui.TextUnformatted("Total size (compressed for up/download only):");
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
{
ImGui.TextUnformatted(UiSharedService.ByteToString(_cachedAnalysis!.Sum(c => c.Value.Sum(c => c.Value.CompressedSize))));
if (needAnalysis && !isAnalyzing)
{
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
}
}
ImGui.TextUnformatted($"Total modded model triangles: {UiSharedService.TrisToString(_cachedAnalysis.Sum(c => c.Value.Sum(f => f.Value.Triangles)))}");
ImGui.Separator();
var playerName = analyzer.LastPlayerName;
if (playerName.Length == 0)
{
playerName = Pair.PlayerName ?? string.Empty;
analyzer.LastPlayerName = playerName;
}
using var tabbar = ImRaii.TabBar("objectSelection");
foreach (var kvp in _cachedAnalysis)
{
using var id = ImRaii.PushId(kvp.Key.ToString());
string tabText = kvp.Key == ObjectKind.Player ? playerName : $"{playerName}'s {kvp.Key}";
using var tab = ImRaii.TabItem(tabText + "###" + kvp.Key.ToString());
if (tab.Success)
{
var groupedfiles = kvp.Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
ImGui.TextUnformatted($"Files for {tabText}");
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 = "";
text = string.Join(Environment.NewLine, groupedfiles
.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 (actual):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.OriginalSize)));
ImGui.TextUnformatted($"{kvp.Key} size (download size):");
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, needAnalysis))
{
ImGui.TextUnformatted(UiSharedService.ByteToString(kvp.Value.Sum(c => c.Value.CompressedSize)));
if (needAnalysis && !isAnalyzing)
{
ImGui.SameLine();
using (ImRaii.PushFont(UiBuilder.IconFont))
ImGui.TextUnformatted(FontAwesomeIcon.ExclamationCircle.ToIconString());
UiSharedService.AttachToolTip("Click \"Start analysis\" to calculate download size");
}
}
ImGui.TextUnformatted($"{kvp.Key} VRAM usage:");
ImGui.SameLine();
var vramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
if (vramUsage != null)
{
ImGui.TextUnformatted(UiSharedService.ByteToString(vramUsage.Sum(f => f.OriginalSize)));
}
ImGui.TextUnformatted($"{kvp.Key} modded model triangles: {UiSharedService.TrisToString(kvp.Value.Sum(f => f.Value.Triangles))}");
ImGui.Separator();
if (_selectedObjectTab != kvp.Key)
{
_selectedHash = string.Empty;
_selectedObjectTab = kvp.Key;
_selectedFileTypeTab = string.Empty;
}
using var fileTabBar = ImRaii.TabBar("fileTabs");
foreach (IGrouping<string, CharacterAnalyzer.FileDataEntry>? fileGroup in groupedfiles)
{
string fileGroupText = fileGroup.Key + " [" + fileGroup.Count() + "]";
var requiresCompute = fileGroup.Any(k => !k.IsComputed);
using var tabcol = ImRaii.PushColor(ImGuiCol.Tab, UiSharedService.Color(ImGuiColors.DalamudYellow), requiresCompute);
ImRaii.IEndObject fileTab;
using (var textcol = ImRaii.PushColor(ImGuiCol.Text, UiSharedService.Color(new(0, 0, 0, 1)),
requiresCompute && !string.Equals(_selectedFileTypeTab, fileGroup.Key, StringComparison.Ordinal)))
{
fileTab = ImRaii.TabItem(fileGroupText + "###" + fileGroup.Key);
}
if (!fileTab) { fileTab.Dispose(); continue; }
if (!string.Equals(fileGroup.Key, _selectedFileTypeTab, StringComparison.Ordinal))
{
_selectedFileTypeTab = fileGroup.Key;
_selectedHash = string.Empty;
}
ImGui.TextUnformatted($"{fileGroup.Key} files");
ImGui.SameLine();
ImGui.TextUnformatted(fileGroup.Count().ToString());
ImGui.TextUnformatted($"{fileGroup.Key} files size (actual):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.OriginalSize)));
ImGui.TextUnformatted($"{fileGroup.Key} files size (download size):");
ImGui.SameLine();
ImGui.TextUnformatted(UiSharedService.ByteToString(fileGroup.Sum(c => c.CompressedSize)));
ImGui.Separator();
DrawTable(fileGroup);
fileTab.Dispose();
}
}
}
ImGui.Separator();
ImGui.TextUnformatted("Selected file:");
ImGui.SameLine();
UiSharedService.ColorText(_selectedHash, ImGuiColors.DalamudYellow);
if (_cachedAnalysis[_selectedObjectTab].TryGetValue(_selectedHash, out CharacterAnalyzer.FileDataEntry? item))
{
var gamepaths = item.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.IconText(FontAwesomeIcon.InfoCircle);
UiSharedService.AttachToolTip(string.Join(Environment.NewLine, gamepaths.Skip(1)));
}
}
}
private void DrawTable(IGrouping<string, CharacterAnalyzer.FileDataEntry> fileGroup)
{
var tableColumns = string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal)
? 5
: (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) ? 5 : 4);
using var table = ImRaii.Table("Analysis", tableColumns, ImGuiTableFlags.Sortable | ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
new Vector2(0, 300));
if (!table.Success) return;
ImGui.TableSetupColumn("Hash");
ImGui.TableSetupColumn("Gamepaths", ImGuiTableColumnFlags.PreferSortDescending);
ImGui.TableSetupColumn("File Size", ImGuiTableColumnFlags.DefaultSort | ImGuiTableColumnFlags.PreferSortDescending);
ImGui.TableSetupColumn("Download Size", ImGuiTableColumnFlags.PreferSortDescending);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
{
ImGui.TableSetupColumn("Format");
}
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
{
ImGui.TableSetupColumn("Triangles", ImGuiTableColumnFlags.PreferSortDescending);
}
ImGui.TableSetupScrollFreeze(0, 1);
ImGui.TableHeadersRow();
var sortSpecs = ImGui.TableGetSortSpecs();
if (sortSpecs.SpecsDirty || _sortDirty)
{
var idx = sortSpecs.Specs.ColumnIndex;
if (idx == 0 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 0 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Key, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 1 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.GamePaths.Count).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 2 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.OriginalSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (idx == 3 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.CompressedSize).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal) && idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Triangles).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Ascending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderBy(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal) && idx == 4 && sortSpecs.Specs.SortDirection == ImGuiSortDirection.Descending)
_cachedAnalysis![_selectedObjectTab] = _cachedAnalysis[_selectedObjectTab].OrderByDescending(k => k.Value.Format.Value, StringComparer.Ordinal).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal);
sortSpecs.SpecsDirty = false;
_sortDirty = false;
}
foreach (var item in fileGroup)
{
using var text = ImRaii.PushColor(ImGuiCol.Text, new Vector4(0, 0, 0, 1), string.Equals(item.Hash, _selectedHash, StringComparison.Ordinal));
using var text2 = ImRaii.PushColor(ImGuiCol.Text, new Vector4(1, 1, 1, 1), !item.IsComputed);
ImGui.TableNextColumn();
if (string.Equals(_selectedHash, item.Hash, StringComparison.Ordinal))
{
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg1, UiSharedService.Color(ImGuiColors.DalamudYellow));
ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, UiSharedService.Color(ImGuiColors.DalamudYellow));
}
ImGui.TextUnformatted(item.Hash);
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
ImGui.TableNextColumn();
ImGui.TextUnformatted(item.GamePaths.Count.ToString());
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
ImGui.TableNextColumn();
ImGui.TextUnformatted(UiSharedService.ByteToString(item.OriginalSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
ImGui.TableNextColumn();
using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudYellow, !item.IsComputed))
ImGui.TextUnformatted(UiSharedService.ByteToString(item.CompressedSize));
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
if (string.Equals(fileGroup.Key, "tex", StringComparison.Ordinal))
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(item.Format.Value);
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
}
if (string.Equals(fileGroup.Key, "mdl", StringComparison.Ordinal))
{
ImGui.TableNextColumn();
ImGui.TextUnformatted(UiSharedService.TrisToString(item.Triangles));
if (ImGui.IsItemClicked()) _selectedHash = item.Hash;
}
}
}
}

View File

@@ -322,7 +322,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
var filePath = _fileDbManager.GetCacheFilePath(fileHash, fileExtension); var filePath = _fileDbManager.GetCacheFilePath(fileHash, fileExtension);
await _fileCompactor.WriteAllBytesAsync(filePath, decompressedFile.ToArray(), token).ConfigureAwait(false); await _fileCompactor.WriteAllBytesAsync(filePath, decompressedFile.ToArray(), token).ConfigureAwait(false);
PersistFileToStorage(fileHash, filePath); PersistFileToStorage(fileHash, filePath, fileLengthBytes);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -355,7 +355,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
return await response.Content.ReadFromJsonAsync<List<DownloadFileDto>>(cancellationToken: ct).ConfigureAwait(false) ?? []; return await response.Content.ReadFromJsonAsync<List<DownloadFileDto>>(cancellationToken: ct).ConfigureAwait(false) ?? [];
} }
private void PersistFileToStorage(string fileHash, string filePath) private void PersistFileToStorage(string fileHash, string filePath, long? compressedSize = null)
{ {
var fi = new FileInfo(filePath); var fi = new FileInfo(filePath);
Func<DateTime> RandomDayInThePast() Func<DateTime> RandomDayInThePast()
@@ -378,6 +378,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
File.Delete(filePath); File.Delete(filePath);
_fileDbManager.RemoveHashedFile(entry.Hash, entry.PrefixedFilePath); _fileDbManager.RemoveHashedFile(entry.Hash, entry.PrefixedFilePath);
} }
if (entry != null)
entry.CompressedSize = compressedSize;
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -245,6 +245,8 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
var compressedSize = CurrentUploads.Sum(c => c.Total); var compressedSize = CurrentUploads.Sum(c => c.Total);
Logger.LogDebug("Upload complete, compressed {size} to {compressed}", UiSharedService.ByteToString(totalSize), UiSharedService.ByteToString(compressedSize)); Logger.LogDebug("Upload complete, compressed {size} to {compressed}", UiSharedService.ByteToString(totalSize), UiSharedService.ByteToString(compressedSize));
_fileDbManager.WriteOutFullCsv();
} }
foreach (var file in unverifiedUploadHashes.Where(c => !CurrentUploads.Exists(u => string.Equals(u.Hash, c, StringComparison.Ordinal)))) foreach (var file in unverifiedUploadHashes.Where(c => !CurrentUploads.Exists(u => string.Equals(u.Hash, c, StringComparison.Ordinal))))