Use explicit priorities for all internal communication events.

This commit is contained in:
Ottermandias 2023-05-02 17:46:13 +02:00
parent f46daf0f54
commit fb84b43d69
36 changed files with 681 additions and 384 deletions

View file

@ -22,6 +22,7 @@ using Penumbra.String;
using Penumbra.String.Classes;
using Penumbra.Services;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Interop.Services;
using Penumbra.UI;
@ -34,13 +35,13 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public event Action<string>? PreSettingsPanelDraw
{
add => _communicator.PreSettingsPanelDraw.Subscribe(value!);
add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default);
remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!);
}
public event Action<string>? PostSettingsPanelDraw
{
add => _communicator.PostSettingsPanelDraw.Subscribe(value!);
add => _communicator.PostSettingsPanelDraw.Subscribe(value!, Communication.PostSettingsPanelDraw.Priority.Default);
remove => _communicator.PostSettingsPanelDraw.Unsubscribe(value!);
}
@ -68,7 +69,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return;
CheckInitialized();
_communicator.CreatingCharacterBase.Subscribe(new Action<nint, string, nint, nint, nint>(value));
_communicator.CreatingCharacterBase.Subscribe(new Action<nint, string, nint, nint, nint>(value), Communication.CreatingCharacterBase.Priority.Api);
}
remove
{
@ -88,7 +89,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return;
CheckInitialized();
_communicator.CreatedCharacterBase.Subscribe(new Action<nint, string, nint>(value));
_communicator.CreatedCharacterBase.Subscribe(new Action<nint, string, nint>(value), Communication.CreatedCharacterBase.Priority.Api);
}
remove
{
@ -147,8 +148,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
_lumina = _dalamud.GameData.GameData;
_resourceLoader.ResourceLoaded += OnResourceLoaded;
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, -1000);
_communicator.ModPathChanged.Subscribe(ModPathChangeSubscriber, ModPathChanged.Priority.Api);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
}
public unsafe void Dispose()
@ -180,7 +181,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public event ChangedItemClick? ChangedItemClicked
{
add => _communicator.ChangedItemClick.Subscribe(new Action<MouseButton, object?>(value!));
add => _communicator.ChangedItemClick.Subscribe(new Action<MouseButton, object?>(value!), Communication.ChangedItemClick.Priority.Default);
remove => _communicator.ChangedItemClick.Unsubscribe(new Action<MouseButton, object?>(value!));
}
@ -203,7 +204,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
add
{
CheckInitialized();
_communicator.ModDirectoryChanged.Subscribe(value!);
_communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
}
remove
{
@ -220,7 +221,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
add
{
CheckInitialized();
_communicator.EnabledChanged.Subscribe(value!, int.MinValue);
_communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
}
remove
{
@ -237,7 +238,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public event ChangedItemHover? ChangedItemTooltip
{
add => _communicator.ChangedItemHover.Subscribe(new Action<object?>(value!));
add => _communicator.ChangedItemHover.Subscribe(new Action<object?>(value!), Communication.ChangedItemHover.Priority.Default);
remove => _communicator.ChangedItemHover.Unsubscribe(new Action<object?>(value!));
}

View file

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Penumbra.Services;
using Penumbra.String.Classes;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
namespace Penumbra.Api;
@ -27,7 +28,7 @@ public class TempModManager : IDisposable
public TempModManager(CommunicatorService communicator)
{
_communicator = communicator;
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.TempModManager);
}
public void Dispose()

View file

@ -8,6 +8,7 @@ using OtterGui.Classes;
using Penumbra.Api;
using Penumbra.Api.Enums;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Meta;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
@ -44,15 +45,15 @@ public class CollectionCacheManager : IDisposable
if (!_active.Individuals.IsLoaded)
_active.Individuals.Loaded += CreateNecessaryCaches;
_communicator.CollectionChange.Subscribe(OnCollectionChange, -100);
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, -100);
_communicator.ModPathChanged.Subscribe(OnModChangeRemoval, 100);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, -100);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange);
_communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange);
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, -100);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionCacheManager);
_communicator.ModPathChanged.Subscribe(OnModChangeAddition, ModPathChanged.Priority.CollectionCacheManagerAddition);
_communicator.ModPathChanged.Subscribe(OnModChangeRemoval, ModPathChanged.Priority.CollectionCacheManagerRemoval);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange, TemporaryGlobalModChange.Priority.CollectionCacheManager);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionCacheManager);
_communicator.ModSettingChanged.Subscribe(OnModSettingChange, ModSettingChanged.Priority.CollectionCacheManager);
_communicator.CollectionInheritanceChanged.Subscribe(OnCollectionInheritanceChange, CollectionInheritanceChanged.Priority.CollectionCacheManager);
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted, ModDiscoveryStarted.Priority.CollectionCacheManager);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionCacheManager);
if (!MetaFileManager.CharacterUtility.Ready)
MetaFileManager.CharacterUtility.LoadingFinished += IncrementCounters;

View file

@ -6,6 +6,7 @@ using Dalamud.Interface.Internal.Notifications;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.Communication;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.Services;
@ -44,7 +45,7 @@ public class ActiveCollections : ISavable, IDisposable
Default = storage.DefaultNamed;
Interface = storage.DefaultNamed;
Individuals = new IndividualCollections(actors, config, false);
_communicator.CollectionChange.Subscribe(OnCollectionChange, -100);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ActiveCollections);
LoadCollections();
UpdateCurrentCollectionInUse();
Individuals.Loaded += UpdateCurrentCollectionInUse;

View file

@ -7,6 +7,7 @@ using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
@ -56,10 +57,10 @@ public class CollectionStorage : IReadOnlyList<ModCollection>, IDisposable
_communicator = communicator;
_saveService = saveService;
_modStorage = modStorage;
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished);
_communicator.ModPathChanged.Subscribe(OnModPathChange, 10);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, 100);
_communicator.ModDiscoveryStarted.Subscribe(OnModDiscoveryStarted, ModDiscoveryStarted.Priority.CollectionStorage);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.CollectionStorage);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.CollectionStorage);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.CollectionStorage);
ReadCollections(out DefaultNamed);
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
@ -47,7 +48,7 @@ public class InheritanceManager : IDisposable
_modStorage = modStorage;
ApplyInheritances();
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.InheritanceManager);
}
public void Dispose()

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Penumbra.Api;
using Penumbra.Communication;
using Penumbra.GameData.Actors;
using Penumbra.Mods;
using Penumbra.Services;
@ -27,7 +28,7 @@ public class TempCollectionManager : IDisposable
_storage = storage;
Collections = new IndividualCollections(actors, config, true);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange);
_communicator.TemporaryGlobalModChange.Subscribe(OnGlobalModChange, TemporaryGlobalModChange.Priority.TempCollectionManager);
}
public void Dispose()

View file

@ -0,0 +1,28 @@
using System;
using Penumbra.Api.Enums;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered when a Changed Item in Penumbra is clicked.
/// <list type="number">
/// <item>Parameter is the clicked mouse button. </item>
/// <item>Parameter is the clicked object data if any.. </item>
/// </list>
/// </summary>
public sealed class ChangedItemClick : EventWrapper<Action<MouseButton, object?>, ChangedItemClick.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.ChangedItemClicked"/>
Default = 0,
}
public ChangedItemClick()
: base(nameof(ChangedItemClick))
{ }
public void Invoke(MouseButton button, object? data)
=> Invoke(this, button, data);
}

View file

@ -0,0 +1,29 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered when a Changed Item in Penumbra is hovered.
/// <list type="number">
/// <item>Parameter is the hovered object data if any. </item>
/// </list>
/// </summary>
public sealed class ChangedItemHover : EventWrapper<Action<object?>, ChangedItemHover.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.ChangedItemTooltip"/>
Default = 0,
}
public ChangedItemHover()
: base(nameof(ChangedItemHover))
{ }
public void Invoke(object? data)
=> Invoke(this, data);
public bool HasTooltip
=> HasSubscribers;
}

View file

@ -0,0 +1,54 @@
using System;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever collection setup is changed.
/// <list type="number">
/// <item>Parameter is the type of the changed collection. (Inactive or Temporary for additions or deletions)</item>
/// <item>Parameter is the old collection, or null on additions.</item>
/// <item>Parameter is the new collection, or null on deletions.</item>
/// <item>Parameter is the display name for Individual collections or an empty string otherwise.</item>
/// </list> </summary>
public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCollection?, ModCollection?, string>, CollectionChange.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnCollectionChange"/>
CollectionCacheManager = -2,
/// <seealso cref="Collections.Manager.ActiveCollections.OnCollectionChange"/>
ActiveCollections = -1,
/// <seealso cref="Api.TempModManager.OnCollectionChange"/>
TempModManager = 0,
/// <seealso cref="Collections.Manager.InheritanceManager.OnCollectionChange" />
InheritanceManager = 0,
/// <seealso cref="Interop.PathResolving.IdentifiedCollectionCache.CollectionChangeClear" />
IdentifiedCollectionCache = 0,
/// <seealso cref="UI.AdvancedWindow.ItemSwapTab.OnCollectionChange" />
ItemSwapTab = 0,
/// <seealso cref="UI.CollectionTab.CollectionSelector.OnCollectionChange" />
CollectionSelector = 0,
/// <seealso cref="UI.CollectionTab.IndividualAssignmentUi.UpdateIdentifiers"/>
IndividualAssignmentUi = 0,
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnCollectionChange"/>
ModFileSystemSelector = 0,
}
public CollectionChange()
: base(nameof(CollectionChange))
{ }
public void Invoke(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string displayName)
=> Invoke(this, collectionType, oldCollection, newCollection, displayName);
}

View file

@ -0,0 +1,34 @@
using System;
using Penumbra.Collections;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever a collections inheritances change.
/// <list type="number">
/// <item>Parameter is the collection whose ancestors were changed. </item>
/// <item>Parameter is whether the change was itself inherited, i.e. if it happened in a direct parent (false) or a more removed ancestor (true). </item>
/// </list>
/// </summary>
public sealed class CollectionInheritanceChanged : EventWrapper<Action<ModCollection, bool>, CollectionInheritanceChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnCollectionInheritanceChange"/>
CollectionCacheManager = 0,
/// <seealso cref="UI.AdvancedWindow.ItemSwapTab.OnInheritanceChange"/>
ItemSwapTab = 0,
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnInheritanceChange"/>
ModFileSystemSelector = 0,
}
public CollectionInheritanceChanged()
: base(nameof(CollectionInheritanceChanged))
{ }
public void Invoke(ModCollection collection, bool inherited)
=> Invoke(this, collection, inherited);
}

View file

@ -0,0 +1,25 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary> <list type="number">
/// <item>Parameter is the game object for which a draw object is created. </item>
/// <item>Parameter is the name of the applied collection. </item>
/// <item>Parameter is the created draw object. </item>
/// </list> </summary>
public sealed class CreatedCharacterBase : EventWrapper<Action<nint, string, nint>, CreatedCharacterBase.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.CreatedCharacterBase"/>
Api = 0,
}
public CreatedCharacterBase()
: base(nameof(CreatedCharacterBase))
{ }
public void Invoke(nint gameObject, string appliedCollectionName, nint drawObject)
=> Invoke(this, gameObject, appliedCollectionName, drawObject);
}

View file

@ -0,0 +1,29 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever a character base draw object is being created by the game.
/// <list type="number">
/// <item>Parameter is the game object for which a draw object is created. </item>
/// <item>Parameter is the name of the applied collection. </item>
/// <item>Parameter is a pointer to the model id (an uint). </item>
/// <item>Parameter is a pointer to the customize array. </item>
/// <item>Parameter is a pointer to the equip data array. </item>
/// </list> </summary>
public sealed class CreatingCharacterBase : EventWrapper<Action<nint, string, nint, nint, nint>, CreatingCharacterBase.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.CreatingCharacterBase"/>
Api = 0,
}
public CreatingCharacterBase()
: base(nameof(CreatingCharacterBase))
{ }
public void Invoke(nint gameObject, string appliedCollectionName, nint modelIdAddress, nint customizeArrayAddress, nint equipDataAddress)
=> Invoke(this, gameObject, appliedCollectionName, modelIdAddress, customizeArrayAddress, equipDataAddress);
}

View file

@ -0,0 +1,26 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered when the general Enabled state of Penumbra is changed.
/// <list type="number">
/// <item>Parameter is whether Penumbra is now Enabled (true) or Disabled (false). </item>
/// </list>
/// </summary>
public sealed class EnabledChanged : EventWrapper<Action<bool>, EnabledChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.EnabledChange"/>
Api = int.MinValue,
}
public EnabledChanged()
: base(nameof(EnabledChanged))
{ }
public void Invoke(bool enabled)
=> Invoke(this, enabled);
}

View file

@ -0,0 +1,35 @@
using System;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever mod meta data or local data is changed.
/// <list type="number">
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
/// </list> </summary>
public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod, string?>, ModDataChanged.Priority>
{
public enum Priority
{
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnModDataChange"/>
ModFileSystemSelector = -10,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDataChange"/>
ModCacheManager = 0,
/// <seealso cref="Mods.ModFileSystem.OnDataChange"/>
ModFileSystem = 0,
}
public ModDataChanged()
: base(nameof(ModDataChanged))
{ }
public void Invoke(ModDataChangeType changeType, Mod mod, string? oldName)
=> Invoke(this, changeType, mod, oldName);
}

View file

@ -0,0 +1,30 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever the mod root directory changes.
/// <list type="number">
/// <item>Parameter is the full path of the new directory. </item>
/// <item>Parameter is whether the new directory is valid. </item>
/// </list>
/// </summary>
public sealed class ModDirectoryChanged : EventWrapper<Action<string, bool>, ModDirectoryChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.ModDirectoryChanged"/>
Api = 0,
/// <seealso cref="UI.FileDialogService.OnModDirectoryChange"/>
FileDialogService = 0,
}
public ModDirectoryChanged()
: base(nameof(ModDirectoryChanged))
{ }
public void Invoke(string newModDirectory, bool newDirectoryValid)
=> Invoke(this, newModDirectory, newDirectoryValid);
}

View file

@ -0,0 +1,33 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary> Triggered whenever a new mod discovery has finished. </summary>
public sealed class ModDiscoveryFinished : EventWrapper<Action, ModDiscoveryFinished.Priority>
{
public enum Priority
{
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.RestoreLastSelection"/>
ModFileSystemSelector = -200,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModDiscoveryFinished"/>
CollectionCacheManager = -100,
/// <seealso cref="Collections.Manager.CollectionStorage.OnModDiscoveryFinished"/>
CollectionStorage = 0,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModDiscoveryFinished"/>
ModCacheManager = 0,
/// <seealso cref="Mods.ModFileSystem.Reload"/>
ModFileSystem = 0,
}
public ModDiscoveryFinished()
: base(nameof(ModDiscoveryFinished))
{ }
public void Invoke()
=> Invoke(this);
}

View file

@ -0,0 +1,26 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary> Triggered whenever mods are prepared to be rediscovered. </summary>
public sealed class ModDiscoveryStarted : EventWrapper<Action, ModDiscoveryStarted.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModDiscoveryStarted"/>
CollectionCacheManager = 0,
/// <seealso cref="Collections.Manager.CollectionStorage.OnModDiscoveryStarted"/>
CollectionStorage = 0,
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.StoreCurrentSelection"/>
ModFileSystemSelector = 200,
}
public ModDiscoveryStarted()
: base(nameof(ModDiscoveryStarted))
{ }
public void Invoke()
=> Invoke(this);
}

View file

@ -0,0 +1,40 @@
using System;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever an option of a mod is changed inside the mod.
/// <list type="number">
/// <item>Parameter is the type option change. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the index of the changed group inside the mod. </item>
/// <item>Parameter is the index of the changed option inside the group or -1 if it does not concern a specific option. </item>
/// <item>Parameter is the index of the group an option was moved to. </item>
/// </list> </summary>
public sealed class ModOptionChanged : EventWrapper<Action<ModOptionChangeType, Mod, int, int, int>, ModOptionChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModOptionChange"/>
CollectionCacheManager = -100,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModOptionChange"/>
ModCacheManager = 0,
/// <seealso cref="UI.AdvancedWindow.ItemSwapTab.OnModOptionChange"/>
ItemSwapTab = 0,
/// <seealso cref="Collections.Manager.CollectionStorage.OnModOptionChange"/>
CollectionStorage = 100,
}
public ModOptionChanged()
: base(nameof(ModOptionChanged))
{ }
public void Invoke(ModOptionChangeType changeType, Mod mod, int groupIndex, int optionIndex, int moveToIndex)
=> Invoke(this, changeType, mod, groupIndex, optionIndex, moveToIndex);
}

View file

@ -0,0 +1,54 @@
using System;
using System.IO;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever a mod is added, deleted, moved or reloaded.
/// <list type="number">
/// <item>Parameter is the type of change. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the old directory on deletion, move or reload and null on addition. </item>
/// <item>Parameter is the new directory on addition, move or reload and null on deletion. </item>
/// </list>
/// </summary>
public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod, DirectoryInfo?, DirectoryInfo?>, ModPathChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeAddition"/>
CollectionCacheManagerAddition = -100,
/// <seealso cref="Api.PenumbraApi.ModPathChangeSubscriber"/>
Api = 0,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModPathChange"/>
ModCacheManager = 0,
/// <seealso cref="Mods.Manager.ModExportManager.OnModPathChange"/>
ModExportManager = 0,
/// <seealso cref="Mods.ModFileSystem.OnModPathChange"/>
ModFileSystem = 0,
/// <seealso cref="Mods.Manager.ModManager.OnModPathChange"/>
ModManager = 0,
/// <seealso cref="Collections.Manager.CollectionStorage.OnModPathChange"/>
CollectionStorage = 10,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeRemoval"/>
CollectionCacheManagerRemoval = 100,
}
public ModPathChanged()
: base(nameof(ModPathChanged))
{ }
public void Invoke(ModPathChangeType changeType, Mod mod, DirectoryInfo? oldModDirectory, DirectoryInfo? newModDirectory)
=> Invoke(this, changeType, mod, oldModDirectory, newModDirectory);
}

View file

@ -0,0 +1,43 @@
using System;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever a mod setting is changed.
/// <list type="number">
/// <item>Parameter is the collection in which the setting was changed. </item>
/// <item>Parameter is the type of change. </item>
/// <item>Parameter is the mod the setting was changed for, unless it was a multi-change. </item>
/// <item>Parameter is the old value of the setting before the change as int. </item>
/// <item>Parameter is the index of the changed group if the change type is Setting. </item>
/// <item>Parameter is whether the change was inherited from another collection. </item>
/// </list>
/// </summary>
public sealed class ModSettingChanged : EventWrapper<Action<ModCollection, ModSettingChange, Mod?, int, int, bool>, ModSettingChanged.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.OnModSettingChange"/>
Api = int.MinValue,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModSettingChange"/>
CollectionCacheManager = 0,
/// <seealso cref="UI.AdvancedWindow.ItemSwapTab.OnSettingChange"/>
ItemSwapTab = 0,
/// <seealso cref="UI.ModsTab.ModFileSystemSelector.OnSettingChange"/>
ModFileSystemSelector = 0,
}
public ModSettingChanged()
: base(nameof(ModSettingChanged))
{ }
public void Invoke(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited)
=> Invoke(this, collection, type, mod, oldValue, groupIdx, inherited);
}

View file

@ -0,0 +1,26 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered after the settings panel is drawn.
/// <list type="number">
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
/// </list>
/// </summary>
public sealed class PostSettingsPanelDraw : EventWrapper<Action<string>, PostSettingsPanelDraw.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.PostSettingsPanelDraw"/>
Default = 0,
}
public PostSettingsPanelDraw()
: base(nameof(PostSettingsPanelDraw))
{ }
public void Invoke(string modDirectory)
=> Invoke(this, modDirectory);
}

View file

@ -0,0 +1,26 @@
using System;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered before the settings panel is drawn.
/// <list type="number">
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
/// </list>
/// </summary>
public sealed class PreSettingsPanelDraw : EventWrapper<Action<string>, PreSettingsPanelDraw.Priority>
{
public enum Priority
{
/// <seealso cref="Api.PenumbraApi.PreSettingsPanelDraw"/>
Default = 0,
}
public PreSettingsPanelDraw()
: base(nameof(PreSettingsPanelDraw))
{ }
public void Invoke(string modDirectory)
=> Invoke(this, modDirectory);
}

View file

@ -0,0 +1,31 @@
using System;
using Penumbra.Mods;
using Penumbra.Util;
namespace Penumbra.Communication;
/// <summary>
/// Triggered whenever a temporary mod for all collections is changed.
/// <list type="number">
/// <item>Parameter added, deleted or edited temporary mod.</item>
/// <item>Parameter is whether the mod was newly created.</item>
/// <item>Parameter is whether the mod was deleted.</item>
/// </list> </summary>
public sealed class TemporaryGlobalModChange : EventWrapper<Action<TemporaryMod, bool, bool>, TemporaryGlobalModChange.Priority>
{
public enum Priority
{
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnGlobalModChange"/>
CollectionCacheManager = 0,
/// <seealso cref="Collections.Manager.TempCollectionManager.OnGlobalModChange"/>
TempCollectionManager = 0,
}
public TemporaryGlobalModChange()
: base(nameof(TemporaryGlobalModChange))
{ }
public void Invoke(TemporaryMod temporaryMod, bool newlyCreated, bool deleted)
=> Invoke(this, temporaryMod, newlyCreated, deleted);
}

View file

@ -6,6 +6,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Actors;
using Penumbra.Interop.Services;
using Penumbra.Services;
@ -26,7 +27,7 @@ public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable<(nint A
_communicator = communicator;
_events = events;
_communicator.CollectionChange.Subscribe(CollectionChangeClear);
_communicator.CollectionChange.Subscribe(CollectionChangeClear, CollectionChange.Priority.IdentifiedCollectionCache);
_clientState.TerritoryChanged += TerritoryClear;
_events.CharacterDestructor += OnCharacterDestruct;
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Penumbra.Communication;
using Penumbra.GameData;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
@ -23,10 +24,10 @@ public class ModCacheManager : IDisposable
_identifier = identifier;
_modManager = modStorage;
_communicator.ModOptionChanged.Subscribe(OnModOptionChange);
_communicator.ModPathChanged.Subscribe(OnModPathChange);
_communicator.ModDataChanged.Subscribe(OnModDataChange);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.ModCacheManager);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModCacheManager);
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModCacheManager);
_communicator.ModDiscoveryFinished.Subscribe(OnModDiscoveryFinished, ModDiscoveryFinished.Priority.ModCacheManager);
if (!identifier.Valid)
identifier.FinishedCreation += OnIdentifierCreation;
OnModDiscoveryFinished();

View file

@ -1,5 +1,6 @@
using System;
using System.IO;
using Penumbra.Communication;
using Penumbra.Services;
namespace Penumbra.Mods.Manager;
@ -21,7 +22,7 @@ public class ModExportManager : IDisposable
_communicator = communicator;
_modManager = modManager;
UpdateExportDirectory(_config.ExportDirectory, false);
_communicator.ModPathChanged.Subscribe(OnModPathChange);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModExportManager);
}
/// <inheritdoc cref="UpdateExportDirectory(string, bool)"/>

View file

@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using OtterGui.Filesystem;
using Penumbra.Communication;
using Penumbra.Mods.Manager;
using Penumbra.Services;
using Penumbra.Util;
@ -25,9 +26,9 @@ public sealed class ModFileSystem : FileSystem<Mod>, IDisposable, ISavable
_saveService = saveService;
Reload();
Changed += OnChange;
_communicator.ModDiscoveryFinished.Subscribe(Reload);
_communicator.ModDataChanged.Subscribe(OnDataChange);
_communicator.ModPathChanged.Subscribe(OnModPathChange);
_communicator.ModDiscoveryFinished.Subscribe(Reload, ModDiscoveryFinished.Priority.ModFileSystem);
_communicator.ModDataChanged.Subscribe(OnDataChange, ModDataChanged.Priority.ModFileSystem);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModFileSystem);
}
public void Dispose()

View file

@ -3,6 +3,7 @@ using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Penumbra.Communication;
using Penumbra.Services;
namespace Penumbra.Mods.Manager;
@ -50,7 +51,7 @@ public sealed class ModManager : ModStorage, IDisposable
OptionEditor = optionEditor;
Creator = creator;
SetBaseDirectory(config.ModDirectory, true);
_communicator.ModPathChanged.Subscribe(OnModPathChange);
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModManager);
DiscoverMods();
}

View file

@ -1,350 +1,59 @@
using System;
using System.IO;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Util;
using Penumbra.Communication;
namespace Penumbra.Services;
/// <summary>
/// Triggered whenever collection setup is changed.
/// <list type="number">
/// <item>Parameter is the type of the changed collection. (Inactive or Temporary for additions or deletions)</item>
/// <item>Parameter is the old collection, or null on additions.</item>
/// <item>Parameter is the new collection, or null on deletions.</item>
/// <item>Parameter is the display name for Individual collections or an empty string otherwise.</item>
/// </list> </summary>
public sealed class CollectionChange : EventWrapper<Action<CollectionType, ModCollection?, ModCollection?, string>>
{
public CollectionChange()
: base(nameof(CollectionChange))
{ }
public void Invoke(CollectionType collectionType, ModCollection? oldCollection, ModCollection? newCollection, string displayName)
=> Invoke(this, collectionType, oldCollection, newCollection, displayName);
}
/// <summary>
/// Triggered whenever a temporary mod for all collections is changed.
/// <list type="number">
/// <item>Parameter added, deleted or edited temporary mod.</item>
/// <item>Parameter is whether the mod was newly created.</item>
/// <item>Parameter is whether the mod was deleted.</item>
/// </list> </summary>
public sealed class TemporaryGlobalModChange : EventWrapper<Action<TemporaryMod, bool, bool>>
{
public TemporaryGlobalModChange()
: base(nameof(TemporaryGlobalModChange))
{ }
public void Invoke(TemporaryMod temporaryMod, bool newlyCreated, bool deleted)
=> Invoke(this, temporaryMod, newlyCreated, deleted);
}
/// <summary>
/// Triggered whenever a character base draw object is being created by the game.
/// <list type="number">
/// <item>Parameter is the game object for which a draw object is created. </item>
/// <item>Parameter is the name of the applied collection. </item>
/// <item>Parameter is a pointer to the model id (an uint). </item>
/// <item>Parameter is a pointer to the customize array. </item>
/// <item>Parameter is a pointer to the equip data array. </item>
/// </list> </summary>
public sealed class CreatingCharacterBase : EventWrapper<Action<nint, string, nint, nint, nint>>
{
public CreatingCharacterBase()
: base(nameof(CreatingCharacterBase))
{ }
public void Invoke(nint gameObject, string appliedCollectionName, nint modelIdAddress, nint customizeArrayAddress, nint equipDataAddress)
=> Invoke(this, gameObject, appliedCollectionName, modelIdAddress, customizeArrayAddress, equipDataAddress);
}
/// <summary> <list type="number">
/// <item>Parameter is the game object for which a draw object is created. </item>
/// <item>Parameter is the name of the applied collection. </item>
/// <item>Parameter is the created draw object. </item>
/// </list> </summary>
public sealed class CreatedCharacterBase : EventWrapper<Action<nint, string, nint>>
{
public CreatedCharacterBase()
: base(nameof(CreatedCharacterBase))
{ }
public void Invoke(nint gameObject, string appliedCollectionName, nint drawObject)
=> Invoke(this, gameObject, appliedCollectionName, drawObject);
}
/// <summary>
/// Triggered whenever mod meta data or local data is changed.
/// <list type="number">
/// <item>Parameter is the type of data change for the mod, which can be multiple flags. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the old name of the mod in case of a name change, and null otherwise. </item>
/// </list> </summary>
public sealed class ModDataChanged : EventWrapper<Action<ModDataChangeType, Mod, string?>>
{
public ModDataChanged()
: base(nameof(ModDataChanged))
{ }
public void Invoke(ModDataChangeType changeType, Mod mod, string? oldName)
=> Invoke(this, changeType, mod, oldName);
}
/// <summary>
/// Triggered whenever an option of a mod is changed inside the mod.
/// <list type="number">
/// <item>Parameter is the type option change. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the index of the changed group inside the mod. </item>
/// <item>Parameter is the index of the changed option inside the group or -1 if it does not concern a specific option. </item>
/// <item>Parameter is the index of the group an option was moved to. </item>
/// </list> </summary>
public sealed class ModOptionChanged : EventWrapper<Action<ModOptionChangeType, Mod, int, int, int>>
{
public ModOptionChanged()
: base(nameof(ModOptionChanged))
{ }
public void Invoke(ModOptionChangeType changeType, Mod mod, int groupIndex, int optionIndex, int moveToIndex)
=> Invoke(this, changeType, mod, groupIndex, optionIndex, moveToIndex);
}
/// <summary> Triggered whenever mods are prepared to be rediscovered. </summary>
public sealed class ModDiscoveryStarted : EventWrapper<Action>
{
public ModDiscoveryStarted()
: base(nameof(ModDiscoveryStarted))
{ }
public void Invoke()
=> EventWrapper<Action>.Invoke(this);
}
/// <summary> Triggered whenever a new mod discovery has finished. </summary>
public sealed class ModDiscoveryFinished : EventWrapper<Action>
{
public ModDiscoveryFinished()
: base(nameof(ModDiscoveryFinished))
{ }
public void Invoke()
=> Invoke(this);
}
/// <summary>
/// Triggered whenever the mod root directory changes.
/// <list type="number">
/// <item>Parameter is the full path of the new directory. </item>
/// <item>Parameter is whether the new directory is valid. </item>
/// </list>
/// </summary>
public sealed class ModDirectoryChanged : EventWrapper<Action<string, bool>>
{
public ModDirectoryChanged()
: base(nameof(ModDirectoryChanged))
{ }
public void Invoke(string newModDirectory, bool newDirectoryValid)
=> Invoke(this, newModDirectory, newDirectoryValid);
}
/// <summary>
/// Triggered whenever a mod is added, deleted, moved or reloaded.
/// <list type="number">
/// <item>Parameter is the type of change. </item>
/// <item>Parameter is the changed mod. </item>
/// <item>Parameter is the old directory on deletion, move or reload and null on addition. </item>
/// <item>Parameter is the new directory on addition, move or reload and null on deletion. </item>
/// </list>
/// </summary>
public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod, DirectoryInfo?, DirectoryInfo?>>
{
public ModPathChanged()
: base(nameof(ModPathChanged))
{ }
public void Invoke(ModPathChangeType changeType, Mod mod, DirectoryInfo? oldModDirectory, DirectoryInfo? newModDirectory)
=> Invoke(this, changeType, mod, oldModDirectory, newModDirectory);
}
/// <summary>
/// Triggered whenever a mod setting is changed.
/// <list type="number">
/// <item>Parameter is the collection in which the setting was changed. </item>
/// <item>Parameter is the type of change. </item>
/// <item>Parameter is the mod the setting was changed for, unless it was a multi-change. </item>
/// <item>Parameter is the old value of the setting before the change as int. </item>
/// <item>Parameter is the index of the changed group if the change type is Setting. </item>
/// <item>Parameter is whether the change was inherited from another collection. </item>
/// </list>
/// </summary>
public sealed class ModSettingChanged : EventWrapper<Action<ModCollection, ModSettingChange, Mod?, int, int, bool>>
{
public ModSettingChanged()
: base(nameof(ModSettingChanged))
{ }
public void Invoke(ModCollection collection, ModSettingChange type, Mod? mod, int oldValue, int groupIdx, bool inherited)
=> Invoke(this, collection, type, mod, oldValue, groupIdx, inherited);
}
/// <summary>
/// Triggered whenever a collections inheritances change.
/// <list type="number">
/// <item>Parameter is the collection whose ancestors were changed. </item>
/// <item>Parameter is whether the change was itself inherited, i.e. if it happened in a direct parent (false) or a more removed ancestor (true). </item>
/// </list>
/// </summary>
public sealed class CollectionInheritanceChanged : EventWrapper<Action<ModCollection, bool>>
{
public CollectionInheritanceChanged()
: base(nameof(CollectionInheritanceChanged))
{ }
public void Invoke(ModCollection collection, bool inherited)
=> Invoke(this, collection, inherited);
}
/// <summary>
/// Triggered when the general Enabled state of Penumbra is changed.
/// <list type="number">
/// <item>Parameter is whether Penumbra is now Enabled (true) or Disabled (false). </item>
/// </list>
/// </summary>
public sealed class EnabledChanged : EventWrapper<Action<bool>>
{
public EnabledChanged()
: base(nameof(EnabledChanged))
{ }
public void Invoke(bool enabled)
=> Invoke(this, enabled);
}
/// <summary>
/// Triggered before the settings panel is drawn.
/// <list type="number">
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
/// </list>
/// </summary>
public sealed class PreSettingsPanelDraw : EventWrapper<Action<string>>
{
public PreSettingsPanelDraw()
: base(nameof(PreSettingsPanelDraw))
{ }
public void Invoke(string modDirectory)
=> Invoke(this, modDirectory);
}
/// <summary>
/// Triggered after the settings panel is drawn.
/// <list type="number">
/// <item>Parameter is the identifier (directory name) of the currently selected mod. </item>
/// </list>
/// </summary>
public sealed class PostSettingsPanelDraw : EventWrapper<Action<string>>
{
public PostSettingsPanelDraw()
: base(nameof(PostSettingsPanelDraw))
{ }
public void Invoke(string modDirectory)
=> Invoke(this, modDirectory);
}
/// <summary>
/// Triggered when a Changed Item in Penumbra is hovered.
/// <list type="number">
/// <item>Parameter is the hovered object data if any. </item>
/// </list>
/// </summary>
public sealed class ChangedItemHover : EventWrapper<Action<object?>>
{
public ChangedItemHover()
: base(nameof(ChangedItemHover))
{ }
public void Invoke(object? data)
=> Invoke(this, data);
public bool HasTooltip
=> HasSubscribers;
}
/// <summary>
/// Triggered when a Changed Item in Penumbra is clicked.
/// <list type="number">
/// <item>Parameter is the clicked mouse button. </item>
/// <item>Parameter is the clicked object data if any.. </item>
/// </list>
/// </summary>
public sealed class ChangedItemClick : EventWrapper<Action<MouseButton, object?>>
{
public ChangedItemClick()
: base(nameof(ChangedItemClick))
{ }
public void Invoke(MouseButton button, object? data)
=> Invoke(this, button, data);
}
public class CommunicatorService : IDisposable
{
/// <inheritdoc cref="Services.CollectionChange"/>
/// <inheritdoc cref="Communication.CollectionChange"/>
public readonly CollectionChange CollectionChange = new();
/// <inheritdoc cref="Services.TemporaryGlobalModChange"/>
/// <inheritdoc cref="Communication.TemporaryGlobalModChange"/>
public readonly TemporaryGlobalModChange TemporaryGlobalModChange = new();
/// <inheritdoc cref="Services.CreatingCharacterBase"/>
/// <inheritdoc cref="Communication.CreatingCharacterBase"/>
public readonly CreatingCharacterBase CreatingCharacterBase = new();
/// <inheritdoc cref="Services.CreatedCharacterBase"/>
/// <inheritdoc cref="Communication.CreatedCharacterBase"/>
public readonly CreatedCharacterBase CreatedCharacterBase = new();
/// <inheritdoc cref="Services.ModDataChanged"/>
/// <inheritdoc cref="Communication.ModDataChanged"/>
public readonly ModDataChanged ModDataChanged = new();
/// <inheritdoc cref="Services.ModOptionChanged"/>
/// <inheritdoc cref="Communication.ModOptionChanged"/>
public readonly ModOptionChanged ModOptionChanged = new();
/// <inheritdoc cref="Services.ModDiscoveryStarted"/>
/// <inheritdoc cref="Communication.ModDiscoveryStarted"/>
public readonly ModDiscoveryStarted ModDiscoveryStarted = new();
/// <inheritdoc cref="Services.ModDiscoveryFinished"/>
/// <inheritdoc cref="Communication.ModDiscoveryFinished"/>
public readonly ModDiscoveryFinished ModDiscoveryFinished = new();
/// <inheritdoc cref="Services.ModDirectoryChanged"/>
/// <inheritdoc cref="Communication.ModDirectoryChanged"/>
public readonly ModDirectoryChanged ModDirectoryChanged = new();
/// <inheritdoc cref="Services.ModPathChanged"/>
/// <inheritdoc cref="Communication.ModPathChanged"/>
public readonly ModPathChanged ModPathChanged = new();
/// <inheritdoc cref="Services.ModSettingChanged"/>
/// <inheritdoc cref="Communication.ModSettingChanged"/>
public readonly ModSettingChanged ModSettingChanged = new();
/// <inheritdoc cref="Services.CollectionInheritanceChanged"/>
/// <inheritdoc cref="Communication.CollectionInheritanceChanged"/>
public readonly CollectionInheritanceChanged CollectionInheritanceChanged = new();
/// <inheritdoc cref="Services.EnabledChanged"/>
/// <inheritdoc cref="Communication.EnabledChanged"/>
public readonly EnabledChanged EnabledChanged = new();
/// <inheritdoc cref="Services.PreSettingsPanelDraw"/>
/// <inheritdoc cref="Communication.PreSettingsPanelDraw"/>
public readonly PreSettingsPanelDraw PreSettingsPanelDraw = new();
/// <inheritdoc cref="Services.PostSettingsPanelDraw"/>
/// <inheritdoc cref="Communication.PostSettingsPanelDraw"/>
public readonly PostSettingsPanelDraw PostSettingsPanelDraw = new();
/// <inheritdoc cref="Services.ChangedItemHover"/>
/// <inheritdoc cref="Communication.ChangedItemHover"/>
public readonly ChangedItemHover ChangedItemHover = new();
/// <inheritdoc cref="Services.ChangedItemClick"/>
/// <inheritdoc cref="Communication.ChangedItemClick"/>
public readonly ChangedItemClick ChangedItemClick = new();
public void Dispose()

View file

@ -13,6 +13,7 @@ using OtterGui.Widgets;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.Meta;
@ -57,10 +58,10 @@ public class ItemSwapTab : IDisposable, ITab
// @formatter:on
};
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.ModSettingChanged.Subscribe(OnSettingChange);
_communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ItemSwapTab);
_communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ItemSwapTab);
_communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ItemSwapTab);
_communicator.ModOptionChanged.Subscribe(OnModOptionChange, ModOptionChanged.Priority.ItemSwapTab);
}
/// <summary> Update the currently selected mod or its settings. </summary>

View file

@ -6,6 +6,7 @@ using OtterGui;
using OtterGui.Raii;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Actors;
using Penumbra.Services;
using Penumbra.UI.Classes;
@ -34,7 +35,7 @@ public sealed class CollectionSelector : ItemSelector<ModCollection>, IDisposabl
_active = active;
_tutorial = tutorial;
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.CollectionSelector);
// Set items.
OnCollectionChange(CollectionType.Inactive, null, null, string.Empty);
// Set selection.

View file

@ -5,6 +5,7 @@ using ImGuiNET;
using OtterGui.Raii;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.GameData.Actors;
using Penumbra.Services;
@ -30,7 +31,7 @@ public class IndividualAssignmentUi : IDisposable
_communicator = communicator;
_actorService = actors;
_collectionManager = collectionManager;
_communicator.CollectionChange.Subscribe(UpdateIdentifiers);
_communicator.CollectionChange.Subscribe(UpdateIdentifiers, CollectionChange.Priority.IndividualAssignmentUi);
if (_actorService.Valid)
SetupCombos();
else
@ -57,7 +58,7 @@ public class IndividualAssignmentUi : IDisposable
public void DrawWorldCombo(float width)
{
if (_ready && _worldCombo.Draw(width))
UpdateIdentifiers();
UpdateIdentifiersInternal();
}
public void DrawObjectKindCombo(float width)
@ -76,7 +77,7 @@ public class IndividualAssignmentUi : IDisposable
continue;
_newKind = kind;
UpdateIdentifiers();
UpdateIdentifiersInternal();
}
}
@ -87,7 +88,7 @@ public class IndividualAssignmentUi : IDisposable
ImGui.SetNextItemWidth(width);
if (ImGui.InputTextWithHint("##NewCharacter", "Character Name...", ref _newCharacterName, 32))
UpdateIdentifiers();
UpdateIdentifiersInternal();
}
public void DrawNewNpcCollection(float width)
@ -97,7 +98,7 @@ public class IndividualAssignmentUi : IDisposable
var combo = GetNpcCombo(_newKind);
if (combo.Draw(width))
UpdateIdentifiers();
UpdateIdentifiersInternal();
}
public void Dispose()
@ -154,10 +155,10 @@ public class IndividualAssignmentUi : IDisposable
private void UpdateIdentifiers(CollectionType type, ModCollection? _1, ModCollection? _2, string _3)
{
if (type == CollectionType.Individual)
UpdateIdentifiers();
UpdateIdentifiersInternal();
}
private void UpdateIdentifiers()
private void UpdateIdentifiersInternal()
{
var combo = GetNpcCombo(_newKind);
PlayerTooltip = _collectionManager.Active.Individuals.CanAdd(IdentifierType.Player, _newCharacterName,

View file

@ -9,6 +9,7 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Utility;
using ImGuiNET;
using OtterGui;
using Penumbra.Communication;
using Penumbra.Services;
namespace Penumbra.UI;
@ -24,7 +25,7 @@ public class FileDialogService : IDisposable
{
_communicator = communicator;
_manager = SetupFileManager(config.ModDirectory);
_communicator.ModDirectoryChanged.Subscribe(OnModDirectoryChange);
_communicator.ModDirectoryChanged.Subscribe(OnModDirectoryChange, ModDirectoryChanged.Priority.FileDialogService);
}
public void OpenFilePicker(string title, string filters, Action<bool, List<string>> callback, int selectionCountMax, string? startPath,

View file

@ -14,6 +14,7 @@ using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.Collections;
using Penumbra.Collections.Manager;
using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Manager;
using Penumbra.Services;
@ -72,12 +73,12 @@ public sealed class ModFileSystemSelector : FileSystemSelector<Mod, ModFileSyste
SetFilterTooltip();
SelectionChanged += OnSelectionChange;
_communicator.CollectionChange.Subscribe(OnCollectionChange);
_communicator.ModSettingChanged.Subscribe(OnSettingChange);
_communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange);
_communicator.ModDataChanged.Subscribe(OnModDataChange);
_communicator.ModDiscoveryStarted.Subscribe(StoreCurrentSelection);
_communicator.ModDiscoveryFinished.Subscribe(RestoreLastSelection);
_communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.ModFileSystemSelector);
_communicator.ModSettingChanged.Subscribe(OnSettingChange, ModSettingChanged.Priority.ModFileSystemSelector);
_communicator.CollectionInheritanceChanged.Subscribe(OnInheritanceChange, CollectionInheritanceChanged.Priority.ModFileSystemSelector);
_communicator.ModDataChanged.Subscribe(OnModDataChange, ModDataChanged.Priority.ModFileSystemSelector);
_communicator.ModDiscoveryStarted.Subscribe(StoreCurrentSelection, ModDiscoveryStarted.Priority.ModFileSystemSelector);
_communicator.ModDiscoveryFinished.Subscribe(RestoreLastSelection, ModDiscoveryFinished.Priority.ModFileSystemSelector);
OnCollectionChange(CollectionType.Current, null, _collectionManager.Active.Current, "");
}

View file

@ -4,10 +4,12 @@ using System.Linq;
namespace Penumbra.Util;
public abstract class EventWrapper<T> : IDisposable where T : Delegate
public abstract class EventWrapper<T, TPriority> : IDisposable
where T : Delegate
where TPriority : struct, Enum
{
private readonly string _name;
private readonly List<(object Subscriber, int Priority)> _event = new();
private readonly List<(object Subscriber, TPriority Priority)> _event = new();
public bool HasSubscribers
=> _event.Count > 0;
@ -23,12 +25,12 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
public void Subscribe(T subscriber, int priority = 0)
public void Subscribe(T subscriber, TPriority priority)
{
lock (_event)
{
var existingIdx = _event.FindIndex(p => (T)p.Subscriber == subscriber);
var idx = _event.FindIndex(p => p.Priority > priority);
var idx = _event.FindIndex(p => p.Priority.CompareTo(priority) > 0);
if (idx == existingIdx)
{
if (idx < 0)
@ -60,7 +62,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
protected static void Invoke(EventWrapper<T> wrapper)
protected static void Invoke(EventWrapper<T, TPriority> wrapper)
{
lock (wrapper._event)
{
@ -78,7 +80,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1>(EventWrapper<T> wrapper, T1 a)
protected static void Invoke<T1>(EventWrapper<T, TPriority> wrapper, T1 a)
{
lock (wrapper._event)
{
@ -96,7 +98,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1, T2>(EventWrapper<T> wrapper, T1 a, T2 b)
protected static void Invoke<T1, T2>(EventWrapper<T, TPriority> wrapper, T1 a, T2 b)
{
lock (wrapper._event)
{
@ -114,7 +116,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1, T2, T3>(EventWrapper<T> wrapper, T1 a, T2 b, T3 c)
protected static void Invoke<T1, T2, T3>(EventWrapper<T, TPriority> wrapper, T1 a, T2 b, T3 c)
{
lock (wrapper._event)
{
@ -132,7 +134,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1, T2, T3, T4>(EventWrapper<T> wrapper, T1 a, T2 b, T3 c, T4 d)
protected static void Invoke<T1, T2, T3, T4>(EventWrapper<T, TPriority> wrapper, T1 a, T2 b, T3 c, T4 d)
{
lock (wrapper._event)
{
@ -150,7 +152,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1, T2, T3, T4, T5>(EventWrapper<T> wrapper, T1 a, T2 b, T3 c, T4 d, T5 e)
protected static void Invoke<T1, T2, T3, T4, T5>(EventWrapper<T, TPriority> wrapper, T1 a, T2 b, T3 c, T4 d, T5 e)
{
lock (wrapper._event)
{
@ -168,7 +170,7 @@ public abstract class EventWrapper<T> : IDisposable where T : Delegate
}
}
protected static void Invoke<T1, T2, T3, T4, T5, T6>(EventWrapper<T> wrapper, T1 a, T2 b, T3 c, T4 d, T5 e, T6 f)
protected static void Invoke<T1, T2, T3, T4, T5, T6>(EventWrapper<T, TPriority> wrapper, T1 a, T2 b, T3 c, T4 d, T5 e, T6 f)
{
lock (wrapper._event)
{