perf(client): lazy child detection in BrowseAsync, NodeId-indexed UI notifications
R08 3.6: BrowseAsync no longer issues one HasChildren Browse per Object child; an Object reports HasChildren (may have children) and is confirmed when expanded or recursed. ISessionAdapter.HasChildrenAsync is removed. The UI subscriptions panel routes each notification through a NodeId index kept in step with ActiveSubscriptions (add/remove/replace/reset) instead of scanning every row. Refs #747 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
This commit is contained in:
@@ -111,6 +111,70 @@ public class SubscriptionsViewModelTests
|
||||
_vm.ActiveSubscriptions[0].Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// R08 3.6: notifications are routed through a NodeId index, not a scan of every row. The index follows every
|
||||
/// mutation of <see cref="SubscriptionsViewModel.ActiveSubscriptions"/> (add, remove, replace, clear).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void NodeId_index_follows_every_collection_mutation()
|
||||
{
|
||||
var a = new SubscriptionItemViewModel("ns=2;s=A", 1000);
|
||||
var b = new SubscriptionItemViewModel("ns=2;s=B", 1000);
|
||||
var c = new SubscriptionItemViewModel("ns=2;s=C", 1000);
|
||||
_vm.ActiveSubscriptions.Add(a);
|
||||
_vm.ActiveSubscriptions.Add(b);
|
||||
_vm.ActiveSubscriptions.Add(c);
|
||||
_vm.IndexedNodeCount.ShouldBe(3);
|
||||
|
||||
_vm.ActiveSubscriptions.Remove(b);
|
||||
_vm.IndexedNodeCount.ShouldBe(2);
|
||||
|
||||
_vm.ActiveSubscriptions[0] = new SubscriptionItemViewModel("ns=2;s=D", 1000); // replaces A
|
||||
Raise("ns=2;s=A", 1);
|
||||
Raise("ns=2;s=D", 2);
|
||||
a.Value.ShouldBeNull();
|
||||
_vm.ActiveSubscriptions[0].Value.ShouldBe("2");
|
||||
|
||||
_vm.ActiveSubscriptions.Clear();
|
||||
_vm.IndexedNodeCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
/// <summary>A removed row stops receiving values while its sibling (the positive control) still does.</summary>
|
||||
[Fact]
|
||||
public void DataChanged_after_remove_updates_only_the_remaining_rows()
|
||||
{
|
||||
var a = new SubscriptionItemViewModel("ns=2;s=A", 1000);
|
||||
var b = new SubscriptionItemViewModel("ns=2;s=B", 1000);
|
||||
_vm.ActiveSubscriptions.Add(a);
|
||||
_vm.ActiveSubscriptions.Add(b);
|
||||
_vm.ActiveSubscriptions.Remove(a);
|
||||
|
||||
Raise("ns=2;s=A", 7);
|
||||
Raise("ns=2;s=B", 42);
|
||||
|
||||
a.Value.ShouldBeNull();
|
||||
b.Value.ShouldBe("42");
|
||||
}
|
||||
|
||||
/// <summary>Two rows for the same node (no dedup on the manual-add path) both update, as before.</summary>
|
||||
[Fact]
|
||||
public void DataChanged_updates_every_row_for_the_node()
|
||||
{
|
||||
var first = new SubscriptionItemViewModel("ns=2;s=A", 1000);
|
||||
var second = new SubscriptionItemViewModel("ns=2;s=A", 500);
|
||||
_vm.ActiveSubscriptions.Add(first);
|
||||
_vm.ActiveSubscriptions.Add(second);
|
||||
|
||||
Raise("ns=2;s=A", 5);
|
||||
|
||||
first.Value.ShouldBe("5");
|
||||
second.Value.ShouldBe("5");
|
||||
}
|
||||
|
||||
private void Raise(string nodeId, int value) =>
|
||||
_service.RaiseDataChanged(new DataChangedEventArgs(nodeId,
|
||||
new DataValue(new Variant(value), StatusCodes.Good, DateTime.UtcNow)));
|
||||
|
||||
/// <summary>Verifies that Clear removes all subscriptions.</summary>
|
||||
[Fact]
|
||||
public void Clear_RemovesAllSubscriptions()
|
||||
|
||||
Reference in New Issue
Block a user