diff --git a/Penumbra.Api b/Penumbra.Api index b65f0a4e..97dc16ba 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit b65f0a4e2a761a3142a07587a6c0f8657f1361ee +Subproject commit 97dc16ba32bf78c4d3a4d210a08010cd6d4eec3c diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index 2897c380..90a17687 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -678,6 +678,32 @@ public class IpcTester : IDisposable } } } + + DrawIntro( Ipc.ResolvePlayerPaths.Label, "Resolved Paths (Player)" ); + if( _currentResolvePath.Length > 0 || _currentReversePath.Length > 0 ) + { + var forwardArray = _currentResolvePath.Length > 0 ? new[] { _currentResolvePath } : Array.Empty< string >(); + var reverseArray = _currentReversePath.Length > 0 ? new[] { _currentReversePath } : Array.Empty< string >(); + var ret = Ipc.ResolvePlayerPaths.Subscriber( _pi ).Invoke( forwardArray, reverseArray ); + var text = string.Empty; + if( ret.Item1.Length > 0 ) + { + if( ret.Item2.Length > 0 ) + { + text = $"Forward: {ret.Item1[ 0 ]} | Reverse: {string.Join( "; ", ret.Item2[ 0 ] )}."; + } + else + { + text = $"Forward: {ret.Item1[ 0 ]}."; + } + } + else if( ret.Item2.Length > 0 ) + { + text = $"Reverse: {string.Join( "; ", ret.Item2[ 0 ] )}."; + } + + ImGui.TextUnformatted( text ); + } } } @@ -685,10 +711,10 @@ public class IpcTester : IDisposable { private readonly DalamudPluginInterface _pi; - private int _objectIdx = 0; - private string _collectionName = string.Empty; - private bool _allowCreation = true; - private bool _allowDeletion = true; + private int _objectIdx = 0; + private string _collectionName = string.Empty; + private bool _allowCreation = true; + private bool _allowDeletion = true; private ApiCollectionType _type = ApiCollectionType.Current; private string _characterCollectionName = string.Empty; @@ -709,7 +735,7 @@ public class IpcTester : IDisposable return; } - ImGuiUtil.GenericEnumCombo( "Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName() ); + ImGuiUtil.GenericEnumCombo( "Collection Type", 200, _type, out _type, t => ( ( CollectionType )t ).ToName() ); ImGui.InputInt( "Object Index##Collections", ref _objectIdx, 0, 0 ); ImGui.InputText( "Collection Name##Collections", ref _collectionName, 64 ); ImGui.Checkbox( "Allow Assignment Creation", ref _allowCreation ); @@ -766,8 +792,11 @@ public class IpcTester : IDisposable { ( _returnCode, _oldCollection ) = Ipc.SetCollectionForObject.Subscriber( _pi ).Invoke( _objectIdx, _collectionName, _allowCreation, _allowDeletion ); } + if( _returnCode == PenumbraApiEc.NothingChanged && _oldCollection.IsNullOrEmpty() ) + { _oldCollection = null; + } DrawIntro( Ipc.GetChangedItems.Label, "Changed Item List" ); ImGui.SetNextItemWidth( 200 * ImGuiHelpers.GlobalScale ); diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs index 7997f8e8..8e609cb7 100644 --- a/Penumbra/Api/PenumbraApi.cs +++ b/Penumbra/Api/PenumbraApi.cs @@ -13,6 +13,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using Penumbra.Api.Enums; using Penumbra.GameData.Actors; using Penumbra.String; @@ -23,7 +24,7 @@ namespace Penumbra.Api; public class PenumbraApi : IDisposable, IPenumbraApi { public (int, int) ApiVersion - => ( 4, 18 ); + => ( 4, 19 ); private Penumbra? _penumbra; private Lumina.GameData? _lumina; @@ -271,6 +272,20 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ret.Select( r => r.ToString() ).ToArray(); } + public (string[], string[][]) ResolvePlayerPaths( string[] forward, string[] reverse ) + { + CheckInitialized(); + if( !Penumbra.Config.EnableMods ) + { + return ( forward, reverse.Select( p => new[] { p } ).ToArray() ); + } + + var playerCollection = PathResolver.PlayerCollection(); + var resolved = forward.Select( p => ResolvePath( p, Penumbra.ModManager, playerCollection ) ).ToArray(); + var reverseResolved = playerCollection.ReverseResolvePaths( reverse ); + return ( resolved, reverseResolved.Select( a => a.Select( p => p.ToString() ).ToArray() ).ToArray() ); + } + public T? GetFile< T >( string gamePath ) where T : FileResource => GetFileIntern< T >( ResolveDefaultPath( gamePath ) ); @@ -306,17 +321,21 @@ public class PenumbraApi : IDisposable, IPenumbraApi { CheckInitialized(); if( !Enum.IsDefined( type ) ) + { return string.Empty; + } var collection = Penumbra.CollectionManager.ByType( ( CollectionType )type ); return collection?.Name ?? string.Empty; } - public (PenumbraApiEc, string OldCollection) SetCollectionForType( Enums.ApiCollectionType type, string collectionName, bool allowCreateNew, bool allowDelete ) + public (PenumbraApiEc, string OldCollection) SetCollectionForType( ApiCollectionType type, string collectionName, bool allowCreateNew, bool allowDelete ) { CheckInitialized(); if( !Enum.IsDefined( type ) ) + { return ( PenumbraApiEc.InvalidArgument, string.Empty ); + } var oldCollection = Penumbra.CollectionManager.ByType( ( CollectionType )type )?.Name ?? string.Empty; @@ -327,18 +346,18 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ( PenumbraApiEc.NothingChanged, oldCollection ); } - if( !allowDelete || type is Enums.ApiCollectionType.Current or Enums.ApiCollectionType.Default or Enums.ApiCollectionType.Interface ) + if( !allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface ) { return ( PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection ); } - Penumbra.CollectionManager.RemoveSpecialCollection( (CollectionType) type ); + Penumbra.CollectionManager.RemoveSpecialCollection( ( CollectionType )type ); return ( PenumbraApiEc.Success, oldCollection ); } if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) ) { - return (PenumbraApiEc.CollectionMissing, oldCollection); + return ( PenumbraApiEc.CollectionMissing, oldCollection ); } if( oldCollection.Length == 0 ) @@ -355,7 +374,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi return ( PenumbraApiEc.NothingChanged, oldCollection ); } - Penumbra.CollectionManager.SetCollection( collection, (CollectionType) type ); + Penumbra.CollectionManager.SetCollection( collection, ( CollectionType )type ); return ( PenumbraApiEc.Success, oldCollection ); } @@ -392,28 +411,29 @@ public class PenumbraApi : IDisposable, IPenumbraApi { if( oldCollection.Length == 0 ) { - return (PenumbraApiEc.NothingChanged, oldCollection); + return ( PenumbraApiEc.NothingChanged, oldCollection ); } if( !allowDelete ) { - return (PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection); + return ( PenumbraApiEc.AssignmentDeletionDisallowed, oldCollection ); } + var idx = Penumbra.CollectionManager.Individuals.Index( id ); - Penumbra.CollectionManager.RemoveIndividualCollection(idx ); - return (PenumbraApiEc.Success, oldCollection); + Penumbra.CollectionManager.RemoveIndividualCollection( idx ); + return ( PenumbraApiEc.Success, oldCollection ); } if( !Penumbra.CollectionManager.ByName( collectionName, out var collection ) ) { - return (PenumbraApiEc.CollectionMissing, oldCollection); + return ( PenumbraApiEc.CollectionMissing, oldCollection ); } if( oldCollection.Length == 0 ) { if( !allowCreateNew ) { - return (PenumbraApiEc.AssignmentCreationDisallowed, oldCollection); + return ( PenumbraApiEc.AssignmentCreationDisallowed, oldCollection ); } var ids = Penumbra.CollectionManager.Individuals.GetGroup( id ); @@ -421,11 +441,11 @@ public class PenumbraApi : IDisposable, IPenumbraApi } else if( oldCollection == collection.Name ) { - return (PenumbraApiEc.NothingChanged, oldCollection); + return ( PenumbraApiEc.NothingChanged, oldCollection ); } Penumbra.CollectionManager.SetCollection( collection, CollectionType.Individual, Penumbra.CollectionManager.Individuals.Index( id ) ); - return (PenumbraApiEc.Success, oldCollection); + return ( PenumbraApiEc.Success, oldCollection ); } public IList< string > GetCollections() @@ -983,6 +1003,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi => ChangedItemClicked?.Invoke( button, it ); + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private void CheckInitialized() { if( !Valid ) @@ -993,6 +1014,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi // Return the collection associated to a current game object. If it does not exist, return the default collection. // If the index is invalid, returns false and the default collection. + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private static unsafe bool AssociatedCollection( int gameObjectIdx, out ModCollection collection ) { collection = Penumbra.CollectionManager.Default; @@ -1011,17 +1033,20 @@ public class PenumbraApi : IDisposable, IPenumbraApi return true; } + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private static unsafe ActorIdentifier AssociatedIdentifier( int gameObjectIdx ) { if( gameObjectIdx < 0 || gameObjectIdx >= Dalamud.Objects.Length ) { return ActorIdentifier.Invalid; } + var ptr = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )Dalamud.Objects.GetObjectAddress( gameObjectIdx ); return Penumbra.Actors.FromObject( ptr, out _, false, true ); } // Resolve a path given by string for a specific collection. + [MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )] private static string ResolvePath( string path, Mod.Manager _, ModCollection collection ) { if( !Penumbra.Config.EnableMods ) diff --git a/Penumbra/Api/PenumbraIpcProviders.cs b/Penumbra/Api/PenumbraIpcProviders.cs index 5e23624f..0425d721 100644 --- a/Penumbra/Api/PenumbraIpcProviders.cs +++ b/Penumbra/Api/PenumbraIpcProviders.cs @@ -49,26 +49,27 @@ public class PenumbraIpcProviders : IDisposable internal readonly EventProvider< nint, string, string > GameObjectResourcePathResolved; // Resolve - internal readonly FuncProvider< string, string > ResolveDefaultPath; - internal readonly FuncProvider< string, string > ResolveInterfacePath; - internal readonly FuncProvider< string, string > ResolvePlayerPath; - internal readonly FuncProvider< string, int, string > ResolveGameObjectPath; - internal readonly FuncProvider< string, string, string > ResolveCharacterPath; - internal readonly FuncProvider< string, string, string[] > ReverseResolvePath; - internal readonly FuncProvider< string, int, string[] > ReverseResolveGameObjectPath; - internal readonly FuncProvider< string, string[] > ReverseResolvePlayerPath; + internal readonly FuncProvider< string, string > ResolveDefaultPath; + internal readonly FuncProvider< string, string > ResolveInterfacePath; + internal readonly FuncProvider< string, string > ResolvePlayerPath; + internal readonly FuncProvider< string, int, string > ResolveGameObjectPath; + internal readonly FuncProvider< string, string, string > ResolveCharacterPath; + internal readonly FuncProvider< string, string, string[] > ReverseResolvePath; + internal readonly FuncProvider< string, int, string[] > ReverseResolveGameObjectPath; + internal readonly FuncProvider< string, string[] > ReverseResolvePlayerPath; + internal readonly FuncProvider< string[], string[], (string[], string[][]) > ResolvePlayerPaths; // Collections - internal readonly FuncProvider< IList< string > > GetCollections; - internal readonly FuncProvider< string > GetCurrentCollectionName; - internal readonly FuncProvider< string > GetDefaultCollectionName; - internal readonly FuncProvider< string > GetInterfaceCollectionName; - internal readonly FuncProvider< string, (string, bool) > GetCharacterCollectionName; + internal readonly FuncProvider< IList< string > > GetCollections; + internal readonly FuncProvider< string > GetCurrentCollectionName; + internal readonly FuncProvider< string > GetDefaultCollectionName; + internal readonly FuncProvider< string > GetInterfaceCollectionName; + internal readonly FuncProvider< string, (string, bool) > GetCharacterCollectionName; internal readonly FuncProvider< ApiCollectionType, string > GetCollectionForType; internal readonly FuncProvider< ApiCollectionType, string, bool, bool, (PenumbraApiEc, string) > SetCollectionForType; - internal readonly FuncProvider< int, (bool, bool, string) > GetCollectionForObject; - internal readonly FuncProvider< int, string, bool, bool, (PenumbraApiEc, string) > SetCollectionForObject; - internal readonly FuncProvider< string, IReadOnlyDictionary< string, object? > > GetChangedItems; + internal readonly FuncProvider< int, (bool, bool, string) > GetCollectionForObject; + internal readonly FuncProvider< int, string, bool, bool, (PenumbraApiEc, string) > SetCollectionForObject; + internal readonly FuncProvider< string, IReadOnlyDictionary< string, object? > > GetChangedItems; // Meta internal readonly FuncProvider< string > GetPlayerMetaManipulations; @@ -160,6 +161,7 @@ public class PenumbraIpcProviders : IDisposable ReverseResolvePath = Ipc.ReverseResolvePath.Provider( pi, Api.ReverseResolvePath ); ReverseResolveGameObjectPath = Ipc.ReverseResolveGameObjectPath.Provider( pi, Api.ReverseResolveGameObjectPath ); ReverseResolvePlayerPath = Ipc.ReverseResolvePlayerPath.Provider( pi, Api.ReverseResolvePlayerPath ); + ResolvePlayerPaths = Ipc.ResolvePlayerPaths.Provider( pi, Api.ResolvePlayerPaths ); // Collections GetCollections = Ipc.GetCollections.Provider( pi, Api.GetCollections ); @@ -263,6 +265,7 @@ public class PenumbraIpcProviders : IDisposable ReverseResolvePath.Dispose(); ReverseResolveGameObjectPath.Dispose(); ReverseResolvePlayerPath.Dispose(); + ResolvePlayerPaths.Dispose(); // Collections GetCollections.Dispose(); diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs index a4d1619c..d425a031 100644 --- a/Penumbra/Collections/ModCollection.Cache.Access.cs +++ b/Penumbra/Collections/ModCollection.Cache.Access.cs @@ -4,6 +4,7 @@ using Penumbra.Meta.Manager; using Penumbra.Mods; using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Penumbra.Interop; @@ -68,6 +69,9 @@ public partial class ModCollection public IEnumerable< Utf8GamePath > ReverseResolvePath( FullPath path ) => _cache?.ReverseResolvePath( path ) ?? Array.Empty< Utf8GamePath >(); + public HashSet< Utf8GamePath >[] ReverseResolvePaths( string[] paths ) + => _cache?.ReverseResolvePaths( paths ) ?? paths.Select( _ => new HashSet< Utf8GamePath >() ).ToArray(); + public FullPath? ResolvePath( Utf8GamePath path ) => _cache?.ResolvePath( path ); diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs index 46d049e8..1b60561d 100644 --- a/Penumbra/Collections/ModCollection.Cache.cs +++ b/Penumbra/Collections/ModCollection.Cache.cs @@ -5,6 +5,7 @@ using Penumbra.Meta.Manipulations; using Penumbra.Mods; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using Penumbra.Api.Enums; using Penumbra.String.Classes; @@ -104,6 +105,33 @@ public partial class ModCollection return iterator; } + // Reverse resolve multiple paths at once for efficiency. + public HashSet< Utf8GamePath >[] ReverseResolvePaths( IReadOnlyCollection< string > fullPaths ) + { + if( fullPaths.Count == 0 ) + return Array.Empty< HashSet< Utf8GamePath > >(); + + var ret = new HashSet< Utf8GamePath >[fullPaths.Count]; + var dict = new Dictionary< FullPath, int >( fullPaths.Count ); + foreach( var (path, idx) in fullPaths.WithIndex() ) + { + dict[ new FullPath(path) ] = idx; + ret[ idx ] = !Path.IsPathRooted( path ) && Utf8GamePath.FromString( path, out var utf8 ) + ? new HashSet< Utf8GamePath > { utf8 } + : new HashSet< Utf8GamePath >(); + } + + foreach( var (game, full) in ResolvedFiles ) + { + if( dict.TryGetValue( full.Path, out var idx ) ) + { + ret[ idx ].Add( game ); + } + } + + return ret; + } + private void OnModSettingChange( ModSettingChange type, int modIdx, int oldValue, int groupIdx, bool _ ) { switch( type ) @@ -474,7 +502,7 @@ public partial class ModCollection // Skip IMCs because they would result in far too many false-positive items, // since they are per set instead of per item-slot/item/variant. var identifier = Penumbra.Identifier; - var items = new SortedList< string, object? >(512); + var items = new SortedList< string, object? >( 512 ); void AddItems( IMod mod ) {