From 7b3e2e34aed027a92095f0c2d934b8e17d287812 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Fri, 10 Mar 2023 22:46:18 +0100 Subject: [PATCH] Fix and improve DataShare somewhat and add a visualization of current shares to the DataWindow. --- .../Interface/Internal/Windows/DataWindow.cs | 33 ++++ Dalamud/Plugin/Ipc/Internal/DataShare.cs | 173 +++++++++++++----- 2 files changed, 159 insertions(+), 47 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/DataWindow.cs b/Dalamud/Interface/Internal/Windows/DataWindow.cs index 0bd96ac19..2c328d7f1 100644 --- a/Dalamud/Interface/Internal/Windows/DataWindow.cs +++ b/Dalamud/Interface/Internal/Windows/DataWindow.cs @@ -182,6 +182,7 @@ internal class DataWindow : Window Aetherytes, Dtr_Bar, UIColor, + DataShare, } /// @@ -378,6 +379,9 @@ internal class DataWindow : Window case DataKind.UIColor: this.DrawUIColor(); break; + case DataKind.DataShare: + this.DrawDataShareTab(); + break; } } else @@ -1765,6 +1769,35 @@ internal class DataWindow : Window } } + private void DrawDataShareTab() + { + if (!ImGui.BeginTable("###DataShareTable", 4, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg)) + return; + + try + { + ImGui.TableSetupColumn("Shared Tag"); + ImGui.TableSetupColumn("Creator Assembly"); + ImGui.TableSetupColumn("#", ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); + ImGui.TableSetupColumn("Consumers"); + ImGui.TableHeadersRow(); + foreach (var share in Service.Get().GetAllShares()) + { + ImGui.TableNextColumn(); + ImGui.TextUnformatted(share.Tag); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(share.CreatorAssembly); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(share.Users.Length.ToString()); + ImGui.TableNextColumn(); + ImGui.TextUnformatted(string.Join(", ", share.Users)); + } + } finally + { + ImGui.EndTable(); + } + } + private void DrawUIColor() { var colorSheet = Service.Get().GetExcelSheet(); diff --git a/Dalamud/Plugin/Ipc/Internal/DataShare.cs b/Dalamud/Plugin/Ipc/Internal/DataShare.cs index dab01e45b..5d0faabda 100644 --- a/Dalamud/Plugin/Ipc/Internal/DataShare.cs +++ b/Dalamud/Plugin/Ipc/Internal/DataShare.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Reflection; using Dalamud.Plugin.Ipc.Exceptions; +using Serilog; namespace Dalamud.Plugin.Ipc.Internal; @@ -32,30 +35,41 @@ internal class DataShare : IServiceType /// Thrown if a cache for exists, but contains data of a type not assignable to . /// Thrown if the stored data for a cache is null. /// Thrown if throws an exception or returns null. - public T GetOrCreateData(string tag, Func dataGenerator) where T : class + public T GetOrCreateData(string tag, Func dataGenerator) + where T : class { - var callerName = Assembly.GetCallingAssembly().GetName().Name ?? string.Empty; - if (this.caches.TryGetValue(tag, out var cache)) + var callerName = GetCallerName(); + lock (this.caches) { - if (!cache.Type.IsAssignableTo(typeof(T))) - throw new DataCacheTypeMismatchError(tag, cache.CreatorAssemblyName, typeof(T), cache.Type); - cache.UserAssemblyNames.Add(callerName); - return cache.Data as T ?? throw new DataCacheValueNullError(tag, cache.Type); - } + if (this.caches.TryGetValue(tag, out var cache)) + { + if (!cache.Type.IsAssignableTo(typeof(T))) + { + throw new DataCacheTypeMismatchError(tag, cache.CreatorAssemblyName, typeof(T), cache.Type); + } - try - { - var obj = dataGenerator.Invoke(); - if (obj == null) - throw new Exception("Returned data was null."); + cache.UserAssemblyNames.Add(callerName); + return cache.Data as T ?? throw new DataCacheValueNullError(tag, cache.Type); + } - cache = new DataCache(callerName, obj, typeof(T)); - this.caches[tag] = cache; - return obj; - } - catch (Exception e) - { - throw new DataCacheCreationError(tag, callerName, typeof(T), e); + try + { + var obj = dataGenerator.Invoke(); + if (obj == null) + { + throw new Exception("Returned data was null."); + } + + cache = new DataCache(callerName, obj, typeof(T)); + this.caches[tag] = cache; + + Log.Verbose("[DataShare] Created new data for [{Tag:l}] for creator {Creator:l}.", tag, callerName); + return obj; + } + catch (Exception e) + { + throw new DataCacheCreationError(tag, callerName, typeof(T), e); + } } } @@ -66,16 +80,35 @@ internal class DataShare : IServiceType /// The name for the data cache. public void RelinquishData(string tag) { - if (!this.caches.TryGetValue(tag, out var cache)) - return; + lock (this.caches) + { + if (!this.caches.TryGetValue(tag, out var cache)) + { + return; + } - var callerName = Assembly.GetCallingAssembly().GetName().Name ?? string.Empty; - if (!cache.UserAssemblyNames.Remove(callerName) || cache.UserAssemblyNames.Count > 0) - return; + var callerName = GetCallerName(); + lock (this.caches) + { + if (!cache.UserAssemblyNames.Remove(callerName) || cache.UserAssemblyNames.Count > 0) + { + return; + } - this.caches.Remove(tag); - if (cache.Data is IDisposable disposable) - disposable.Dispose(); + if (this.caches.Remove(tag)) + { + if (cache.Data is IDisposable disposable) + { + disposable.Dispose(); + Log.Verbose("[DataShare] Disposed [{Tag:l}] after it was removed from all shares.", tag); + } + else + { + Log.Verbose("[DataShare] Removed [{Tag:l}] from all shares.", tag); + } + } + } + } } /// @@ -86,19 +119,27 @@ internal class DataShare : IServiceType /// The name for the data cache. /// The requested data on success, null otherwise. /// True if the requested data exists and is assignable to the requested type. - public bool TryGetData(string tag, [NotNullWhen(true)] out T? data) where T : class + public bool TryGetData(string tag, [NotNullWhen(true)] out T? data) + where T : class { data = null; - if (!this.caches.TryGetValue(tag, out var cache) || !cache.Type.IsAssignableTo(typeof(T))) - return false; + lock (this.caches) + { + if (!this.caches.TryGetValue(tag, out var cache) || !cache.Type.IsAssignableTo(typeof(T))) + { + return false; + } - var callerName = Assembly.GetCallingAssembly().GetName().Name ?? string.Empty; - data = cache.Data as T; - if (data == null) - return false; + var callerName = GetCallerName(); + data = cache.Data as T; + if (data == null) + { + return false; + } - cache.UserAssemblyNames.Add(callerName); - return true; + cache.UserAssemblyNames.Add(callerName); + return true; + } } /// @@ -111,19 +152,57 @@ internal class DataShare : IServiceType /// Thrown if is not registered. /// Thrown if a cache for exists, but contains data of a type not assignable to . /// Thrown if the stored data for a cache is null. - public T GetData(string tag) where T : class + public T GetData(string tag) + where T : class { - if (!this.caches.TryGetValue(tag, out var cache)) - throw new KeyNotFoundException($"The data cache {tag} is not registered."); + lock (this.caches) + { + if (!this.caches.TryGetValue(tag, out var cache)) + { + throw new KeyNotFoundException($"The data cache [{tag}] is not registered."); + } - var callerName = Assembly.GetCallingAssembly().GetName().Name ?? string.Empty; - if (!cache.Type.IsAssignableTo(typeof(T))) - throw new DataCacheTypeMismatchError(tag, callerName, typeof(T), cache.Type); + var callerName = Assembly.GetCallingAssembly().GetName().Name ?? string.Empty; + if (!cache.Type.IsAssignableTo(typeof(T))) + { + throw new DataCacheTypeMismatchError(tag, callerName, typeof(T), cache.Type); + } - if (cache.Data is not T data) - throw new DataCacheValueNullError(tag, typeof(T)); + if (cache.Data is not T data) + { + throw new DataCacheValueNullError(tag, typeof(T)); + } - cache.UserAssemblyNames.Add(callerName); - return data; + cache.UserAssemblyNames.Add(callerName); + return data; + } + } + + /// + /// Obtain a read-only list of data shares. + /// + /// All currently subscribed tags, their creator names and all their users. + internal IEnumerable<(string Tag, string CreatorAssembly, string[] Users)> GetAllShares() + { + lock (this.caches) + { + return this.caches.Select(kvp => (kvp.Key, kvp.Value.CreatorAssemblyName, kvp.Value.UserAssemblyNames.ToArray())); + } + } + + /// Obtain the last assembly name in the stack trace that is not a system or dalamud assembly. + private static string GetCallerName() + { + var frames = new StackTrace().GetFrames(); + foreach (var frame in frames.Reverse()) + { + var name = frame.GetMethod()?.DeclaringType?.Assembly.GetName().Name ?? "Unknown"; + if (!name.StartsWith("System") && !name.StartsWith("Dalamud")) + { + return name; + } + } + + return "Unknown"; } }