Efficiently share data between plugins without serialization overhead
The DataShare system provides efficient sharing of reference-type data between plugins without the serialization overhead of CallGate. This is ideal for large, read-only data structures that multiple plugins need to access.
Data sharing only works with reference types (classes) that are loaded from shared assemblies like Dalamud itself. Types from plugin assemblies cannot be shared.
var data = this.pluginInterface.GetOrCreateData<List<string>>( "MyPlugin.SharedList", () => { // This function only runs if data doesn't exist yet return new List<string> { "Item1", "Item2", "Item3" }; });// Use the dataforeach (var item in data){ PluginLog.Information(item);}
using System.Collections.Generic;using Dalamud.Game.ClientState.Objects.Types;// Note: Using IReadOnlyList for thread safetyvar targets = this.pluginInterface.GetOrCreateData<IReadOnlyList<IGameObject>>( "TargetTracker.RecentTargets", () => { // Build initial target list var list = new List<IGameObject>(); foreach (var obj in this.objectTable) { if (obj is ICharacter character && character.IsValid()) { list.Add(character); } } return list.AsReadOnly(); });
if (this.pluginInterface.TryGetData<List<string>>( "OtherPlugin.SharedList", out var data)){ // Data exists and was retrieved PluginLog.Information($"Found {data.Count} items");}else{ // Data doesn't exist or wrong type PluginLog.Information("Data not available");}
try{ var data = this.pluginInterface.GetData<Dictionary<string, int>>( "OtherPlugin.Cache"); // Use data}catch (KeyNotFoundException){ // Tag not registered}catch (DataCacheTypeMismatchError){ // Type doesn't match}catch (DataCacheValueNullError){ // Data is null}
The DataShare system automatically manages data lifecycle:
// Plugin A creates datavar data = pluginA.GetOrCreateData<List<string>>( "Shared.List", () => new List<string>());// Reference count: 1// Plugin B accesses same datavar sameData = pluginB.GetOrCreateData<List<string>>( "Shared.List", () => new List<string>()); // Generator not called// Reference count: 2// Both plugins have the same instance// Plugin A releasespluginA.RelinquishData("Shared.List");// Reference count: 1// Plugin B releasespluginB.RelinquishData("Shared.List");// Reference count: 0// Data is removed and disposed (if IDisposable)
try{ var data = this.pluginInterface.GetOrCreateData<ExpensiveData>( "MyTag", () => { // If this throws, GetOrCreateData will wrap it in DataCacheCreationError return ExpensiveData.LoadFromFile("data.bin"); });}catch (DataCacheCreationError ex){ PluginLog.Error(ex, "Failed to create shared data"); PluginLog.Error($"Inner exception: {ex.InnerException?.Message}");}
public IReadOnlyList<IGameObject> GetSharedTargets(){ if (this.pluginInterface.TryGetData<IReadOnlyList<IGameObject>>( "TargetTracker.Targets", out var targets)) { return targets; } // Fallback to empty list return new List<IGameObject>().AsReadOnly();}
public class DataSharingPlugin : IDisposable{ private readonly List<string> dataTags = new(); public void AccessData(string tag) { var data = this.pluginInterface.GetOrCreateData<MyType>(tag, () => new MyType()); this.dataTags.Add(tag); } public void Dispose() { // Release all accessed data foreach (var tag in this.dataTags) { this.pluginInterface.RelinquishData(tag); } }}
DataShare doesn’t provide automatic thread synchronization. If multiple plugins modify shared data, implement your own locking mechanism.
public class ThreadSafeCache{ private readonly object lockObj = new(); private readonly Dictionary<string, int> data = new(); public void Set(string key, int value) { lock (this.lockObj) { this.data[key] = value; } } public int Get(string key) { lock (this.lockObj) { return this.data.TryGetValue(key, out var value) ? value : 0; } }}
// Providervar list = pluginInterface.GetOrCreateData<IReadOnlyList<string>>( "SharedList", () => this.largeList.AsReadOnly()); // Shared once// Consumervar list = pluginInterface.TryGetData<IReadOnlyList<string>>( "SharedList", out var data); // Direct reference, no serialization