ResourceTree: Avoid enumerating the whole object table in some cases

This commit is contained in:
Exter-N 2023-09-18 12:35:41 +02:00 committed by Ottermandias
parent 22966e648d
commit a241b933ca
4 changed files with 48 additions and 29 deletions

View file

@ -1019,7 +1019,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects) public IReadOnlyDictionary<string, string[]>?[] GetGameObjectResourcePaths(ushort[] gameObjects)
{ {
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>(); var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, false, false); var resourceTrees = _resourceTreeFactory.FromCharacters(characters, 0);
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees); var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
return Array.ConvertAll(gameObjects, obj => pathDictionaries.TryGetValue(obj, out var pathDict) ? pathDict : null); return Array.ConvertAll(gameObjects, obj => pathDictionaries.TryGetValue(obj, out var pathDict) ? pathDict : null);
@ -1027,7 +1027,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>> GetPlayerResourcePaths() public IReadOnlyDictionary<ushort, IReadOnlyDictionary<string, string[]>> GetPlayerResourcePaths()
{ {
var resourceTrees = _resourceTreeFactory.FromObjectTable(true, false, false); var resourceTrees = _resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly);
var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees); var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
return pathDictionaries.AsReadOnly(); return pathDictionaries.AsReadOnly();
@ -1036,7 +1036,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ushort[] gameObjects, ResourceType type, bool withUIData) public IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>?[] GetGameObjectResourcesOfType(ushort[] gameObjects, ResourceType type, bool withUIData)
{ {
var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>(); var characters = gameObjects.Select(index => _dalamud.Objects[index]).OfType<Character>();
var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData, false); var resourceTrees = _resourceTreeFactory.FromCharacters(characters, withUIData ? ResourceTreeFactory.Flags.WithUIData : 0);
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type); var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
return Array.ConvertAll(gameObjects, obj => resDictionaries.TryGetValue(obj, out var resDict) ? resDict : null); return Array.ConvertAll(gameObjects, obj => resDictionaries.TryGetValue(obj, out var resDict) ? resDict : null);
@ -1044,7 +1044,7 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetPlayerResourcesOfType(ResourceType type, bool withUIData) public IReadOnlyDictionary<ushort, IReadOnlyDictionary<nint, (string, string, ChangedItemIcon)>> GetPlayerResourcesOfType(ResourceType type, bool withUIData)
{ {
var resourceTrees = _resourceTreeFactory.FromObjectTable(true, withUIData, false); var resourceTrees = _resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly | (withUIData ? ResourceTreeFactory.Flags.WithUIData : 0));
var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type); var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
return resDictionaries.AsReadOnly(); return resDictionaries.AsReadOnly();

View file

@ -27,49 +27,46 @@ public class ResourceTreeFactory
_actors = actors; _actors = actors;
} }
private TreeBuildCache CreateTreeBuildCache() private TreeBuildCache CreateTreeBuildCache(bool withCharacters)
=> new(_objects, _gameData, _actors); => new(_objects, _gameData, _actors, withCharacters);
public IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> GetLocalPlayerRelatedCharacters() public IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> GetLocalPlayerRelatedCharacters()
{ {
var cache = CreateTreeBuildCache(); var cache = CreateTreeBuildCache(true);
return cache.Characters.Where(cache.IsLocalPlayerRelated); return cache.Characters.Where(cache.IsLocalPlayerRelated);
} }
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromObjectTable( public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromObjectTable(
bool localPlayerRelatedOnly = false, bool withUIData = true, bool redactExternalPaths = true) Flags flags)
{ {
var cache = CreateTreeBuildCache(); var cache = CreateTreeBuildCache(true);
var characters = localPlayerRelatedOnly ? cache.Characters.Where(cache.IsLocalPlayerRelated) : cache.Characters; var characters = (flags & Flags.LocalPlayerRelatedOnly) != 0 ? cache.Characters.Where(cache.IsLocalPlayerRelated) : cache.Characters;
foreach (var character in characters) foreach (var character in characters)
{ {
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths); var tree = FromCharacter(character, cache, flags);
if (tree != null) if (tree != null)
yield return (character, tree); yield return (character, tree);
} }
} }
public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters( public IEnumerable<(Dalamud.Game.ClientState.Objects.Types.Character Character, ResourceTree ResourceTree)> FromCharacters(
IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters, IEnumerable<Dalamud.Game.ClientState.Objects.Types.Character> characters, Flags flags)
bool withUIData = true, bool redactExternalPaths = true)
{ {
var cache = CreateTreeBuildCache(); var cache = CreateTreeBuildCache((flags & Flags.WithOwnership) != 0);
foreach (var character in characters) foreach (var character in characters)
{ {
var tree = FromCharacter(character, cache, withUIData, redactExternalPaths); var tree = FromCharacter(character, cache, flags);
if (tree != null) if (tree != null)
yield return (character, tree); yield return (character, tree);
} }
} }
public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, bool withUIData = true, public ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, Flags flags)
bool redactExternalPaths = true) => FromCharacter(character, CreateTreeBuildCache((flags & Flags.WithOwnership) != 0), flags);
=> FromCharacter(character, CreateTreeBuildCache(), withUIData, redactExternalPaths);
private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache, private unsafe ResourceTree? FromCharacter(Dalamud.Game.ClientState.Objects.Types.Character character, TreeBuildCache cache, Flags flags)
bool withUIData = true, bool redactExternalPaths = true)
{ {
if (!character.IsValid()) if (!character.IsValid())
return null; return null;
@ -88,7 +85,7 @@ public class ResourceTreeFactory
var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId; var networked = character.ObjectId != Dalamud.Game.ClientState.Objects.Types.GameObject.InvalidGameObjectId;
var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related, networked, collectionResolveData.ModCollection.Name); var tree = new ResourceTree(name, character.ObjectIndex, (nint)gameObjStruct, (nint)drawObjStruct, localPlayerRelated, related, networked, collectionResolveData.ModCollection.Name);
var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection, var globalContext = new GlobalResolveContext(_config, _identifier.AwaitedService, cache, collectionResolveData.ModCollection,
((Character*)gameObjStruct)->CharacterData.ModelCharaId, withUIData, redactExternalPaths); ((Character*)gameObjStruct)->CharacterData.ModelCharaId, (flags & Flags.WithUIData) != 0, (flags & Flags.RedactExternalPaths) != 0);
tree.LoadResources(globalContext); tree.LoadResources(globalContext);
tree.FlatNodes.UnionWith(globalContext.Nodes.Values); tree.FlatNodes.UnionWith(globalContext.Nodes.Values);
return tree; return tree;
@ -119,4 +116,13 @@ public class ResourceTreeFactory
return (name, playerRelated); return (name, playerRelated);
} }
[Flags]
public enum Flags
{
RedactExternalPaths = 1,
WithUIData = 2,
LocalPlayerRelatedOnly = 4,
WithOwnership = 8,
}
} }

View file

@ -16,16 +16,24 @@ internal class TreeBuildCache
public readonly List<Character> Characters; public readonly List<Character> Characters;
public readonly Dictionary<uint, Character> CharactersById; public readonly Dictionary<uint, Character> CharactersById;
public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors) public TreeBuildCache(IObjectTable objects, IDataManager dataManager, ActorService actors, bool withCharacters)
{ {
_dataManager = dataManager; _dataManager = dataManager;
_actors = actors; _actors = actors;
Characters = objects.OfType<Character>().Where(ch => ch.IsValid()).ToList(); _localPlayerId = objects[0]?.ObjectId ?? GameObject.InvalidGameObjectId;
CharactersById = Characters if (withCharacters)
.Where(c => c.ObjectId != GameObject.InvalidGameObjectId) {
.GroupBy(c => c.ObjectId) Characters = objects.OfType<Character>().Where(ch => ch.IsValid()).ToList();
.ToDictionary(c => c.Key, c => c.First()); CharactersById = Characters
_localPlayerId = Characters.Count > 0 && Characters[0].ObjectIndex == 0 ? Characters[0].ObjectId : GameObject.InvalidGameObjectId; .Where(c => c.ObjectId != GameObject.InvalidGameObjectId)
.GroupBy(c => c.ObjectId)
.ToDictionary(c => c.Key, c => c.First());
}
else
{
Characters = new();
CharactersById = new();
}
} }
public unsafe bool IsLocalPlayerRelated(Character character) public unsafe bool IsLocalPlayerRelated(Character character)

View file

@ -9,6 +9,11 @@ namespace Penumbra.UI.AdvancedWindow;
public class ResourceTreeViewer public class ResourceTreeViewer
{ {
private const ResourceTreeFactory.Flags ResourceTreeFactoryFlags =
ResourceTreeFactory.Flags.RedactExternalPaths |
ResourceTreeFactory.Flags.WithUIData |
ResourceTreeFactory.Flags.WithOwnership;
private readonly Configuration _config; private readonly Configuration _config;
private readonly ResourceTreeFactory _treeFactory; private readonly ResourceTreeFactory _treeFactory;
private readonly ChangedItemDrawer _changedItemDrawer; private readonly ChangedItemDrawer _changedItemDrawer;
@ -101,7 +106,7 @@ public class ResourceTreeViewer
{ {
try try
{ {
return _treeFactory.FromObjectTable() return _treeFactory.FromObjectTable(ResourceTreeFactoryFlags)
.Select(entry => entry.ResourceTree) .Select(entry => entry.ResourceTree)
.ToArray(); .ToArray();
} }