/* ========================================================================
* 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);
}
}
}