Files
lmxopcua/src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/CapturingAddressSpaceBuilder.cs
T
Joseph Doherty 826312befe docs: fill missing XML docs and strip tracking IDs from src comments
Clears CommentChecker findings in src/ (3,280 -> 209, the remainder being false
positives like PLC-5/IEEE-754/M0.0) by adding missing summaries/params/returns,
normalising inheritdoc usage, and removing Gitea/review tracking IDs while keeping
the rationale they annotated. Comment-only: comment-stripped sources are identical
to the parent commit.
2026-10-15 06:14:58 -04:00

119 lines
4.3 KiB
C#

using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Commons.Browsing;
/// <summary>
/// In-memory <see cref="IAddressSpaceBuilder"/> that records the streamed discovery tree
/// for the universal browser instead of materializing OPC UA nodes. Single-threaded like
/// every builder a driver streams into during DiscoverAsync. Enforces a node cap: once
/// exceeded it stops recording (drivers keep streaming harmlessly) and marks the tree
/// truncated. See docs/plans/2026-07-15-universal-discovery-browser-design.md §4.1.
/// </summary>
public sealed class CapturingAddressSpaceBuilder : IAddressSpaceBuilder
{
private sealed class CaptureState(int nodeCap)
{
public int Count;
public bool Truncated;
public readonly int NodeCap = nodeCap;
public readonly Dictionary<string, CapturedNode> ById = new(StringComparer.Ordinal);
/// <summary>Reserves a slot for one more node, marking the capture truncated when the cap is reached.</summary>
/// <returns><see langword="true"/> when the node may be recorded; <see langword="false"/> when the cap is reached.</returns>
public bool TryReserve()
{
if (Count >= NodeCap) { Truncated = true; return false; }
Count++;
return true;
}
}
private readonly CaptureState _state;
private readonly CapturedNode _scope;
/// <summary>Create a root capturing builder with the given node cap (default 50 000).</summary>
/// <param name="nodeCap">Maximum number of nodes recorded before the tree is marked truncated.</param>
public CapturingAddressSpaceBuilder(int nodeCap = 50_000)
{
_state = new CaptureState(nodeCap);
_scope = new CapturedNode { Id = "", Name = "", DisplayName = "", IsLeaf = false };
}
private CapturingAddressSpaceBuilder(CaptureState state, CapturedNode scope)
{
_state = state;
_scope = scope;
}
/// <inheritdoc />
public IAddressSpaceBuilder Folder(string browseName, string displayName)
{
var node = new CapturedNode
{
Id = _scope.Id + "/" + browseName,
Name = browseName,
DisplayName = displayName,
IsLeaf = false,
};
if (_state.TryReserve())
{
_scope.Children.Add(node);
_state.ById.TryAdd(node.Id, node);
}
// Over cap: the child builder writes into a detached node — recording stops, streaming doesn't break.
return new CapturingAddressSpaceBuilder(_state, node);
}
/// <inheritdoc />
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
{
var node = new CapturedNode
{
Id = attributeInfo.FullName,
Name = browseName,
DisplayName = displayName,
IsLeaf = true,
Attribute = attributeInfo,
};
if (_state.TryReserve())
{
_scope.Children.Add(node);
_state.ById.TryAdd(node.Id, node);
}
return new CapturedVariableHandle(node);
}
/// <inheritdoc />
public void AddProperty(string browseName, DriverDataType dataType, object? value) =>
_scope.Properties.Add((browseName, dataType, value));
/// <summary>Snapshot the captured tree. Only valid on the root builder (the one the browser constructed).</summary>
/// <returns>An immutable-after-build <see cref="CapturedTree"/>.</returns>
public CapturedTree Build() => new()
{
Root = _scope,
Truncated = _state.Truncated,
Count = _state.Count,
ById = _state.ById,
};
private sealed class CapturedVariableHandle(CapturedNode node) : IVariableHandle
{
/// <inheritdoc />
public string FullReference => node.Id;
/// <inheritdoc />
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info)
{
node.IsAlarmMarked = true;
return NoopAlarmSink.Instance;
}
}
private sealed class NoopAlarmSink : IAlarmConditionSink
{
public static readonly NoopAlarmSink Instance = new();
/// <inheritdoc />
public void OnTransition(AlarmEventArgs args) { } // browse never delivers live transitions
}
}