Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Opc.Ua.Di.Server/DiNodeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
using Opc.Ua.Di.Server.Builders;
using Opc.Ua.Server;
using Opc.Ua.Server.Fluent;
using Opc.Ua.Server.NodeManager;
using ConformanceUnitNames = Opc.Ua.Di.Server.ConformanceUnits;
using ServerProfileUris = Opc.Ua.Di.Server.ServerProfiles;

Expand Down Expand Up @@ -825,7 +826,8 @@ internal NodeManagerBuilder GetOrCreateBuilder()
}
}
return results;
});
},
dataTypeId => PredefinedNodes.Values.FindByDataType(dataTypeId));

AttachToBuilder(m_builder);
return m_builder;
Expand Down
306 changes: 153 additions & 153 deletions src/Opc.Ua.ISA95/Design/Common/Opc.ISA95.NodeIds.csv

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/Opc.Ua.OpenUsd.Server/OpenUsdRepresentationAuthoring.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,11 @@ public static OpenUsdLiveBindingState AddLiveBinding(
throw new ArgumentNullException(nameof(context));
}

// AddxBinding_ instantiates the <Binding> placeholder as a HasComponent
// AddBinding_Placeholder instantiates the <Binding> placeholder as a HasComponent
// child (browsable) and creates its mandatory base members. The binding
// intent is the concrete subtype (§5.4): retype the instance to the
// requested OpenUsd{ValueChange,Alarm,History,Command}BindingType.
OpenUsdLiveBindingState b = representation.AddxBinding_(context, new QualifiedName(name, ns));
OpenUsdLiveBindingState b = representation.AddBinding_Placeholder(context, new QualifiedName(name, ns));
b.TypeDefinitionId = new NodeId(bindingTypeId, ns);

// Mandatory base members already exist on the instance; set their values.
Expand Down Expand Up @@ -245,7 +245,7 @@ public static OpenUsdComponentBindingState AddComponentBinding(
throw new ArgumentNullException(nameof(context));
}

OpenUsdComponentBindingState b = representation.AddxComponent_(context, new QualifiedName(name, ns));
OpenUsdComponentBindingState b = representation.AddComponent_Placeholder(context, new QualifiedName(name, ns));
b.CreateOrReplaceBindingDefinitionId(context, null!).Value = new Uuid(bindingDefinitionId);
b.CreateOrReplaceEnabled(context, null!).Value = true;
b.CreateOrReplaceCardinality(context, null!).Value = cardinality;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private static UsdAttributeState MaterializeAttribute(
ushort ns,
UsdMaterializationOptions options)
{
UsdAttributeState node = primNode.AddxUsdAttribute_(
UsdAttributeState node = primNode.AddUsdAttribute_Placeholder(
context, new QualifiedName(attribute.Name, ns));

UsdValueTypeMapping mapping = UsdValueTypeMap.Map(
Expand Down Expand Up @@ -140,7 +140,7 @@ private static UsdRelationshipState MaterializeRelationship(
UsdRelationship relationship,
ushort ns)
{
UsdRelationshipState node = primNode.AddxUsdRelationship_(
UsdRelationshipState node = primNode.AddUsdRelationship_Placeholder(
context, new QualifiedName(relationship.Name, ns));

var paths = new string[relationship.Targets.Count];
Expand Down Expand Up @@ -295,9 +295,9 @@ private static void MaterializeVariantSets(
// (fail closed); its selection is still carried by the Selection property above.
foreach (UsdPrim branch in variantSet.Variants)
{
UsdPrimState branchNode = node.AddxVariant_(
UsdPrimState branchNode = node.AddVariant_Placeholder(
context, new QualifiedName(branch.Name, ns));
// AddxVariant_ leaves the type's placeholder NodeId (i=6055); force a fresh
// AddVariant_Placeholder leaves the type's placeholder NodeId (i=6055); force a fresh
// per-instance NodeId so branches on different sets never collide, matching
// how the CreateInstanceOf* factories mint instance ids.
context.AssignInstanceNodeId(branchNode);
Expand Down
10 changes: 5 additions & 5 deletions src/Opc.Ua.OpenUsdScene.Server/UsdSceneMaterializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -462,12 +462,12 @@ private static void MaterializePrim(
// A prim of a known typed schema is instantiated as that generated ObjectType
// subclass, so the node *is* the type it declares and carries the subtype's
// members (§5.3). An unknown schema returns null here and degrades to a concrete
// UsdPrimType via AddxUsdPrim_, still keeping its TypeName token (§8.4).
// UsdPrimType via AddUsdPrim_Placeholder, still keeping its TypeName token (§8.4).
UsdPrimState? primNode = CreateTypedPrimInstance(context, parent, prim.TypeName, browseName);
primNode ??= parent switch
{
UsdStageState stageParent => stageParent.AddxUsdPrim_(context, browseName),
UsdPrimState primParent => primParent.AddxUsdPrim_(context, browseName),
UsdStageState stageParent => stageParent.AddUsdPrim_Placeholder(context, browseName),
UsdPrimState primParent => primParent.AddUsdPrim_Placeholder(context, browseName),
_ => throw new ArgumentException(
"A prim can only be materialized under a stage or another prim.",
nameof(parent))
Expand Down Expand Up @@ -577,7 +577,7 @@ private static void PopulatePrimNode(

/// <summary>
/// Instantiates a prim as the generated State subclass for a known typed schema (§5.3),
/// attached under <paramref name="parent"/> exactly as <c>AddxUsdPrim_</c> or the stage
/// attached under <paramref name="parent"/> exactly as <c>AddUsdPrim_Placeholder</c> or the stage
/// attach idiom would attach a plain prim (<c>HasComponent</c> + a factory NodeId, so it
/// is browsable and uniquely identified identically). Returns <c>null</c> for an unknown
/// or untyped schema so the caller applies the §8.4 fallback — a concrete
Expand Down Expand Up @@ -621,7 +621,7 @@ private static void PopulatePrimNode(

// The instance factory leaves ReferenceTypeId = Null and seeds a type NodeId;
// Attach makes it a HasComponent child with a fresh factory NodeId, matching how
// AddxUsdPrim_ and MaterializeUsdStage attach every other node (§7.1).
// AddUsdPrim_Placeholder and MaterializeUsdStage attach every other node (§7.1).
Attach(context, parent, node, Opc.Ua.ReferenceTypeIds.HasComponent);
return node;
}
Expand Down
5 changes: 3 additions & 2 deletions src/Opc.Ua.Server/Fluent/FluentNodeManagerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ protected FluentNodeManagerBase(
/// .Configure(Configure)
/// .Seal();
/// </code>
/// The three root/nodeId/typeId lookups default to scanning the
/// The root/nodeId/typeId/dataTypeId lookups default to scanning the
/// manager's <see cref="CustomNodeManager2.PredefinedNodes"/>
/// dictionary, mirroring the resolver wiring that the
/// source-generated <c>NodeManagerBase.CreateAddressSpaceAsync</c>
Expand All @@ -196,7 +196,8 @@ public NodeManagerBuilder CreateFluentBuilder(ushort defaultNamespaceIndex)
defaultNamespaceIndex,
browseName => PredefinedNodes.Values.FindByBrowseName(browseName)!,
nodeId => PredefinedNodes.FindById(nodeId)!,
PredefinedNodes.Values.FindByTypeDefinition);
PredefinedNodes.Values.FindByTypeDefinition,
PredefinedNodes.Values.FindByDataType);
AttachToBuilder(builder);
return builder;
}
Expand Down
59 changes: 57 additions & 2 deletions src/Opc.Ua.Server/Fluent/INodeManagerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,12 @@ INodeBuilder<TState> Node<TState>(NodeId nodeId)
/// <exception cref="ServiceResultException">
/// <list type="bullet">
/// <item><description><see cref="StatusCodes.BadNodeIdInvalid"/> — the id is null.</description></item>
/// <item><description><see cref="StatusCodes.BadNodeIdUnknown"/> — no instance carries that type definition.</description></item>
/// <item><description><see cref="StatusCodes.BadBrowseNameDuplicated"/> — more than one instance matches; supply a <see cref="QualifiedName"/> disambiguator via the <see cref="NodeFromTypeId(NodeId, QualifiedName)"/> overload.</description></item>
/// <item><description><see cref="StatusCodes.BadNodeIdUnknown"/> —
/// no instance carries that type definition.</description></item>
/// <item><description><see cref="StatusCodes.BadBrowseNameDuplicated"/> —
/// more than one instance matches; supply a <see cref="QualifiedName"/>
/// disambiguator via the <see cref="NodeFromTypeId(NodeId, QualifiedName)"/>
/// overload.</description></item>
/// </list>
/// </exception>
INodeBuilder NodeFromTypeId(NodeId typeDefinitionId);
Expand Down Expand Up @@ -247,5 +251,56 @@ INodeBuilder<TState> NodeFromTypeId<TState>(NodeId typeDefinitionId, QualifiedNa
/// CLR type carried by the variable's <c>Value</c> attribute.
/// </typeparam>
IVariableBuilder<TValue> VariableFromTypeId<TValue>(NodeId typeDefinitionId, QualifiedName browseName);

/// <summary>
/// Resolves the unique variable instance whose
/// <c>DataType</c> attribute equals <paramref name="dataTypeId"/>
/// and returns a typed <see cref="IVariableBuilder{TValue}"/>
/// view. Useful for singleton variables whose well-known DataType
/// is more stable than the deployment-specific browse path.
/// </summary>
/// <typeparam name="TValue">
/// CLR type carried by the variable's <c>Value</c> attribute.
/// </typeparam>
/// <param name="dataTypeId">
/// The DataType id of the variable to locate (typically a
/// generated <c>DataTypeIds.*</c> constant).
/// </param>
/// <exception cref="ServiceResultException">
/// <list type="bullet">
/// <item><description><see cref="StatusCodes.BadNodeIdInvalid"/> — the id is null.</description></item>
/// <item><description><see cref="StatusCodes.BadNodeIdUnknown"/> —
/// no variable carries that DataType.</description></item>
/// <item><description><see cref="StatusCodes.BadBrowseNameDuplicated"/> —
/// more than one variable matches; supply a <see cref="QualifiedName"/>
/// disambiguator via the
/// <see cref="VariableFromDataTypeId{TValue}(NodeId, QualifiedName)"/>
/// overload.</description></item>
/// <item><description><see cref="StatusCodes.BadTypeMismatch"/> —
/// the resolved variable's <c>Value</c> is not assignable to
/// <typeparamref name="TValue"/>.</description></item>
/// </list>
/// </exception>
IVariableBuilder<TValue> VariableFromDataTypeId<TValue>(NodeId dataTypeId);

/// <summary>
/// Like <see cref="VariableFromDataTypeId{TValue}(NodeId)"/> but
/// disambiguates among multiple instances by matching
/// <paramref name="browseName"/> against
/// <see cref="NodeState.BrowseName"/>.
/// </summary>
/// <typeparam name="TValue">
/// CLR type carried by the variable's <c>Value</c> attribute.
/// </typeparam>
/// <param name="dataTypeId">See <see cref="VariableFromDataTypeId{TValue}(NodeId)"/>.</param>
/// <param name="browseName">
/// Browse name of the instance to pick out.
/// </param>
/// <exception cref="ServiceResultException">
/// Same conditions as <see cref="VariableFromDataTypeId{TValue}(NodeId)"/>
/// plus <see cref="StatusCodes.BadNodeIdUnknown"/> when the
/// disambiguator matches no candidate.
/// </exception>
IVariableBuilder<TValue> VariableFromDataTypeId<TValue>(NodeId dataTypeId, QualifiedName browseName);
}
}
95 changes: 94 additions & 1 deletion src/Opc.Ua.Server/Fluent/NodeManagerBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ public sealed class NodeManagerBuilder : INodeManagerBuilder, IFluentDispatcher
/// <c>TypeDefinitionId</c> matches the supplied <see cref="NodeId"/>.
/// Typically a generated walk over the manager's predefined nodes.
/// </param>
/// <param name="dataTypeIdResolver">
/// Delegate that returns every <see cref="BaseVariableState"/> whose
/// <c>DataType</c> matches the supplied <see cref="NodeId"/>.
/// Typically a generated walk over the manager's predefined nodes.
/// When <c>null</c>, DataType lookups always resolve to no
/// candidates (as if no variable declared that DataType).
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="context"/>, <paramref name="nodeManager"/>,
/// <paramref name="rootResolver"/>, <paramref name="nodeIdResolver"/>,
Expand All @@ -95,14 +102,16 @@ public NodeManagerBuilder(
ushort defaultNamespaceIndex,
Func<QualifiedName, NodeState> rootResolver,
Func<NodeId, NodeState> nodeIdResolver,
Func<NodeId, IReadOnlyList<NodeState>> typeIdResolver)
Func<NodeId, IReadOnlyList<NodeState>> typeIdResolver,
Func<NodeId, ArrayOf<NodeState>>? dataTypeIdResolver = null)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
NodeManager = nodeManager ?? throw new ArgumentNullException(nameof(nodeManager));
m_defaultNamespaceIndex = defaultNamespaceIndex;
m_rootResolver = rootResolver ?? throw new ArgumentNullException(nameof(rootResolver));
m_nodeIdResolver = nodeIdResolver ?? throw new ArgumentNullException(nameof(nodeIdResolver));
m_typeIdResolver = typeIdResolver ?? throw new ArgumentNullException(nameof(typeIdResolver));
m_dataTypeIdResolver = dataTypeIdResolver ?? (static _ => []);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -284,6 +293,27 @@ public IVariableBuilder<TValue> VariableFromTypeId<TValue>(NodeId typeDefinition
browseName));
}

/// <inheritdoc/>
public IVariableBuilder<TValue> VariableFromDataTypeId<TValue>(NodeId dataTypeId)
{
ThrowIfSealed();
NodeState node = ResolveByDataType(dataTypeId, (QualifiedName)null!);
return ToVariableBuilder<TValue>(node, FormatNodeId(dataTypeId));
}

/// <inheritdoc/>
public IVariableBuilder<TValue> VariableFromDataTypeId<TValue>(NodeId dataTypeId, QualifiedName browseName)
{
ThrowIfSealed();
NodeState node = ResolveByDataType(dataTypeId, browseName);
return ToVariableBuilder<TValue>(
node,
CoreUtils.Format(
"{0} (browse name '{1}')",
FormatNodeId(dataTypeId),
browseName));
}

internal VariableBuilder<TValue> ToVariableBuilder<TValue>(NodeState node, string lookupHint)
{
if (node is not BaseVariableState variable)
Expand Down Expand Up @@ -590,6 +620,68 @@ private NodeState ResolveByTypeDefinition(NodeId typeDefinitionId, QualifiedName
return match;
}

private NodeState ResolveByDataType(NodeId dataTypeId, QualifiedName browseName)
{
if (dataTypeId.IsNull)
{
throw ServiceResultException.Create(
StatusCodes.BadNodeIdInvalid,
"DataTypeId is null or empty.");
}

ArrayOf<NodeState> candidates = m_dataTypeIdResolver(dataTypeId);

if (candidates.Count == 0)
{
throw ServiceResultException.Create(
StatusCodes.BadNodeIdUnknown,
"No predefined variable has DataType '{0}'.",
dataTypeId);
}

if (browseName.IsNull)
{
if (candidates.Count > 1)
{
throw ServiceResultException.Create(
StatusCodes.BadBrowseNameDuplicated,
"DataType '{0}' is ambiguous: {1} matching variables found. " +
"Pass a QualifiedName disambiguator to VariableFromDataTypeId.",
dataTypeId,
candidates.Count);
}
return candidates[0];
}

NodeState? match = null;
for (int i = 0; i < candidates.Count; i++)
{
if (candidates[i].BrowseName == browseName)
{
if (match != null)
{
throw ServiceResultException.Create(
StatusCodes.BadBrowseNameDuplicated,
"DataType '{0}' has multiple variables with browse name '{1}'.",
dataTypeId,
browseName);
}
match = candidates[i];
}
}

if (match == null)
{
throw ServiceResultException.Create(
StatusCodes.BadNodeIdUnknown,
"DataType '{0}' has no variable with browse name '{1}'.",
dataTypeId,
browseName);
}

return match;
}

private void ThrowIfSealed()
{
if (m_sealed)
Expand Down Expand Up @@ -621,6 +713,7 @@ private static void ThrowIfDuplicate<T>(
private readonly Func<QualifiedName, NodeState> m_rootResolver;
private readonly Func<NodeId, NodeState> m_nodeIdResolver;
private readonly Func<NodeId, IReadOnlyList<NodeState>> m_typeIdResolver;
private readonly Func<NodeId, ArrayOf<NodeState>> m_dataTypeIdResolver;
private bool m_sealed;
private readonly Dictionary<NodeId, HistoryReadHandler> m_historyRead = [];
private readonly Dictionary<NodeId, HistoryUpdateHandler> m_historyUpdate = [];
Expand Down
42 changes: 40 additions & 2 deletions src/Opc.Ua.Server/NodeManager/NodeStateLookupExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,12 @@ namespace Opc.Ua.Server.NodeManager
{
/// <summary>
/// Linear-scan + dictionary lookups over a node-manager's predefined
/// node collection. Surfaces the three patterns that node-manager
/// node collection. Surfaces the patterns that node-manager
/// subclasses repeatedly hand-roll against
/// <c>CustomNodeManager.PredefinedNodes</c> /
/// <c>AsyncCustomNodeManager.PredefinedNodes</c>:
/// browse-name root lookup, NodeId lookup, and TypeDefinitionId scan.
/// browse-name root lookup, NodeId lookup, TypeDefinitionId scan, and
/// DataType scan.
/// </summary>
public static class NodeStateLookupExtensions
{
Expand Down Expand Up @@ -135,5 +136,42 @@ public static List<NodeState> FindByTypeDefinition(
}
return results;
}

/// <summary>
/// Returns every <see cref="BaseVariableState"/> in
/// <paramref name="nodes"/> whose
/// <see cref="BaseVariableState.DataType"/> equals
/// <paramref name="dataTypeId"/>. The list is empty when no match
/// exists; non-variable nodes are skipped.
/// </summary>
/// <param name="nodes">
/// The set to scan, typically <c>PredefinedNodes.Values</c>.
/// </param>
/// <param name="dataTypeId">
/// DataType NodeId to match (e.g. a known <c>DataTypeIds</c>
/// constant).
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="nodes"/> is <see langword="null"/>.
/// </exception>
public static ArrayOf<NodeState> FindByDataType(
this IEnumerable<NodeState> nodes,
NodeId dataTypeId)
{
if (nodes == null)
{
throw new ArgumentNullException(nameof(nodes));
}
var results = new List<NodeState>();
foreach (NodeState node in nodes)
{
if (node is BaseVariableState variable &&
variable.DataType == dataTypeId)
{
results.Add(node);
}
}
return new ArrayOf<NodeState>(results.ToArray());
}
}
}
Loading
Loading