refactor(otopcua): align device-host map parity + document EquipmentNode rebuild trade-off (follow-up E)
This commit is contained in:
@@ -72,7 +72,17 @@ public sealed record UnsLineProjection(string UnsLineId, string UnsAreaId, strin
|
||||
/// multi-device driver by host. The value is normalized identically on both the live-edit composer and
|
||||
/// the artifact-decode sides (single source of truth: <see cref="AddressSpaceComposer.TryExtractDeviceHost"/>);
|
||||
/// the later partition task MUST normalize the driver-discovered device-host folder segment the same way
|
||||
/// (trim + lower-case) so the two compare equal.</para></summary>
|
||||
/// (trim + lower-case) so the two compare equal.</para>
|
||||
/// <para><b>Address-space-rebuild interaction (accepted trade-off).</b> These three fields participate in
|
||||
/// <see cref="EquipmentNode"/>'s record value-equality, which <c>AddressSpacePlan.Compute</c> uses to
|
||||
/// build its changed-equipment set. So editing a <c>Device</c>'s <c>DeviceConfig</c> host/port, or
|
||||
/// rebinding an equipment's <c>DriverInstanceId</c> / <c>DeviceId</c>, now yields an
|
||||
/// <see cref="EquipmentNode"/> delta that triggers a full structural address-space rebuild on the next
|
||||
/// deploy (a momentary subscription teardown for that equipment). This is a deliberate, accepted
|
||||
/// decision: it fires only on rare operator-initiated config edits at deploy time (routine redeploys of
|
||||
/// unchanged config are unaffected — the delta is empty), it is recoverable, and it is directionally
|
||||
/// correct for the multi-device FixedTree re-partition (a later task). <c>AddressSpacePlan</c> is left
|
||||
/// unchanged.</para></summary>
|
||||
public sealed record EquipmentNode(
|
||||
string EquipmentId,
|
||||
string DisplayName,
|
||||
@@ -343,10 +353,16 @@ public static class AddressSpaceComposer
|
||||
// DeviceId → connection host, resolved once from each bound Device's schemaless DeviceConfig JSON
|
||||
// via the shared TryExtractDeviceHost (single source of truth + normalization for both this
|
||||
// composer and the artifact-decode mirror in DeploymentArtifact, so EquipmentNode.DeviceHost is
|
||||
// byte-parity-equal). Ordinal comparer matches the decode-side map exactly.
|
||||
var deviceHostById = (devices ?? Array.Empty<Device>())
|
||||
.Where(d => d.DeviceId != null)
|
||||
.ToDictionary(d => d.DeviceId, d => TryExtractDeviceHost(d.DeviceConfig), StringComparer.Ordinal);
|
||||
// byte-parity-equal). This MUST match DeploymentArtifact.BuildDeviceHostMap semantics EXACTLY:
|
||||
// Ordinal comparer, skip blank/whitespace DeviceIds, and LAST-WINS on a duplicate DeviceId (a
|
||||
// foreach assignment, NOT ToDictionary which would THROW on a dupe — diverging from the decode
|
||||
// side's last-wins). DeviceId is DB-unique so a dupe is defensive-only.
|
||||
var deviceHostById = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||
foreach (var d in devices ?? Array.Empty<Device>())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(d.DeviceId)) continue;
|
||||
deviceHostById[d.DeviceId] = TryExtractDeviceHost(d.DeviceConfig);
|
||||
}
|
||||
var areas = unsAreas
|
||||
.OrderBy(a => a.UnsAreaId, StringComparer.Ordinal)
|
||||
.Select(a => new UnsAreaProjection(a.UnsAreaId, a.Name))
|
||||
|
||||
Reference in New Issue
Block a user