Skip to content

Assign instance NodeIds in generated CreateOrReplace<Child> helpers - #4104

Merged
marcschier merged 7 commits into
masterfrom
marcschier/createorreplace-instance-nodeids
Jul 28, 2026
Merged

marcschier merged 7 commits into
masterfrom
marcschier/createorreplace-instance-nodeids

Conversation

@marcschier

@marcschier marcschier commented Jul 28, 2026 •

Copy link
Copy Markdown
Collaborator

Description

The model source generator emits two families of instance helpers with inconsistent NodeId behaviour:

  • ISystemContext.CreateInstanceOf<Type>(parent, browseName) rebases the whole materialised subtree onto per-instance NodeIds.
  • <Owner>.CreateOrReplace<Child>(context, replacement) — the plumbing behind NodeState.CreateChild / ReplaceChild — did not. The child it produced was either bare (null NodeId, no browse name) or kept the type-level NodeId of the replacement.

Any node manager mixing the two had to hand-roll a recursive re-stamp before registration, or sibling instances collided on NodeIds — a failure that only surfaces at registration time. samples/PumpDeviceIntegrationServer did exactly this.

What changed

Generator (tools/Opc.Ua.SourceGeneration.Core)

  • CreateOrReplace<Child> gains bool assignInstanceNodeIds = true. When enabled and the context supplies an ISystemContext.NodeIdFactory, a child whose NodeId is null or still equal to its declaration NodeId — and its descendants — are rebased through NodeInstanceExtensions.AssignInstanceNodeId / AssignInstanceChildNodeIds, with the owner as reference root. A NodeId the caller already assigned is never overwritten.
  • The helper also stamps the new child's SymbolicName / BrowseName / DisplayName. Without an identity, a path-based INodeIdFactory derives {parent}_ for every child of a parent, so the assignment would have introduced collisions rather than removed them. BrowseName stamping is guarded on context.NamespaceUris != null — event-reporting paths pass a context without a namespace table.
  • All generated Create* factory call sites pass assignInstanceNodeIds: false: they build declaration subtrees whose NodeIds must stay at their type-level values, and the enclosing CreateInstanceOf<Type> factory rebases the finished subtree in a single pass.
  • The CreateInstanceOf<Type> rebase guard drops the parent != null requirement — supplying a browseName is what marks a dynamically materialised instance. Calling it without one (as the generated NodeStateActivators do, and when replacing a well-known singleton) still keeps the declaration NodeIds.
  • replacement is now emitted as BaseInstanceState? on CreateOrReplace<Child> and on the generated FindChild override, matching the documented contract and the base NodeState.FindChild signature.

Hand-written twins — MethodState.CreateOrReplace{Input,Output}Arguments and BaseDataVariableState.CreateOrReplaceEnumStrings gain the same parameter and behaviour, sharing a new internal NodeInstanceExtensions.AssignNewChildInstanceNodeIds. This is also required so the generated public new overrides keep hiding a matching base signature (otherwise CS0109 with TreatWarningsAsErrors).

Sample — PumpDeviceIntegrationServer now passes the real DeviceSet parent to the factory and drops AssignChildNodeIds plus the manual root stamp.

Docs — new "Materialising instances at runtime — NodeId assignment" section in SourceGeneratedNodeManagers.md and a refreshed DeviceIntegration.md.

Design note: why not the guard proposed in the issue

Gating on "the owner already has an instance NodeId" cannot be implemented as owner.NodeId != <ownerTypeNodeId>. The generated child factories call <Owner>.CreateOrReplace<Child> on declaration instances — CreatePumpType_Operational runs on a FunctionalGroupState whose NodeId is ObjectIds.PumpType_Operational, not ObjectTypeIds.FunctionalGroupType — so the comparison would be true while the type template is being materialised (forInstance: false) and would rewrite the address space's declaration NodeIds. The explicit opt-out separates the two callers unambiguously.

Compatibility

Adding an optional parameter is source-compatible. Behaviour changes for callers of NodeState.CreateChild / ReplaceChild when the context carries a NodeIdFactory: the new child now carries a browse name and a per-instance NodeId instead of nothing.

Related Issues

Note

The issue asks to delete AssignChildNodeIds from samples/MinimalRobotServer. That sample does not exist on master — it only lives on the still-open #4096 branch and carries no such helper. The equivalent boilerplate on master is PumpDeviceIntegrationServer/PumpNodeManager, which this PR cleans up. Details in this comment.

Note

One commit is an unrelated one-line-scope build-hygiene fix: two CA2000 warnings in ConsoleReferenceClient where subscription ownership transfers to the session. Happy to split it out if preferred.

Checklist

  • I have signed the CLA and read the CONTRIBUTING doc.
  • I have added tests that prove my fix is effective or that my feature works and increased code coverage.
  • I have added all necessary documentation.
  • I have verified that my changes do not introduce (new) build or analyzer warnings.
  • I ran all tests locally using the UA.slnx solution against at least .net framework and .net 10, and all passed.
  • I fixed all failing and flaky tests in the CI pipelines and all CodeQL warnings.
  • I have addressed all PR feedback received.

Tests added

Generator emission (NodeManagerGeneratorTests) — CreateOrReplaceChildAssignsInstanceNodeIdsByDefault, TypeFactoriesOptOutOfCreateOrReplaceNodeIdAssignment, CreateInstanceOfFactoriesRebaseWithoutAnExplicitParent.

Helper behaviour (NodeInstanceExtensionsTests) — CreateOrReplaceArgumentsAssignsInstanceNodeIds, CreateOrReplaceArgumentsKeepsCallerAssignedNodeId, CreateOrReplaceArgumentsHonoursTheAssignmentOptOut, CreateOrReplaceEnumStringsAssignsInstanceNodeIds.

Pump DI server regression (PumpInstanceNodeIdRegressionTests, added on review feedback):

Test Asserts
ConfiguredPumpExposesTheFullModelSurface The exact set of 77 model identifiers a configured pump materialises.
EveryPumpInstanceExposesTheSameGeneratedModelSurface A second pump exposes the identical generated surface, minus what the fluent Configure pass adds.
MaterialisedPumpNodeIdsComeFromTheNodeIdFactory Every NodeId is {parentIdentifier}_{symbolicName} in the parent's namespace — minted by PumpNodeManager.New.
MaterialisedPumpNodeIdsAreUniqueAcrossInstances No NodeId is null or shared between two instances of the type.
GeneratedFactoryAssignsInstanceNodeIdsBeforeRegistration The factory output already carries per-instance NodeIds before AddPredefinedNodeAsync applies its defensive rebase.
AlarmSubtreeKeepsStandardDeclarationNodeIds Pins the alarm subtree's standard-namespace ids — a pre-existing gap outside the generated helpers.

The 77-identifier baseline was captured by running the same subtree walk against the pre-change commit and diffing it against the post-change run: identical — same node set, same NodeIds.

Local test runs

Suite net10.0 net48 net472
Opc.Ua.SourceGeneration.Core.Tests 3722 passed 3721 passed —
Opc.Ua.SourceGeneration.Tests 81 passed 81 passed —
Opc.Ua.SourceGeneration.Stack.Tests 90 passed — —
Opc.Ua.Types.Tests 8083 passed 8076 passed —
Opc.Ua.Di.Tests 303 passed 303 passed 277 passed
Opc.Ua.Core.Tests 4134 passed — —
Opc.Ua.Gds.Tests 1095 passed — —
Opc.Ua.History.Tests 506 passed — —
Opc.Ua.Server.Tests 3626 passed, 1 failed 3574 passed, 1 failed —

The single Opc.Ua.Server.Tests failure — ServerFluentApiHostingTests.ConfigureApplicationBuildsSharedClientAndServerConfigurationAsync — is pre-existing and unrelated; it reproduces on a stashed clean master tree.

Note

The source-generation test harness compiles generated model code against a hand-written Opc.Ua API stub (tests/Opc.Ua.SourceGeneration.Core.Tests/CompilerUtils.cs, shared by three test projects). Its 73 CreateOrReplace<Child> declarations were updated to the new signature — without that, every generated call site passing assignInstanceNodeIds: false failed to bind. NodesetMethodArgumentTests pinned the exact emitted call and was updated to the new one for the same reason as the NodeStateGeneratorTests expectations.

Note

master has been merged into this branch. The only conflict was in NodeStateGeneratorTests.cs, where master rewrapped the expected call-site strings while this branch appended assignInstanceNodeIds: false to them — kept master's wrapping and re-applied the argument. Master's two new templates (Create_MethodArguments, Assign_MethodArgumentValues) call CreateOrReplace{Input,Output}Arguments while building a declaration subtree, so they now pass assignInstanceNodeIds: false to preserve their stated intent of leaving the property node's identity untouched.

Tip

Changing a source generator leaves dependent projects' cached generated output stale. If a local build reports CS1739: ... does not have a parameter named 'assignInstanceNodeIds' or CS0109 on a generated CreateOrReplace*, run dotnet build <project> -t:Rebuild. Clean CI builds are unaffected.

marcschier and others added 2 commits July 28, 2026 11:41
…4099)

The generated CreateInstanceOf<Type> factories rebase a materialised
subtree onto per-instance NodeIds, but <Owner>.CreateOrReplace<Child> -
the plumbing behind NodeState.CreateChild / ReplaceChild - did not. Any
node manager mixing the two had to hand-roll a recursive re-stamp before
registration or sibling instances collided on NodeIds.

CreateOrReplace<Child> now takes an optional assignInstanceNodeIds flag
(default true). When the context supplies a NodeIdFactory, a child whose
NodeId is null or still equal to its declaration NodeId - and its
descendants - are rebased through AssignInstanceNodeId /
AssignInstanceChildNodeIds. A NodeId the caller already assigned is never
overwritten. The helper also stamps the new child's SymbolicName,
BrowseName and DisplayName: without an identity a path-based
INodeIdFactory derives the same id for every child of a parent, so the
assignment would have introduced collisions rather than removed them.

The generated Create* factories pass assignInstanceNodeIds: false. They
build declaration subtrees whose NodeIds must stay at their type-level
values, and the enclosing CreateInstanceOf<Type> factory rebases the
finished subtree in a single pass. Gating on "the owner already has an
instance NodeId" instead is not viable: the child factories run on
declaration instances (CreatePumpType_Operational executes on a
FunctionalGroupState whose NodeId is ObjectIds.PumpType_Operational, not
ObjectTypeIds.FunctionalGroupType), so such a guard would fire while the
type template is being built and rewrite declaration NodeIds.

CreateInstanceOf<Type> no longer requires a non-null parent to rebase -
supplying a browseName is what marks a dynamically materialised instance.
This lets PumpDeviceIntegrationServer pass the real DeviceSet parent and
drop its manual AssignChildNodeIds walk.

The hand-written twins MethodState.CreateOrReplace{Input,Output}Arguments
and BaseDataVariableState.CreateOrReplaceEnumStrings gain the same
parameter and behaviour, sharing a new internal
NodeInstanceExtensions.AssignNewChildInstanceNodeIds. This is also
required so the generated "public new" overrides keep hiding a matching
base signature.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
Both subscriptions are handed to the session through
session.AddSubscription, and Session.DisposeAsyncCore disposes every
subscription it holds, so this is an ownership transfer CA2000 cannot
infer. Wrap the two constructions in the pragma pair the repository
already uses for this pattern (see RedundantClientSessionBuilder.Build).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
Comment thread docs/migrate/2.0.x/node-states.md Outdated
@marcschier
marcschier marked this pull request as ready for review July 28, 2026 09:55
Copilot AI review requested due to automatic review settings July 28, 2026 09:55
Comment thread samples/PumpDeviceIntegrationServer/PumpNodeManager.cs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves runtime instance materialization consistency by ensuring source-generated CreateOrReplace<Child> helpers (used by NodeState.CreateChild/ReplaceChild) can assign per-instance NodeIds and stamp child identity, aligning behavior with CreateInstanceOf<Type> and removing the need for manual re-stamping in node managers (e.g., Pump sample). It also adds tests and updates documentation/migration guidance; plus a small build-hygiene change for CA2000 in the console client sample.

Changes:

  • Extend generated CreateOrReplace<Child> helpers with assignInstanceNodeIds (default true) and per-instance NodeId rebasing logic; update generator call sites to opt out in type/declaration factories.
  • Add shared runtime helper NodeInstanceExtensions.AssignNewChildInstanceNodeIds and apply it to handwritten MethodState and BaseDataVariableState CreateOrReplace* helpers.
  • Update sample + docs/migration guidance to rely on the new NodeId assignment behavior (and add targeted unit tests).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateTemplates.cs Updates generated CreateOrReplace<Child> template to stamp identity + optionally assign per-instance NodeIds.
tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateGenerator.cs Ensures generator call sites opt out of assignment when building declaration/type subtrees; adds child declaration NodeId constant for comparisons.
src/Opc.Ua.Types/State/NodeInstanceExtensions.cs Adds shared helper to assign per-instance NodeIds for newly materialized children.
src/Opc.Ua.Types/State/MethodState.cs Adds opt-in/out parameter and uses shared helper for per-instance NodeId assignment of argument properties.
src/Opc.Ua.Types/State/BaseDataVariableState.cs Adds opt-in/out parameter and uses shared helper for per-instance NodeId assignment of EnumStrings.
tests/Opc.Ua.Types.Tests/State/NodeInstanceExtensionsTests.cs Adds coverage for new per-instance NodeId assignment behavior and opt-out/caller-assigned cases.
tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeManagerGeneratorTests.cs Adds generator-output assertions for default assignment + type-factory opt-out + CreateInstanceOf guard change.
tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/NodeStateGeneratorTests.cs Updates expected generated call sites to include assignInstanceNodeIds: false.
samples/PumpDeviceIntegrationServer/PumpNodeManager.cs Removes manual recursive NodeId re-stamping; passes the real parent into CreateInstanceOfPumpType.
samples/ConsoleReferenceClient/ClientSamples.cs Adds targeted CA2000 suppression around subscription allocation with rationale.
docs/SourceGeneratedNodeManagers.md Documents runtime instance NodeId assignment behavior and helper families.
docs/migrate/2.0.x/node-states.md Adds migration note explaining changed CreateChild/ReplaceChild behavior and removal of manual re-stamping.
docs/DeviceIntegration.md Refreshes narrative to reflect new instance rebasing behavior.
Comments suppressed due to low confidence (1)

tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateTemplates.cs:1248

  • When replacement is already of the target child type (typedReplacement), the code adopts it directly but does not stamp SymbolicName/BrowseName/DisplayName. This undermines the new per-instance NodeId assignment for path-based NodeIdFactory implementations (a bare typed replacement will still collide) and contradicts the stated intent that the helper stamps the new child's identity.
                if (replacement is {{Tokens.ClassName}} typedReplacement)
                {
                    // a replacement of the matching type is used directly,
                    // replacing any child that may already exist.
                    {{Tokens.ChildName}} = typedReplacement;
                }

Comment thread tools/Opc.Ua.SourceGeneration.Core/Generators/NodeStateTemplates.cs
@codecov

codecov Bot commented Jul 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.82353% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 79.87%. Comparing base (38c7356) to head (9eb8956).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
src/Opc.Ua.Types/State/NodeInstanceExtensions.cs 87.50% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           master    #4104       +/-   ##
===========================================
+ Coverage   62.58%   79.87%   +17.29%     
===========================================
  Files        1403     1514      +111     
  Lines      190439   209245    +18806     
  Branches    33322    36008     +2686     
===========================================
+ Hits       119184   167139    +47955     
+ Misses      59788    29568    -30220     
- Partials    11467    12538     +1071     
Files with missing lines Coverage Δ
src/Opc.Ua.Types/State/BaseDataVariableState.cs 88.75% <100.00%> (+12.43%) ⬆️
src/Opc.Ua.Types/State/MethodState.cs 88.42% <100.00%> (+20.91%) ⬆️
...ceGeneration.Core/Generators/NodeStateGenerator.cs 87.53% <100.00%> (+2.25%) ⬆️
...ceGeneration.Core/Generators/NodeStateTemplates.cs 100.00% <100.00%> (ø)
src/Opc.Ua.Types/State/NodeInstanceExtensions.cs 90.38% <87.50%> (+17.65%) ⬆️

... and 618 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

marcschier and others added 3 commits July 28, 2026 13:23
…tests

Two review comments on #4104:

* docs/migrate/2.0.x/node-states.md - removed the CreateChild/ReplaceChild
  section. The source generators are new in 2.0, so their NodeId behaviour
  is not a migration topic. The behaviour stays documented in
  docs/SourceGeneratedNodeManagers.md.

* Added PumpInstanceNodeIdRegressionTests covering the pump DI server:
  - the full set of 77 model identifiers a configured pump materialises,
    captured from the manager before this change so a child that stops
    being materialised fails the test;
  - a second pump exposes the identical generated model surface, minus
    what the fluent configuration adds on top;
  - every NodeId the generated helpers assign is minted by
    PumpNodeManager.New ({parentIdentifier}_{symbolicName} in the
    parent's namespace) rather than being a type-level declaration id;
  - no NodeId is shared between two instances of the type;
  - the generated factory already carries per-instance NodeIds before
    registration, so a regression is not masked by the defensive rebase
    AsyncCustomNodeManager.AddPredefinedNodeAsync performs;
  - the alarm subtree the fluent builder attaches keeps standard
    declaration NodeIds - a pre-existing gap outside the generated
    instance helpers, pinned so it cannot drift unnoticed.

The 77-identifier baseline was captured by running the same walk against
the pre-change commit; the resulting node set and every NodeId are
identical before and after.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
The XML docs on CreateOrReplace<Child> state that a null replacement
creates a new child, and the body branches on replacement != null, but
the emitted signature declared a non-nullable BaseInstanceState. In a
nullable-enabled consumer that advertises the wrong contract and makes
the documented CreateOrReplace<Child>(context, null) call produce CS8625.

The hand-written twins (MethodState.CreateOrReplace{Input,Output}Arguments,
BaseDataVariableState.CreateOrReplaceEnumStrings) already declare
BaseInstanceState?, so the generated helpers now match them.

The generated FindChild override gets the same treatment: NodeState
declares replacement as nullable, so the override was carrying a latent
nullability mismatch with its base.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
Conflict in tests/Opc.Ua.SourceGeneration.Core.Tests/Generators/
NodeStateGeneratorTests.cs: master rewrapped the expected call-site
strings to stay inside the line-length limit while this branch appended
the assignInstanceNodeIds: false argument to them. Kept master's wrapped
form and re-applied the argument.

master also added two new templates - Create_MethodArguments and
Assign_MethodArgumentValues - that call
state.CreateOrReplace{InputArguments,OutputArguments}(context, null)
while building a declaration subtree. Both now pass
assignInstanceNodeIds: false, which preserves master's stated intent of
"leaving the identity of the property node created by its own child
factory untouched": without the opt-out the helper would mint a
per-instance NodeId for the arguments property during type-template
construction.

Verified after the merge: SourceGeneration.Core 3722 passed,
Di 303 passed, Types 8083 passed on net10.0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
@marcschier marcschier added the ready Ready to merge once CI Passes label Jul 28, 2026
The Opc.Ua.SourceGeneration.Tests jobs (net10.0 and net48) compile
generated model code against the hand-written Opc.Ua API stub in
tests/Opc.Ua.SourceGeneration.Core.Tests/CompilerUtils.cs. The stub's 73
CreateOrReplace<Child> declarations still had the old two-parameter
signature, so every generated call site that now passes
assignInstanceNodeIds: false failed to bind (CS1739), producing up to
1430 compile errors per test case. The stub now mirrors the real API:
nullable replacement plus the optional assignInstanceNodeIds parameter.

NodesetMethodArgumentTests pinned the exact emitted call
`state.CreateOrReplace{Input,Output}Arguments(context, null)`. That call
site intentionally changed in the master merge - the generated method
factories opt out of NodeId assignment so the arguments property keeps
the identity its own child factory gives it - so the expectation is
updated to match rather than relaxed.

Verified: Opc.Ua.SourceGeneration.Tests 81 passed on net10.0 and net48
(was 25 failed / 56 passed), Opc.Ua.SourceGeneration.Core.Tests 3722
passed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b019f3a2-f58d-4f7e-93db-c8a0dec656e2
@marcschier
marcschier merged commit cfd15f0 into master Jul 28, 2026
172 of 173 checks passed
@marcschier
marcschier deleted the marcschier/createorreplace-instance-nodeids branch July 28, 2026 19:22
marcschier added a commit that referenced this pull request Jul 29, 2026
Master landed #4104, which assigns instance NodeIds inside the generated CreateOrReplace<Child> helpers - the issue this branch opened. Two files conflicted.

docs/DependencyInjection.md: additive on both sides, so both sets of rows are listed.

PumpDeviceIntegrationServer: master deleted the sample's manual AssignChildNodeIds walk and its private helper, because the generator now does that work. That deletion is kept, and this branch's AttachOpenUsdRepresentation call is preserved alongside it.

Three follow-on fixes the textual merge could not make. The branch's other OpenUSD subtrees still called the now-deleted private helper, so they use master's public replacement, ISystemContext.AssignInstanceChildNodeIds, which has the same recursive NodeIdFactory semantics plus reference fix-up. Two comments that referenced the removed call were corrected. And VirtualFileSystem's per-file memory map was raised from 32 MB to 256 MB: the generator writes whole model sources through it, and #4104's extra per-child code pushed Opc.Ua.Pumps.NodeStates.ex.g.cs to 32.1 MB, so generation failed with MODELGEN003. The map uses DelayAllocatePages, so this reserves address space rather than allocating.

NOT READY TO PUSH. Four tests fail deterministically because #4104 changed when instance NodeIds are allocated, and this branch's Robotics NodeId allocator and its expectations were built around the previous flow: NewSkipsExistingNumericNodeId and BuildContextAllocatorSkipsExistingNumericNodeId expect ns=5;i=2 but get ns=5;i=11, FailedBuildsReleaseStockReservations expects 0 released reservations but sees 10, and ConfiguredPumpExposesTheFullModelSurface sees the same renumbering. Reconciling the allocator's reservation semantics with the new assignment point needs the author's intent, so it is deliberately left unresolved rather than guessed at.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
marcschier added a commit that referenced this pull request Jul 29, 2026
Since #4104 the generated CreateOrReplace<Child> helpers assign a per-instance
NodeId whenever the system context carries a NodeIdFactory. NodeState.Initialize
materialises the children of a copy through CreateChild, which goes through that
same plumbing - but the copy overwrites every child NodeId from its source right
afterwards, so each of those assignments is discarded.

For a counting factory that silently burns identifiers. For a factory that
tracks outstanding allocations it leaks them: booting the Robotics node manager
left ten reservations behind, held by nodes that no longer carried the reserved
NodeId, taken while the generated model copied the AddComment/Acknowledge and
CreateDirectory/CreateFile/MoveOrCopy argument properties of the ConditionType
and FileDirectoryType declarations. Those ten identifiers were then skipped by
every later allocation.

The assignInstanceNodeIds flag cannot carry the intent here, because it is not
part of the virtual FindChild contract the copy passes through - the declaration
factories already pass false and it is lost at NodeState.Create. Hide the
factory from the copy instead, via a context wrapper that forwards everything
else; the existing assignment sites already treat a missing factory as "do not
assign".

Also refreshes the pump NodeId surface baseline, which had not seen the OpenUSD
representation, component and signal nodes the sample adds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8cbb8cd0-f0cb-4ab0-bea2-6202fbf69485
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Ready to merge once CI Passes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Source generator: CreateOrReplace<Child> helpers should assign instance NodeIds

3 participants