mirror of
https://github.com/OPCFoundation/UA-.NETStandard.git
synced 2026-10-19 07:02:16 +08:00
96c5ef1623
## Proposed changes Closes #4537. The configure pipeline now lives in `FluentNodeManagerBase`, so a hand-written fluent node manager only overrides `ConfigureAsync` and no longer writes `CreateAddressSpaceAsync` itself. `FluentNodeManagerBase.CreateAddressSpaceAsync` now: 1. loads the predefined nodes (as `AsyncCustomNodeManager` did before), 2. creates a builder for `NamespaceIndex` with `CreateFluentBuilder`, 3. awaits `ConfigureAsync(builder, ct)`, 4. runs `RegisterAuthoredNodesAsync` and then `CompleteConfigureAsync`, 5. finishes with `SealConfigurationAsync`, which seals the builder, replays `NotifyNodeAdded`, then activates behaviors and simulations. ### Breaking change (2.0) Some managers need a different order, for example a post-setup runner before sealing, a builder for another namespace, or work after sealing. They must stop calling `base.CreateAddressSpaceAsync`, because that would now run a second, empty pipeline and seal the manager-owned registries too early. These managers now call `LoadPredefinedNodesAsync(SystemContext, externalReferences, ct)` and keep driving their own pipeline: - the source-generated `NodeManager` template (the ordering test in `NodeManagerGeneratorTests` is updated) - `DiNodeManager`, `Isa95NodeManager`, `PositioningNodeManager`, `RobotIntentNodeManager`, `VisionNodeManager` - `samples/OpenUsd/SiteCompositionServer/SiteNodeManager` These now use the base pipeline: - `samples/FluentApi/BasicFluentApiServer/BasicFluentNodeManager` becomes a single `ConfigureAsync` override. Before this change it never sealed its builder. - the hosting `FluentNodeManager` (from `AddNodeManager(namespaceUri, build)`) becomes a `ConfigureAsync` override. It now seals through `SealConfigurationAsync`, so `OnNodeAdded` handlers wired in the build delegate fire. Before, it called `builder.SealAsync()` directly and they did not. Docs (`NodeManagers.md`, `DeviceIntegration.md`) are updated. `NodeManagers.md` gets a short hand-written `ConfigureAsync` example. ## Types of changes - [x] Breaking change (fix or feature that would cause existing functionality to change) - [x] Enhancement (non-breaking change which adds functionality) ## Checklist - [x] I have read the CONTRIBUTING doc. - [x] I have signed the CLA. - [x] I ran tests locally with my changes, all passed. - [x] I fixed all failing tests in the CI pipelines. - [x] I fixed all introduced issues with CodeQL and LGTM. - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added necessary documentation (if appropriate). ## Tests New `FluentNodeManagerBasePipelineTests`: nodes authored in `ConfigureAsync` get registered, the Objects-folder reference is mirrored into `externalReferences`, `OnNodeAdded` fires once, and the builder is sealed afterwards. Local run on net10.0, all passing: - Opc.Ua.Server.Tests (Fluent/Hosting/StateMachine/RuntimeNodeSet filter): 1286 - Opc.Ua.SourceGeneration.Core.Tests (NodeManager filter): 40 - Opc.Ua.Di.Tests 426, ISA95 136, Positioning 69, Robotics 636, Robotics.Intent 22, Vision 484 (+1 skipped), Vision.Intent 14, OpenUsd 921 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
495 lines
21 KiB
C#
495 lines
21 KiB
C#
/* ========================================================================
|
|
* Copyright (c) 2005-2025 The OPC Foundation, Inc. All rights reserved.
|
|
*
|
|
* OPC Foundation MIT License 1.00
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
* The complete license agreement can be found here:
|
|
* http://opcfoundation.org/License/MIT/1.00/
|
|
* ======================================================================*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using Opc.Ua;
|
|
using Opc.Ua.OpenUsd;
|
|
using Opc.Ua.OpenUsd.Server;
|
|
using Opc.Ua.Server;
|
|
using Opc.Ua.Server.Fluent;
|
|
using Opc.Ua.Server.NodeManager;
|
|
|
|
namespace SiteComposition
|
|
{
|
|
/// <summary>
|
|
/// Runtime options for the site composition sample.
|
|
/// </summary>
|
|
public sealed class SiteCompositionOptions
|
|
{
|
|
/// <summary>
|
|
/// Gets or sets the endpoint of the server that owns the pumps.
|
|
/// </summary>
|
|
public string? PumpServerEndpointUrl { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets the endpoint of the server that owns the generator sets.
|
|
/// </summary>
|
|
public string? GeneratorServerEndpointUrl { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// A supervisory node manager that owns no devices and composes the machines of
|
|
/// other servers into a single scene.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// This is the SCADA-level counterpart to a device server. It publishes a site
|
|
/// stage - ground, buildings, lighting and a control-room camera - and declares
|
|
/// one cross-server component binding per subordinate server. A connector run
|
|
/// with federation enabled opens a session to each of those servers, discovers
|
|
/// their representations and drives their bindings into the same stage, so one
|
|
/// scene shows machines owned by three different servers, live.
|
|
/// </para>
|
|
/// <para>
|
|
/// Nothing is mirrored. The site server never proxies a subordinate's address
|
|
/// space; it only says where the machines are and lets the connector talk to
|
|
/// each owner directly, so there is no cache to invalidate and no second copy of
|
|
/// the truth.
|
|
/// </para>
|
|
/// </remarks>
|
|
public sealed partial class SiteNodeManager : FluentNodeManagerBase
|
|
{
|
|
private const string SiteRootLayerIdentifier = "asset-repo/Site.usd";
|
|
private const string SitePrimPath = "/Site";
|
|
private const string PumpHallPrimPath = "/Site/PumpHall";
|
|
private const string PowerhousePrimPath = "/Site/Powerhouse";
|
|
|
|
private static readonly Guid s_pumpComponentId =
|
|
new("b41d7e20-0001-4f52-9a63-1c7d8e0b5a01");
|
|
|
|
private static readonly Guid s_generatorComponentId =
|
|
new("b41d7e20-0002-4f52-9a63-1c7d8e0b5a02");
|
|
|
|
private readonly SiteCompositionOptions m_options;
|
|
private readonly ILogger m_log;
|
|
private OpenUsdRootState? m_openUsdRoot;
|
|
private OpenUsdStageState? m_siteStage;
|
|
private FolderState? m_areas;
|
|
|
|
/// <summary>
|
|
/// Initialises a new <see cref="SiteNodeManager"/>.
|
|
/// </summary>
|
|
/// <param name="server">The server the manager belongs to.</param>
|
|
/// <param name="configuration">The application configuration.</param>
|
|
/// <param name="options">Sample options naming the subordinate servers.</param>
|
|
public SiteNodeManager(
|
|
IServerInternal server,
|
|
ApplicationConfiguration configuration,
|
|
IOptions<SiteCompositionOptions>? options = null)
|
|
: base(server, configuration, Namespaces.Site, Opc.Ua.OpenUsd.Namespaces.OpenUSD)
|
|
{
|
|
m_options = options?.Value ?? new SiteCompositionOptions();
|
|
m_log = server.Telemetry.CreateLogger<SiteNodeManager>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the namespace index this sample's instances live in.
|
|
/// </summary>
|
|
private ushort SiteNamespaceIndex => NamespaceIndexes[0];
|
|
|
|
/// <inheritdoc/>
|
|
protected override ValueTask<NodeStateCollection> LoadPredefinedNodesAsync(
|
|
ISystemContext context,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var nodes = new NodeStateCollection();
|
|
nodes.AddOpcUaOpenUsd(context);
|
|
return new ValueTask<NodeStateCollection>(nodes);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public override async ValueTask CreateAddressSpaceAsync(
|
|
IDictionary<NodeId, IList<IReference>> externalReferences,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
await LoadPredefinedNodesAsync(SystemContext, externalReferences, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
|
|
NodeManagerBuilder builder = CreateFluentBuilder(SiteNamespaceIndex);
|
|
MaterialiseSiteTopology(builder);
|
|
await MaterialiseOpenUsdFacilityAsync(cancellationToken).ConfigureAwait(false);
|
|
await MaterialiseCrossServerCompositionAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
LinkOpenUsdRootToServer(externalReferences);
|
|
|
|
// Registers the site subtree and mirrors its inverse Organizes
|
|
// reference to the Objects folder into externalReferences.
|
|
await RegisterAuthoredNodesAsync(builder, cancellationToken).ConfigureAwait(false);
|
|
await CompleteConfigureAsync(externalReferences, cancellationToken).ConfigureAwait(false);
|
|
await builder.SealAsync(cancellationToken).ConfigureAwait(false);
|
|
|
|
m_log.SiteAddressSpaceReady(
|
|
m_options.PumpServerEndpointUrl ?? "(none)",
|
|
m_options.GeneratorServerEndpointUrl ?? "(none)");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a small browsable site hierarchy, so the supervisory server is a
|
|
/// real server rather than a bare stage host.
|
|
/// </summary>
|
|
/// <param name="builder">The fluent builder staging the site subtree.</param>
|
|
private void MaterialiseSiteTopology(NodeManagerBuilder builder)
|
|
{
|
|
var areas = new FolderState(null)
|
|
{
|
|
SymbolicName = "Site",
|
|
BrowseName = new QualifiedName("Site", SiteNamespaceIndex),
|
|
DisplayName = new LocalizedText("Site"),
|
|
TypeDefinitionId = Opc.Ua.ObjectTypeIds.FolderType,
|
|
NodeId = new NodeId("Site", SiteNamespaceIndex),
|
|
EventNotifier = EventNotifiers.None,
|
|
};
|
|
|
|
AddArea(areas, "PumpHall", "Pump Hall", m_options.PumpServerEndpointUrl);
|
|
AddArea(areas, "Powerhouse", "Powerhouse", m_options.GeneratorServerEndpointUrl);
|
|
|
|
// Staging assigns the child NodeIds through this manager's own
|
|
// New(...) factory and organises the folder under Objects.
|
|
m_areas = builder.Add(areas).Node;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds one area object recording which server owns the machines in it.
|
|
/// </summary>
|
|
/// <param name="parent">The site folder.</param>
|
|
/// <param name="name">Area browse name.</param>
|
|
/// <param name="displayName">Area display name.</param>
|
|
/// <param name="endpointUrl">Endpoint of the server that owns the area.</param>
|
|
private void AddArea(FolderState parent, string name, string displayName, string? endpointUrl)
|
|
{
|
|
var area = new BaseObjectState(parent)
|
|
{
|
|
SymbolicName = name,
|
|
BrowseName = new QualifiedName(name, SiteNamespaceIndex),
|
|
DisplayName = new LocalizedText(displayName),
|
|
TypeDefinitionId = Opc.Ua.ObjectTypeIds.BaseObjectType,
|
|
ReferenceTypeId = ReferenceTypeIds.Organizes,
|
|
};
|
|
parent.AddChild(area);
|
|
|
|
// The generic variable states are abstract in this stack, so the concrete
|
|
// carrier is the non-generic one with a Variant value.
|
|
var source = new BaseDataVariableState(area)
|
|
{
|
|
SymbolicName = "SourceServer",
|
|
BrowseName = new QualifiedName("SourceServer", SiteNamespaceIndex),
|
|
DisplayName = new LocalizedText("SourceServer"),
|
|
TypeDefinitionId = Opc.Ua.VariableTypeIds.PropertyType,
|
|
ReferenceTypeId = ReferenceTypeIds.HasProperty,
|
|
DataType = Opc.Ua.Types.DataTypeIds.String,
|
|
ValueRank = ValueRanks.Scalar,
|
|
AccessLevel = AccessLevels.CurrentRead,
|
|
UserAccessLevel = AccessLevels.CurrentRead,
|
|
Value = Variant.From(endpointUrl ?? string.Empty),
|
|
};
|
|
area.AddChild(source); }
|
|
|
|
/// <summary>
|
|
/// Declares one cross-server component binding per subordinate server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The binding carries the endpoint of the server that owns the machines.
|
|
/// A connector without federation still composes the placeholder prim and
|
|
/// renders the site shell; with federation it opens a session to each named
|
|
/// server and brings that server's machines in live. Failing closed on the
|
|
/// client rather than the server is deliberate - the site server can
|
|
/// advertise where the machines are without dictating that anyone connect.
|
|
/// </remarks>
|
|
private async ValueTask MaterialiseCrossServerCompositionAsync(
|
|
CancellationToken cancellationToken)
|
|
{
|
|
if (m_siteStage == null || m_areas == null)
|
|
{
|
|
return;
|
|
}
|
|
ushort ns = (ushort)Server.NamespaceUris.GetIndex(Opc.Ua.OpenUsd.Namespaces.OpenUSD);
|
|
|
|
OpenUsdRepresentationState site = SystemContext.CreateRepresentation(
|
|
m_areas, m_siteStage.NodeId, SitePrimPath, ns);
|
|
|
|
AddSubordinate(site, ns, "PumpHall", s_pumpComponentId, PumpHallPrimPath,
|
|
m_options.PumpServerEndpointUrl);
|
|
AddSubordinate(site, ns, "Powerhouse", s_generatorComponentId, PowerhousePrimPath,
|
|
m_options.GeneratorServerEndpointUrl);
|
|
|
|
if (m_openUsdRoot?.Representations is FolderState registry)
|
|
{
|
|
site.RegisterInDiscovery(registry);
|
|
}
|
|
|
|
// The representation is built after its owner was registered, so it has
|
|
// to register its own subtree - otherwise it browses from nowhere and a
|
|
// connector reports that the server publishes no representation at all.
|
|
SystemContext.AssignInstanceChildNodeIds(site);
|
|
await AddPredefinedNodeAsync(SystemContext, site, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Declares one subordinate server as a component of the site.
|
|
/// </summary>
|
|
/// <param name="site">The site representation.</param>
|
|
/// <param name="ns">OpenUSD namespace index.</param>
|
|
/// <param name="name">Binding name.</param>
|
|
/// <param name="definitionId">Stable binding definition id.</param>
|
|
/// <param name="primPath">Scope the subordinate's machines land in.</param>
|
|
/// <param name="endpointUrl">Endpoint of the owning server.</param>
|
|
private void AddSubordinate(
|
|
OpenUsdRepresentationState site,
|
|
ushort ns,
|
|
string name,
|
|
Guid definitionId,
|
|
string primPath,
|
|
string? endpointUrl)
|
|
{
|
|
if (string.IsNullOrEmpty(endpointUrl))
|
|
{
|
|
return;
|
|
}
|
|
site.AddComponentBinding(
|
|
SystemContext,
|
|
ns,
|
|
name,
|
|
definitionId,
|
|
OpenUsdCardinalityEnum.Many,
|
|
OpenUsdCompositionArcEnum.Reference,
|
|
primPath,
|
|
componentRepresentation: default,
|
|
assetReference: null,
|
|
dynamic: false,
|
|
changeEventSource: default,
|
|
componentServerUri: endpointUrl,
|
|
componentEndpointUrl: endpointUrl);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the well-known OpenUSD facility and the site stage.
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token.</param>
|
|
private async ValueTask MaterialiseOpenUsdFacilityAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
ushort ns = (ushort)Server.NamespaceUris.GetIndex(Opc.Ua.OpenUsd.Namespaces.OpenUSD);
|
|
|
|
OpenUsdRootState root = SystemContext.CreateInstanceOfOpenUsdRootType(
|
|
null!, new QualifiedName("OpenUSD", ns));
|
|
root.NodeId = new NodeId("OpenUSD", ns);
|
|
|
|
FolderState stages = root.Stages ?? root.CreateOrReplaceStages(SystemContext, null!);
|
|
_ = root.Representations ?? root.CreateOrReplaceRepresentations(SystemContext, null!);
|
|
|
|
m_siteStage = SystemContext.CreateInstanceOfOpenUsdStageType(
|
|
stages, new QualifiedName("SiteStage", ns));
|
|
stages.AddChild(m_siteStage);
|
|
m_siteStage.CreateOrReplaceRootLayerIdentifier(SystemContext, null!)
|
|
.Value = SiteRootLayerIdentifier;
|
|
|
|
List<ServedAsset> served = LoadServedAssets();
|
|
byte[] rootLayerBytes = served
|
|
.Find(a => a.Kind == OpenUsdAssetKindEnum.RootLayer)!.Bytes;
|
|
byte[] digest;
|
|
#pragma warning disable CA1850 // Prefer static HashData (net48/netstandard2.0 compatibility)
|
|
using (var sha = System.Security.Cryptography.SHA256.Create())
|
|
{
|
|
digest = sha.ComputeHash(rootLayerBytes);
|
|
}
|
|
#pragma warning restore CA1850
|
|
m_siteStage.CreateOrReplaceRootLayerDigest(SystemContext, null!)
|
|
.Value = (ByteString)digest;
|
|
m_siteStage.CreateOrReplaceRootLayerDigestAlgorithm(SystemContext, null!)
|
|
.Value = OpenUsdDigestAlgorithmEnum.Sha256;
|
|
|
|
root.AddReference(ReferenceTypeIds.HasComponent, true, Opc.Ua.ObjectIds.Server);
|
|
UsdAssetDelivery.AttachStageAssets(SystemContext, m_siteStage, ns, served);
|
|
|
|
SystemContext.AssignInstanceChildNodeIds(root);
|
|
await AddPredefinedNodeAsync(SystemContext, root, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
m_openUsdRoot = root;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
m_siteStage = null;
|
|
m_openUsdRoot = null;
|
|
m_log.OpenUsdFacilityFailed(ex);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the site shell layer this server serves.
|
|
/// </summary>
|
|
/// <returns>The served asset closure.</returns>
|
|
/// <remarks>
|
|
/// Only the site shell. The machine geometry belongs to the subordinate
|
|
/// servers, which serve their own component assets to the same connector.
|
|
/// </remarks>
|
|
private static List<ServedAsset> LoadServedAssets()
|
|
{
|
|
return
|
|
[
|
|
new ServedAsset("Site.usda", OpenUsdAssetKindEnum.RootLayer, ReadEmbeddedAsset("Site.usda")),
|
|
];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads one embedded USD layer.
|
|
/// </summary>
|
|
/// <param name="resourceName">Logical resource name.</param>
|
|
/// <returns>The layer bytes, or an empty array when absent.</returns>
|
|
private static byte[] ReadEmbeddedAsset(string resourceName)
|
|
{
|
|
using Stream? s = typeof(SiteNodeManager).Assembly.GetManifestResourceStream(resourceName);
|
|
if (s == null)
|
|
{
|
|
return [];
|
|
}
|
|
using var ms = new MemoryStream();
|
|
s.CopyTo(ms);
|
|
return ms.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Makes the OpenUSD facility browsable from the Server Object.
|
|
/// </summary>
|
|
/// <param name="externalReferences">The shared reference dictionary.</param>
|
|
private void LinkOpenUsdRootToServer(IDictionary<NodeId, IList<IReference>> externalReferences)
|
|
{
|
|
if (m_openUsdRoot == null)
|
|
{
|
|
return;
|
|
}
|
|
if (!externalReferences.TryGetValue(Opc.Ua.ObjectIds.Server, out IList<IReference>? refs))
|
|
{
|
|
refs = new List<IReference>();
|
|
externalReferences[Opc.Ua.ObjectIds.Server] = refs;
|
|
}
|
|
refs.Add(new NodeStateReference(
|
|
ReferenceTypeIds.HasComponent, false, m_openUsdRoot.NodeId));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Namespaces this sample owns.
|
|
/// </summary>
|
|
internal static class Namespaces
|
|
{
|
|
/// <summary>
|
|
/// The site composition sample instance namespace.
|
|
/// </summary>
|
|
public const string Site = "http://opcfoundation.org/UA/Samples/SiteComposition/";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the <see cref="SiteNodeManager"/> for the DI hosting pipeline.
|
|
/// </summary>
|
|
public sealed class SiteNodeManagerFactory : IAsyncNodeManagerFactory
|
|
{
|
|
private readonly IOptions<SiteCompositionOptions>? m_options;
|
|
|
|
/// <summary>
|
|
/// Initialises a factory without options.
|
|
/// </summary>
|
|
public SiteNodeManagerFactory()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initialises a factory with sample options.
|
|
/// </summary>
|
|
/// <param name="options">Sample options.</param>
|
|
public SiteNodeManagerFactory(IOptions<SiteCompositionOptions>? options)
|
|
{
|
|
m_options = options;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public ArrayOf<string> NamespacesUris => new string[]
|
|
{
|
|
Namespaces.Site,
|
|
Opc.Ua.OpenUsd.Namespaces.OpenUSD,
|
|
}.ToArrayOf();
|
|
|
|
/// <inheritdoc/>
|
|
public ValueTask<IAsyncNodeManager> CreateAsync(
|
|
IServerInternal server,
|
|
ApplicationConfiguration configuration,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
#pragma warning disable CA2000 // ownership transferred to server
|
|
IAsyncNodeManager nodeManager = new SiteNodeManager(server, configuration, m_options);
|
|
#pragma warning restore CA2000
|
|
return new ValueTask<IAsyncNodeManager>(nodeManager);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Event id offsets for the site composition server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Each per-file <c><ClassName>Log</c> class allocates its event ids relative
|
|
/// to the offset constant below, using <c>offset + <zero-based message index></c>,
|
|
/// so two files cannot silently claim the same id.
|
|
/// </remarks>
|
|
internal static class SiteCompositionServerEventIds
|
|
{
|
|
/// <summary>
|
|
/// Offset for messages raised by <see cref="SiteNodeManager"/>.
|
|
/// </summary>
|
|
public const int SiteNodeManager = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Source-generated log messages for <see cref="SiteNodeManager"/>.
|
|
/// </summary>
|
|
internal static partial class SiteNodeManagerLog
|
|
{
|
|
[LoggerMessage(
|
|
EventId = SiteCompositionServerEventIds.SiteNodeManager + 0,
|
|
Level = LogLevel.Information,
|
|
Message = "Site composition ready. Pump server: {PumpServer}. Generator server: {GeneratorServer}.")]
|
|
public static partial void SiteAddressSpaceReady(
|
|
this ILogger logger, string pumpServer, string generatorServer);
|
|
|
|
[LoggerMessage(
|
|
EventId = SiteCompositionServerEventIds.SiteNodeManager + 1,
|
|
Level = LogLevel.Error,
|
|
Message = "Failed to materialise the OpenUSD facility.")]
|
|
public static partial void OpenUsdFacilityFailed(this ILogger logger, Exception exception);
|
|
}
|
|
}
|