Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/ParameterDictionaryCache.cs
T
Joseph Doherty b0959f697b perf(adminui): cache the modals' DynamicComponent parameter dictionaries (R04 H2)
DriverConfigModal, DeviceModal and RawTagModal built a new parameter dictionary
and new EventCallbacks every render. ParameterDictionaryCache reuses the
dictionary while the values it carries are unchanged and rebuilds it with the
current value when one changes (a build-once cache would strand edits); the
callbacks are created once. Blazor still re-sets an EventCallback-bearing
child's parameters per parent render, so the forms' _lastParsed guards stay.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 17:43:01 -04:00

51 lines
2.3 KiB
C#

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared;
/// <summary>
/// Caches a <c>DynamicComponent</c> parameter dictionary (R04 H2) and rebuilds it only when one of the
/// values it carries changed. The modals used to build a new dictionary, and new
/// <c>EventCallback</c>s, on every render. A plain build-once cache is wrong: it would pin the JSON
/// captured at first render and silently strand every later edit — so the cache is keyed on the
/// current values, and the callbacks it hands out are created once by the caller and reused.
/// <para>Note the limit: Blazor's change detection treats any <c>EventCallback</c> parameter as
/// possibly changed, so a hosted form still sees <c>SetParametersAsync</c> on each parent render. The
/// per-form <c>_lastParsed</c> guards remain load-bearing; this removes the per-render allocations and
/// gives the child a stable dictionary and callback identity.</para>
/// </summary>
public sealed class ParameterDictionaryCache
{
private object?[]? _keyValues;
private Dictionary<string, object>? _cached;
/// <summary>Returns the cached dictionary when <paramref name="keyValues"/> equal the previous call's,
/// otherwise builds, caches and returns a new one.</summary>
/// <param name="keyValues">The values the dictionary carries (compared with <see cref="object.Equals(object, object)"/>).</param>
/// <param name="build">Builds the dictionary from the current state.</param>
/// <returns>The parameter dictionary.</returns>
public Dictionary<string, object> Get(object?[] keyValues, Func<Dictionary<string, object>> build)
{
if (_cached is not null && _keyValues is not null && Same(_keyValues, keyValues)) return _cached;
_cached = build();
_keyValues = (object?[])keyValues.Clone();
return _cached;
}
/// <summary>Drops the cached dictionary (e.g. on modal close) so the next <see cref="Get"/> rebuilds.</summary>
public void Reset()
{
_cached = null;
_keyValues = null;
}
private static bool Same(object?[] a, object?[] b)
{
if (a.Length != b.Length) return false;
for (var i = 0; i < a.Length; i++)
{
if (!Equals(a[i], b[i])) return false;
}
return true;
}
}