Merge abel stuff
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
using FFXIVClientStructs.FFXIV.Client.UI.Info;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -11,24 +12,35 @@ public unsafe class BlockedCharacterHandler
|
|||||||
private readonly Dictionary<CharaData, bool> _blockedCharacterCache = new();
|
private readonly Dictionary<CharaData, bool> _blockedCharacterCache = new();
|
||||||
|
|
||||||
private readonly ILogger<BlockedCharacterHandler> _logger;
|
private readonly ILogger<BlockedCharacterHandler> _logger;
|
||||||
|
private readonly IObjectTable _objectTable;
|
||||||
|
|
||||||
public BlockedCharacterHandler(ILogger<BlockedCharacterHandler> logger, IGameInteropProvider gameInteropProvider)
|
public BlockedCharacterHandler(ILogger<BlockedCharacterHandler> logger, IGameInteropProvider gameInteropProvider, IObjectTable objectTable)
|
||||||
{
|
{
|
||||||
gameInteropProvider.InitializeFromAttributes(this);
|
gameInteropProvider.InitializeFromAttributes(this);
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_objectTable = objectTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CharaData GetIdsFromPlayerPointer(nint ptr)
|
private CharaData? TryGetIdsFromPlayerPointer(nint ptr, ushort objectIndex)
|
||||||
{
|
{
|
||||||
if (ptr == nint.Zero) return new(0, 0);
|
if (ptr == nint.Zero || objectIndex >= 200)
|
||||||
var castChar = ((BattleChara*)ptr);
|
return null;
|
||||||
|
|
||||||
|
var obj = _objectTable[objectIndex];
|
||||||
|
if (obj is not IPlayerCharacter player || player.Address != ptr)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var castChar = (BattleChara*)player.Address;
|
||||||
return new(castChar->Character.AccountId, castChar->Character.ContentId);
|
return new(castChar->Character.AccountId, castChar->Character.ContentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCharacterBlocked(nint ptr, out bool firstTime)
|
public bool IsCharacterBlocked(nint ptr, ushort objectIndex, out bool firstTime)
|
||||||
{
|
{
|
||||||
firstTime = false;
|
firstTime = false;
|
||||||
var combined = GetIdsFromPlayerPointer(ptr);
|
var combined = TryGetIdsFromPlayerPointer(ptr, objectIndex);
|
||||||
|
if (combined == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
if (_blockedCharacterCache.TryGetValue(combined, out var isBlocked))
|
if (_blockedCharacterCache.TryGetValue(combined, out var isBlocked))
|
||||||
return isBlocked;
|
return isBlocked;
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
public bool PreferNotesOverNamesForVisible { get; set; } = false;
|
||||||
public VisiblePairSortMode VisiblePairSortMode { get; set; } = VisiblePairSortMode.Alphabetical;
|
public VisiblePairSortMode VisiblePairSortMode { get; set; } = VisiblePairSortMode.Alphabetical;
|
||||||
public OnlinePairSortMode OnlinePairSortMode { get; set; } = OnlinePairSortMode.Alphabetical;
|
public OnlinePairSortMode OnlinePairSortMode { get; set; } = OnlinePairSortMode.Alphabetical;
|
||||||
|
public TextureFormatSortMode TextureFormatSortMode { get; set; } = TextureFormatSortMode.None;
|
||||||
public float ProfileDelay { get; set; } = 1.5f;
|
public float ProfileDelay { get; set; } = 1.5f;
|
||||||
public bool ProfilePopoutRight { get; set; } = false;
|
public bool ProfilePopoutRight { get; set; } = false;
|
||||||
public bool ProfilesAllowNsfw { get; set; } = false;
|
public bool ProfilesAllowNsfw { get; set; } = false;
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ public class PlayerPerformanceConfig : ILightlessConfiguration
|
|||||||
public int TextureDownscaleMaxDimension { get; set; } = 2048;
|
public int TextureDownscaleMaxDimension { get; set; } = 2048;
|
||||||
public bool OnlyDownscaleUncompressedTextures { get; set; } = true;
|
public bool OnlyDownscaleUncompressedTextures { get; set; } = true;
|
||||||
public bool KeepOriginalTextureFiles { get; set; } = false;
|
public bool KeepOriginalTextureFiles { get; set; } = false;
|
||||||
|
public bool SkipTextureDownscaleForPreferredPairs { get; set; } = true;
|
||||||
public bool EnableModelDecimation { get; set; } = false;
|
public bool EnableModelDecimation { get; set; } = false;
|
||||||
public int ModelDecimationTriangleThreshold { get; set; } = 50_000;
|
public int ModelDecimationTriangleThreshold { get; set; } = 20_000;
|
||||||
public double ModelDecimationTargetRatio { get; set; } = 0.8;
|
public double ModelDecimationTargetRatio { get; set; } = 0.8;
|
||||||
public bool KeepOriginalModelFiles { get; set; } = true;
|
public bool KeepOriginalModelFiles { get; set; } = true;
|
||||||
|
public bool SkipModelDecimationForPreferredPairs { get; set; } = true;
|
||||||
public bool ModelDecimationAllowBody { get; set; } = false;
|
public bool ModelDecimationAllowBody { get; set; } = false;
|
||||||
public bool ModelDecimationAllowFaceHead { get; set; } = false;
|
public bool ModelDecimationAllowFaceHead { get; set; } = false;
|
||||||
public bool ModelDecimationAllowTail { get; set; } = false;
|
public bool ModelDecimationAllowTail { get; set; } = false;
|
||||||
|
|||||||
@@ -169,6 +169,11 @@ public class PlayerDataFactory
|
|||||||
using var cts = new CancellationTokenSource(_hardBuildTimeout);
|
using var cts = new CancellationTokenSource(_hardBuildTimeout);
|
||||||
var fragment = await CreateCharacterDataInternal(obj, cts.Token).ConfigureAwait(false);
|
var fragment = await CreateCharacterDataInternal(obj, cts.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
fragment.FileReplacements =
|
||||||
|
new HashSet<FileReplacement>(resolvedPaths.Select(c => new FileReplacement([.. c.Value], c.Key)), FileReplacementComparer.Instance)
|
||||||
|
.Where(p => p.HasFileReplacement).ToHashSet();
|
||||||
|
var allowedExtensions = CacheMonitor.AllowedFileExtensions;
|
||||||
|
fragment.FileReplacements.RemoveWhere(c => c.GamePaths.Any(g => !allowedExtensions.Any(e => g.EndsWith(e, StringComparison.OrdinalIgnoreCase))));
|
||||||
_characterBuildCache[key] = new CacheEntry(fragment, DateTime.UtcNow);
|
_characterBuildCache[key] = new CacheEntry(fragment, DateTime.UtcNow);
|
||||||
PruneCharacterCacheIfNeeded();
|
PruneCharacterCacheIfNeeded();
|
||||||
|
|
||||||
@@ -213,6 +218,11 @@ public class PlayerDataFactory
|
|||||||
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct)
|
await _dalamudUtil.WaitWhileCharacterIsDrawing(_logger, playerRelatedObject, Guid.NewGuid(), 30000, ct: ct)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
// get all remaining paths and resolve them
|
||||||
|
var transientPaths = ManageSemiTransientData(objectKind);
|
||||||
|
var resolvedTransientPaths = transientPaths.Count == 0
|
||||||
|
? new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly()
|
||||||
|
: await GetFileReplacementsFromPaths(playerRelatedObject, transientPaths, new HashSet<string>(StringComparer.Ordinal)).ConfigureAwait(false);
|
||||||
ct.ThrowIfCancellationRequested();
|
ct.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (await CheckForNullDrawObject(playerRelatedObject.Address).ConfigureAwait(false))
|
if (await CheckForNullDrawObject(playerRelatedObject.Address).ConfigureAwait(false))
|
||||||
@@ -622,8 +632,15 @@ public class PlayerDataFactory
|
|||||||
{
|
{
|
||||||
var forwardPaths = forwardResolve.ToArray();
|
var forwardPaths = forwardResolve.ToArray();
|
||||||
var reversePaths = reverseResolve.ToArray();
|
var reversePaths = reverseResolve.ToArray();
|
||||||
Dictionary<string, List<string>> resolvedPaths = new(StringComparer.Ordinal);
|
if (forwardPaths.Length == 0 && reversePaths.Length == 0)
|
||||||
|
{
|
||||||
|
return new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase).AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
var forwardPathsLower = forwardPaths.Length == 0 ? Array.Empty<string>() : forwardPaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||||
|
var reversePathsLower = reversePaths.Length == 0 ? Array.Empty<string>() : reversePaths.Select(p => p.ToLowerInvariant()).ToArray();
|
||||||
|
|
||||||
|
Dictionary<string, List<string>> resolvedPaths = new(forwardPaths.Length + reversePaths.Length, StringComparer.Ordinal);
|
||||||
if (handler.ObjectKind != ObjectKind.Player)
|
if (handler.ObjectKind != ObjectKind.Player)
|
||||||
{
|
{
|
||||||
var (objectIndex, forwardResolved, reverseResolved) = await _dalamudUtil.RunOnFrameworkThread(() =>
|
var (objectIndex, forwardResolved, reverseResolved) = await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||||
@@ -654,12 +671,19 @@ public class PlayerDataFactory
|
|||||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
list.Add(forwardPaths[i].ToLowerInvariant());
|
list.Add(forwardPaths[i].ToLowerInvariant());
|
||||||
else
|
else
|
||||||
|
{
|
||||||
resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()];
|
resolvedPaths[filePath] = [forwardPaths[i].ToLowerInvariant()];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < reversePaths.Length; i++)
|
for (int i = 0; i < reversePaths.Length; i++)
|
||||||
{
|
{
|
||||||
var filePath = reversePaths[i].ToLowerInvariant();
|
var filePath = reversePathsLower[i];
|
||||||
|
var reverseResolvedLower = new string[reverseResolved[i].Length];
|
||||||
|
for (var j = 0; j < reverseResolvedLower.Length; j++)
|
||||||
|
{
|
||||||
|
reverseResolvedLower[j] = reverseResolved[i][j].ToLowerInvariant();
|
||||||
|
}
|
||||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
list.AddRange(reverseResolved[i].Select(c => c.ToLowerInvariant()));
|
list.AddRange(reverseResolved[i].Select(c => c.ToLowerInvariant()));
|
||||||
else
|
else
|
||||||
@@ -683,7 +707,12 @@ public class PlayerDataFactory
|
|||||||
|
|
||||||
for (int i = 0; i < reversePaths.Length; i++)
|
for (int i = 0; i < reversePaths.Length; i++)
|
||||||
{
|
{
|
||||||
var filePath = reversePaths[i].ToLowerInvariant();
|
var filePath = reversePathsLower[i];
|
||||||
|
var reverseResolvedLower = new string[reverse[i].Length];
|
||||||
|
for (var j = 0; j < reverseResolvedLower.Length; j++)
|
||||||
|
{
|
||||||
|
reverseResolvedLower[j] = reverse[i][j].ToLowerInvariant();
|
||||||
|
}
|
||||||
if (resolvedPaths.TryGetValue(filePath, out var list))
|
if (resolvedPaths.TryGetValue(filePath, out var list))
|
||||||
list.AddRange(reverse[i].Select(c => c.ToLowerInvariant()));
|
list.AddRange(reverse[i].Select(c => c.ToLowerInvariant()));
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using LightlessSync.API.Data.Enum;
|
|||||||
using LightlessSync.API.Data.Extensions;
|
using LightlessSync.API.Data.Extensions;
|
||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using LightlessSync.Interop.Ipc;
|
using LightlessSync.Interop.Ipc;
|
||||||
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.PlayerData.Factories;
|
using LightlessSync.PlayerData.Factories;
|
||||||
using LightlessSync.PlayerData.Handlers;
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
@@ -39,6 +40,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
private readonly ActorObjectService _actorObjectService;
|
private readonly ActorObjectService _actorObjectService;
|
||||||
private readonly FileDownloadManager _downloadManager;
|
private readonly FileDownloadManager _downloadManager;
|
||||||
private readonly FileCacheManager _fileDbManager;
|
private readonly FileCacheManager _fileDbManager;
|
||||||
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly IHostApplicationLifetime _lifetime;
|
private readonly IHostApplicationLifetime _lifetime;
|
||||||
@@ -204,6 +206,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
ActorObjectService actorObjectService,
|
ActorObjectService actorObjectService,
|
||||||
IHostApplicationLifetime lifetime,
|
IHostApplicationLifetime lifetime,
|
||||||
FileCacheManager fileDbManager,
|
FileCacheManager fileDbManager,
|
||||||
|
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||||
PlayerPerformanceService playerPerformanceService,
|
PlayerPerformanceService playerPerformanceService,
|
||||||
PairProcessingLimiter pairProcessingLimiter,
|
PairProcessingLimiter pairProcessingLimiter,
|
||||||
ServerConfigurationManager serverConfigManager,
|
ServerConfigurationManager serverConfigManager,
|
||||||
@@ -226,6 +229,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
_actorObjectService = actorObjectService;
|
_actorObjectService = actorObjectService;
|
||||||
_lifetime = lifetime;
|
_lifetime = lifetime;
|
||||||
_fileDbManager = fileDbManager;
|
_fileDbManager = fileDbManager;
|
||||||
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
_playerPerformanceService = playerPerformanceService;
|
_playerPerformanceService = playerPerformanceService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
_pairProcessingLimiter = pairProcessingLimiter;
|
||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
@@ -533,11 +537,31 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
return GetCurrentPairs().Any(predicate);
|
return GetCurrentPairs().Any(predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldSkipDownscale()
|
private bool IsPreferredDirectPair()
|
||||||
{
|
{
|
||||||
return GetCurrentPairs().Any(p => p.IsDirectlyPaired && p.SelfToOtherPermissions.IsSticky());
|
return GetCurrentPairs().Any(p => p.IsDirectlyPaired && p.SelfToOtherPermissions.IsSticky());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ShouldSkipDownscale()
|
||||||
|
{
|
||||||
|
if (!_playerPerformanceConfigService.Current.SkipTextureDownscaleForPreferredPairs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsPreferredDirectPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldSkipDecimation()
|
||||||
|
{
|
||||||
|
if (!_playerPerformanceConfigService.Current.SkipModelDecimationForPreferredPairs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsPreferredDirectPair();
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsPaused()
|
private bool IsPaused()
|
||||||
{
|
{
|
||||||
var pairs = GetCurrentPairs();
|
var pairs = GetCurrentPairs();
|
||||||
@@ -631,9 +655,8 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
var dataApplied = !string.IsNullOrEmpty(dataHash)
|
var dataApplied = !string.IsNullOrEmpty(dataHash)
|
||||||
&& string.Equals(dataHash, _lastSuccessfulDataHash ?? string.Empty, StringComparison.Ordinal);
|
&& string.Equals(dataHash, _lastSuccessfulDataHash ?? string.Empty, StringComparison.Ordinal);
|
||||||
var needsApply = !dataApplied;
|
var needsApply = !dataApplied;
|
||||||
var hasModReplacements = sanitized.FileReplacements.Values.Any(list => list.Count > 0);
|
var modFilesChanged = PlayerModFilesChanged(sanitized, _cachedData);
|
||||||
var needsModReapply = needsApply && hasModReplacements;
|
var shouldForceMods = shouldForce || modFilesChanged;
|
||||||
var shouldForceMods = shouldForce || needsModReapply;
|
|
||||||
forceApplyCustomization = forced || needsApply;
|
forceApplyCustomization = forced || needsApply;
|
||||||
var suppressForcedModRedraw = !forced && hasMissingCachedFiles && dataApplied;
|
var suppressForcedModRedraw = !forced && hasMissingCachedFiles && dataApplied;
|
||||||
|
|
||||||
@@ -1854,6 +1877,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
bool skipDownscaleForPair = ShouldSkipDownscale();
|
bool skipDownscaleForPair = ShouldSkipDownscale();
|
||||||
|
bool skipDecimationForPair = ShouldSkipDecimation();
|
||||||
var user = GetPrimaryUserData();
|
var user = GetPrimaryUserData();
|
||||||
Dictionary<(string GamePath, string? Hash), string> moddedPaths;
|
Dictionary<(string GamePath, string? Hash), string> moddedPaths;
|
||||||
List<FileReplacementData> missingReplacements = [];
|
List<FileReplacementData> missingReplacements = [];
|
||||||
@@ -1892,7 +1916,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
}
|
}
|
||||||
|
|
||||||
var handlerForDownload = _charaHandler;
|
var handlerForDownload = _charaHandler;
|
||||||
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair).ConfigureAwait(false));
|
_pairDownloadTask = Task.Run(async () => await _downloadManager.DownloadFiles(handlerForDownload, toDownloadReplacements, downloadToken, skipDownscaleForPair, skipDecimationForPair).ConfigureAwait(false));
|
||||||
|
|
||||||
await _pairDownloadTask.ConfigureAwait(false);
|
await _pairDownloadTask.ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -1915,7 +1939,10 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
{
|
{
|
||||||
await _textureDownscaleService.WaitForPendingJobsAsync(downloadedTextureHashes, downloadToken).ConfigureAwait(false);
|
await _textureDownscaleService.WaitForPendingJobsAsync(downloadedTextureHashes, downloadToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!skipDecimationForPair)
|
||||||
|
{
|
||||||
var downloadedModelHashes = toDownloadReplacements
|
var downloadedModelHashes = toDownloadReplacements
|
||||||
.Where(static replacement => replacement.GamePaths.Any(static path => path.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)))
|
.Where(static replacement => replacement.GamePaths.Any(static path => path.EndsWith(".mdl", StringComparison.OrdinalIgnoreCase)))
|
||||||
.Select(static replacement => replacement.Hash)
|
.Select(static replacement => replacement.Hash)
|
||||||
@@ -2424,6 +2451,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
|||||||
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
|
ConcurrentDictionary<(string GamePath, string? Hash), string> outputDict = new();
|
||||||
bool hasMigrationChanges = false;
|
bool hasMigrationChanges = false;
|
||||||
bool skipDownscaleForPair = ShouldSkipDownscale();
|
bool skipDownscaleForPair = ShouldSkipDownscale();
|
||||||
|
bool skipDecimationForPair = ShouldSkipDecimation();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IHostApplicationLifetime _lifetime;
|
private readonly IHostApplicationLifetime _lifetime;
|
||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||||
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
private readonly PairProcessingLimiter _pairProcessingLimiter;
|
||||||
private readonly ServerConfigurationManager _serverConfigManager;
|
private readonly ServerConfigurationManager _serverConfigManager;
|
||||||
@@ -52,6 +53,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
IFramework framework,
|
IFramework framework,
|
||||||
IHostApplicationLifetime lifetime,
|
IHostApplicationLifetime lifetime,
|
||||||
FileCacheManager fileCacheManager,
|
FileCacheManager fileCacheManager,
|
||||||
|
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||||
PlayerPerformanceService playerPerformanceService,
|
PlayerPerformanceService playerPerformanceService,
|
||||||
PairProcessingLimiter pairProcessingLimiter,
|
PairProcessingLimiter pairProcessingLimiter,
|
||||||
ServerConfigurationManager serverConfigManager,
|
ServerConfigurationManager serverConfigManager,
|
||||||
@@ -74,6 +76,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
_framework = framework;
|
_framework = framework;
|
||||||
_lifetime = lifetime;
|
_lifetime = lifetime;
|
||||||
_fileCacheManager = fileCacheManager;
|
_fileCacheManager = fileCacheManager;
|
||||||
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
_playerPerformanceService = playerPerformanceService;
|
_playerPerformanceService = playerPerformanceService;
|
||||||
_pairProcessingLimiter = pairProcessingLimiter;
|
_pairProcessingLimiter = pairProcessingLimiter;
|
||||||
_serverConfigManager = serverConfigManager;
|
_serverConfigManager = serverConfigManager;
|
||||||
@@ -105,6 +108,7 @@ internal sealed class PairHandlerAdapterFactory : IPairHandlerAdapterFactory
|
|||||||
actorObjectService,
|
actorObjectService,
|
||||||
_lifetime,
|
_lifetime,
|
||||||
_fileCacheManager,
|
_fileCacheManager,
|
||||||
|
_playerPerformanceConfigService,
|
||||||
_playerPerformanceService,
|
_playerPerformanceService,
|
||||||
_pairProcessingLimiter,
|
_pairProcessingLimiter,
|
||||||
_serverConfigManager,
|
_serverConfigManager,
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
services.AddSingleton<HubFactory>();
|
services.AddSingleton<HubFactory>();
|
||||||
services.AddSingleton<FileUploadManager>();
|
services.AddSingleton<FileUploadManager>();
|
||||||
services.AddSingleton<FileTransferOrchestrator>();
|
services.AddSingleton<FileTransferOrchestrator>();
|
||||||
|
services.AddSingleton<FileDownloadDeduplicator>();
|
||||||
services.AddSingleton<LightlessPlugin>();
|
services.AddSingleton<LightlessPlugin>();
|
||||||
services.AddSingleton<LightlessProfileManager>();
|
services.AddSingleton<LightlessProfileManager>();
|
||||||
services.AddSingleton<TextureCompressionService>();
|
services.AddSingleton<TextureCompressionService>();
|
||||||
@@ -180,7 +181,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
|
|
||||||
services.AddSingleton(sp => new BlockedCharacterHandler(
|
services.AddSingleton(sp => new BlockedCharacterHandler(
|
||||||
sp.GetRequiredService<ILogger<BlockedCharacterHandler>>(),
|
sp.GetRequiredService<ILogger<BlockedCharacterHandler>>(),
|
||||||
gameInteropProvider));
|
gameInteropProvider,
|
||||||
|
objectTable));
|
||||||
|
|
||||||
services.AddSingleton(sp => new IpcProvider(
|
services.AddSingleton(sp => new IpcProvider(
|
||||||
sp.GetRequiredService<ILogger<IpcProvider>>(),
|
sp.GetRequiredService<ILogger<IpcProvider>>(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using FFXIVClientStructs.Interop;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using LightlessSync.PlayerData.Handlers;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -16,7 +17,7 @@ using LightlessObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
|||||||
|
|
||||||
namespace LightlessSync.Services.ActorTracking;
|
namespace LightlessSync.Services.ActorTracking;
|
||||||
|
|
||||||
public sealed class ActorObjectService : IHostedService, IDisposable
|
public sealed class ActorObjectService : IHostedService, IDisposable, IMediatorSubscriber
|
||||||
{
|
{
|
||||||
public readonly record struct ActorDescriptor(
|
public readonly record struct ActorDescriptor(
|
||||||
string Name,
|
string Name,
|
||||||
@@ -36,6 +37,8 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
private readonly ICondition _condition;
|
private readonly ICondition _condition;
|
||||||
private readonly LightlessMediator _mediator;
|
private readonly LightlessMediator _mediator;
|
||||||
|
private readonly object _playerRelatedHandlerLock = new();
|
||||||
|
private readonly HashSet<GameObjectHandler> _playerRelatedHandlers = [];
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<nint, ActorDescriptor> _activePlayers = new();
|
private readonly ConcurrentDictionary<nint, ActorDescriptor> _activePlayers = new();
|
||||||
private readonly ConcurrentDictionary<nint, ActorDescriptor> _gposePlayers = new();
|
private readonly ConcurrentDictionary<nint, ActorDescriptor> _gposePlayers = new();
|
||||||
@@ -71,6 +74,25 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
_condition = condition;
|
_condition = condition;
|
||||||
_mediator = mediator;
|
_mediator = mediator;
|
||||||
|
|
||||||
|
_mediator.Subscribe<GameObjectHandlerCreatedMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
if (!msg.OwnedObject) return;
|
||||||
|
lock (_playerRelatedHandlerLock)
|
||||||
|
{
|
||||||
|
_playerRelatedHandlers.Add(msg.GameObjectHandler);
|
||||||
|
}
|
||||||
|
RefreshTrackedActors(force: true);
|
||||||
|
});
|
||||||
|
_mediator.Subscribe<GameObjectHandlerDestroyedMessage>(this, (msg) =>
|
||||||
|
{
|
||||||
|
if (!msg.OwnedObject) return;
|
||||||
|
lock (_playerRelatedHandlerLock)
|
||||||
|
{
|
||||||
|
_playerRelatedHandlers.Remove(msg.GameObjectHandler);
|
||||||
|
}
|
||||||
|
RefreshTrackedActors(force: true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
|
private bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
|
||||||
@@ -84,6 +106,7 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
public IReadOnlyList<ActorDescriptor> PlayerDescriptors => Snapshot.PlayerDescriptors;
|
public IReadOnlyList<ActorDescriptor> PlayerDescriptors => Snapshot.PlayerDescriptors;
|
||||||
public IReadOnlyList<ActorDescriptor> OwnedDescriptors => Snapshot.OwnedDescriptors;
|
public IReadOnlyList<ActorDescriptor> OwnedDescriptors => Snapshot.OwnedDescriptors;
|
||||||
public IReadOnlyList<ActorDescriptor> GposeDescriptors => CurrentGposeSnapshot.GposeDescriptors;
|
public IReadOnlyList<ActorDescriptor> GposeDescriptors => CurrentGposeSnapshot.GposeDescriptors;
|
||||||
|
public LightlessMediator Mediator => _mediator;
|
||||||
|
|
||||||
public bool TryGetActorByHash(string hash, out ActorDescriptor descriptor) => _actorsByHash.TryGetValue(hash, out descriptor);
|
public bool TryGetActorByHash(string hash, out ActorDescriptor descriptor) => _actorsByHash.TryGetValue(hash, out descriptor);
|
||||||
public bool TryGetValidatedActorByHash(string hash, out ActorDescriptor descriptor)
|
public bool TryGetValidatedActorByHash(string hash, out ActorDescriptor descriptor)
|
||||||
@@ -324,6 +347,11 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
_actorsByHash.Clear();
|
_actorsByHash.Clear();
|
||||||
_actorsByName.Clear();
|
_actorsByName.Clear();
|
||||||
_pendingHashResolutions.Clear();
|
_pendingHashResolutions.Clear();
|
||||||
|
_mediator.UnsubscribeAll(this);
|
||||||
|
lock (_playerRelatedHandlerLock)
|
||||||
|
{
|
||||||
|
_playerRelatedHandlers.Clear();
|
||||||
|
}
|
||||||
Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
|
Volatile.Write(ref _snapshot, ActorSnapshot.Empty);
|
||||||
Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty);
|
Volatile.Write(ref _gposeSnapshot, GposeSnapshot.Empty);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -500,7 +528,9 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
if (objectKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
if (objectKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
||||||
{
|
{
|
||||||
var expectedMinionOrMount = GetMinionOrMountAddress(localPlayerAddress, localEntityId);
|
var expectedMinionOrMount = GetMinionOrMountAddress(localPlayerAddress, localEntityId);
|
||||||
if (expectedMinionOrMount != nint.Zero && (nint)gameObject == expectedMinionOrMount)
|
if (expectedMinionOrMount != nint.Zero
|
||||||
|
&& (nint)gameObject == expectedMinionOrMount
|
||||||
|
&& IsPlayerRelatedOwnedAddress(expectedMinionOrMount, LightlessObjectKind.MinionOrMount))
|
||||||
{
|
{
|
||||||
var resolvedOwner = ownerId != 0 ? ownerId : localEntityId;
|
var resolvedOwner = ownerId != 0 ? ownerId : localEntityId;
|
||||||
return (LightlessObjectKind.MinionOrMount, resolvedOwner);
|
return (LightlessObjectKind.MinionOrMount, resolvedOwner);
|
||||||
@@ -514,16 +544,37 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
return (null, ownerId);
|
return (null, ownerId);
|
||||||
|
|
||||||
var expectedPet = GetPetAddress(localPlayerAddress, localEntityId);
|
var expectedPet = GetPetAddress(localPlayerAddress, localEntityId);
|
||||||
if (expectedPet != nint.Zero && (nint)gameObject == expectedPet)
|
if (expectedPet != nint.Zero
|
||||||
|
&& (nint)gameObject == expectedPet
|
||||||
|
&& IsPlayerRelatedOwnedAddress(expectedPet, LightlessObjectKind.Pet))
|
||||||
return (LightlessObjectKind.Pet, ownerId);
|
return (LightlessObjectKind.Pet, ownerId);
|
||||||
|
|
||||||
var expectedCompanion = GetCompanionAddress(localPlayerAddress, localEntityId);
|
var expectedCompanion = GetCompanionAddress(localPlayerAddress, localEntityId);
|
||||||
if (expectedCompanion != nint.Zero && (nint)gameObject == expectedCompanion)
|
if (expectedCompanion != nint.Zero
|
||||||
|
&& (nint)gameObject == expectedCompanion
|
||||||
|
&& IsPlayerRelatedOwnedAddress(expectedCompanion, LightlessObjectKind.Companion))
|
||||||
return (LightlessObjectKind.Companion, ownerId);
|
return (LightlessObjectKind.Companion, ownerId);
|
||||||
|
|
||||||
return (null, ownerId);
|
return (null, ownerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsPlayerRelatedOwnedAddress(nint address, LightlessObjectKind expectedKind)
|
||||||
|
{
|
||||||
|
if (address == nint.Zero)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lock (_playerRelatedHandlerLock)
|
||||||
|
{
|
||||||
|
foreach (var handler in _playerRelatedHandlers)
|
||||||
|
{
|
||||||
|
if (handler.Address == address && handler.ObjectKind == expectedKind)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private unsafe nint GetMinionOrMountAddress(nint localPlayerAddress, uint ownerEntityId)
|
private unsafe nint GetMinionOrMountAddress(nint localPlayerAddress, uint ownerEntityId)
|
||||||
{
|
{
|
||||||
if (localPlayerAddress == nint.Zero)
|
if (localPlayerAddress == nint.Zero)
|
||||||
@@ -531,20 +582,20 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
|
|
||||||
var playerObject = (GameObject*)localPlayerAddress;
|
var playerObject = (GameObject*)localPlayerAddress;
|
||||||
var candidateAddress = _objectTable.GetObjectAddress(playerObject->ObjectIndex + 1);
|
var candidateAddress = _objectTable.GetObjectAddress(playerObject->ObjectIndex + 1);
|
||||||
|
if (ownerEntityId == 0)
|
||||||
|
return nint.Zero;
|
||||||
|
|
||||||
if (candidateAddress != nint.Zero)
|
if (candidateAddress != nint.Zero)
|
||||||
{
|
{
|
||||||
var candidate = (GameObject*)candidateAddress;
|
var candidate = (GameObject*)candidateAddress;
|
||||||
var candidateKind = (DalamudObjectKind)candidate->ObjectKind;
|
var candidateKind = (DalamudObjectKind)candidate->ObjectKind;
|
||||||
if (candidateKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
if (candidateKind is DalamudObjectKind.MountType or DalamudObjectKind.Companion)
|
||||||
{
|
{
|
||||||
if (ownerEntityId == 0 || ResolveOwnerId(candidate) == ownerEntityId)
|
if (ResolveOwnerId(candidate) == ownerEntityId)
|
||||||
return candidateAddress;
|
return candidateAddress;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ownerEntityId == 0)
|
|
||||||
return candidateAddress;
|
|
||||||
|
|
||||||
foreach (var obj in _objectTable)
|
foreach (var obj in _objectTable)
|
||||||
{
|
{
|
||||||
if (obj is null || obj.Address == nint.Zero || obj.Address == localPlayerAddress)
|
if (obj is null || obj.Address == nint.Zero || obj.Address == localPlayerAddress)
|
||||||
@@ -558,7 +609,7 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
return obj.Address;
|
return obj.Address;
|
||||||
}
|
}
|
||||||
|
|
||||||
return candidateAddress;
|
return nint.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe nint GetPetAddress(nint localPlayerAddress, uint ownerEntityId)
|
private unsafe nint GetPetAddress(nint localPlayerAddress, uint ownerEntityId)
|
||||||
@@ -1029,6 +1080,7 @@ public sealed class ActorObjectService : IHostedService, IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DisposeHooks();
|
DisposeHooks();
|
||||||
|
_mediator.UnsubscribeAll(this);
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1022,7 +1022,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
if (actor.ObjectIndex >= 200)
|
if (actor.ObjectIndex >= 200)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, out bool firstTime) && firstTime)
|
if (_blockedCharacterHandler.IsCharacterBlocked(playerAddress, actor.ObjectIndex, out bool firstTime) && firstTime)
|
||||||
{
|
{
|
||||||
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
|
_logger.LogTrace("Skipping character {addr}, blocked/muted", playerAddress.ToString("X"));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Lumina.Data.Parsing;
|
using Lumina.Data.Parsing;
|
||||||
using Lumina.Extensions;
|
using Lumina.Extensions;
|
||||||
using MeshDecimator;
|
using MeshDecimator;
|
||||||
|
using MeshDecimator.Algorithms;
|
||||||
using MeshDecimator.Math;
|
using MeshDecimator.Math;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Penumbra.GameData.Files.ModelStructs;
|
using Penumbra.GameData.Files.ModelStructs;
|
||||||
@@ -94,6 +95,8 @@ internal static class MdlDecimator
|
|||||||
var newVertexBuffer = new List<byte>(mdl.VertexBufferSize[lodIndex] > 0 ? (int)mdl.VertexBufferSize[lodIndex] : 0);
|
var newVertexBuffer = new List<byte>(mdl.VertexBufferSize[lodIndex] > 0 ? (int)mdl.VertexBufferSize[lodIndex] : 0);
|
||||||
var newIndexBuffer = new List<ushort>(mdl.IndexBufferSize[lodIndex] > 0 ? (int)(mdl.IndexBufferSize[lodIndex] / sizeof(ushort)) : 0);
|
var newIndexBuffer = new List<ushort>(mdl.IndexBufferSize[lodIndex] > 0 ? (int)(mdl.IndexBufferSize[lodIndex] / sizeof(ushort)) : 0);
|
||||||
var subMeshCursor = 0;
|
var subMeshCursor = 0;
|
||||||
|
DecimationAlgorithm? decimationAlgorithm = null;
|
||||||
|
int? decimationUvChannelCount = null;
|
||||||
|
|
||||||
for (var meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
|
for (var meshIndex = 0; meshIndex < meshes.Length; meshIndex++)
|
||||||
{
|
{
|
||||||
@@ -119,6 +122,8 @@ internal static class MdlDecimator
|
|||||||
out vertexStreams,
|
out vertexStreams,
|
||||||
out indices,
|
out indices,
|
||||||
out decimated,
|
out decimated,
|
||||||
|
ref decimationAlgorithm,
|
||||||
|
ref decimationUvChannelCount,
|
||||||
logger))
|
logger))
|
||||||
{
|
{
|
||||||
updatedSubMeshes = OffsetSubMeshes(updatedSubMeshes, meshIndexBase);
|
updatedSubMeshes = OffsetSubMeshes(updatedSubMeshes, meshIndexBase);
|
||||||
@@ -309,6 +314,8 @@ internal static class MdlDecimator
|
|||||||
out byte[][] vertexStreams,
|
out byte[][] vertexStreams,
|
||||||
out int[] indices,
|
out int[] indices,
|
||||||
out bool decimated,
|
out bool decimated,
|
||||||
|
ref DecimationAlgorithm? decimationAlgorithm,
|
||||||
|
ref int? decimationUvChannelCount,
|
||||||
MsLogger logger)
|
MsLogger logger)
|
||||||
{
|
{
|
||||||
updatedMesh = mesh;
|
updatedMesh = mesh;
|
||||||
@@ -352,8 +359,7 @@ internal static class MdlDecimator
|
|||||||
}
|
}
|
||||||
|
|
||||||
var meshDecimatorMesh = BuildMesh(decoded, subMeshIndices);
|
var meshDecimatorMesh = BuildMesh(decoded, subMeshIndices);
|
||||||
var algorithm = MeshDecimation.CreateAlgorithm(Algorithm.Default);
|
var algorithm = GetOrCreateAlgorithm(format, ref decimationAlgorithm, ref decimationUvChannelCount, logger);
|
||||||
algorithm.Logger = logger;
|
|
||||||
algorithm.Initialize(meshDecimatorMesh);
|
algorithm.Initialize(meshDecimatorMesh);
|
||||||
algorithm.DecimateMesh(targetTriangles);
|
algorithm.DecimateMesh(targetTriangles);
|
||||||
var decimatedMesh = algorithm.ToMesh();
|
var decimatedMesh = algorithm.ToMesh();
|
||||||
@@ -374,6 +380,23 @@ internal static class MdlDecimator
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DecimationAlgorithm GetOrCreateAlgorithm(
|
||||||
|
VertexFormat format,
|
||||||
|
ref DecimationAlgorithm? decimationAlgorithm,
|
||||||
|
ref int? decimationUvChannelCount,
|
||||||
|
MsLogger logger)
|
||||||
|
{
|
||||||
|
var uvChannelCount = format.UvChannelCount;
|
||||||
|
if (decimationAlgorithm == null || decimationUvChannelCount != uvChannelCount)
|
||||||
|
{
|
||||||
|
decimationAlgorithm = MeshDecimation.CreateAlgorithm(Algorithm.Default);
|
||||||
|
decimationAlgorithm.Logger = logger;
|
||||||
|
decimationUvChannelCount = uvChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decimationAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
private static Mesh BuildMesh(DecodedMeshData decoded, int[][] subMeshIndices)
|
private static Mesh BuildMesh(DecodedMeshData decoded, int[][] subMeshIndices)
|
||||||
{
|
{
|
||||||
var mesh = new Mesh(decoded.Positions, subMeshIndices);
|
var mesh = new Mesh(decoded.Positions, subMeshIndices);
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ public class PlayerPerformanceService
|
|||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
var skipDecimation = config.SkipModelDecimationForPreferredPairs && pairHandler.IsDirectlyPaired && pairHandler.HasStickyPermissions;
|
||||||
|
|
||||||
foreach (var hash in moddedModelHashes)
|
foreach (var hash in moddedModelHashes)
|
||||||
{
|
{
|
||||||
var tris = await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
|
var tris = await _xivDataAnalyzer.GetTrianglesByHash(hash).ConfigureAwait(false);
|
||||||
@@ -138,7 +140,12 @@ public class PlayerPerformanceService
|
|||||||
var fileEntry = _fileCacheManager.GetFileCacheByHash(hash);
|
var fileEntry = _fileCacheManager.GetFileCacheByHash(hash);
|
||||||
if (fileEntry != null)
|
if (fileEntry != null)
|
||||||
{
|
{
|
||||||
var preferredPath = _modelDecimationService.GetPreferredPath(hash, fileEntry.ResolvedFilepath);
|
var preferredPath = fileEntry.ResolvedFilepath;
|
||||||
|
if (!skipDecimation)
|
||||||
|
{
|
||||||
|
preferredPath = _modelDecimationService.GetPreferredPath(hash, fileEntry.ResolvedFilepath);
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.Equals(preferredPath, fileEntry.ResolvedFilepath, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(preferredPath, fileEntry.ResolvedFilepath, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var decimatedTris = await _xivDataAnalyzer.GetEffectiveTrianglesByHash(hash, preferredPath).ConfigureAwait(false);
|
var decimatedTris = await _xivDataAnalyzer.GetEffectiveTrianglesByHash(hash, preferredPath).ConfigureAwait(false);
|
||||||
@@ -192,7 +199,9 @@ public class PlayerPerformanceService
|
|||||||
public bool ComputeAndAutoPauseOnVRAMUsageThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData, List<DownloadFileTransfer> toDownloadFiles)
|
public bool ComputeAndAutoPauseOnVRAMUsageThresholds(IPairPerformanceSubject pairHandler, CharacterData charaData, List<DownloadFileTransfer> toDownloadFiles)
|
||||||
{
|
{
|
||||||
var config = _playerPerformanceConfigService.Current;
|
var config = _playerPerformanceConfigService.Current;
|
||||||
bool skipDownscale = pairHandler.IsDirectlyPaired && pairHandler.HasStickyPermissions;
|
bool skipDownscale = config.SkipTextureDownscaleForPreferredPairs
|
||||||
|
&& pairHandler.IsDirectlyPaired
|
||||||
|
&& pairHandler.HasStickyPermissions;
|
||||||
|
|
||||||
long vramUsage = 0;
|
long vramUsage = 0;
|
||||||
long effectiveVramUsage = 0;
|
long effectiveVramUsage = 0;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using LightlessSync.LightlessConfiguration;
|
|||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
using LightlessSync.Services.TextureCompression;
|
using LightlessSync.Services.TextureCompression;
|
||||||
|
using LightlessSync.UI.Models;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using OtterTex;
|
using OtterTex;
|
||||||
@@ -42,6 +43,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
private readonly Progress<TextureConversionProgress> _conversionProgress = new();
|
private readonly Progress<TextureConversionProgress> _conversionProgress = new();
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
private readonly UiSharedService _uiSharedService;
|
private readonly UiSharedService _uiSharedService;
|
||||||
|
private readonly LightlessConfigService _configService;
|
||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfig;
|
||||||
private readonly TransientResourceManager _transientResourceManager;
|
private readonly TransientResourceManager _transientResourceManager;
|
||||||
private readonly TransientConfigService _transientConfigService;
|
private readonly TransientConfigService _transientConfigService;
|
||||||
@@ -106,10 +108,12 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
private TextureUsageCategory? _textureCategoryFilter = null;
|
private TextureUsageCategory? _textureCategoryFilter = null;
|
||||||
private TextureMapKind? _textureMapFilter = null;
|
private TextureMapKind? _textureMapFilter = null;
|
||||||
private TextureCompressionTarget? _textureTargetFilter = null;
|
private TextureCompressionTarget? _textureTargetFilter = null;
|
||||||
|
private TextureFormatSortMode _textureFormatSortMode = TextureFormatSortMode.None;
|
||||||
|
|
||||||
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, LightlessMediator mediator,
|
public DataAnalysisUi(ILogger<DataAnalysisUi> logger, LightlessMediator mediator,
|
||||||
CharacterAnalyzer characterAnalyzer, IpcManager ipcManager,
|
CharacterAnalyzer characterAnalyzer, IpcManager ipcManager,
|
||||||
PerformanceCollectorService performanceCollectorService, UiSharedService uiSharedService,
|
PerformanceCollectorService performanceCollectorService, UiSharedService uiSharedService,
|
||||||
|
LightlessConfigService configService,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfig, TransientResourceManager transientResourceManager,
|
PlayerPerformanceConfigService playerPerformanceConfig, TransientResourceManager transientResourceManager,
|
||||||
TransientConfigService transientConfigService, TextureCompressionService textureCompressionService,
|
TransientConfigService transientConfigService, TextureCompressionService textureCompressionService,
|
||||||
TextureMetadataHelper textureMetadataHelper)
|
TextureMetadataHelper textureMetadataHelper)
|
||||||
@@ -118,6 +122,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_characterAnalyzer = characterAnalyzer;
|
_characterAnalyzer = characterAnalyzer;
|
||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
_uiSharedService = uiSharedService;
|
_uiSharedService = uiSharedService;
|
||||||
|
_configService = configService;
|
||||||
_playerPerformanceConfig = playerPerformanceConfig;
|
_playerPerformanceConfig = playerPerformanceConfig;
|
||||||
_transientResourceManager = transientResourceManager;
|
_transientResourceManager = transientResourceManager;
|
||||||
_transientConfigService = transientConfigService;
|
_transientConfigService = transientConfigService;
|
||||||
@@ -971,6 +976,13 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
ResetDebugCompressionModalState();
|
ResetDebugCompressionModalState();
|
||||||
#endif
|
#endif
|
||||||
|
var savedFormatSort = _configService.Current.TextureFormatSortMode;
|
||||||
|
if (!Enum.IsDefined(typeof(TextureFormatSortMode), savedFormatSort))
|
||||||
|
{
|
||||||
|
savedFormatSort = TextureFormatSortMode.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTextureFormatSortMode(savedFormatSort, persist: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
protected override void Dispose(bool disposing)
|
||||||
@@ -2198,26 +2210,56 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.TableSetupColumn("Original", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn("Original", ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
ImGui.TableSetupColumn("Compressed", ImGuiTableColumnFlags.PreferSortDescending);
|
ImGui.TableSetupColumn("Compressed", ImGuiTableColumnFlags.PreferSortDescending);
|
||||||
ImGui.TableSetupScrollFreeze(0, 1);
|
ImGui.TableSetupScrollFreeze(0, 1);
|
||||||
ImGui.TableHeadersRow();
|
DrawTextureTableHeaderRow();
|
||||||
|
|
||||||
var targets = _textureCompressionService.SelectableTargets;
|
var targets = _textureCompressionService.SelectableTargets;
|
||||||
|
|
||||||
IEnumerable<TextureRow> orderedRows = rows;
|
IEnumerable<TextureRow> orderedRows = rows;
|
||||||
var sortSpecs = ImGui.TableGetSortSpecs();
|
var sortSpecs = ImGui.TableGetSortSpecs();
|
||||||
|
var sizeSortColumn = -1;
|
||||||
|
var sizeSortDirection = ImGuiSortDirection.Ascending;
|
||||||
if (sortSpecs.SpecsCount > 0)
|
if (sortSpecs.SpecsCount > 0)
|
||||||
{
|
{
|
||||||
var spec = sortSpecs.Specs[0];
|
var spec = sortSpecs.Specs[0];
|
||||||
orderedRows = spec.ColumnIndex switch
|
if (spec.ColumnIndex is 7 or 8)
|
||||||
{
|
{
|
||||||
7 => spec.SortDirection == ImGuiSortDirection.Ascending
|
sizeSortColumn = spec.ColumnIndex;
|
||||||
? rows.OrderBy(r => r.OriginalSize)
|
sizeSortDirection = spec.SortDirection;
|
||||||
: rows.OrderByDescending(r => r.OriginalSize),
|
}
|
||||||
8 => spec.SortDirection == ImGuiSortDirection.Ascending
|
}
|
||||||
? rows.OrderBy(r => r.CompressedSize)
|
|
||||||
: rows.OrderByDescending(r => r.CompressedSize),
|
|
||||||
_ => rows
|
|
||||||
};
|
|
||||||
|
|
||||||
|
var hasSizeSort = sizeSortColumn != -1;
|
||||||
|
var indexedRows = rows.Select((row, idx) => (row, idx));
|
||||||
|
|
||||||
|
if (_textureFormatSortMode != TextureFormatSortMode.None)
|
||||||
|
{
|
||||||
|
bool compressedFirst = _textureFormatSortMode == TextureFormatSortMode.CompressedFirst;
|
||||||
|
int GroupKey(TextureRow row) => row.IsAlreadyCompressed == compressedFirst ? 0 : 1;
|
||||||
|
long SizeKey(TextureRow row) => sizeSortColumn == 7 ? row.OriginalSize : row.CompressedSize;
|
||||||
|
|
||||||
|
var ordered = indexedRows.OrderBy(pair => GroupKey(pair.row));
|
||||||
|
if (hasSizeSort)
|
||||||
|
{
|
||||||
|
ordered = sizeSortDirection == ImGuiSortDirection.Ascending
|
||||||
|
? ordered.ThenBy(pair => SizeKey(pair.row))
|
||||||
|
: ordered.ThenByDescending(pair => SizeKey(pair.row));
|
||||||
|
}
|
||||||
|
|
||||||
|
orderedRows = ordered
|
||||||
|
.ThenBy(pair => pair.idx)
|
||||||
|
.Select(pair => pair.row);
|
||||||
|
}
|
||||||
|
else if (hasSizeSort)
|
||||||
|
{
|
||||||
|
long SizeKey(TextureRow row) => sizeSortColumn == 7 ? row.OriginalSize : row.CompressedSize;
|
||||||
|
|
||||||
|
orderedRows = sizeSortDirection == ImGuiSortDirection.Ascending
|
||||||
|
? indexedRows.OrderBy(pair => SizeKey(pair.row)).ThenBy(pair => pair.idx).Select(pair => pair.row)
|
||||||
|
: indexedRows.OrderByDescending(pair => SizeKey(pair.row)).ThenBy(pair => pair.idx).Select(pair => pair.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortSpecs.SpecsCount > 0)
|
||||||
|
{
|
||||||
sortSpecs.SpecsDirty = false;
|
sortSpecs.SpecsDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2259,6 +2301,79 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawTextureTableHeaderRow()
|
||||||
|
{
|
||||||
|
ImGui.TableNextRow(ImGuiTableRowFlags.Headers);
|
||||||
|
|
||||||
|
DrawHeaderCell(0, "##select");
|
||||||
|
DrawHeaderCell(1, "Texture");
|
||||||
|
DrawHeaderCell(2, "Slot");
|
||||||
|
DrawHeaderCell(3, "Map");
|
||||||
|
DrawFormatHeaderCell();
|
||||||
|
DrawHeaderCell(5, "Recommended");
|
||||||
|
DrawHeaderCell(6, "Target");
|
||||||
|
DrawHeaderCell(7, "Original");
|
||||||
|
DrawHeaderCell(8, "Compressed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawHeaderCell(int columnIndex, string label)
|
||||||
|
{
|
||||||
|
ImGui.TableSetColumnIndex(columnIndex);
|
||||||
|
ImGui.TableHeader(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawFormatHeaderCell()
|
||||||
|
{
|
||||||
|
ImGui.TableSetColumnIndex(4);
|
||||||
|
ImGui.TableHeader(GetFormatHeaderLabel());
|
||||||
|
|
||||||
|
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
|
||||||
|
{
|
||||||
|
CycleTextureFormatSortMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsItemHovered())
|
||||||
|
{
|
||||||
|
ImGui.SetTooltip("Click to cycle sort: normal, compressed first, uncompressed first.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFormatHeaderLabel()
|
||||||
|
=> _textureFormatSortMode switch
|
||||||
|
{
|
||||||
|
TextureFormatSortMode.CompressedFirst => "Format (C)##formatHeader",
|
||||||
|
TextureFormatSortMode.UncompressedFirst => "Format (U)##formatHeader",
|
||||||
|
_ => "Format##formatHeader"
|
||||||
|
};
|
||||||
|
|
||||||
|
private void SetTextureFormatSortMode(TextureFormatSortMode mode, bool persist = true)
|
||||||
|
{
|
||||||
|
if (_textureFormatSortMode == mode)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_textureFormatSortMode = mode;
|
||||||
|
if (persist)
|
||||||
|
{
|
||||||
|
_configService.Current.TextureFormatSortMode = mode;
|
||||||
|
_configService.Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CycleTextureFormatSortMode()
|
||||||
|
{
|
||||||
|
var nextMode = _textureFormatSortMode switch
|
||||||
|
{
|
||||||
|
TextureFormatSortMode.None => TextureFormatSortMode.CompressedFirst,
|
||||||
|
TextureFormatSortMode.CompressedFirst => TextureFormatSortMode.UncompressedFirst,
|
||||||
|
_ => TextureFormatSortMode.None
|
||||||
|
};
|
||||||
|
|
||||||
|
SetTextureFormatSortMode(nextMode);
|
||||||
|
}
|
||||||
|
|
||||||
private void StartTextureConversion()
|
private void StartTextureConversion()
|
||||||
{
|
{
|
||||||
if (_conversionTask != null && !_conversionTask.IsCompleted)
|
if (_conversionTask != null && !_conversionTask.IsCompleted)
|
||||||
|
|||||||
@@ -103,10 +103,19 @@ public sealed class DtrEntry : IDisposable, IHostedService
|
|||||||
|
|
||||||
public async Task StopAsync(CancellationToken cancellationToken)
|
public async Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
_cancellationTokenSource.Cancel();
|
||||||
|
|
||||||
|
if (_dalamudUtilService.IsOnFrameworkThread)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Skipping Lightfinder DTR wait on framework thread during shutdown.");
|
||||||
|
_cancellationTokenSource.Dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _runTask!.ConfigureAwait(false);
|
if (_runTask != null)
|
||||||
|
await _runTask.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
|
|||||||
8
LightlessSync/UI/Models/TextureFormatSortMode.cs
Normal file
8
LightlessSync/UI/Models/TextureFormatSortMode.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace LightlessSync.UI.Models;
|
||||||
|
|
||||||
|
public enum TextureFormatSortMode
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
CompressedFirst = 1,
|
||||||
|
UncompressedFirst = 2
|
||||||
|
}
|
||||||
@@ -3697,6 +3697,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow")));
|
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective VRAM usage information will not work.", UIColors.Get("LightlessYellow")));
|
||||||
|
|
||||||
|
var skipPreferredDownscale = textureConfig.SkipTextureDownscaleForPreferredPairs;
|
||||||
|
if (ImGui.Checkbox("Skip downscale for preferred/direct pairs", ref skipPreferredDownscale))
|
||||||
|
{
|
||||||
|
textureConfig.SkipTextureDownscaleForPreferredPairs = skipPreferredDownscale;
|
||||||
|
_playerPerformanceConfigService.Save();
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("When enabled, textures for direct pairs with preferred permissions are left untouched.");
|
||||||
|
|
||||||
if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale)
|
if (!textureConfig.EnableNonIndexTextureMipTrim && !textureConfig.EnableIndexTextureDownscale)
|
||||||
{
|
{
|
||||||
UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed"));
|
UiSharedService.ColorTextWrapped("Both trimming and downscale are disabled. Lightless will keep original textures regardless of size.", UIColors.Get("DimRed"));
|
||||||
@@ -3734,8 +3742,31 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
|
new SeStringUtils.RichTextEntry("destructive", UIColors.Get("DimRed"), true),
|
||||||
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
|
new SeStringUtils.RichTextEntry(" process and may cause broken or incorrect character appearances."));
|
||||||
|
|
||||||
|
|
||||||
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
|
_uiShared.DrawNoteLine("! ", UIColors.Get("DimRed"),
|
||||||
new SeStringUtils.RichTextEntry("this shit is placeholder still owo"));
|
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 decimation "),
|
||||||
|
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));
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(15));
|
||||||
|
|
||||||
|
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
|
||||||
|
new SeStringUtils.RichTextEntry("If a mesh exceeds the "),
|
||||||
|
new SeStringUtils.RichTextEntry("triangle threshold", UIColors.Get("LightlessGreen"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(", it will be decimated automatically to the set "),
|
||||||
|
new SeStringUtils.RichTextEntry("target triangle ratio", UIColors.Get("LightlessGreen"), true),
|
||||||
|
new SeStringUtils.RichTextEntry(". This will reduce quality of the mesh or may break it's intended structure."));
|
||||||
|
|
||||||
|
|
||||||
var performanceConfig = _playerPerformanceConfigService.Current;
|
var performanceConfig = _playerPerformanceConfigService.Current;
|
||||||
var enableDecimation = performanceConfig.EnableModelDecimation;
|
var enableDecimation = performanceConfig.EnableModelDecimation;
|
||||||
@@ -3756,11 +3787,19 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective triangle usage information will not work.", UIColors.Get("LightlessYellow")));
|
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"), new SeStringUtils.RichTextEntry("If disabled, saved + effective triangle usage information will not work.", UIColors.Get("LightlessYellow")));
|
||||||
|
|
||||||
|
var skipPreferredDecimation = performanceConfig.SkipModelDecimationForPreferredPairs;
|
||||||
|
if (ImGui.Checkbox("Skip decimation for preferred/direct pairs", ref skipPreferredDecimation))
|
||||||
|
{
|
||||||
|
performanceConfig.SkipModelDecimationForPreferredPairs = skipPreferredDecimation;
|
||||||
|
_playerPerformanceConfigService.Save();
|
||||||
|
}
|
||||||
|
_uiShared.DrawHelpText("When enabled, models for direct pairs with preferred permissions are left untouched.");
|
||||||
|
|
||||||
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
|
var triangleThreshold = performanceConfig.ModelDecimationTriangleThreshold;
|
||||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 10_000, 100_000))
|
if (ImGui.SliderInt("Decimate models above", ref triangleThreshold, 8_000, 100_000))
|
||||||
{
|
{
|
||||||
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 10_000, 100_000);
|
performanceConfig.ModelDecimationTriangleThreshold = Math.Clamp(triangleThreshold, 8_000, 100_000);
|
||||||
_playerPerformanceConfigService.Save();
|
_playerPerformanceConfigService.Save();
|
||||||
}
|
}
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
@@ -3768,7 +3807,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_uiShared.DrawHelpText($"Models below this triangle count are left untouched.{UiSharedService.TooltipSeparator}Default: 50,000");
|
_uiShared.DrawHelpText($"Models below this triangle count are left untouched.{UiSharedService.TooltipSeparator}Default: 50,000");
|
||||||
|
|
||||||
var targetPercent = (float)(performanceConfig.ModelDecimationTargetRatio * 100.0);
|
var targetPercent = (float)(performanceConfig.ModelDecimationTargetRatio * 100.0);
|
||||||
var clampedPercent = Math.Clamp(targetPercent, 70f, 99f);
|
var clampedPercent = Math.Clamp(targetPercent, 60f, 99f);
|
||||||
if (Math.Abs(clampedPercent - targetPercent) > float.Epsilon)
|
if (Math.Abs(clampedPercent - targetPercent) > float.Epsilon)
|
||||||
{
|
{
|
||||||
performanceConfig.ModelDecimationTargetRatio = clampedPercent / 100.0;
|
performanceConfig.ModelDecimationTargetRatio = clampedPercent / 100.0;
|
||||||
@@ -3776,17 +3815,30 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
targetPercent = clampedPercent;
|
targetPercent = clampedPercent;
|
||||||
}
|
}
|
||||||
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
|
||||||
if (ImGui.SliderFloat("Target triangle ratio", ref targetPercent, 70f, 99f, "%.0f%%"))
|
if (ImGui.SliderFloat("Target triangle ratio", ref targetPercent, 60f, 99f, "%.0f%%"))
|
||||||
{
|
{
|
||||||
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.7f, 0.99f);
|
performanceConfig.ModelDecimationTargetRatio = Math.Clamp(targetPercent / 100f, 0.6f, 0.99f);
|
||||||
_playerPerformanceConfigService.Save();
|
_playerPerformanceConfigService.Save();
|
||||||
}
|
}
|
||||||
_uiShared.DrawHelpText($"Target ratio relative to original triangle count (70% keeps 70% of triangles).{UiSharedService.TooltipSeparator}Default: 70%");
|
_uiShared.DrawHelpText($"Target ratio relative to original triangle count (80% keeps 80% of triangles).{UiSharedService.TooltipSeparator}Default: 80%");
|
||||||
|
|
||||||
ImGui.Dummy(new Vector2(5));
|
ImGui.Dummy(new Vector2(15));
|
||||||
ImGui.TextUnformatted("Decimation targets");
|
ImGui.TextUnformatted("Decimation targets");
|
||||||
_uiShared.DrawHelpText("Hair mods are always excluded from decimation.");
|
_uiShared.DrawHelpText("Hair mods are always excluded from decimation.");
|
||||||
|
|
||||||
|
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessGreen"),
|
||||||
|
new SeStringUtils.RichTextEntry("Automatic decimation will only target the selected "),
|
||||||
|
new SeStringUtils.RichTextEntry("decimation targets", UIColors.Get("LightlessGreen"), true),
|
||||||
|
new SeStringUtils.RichTextEntry("."));
|
||||||
|
|
||||||
|
_uiShared.DrawNoteLine("! ", UIColors.Get("LightlessYellow"),
|
||||||
|
new SeStringUtils.RichTextEntry("It is advised to not decimate any body related meshes which includes: "),
|
||||||
|
new SeStringUtils.RichTextEntry("facial mods + sculpts, chest, legs, hands and feet", UIColors.Get("LightlessYellow"), true),
|
||||||
|
new SeStringUtils.RichTextEntry("."));
|
||||||
|
|
||||||
|
_uiShared.DrawNoteLine("!!! ", UIColors.Get("DimRed"),
|
||||||
|
new SeStringUtils.RichTextEntry("Remember, automatic decimation is not perfect and can cause meshes to be ruined, especially hair mods.", UIColors.Get("DimRed"), true));
|
||||||
|
|
||||||
var allowBody = performanceConfig.ModelDecimationAllowBody;
|
var allowBody = performanceConfig.ModelDecimationAllowBody;
|
||||||
if (ImGui.Checkbox("Body", ref allowBody))
|
if (ImGui.Checkbox("Body", ref allowBody))
|
||||||
{
|
{
|
||||||
@@ -3822,6 +3874,10 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
|||||||
_playerPerformanceConfigService.Save();
|
_playerPerformanceConfigService.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|
||||||
|
UiSharedService.ColoredSeparator(UIColors.Get("LightlessGrey"), 3f);
|
||||||
|
|
||||||
ImGui.Dummy(new Vector2(5));
|
ImGui.Dummy(new Vector2(5));
|
||||||
DrawTriangleDecimationCounters();
|
DrawTriangleDecimationCounters();
|
||||||
ImGui.Dummy(new Vector2(5));
|
ImGui.Dummy(new Vector2(5));
|
||||||
|
|||||||
@@ -205,10 +205,8 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
|
|
||||||
private void ApplyUiVisibilitySettings()
|
private void ApplyUiVisibilitySettings()
|
||||||
{
|
{
|
||||||
var config = _chatConfigService.Current;
|
|
||||||
_uiBuilder.DisableUserUiHide = true;
|
_uiBuilder.DisableUserUiHide = true;
|
||||||
_uiBuilder.DisableCutsceneUiHide = config.ShowInCutscenes;
|
_uiBuilder.DisableCutsceneUiHide = true;
|
||||||
_uiBuilder.DisableGposeUiHide = config.ShowInGpose;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldHide()
|
private bool ShouldHide()
|
||||||
@@ -220,6 +218,16 @@ public sealed class ZoneChatUi : WindowMediatorSubscriberBase
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!config.ShowInGpose && _dalamudUtilService.IsInGpose)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.ShowInCutscenes && _dalamudUtilService.IsInCutscene)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (config.HideInCombat && _dalamudUtilService.IsInCombat)
|
if (config.HideInCombat && _dalamudUtilService.IsInCombat)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -93,12 +93,12 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
CurrentOwnerToken = null;
|
CurrentOwnerToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DownloadFiles(GameObjectHandler? gameObject, List<FileReplacementData> fileReplacementDto, CancellationToken ct, bool skipDownscale = false)
|
public async Task DownloadFiles(GameObjectHandler? gameObject, List<FileReplacementData> fileReplacementDto, CancellationToken ct, bool skipDownscale = false, bool skipDecimation = false)
|
||||||
{
|
{
|
||||||
Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles)));
|
Mediator.Publish(new HaltScanMessage(nameof(DownloadFiles)));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DownloadFilesInternal(gameObject, fileReplacementDto, ct, skipDownscale).ConfigureAwait(false);
|
await DownloadFilesInternal(gameObject, fileReplacementDto, ct, skipDownscale, skipDecimation).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -458,7 +458,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
IReadOnlyDictionary<string, long> rawSizeLookup,
|
IReadOnlyDictionary<string, long> rawSizeLookup,
|
||||||
string downloadLabel,
|
string downloadLabel,
|
||||||
CancellationToken ct,
|
CancellationToken ct,
|
||||||
bool skipDownscale)
|
bool skipDownscale,
|
||||||
|
bool skipDecimation)
|
||||||
{
|
{
|
||||||
SetStatus(downloadStatusKey, DownloadStatus.Decompressing);
|
SetStatus(downloadStatusKey, DownloadStatus.Decompressing);
|
||||||
MarkTransferredFiles(downloadStatusKey, 1);
|
MarkTransferredFiles(downloadStatusKey, 1);
|
||||||
@@ -617,7 +618,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
yield return items.GetRange(i, Math.Min(chunkSize, items.Count - i));
|
yield return items.GetRange(i, Math.Min(chunkSize, items.Count - i));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DownloadFilesInternal(GameObjectHandler? gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct, bool skipDownscale)
|
private async Task DownloadFilesInternal(GameObjectHandler? gameObjectHandler, List<FileReplacementData> fileReplacement, CancellationToken ct, bool skipDownscale, bool skipDecimation)
|
||||||
{
|
{
|
||||||
var objectName = gameObjectHandler?.Name ?? "Unknown";
|
var objectName = gameObjectHandler?.Name ?? "Unknown";
|
||||||
|
|
||||||
@@ -729,13 +730,13 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
Task batchTask = batchChunks.Length == 0
|
Task batchTask = batchChunks.Length == 0
|
||||||
? Task.CompletedTask
|
? Task.CompletedTask
|
||||||
: Parallel.ForEachAsync(batchChunks, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct },
|
: Parallel.ForEachAsync(batchChunks, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct },
|
||||||
async (chunk, token) => await ProcessBatchChunkAsync(chunk, replacementLookup, rawSizeLookup, token, skipDownscale).ConfigureAwait(false));
|
async (chunk, token) => await ProcessBatchChunkAsync(chunk, replacementLookup, rawSizeLookup, token, skipDownscale, skipDecimation).ConfigureAwait(false));
|
||||||
|
|
||||||
// direct downloads
|
// direct downloads
|
||||||
Task directTask = directDownloads.Count == 0
|
Task directTask = directDownloads.Count == 0
|
||||||
? Task.CompletedTask
|
? Task.CompletedTask
|
||||||
: Parallel.ForEachAsync(directDownloads, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct },
|
: Parallel.ForEachAsync(directDownloads, new ParallelOptions { MaxDegreeOfParallelism = workerDop, CancellationToken = ct },
|
||||||
async (d, token) => await ProcessDirectAsync(d, replacementLookup, rawSizeLookup, token, skipDownscale).ConfigureAwait(false));
|
async (d, token) => await ProcessDirectAsync(d, replacementLookup, rawSizeLookup, token, skipDownscale, skipDecimation).ConfigureAwait(false));
|
||||||
|
|
||||||
await Task.WhenAll(batchTask, directTask).ConfigureAwait(false);
|
await Task.WhenAll(batchTask, directTask).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -751,7 +752,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
Dictionary<string, (string Extension, string GamePath)> replacementLookup,
|
Dictionary<string, (string Extension, string GamePath)> replacementLookup,
|
||||||
IReadOnlyDictionary<string, long> rawSizeLookup,
|
IReadOnlyDictionary<string, long> rawSizeLookup,
|
||||||
CancellationToken ct,
|
CancellationToken ct,
|
||||||
bool skipDownscale)
|
bool skipDownscale,
|
||||||
|
bool skipDecimation)
|
||||||
{
|
{
|
||||||
var statusKey = chunk.StatusKey;
|
var statusKey = chunk.StatusKey;
|
||||||
|
|
||||||
@@ -784,7 +786,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, fi.Name, ct, skipDownscale).ConfigureAwait(false);
|
await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, fi.Name, ct, skipDownscale, skipDecimation).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
@@ -806,7 +808,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
Dictionary<string, (string Extension, string GamePath)> replacementLookup,
|
Dictionary<string, (string Extension, string GamePath)> replacementLookup,
|
||||||
IReadOnlyDictionary<string, long> rawSizeLookup,
|
IReadOnlyDictionary<string, long> rawSizeLookup,
|
||||||
CancellationToken ct,
|
CancellationToken ct,
|
||||||
bool skipDownscale)
|
bool skipDownscale,
|
||||||
|
bool skipDecimation)
|
||||||
{
|
{
|
||||||
var progress = CreateInlineProgress(bytes =>
|
var progress = CreateInlineProgress(bytes =>
|
||||||
{
|
{
|
||||||
@@ -816,7 +819,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
if (!ShouldUseDirectDownloads() || string.IsNullOrEmpty(directDownload.DirectDownloadUrl))
|
if (!ShouldUseDirectDownloads() || string.IsNullOrEmpty(directDownload.DirectDownloadUrl))
|
||||||
{
|
{
|
||||||
await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale).ConfigureAwait(false);
|
await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale, skipDecimation).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,7 +867,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _fileCompactor.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false);
|
await _fileCompactor.WriteAllBytesAsync(finalFilename, decompressedBytes, ct).ConfigureAwait(false);
|
||||||
PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale);
|
PersistFileToStorage(directDownload.Hash, finalFilename, repl.GamePath, skipDownscale, skipDecimation);
|
||||||
|
|
||||||
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
||||||
SetStatus(directDownload.DirectDownloadUrl!, DownloadStatus.Completed);
|
SetStatus(directDownload.DirectDownloadUrl!, DownloadStatus.Completed);
|
||||||
@@ -891,7 +894,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale).ConfigureAwait(false);
|
await ProcessDirectAsQueuedFallbackAsync(directDownload, replacementLookup, rawSizeLookup, progress, ct, skipDownscale, skipDecimation).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!expectedDirectDownloadFailure && failureCount >= 3 && !_disableDirectDownloads)
|
if (!expectedDirectDownloadFailure && failureCount >= 3 && !_disableDirectDownloads)
|
||||||
{
|
{
|
||||||
@@ -921,7 +924,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
IReadOnlyDictionary<string, long> rawSizeLookup,
|
IReadOnlyDictionary<string, long> rawSizeLookup,
|
||||||
IProgress<long> progress,
|
IProgress<long> progress,
|
||||||
CancellationToken ct,
|
CancellationToken ct,
|
||||||
bool skipDownscale)
|
bool skipDownscale,
|
||||||
|
bool skipDecimation)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(directDownload.DirectDownloadUrl))
|
if (string.IsNullOrEmpty(directDownload.DirectDownloadUrl))
|
||||||
throw new InvalidOperationException("Direct download fallback requested without a direct download URL.");
|
throw new InvalidOperationException("Direct download fallback requested without a direct download URL.");
|
||||||
@@ -946,7 +950,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
if (!File.Exists(blockFile))
|
if (!File.Exists(blockFile))
|
||||||
throw new FileNotFoundException("Block file missing after direct download fallback.", blockFile);
|
throw new FileNotFoundException("Block file missing after direct download fallback.", blockFile);
|
||||||
|
|
||||||
await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, $"fallback-{directDownload.Hash}", ct, skipDownscale)
|
await DecompressBlockFileAsync(statusKey, blockFile, replacementLookup, rawSizeLookup, $"fallback-{directDownload.Hash}", ct, skipDownscale, skipDecimation)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -973,7 +977,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, string gamePath, bool skipDownscale)
|
private void PersistFileToStorage(string fileHash, string filePath, string gamePath, bool skipDownscale, bool skipDecimation)
|
||||||
{
|
{
|
||||||
var fi = new FileInfo(filePath);
|
var fi = new FileInfo(filePath);
|
||||||
|
|
||||||
@@ -1005,7 +1009,7 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
|||||||
() => _textureMetadataHelper.DetermineMapKind(gamePath, filePath));
|
() => _textureMetadataHelper.DetermineMapKind(gamePath, filePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipDownscale && _modelDecimationService.ShouldScheduleDecimation(fileHash, filePath, gamePath))
|
if (!skipDecimation && _modelDecimationService.ShouldScheduleDecimation(fileHash, filePath, gamePath))
|
||||||
{
|
{
|
||||||
_modelDecimationService.ScheduleDecimation(fileHash, filePath, gamePath);
|
_modelDecimationService.ScheduleDecimation(fileHash, filePath, gamePath);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user