mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-23 18:39:26 +08:00
1bb4bdca1d
# Description Implement the released [OPC 40100-1](https://reference.opcfoundation.org/specs/OPC-40100-1) and [OPC 40100-2](https://reference.opcfoundation.org/specs/OPC-40100-2) specifications with generated models, provider-backed runtimes, typed clients, MCP workflows, and DI/Machinery integration. - **Part 1 stays in `Opc.Ua.Vision`** and its client/server packages alongside the distinct Perception model. Provide control/acquisition state machines, jobs and synchronized steps, recipe/product/configuration/result management, file transfers, diagnostics, events, and conditions. - **Part 2 is in `Opc.Ua.MachineVision`, `.Client`, and `.Server`.** Provide asset categories, identification, image-handling aspects, health/maintenance data, topology, and transactional updates. The base Vision assemblies do not reference these optional packages; the Part 2 wire model does not depend on Part 1. - Preserve the release-pinned model bytes, wire namespaces and NodeIds. Perception types are in `Opc.Ua.Vision.Perception`; the AMCM generated namespace remains `Opc.Ua.Vision.MachineVision.AMCM`, and asset client/server extension namespaces remain `Opc.Ua.Vision.Client` and `Opc.Ua.Vision.Server`. - Use `AddOpcUaMachineVision` and `AddOpcUaMachineVisionAMCM` for model loading and factory registration. Each has node-collection, encodeable-factory-builder and node-state-factory-builder overloads. These short entry points delegate to the generated implementations; existing namespace-derived generated names remain available for compatibility. - Support multiple independent named systems/assets under one namespace-owning manager. Reuse injectable providers, file binding, bounded memory stores, optional transactional `IFileSystemProvider` persistence, state machines, telemetry and conformance infrastructure. - Keep one `docs/Vision.md` guide, titled `Machine Vision (OPC 40100-1 and OPC 40100-2)`, and one `Opc.Ua.Mcp.Vision` package. Include deterministic server/client samples. - Expose 17 Machine Vision MCP tools, including capabilities/status, bounded relationships and observation, complete small-file uploads/downloads, management, explicit control, conditions, and chunked transfers. Catalogue counts: 39 Vision-specific, 43 with connection tools, 81 Vision+Robotics, 155 Full. Every Machine Vision wire parameter has a schema description. **Compatibility and limits:** `AddVision()` remains Perception-only unless released parts are selected. Standalone Part 2 uses `AddVision().AddMachineVision()`; Machinery composition uses `AddVisionToMachinery()`. Existing tool defaults and command signatures are preserved. Observation owns and cleans up its subscription: 5 seconds/50 notifications by default, capped at 60 seconds/100 notifications. Complete-file tools are capped at 1 MiB; larger files use chunked transfers. Domain Error values remain distinct from UA service failures. **Versioning:** the seven Vision/MachineVision/MCP packages and their Debug variants follow the repository root version instead of being forced to preview. Vision.OpenUsd and its descendants remain preview-only. Development builds still inherit the root prerelease version. No release version, tag, release branch, publication policy, or feed promotion is changed. **Review actions:** all fourteen review threads have action/evidence replies and are resolved. Changes include the unified guide, Perception namespace, separate Part 2 packages and final naming, multi-system ownership explanation, interrupted-write cleanup, observer failure isolation, complete MCP parameter descriptions, and explicit package inventory. The preview-layout migration topic and the requested ReleaseProcess caveat were removed. The test Publish collector now retries recognized transient failures within one cancellation budget and propagates other exceptions. Commit69b1f5db9applies the requested short loader names and renames standalone hosting to `AddMachineVision`, with callers, tests and documentation updated. The intervening master merge is preserved. ### Verification **Latest API naming changes, verified locally:** | Scope | Result | | --- | --- | | Full Vision suite, net10 | 572 passed, 1 existing skip, 0 failed | | Full Vision suite, net48 | 515 passed, 0 skipped, 0 failed | | Final loader/hosting cases after cross-TFM null-guard adjustment | 9 passed, 0 failed, 0 skipped | | Machine Vision MCP and profile cases | 198 passed, 0 failed, 0 skipped | | Main and MCP AOT test-project compilation | Both built with 0 warnings/errors | | MCP AOT companion, managed execution | 5 passed, 0 failed, 0 skipped; not claimed as native execution | `ModelLoaderNamesRegisterTypedFactories`, `ModelLoaderNamesRejectNullArguments`, and `GeneratedModelsLoadTogetherAndCreateTypedInstances` cover all six loader overloads, fluent return identities, typed instances, namespace coexistence and null guards. Existing standalone Part 2 tests verify idempotent registration, one-owner enforcement and null handling through `AddMachineVision`. No source-generator implementation, CLR namespace, wire identity or model payload was changed for these naming updates. The current69b1f5db9revision is running [CI](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38165826937), [package validation](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38165823359), [MCP usability](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38165826874), [CodeQL](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38165826906), and [Images CI](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38165826884). These runs are not claimed as completed results. **Earlier completed verification:** all seven default-TFM package archives were inspected after the package rename: exact IDs, 27 clean DLLs, correct dependencies/readmes/licenses, and unchanged pinned model hashes. [CI onc75ec4fa6](https://github.com/OPCFoundation/UA-.NETStandard/actions/runs/38142519760) passed, including the formerly failing Linux batch and required summary. Its unchanged coverage gate passed at **88.56% lines, 78.96% branches, 84.28% patch** against the 75% patch floor. The later bounded-Publish-helper regressions reproduced 13 failures before the fix and passed afterward; the full mainline triggering fixture passed 67 tests on each of net10/net48. These are historical results, not current full-matrix results. <details> <summary>Executed API naming validation commands and outcomes</summary> Run from the worktree root using the established artifact trees. Dependency builds were performed before the no-build full net10 run; the focused loader/hosting selection was rebuilt and rerun after the final null-guard adjustment. ```powershell $files = 'C:\Users\mschier\.copilot\session-state\deb2ba10-b0e9-4a44-8783-8331bc850814\files' $env:DOTNET_CLI_USE_MSBUILD_SERVER = '0' $env:TEMP = 'C:\Temp\mv-deb2\followup' $env:TMP = $env:TEMP # Passed: 572; Failed: 0; Skipped: 1. dotnet test tests\Opc.Ua.Vision.Tests\Opc.Ua.Vision.Tests.csproj -c Release -f net10.0 --no-build --no-restore --artifacts-path artifacts\vision-followup -p:CustomTestTarget=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --logger 'console;verbosity=minimal' --logger trx --results-directory "$files\vision-api-names-net10" -- 'NUnit.WorkDirectory=C:\Temp\mv-deb2\followup' # Passed: 9; Failed: 0; Skipped: 0, on the final source. dotnet test tests\Opc.Ua.Vision.Tests\Opc.Ua.Vision.Tests.csproj -c Release -f net10.0 --no-restore --artifacts-path artifacts\vision-followup -p:CustomTestTarget=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --filter 'FullyQualifiedName~MachineVisionModelTests|FullyQualifiedName~StandalonePartTwoRegistrationUsesOneOwnerAndOneModelProvider|FullyQualifiedName~StandalonePartTwoRequiresRegistrationAndRejectsCompetingOwnership' --logger 'console;verbosity=minimal' --logger trx --results-directory "$files\vision-api-names-final-model" -- 'NUnit.WorkDirectory=C:\Temp\mv-deb2\followup' # Passed: 198; Failed: 0; Skipped: 0. dotnet test tests\Opc.Ua.Tools.Tests\Opc.Ua.Tools.Tests.csproj -c Release -f net10.0 --no-restore --artifacts-path artifacts\vision-followup -p:CustomTestTarget=net10.0 -p:UseSharedCompilation=false -m:1 -nr:false --filter 'FullyQualifiedName~VisionMachineToolsTests|FullyQualifiedName~McpHostBuilderTests|FullyQualifiedName~McpToolProfileCompositionTests' --logger 'console;verbosity=minimal' --logger trx --results-directory "$files\vision-api-names-final-mcp" -- 'NUnit.WorkDirectory=C:\Temp\mv-deb2\followup' $env:TEMP = 'C:\Temp\mv-deb2\followup48' $env:TMP = $env:TEMP # Passed: 515; Failed: 0; Skipped: 0. dotnet test tests\Opc.Ua.Vision.Tests\Opc.Ua.Vision.Tests.csproj -c Debug -f net48 --no-restore --artifacts-path "$files\solution-net48-artifacts" -p:CustomTestTarget=net48 -p:UseSharedCompilation=false -m:1 -nr:false --logger 'console;verbosity=minimal' --logger trx --results-directory "$files\vision-api-names-net48-final" -- 'NUnit.WorkDirectory=C:\Temp\mv-deb2\followup48' ``` Opt-in long-running/stress/durable tiers have not been rerun locally; the unqualified **all tests** checklist item remains open. Original implementation matrix counts are not presented as current results. </details> ## Related Issues - Fixes #4732. The requester reviewed and approved the implementation plan and subsequent packaging/API decisions in the development session. The tracking issue records the design; maintainer architectural discussion/sign-off there is still pending. This PR remains a draft. ## Checklist _Put an `x` in the boxes that apply. You can complete these step by step after opening the PR._ - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [x] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] 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. - [x] I have addressed **all** PR feedback received.
179 lines
9.3 KiB
Markdown
179 lines
9.3 KiB
Markdown
# Cross-Assembly Model Dependencies
|
|
|
|
## Contents
|
|
|
|
- [Attribute shape](#attribute-shape)
|
|
- [Payload wire format (`ModelDependencyV1`)](#payload-wire-format-modeldependencyv1)
|
|
- [Diagnostics](#diagnostics)
|
|
- [Implementation](#implementation)
|
|
|
|
The OPC UA source generator emits `[assembly: Opc.Ua.ModelDependencyAttribute(...)]`
|
|
metadata on every assembly that has nodesets or design files in `<AdditionalFiles>`.
|
|
A single attribute carries both the lightweight dependency-closure information
|
|
**and**, on the assembly's self-declaration entry, a compact binary type-table
|
|
payload that downstream source generators can decode without re-walking
|
|
`AdditionalFiles`. Each instance of the attribute records, for one model the
|
|
assembly emits or transitively consumes:
|
|
|
|
- the model URI,
|
|
- the C# namespace prefix the generator used,
|
|
- (when known) the version and publication date,
|
|
- the C# identifier of the assembly's `Namespaces` class entry for the model,
|
|
- and — on self-declaration entries only — a base64-encoded Deflate-compressed
|
|
`ModelDependencyV1` type-table payload.
|
|
|
|
Downstream consumers that reference such an assembly do not need to re-add
|
|
those upstream nodesets to their own `<AdditionalFiles>`. The generator scans the
|
|
attributes on referenced assemblies and uses them to:
|
|
|
|
1. **Suppress the missing-dependency error** in nodeset dependency collection
|
|
when the missing namespace is known to be supplied by a referenced assembly.
|
|
2. **Apply override resolution** when a local `<AdditionalFiles>` entry refers
|
|
to a model URI that is also in a referenced assembly:
|
|
- **Same C# prefix** → local generation is silently skipped (the referenced
|
|
assembly already supplies the types; duplicate emission would cause
|
|
`CS0101`).
|
|
- **Different C# prefix** → local generation proceeds; types live in a
|
|
different C# namespace so no symbol conflict occurs.
|
|
3. **Cross-namespace prefix mapping** — when a local generator loads a
|
|
NodeSet2 that depends on another model, it auto-generates dependency
|
|
prefixes (e.g. `Opc.Ua.DI`) that may not match the C# namespace the
|
|
referenced assembly actually uses (`Opc.Ua.Di`). The override step rewrites
|
|
the dependency namespace's `Prefix` (and `Name`) to match the values
|
|
published by the referenced assembly's `[ModelDependencyAttribute]`, so
|
|
cross-namespace type references emitted into the local code compile
|
|
correctly. The target namespace itself is never rewritten.
|
|
4. **Tie-break** when more than one referenced assembly provides the same model
|
|
URI by selecting the entry with the highest `(Version, PublicationDate)`
|
|
lexicographic tuple. The losing entries are reported as `MODELGEN012` info
|
|
diagnostics.
|
|
5. **Import the type-table payload** on self-declaration entries so the
|
|
validator's node table is pre-populated with the upstream's types,
|
|
children, method arguments, DataType fields, and effective variable
|
|
metadata. Cross-namespace
|
|
`BaseType` / `TypeDefinition` / `DataType` references in the consumer's
|
|
own models then resolve against the imported types without needing the
|
|
upstream NodeSet2/ModelDesign XML in `AdditionalFiles`.
|
|
|
|
## Attribute shape
|
|
|
|
`Opc.Ua.Vision` produces the Perception and Part 1 [Machine Vision](Vision.md)
|
|
models with separate CLR namespaces and loaders. `Opc.Ua.MachineVision`
|
|
produces the Part 2 AMCM model, which consumes DI and Machinery independently
|
|
of Part 1. Recursive optional instance
|
|
declarations, such as AMCM SEMI E10 `SubStates`, keep their type information without
|
|
recursively materializing an unbounded default instance tree.
|
|
|
|
The attribute constructor accepts six arguments (the trailing four are
|
|
optional):
|
|
|
|
```csharp
|
|
[assembly: Opc.Ua.ModelDependencyAttribute(
|
|
modelUri: "http://opcfoundation.org/UA/DI/",
|
|
prefix: "Opc.Ua.Di",
|
|
version: "1.05.0",
|
|
publicationDate: "2025-11-15T00:00:00Z",
|
|
name: "OpcUaDi",
|
|
payload: "qscBA…<base64 ModelDependencyV1>…AAA=")]
|
|
```
|
|
|
|
The `name` parameter records the C# identifier the assembly used inside its
|
|
`Namespaces` class — i.e. the `name` consumers must use when emitting
|
|
`global::{Prefix}.Namespaces.{Name}` cross-namespace constant references.
|
|
|
|
The `payload` parameter is non-null only on the assembly's own
|
|
self-declaration entry. Transitive-dependency entries (the one-per-referenced-
|
|
model rows the generator re-emits) carry `null`. The producing assembly is the
|
|
canonical source of its type-table description.
|
|
|
|
## Payload wire format (`ModelDependencyV1`)
|
|
|
|
Encoding lives in
|
|
`tools/Opc.Ua.SourceGeneration.Core/Dependency/ModelDependencyV1.cs`:
|
|
|
|
- Magic header: `0xAA 0xC7`
|
|
- Version byte: `0x01`
|
|
- Compression byte: `0x01` (Deflate)
|
|
- Body (compressed): `ModelUri` string + node array, each carrying symbolic
|
|
name/namespace, class name, kind, base-type chain, numeric/string NodeId,
|
|
abstract / enumeration flags, DataType fields, and declared instance
|
|
children. Variable children also carry access and explicit user-access
|
|
bitmasks, minimum sampling interval, historizing state, the corresponding
|
|
presence flags, and serialized default-value XML. Method children carry
|
|
their argument lists and effective method identity. The payload is
|
|
deterministically sorted by
|
|
`(SymbolicNamespace, SymbolicName)` so the produced base64 string is
|
|
byte-reproducible across builds.
|
|
|
|
Readers reject unknown versions cleanly and the downstream pipeline falls
|
|
back to explicit `AdditionalFiles` resolution when a payload cannot be
|
|
decoded.
|
|
|
|
## Diagnostics
|
|
|
|
| ID | Severity | Meaning |
|
|
| ------------- | -------- | ------------------------------------------------------------------ |
|
|
| `MODELGEN010` | Warning | A `[NodeManager]` attribute could not be bound to a model (unmatched selector, or ambiguous when the project has multiple models). |
|
|
| `MODELGEN012` | Info | Multiple referenced assemblies provide the same model URI. |
|
|
| `MODELGEN013` | Info | Local model skipped because a referenced assembly provides it. |
|
|
|
|
## Implementation
|
|
|
|
The Roslyn-side scan lives in
|
|
`tools/Opc.Ua.SourceGeneration/ReferencedModelDependencyScanner.cs` and uses
|
|
`IAssemblySymbol.GetAttributes()` so it works for both
|
|
`PortableExecutableReference` and `CompilationReference`, hooks Roslyn's
|
|
per-symbol incremental cache, avoids file IO, and is AOT-safe inside the
|
|
generator. Each attribute is read into a `ModelDependencyReference` whose
|
|
`GetDependency()` method lazily decodes the payload through a memoising
|
|
`ConditionalWeakTable` keyed on the payload string.
|
|
|
|
The emitter lives in
|
|
`tools/Opc.Ua.SourceGeneration.Core/Generators/ModelDependencyGenerator.cs`
|
|
and produces one `{prefix}.ModelDependencies.g.cs` per generated model
|
|
containing assembly-attribute lines for the model itself (with the
|
|
`ModelDependencyV1` payload) and every model it consumes (with a `null`
|
|
payload). The template lives in `ModelDependencyTemplates.cs` and uses the
|
|
shared `Token` infrastructure for replacement.
|
|
|
|
The cross-namespace prefix override step lives in
|
|
`tools/Opc.Ua.SourceGeneration.Core/Generators.cs` as
|
|
`OverrideDependencyPrefixes`. It runs after `OpenModelDesign` and before
|
|
generation so that all downstream emitters see the harmonised prefix/name
|
|
values.
|
|
|
|
Both `GenerateCode` passes (NodeSet2 and ModelDesign) take the referenced
|
|
models as a single per-URI map of `ModelDependencyReference` and derive the
|
|
decoded payload map from it themselves via `BuildReferencedDependencyMap` —
|
|
the reference carries the raw base64 payload and memoises `GetDependency()`,
|
|
so the payload map is never threaded as a separate argument.
|
|
|
|
The payload-import surface lives directly on
|
|
`tools/Opc.Ua.SourceGeneration.Core/Schema/ModelDesignValidator.cs` (the
|
|
former `ModelDesignValidator.SnapshotImport.cs` partial was folded into the
|
|
main file). The validator's `ImportDependency(dependency, prefix, name)` API
|
|
queues a `ModelDependencyV1` for ingestion; `ApplyPendingDependencies()`
|
|
materialises the carried types into the validator's node table before the
|
|
dependency-loading pass walks `AdditionalFiles`, so consumer types can
|
|
resolve `BaseType` / `TypeDefinition` / `DataType` references against the
|
|
upstream's published types without those upstream models being present in
|
|
`AdditionalFiles`. Payloads for models that *are* present as design files in
|
|
the current compilation are skipped — the explicit file is authoritative.
|
|
After all design files are loaded, the validator links the
|
|
payload-materialised data types (base types, field data types, structure /
|
|
enumeration classification), so a local `ModelDesign` structure can subtype
|
|
a structure published only through a referenced assembly's payload.
|
|
|
|
Design files loaded as dependencies of the current target skip full
|
|
dictionary validation, so the validator links them the same way after
|
|
loading: their data types (as above) and their instances — the
|
|
`TypeDefinition` / `DataType` node references of the types' children,
|
|
variable-type data type restrictions, and method arguments including the
|
|
`InputArguments` / `OutputArguments` argument properties. A target
|
|
`ModelDesign` can therefore declare an `Object` or `Variable` whose
|
|
`TypeDefinition` is a type from another design file and have its inherited
|
|
children generate exactly as if the type were declared locally. Access level,
|
|
explicit user access, minimum sampling interval, historizing state, and
|
|
default values are retained when those inherited children come only from a
|
|
referenced assembly's payload.
|