Files
lmxopcua/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawNodeMenuCache.cs
T
Joseph Doherty 2a788ed8ac perf(adminui): RawTree renders a flat Virtualize list, pages 500 per level, caches menus (R04 P5)
The recursive RenderNode rendered every row of an expanded device, and built a
fresh context-menu list + closures per node per render. RawTreeRows flattens the
expanded tree (explicit stack) into rows rendered by one Virtualize; each level
shows 500 children, then a 'Show more' row. RawNodeMenuCache keys menus weakly
on the node instance and rebuilds on a picker-mode or Enabled change.

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

39 lines
1.7 KiB
C#

using System.Runtime.CompilerServices;
using ZB.MOM.WW.OtOpcUa.AdminUI.Components.Shared;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
/// <summary>
/// Caches each <see cref="RawNode"/>'s context-menu item list (R04 P5). <c>RawTree</c> used to build a
/// fresh list (and fresh closures) per node on every render, and hand it to that row's
/// <c>ContextMenu</c> as a new reference each time. The cache is keyed weakly on the node instance, so
/// a reload (which replaces the child nodes) drops the stale entries with the nodes, and it rebuilds
/// when anything the menu depends on (picker mode, the node's enabled state) differs.
/// </summary>
public sealed class RawNodeMenuCache
{
private readonly ConditionalWeakTable<RawNode, Entry> _entries = new();
/// <summary>Returns the cached menu for <paramref name="node"/>, building it on first use or after a dependency changed.</summary>
/// <param name="node">The node.</param>
/// <param name="pickerMode">Whether the tree is in picker mode (picker menus differ entirely).</param>
/// <param name="build">Builds the menu for the node.</param>
/// <returns>The menu items.</returns>
public IReadOnlyList<ContextMenuItem> GetOrBuild(
RawNode node, bool pickerMode, Func<RawNode, IReadOnlyList<ContextMenuItem>> build)
{
if (_entries.TryGetValue(node, out var entry)
&& entry.PickerMode == pickerMode
&& entry.Enabled == node.Enabled)
{
return entry.Items;
}
var items = build(node);
_entries.AddOrUpdate(node, new Entry(pickerMode, node.Enabled, items));
return items;
}
private sealed record Entry(bool PickerMode, bool Enabled, IReadOnlyList<ContextMenuItem> Items);
}