Skip to content

Overlay a NodeSet2 document onto a generated model in RuntimeNodeSets - #882

Merged
romanett merged 1 commit into
masterfrom
romanett/ua-net-sample-extension-6d021c
Sep 10, 2026
Merged

romanett merged 1 commit into
masterfrom
romanett/ua-net-sample-extension-6d021c

Conversation

@romanett

@romanett romanett commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Closes #878.

What

UA-.NETStandard#4432 added
INodeManagerBuilder.Import(UANodeSet, INodeSetImportFactoryProvider?), which lets a
generated node manager overlay a NodeSet2 document that is only known at runtime onto the
model it was generated from. No sample used it.

The issue left one thing open — a new Workshop/NodeSetOverlay or a second half of
Workshop/RuntimeNodeSets. This is the second half. The existing sample already answers
"a document the server has no code for at all", Import answers "a document that extends
a model the server does have code for", and a reader is much better served by the two
side by side than by two samples that each explain half of a decision. The sample now
reads as the two ways a NodeSet2 document reaches a running server.

Server

ModelDesign.xml / .csv StationType (Throughput, Status, Reset(ClearFaults) : Accepted) and a SiteType whose two Station children are the placeholder slots. The identifiers are pinned because an overlay is written against them.
NodeSets/Site.Layout.NodeSet2.xml Replaces the Station1 placeholder, and adds a Station3 below Site — a node the manager already owns.
NodeSets/Site.Instrumentation.NodeSet2.xml Hangs a Reset and a Temperature off Station3: a parent which only exists in the other document of the same batch.
SiteNodeManager Imports both, then wires. Resolves the imported nodes by the NodeId their document gives them.
SiteOverlayIds The identifiers the generator cannot emit, because it never sees the documents.
SiteOverlayNodeManagerFactory Derives from the generated factory to hand the manager the document library out of the container.

The address space that comes out:

Node From What happened
Site/Station1 Site.Layout replaced the generated placeholder, which left the address space
Site/Station2 the model untouched — no document claims that slot
Site/Station3 Site.Layout added below a node the manager already owns
Site/Station3/Reset Site.Instrumentation added, parent from the other document
Site/Station3/Temperature Site.Instrumentation added, and no station type declares it

Client

BrowseSiteModelAsync reports, for every node, whether the TypeDefinition of its parent
declares that browse name. That is what a client can actually see of an overlay — an
imported node is an ordinary node, and no Browse response says which document it came
from — so the evidence is that the address space says more than the types do, and that a
declared slot was answered with something else. The window shows it in a new pane beside
the existing one.

Tests

  • RuntimeNodeSetsOverlayNodeManagerTests (tier 1.5, 11 tests): the batch links across
    documents, the replacement takes the slot and the placeholder leaves the address space
    with its references retargeted, an imported instance carries exactly the children its
    document declares, and behaviour wired onto an imported Method answers like behaviour
    wired onto a generated one.
  • One tier 1.7 test for the client model.

Tiers 0, 1, 1.5, 1.7 and 2 pass for this sample; the full tier 1.5 (192) and tier 1.7
(422) suites pass; the solution builds with no new warnings.

Notes for the reviewer

Placeholder replacement needs the slot declared on the type. My first model design
declared Station1/Station2 on the Site instance of ua:BaseObjectType. No
generated state class means no slots, so TryReplaceExplicitlyDefinedChild saw an ordinary
child, refused to evict it, and the import added a second Station1 beside the first —
silently, with no error anywhere. SiteType fixed it, and the README and the ModelDesign
comment both say so, because this is the trap a reader will hit first.

Import factories match Variables by TypeDefinition. The documents give Throughput,
Status and Temperature the base BaseDataVariableType, so they import as plain
BaseDataVariableStates and the generated StationState.Throughput property stays null.
Only the stations (StationType) and Station3/Reset (MethodDeclarationId) are typed.
The sample proves that typing rather than asserting it: builder.Node<StationState>(...)
answers BadTypeMismatch otherwise, so a server which failed to materialize the typed
state would not start.

The two refusals. The BadInvalidState for wiring a node the import displaces was
provoked and is quoted verbatim in the README, message and server log line — it names the
configured descendant, not the replaced root, and the tier 1.5 readiness gate reports
only a TaskCanceledException, so getting the text needs a standalone host. The other
refusal (sealing with an unregistered import batch) is unreachable from a source-generated
manager, since the emitted partial always completes the batch; the README says where it
does apply rather than contriving a way to trigger it.

🤖 Generated with Claude Code

…#878)

`INodeManagerBuilder.Import(UANodeSet, INodeSetImportFactoryProvider?)` lets a
generated node manager overlay a NodeSet2 document that is only known at runtime
onto the model it was generated from. No sample used it.

`Workshop/RuntimeNodeSets` gains that as its second half rather than becoming a
new sample: the existing half already answers "a document the server has no code
for", and the two belong next to each other. The sample now reads as the two ways
a NodeSet2 document reaches a running server.

Server
- `ModelDesign.xml`/`.csv`: `StationType` (Throughput, Status, Reset) and a
  `SiteType` whose two Station children are the placeholder slots. The
  identifiers are fixed because an overlay is written against them.
- `NodeSets/Site.Layout.NodeSet2.xml` replaces the `Station1` placeholder and
  adds a `Station3` below `Site`, a node the manager already owns.
  `NodeSets/Site.Instrumentation.NodeSet2.xml` hangs a `Reset` and a
  `Temperature` off `Station3` - a parent which only exists in the other
  document of the same batch.
- `SiteNodeManager` imports both and then wires, and resolves the imported nodes
  by the NodeId their document gives them. `SiteOverlayIds` holds the
  identifiers the generator cannot emit, because it never sees the documents.
- `SiteOverlayNodeManagerFactory` derives from the generated factory to hand the
  manager the document library out of the container.

Client
- `BrowseSiteModelAsync` reports, for every node, whether the TypeDefinition of
  its parent declares that browse name. That is what a Client can see of an
  overlay: an imported node is an ordinary node, so the evidence is that the
  address space says more than the types do, and that a declared slot was
  answered with something else. The window shows it in a new pane.

Tests
- `RuntimeNodeSetsOverlayNodeManagerTests` (tier 1.5, 11 tests): the batch links
  across documents, the replacement takes the slot and the placeholder leaves the
  address space with the references retargeted, an imported instance carries
  exactly the children its document declares, and behaviour wired onto an
  imported Method answers like behaviour wired onto a generated one.
- One tier 1.7 test for the client model.

The README documents the reconciliation rules against that working reference,
including the two refusals: the `BadInvalidState` for wiring a node the import
displaces (provoked and quoted verbatim, message and log line), and the one a
source-generated manager cannot reach.

Notable: placeholder replacement needs the slot declared on the *type*. An
instance of a plain `ua:BaseObjectType` has no generated state class and
therefore no slots, and the same document then adds a second child with the same
BrowseName instead of replacing - silently, with no error anywhere.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@romanett
romanett merged commit e09b4ca into master Sep 10, 2026
9 checks passed
@romanett
romanett deleted the romanett/ua-net-sample-extension-6d021c branch September 10, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No sample overlays a NodeSet2 document through the fluent builder

1 participant