diff --git a/OtterGui b/OtterGui index cf42043c..5a2e12a1 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit cf42043c2b0e76b59919688dc250a762fe52d4b1 +Subproject commit 5a2e12a1acd6760a3a592447a92215135e79197c diff --git a/Penumbra.GameData b/Penumbra.GameData index c0c7eb0d..c39f683d 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit c0c7eb0dedb32ea83b019626abba041e90a95319 +Subproject commit c39f683d65d4541e9f97ed4ea1abcb10e8ca5690 diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index aed1a963..f5bb67bd 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -10,6 +10,7 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Compression; +using OtterGui.Log; using Penumbra.Api.Enums; using Penumbra.GameData.Actors; using Penumbra.Interop.ResourceLoading; @@ -642,10 +643,10 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) - return PenumbraApiEc.ModMissing; + return Return(PenumbraApiEc.ModMissing, Args("ModDirectory", modDirectory, "ModName", modName)); _modManager.ReloadMod(mod); - return PenumbraApiEc.Success; + return Return(PenumbraApiEc.Success, Args("ModDirectory", modDirectory, "ModName", modName)); } public PenumbraApiEc InstallMod(string modFilePackagePath) @@ -653,11 +654,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (File.Exists(modFilePackagePath)) { _modImportManager.AddUnpack(modFilePackagePath); - return PenumbraApiEc.Success; + return Return(PenumbraApiEc.Success, Args("ModFilePackagePath", modFilePackagePath)); } else { - return PenumbraApiEc.FileMissing; + return Return(PenumbraApiEc.FileMissing, Args("ModFilePackagePath", modFilePackagePath)); } } @@ -666,23 +667,24 @@ public class PenumbraApi : IDisposable, IPenumbraApi CheckInitialized(); var dir = new DirectoryInfo(Path.Join(_modManager.BasePath.FullName, Path.GetFileName(modDirectory))); if (!dir.Exists) - return PenumbraApiEc.FileMissing; + return Return(PenumbraApiEc.FileMissing, Args("ModDirectory", modDirectory)); + _modManager.AddMod(dir); if (_config.UseFileSystemCompression) new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories), CompressionAlgorithm.Xpress8K); - return PenumbraApiEc.Success; + return Return(PenumbraApiEc.Success, Args("ModDirectory", modDirectory)); } public PenumbraApiEc DeleteMod(string modDirectory, string modName) { CheckInitialized(); if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) - return PenumbraApiEc.NothingChanged; + return Return(PenumbraApiEc.NothingChanged, Args("ModDirectory", modDirectory, "ModName", modName)); _modManager.DeleteMod(mod); - return PenumbraApiEc.Success; + return Return(PenumbraApiEc.Success, Args("ModDirectory", modDirectory, "ModName", modName)); } public event Action? ModDeleted; @@ -784,22 +786,33 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if (!_collectionManager.Storage.ByName(collectionName, out var collection)) - return PenumbraApiEc.CollectionMissing; + return Return(PenumbraApiEc.CollectionMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "OptionName", optionName)); if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) - return PenumbraApiEc.ModMissing; + return Return(PenumbraApiEc.ModMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "OptionName", optionName)); var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName); if (groupIdx < 0) - return PenumbraApiEc.OptionGroupMissing; + return Return(PenumbraApiEc.OptionGroupMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "OptionName", optionName)); var optionIdx = mod.Groups[groupIdx].IndexOf(o => o.Name == optionName); if (optionIdx < 0) - return PenumbraApiEc.OptionMissing; + return Return(PenumbraApiEc.OptionMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "OptionName", optionName)); var setting = mod.Groups[groupIdx].Type == GroupType.Multi ? 1u << optionIdx : (uint)optionIdx; - return _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return Return( + _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "OptionName", optionName)); } public PenumbraApiEc TrySetModSettings(string collectionName, string modDirectory, string modName, string optionGroupName, @@ -807,14 +820,20 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if (!_collectionManager.Storage.ByName(collectionName, out var collection)) - return PenumbraApiEc.CollectionMissing; + return Return(PenumbraApiEc.CollectionMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "#optionNames", optionNames.Count.ToString())); if (!_modManager.TryGetMod(modDirectory, modName, out var mod)) - return PenumbraApiEc.ModMissing; + return Return(PenumbraApiEc.ModMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "#optionNames", optionNames.Count.ToString())); var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName); if (groupIdx < 0) - return PenumbraApiEc.OptionGroupMissing; + return Return(PenumbraApiEc.OptionGroupMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "#optionNames", optionNames.Count.ToString())); var group = mod.Groups[groupIdx]; @@ -823,7 +842,9 @@ public class PenumbraApi : IDisposable, IPenumbraApi { var optionIdx = optionNames.Count == 0 ? -1 : group.IndexOf(o => o.Name == optionNames[^1]); if (optionIdx < 0) - return PenumbraApiEc.OptionMissing; + return Return(PenumbraApiEc.OptionMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "#optionNames", optionNames.Count.ToString())); setting = (uint)optionIdx; } @@ -833,13 +854,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi { var optionIdx = group.IndexOf(o => o.Name == name); if (optionIdx < 0) - return PenumbraApiEc.OptionMissing; + return Return(PenumbraApiEc.OptionMissing, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", + optionGroupName, "#optionNames", optionNames.Count.ToString())); setting |= 1u << optionIdx; } } - return _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged; + return Return( + _collectionEditor.SetModSetting(collection, mod, groupIdx, setting) ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, + Args("CollectionName", collectionName, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName", optionGroupName, + "#optionNames", optionNames.Count.ToString())); } @@ -1296,4 +1322,33 @@ public class PenumbraApi : IDisposable, IPenumbraApi if (settings is { Enabled: true }) ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Name, mod.Identifier, parent != collection); } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static LazyString Args(params string[] arguments) + { + if (arguments.Length == 0) + return new LazyString(() => "no arguments"); + + return new LazyString(() => + { + var sb = new StringBuilder(); + for (var i = 0; i < arguments.Length / 2; ++i) + { + sb.Append(arguments[2 * i]); + sb.Append(" = "); + sb.Append(arguments[2 * i + 1]); + sb.Append(", "); + } + + return sb.ToString(0, sb.Length - 2); + }); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static PenumbraApiEc Return(PenumbraApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown") + { + Penumbra.Log.Debug( + $"[{name}] Called with {args}, returned {ec}."); + return ec; + } } diff --git a/Penumbra/Collections/Manager/CollectionStorage.cs b/Penumbra/Collections/Manager/CollectionStorage.cs index a84c79e6..0ee55376 100644 --- a/Penumbra/Collections/Manager/CollectionStorage.cs +++ b/Penumbra/Collections/Manager/CollectionStorage.cs @@ -265,6 +265,14 @@ public class CollectionStorage : IReadOnlyList, IDisposable foreach (var collection in this.Where(collection => collection.Settings[mod.Index] != null)) _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); break; + case ModPathChangeType.Reloaded: + foreach (var collection in this) + { + if (collection.Settings[mod.Index]?.FixAllSettings(mod) ?? false) + _saveService.QueueSave(new ModCollectionSave(_modStorage, collection)); + } + + break; } } diff --git a/Penumbra/Collections/Manager/IndividualCollections.Files.cs b/Penumbra/Collections/Manager/IndividualCollections.Files.cs index dc20da1e..21a8cf8a 100644 --- a/Penumbra/Collections/Manager/IndividualCollections.Files.cs +++ b/Penumbra/Collections/Manager/IndividualCollections.Files.cs @@ -41,7 +41,7 @@ public partial class IndividualCollections saver.ImmediateSave(parent); IsLoaded = true; Loaded.Invoke(); - }); + }, TaskScheduler.Default); return false; } diff --git a/Penumbra/Import/Models/ModelManager.cs b/Penumbra/Import/Models/ModelManager.cs index 2c341c8b..1a52c4dd 100644 --- a/Penumbra/Import/Models/ModelManager.cs +++ b/Penumbra/Import/Models/ModelManager.cs @@ -162,7 +162,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect { return _tasks.TryRemove(a, out var unused); } - }, CancellationToken.None); + }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); return (t, token); }).Item1; } @@ -178,7 +178,7 @@ public sealed class ModelManager(IFramework framework, ActiveCollections collect throw task.Exception; return process(action); - }); + }, TaskScheduler.Default); private class ExportToGltfAction( ModelManager manager, diff --git a/Penumbra/Import/TexToolsImport.cs b/Penumbra/Import/TexToolsImport.cs index 3f3304b8..bb006d8d 100644 --- a/Penumbra/Import/TexToolsImport.cs +++ b/Penumbra/Import/TexToolsImport.cs @@ -46,12 +46,12 @@ public partial class TexToolsImporter : IDisposable ExtractedMods = new List<(FileInfo, DirectoryInfo?, Exception?)>(count); _token = _cancellation.Token; Task.Run(ImportFiles, _token) - .ContinueWith(_ => CloseStreams()) + .ContinueWith(_ => CloseStreams(), TaskScheduler.Default) .ContinueWith(_ => { foreach (var (file, dir, error) in ExtractedMods) handler(file, dir, error); - }); + }, TaskScheduler.Default); } private void CloseStreams() diff --git a/Penumbra/Import/Textures/TextureManager.cs b/Penumbra/Import/Textures/TextureManager.cs index 5653d760..976bc179 100644 --- a/Penumbra/Import/Textures/TextureManager.cs +++ b/Penumbra/Import/Textures/TextureManager.cs @@ -64,7 +64,7 @@ public sealed class TextureManager : SingleTaskQueue, IDisposable { var token = new CancellationTokenSource(); var task = Enqueue(a, token.Token); - task.ContinueWith(_ => _tasks.TryRemove(a, out var unused), CancellationToken.None); + task.ContinueWith(_ => _tasks.TryRemove(a, out var unused), CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); return (task, token); }).Item1; } diff --git a/Penumbra/Mods/Manager/ModCacheManager.cs b/Penumbra/Mods/Manager/ModCacheManager.cs index a95567ce..99ad1a4f 100644 --- a/Penumbra/Mods/Manager/ModCacheManager.cs +++ b/Penumbra/Mods/Manager/ModCacheManager.cs @@ -23,7 +23,7 @@ public class ModCacheManager : IDisposable _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModCacheManager); _communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModCacheManager); _communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.ModCacheManager); - identifier.Awaiter.ContinueWith(_ => OnIdentifierCreation()); + identifier.Awaiter.ContinueWith(_ => OnIdentifierCreation(), TaskScheduler.Default); OnModDiscoveryFinished(); } diff --git a/Penumbra/Mods/Subclasses/ModSettings.cs b/Penumbra/Mods/Subclasses/ModSettings.cs index a20cb9cb..ed8ad84e 100644 --- a/Penumbra/Mods/Subclasses/ModSettings.cs +++ b/Penumbra/Mods/Subclasses/ModSettings.cs @@ -138,6 +138,22 @@ public class ModSettings } } + public bool FixAllSettings(Mod mod) + { + var ret = false; + for (var i = 0; i < Settings.Count; ++i) + { + var newValue = FixSetting(mod.Groups[i], Settings[i]); + if (newValue != Settings[i]) + { + ret = true; + Settings[i] = newValue; + } + } + + return AddMissingSettings(mod) || ret; + } + // Ensure that a value is valid for a group. private static uint FixSetting(IModGroup group, uint value) => group.Type switch diff --git a/Penumbra/Services/ServiceWrapper.cs b/Penumbra/Services/ServiceWrapper.cs index 37acdfd0..e321b35c 100644 --- a/Penumbra/Services/ServiceWrapper.cs +++ b/Penumbra/Services/ServiceWrapper.cs @@ -74,7 +74,7 @@ public abstract class AsyncServiceWrapper : IDisposable { if (!_isDisposed) FinishedCreation?.Invoke(); - }, null); + }, TaskScheduler.Default); } public void Dispose() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs index 637c8401..cca8fe10 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Models.MdlTab.cs @@ -92,7 +92,7 @@ public partial class ModEditWindow .ToList(); }); - task.ContinueWith(t => { GamePaths = FinalizeIo(t); }); + task.ContinueWith(t => { GamePaths = FinalizeIo(t); }, TaskScheduler.Default); } private EstManipulation[] GetCurrentEstManipulations() @@ -130,7 +130,7 @@ public partial class ModEditWindow BeginIo(); _edit._models.ExportToGltf(ExportConfig, Mdl, sklbPaths, ReadFile, outputPath) - .ContinueWith(FinalizeIo); + .ContinueWith(FinalizeIo, TaskScheduler.Default); } /// Import a model from an interchange format. @@ -144,7 +144,7 @@ public partial class ModEditWindow var mdlFile = FinalizeIo(task, result => result.Item1, result => result.Item2); if (mdlFile != null) FinalizeImport(mdlFile); - }); + }, TaskScheduler.Default); } /// Finalise the import of a .mdl, applying any post-import transformations and state updates. diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs index 71c64059..652ecb49 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Textures.cs @@ -255,7 +255,7 @@ public partial class ModEditWindow return; _framework.RunOnFrameworkThread(() => tex.Reload(_textures)); - }); + }, TaskScheduler.Default); } private Vector2 GetChildWidth() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index afa846b5..72dd91d3 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -326,7 +326,7 @@ public partial class ModEditWindow : Window, IDisposable else if (ImGuiUtil.DrawDisabledButton("Re-Duplicate and Normalize Mod", Vector2.Zero, tt, !_allowReduplicate && !modifier)) { _editor.ModNormalizer.Normalize(Mod!); - _editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.OptionIdx)); + _editor.ModNormalizer.Worker.ContinueWith(_ => _editor.LoadMod(Mod!, _editor.GroupIdx, _editor.OptionIdx), TaskScheduler.Default); } if (!_editor.Duplicates.Worker.IsCompleted) diff --git a/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs b/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs index a0e35cff..fd8f9b25 100644 --- a/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs +++ b/Penumbra/UI/CollectionTab/IndividualAssignmentUi.cs @@ -33,7 +33,7 @@ public class IndividualAssignmentUi : IDisposable _actors = actors; _collectionManager = collectionManager; _communicator.CollectionChange.Subscribe(UpdateIdentifiers, CollectionChange.Priority.IndividualAssignmentUi); - _actors.Awaiter.ContinueWith(_ => SetupCombos()); + _actors.Awaiter.ContinueWith(_ => SetupCombos(), TaskScheduler.Default); } public string PlayerTooltip { get; private set; } = NewPlayerTooltipEmpty; diff --git a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs index 195c07d6..5c7ddbf3 100644 --- a/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs +++ b/Penumbra/UI/ModsTab/ModPanelSettingsTab.cs @@ -211,6 +211,11 @@ public class ModPanelSettingsTab : ITab var selectedOption = _empty ? (int)group.DefaultSettings : (int)_settings.Settings[groupIdx]; var minWidth = Widget.BeginFramedGroup(group.Name, description:group.Description); + DrawCollapseHandling(group, minWidth, DrawOptions); + + Widget.EndFramedGroup(); + return; + void DrawOptions() { for (var idx = 0; idx < group.Count; ++idx) @@ -227,10 +232,6 @@ public class ModPanelSettingsTab : ITab ImGuiComponents.HelpMarker(option.Description); } } - - DrawCollapseHandling(group, minWidth, DrawOptions); - - Widget.EndFramedGroup(); }