Variable

The Variable class is an interface for workspace variables, designed to manage and manipulate various types of variables within a software system. It provides an abstract representation of variables with different types and allows users to interact with them uniformly while ensuring thread safety for specific methods.

The Variable class uses sup::dto::AnyValue as a common interface to represent any structured data.

While all variables are globally accessible by the instructions in a procedure, the backend, i.e. where the values of these variables are stored, could be very diverse: file, memory, network, database, etc.

Architecture

The Variable class is based on the non-virtual interface (NVI) idiom to support the distinction between thread-safe and non-thread-safe methods and to allow for a simpler API to override by implementers of concrete variables. It employs locks to provide thread safety for certain public methods that can be called during execution, ensuring that multiple threads can safely access these methods concurrently. Other methods should be called only from a single thread to prevent data corruption or race conditions.

The Variable class allows users to define variables of different types. Since it is an abstract base class, concrete variables are implemented in derived classes.

Variable Value Constraints

The generic non-virtual implementation of Variable tries not to impose any constraints on the type of values that can be set or read back from a concrete Variable. However, concrete implementations can, and often will, impose specific constraints in the way they override the virtual member functions GetValueImpl and SetValueImpl.

To clarify the general semantics of getting/setting values to a Variable, the following subsections will detail the internal logic that is provided in the base class implementations.

GetValue

The full signature of the GetValue member function is:

bool Variable::GetValue(sup::dto::AnyValue& value, const std::string& fieldname) const;

This function will perform the following steps:

  • Retrieve the full value of the concrete Variable by calling the overriden GetValueImpl member function. Return false if this fails.

  • If fieldname is non-empty, retrieve the substructure identified by this fieldname. Return false is the value didn’t have such a field.

  • If value is an empty value, assign the previous result to it. Otherwise, try to convert that result to value.

This means that the client of this function can request a conversion of the underlying value by providing an AnyValue with non-empty type.

SetValue

The full signature of the SetValue member function is:

bool Variable::SetValue(const sup::dto::AnyValue& value, const std::string& fieldname) const;

This function will perform the following steps:

  • If ‘fieldname’ is empty, return the result of SetValueImpl.

  • Otherwise: * Retrieve the value of the variable by calling GetValueImpl. * If that value does not contain the field with name fieldname, return false. * Try to assign value to the field with name ‘fieldname’ and return false if that fails. Note that this assignment will only fail if the type of the field cannot be changed, i.e. it is an array element or descendant thereof, and conversion fails. * Return the result SetValueImpl with the updated value as argument.

Usage

Two specialisations of the Variable class are included in oac-tree. They are the local variable and the file variable. The implementation of Local Variable will be used to provide examples.

Note that more specialized variables are provided by plugins. For example, an EPICS ChannelAccess client variable is defined by oac-tree-plugin-epics.

Creating a Variable

To obtain an instance of an existing variable type, provided by the core library or one of the loaded plugins, one typically asks a VariableRegistry to instantiate it:

auto local_var = GlobalVariableRegistry().Create("Local");

Variable Initialization

Before instructions can access, i.e. read or write, the value of a variable, it needs to be properly initialized first. This is done by calling the Setup method on the variable. Initialization may include connecting to a database, obtaining a file handle, etc. The Setup method also receives a reference to a Workspace, allowing it to access its AnyTypeRegistry. The method returns an object of type SetupTeardownActions that allows the workspace to register actions that need to be executed after all variables have been setup, as well as teardown actions that will be executed before all variables will be torn down. The default constructed SetupTeardownActions object contains no actions at all. This object also contains an identifier that allows to avoid multiple registrations. For example, if such an action only needs to be called once per variable type, this type can be used as an identifier and the workspace will only register a single instance with that identifier.

Workspace ws;
local_var->Setup(ws);

Setting and Getting Variable Values

Users can access and modify the value of a variable using the GetValue and SetValue methods, respectively. These methods provide a way to read and update variable values, or their subfields, in a controlled manner.

// Set the current value of a variable
sup::dto::AnyValue value = {{"index", {SignedInteger8Type, 1}}};
local_var->SetValue(value);

// Get the index field from the variable
sup::dto::AnyValue index;
local_var->GetValue(index, "index");

The access to subfields of a variable’s value is provided by the base class Variable itself, requiring implementers of custom variables to only override the virtual private methods for non field-based access.

Checking Variable Availability

The availability of a variable depends on the specific derived class implementation. For example, for a network variable, availability could mean that a stable network connection was made to be able to obtain its value. Users can check if a variable is available using the IsAvailable method.

if (local_var->IsAvailable()) {
    // Variable is available
} else {
    // Variable is not available
}

Registering Callback for Value Updates

Users can register a callback function to be notified of value updates using the SetNotifyCallback method. This allows for real-time responsiveness to changes in variable values. Note however that it is typically the responsibility of the Workspace to subscribe to such changes and propagate these to other interested software components.

// Define a callback function
void OnValueUpdate(const sup::dto::AnyValue& value, bool connected) {
    // Handle the value update
    // ...
}

local_var->SetNotifyCallback(OnValueUpdate); // Register the callback function

Managing Attributes

The Variable class supports an attribute system (see Attribute System). Users can set, retrieve, and manipulate attributes using various attribute-related methods:

// Add attributes to the numeric variable
local_var->AddAttribute("units", "kg");
local_var->AddAttribute("precision", "2");

// Retrieve attribute values
std::string units = local_var->GetAttributeString("units");
int precision;
if (!local_var->GetAttributeValue("precision", precision))
{
  // deal with error retrieving the attribute's value as an integer...
}

Tearing down a Variable

The Teardown method resets the variable to the state it had prior to initialization. This means that attributes are still present, but other internal state data is reset. For example, it can disconnect from external resources or clear values.

local_var->Teardown(); // Reset the numeric variable

Attribute System

The attribute system, together with a fixed typename for each concrete variable implementation, allows for handling variables in an opaque way: together they fully define the behavior of a variable and no implementation specific methods are required to initialize them. This system makes it possible to fully instantiate and initialize variables in a data-driven way and is used when parsing procedure XML files. See Attribute System in the Instruction documentation for more information on the attribute system.

Class definition

Next is presented the definition of the Variable class and its main methods.

class Variable

Interface for workspace variables.

Most of the Variable API is implemented using the non-virtual interface (NVI) idiom and locks are provided for those public methods that can be called during execution to ensure thread safety. Those methods are:

  • Value access functions: GetValue, SetValue and IsAvailable;

  • Notification callback functions: Notify and SetNotifyCallback. Note that all other methods are not thread safe and thus should be called only from a single thread. These methods are typically called during the initialization of a procedure.

Subclassed by sup::oac_tree::FileVariable, sup::oac_tree::LocalVariable

Public Functions

std::string GetType() const

Get variable type.

Returns:

variable type

std::string GetName() const

Get variable name.

Returns:

variable name

void SetName(const std::string &name)

Set variable name.

Parameters:

name – Name to set

Returns:

void

SetupTeardownActions Setup(const Workspace &ws)

Setup variable method.

Parameters:

ws – Current workspace.

Throws:

VariableSetupException – when the variable could not be setup properly.

Returns:

Optional setup/teardown actions that need to be called once per class of Variables (identified by a unique string identifier).

void Reset(const Workspace &ws)

Resets the variable to the state just after the initial Setup.

By default, this methods calls Teardown and Setup, but custom variables may override this behavior.

Parameters:

ws – Current workspace.

Throws:

VariableSetupException – when the variable could not be reset properly.

bool GetValue(sup::dto::AnyValue &value, const std::string &fieldname = {}) const

Get value of variable.

Note

Non-virtual interface. This member function will fail, i.e. return false, if value is non-empty and the value to be retrieved cannot be converted to value’s type.

Parameters:
  • value – variable reference to contain the value.

  • fieldname – optional field name.

Returns:

true on success.

bool SetValue(const sup::dto::AnyValue &value, const std::string &fieldname = {})

Set value of variable.

Note

Non-virtual interface. This member function does not enforce any type constraints on the value. Variable implementations are fully responsible to enforce this if needed.

Parameters:
  • value – value to set.

  • fieldname – optional field name.

Returns:

true on success.

bool IsAvailable() const

Check if variable is available.

Note

Non-virtual interface.

Note

Availability is context-dependent: e.g. a network variable is available when it is connected and its value can be read.

Returns:

true on success.

void Notify(const sup::dto::AnyValue &value, bool connected) const

Notify waiting threads of an update to the variable.

Note

Needs to be called whenever the variable is updated. It has to be called without holding the mutex lock.

Parameters:
  • value – New value of variable.

  • connected – New connectivity status of variable.

void SetNotifyCallback(Callback func)

Set callback for value update notifications.

Note

This method will overwrite an existing callback if there was one.

Parameters:

func – Callback function object.

Returns:

true if successful.

void Teardown()

Tear down the variable.

This method resets the variable to its initial, i.e. uninitialized, state. For example, network variables will disconnect.

bool HasAttribute(const std::string &name) const

Indicate presence of attribute with given name.

Parameters:

name – Attribute name.

Returns:

true when present.

std::string GetAttributeString(const std::string &attr_name) const

Get attribute with given name.

Parameters:

attr_name – Attribute name.

Returns:

Attribute value.

const StringAttributeList &GetStringAttributes() const

Get all attributes.

Returns:

List containing all attributes.

bool AddAttribute(const std::string &attr_name, const std::string &attr_value)

Set attribute with given name and value.

Parameters:
  • attr_name – Attribute name.

  • attr_value – Attribute value.

Returns:

true when successful.

bool AddAttributes(const StringAttributeList &str_attributes)

Set all attributes in given list.

Parameters:

str_attributes – Attribute list.

Returns:

true when successful.

template<typename T>
inline bool GetAttributeValue(const std::string &attr_name, T &val) const

Get attribute value with given name and type.

Note

The reason for returning true in the absence of the attribute is that mandatory attributes are already checked during setup and non-mandatory attributes should not cause an error when they are not present.

Parameters:
  • attr_name – Attribute name.

  • val – Output parameter for value.

Returns:

True on success or when the attribute is not present.

const std::vector<AttributeDefinition> &GetAttributeDefinitions() const

Get all attribute definitions.

Returns:

List of attribute definitions.