/* ======================================================================== * 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; namespace Opc.Ua.Server.StateMachines { /// /// Immutable snapshot of a Part 16 finite state machine definition. /// Produced by (snapshot /// of the mutable holder taken on first read) and exposed via /// for /// introspection. /// public sealed class StateMachineDefinition { internal StateMachineDefinition( IReadOnlyList states, IReadOnlyList transitions, IReadOnlyList causeMappings, uint? initialStateId, string elementNamespaceUri) { States = states; Transitions = transitions; CauseMappings = causeMappings; InitialStateId = initialStateId; ElementNamespaceUri = elementNamespaceUri; } /// /// The set of states declared on the state machine. /// public IReadOnlyList States { get; } /// /// The set of transitions declared on the state machine. /// public IReadOnlyList Transitions { get; } /// /// The list of (cause method id, from state, transition) tuples /// used by the runtime to resolve a transition for an inbound /// method call. /// public IReadOnlyList CauseMappings { get; } /// /// The initial state's numeric id, or null when none was /// designated. Concrete state machines apply this on activation. /// public uint? InitialStateId { get; } /// /// The namespace URI that qualifies the state and transition /// browse names + node ids in the OPC UA address space. /// Defaults to http://opcfoundation.org/UA/. /// public string ElementNamespaceUri { get; } } /// /// Definition of a single state in a Part 16 state machine. /// /// The state's numeric id (used as the /// StateNumber property and the NodeId identifier /// when applicable). /// The state's localized browse name. /// Whether this state is the starting /// state for the machine. public sealed record StateMachineStateDefinition( uint Id, string BrowseName, bool IsInitial = false); /// /// Definition of a transition between two states. /// /// The transition's numeric id. /// The transition's browse name. /// The source state id. /// The destination state id. /// Whether the transition fires a /// TransitionEventType. Defaults to true. public sealed record StateMachineTransitionDefinition( uint Id, string BrowseName, uint FromStateId, uint ToStateId, bool HasEffect = true); /// /// Maps a cause (method NodeId) plus current state to the /// transition that should fire. Used by /// FiniteStateMachineState.GetTransitionForCause at runtime. /// /// The cause (method) numeric id. /// The current state id. /// The transition that fires. public sealed record StateMachineCauseMapping( uint CauseId, uint FromStateId, uint TransitionId); /// /// Internal mutable holder for a state machine definition. The /// unified populates an /// instance of this type as the user chains /// AddState / AddTransition / OnCause calls, /// and reads its /// runtime tables through it (with invalidate-on-mutation cached /// projections for hot-path access). /// internal sealed class MutableStateMachineDefinition { public readonly List States = []; public readonly List Transitions = []; public readonly List CauseMappings = []; public uint? InitialStateId; public string ElementNamespaceUri = Types.Namespaces.OpcUa; /// /// Version is bumped on every mutation; projections cache by version. /// public int Version; public bool Frozen; public void EnsureNotFrozen() { if (Frozen) { throw new InvalidOperationException( "The state-machine definition has been frozen; " + "no further AddState / AddTransition / OnCause / " + "UseElementNamespace calls are permitted after the " + "first lifecycle method (OnEnterState / OnExitState / " + "OnTransition / OnBeforeTransition / WithCause / " + "WithTimedTransition) or after the StateMachine has " + "been read."); } } public StateMachineDefinition Snapshot() { return new StateMachineDefinition( [.. States], [.. Transitions], [.. CauseMappings], InitialStateId, ElementNamespaceUri); } } }