mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add some more safety and better IPC for draw object storage.
This commit is contained in:
parent
33ada1d994
commit
129156a1c1
8 changed files with 89 additions and 45 deletions
|
|
@ -1 +1 @@
|
|||
Subproject commit bd56d82816b8366e19dddfb2dc7fd7f167e264ee
|
||||
Subproject commit 47bd5424d04c667d0df1ac1dd1eeb3e50b476c2c
|
||||
|
|
@ -14,16 +14,18 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
|
|||
{
|
||||
private readonly CommunicatorService _communicator;
|
||||
private readonly CollectionResolver _collectionResolver;
|
||||
private readonly DrawObjectState _drawObjectState;
|
||||
private readonly CutsceneService _cutsceneService;
|
||||
private readonly ResourceLoader _resourceLoader;
|
||||
|
||||
public unsafe GameStateApi(CommunicatorService communicator, CollectionResolver collectionResolver, CutsceneService cutsceneService,
|
||||
ResourceLoader resourceLoader)
|
||||
ResourceLoader resourceLoader, DrawObjectState drawObjectState)
|
||||
{
|
||||
_communicator = communicator;
|
||||
_collectionResolver = collectionResolver;
|
||||
_cutsceneService = cutsceneService;
|
||||
_resourceLoader = resourceLoader;
|
||||
_drawObjectState = drawObjectState;
|
||||
_resourceLoader.ResourceLoaded += OnResourceLoaded;
|
||||
_resourceLoader.PapRequested += OnPapRequested;
|
||||
_communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
|
||||
|
|
@ -67,6 +69,30 @@ public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
|
|||
public int GetCutsceneParentIndex(int actorIdx)
|
||||
=> _cutsceneService.GetParentIndex(actorIdx);
|
||||
|
||||
public Func<int, int> GetCutsceneParentIndexFunc()
|
||||
{
|
||||
var weakRef = new WeakReference<CutsceneService>(_cutsceneService);
|
||||
return idx =>
|
||||
{
|
||||
if (!weakRef.TryGetTarget(out var c))
|
||||
throw new ObjectDisposedException("The underlying cutscene state storage of this IPC container was disposed.");
|
||||
|
||||
return c.GetParentIndex(idx);
|
||||
};
|
||||
}
|
||||
|
||||
public Func<nint, nint> GetGameObjectFromDrawObjectFunc()
|
||||
{
|
||||
var weakRef = new WeakReference<DrawObjectState>(_drawObjectState);
|
||||
return model =>
|
||||
{
|
||||
if (!weakRef.TryGetTarget(out var c))
|
||||
throw new ObjectDisposedException("The underlying draw object state storage of this IPC container was disposed.");
|
||||
|
||||
return c.TryGetValue(model, out var data) ? data.Item1.Address : nint.Zero;
|
||||
};
|
||||
}
|
||||
|
||||
public PenumbraApiEc SetCutsceneParentIndex(int copyIdx, int newParentIdx)
|
||||
=> _cutsceneService.SetParentIndex(copyIdx, newParentIdx)
|
||||
? PenumbraApiEc.Success
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public class PenumbraApi(
|
|||
UiApi ui) : IDisposable, IApiService, IPenumbraApi
|
||||
{
|
||||
public const int BreakingVersion = 5;
|
||||
public const int FeatureVersion = 8;
|
||||
public const int FeatureVersion = 9;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,6 +40,8 @@ public sealed class IpcProviders : IDisposable, IApiService
|
|||
IpcSubscribers.CreatingCharacterBase.Provider(pi, api.GameState),
|
||||
IpcSubscribers.CreatedCharacterBase.Provider(pi, api.GameState),
|
||||
IpcSubscribers.GameObjectResourcePathResolved.Provider(pi, api.GameState),
|
||||
IpcSubscribers.GetCutsceneParentIndexFunc.Provider(pi, api.GameState),
|
||||
IpcSubscribers.GetGameObjectFromDrawObjectFunc.Provider(pi, api.GameState),
|
||||
|
||||
IpcSubscribers.GetPlayerMetaManipulations.Provider(pi, api.Meta),
|
||||
IpcSubscribers.GetMetaManipulations.Provider(pi, api.Meta),
|
||||
|
|
|
|||
|
|
@ -89,10 +89,13 @@ public sealed unsafe class CollectionResolver(
|
|||
/// <summary> Identify the correct collection for a draw object. </summary>
|
||||
public ResolveData IdentifyCollection(DrawObject* drawObject, bool useCache)
|
||||
{
|
||||
var obj = (GameObject*)(drawObjectState.TryGetValue((nint)drawObject, out var gameObject)
|
||||
if (drawObject is null)
|
||||
return DefaultCollection;
|
||||
|
||||
Actor obj = drawObjectState.TryGetValue(drawObject, out var gameObject)
|
||||
? gameObject.Item1
|
||||
: drawObjectState.LastGameObject);
|
||||
return IdentifyCollection(obj, useCache);
|
||||
: drawObjectState.LastGameObject;
|
||||
return IdentifyCollection(obj.AsObject, useCache);
|
||||
}
|
||||
|
||||
/// <summary> Get the default collection. </summary>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ using Penumbra.Interop.Hooks.Objects;
|
|||
|
||||
namespace Penumbra.Interop.PathResolving;
|
||||
|
||||
public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (nint, bool)>, IService
|
||||
public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<Model, (Actor, ObjectIndex, bool)>, IService
|
||||
{
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly CreateCharacterBase _createCharacterBase;
|
||||
|
|
@ -18,7 +18,7 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
private readonly CharacterDestructor _characterDestructor;
|
||||
private readonly GameState _gameState;
|
||||
|
||||
private readonly Dictionary<nint, (nint GameObject, bool IsChild)> _drawObjectToGameObject = [];
|
||||
private readonly Dictionary<Model, (Actor GameObject, ObjectIndex Index, bool IsChild)> _drawObjectToGameObject = [];
|
||||
|
||||
public nint LastGameObject
|
||||
=> _gameState.LastGameObject;
|
||||
|
|
@ -41,11 +41,10 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
_characterDestructor.Subscribe(OnCharacterDestructor, CharacterDestructor.Priority.DrawObjectState);
|
||||
}
|
||||
|
||||
|
||||
public bool ContainsKey(nint key)
|
||||
public bool ContainsKey(Model key)
|
||||
=> _drawObjectToGameObject.ContainsKey(key);
|
||||
|
||||
public IEnumerator<KeyValuePair<nint, (nint, bool)>> GetEnumerator()
|
||||
public IEnumerator<KeyValuePair<Model, (Actor, ObjectIndex, bool)>> GetEnumerator()
|
||||
=> _drawObjectToGameObject.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
|
|
@ -54,16 +53,28 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
public int Count
|
||||
=> _drawObjectToGameObject.Count;
|
||||
|
||||
public bool TryGetValue(nint drawObject, out (nint, bool) gameObject)
|
||||
=> _drawObjectToGameObject.TryGetValue(drawObject, out gameObject);
|
||||
public bool TryGetValue(Model drawObject, out (Actor, ObjectIndex, bool) gameObject)
|
||||
{
|
||||
if (!_drawObjectToGameObject.TryGetValue(drawObject, out gameObject))
|
||||
return false;
|
||||
|
||||
public (nint, bool) this[nint key]
|
||||
var currentObject = _objects[gameObject.Item2];
|
||||
if (currentObject != gameObject.Item1)
|
||||
{
|
||||
Penumbra.Log.Warning($"[DrawObjectState] Stored association {drawObject} -> {gameObject.Item1} has index {gameObject.Item2}, which resolves to {currentObject}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public (Actor, ObjectIndex, bool) this[Model key]
|
||||
=> _drawObjectToGameObject[key];
|
||||
|
||||
public IEnumerable<nint> Keys
|
||||
public IEnumerable<Model> Keys
|
||||
=> _drawObjectToGameObject.Keys;
|
||||
|
||||
public IEnumerable<(nint, bool)> Values
|
||||
public IEnumerable<(Actor, ObjectIndex, bool)> Values
|
||||
=> _drawObjectToGameObject.Values;
|
||||
|
||||
public unsafe void Dispose()
|
||||
|
|
@ -87,20 +98,21 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
var character = (nint)a;
|
||||
var delete = stackalloc nint[5];
|
||||
var current = 0;
|
||||
foreach (var (drawObject, (gameObject, _)) in _drawObjectToGameObject)
|
||||
foreach (var (drawObject, (gameObject, _, _)) in _drawObjectToGameObject)
|
||||
{
|
||||
if (gameObject != character)
|
||||
continue;
|
||||
|
||||
|
||||
delete[current++] = drawObject;
|
||||
if (current is 4)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
for (var ptr = delete; *ptr != nint.Zero; ++ptr)
|
||||
{
|
||||
_drawObjectToGameObject.Remove(*ptr, out var pair);
|
||||
Penumbra.Log.Excessive($"[DrawObjectState] Removed draw object 0x{*ptr:X} -> 0x{(nint)a:X} (actual: 0x{pair.GameObject:X}, {pair.IsChild}).");
|
||||
Penumbra.Log.Excessive(
|
||||
$"[DrawObjectState] Removed draw object 0x{*ptr:X} -> 0x{(nint)a:X} (actual: 0x{pair.GameObject.Address:X}, {pair.IsChild}).");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -119,9 +131,9 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
private unsafe void OnCharacterBaseCreated(ModelCharaId modelCharaId, CustomizeArray* customize, CharacterArmor* equipment,
|
||||
CharacterBase* drawObject)
|
||||
{
|
||||
var gameObject = LastGameObject;
|
||||
if (gameObject != nint.Zero)
|
||||
_drawObjectToGameObject[(nint)drawObject] = (gameObject, false);
|
||||
Actor gameObject = LastGameObject;
|
||||
if (gameObject.Valid)
|
||||
_drawObjectToGameObject[(nint)drawObject] = (gameObject, gameObject.Index, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -137,12 +149,12 @@ public sealed class DrawObjectState : IDisposable, IReadOnlyDictionary<nint, (ni
|
|||
}
|
||||
}
|
||||
|
||||
private unsafe void IterateDrawObjectTree(Object* drawObject, nint gameObject, bool isChild, bool iterate)
|
||||
private unsafe void IterateDrawObjectTree(Object* drawObject, Actor gameObject, bool isChild, bool iterate)
|
||||
{
|
||||
if (drawObject == null)
|
||||
return;
|
||||
|
||||
_drawObjectToGameObject[(nint)drawObject] = (gameObject, isChild);
|
||||
_drawObjectToGameObject[drawObject] = (gameObject, gameObject.Index, isChild);
|
||||
IterateDrawObjectTree(drawObject->ChildObject, gameObject, true, true);
|
||||
if (!iterate)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManage
|
|||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.Hooks.PostProcessing;
|
||||
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||
|
|
@ -190,7 +189,7 @@ public class Penumbra : IDalamudPlugin
|
|||
ReadOnlySpan<string> relevantPlugins =
|
||||
[
|
||||
"Glamourer", "MareSynchronos", "CustomizePlus", "SimpleHeels", "VfxEditor", "heliosphere-plugin", "Ktisis", "Brio", "DynamicBridge",
|
||||
"IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn",
|
||||
"IllusioVitae", "Aetherment", "LoporritSync", "GagSpeak", "ProjectGagSpeak", "RoleplayingVoiceDalamud", "AQuestReborn",
|
||||
];
|
||||
var plugins = _services.GetService<IDalamudPluginInterface>().InstalledPlugins
|
||||
.GroupBy(p => p.InternalName)
|
||||
|
|
|
|||
|
|
@ -569,29 +569,31 @@ public class DebugTab : Window, ITab, IUiService
|
|||
{
|
||||
if (drawTree)
|
||||
{
|
||||
using var table = Table("###DrawObjectResolverTable", 6, ImGuiTableFlags.SizingFixedFit);
|
||||
using var table = Table("###DrawObjectResolverTable", 8, ImGuiTableFlags.SizingFixedFit);
|
||||
if (table)
|
||||
foreach (var (drawObject, (gameObjectPtr, child)) in _drawObjectState
|
||||
.OrderBy(kvp => ((GameObject*)kvp.Value.Item1)->ObjectIndex)
|
||||
.ThenBy(kvp => kvp.Value.Item2)
|
||||
.ThenBy(kvp => kvp.Key))
|
||||
foreach (var (drawObject, (gameObjectPtr, idx, child)) in _drawObjectState
|
||||
.OrderBy(kvp => kvp.Value.Item2.Index)
|
||||
.ThenBy(kvp => kvp.Value.Item3)
|
||||
.ThenBy(kvp => kvp.Key.Address))
|
||||
{
|
||||
var gameObject = (GameObject*)gameObjectPtr;
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable($"{drawObject}");
|
||||
ImUtf8.DrawTableColumn($"{gameObjectPtr.Index}");
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF, gameObjectPtr.Index != idx))
|
||||
{
|
||||
ImUtf8.DrawTableColumn($"{idx}");
|
||||
}
|
||||
|
||||
ImGuiUtil.CopyOnClickSelectable($"0x{drawObject:X}");
|
||||
ImUtf8.DrawTableColumn(child ? "Child"u8 : "Main"u8);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(gameObject->ObjectIndex.ToString());
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(child ? "Child" : "Main");
|
||||
ImGui.TableNextColumn();
|
||||
var (address, name) = ($"0x{gameObjectPtr:X}", new ByteString(gameObject->Name).ToString());
|
||||
ImGuiUtil.CopyOnClickSelectable(address);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(name);
|
||||
ImGui.TableNextColumn();
|
||||
var collection = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||
ImGui.TextUnformatted(collection.ModCollection.Identity.Name);
|
||||
ImUtf8.CopyOnClickSelectable($"{gameObjectPtr}");
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, 0xFF0000FF, _objects[idx] != gameObjectPtr))
|
||||
{
|
||||
ImUtf8.DrawTableColumn($"{_objects[idx]}");
|
||||
}
|
||||
ImUtf8.DrawTableColumn(gameObjectPtr.Utf8Name.Span);
|
||||
var collection = _collectionResolver.IdentifyCollection(gameObjectPtr.AsObject, true);
|
||||
ImUtf8.DrawTableColumn(collection.ModCollection.Identity.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue