Cache collections instead of looking them up for every single file.

This commit is contained in:
Ottermandias 2022-11-18 19:53:06 +01:00
parent f676bd1889
commit 2fac923452
12 changed files with 194 additions and 28 deletions

View file

@ -162,10 +162,15 @@ public partial class ActorManager
((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)actor)->NameID);
}
case ObjectKind.EventNpc:
{
var dataId = actor->DataID;
// Special case for squadron that is also in the game functions, cf. E8 ?? ?? ?? ?? 89 87 ?? ?? ?? ?? 4C 89 BF
if (dataId == 0xf845d)
dataId = actor->GetNpcID();
return check
? CreateNpc(ObjectKind.EventNpc, actor->DataID, actor->ObjectIndex)
: CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, actor->ObjectIndex, ObjectKind.EventNpc,
actor->ObjectIndex);
? CreateNpc(ObjectKind.EventNpc, dataId, actor->ObjectIndex)
: CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty, actor->ObjectIndex, ObjectKind.EventNpc, dataId);
}
case ObjectKind.MountType:
case ObjectKind.Companion:
case (ObjectKind)15: // TODO: CS Update

View file

@ -35,6 +35,8 @@ public class TempModManager
public bool CollectionByName( string name, [NotNullWhen( true )] out ModCollection? collection )
=> Collections.Values.FindFirst( c => string.Equals( c.Name, name, StringComparison.OrdinalIgnoreCase ), out collection );
public event ModCollection.Manager.CollectionChangeDelegate? CollectionChanged;
// These functions to check specific redirections or meta manipulations for existence are currently unused.
//public bool IsRegistered( string tag, ModCollection? collection, Utf8GamePath gamePath, out FullPath? fullPath, out int priority )
//{
@ -151,6 +153,7 @@ public class TempModManager
{
var collection = ModCollection.CreateNewTemporary( tag, characterName );
_collections[ characterName ] = collection;
CollectionChanged?.Invoke(CollectionType.Temporary, null, collection );
return collection.Name;
}
@ -160,6 +163,7 @@ public class TempModManager
{
_mods.Remove( c );
c.ClearCache();
CollectionChanged?.Invoke( CollectionType.Temporary, c, null );
return true;
}

View file

@ -126,7 +126,7 @@ public partial class ModCollection
RemoveCache( oldCollectionIdx );
UpdateCurrentCollectionInUse();
CollectionChanged.Invoke( collectionType, this[ oldCollectionIdx ], newCollection, Individuals[ individualIndex ].DisplayName );
CollectionChanged.Invoke( collectionType, this[ oldCollectionIdx ], newCollection, collectionType == CollectionType.Individual ? Individuals[ individualIndex ].DisplayName : string.Empty );
}
private void UpdateCurrentCollectionInUse()

View file

@ -99,6 +99,7 @@ public enum CollectionType : byte
Interface, // The ui collection was changed
Individual, // An individual collection was changed
Current, // The current collection was changed
Temporary, // A temporary collections was set or deleted via IPC
}
public static class CollectionTypeExtensions

View file

@ -0,0 +1,129 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Collections;
using Penumbra.GameData.Actors;
namespace Penumbra.Interop.Resolver;
public unsafe class IdentifiedCollectionCache : IDisposable, IEnumerable< (IntPtr Address, ActorIdentifier Identifier, ModCollection Collection) >
{
private readonly Dictionary< IntPtr, (ActorIdentifier, ModCollection) > _cache = new(317);
private bool _dirty = false;
private bool _enabled = false;
public IdentifiedCollectionCache()
{
SignatureHelper.Initialise( this );
}
public void Enable()
{
if( _enabled )
{
return;
}
Penumbra.CollectionManager.CollectionChanged += CollectionChangeClear;
Penumbra.TempMods.CollectionChanged += CollectionChangeClear;
Dalamud.ClientState.TerritoryChanged += TerritoryClear;
_characterDtorHook.Enable();
_enabled = true;
}
public void Disable()
{
if( !_enabled )
{
return;
}
Penumbra.CollectionManager.CollectionChanged -= CollectionChangeClear;
Penumbra.TempMods.CollectionChanged -= CollectionChangeClear;
Dalamud.ClientState.TerritoryChanged -= TerritoryClear;
_characterDtorHook.Disable();
_enabled = false;
}
public ResolveData Set( ModCollection collection, ActorIdentifier identifier, GameObject* data )
{
if( _dirty )
{
_dirty = false;
_cache.Clear();
}
_cache[ ( IntPtr )data ] = ( identifier, collection );
return collection.ToResolveData( data );
}
public bool TryGetValue( GameObject* gameObject, out ResolveData resolve )
{
if( _dirty )
{
_dirty = false;
_cache.Clear();
}
else if( _cache.TryGetValue( ( IntPtr )gameObject, out var p ) )
{
resolve = p.Item2.ToResolveData( gameObject );
return true;
}
resolve = default;
return false;
}
public void Dispose()
{
Disable();
_characterDtorHook.Dispose();
GC.SuppressFinalize( this );
}
public IEnumerator< (IntPtr Address, ActorIdentifier Identifier, ModCollection Collection) > GetEnumerator()
{
foreach( var (address, (identifier, collection)) in _cache )
{
if( _dirty )
{
yield break;
}
yield return ( address, identifier, collection );
}
}
~IdentifiedCollectionCache()
=> Dispose();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
private void CollectionChangeClear( CollectionType type, ModCollection? _1, ModCollection? _2, string _3 )
{
if( type is not (CollectionType.Current or CollectionType.Interface or CollectionType.Inactive) )
{
_dirty = _cache.Count > 0;
}
}
private void TerritoryClear( object? _1, ushort _2 )
=> _dirty = _cache.Count > 0;
private delegate void CharacterDestructorDelegate( Character* character );
[Signature( "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 48 8D 05 ?? ?? ?? ?? 48 89 81 ?? ?? ?? ?? 48 8D 05",
DetourName = nameof( CharacterDestructorDetour ) )]
private Hook< CharacterDestructorDelegate > _characterDtorHook = null!;
private void CharacterDestructorDetour( Character* character )
{
_cache.Remove( ( IntPtr )character );
_characterDtorHook.Original( character );
}
}

View file

@ -118,7 +118,7 @@ public unsafe partial class PathResolver
if( idx >= 0 && idx < Dalamud.Objects.Length )
{
var obj = Dalamud.Objects[ idx ];
_animationLoadData = obj != null ? IdentifyCollection( ( GameObject* )obj.Address ) : ResolveData.Invalid;
_animationLoadData = obj != null ? IdentifyCollection( ( GameObject* )obj.Address, true ) : ResolveData.Invalid;
}
else
{
@ -165,7 +165,7 @@ public unsafe partial class PathResolver
private ulong LoadSomeAvfxDetour( uint a1, IntPtr gameObject, IntPtr gameObject2, float unk1, IntPtr unk2, IntPtr unk3 )
{
var last = _animationLoadData;
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject, true );
var ret = _loadSomeAvfxHook.Original( a1, gameObject, gameObject2, unk1, unk2, unk3 );
_animationLoadData = last;
return ret;
@ -187,7 +187,7 @@ public unsafe partial class PathResolver
var actorIdx = ( int )( *( *( ulong** )timelinePtr + 1 ) >> 3 );
if( actorIdx >= 0 && actorIdx < Dalamud.Objects.Length )
{
_animationLoadData = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ) );
_animationLoadData = IdentifyCollection( ( GameObject* )( Dalamud.Objects[ actorIdx ]?.Address ?? IntPtr.Zero ), true );
}
}
@ -202,7 +202,7 @@ public unsafe partial class PathResolver
private void SomeActionLoadDetour( IntPtr gameObject )
{
var last = _animationLoadData;
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject );
_animationLoadData = IdentifyCollection( ( GameObject* )gameObject, true );
_someActionLoadHook.Original( gameObject );
_animationLoadData = last;
}
@ -214,7 +214,7 @@ public unsafe partial class PathResolver
{
var last = _animationLoadData;
var gameObject = ( GameObject* )( unk - 0x8D0 );
_animationLoadData = IdentifyCollection( gameObject );
_animationLoadData = IdentifyCollection( gameObject, true );
_someOtherAvfxHook.Original( unk );
_animationLoadData = last;
}

View file

@ -44,7 +44,7 @@ public unsafe partial class PathResolver
{
if( parentObject == IntPtr.Zero && LastGameObject != null )
{
var collection = IdentifyCollection( LastGameObject );
var collection = IdentifyCollection( LastGameObject, true );
_drawObjectToObject[ drawObject ] = ( collection, LastGameObject->ObjectIndex );
return collection;
}
@ -86,6 +86,7 @@ public unsafe partial class PathResolver
_weaponReloadHook.Enable();
InitializeDrawObjects();
Penumbra.CollectionManager.CollectionChanged += CheckCollections;
Penumbra.TempMods.CollectionChanged += CheckCollections;
}
public void Disable()
@ -95,6 +96,7 @@ public unsafe partial class PathResolver
_enableDrawHook.Disable();
_weaponReloadHook.Disable();
Penumbra.CollectionManager.CollectionChanged -= CheckCollections;
Penumbra.TempMods.CollectionChanged -= CheckCollections;
}
public void Dispose()
@ -139,7 +141,7 @@ public unsafe partial class PathResolver
var meta = DisposableContainer.Empty;
if( LastGameObject != null )
{
_lastCreatedCollection = IdentifyCollection( LastGameObject );
_lastCreatedCollection = IdentifyCollection( LastGameObject, false );
// Change the transparent or 1.0 Decal if necessary.
var decal = new CharacterUtility.DecalReverter( _lastCreatedCollection.ModCollection, UsesDecal( a, c ) );
// Change the rsp parameters if necessary.
@ -235,7 +237,7 @@ public unsafe partial class PathResolver
_drawObjectToObject.Remove( key );
}
var newCollection = IdentifyCollection( obj );
var newCollection = IdentifyCollection( obj, false );
_drawObjectToObject[ key ] = ( newCollection, idx );
}
}
@ -249,7 +251,7 @@ public unsafe partial class PathResolver
var ptr = ( GameObject* )Dalamud.Objects.GetObjectAddress( i );
if( ptr != null && ptr->IsCharacter() && ptr->DrawObject != null )
{
_drawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr ), ptr->ObjectIndex );
_drawObjectToObject[ ( IntPtr )ptr->DrawObject ] = ( IdentifyCollection( ptr, false ), ptr->ObjectIndex );
}
}
}

View file

@ -17,7 +17,7 @@ namespace Penumbra.Interop.Resolver;
public unsafe partial class PathResolver
{
// Identify the correct collection for a GameObject by index and name.
private static ResolveData IdentifyCollection( GameObject* gameObject )
private static ResolveData IdentifyCollection( GameObject* gameObject, bool useCache )
{
if( gameObject == null )
{
@ -26,6 +26,11 @@ public unsafe partial class PathResolver
try
{
if( useCache && IdentifiedCache.TryGetValue( gameObject, out var data ) )
{
return data;
}
// Login screen. Names are populated after actors are drawn,
// so it is not possible to fetch names from the ui list.
// Actors are also not named. So use Yourself > Players > Racial > Default.
@ -34,7 +39,7 @@ public unsafe partial class PathResolver
var collection = Penumbra.CollectionManager.ByType( CollectionType.Yourself )
?? CollectionByAttributes( gameObject )
?? Penumbra.CollectionManager.Default;
return collection.ToResolveData( gameObject );
return IdentifiedCache.Set( collection, ActorIdentifier.Invalid, gameObject );
}
else
{
@ -44,7 +49,7 @@ public unsafe partial class PathResolver
?? CollectionByAttributes( gameObject )
?? CheckOwnedCollection( identifier, gameObject )
?? Penumbra.CollectionManager.Default;
return collection.ToResolveData( gameObject );
return IdentifiedCache.Set( collection, identifier, gameObject );
}
}
catch( Exception e )

View file

@ -3,7 +3,6 @@ using System.Runtime.CompilerServices;
using Dalamud.Hooking;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Manipulations;
namespace Penumbra.Interop.Resolver;

View file

@ -26,14 +26,15 @@ public partial class PathResolver : IDisposable
{
public bool Enabled { get; private set; }
private readonly ResourceLoader _loader;
private static readonly CutsceneCharacters Cutscenes = new();
private static readonly DrawObjectState DrawObjects = new();
private static readonly BitArray ValidHumanModels;
private readonly AnimationState _animations;
private readonly PathState _paths;
private readonly MetaState _meta;
private readonly MaterialState _materials;
private readonly ResourceLoader _loader;
private static readonly CutsceneCharacters Cutscenes = new();
private static readonly DrawObjectState DrawObjects = new();
private static readonly BitArray ValidHumanModels;
internal static readonly IdentifiedCollectionCache IdentifiedCache = new();
private readonly AnimationState _animations;
private readonly PathState _paths;
private readonly MetaState _meta;
private readonly MaterialState _materials;
static PathResolver()
=> ValidHumanModels = GetValidHumanModels( Dalamud.GameData );
@ -87,6 +88,7 @@ public partial class PathResolver : IDisposable
Enabled = true;
Cutscenes.Enable();
DrawObjects.Enable();
IdentifiedCache.Enable();
_animations.Enable();
_paths.Enable();
_meta.Enable();
@ -107,6 +109,7 @@ public partial class PathResolver : IDisposable
_animations.Disable();
DrawObjects.Disable();
Cutscenes.Disable();
IdentifiedCache.Disable();
_paths.Disable();
_meta.Disable();
_materials.Disable();
@ -122,6 +125,7 @@ public partial class PathResolver : IDisposable
_animations.Dispose();
DrawObjects.Dispose();
Cutscenes.Dispose();
IdentifiedCache.Dispose();
_meta.Dispose();
_materials.Dispose();
}
@ -147,11 +151,11 @@ public partial class PathResolver : IDisposable
if( DrawObjects.LastGameObject != null
&& ( DrawObjects.LastGameObject->DrawObject == null || DrawObjects.LastGameObject->DrawObject == ( DrawObject* )drawObject ) )
{
resolveData = IdentifyCollection( DrawObjects.LastGameObject );
resolveData = IdentifyCollection( DrawObjects.LastGameObject, true );
return DrawObjects.LastGameObject;
}
resolveData = IdentifyCollection( null );
resolveData = IdentifyCollection( null, true );
return null;
}

View file

@ -14,7 +14,6 @@ using System.IO;
using System.Linq;
using System.Numerics;
using Penumbra.Api.Enums;
using Penumbra.GameData.Actors;
namespace Penumbra.UI.Classes;

View file

@ -12,6 +12,7 @@ using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Files;
using Penumbra.Interop.Loader;
using Penumbra.Interop.Resolver;
using Penumbra.Interop.Structs;
using Penumbra.String;
using CharacterUtility = Penumbra.Interop.CharacterUtility;
@ -250,6 +251,23 @@ public partial class ConfigWindow
}
}
using( var identifiedTree = ImRaii.TreeNode( "Identified Collections" ) )
{
if( identifiedTree )
{
using var table = ImRaii.Table( "##PathCollectionsIdentifiedTable", 3, ImGuiTableFlags.SizingFixedFit );
if( table )
{
foreach( var (address, identifier, collection) in PathResolver.IdentifiedCache )
{
ImGuiUtil.DrawTableColumn( $"0x{address:X}" );
ImGuiUtil.DrawTableColumn( identifier.ToString() );
ImGuiUtil.DrawTableColumn( collection.Name );
}
}
}
}
using var cutsceneTree = ImRaii.TreeNode( "Cutscene Actors" );
if( cutsceneTree )
{