Use effective working ID + internal name

This commit is contained in:
Critical Impact 2026-02-13 19:04:33 +10:00
parent 9e18b843db
commit b963e83cba
7 changed files with 76 additions and 50 deletions

View file

@ -87,7 +87,7 @@ internal class DataShareWidget : IDataWindowWidget
try try
{ {
var dataShare = Service<DataShare>.Get(); var dataShare = Service<DataShare>.Get();
var data2 = dataShare.GetData<object>(name, "DataShareWidget"); var data2 = dataShare.GetData<object>(name, new DataCachePluginId("DataShareWidget", Guid.Empty));
try try
{ {
data = Encoding.UTF8.GetBytes( data = Encoding.UTF8.GetBytes(
@ -98,7 +98,7 @@ internal class DataShareWidget : IDataWindowWidget
} }
finally finally
{ {
dataShare.RelinquishData(name, "DataShareWidget"); dataShare.RelinquishData(name, new DataCachePluginId("DataShareWidget", Guid.Empty));
} }
} }
catch (Exception e) catch (Exception e)
@ -284,7 +284,7 @@ internal class DataShareWidget : IDataWindowWidget
ImGui.TableSetupColumn("Shared Tag"u8); ImGui.TableSetupColumn("Shared Tag"u8);
ImGui.TableSetupColumn("Show"u8); ImGui.TableSetupColumn("Show"u8);
ImGui.TableSetupColumn("Creator Internal Name"u8); ImGui.TableSetupColumn("Creator"u8);
ImGui.TableSetupColumn("#"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale); ImGui.TableSetupColumn("#"u8, ImGuiTableColumnFlags.WidthFixed, 30 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("Consumers"u8); ImGui.TableSetupColumn("Consumers"u8);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
@ -312,9 +312,9 @@ internal class DataShareWidget : IDataWindowWidget
this.nextTab = 2 + index; this.nextTab = 2 + index;
} }
this.DrawTextCell(share.CreatorAssembly, null, true); this.DrawTextCell(share.CreatorPluginId.InternalName, () => share.CreatorPluginId.EffectiveWorkingId.ToString(), true);
this.DrawTextCell(share.Users.Length.ToString(), null, true); this.DrawTextCell(share.UserPluginIds.Length.ToString(), null, true);
this.DrawTextCell(string.Join(", ", share.Users), null, true); this.DrawTextCell(string.Join(", ", share.UserPluginIds.Select(c => c.InternalName)), () => string.Join("\n", share.UserPluginIds.Select(c => $"{c.InternalName} ({c.EffectiveWorkingId.ToString()}")), true);
} }
} }
} }

View file

@ -227,19 +227,19 @@ internal sealed class DalamudPluginInterface : IDalamudPluginInterface, IDisposa
/// <inheritdoc/> /// <inheritdoc/>
public T GetOrCreateData<T>(string tag, Func<T> dataGenerator) where T : class public T GetOrCreateData<T>(string tag, Func<T> dataGenerator) where T : class
=> Service<DataShare>.Get().GetOrCreateData(tag, this.plugin.InternalName, dataGenerator); => Service<DataShare>.Get().GetOrCreateData(tag, new DataCachePluginId(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId), dataGenerator);
/// <inheritdoc/> /// <inheritdoc/>
public void RelinquishData(string tag) public void RelinquishData(string tag)
=> Service<DataShare>.Get().RelinquishData(tag, this.plugin.InternalName); => Service<DataShare>.Get().RelinquishData(tag, new DataCachePluginId(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId));
/// <inheritdoc/> /// <inheritdoc/>
public bool TryGetData<T>(string tag, [NotNullWhen(true)] out T? data) where T : class public bool TryGetData<T>(string tag, [NotNullWhen(true)] out T? data) where T : class
=> Service<DataShare>.Get().TryGetData(tag, this.plugin.InternalName, out data); => Service<DataShare>.Get().TryGetData(tag, new DataCachePluginId(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId), out data);
/// <inheritdoc/> /// <inheritdoc/>
public T? GetData<T>(string tag) where T : class public T? GetData<T>(string tag) where T : class
=> Service<DataShare>.Get().GetData<T>(tag, this.plugin.InternalName); => Service<DataShare>.Get().GetData<T>(tag, new DataCachePluginId(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId));
/// <inheritdoc/> /// <inheritdoc/>
public ICallGateProvider<TRet> GetIpcProvider<TRet>(string name) public ICallGateProvider<TRet> GetIpcProvider<TRet>(string name)

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Ipc.Internal;
namespace Dalamud.Plugin.Ipc.Exceptions; namespace Dalamud.Plugin.Ipc.Exceptions;
/// <summary> /// <summary>
@ -9,11 +11,11 @@ public class DataCacheCreationError : IpcError
/// Initializes a new instance of the <see cref="DataCacheCreationError"/> class. /// Initializes a new instance of the <see cref="DataCacheCreationError"/> class.
/// </summary> /// </summary>
/// <param name="tag">Tag of the data cache.</param> /// <param name="tag">Tag of the data cache.</param>
/// <param name="creator">The assembly name of the caller.</param> /// <param name="creatorPluginId">The plugin ID of the creating plugin.</param>
/// <param name="expectedType">The type expected.</param> /// <param name="expectedType">The type expected.</param>
/// <param name="ex">The thrown exception.</param> /// <param name="ex">The thrown exception.</param>
public DataCacheCreationError(string tag, string creator, Type expectedType, Exception ex) public DataCacheCreationError(string tag, DataCachePluginId creatorPluginId, Type expectedType, Exception ex)
: base($"The creation of the {expectedType} data cache {tag} initialized by {creator} was unsuccessful.", ex) : base($"The creation of the {expectedType} data cache {tag} initialized by {creatorPluginId.InternalName} ({creatorPluginId.EffectiveWorkingId}) was unsuccessful.", ex)
{ {
} }
} }

View file

@ -1,3 +1,5 @@
using Dalamud.Plugin.Ipc.Internal;
namespace Dalamud.Plugin.Ipc.Exceptions; namespace Dalamud.Plugin.Ipc.Exceptions;
/// <summary> /// <summary>
@ -9,11 +11,11 @@ public class DataCacheTypeMismatchError : IpcError
/// Initializes a new instance of the <see cref="DataCacheTypeMismatchError"/> class. /// Initializes a new instance of the <see cref="DataCacheTypeMismatchError"/> class.
/// </summary> /// </summary>
/// <param name="tag">Tag of the data cache.</param> /// <param name="tag">Tag of the data cache.</param>
/// <param name="creator">Assembly name of the plugin creating the cache.</param> /// <param name="creatorPluginId">The plugin ID of the creating plugin.</param>
/// <param name="requestedType">The requested type.</param> /// <param name="requestedType">The requested type.</param>
/// <param name="actualType">The stored type.</param> /// <param name="actualType">The stored type.</param>
public DataCacheTypeMismatchError(string tag, string creator, Type requestedType, Type actualType) public DataCacheTypeMismatchError(string tag, DataCachePluginId creatorPluginId, Type requestedType, Type actualType)
: base($"Data cache {tag} was requested with type {requestedType}, but {creator} created type {actualType}.") : base($"Data cache {tag} was requested with type {requestedType}, but {creatorPluginId.InternalName} ({creatorPluginId.EffectiveWorkingId}) created type {actualType}.")
{ {
} }
} }

View file

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using Dalamud.Plugin.Ipc.Exceptions; using Dalamud.Plugin.Ipc.Exceptions;
@ -16,12 +17,12 @@ internal readonly struct DataCache
/// <summary> Name of the data. </summary> /// <summary> Name of the data. </summary>
internal readonly string Tag; internal readonly string Tag;
/// <summary> The assembly name of the initial creator. </summary> /// <summary> The creating plugin ID of this DataCache entry. </summary>
internal readonly string CreatorAssemblyName; internal readonly DataCachePluginId CreatorPluginId;
/// <summary> A not-necessarily distinct list of current users. </summary> /// <summary> A distinct list of plugin IDs that are using this data. </summary>
/// <remarks> Also used as a reference count tracker. </remarks> /// <remarks> Also used as a reference count tracker. </remarks>
internal readonly List<string> UserAssemblyNames; internal readonly List<DataCachePluginId> UserPluginIds;
/// <summary> The type the data was registered as. </summary> /// <summary> The type the data was registered as. </summary>
internal readonly Type Type; internal readonly Type Type;
@ -33,14 +34,14 @@ internal readonly struct DataCache
/// Initializes a new instance of the <see cref="DataCache"/> struct. /// Initializes a new instance of the <see cref="DataCache"/> struct.
/// </summary> /// </summary>
/// <param name="tag">Name of the data.</param> /// <param name="tag">Name of the data.</param>
/// <param name="creatorAssemblyName">The assembly name of the initial creator.</param> /// <param name="creatorPluginId">The internal name and effective working ID of the creating plugin.</param>
/// <param name="data">A reference to data.</param> /// <param name="data">A reference to data.</param>
/// <param name="type">The type of the data.</param> /// <param name="type">The type of the data.</param>
public DataCache(string tag, string creatorAssemblyName, object? data, Type type) public DataCache(string tag, DataCachePluginId creatorPluginId, object? data, Type type)
{ {
this.Tag = tag; this.Tag = tag;
this.CreatorAssemblyName = creatorAssemblyName; this.CreatorPluginId = creatorPluginId;
this.UserAssemblyNames = []; this.UserPluginIds = [];
this.Data = data; this.Data = data;
this.Type = type; this.Type = type;
} }
@ -49,40 +50,40 @@ internal readonly struct DataCache
/// Creates a new instance of the <see cref="DataCache"/> struct, using the given data generator function. /// Creates a new instance of the <see cref="DataCache"/> struct, using the given data generator function.
/// </summary> /// </summary>
/// <param name="tag">The name for the data cache.</param> /// <param name="tag">The name for the data cache.</param>
/// <param name="creatorAssemblyName">The assembly name of the initial creator.</param> /// <param name="creatorPluginId">The internal name and effective working ID of the creating plugin.</param>
/// <param name="dataGenerator">The function that generates the data if it does not already exist.</param> /// <param name="dataGenerator">The function that generates the data if it does not already exist.</param>
/// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam> /// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam>
/// <returns>The new instance of <see cref="DataCache"/>.</returns> /// <returns>The new instance of <see cref="DataCache"/>.</returns>
public static DataCache From<T>(string tag, string creatorAssemblyName, Func<T> dataGenerator) public static DataCache From<T>(string tag, DataCachePluginId creatorPluginId, Func<T> dataGenerator)
where T : class where T : class
{ {
try try
{ {
var result = new DataCache(tag, creatorAssemblyName, dataGenerator.Invoke(), typeof(T)); var result = new DataCache(tag, creatorPluginId, dataGenerator.Invoke(), typeof(T));
Log.Verbose( Log.Verbose(
"[{who}] Created new data for [{Tag:l}] for creator {Creator:l}.", "[{who}] Created new data for [{Tag:l}] for creator {Creator:l}.",
nameof(DataShare), nameof(DataShare),
tag, tag,
creatorAssemblyName); creatorPluginId);
return result; return result;
} }
catch (Exception e) catch (Exception e)
{ {
throw ExceptionDispatchInfo.SetCurrentStackTrace( throw ExceptionDispatchInfo.SetCurrentStackTrace(
new DataCacheCreationError(tag, creatorAssemblyName, typeof(T), e)); new DataCacheCreationError(tag, creatorPluginId, typeof(T), e));
} }
} }
/// <summary> /// <summary>
/// Attempts to fetch the data. /// Attempts to fetch the data.
/// </summary> /// </summary>
/// <param name="callerName">The name of the caller assembly.</param> /// <param name="callingPluginId">The calling plugin ID.</param>
/// <param name="value">The value, if succeeded.</param> /// <param name="value">The value, if succeeded.</param>
/// <param name="ex">The exception, if failed.</param> /// <param name="ex">The exception, if failed.</param>
/// <typeparam name="T">Desired type of the data.</typeparam> /// <typeparam name="T">Desired type of the data.</typeparam>
/// <returns><c>true</c> on success.</returns> /// <returns><c>true</c> on success.</returns>
public bool TryGetData<T>( public bool TryGetData<T>(
string callerName, DataCachePluginId callingPluginId,
[NotNullWhen(true)] out T? value, [NotNullWhen(true)] out T? value,
[NotNullWhen(false)] out Exception? ex) [NotNullWhen(false)] out Exception? ex)
where T : class where T : class
@ -98,16 +99,21 @@ internal readonly struct DataCache
value = data; value = data;
ex = null; ex = null;
// Register the access history // Register the access history. The effective working ID is unique per plugin and persists between reloads, so only add it once.
lock (this.UserAssemblyNames) lock (this.UserPluginIds)
this.UserAssemblyNames.Add(callerName); {
if (this.UserPluginIds.All(c => c.EffectiveWorkingId != callingPluginId.EffectiveWorkingId))
{
this.UserPluginIds.Add(callingPluginId);
}
}
return true; return true;
default: default:
value = null; value = null;
ex = ExceptionDispatchInfo.SetCurrentStackTrace( ex = ExceptionDispatchInfo.SetCurrentStackTrace(
new DataCacheTypeMismatchError(this.Tag, this.CreatorAssemblyName, typeof(T), this.Type)); new DataCacheTypeMismatchError(this.Tag, this.CreatorPluginId, typeof(T), this.Type));
return false; return false;
} }
} }

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.ExceptionServices;
using Dalamud.Plugin.Ipc.Exceptions;
using Serilog;
namespace Dalamud.Plugin.Ipc.Internal;
/// <summary>
/// Stores the internal name and effective working ID of a plugin accessing datashare.
/// </summary>
/// <param name="InternalName">The internal name of the plugin.</param>
/// <param name="EffectiveWorkingId">The effective working ID of the plugin.</param>
public record DataCachePluginId(string InternalName, Guid EffectiveWorkingId);

View file

@ -33,23 +33,23 @@ internal class DataShare : IServiceType
/// </summary> /// </summary>
/// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam> /// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam>
/// <param name="tag">The name for the data cache.</param> /// <param name="tag">The name for the data cache.</param>
/// <param name="callerName">The name of the caller.</param> /// <param name="callingPluginId">The ID of the calling plugin.</param>
/// <param name="dataGenerator">The function that generates the data if it does not already exist.</param> /// <param name="dataGenerator">The function that generates the data if it does not already exist.</param>
/// <returns>Either the existing data for <paramref name="tag"/> or the data generated by <paramref name="dataGenerator"/>.</returns> /// <returns>Either the existing data for <paramref name="tag"/> or the data generated by <paramref name="dataGenerator"/>.</returns>
/// <exception cref="DataCacheTypeMismatchError">Thrown if a cache for <paramref name="tag"/> exists, but contains data of a type not assignable to <typeparamref name="T>"/>.</exception> /// <exception cref="DataCacheTypeMismatchError">Thrown if a cache for <paramref name="tag"/> exists, but contains data of a type not assignable to <typeparamref name="T>"/>.</exception>
/// <exception cref="DataCacheValueNullError">Thrown if the stored data for a cache is null.</exception> /// <exception cref="DataCacheValueNullError">Thrown if the stored data for a cache is null.</exception>
/// <exception cref="DataCacheCreationError">Thrown if <paramref name="dataGenerator"/> throws an exception or returns null.</exception> /// <exception cref="DataCacheCreationError">Thrown if <paramref name="dataGenerator"/> throws an exception or returns null.</exception>
public T GetOrCreateData<T>(string tag, string callerName, Func<T> dataGenerator) public T GetOrCreateData<T>(string tag, DataCachePluginId callingPluginId, Func<T> dataGenerator)
where T : class where T : class
{ {
Lazy<DataCache> cacheLazy; Lazy<DataCache> cacheLazy;
lock (this.caches) lock (this.caches)
{ {
if (!this.caches.TryGetValue(tag, out cacheLazy)) if (!this.caches.TryGetValue(tag, out cacheLazy))
this.caches[tag] = cacheLazy = new(() => DataCache.From(tag, callerName, dataGenerator)); this.caches[tag] = cacheLazy = new(() => DataCache.From(tag, callingPluginId, dataGenerator));
} }
return cacheLazy.Value.TryGetData<T>(callerName, out var value, out var ex) ? value : throw ex; return cacheLazy.Value.TryGetData<T>(callingPluginId, out var value, out var ex) ? value : throw ex;
} }
/// <summary> /// <summary>
@ -57,8 +57,8 @@ internal class DataShare : IServiceType
/// If no assembly uses the data anymore, the cache will be removed from the data share and if it is an IDisposable, Dispose will be called on it. /// If no assembly uses the data anymore, the cache will be removed from the data share and if it is an IDisposable, Dispose will be called on it.
/// </summary> /// </summary>
/// <param name="tag">The name for the data cache.</param> /// <param name="tag">The name for the data cache.</param>
/// <param name="callerName">The name of the caller.</param> /// <param name="callingPluginId">The ID of the calling plugin.</param>
public void RelinquishData(string tag, string callerName) public void RelinquishData(string tag, DataCachePluginId callingPluginId)
{ {
DataCache cache; DataCache cache;
lock (this.caches) lock (this.caches)
@ -67,7 +67,7 @@ internal class DataShare : IServiceType
return; return;
cache = cacheLazy.Value; cache = cacheLazy.Value;
if (!cache.UserAssemblyNames.Remove(callerName) || cache.UserAssemblyNames.Count > 0) if (!cache.UserPluginIds.Remove(callingPluginId) || cache.UserPluginIds.Count > 0)
return; return;
if (!this.caches.Remove(tag)) if (!this.caches.Remove(tag))
return; return;
@ -97,10 +97,10 @@ internal class DataShare : IServiceType
/// </summary> /// </summary>
/// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam> /// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam>
/// <param name="tag">The name for the data cache.</param> /// <param name="tag">The name for the data cache.</param>
/// <param name="callerName">The name of the caller.</param> /// <param name="callingPluginId">The ID of the calling plugin.</param>
/// <param name="data">The requested data on success, null otherwise.</param> /// <param name="data">The requested data on success, null otherwise.</param>
/// <returns>True if the requested data exists and is assignable to the requested type.</returns> /// <returns>True if the requested data exists and is assignable to the requested type.</returns>
public bool TryGetData<T>(string tag, string callerName, [NotNullWhen(true)] out T? data) public bool TryGetData<T>(string tag, DataCachePluginId callingPluginId, [NotNullWhen(true)] out T? data)
where T : class where T : class
{ {
data = null; data = null;
@ -111,7 +111,7 @@ internal class DataShare : IServiceType
return false; return false;
} }
return cacheLazy.Value.TryGetData(callerName, out data, out _); return cacheLazy.Value.TryGetData(callingPluginId, out data, out _);
} }
/// <summary> /// <summary>
@ -120,12 +120,12 @@ internal class DataShare : IServiceType
/// </summary> /// </summary>
/// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam> /// <typeparam name="T">The type of the stored data - needs to be a reference type that is shared through Dalamud itself, not loaded by the plugin.</typeparam>
/// <param name="tag">The name for the data cache.</param> /// <param name="tag">The name for the data cache.</param>
/// <param name="callerName">The name of the caller.</param> /// <param name="callingPluginId">The ID of the calling plugin.</param>
/// <returns>The requested data.</returns> /// <returns>The requested data.</returns>
/// <exception cref="KeyNotFoundException">Thrown if <paramref name="tag"/> is not registered.</exception> /// <exception cref="KeyNotFoundException">Thrown if <paramref name="tag"/> is not registered.</exception>
/// <exception cref="DataCacheTypeMismatchError">Thrown if a cache for <paramref name="tag"/> exists, but contains data of a type not assignable to <typeparamref name="T>"/>.</exception> /// <exception cref="DataCacheTypeMismatchError">Thrown if a cache for <paramref name="tag"/> exists, but contains data of a type not assignable to <typeparamref name="T>"/>.</exception>
/// <exception cref="DataCacheValueNullError">Thrown if the stored data for a cache is null.</exception> /// <exception cref="DataCacheValueNullError">Thrown if the stored data for a cache is null.</exception>
public T GetData<T>(string tag, string callerName) public T GetData<T>(string tag, DataCachePluginId callingPluginId)
where T : class where T : class
{ {
Lazy<DataCache> cacheLazy; Lazy<DataCache> cacheLazy;
@ -135,19 +135,19 @@ internal class DataShare : IServiceType
throw new KeyNotFoundException($"The data cache [{tag}] is not registered."); throw new KeyNotFoundException($"The data cache [{tag}] is not registered.");
} }
return cacheLazy.Value.TryGetData<T>(callerName, out var value, out var ex) ? value : throw ex; return cacheLazy.Value.TryGetData<T>(callingPluginId, out var value, out var ex) ? value : throw ex;
} }
/// <summary> /// <summary>
/// Obtain a read-only list of data shares. /// Obtain a read-only list of data shares.
/// </summary> /// </summary>
/// <returns>All currently subscribed tags, their creator names and all their users.</returns> /// <returns>All currently subscribed tags, their creator names and all their users.</returns>
internal IEnumerable<(string Tag, string CreatorAssembly, string[] Users)> GetAllShares() internal IEnumerable<(string Tag, DataCachePluginId CreatorPluginId, DataCachePluginId[] UserPluginIds)> GetAllShares()
{ {
lock (this.caches) lock (this.caches)
{ {
return this.caches.Select( return this.caches.Select(
kvp => (kvp.Key, kvp.Value.Value.CreatorAssemblyName, kvp.Value.Value.UserAssemblyNames.ToArray())); kvp => (kvp.Key, kvp.Value.Value.CreatorPluginId, kvp.Value.Value.UserPluginIds.ToArray()));
} }
} }
} }