perf(opcua): skip address-space rebuild for vtag expression/deps/historize-only edits (F10b)
This commit is contained in:
@@ -84,16 +84,23 @@ public sealed class Phase7Applier
|
||||
// Any add / remove / in-place CHANGE of Equipment, ScriptedAlarm, Equipment tag, or Equipment
|
||||
// VirtualTag topology requires a real address-space rebuild — the materialise passes re-derive
|
||||
// every node from the composition, so a changed-only deploy (e.g. a renamed equipment, a
|
||||
// re-severitied alarm, a flipped tag dataType/Writable, or an edited VirtualTag expression) must
|
||||
// still rebuild or the running server keeps the stale node.
|
||||
// re-severitied alarm, a flipped tag dataType/Writable) must still rebuild or the running server
|
||||
// keeps the stale node.
|
||||
// ChangedDrivers is deliberately EXCLUDED: a driver-instance config change doesn't touch the
|
||||
// address-space topology — it routes through DriverHostActor's spawn-plan in Runtime, which
|
||||
// re-spawns the affected driver actor without re-materialising any nodes.
|
||||
// F10b: a CHANGED VirtualTag whose ONLY differences are Expression/DependencyRefs/Historize is
|
||||
// node-IRRELEVANT (see VtagDeltaIsNodeIrrelevant) — its materialised node is byte-identical and
|
||||
// the vtag engine adopts those edits via VirtualTagHostActor's INDEPENDENT respawn
|
||||
// (DriverHostActor → ApplyVirtualTags), so it skips the rebuild and PRESERVES every client's
|
||||
// server-wide subscriptions. Any structural / node-affecting vtag change (Name/FolderPath/
|
||||
// DataType) — or any non-vtag change anywhere — still forces a full rebuild (safe default).
|
||||
var needsRebuild =
|
||||
plan.AddedEquipment.Count > 0 || plan.RemovedEquipment.Count > 0 || plan.ChangedEquipment.Count > 0 ||
|
||||
plan.AddedAlarms.Count > 0 || plan.RemovedAlarms.Count > 0 || plan.ChangedAlarms.Count > 0 ||
|
||||
plan.AddedEquipmentTags.Count > 0 || plan.RemovedEquipmentTags.Count > 0 || plan.ChangedEquipmentTags.Count > 0 ||
|
||||
plan.AddedEquipmentVirtualTags.Count > 0 || plan.RemovedEquipmentVirtualTags.Count > 0 || plan.ChangedEquipmentVirtualTags.Count > 0;
|
||||
plan.AddedEquipmentVirtualTags.Count > 0 || plan.RemovedEquipmentVirtualTags.Count > 0 ||
|
||||
plan.ChangedEquipmentVirtualTags.Any(d => !VtagDeltaIsNodeIrrelevant(d));
|
||||
|
||||
if (needsRebuild)
|
||||
{
|
||||
@@ -319,6 +326,20 @@ public sealed class Phase7Applier
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "Phase7Applier: EnsureVariable threw for {Node}", nodeId); }
|
||||
}
|
||||
|
||||
// A VirtualTag's materialised OPC UA node (MaterialiseEquipmentVirtualTags) is derived ONLY from
|
||||
// {EquipmentId, FolderPath, Name, DataType}. Expression/DependencyRefs/Historize are engine/write-side
|
||||
// only and are adopted by VirtualTagHostActor's INDEPENDENT respawn (DriverHostActor → ApplyVirtualTags),
|
||||
// so a delta changing ONLY those three leaves a byte-identical node and needs no address-space rebuild.
|
||||
// Whitelist-of-may-differ via `with` + the record's custom Equals: any OTHER field difference (current
|
||||
// or future) makes the override unequal → falls back to a full rebuild (safe default).
|
||||
private static bool VtagDeltaIsNodeIrrelevant(Phase7Plan.EquipmentVirtualTagDelta d) =>
|
||||
(d.Previous with
|
||||
{
|
||||
Expression = d.Current.Expression,
|
||||
DependencyRefs = d.Current.DependencyRefs,
|
||||
Historize = d.Current.Historize,
|
||||
}).Equals(d.Current);
|
||||
|
||||
/// <summary>The "no-event" condition state written to a removed equipment / alarm node before the
|
||||
/// rebuild tears it down: inactive, acked, confirmed, enabled, unshelved, severity 0, empty message.
|
||||
/// Drives Retain to false so a removed condition stops replaying on ConditionRefresh.</summary>
|
||||
|
||||
Reference in New Issue
Block a user