From 11bf0d29987d2144c12aa740b9e75c5c6f69a24c Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Thu, 21 Sep 2023 02:05:52 +0200 Subject: [PATCH] Optimize ResourceTree somewhat. --- Penumbra.GameData | 2 +- Penumbra/Api/IpcTester.cs | 9 +- .../Interop/MaterialPreview/MaterialInfo.cs | 7 +- .../ResourceTree/ResourceTreeFactory.cs | 46 ++++---- .../Interop/ResourceTree/TreeBuildCache.cs | 107 +++++++++++++----- 5 files changed, 108 insertions(+), 63 deletions(-) diff --git a/Penumbra.GameData b/Penumbra.GameData index 7c483764..ef403be9 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit 7c483764678c6edb5efd55f056aeaecae144d5fe +Subproject commit ef403be979bfac5ef805030ce76066151d36f112 diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs index de9ab5a7..7766b5af 100644 --- a/Penumbra/Api/IpcTester.cs +++ b/Penumbra/Api/IpcTester.cs @@ -18,6 +18,7 @@ using Penumbra.Collections.Manager; using Dalamud.Plugin.Services; using Penumbra.GameData.Enums; using System.Diagnostics; +using Penumbra.GameData.Structs; namespace Penumbra.Api; @@ -1450,7 +1451,7 @@ public class IpcTester : IDisposable _lastCallDuration = _stopwatch.Elapsed; _lastGameObjectResourcePaths = gameObjects - .Select(GameObjectToString) + .Select(i => GameObjectToString(i)) .Zip(resourcePaths) .ToArray(); @@ -1482,7 +1483,7 @@ public class IpcTester : IDisposable _lastCallDuration = _stopwatch.Elapsed; _lastGameObjectResourcesOfType = gameObjects - .Select(GameObjectToString) + .Select(i => GameObjectToString(i)) .Zip(resourcesOfType) .ToArray(); @@ -1630,9 +1631,9 @@ public class IpcTester : IDisposable .SelectWhere(index => (ushort.TryParse(index.Trim(), out var i), i)) .ToArray(); - private unsafe string GameObjectToString(ushort gameObjectIndex) + private unsafe string GameObjectToString(ObjectIndex gameObjectIndex) { - var gameObject = _objects[gameObjectIndex]; + var gameObject = _objects[gameObjectIndex.Index]; return gameObject != null ? $"[{gameObjectIndex}] {gameObject.Name} ({gameObject.ObjectKind})" diff --git a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs index 26e809f9..7dd6f983 100644 --- a/Penumbra/Interop/MaterialPreview/MaterialInfo.cs +++ b/Penumbra/Interop/MaterialPreview/MaterialInfo.cs @@ -2,6 +2,7 @@ using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; +using Penumbra.GameData.Structs; using Penumbra.Interop.ResourceTree; using Penumbra.String; @@ -15,10 +16,10 @@ public enum DrawObjectType Vfx, }; -public readonly record struct MaterialInfo(ushort ObjectIndex, DrawObjectType Type, int ModelSlot, int MaterialSlot) +public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectType Type, int ModelSlot, int MaterialSlot) { public nint GetCharacter(IObjectTable objects) - => objects.GetObjectAddress(ObjectIndex); + => objects.GetObjectAddress(ObjectIndex.Index); public nint GetDrawObject(nint address) => GetDrawObject(Type, address); @@ -71,7 +72,7 @@ public readonly record struct MaterialInfo(ushort ObjectIndex, DrawObjectType Ty if (gameObject == null) continue; - var index = gameObject->GameObject.ObjectIndex; + var index = (ObjectIndex) gameObject->GameObject.ObjectIndex; foreach (var type in Enum.GetValues()) { diff --git a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs index bd0138c4..6353d5b5 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTreeFactory.cs @@ -30,21 +30,20 @@ public class ResourceTreeFactory _actors = actors; } - private TreeBuildCache CreateTreeBuildCache(bool withCharacters) - => new(_objects, _gameData, _actors, withCharacters); + private TreeBuildCache CreateTreeBuildCache() + => new(_objects, _gameData, _actors); public IEnumerable GetLocalPlayerRelatedCharacters() { - var cache = CreateTreeBuildCache(true); - - return cache.Characters.Where(cache.IsLocalPlayerRelated); + var cache = CreateTreeBuildCache(); + return cache.GetLocalPlayerRelatedCharacters(); } public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromObjectTable( Flags flags) { - var cache = CreateTreeBuildCache(true); - var characters = (flags & Flags.LocalPlayerRelatedOnly) != 0 ? cache.Characters.Where(cache.IsLocalPlayerRelated) : cache.Characters; + var cache = CreateTreeBuildCache(); + var characters = (flags & Flags.LocalPlayerRelatedOnly) != 0 ? cache.GetLocalPlayerRelatedCharacters() : cache.GetCharacters(); foreach (var character in characters) { @@ -57,7 +56,7 @@ public class ResourceTreeFactory public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters( IEnumerable characters, Flags flags) { - var cache = CreateTreeBuildCache((flags & Flags.WithOwnership) != 0); + var cache = CreateTreeBuildCache(); foreach (var character in characters) { var tree = FromCharacter(character, cache, flags); @@ -67,7 +66,7 @@ public class ResourceTreeFactory } public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, Flags flags) - => FromCharacter(character, CreateTreeBuildCache((flags & Flags.WithOwnership) != 0), flags); + => FromCharacter(character, CreateTreeBuildCache(), flags); private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache, Flags flags) { @@ -136,7 +135,7 @@ public class ResourceTreeFactory if (filteredList.Count > 0) resolvedList = filteredList; } - + if (resolvedList.Count != 1) { Penumbra.Log.Debug( @@ -216,27 +215,22 @@ public class ResourceTreeFactory private unsafe (string Name, bool PlayerRelated) GetCharacterName(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache) { - var identifier = _actors.AwaitedService.FromObject((GameObject*)character.Address, out var owner, true, false, false); - string name; - bool playerRelated; + var identifier = _actors.AwaitedService.FromObject((GameObject*)character.Address, out var owner, true, false, false); switch (identifier.Type) { - case IdentifierType.Player: - name = identifier.PlayerName.ToString(); - playerRelated = true; - break; - case IdentifierType.Owned when cache.CharactersById.TryGetValue(owner->ObjectID, out var ownerChara): - var ownerName = GetCharacterName(ownerChara, cache); - name = $"[{ownerName.Name}] {character.Name} ({identifier.Kind.ToName()})"; - playerRelated = ownerName.PlayerRelated; - break; - default: - name = $"{character.Name} ({identifier.Kind.ToName()})"; - playerRelated = false; + case IdentifierType.Player: return (identifier.PlayerName.ToString(), true); + case IdentifierType.Owned: + var ownerChara = _objects.CreateObjectReference((nint)owner) as Dalamud.Game.ClientState.Objects.Types.Character; + if (ownerChara != null) + { + var ownerName = GetCharacterName(ownerChara, cache); + return ($"[{ownerName.Name}] {character.Name} ({identifier.Kind.ToName()})", ownerName.PlayerRelated); + } + break; } - return (name, playerRelated); + return ($"{character.Name} ({identifier.Kind.ToName()})", false); } [Flags] diff --git a/Penumbra/Interop/ResourceTree/TreeBuildCache.cs b/Penumbra/Interop/ResourceTree/TreeBuildCache.cs index d889cf5d..5f724b14 100644 --- a/Penumbra/Interop/ResourceTree/TreeBuildCache.cs +++ b/Penumbra/Interop/ResourceTree/TreeBuildCache.cs @@ -1,55 +1,104 @@ +using System.Diagnostics.CodeAnalysis; using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; +using Penumbra.GameData.Actors; using Penumbra.GameData.Files; -using Penumbra.Interop.Services; +using Penumbra.GameData.Structs; using Penumbra.Services; +using Penumbra.String; using Penumbra.String.Classes; namespace Penumbra.Interop.ResourceTree; -internal class TreeBuildCache +internal readonly struct TreeBuildCache { private readonly IDataManager _dataManager; private readonly ActorService _actors; private readonly Dictionary _shaderPackages = new(); - private readonly uint _localPlayerId; - public readonly List Characters; - public readonly Dictionary CharactersById; + private readonly IObjectTable _objects; - public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors, bool withCharacters) + public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors) { - _dataManager = dataManager; - _actors = actors; - _localPlayerId = objects[0]?.ObjectId ?? GameObject.InvalidGameObjectId; - if (withCharacters) - { - Characters = objects.OfType().Where(ch => ch.IsValid()).ToList(); - CharactersById = Characters - .Where(c => c.ObjectId != GameObject.InvalidGameObjectId) - .GroupBy(c => c.ObjectId) - .ToDictionary(c => c.Key, c => c.First()); - } - else - { - Characters = new(); - CharactersById = new(); - } + _dataManager = dataManager; + _objects = objects; + _actors = actors; } public unsafe bool IsLocalPlayerRelated(Character character) { - if (_localPlayerId == GameObject.InvalidGameObjectId) + var player = _objects[0]; + if (player == null) return false; - // Index 0 is the local player, index 1 is the mount/minion/accessory. - if (character.ObjectIndex < 2 || character.ObjectIndex == RedrawService.GPosePlayerIdx) - return true; + var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)character.Address; + var parent = _actors.AwaitedService.ToCutsceneParent(gameObject->ObjectIndex); + var actualIndex = parent >= 0 ? (ushort)parent : gameObject->ObjectIndex; + return actualIndex switch + { + < 2 => true, + < (int)ScreenActor.CutsceneStart => gameObject->OwnerID == player.ObjectId, + _ => false, + }; + } - if (!_actors.AwaitedService.FromObject(character, out var owner, true, false, false).IsValid) + public IEnumerable GetCharacters() + => _objects.OfType(); + + public IEnumerable GetLocalPlayerRelatedCharacters() + { + var player = _objects[0]; + if (player == null) + yield break; + + yield return (Character)player; + + var minion = _objects[1]; + if (minion != null) + yield return (Character)minion; + + var playerId = player.ObjectId; + for (var i = 2; i < ObjectIndex.CutsceneStart.Index; i += 2) + { + if (_objects[i] is Character owned && owned.OwnerId == playerId) + yield return owned; + } + + for (var i = ObjectIndex.CutsceneStart.Index; i < ObjectIndex.CharacterScreen.Index; ++i) + { + var character = _objects[i] as Character; + if (character == null) + continue; + + var parent = _actors.AwaitedService.ToCutsceneParent(i); + if (parent < 0) + continue; + + if (parent is 0 or 1 || _objects[parent]?.OwnerId == playerId) + yield return character; + } + } + + private unsafe ByteString GetPlayerName(GameObject player) + { + var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)player.Address; + return new ByteString(gameObject->Name); + } + + private unsafe bool GetOwnedId(ByteString playerName, uint playerId, int idx, [NotNullWhen(true)] out Character? character) + { + character = _objects[idx] as Character; + if (character == null) return false; - // Check for SMN/SCH pet, chocobo and other owned NPCs. - return owner != null && owner->ObjectID == _localPlayerId; + var actorId = _actors.AwaitedService.FromObject(character, out var owner, true, true, true); + if (!actorId.IsValid) + return false; + if (owner != null && owner->OwnerID != playerId) + return false; + if (actorId.Type is not IdentifierType.Player || !actorId.PlayerName.Equals(playerName)) + return false; + + return true; } /// Try to read a shpk file from the given path and cache it on success.