The RCC Worker Interface

This section defines the interface between the worker and its container: the API for this authoring model. The term worker method is used as a shorthand and language-neutral term for what is a member function in C++ or a worker entry point function in C. Methods in C++ have an implicit this argument that is hidden by the language. C language RCC workers have the first argument to all worker methods as an explicit self argument. This self argument is a pointer to a structure containing context and state information for the worker as well as C function pointers to container methods

When discussing a worker’s runtime behavior, the term worker is sometimes used as a runtime instance of the worker in contrast to referring to the source code that is written for the worker as a component implementation.

RCC worker code must avoid the prefixes OCPI and RCC (even without a trailing underscore) for compile-time constants and types as these are reserved for use by the authoring model. The RCC authoring model also specifies that all generated macros are upper case, and all generated data types are capitalized and mixed CamelCase. User code is recommended to follow these conventions, but is not required to do so.

The worker interface consists of control operation methods whose behavior is defined in the Control Plane Introduction section of the CDG. In addition, there is a required run method that supports the event-driven execution model defined in the Software Execution Model section of that same document. The run method is the only required method (except C++ where it is optional) and all the other worker methods are optional. All processing of the worker occurs in the context of these methods. There are two categories of methods:

  • Worker methods represent functionality of the worker, to be called by the container, and which may have default implementations. These include the run method, and the lifecycle control operation methods: initialize, start, stop, and release.

  • Container methods represent functionality of the container, to be called by the worker, such as changing run conditions, and accessing ports and buffers. All C language container methods are dispatched through function pointers in the container member of the RCCWorker structure (supplied as the self argument). C++ container methods are accessible member functions inherited from the worker’s base class.

Default methods are the behaviors that are executed if the user supplies no code for a worker method. Default methods for C-language workers are indicated by NULL pointers for those methods in the RCCDispatch Structure Type topic, described below. For C++, they are simply methods in the base class.

Several integral typedefs and constants are defined and used throughout the interface:

  • RCCBoolean is aliased to uint8_t, to match the defined size in property space and message layouts.

  • RCCChar is a signed 8 bit type to represent property values of type Char.

  • RCCOrdinal is used for ordinals for ports, operations, and properties.

  • RCCPortMask indicates a proper subset of a worker’s ports, with ports indicated using, e.g.: (1 << port1_ordinal) | (1 << port2_ordinal).

  • Ordinals for ports are generated as enumeration constants <WORKER>_<PORT>.

  • RCC_NO_PORTS is a mask of type RCCPortMask indicating no ports.

  • RCC_ALL_PORTS is a mask of type RCCPortMask indicating all ports.

More complex data types used in worker and container methods are described in the following sections where they are used.

The RCC Execution Model

The CDG describes the fundamentals of software execution models for OpenCPI software authoring models. RCC workers executing in their containers operate according to this model, with the details described in the Code Generation for RCC Workers topic section below.

The container executes workers such that all execution threads are supplied by the container. Thus there is no need or possibility for workers to create threads. Containers may arrange for workers to run concurrently, each in its own thread, or run one at a time with all workers in the container running in a single thread.

A simple non-preemptive single-threaded container implementation would have a loop, testing run conditions, and calling workers’ run methods. A more complex environment might run workers in different threads for purposes of time preemption, prioritization, etc. This worker execution model allows a variety of container execution models while keeping the worker code simple.

On each execution, the worker sees the status of all I/O ports, and can read from current input buffers, and write to current output buffers. It must return from the run method to get new buffers, after specifying whether buffers are consumed or filled during the execution.

This simple execution environment can be easily implemented in full function GPP environments, providing a test environment and a migration path to more minimal embedded environments such as a single-threaded environment with no real operating system at all (i.e. “bare metal”). Such a minimal environment is possible, although none are currently supported.

The execution source code for an RCC worker should be written for a multi-threaded environment. Since the interfaces between the worker and container are already implicitly thread-safe, this concern mostly applies to workers calling library functions. Library functions such as localtime or strtok should be avoided since they are not thread-safe. Library functions such as setenv or even exit, should never be called since they modify the environment for other workers. More information about appropriate library functions is in the RCC Local Services topic.

Worker Methods: Called by the Container, Implemented by the Worker.

This section describes the methods that a worker may implement with only one being mandatory (and only for C, not C++): run. All other methods are optional and have default behavior. These other methods are control operations that perform lifecycle state transitions. The transitions are described in the control plane introduction section of the CDG. All processing of the worker occurs in the context of these worker methods.

For C++, the OpenCPI code generation tools create a custom base class that the actual worker class inherits. The derived class that implements the worker is declared by the worker author directly in their source code. This derived class inherits the custom base class, but can otherwise contain any other member functions and data members, with the caveat that they do not shadow certain members in the generated custom base class (which are specifically mentioned below). All worker methods in C++ are member functions and this is where all worker processing takes place.

RCCWorker Structure Type – C Language Only

This structure type , a typedef name, represents the visible state of a worker. The container creates this structure (defined in RCC_Worker.h) with no particular content or member ordering, but with the documented members present. The structure members are written by either the container or the worker, but not both. Members written by the container are declared const to enhance error checking when compiling worker code. A pointer to this structure is the first argument to all C language worker methods, called self.

Workers (code in worker methods) access property values via the properties member of the RCCWorker structure, whose type is void *. Its usage is described in the How a Worker Accesses its Properties topic.

The defined members of the RCCWorker structure are described in the table below. Some example C code that uses these various members of RCCWorker is:

XyzProperties *props = self->properties; // void * needs no cast
MyState *state = self->memory;           // void * needs no cast
self->container.setError("stuff happened");
if (self->runCondition == &mySearchCondition) ...
if (self->connectedPorts & (1 << XYZ_IN)) ...
uint32_t *msgdata = self->ports[XYZ_IN].current.data;
Table 67: Members of the RCCWorker Structure for C Workers

Member Name

Member

Data Type

Written

by

Member

Description

properties

void *const

container

A const pointer to the properties structure for the worker, whose layout is implied by the properties declared in the OCS and OWD. The value is NULL if there are no such properties.

memories

void *const *const

container

An array of const pointers to the memory resources requested by the worker in the memSizes member of the RCCDispatch structure. Any memories that are not read-only are initialized to zero before a worker executes any method.

memory

void *const

container

A pointer to the memory resource as requested in the memSize member of the RCCDispatch structure.

container

const RCCContainer

container

A dispatch table of container method/function pointers.

runCondition

RCCRunCondition*

worker

Initialized from the RCCDispatch runCondition member. Checked by container after calling the start method.

connectedPorts

RCCPortMask

container

A mask indicating which ports are connected. A worker can check this to see if an optional port is connected.

ports

RCCPort[]

varies by member

An array of RCCPort structures, indexed by port ordinals.

The Worker Derived Class – C++ Language Only

All the worker methods are member functions of the worker’s derived class. This class inherits a base class specifically generated for the worker and defined in the gen/<worker>-worker.hh file. Worker methods that must exist (based on the OWD), are declared pure virtual in this generated base class. Container methods are accessible member functions of the base class. There are several accessible data members of the generated base class that can used in worker method code. One is m_properties, which is a structure containing members for property values. Its usage is described in the How a Worker Accesses its Properties topic.

There are also data members in the base class for each port. These are described in the Port Management Data Members and Methods topic.

RCCResult Enumeration Type — C and C++ Languages

The RCCResult type is an enumeration type used as the return value for all worker methods. It indicates to the container what to do when the worker method returns, as described in the following table:

Table 68: RCCResult Return Values

Enumeration Identifier

Description

RCC_OK

The worker method succeeded without error.

RCC_ERROR

The worker method did not succeed, but the error is not fatal to worker or container: the method may be retried if defined to allow this.

RCC_FATAL

The worker should never run again and is non-functional. The container or other workers may be damaged. The worker is in an unusable state. The container may know that it, or other workers are protected from damage, but the worker indicates this condition in case there is no such protection.

RCC_FINISHED

The worker needs no more execution. This is a normal completion. The worker is entering the finished state. This allows the container release resources (such as I/O buffers) prior to the worker instance being destroyed or reused. Any messages remaining in input buffers will not be processed and may be discarded. Properties are still accessible.

RCC_ADVANCE

The worker is requesting that all ports that were ready when the run method was entered be advanced (applies only to the run method).

RCC_ADVANCE_FINISHED

The worker is requesting that all ports be advanced (run method only) and declaring that it is also “finished”. The worker is entering the finished state like RCC_FINISHED.

These return values apply to each method as defined in their specific behavior. Some values are not valid results for all methods. When the result is RCC_ERROR or RCC_FATAL, the worker may have also set a descriptive error message via the setError container method described below.

Worker Initializations

A worker may initialize its state in several places, as appropriate. Each is described below in detail, but here is a summary of the different places it may be done and why:

  • C++ Worker Constructor: convenient for one time initializations that do not depend on initial property values, do not generate error conditions, and cannot use member data initializers.

  • C++ Member Data Initializers: convenient for one time member data initializations that do not depend on initial property values and do not generate error conditions.

  • The initialize control operation: one time initializations that do not depend on initial property values for C, and those that might generate errors for C++.

  • The start control operation: initializations that should happen when the worker is first started as well as when it is resumed after being suspended.

  • The firstRun test in the run method: one-time initializations that depend on initial property values. See the firstRun — Container Method topic.

Guidance to choose where initialization should take place is, in priority order:

  1. In C++, use the constructor (or member initializers) if the initializations do not depend on property values and do not generate errors, otherwise

  2. Use the firstRun container method unless it is critical that the first execution of the run method should not be slower than subsequent executions.

If neither #1 nor #2 is suitable:

  1. Use initialize if the initializations do not depend on property values.

  2. Use start if the initializations depend on property values or must be repeated when the worker is resumed after being suspended.

initialize — Worker Method

This worker method implements the initialize control operation as defined in the CDG and it is optional. It cannot depend on any property settings. A worker should implement this method under these conditions, for one-time initializations that do not depend on properties:

  • There are initialization errors that can be reported to the container.

  • There is significant processing work or time involved in the initialization.

  • There are significant resource allocations performed during initialization.

If none of the above conditions are true, this method can be unimplemented and the default implementation will be used.

Using the initialize method will allow the run or start methods to be more consistent in execution time. Without an initialize, all one-time initialization must be done on the first invocation of start, or if start is unimplemented, the first invocation of run. Sometimes this is unavoidable.

For one-time initializations that depend on property values, the firstRun container method can be called in the run method to know when it is being called for the first time.

Synopsis

static RCCResult initialize(RCCWorker *self); // C language
RCCResult initialize();                       // C++ language

Returns

This method shall return a RCCResult value.

If the initialization cannot succeed, it shall return RCC_ERROR. If the worker detects an error that would disable the implementation or its environment, it shall return RCC_FATAL. Otherwise it shall return RCC_OK if normal worker execution should proceed.

start — Worker Method

This optional method implements the start control operation as defined in the CDG. Upon successful completion, the worker is in the operating state or possibly in the finished state. This operation is called for the first time after initialization and after initial property settings are done. It may also be called after a successful stop operation to resume execution. Thus any truly one-time initializations must be protected by some means in the worker implementation to ensure they are executed only once. If this method is not provided the default implementation advances the worker to the operating state.

Synopsis

static RCCResult start(RCCWorker *self); // C language
RCCResult start();                       // C++ language

Returns

This method shall return a RCCResult value.

If the start method cannot succeed, it shall return RCC_ERROR. If the worker detects an error that would disable the implementation or its environment, it shall return RCC_FATAL. It shall return RCC_FINISHED if no worker execution should proceed, to indicate that it is entering the finished state. Otherwise it shall return RCC_OK if normal worker execution should proceed.

Returning RCC_FINISHED indicates to the container that the worker will never require further execution, and provides this advice to the container to allow the container to take advantage of this fact and possibly release resources (such as I/O buffers) prior to the worker instance being destroyed or reused.

stop — Worker Method

This optional method implements the stop control operation as described in the CDG. Upon successful completion, the worker is in the suspended state. It will be also called before the release method, and before destruction, if the worker is in the operating state. If not provided, the default implementation simply advances the worker to the suspended state.

Synopsis

static RCCResult stop(RCCWorker *self); // C language
RCCResult stop();                       // C++ language

Returns

This method shall return a RCCResult value.

If the stop method cannot succeed, it shall return RCC_ERROR. If the worker detects an error that would disable the implementation or its environment, it shall return RCC_FATAL. Otherwise it shall return RCC_OK.

release — Worker Method

This method implements the release control operation as described in the CDG. After successful completion, the worker instance is in the exists state. This method is needed when there are resources allocated by the worker in any other worker methods, such as heap allocations or file handles.

Synopsis

static RCCResult release(RCCWorker *self); // C language
RCCResult release();                       // C++ language

Returns

This method shall return a RCCResult value.

If the release method cannot succeed, it shall return RCC_ERROR. If the worker detects an error that would disable the implementation or its environment, or it is unable to return resources it allocated, it shall return RCC_FATAL. Otherwise it shall return RCC_OK.

run — Worker Method

The run method (optional in C++) requests that the worker perform its normal computation. The container only calls this method when the worker’s run condition is satisfied. The default C++ implementation of this method disables the worker’s run condition causing this method to never be called again. This default behavior allows workers that have no need for run methods to avoid using a non-default run condition for this purpose. They simply do not implement the run method.

Synopsis

static RCCResult run(RCCWorker *self,
                     RCCBoolean timedOut,
                     RCCBoolean *newRunCondition); // C language
RCCResult run(bool timedOut);                      // C++ language

Behavior

The run method shall perform the worker’s computational function and return a result. This method may use information in its property structure, the state of its ports, and its requested local and global/persistent member data to decide what to do. When there are data ports, this typically involves using messages in buffers at input ports to produce messages in buffers at output ports.

The timedOut boolean input argument indicates when the run method is being invoked due to time passing (the usecs value of the run condition) and not due to the ports specified in the run condition being ready.

C Language: The run method may change the run condition by setting a TRUE value to the location indicated by the newRunCondition output argument, and setting a pointer to a new run condition in the runCondition member of RCCWorker. The runCondition member of RCCWorker is initially set to the value in the RCCDispatch structure (including NULL to indicate the default run condition).

C++ Language: The run method may change the run condition using the setRunCondition container method and retrieve it using the getRunCondition container method. In C++, the run method may be omitted entirely, which is equivalent to a permanent run condition which is never satisfied. This is useful when the only function of the worker is to implement other control operations or property notification methods.

If the current runCondition is set to NULL, the default run condition is restored. The run condition for a worker is initially set to this default. Since the worker code is managing the run conditions (passed by pointer), it can use the pointer value as a convenient “state variable” when its execution modes each have a different run condition.

Code in the run method accesses information about ports and current buffers by accessing objects/structures that are ports in the Worker object.

See the Port Management Data Members and Methods and Buffer Management Data Members and Methods topics for container methods and port data members used to process input messages and create output messages.

The firstRun container method may be tested for one-time initializations in the run method.

Returns

This method shall return a RCCResult value. The most common value is RCC_ADVANCE, which indicates that all ports should be advanced that were ready on entry to the run method and were not subject to container methods called since then (e.g. advance, request etc. described below).

It should return RCC_OK or RCC_ADVANCE if normal worker execution should proceed (the run method will be called again when the run condition is true).

The run method should return RCC_FINISHED or RCC_ADVANCE_FINISHED if no further worker execution should happen.

See the RCCResult Enumeration Type — C and C++ Languages topic for a description of all allowed values.

Container Methods, Called by the Worker.

These methods are what a worker calls to invoke functionality provided by the container. The container methods are in three categories:

  • worker scope (this section),

  • port scope (next section)

  • buffer scope (following section).

The use of all container methods is optional, and typically unneeded for simple workers that only use run conditions and the run method. When used, these provide additional flexibility and functionality in message handling.

All container methods are non-blocking.

C Language: container methods are usually accessed using function pointer members in the container structure member of the RCCWorker structure, e.g.:

self->container.setError(...)

C++ Language: container methods exist in an accessible base class for the worker and are called directly. Container methods relating to ports and buffers are methods of the worker’s member data objects for ports, rather than on the worker (or container) objects.

firstRun — Container Method

This method is used by the worker to determine when the run method is being called for the first time. It can be used for initializations that must be done once after all initial property values are established. It can simply be used when it is more convenient or concise than other methods, e.g. (in C++):

RCCResult run(bool timedOut) {
  if (firstRun())
    do_this_once(); // whatever must be done only once
}

Remember that the start control operation may be called more than once if the worker is suspended (using the stop control operation) and subsequently resumed using start a second time. The initialize control operation is also called exactly once, but is called before initial property values are established.

If the firstRun container method is called from other than the run method, its behavior is undefined.

Synopsis

RCCBoolean self->firstRun; // C Language, using structure member
bool firstRun();           // C++ Language

Returns

This method (C++) or structure access (C) returns a boolean which is true when the run method is being called for the first time.

setError — Container Method

This method is used by the worker to convey an error message associated with returning RCC_ERROR or RCC_FATAL. It has semantics similar to sprintf.

Synopsis

RCCResult (*setError)(const char *fmt, ...); // C Language
RCCResult setError(const char *fmt, ...);    // C++ Language

Returns

RCC_ERROR is always returned, allowing a worker to simultaneously set the error message and return the error indication. E.g., a worker method detecting an error could (in C) do:

return self->container.setError("Failure due to %d", arg);

Or, in C++:

return setError("Failure due to %d", arg);

Workers that must return RCC_FATAL, may call this prior to returning that value.

log — Container Method

This method is used by the worker to make an entry into the OpenCPI system log. After an initial argument to specify the log level associated with the message, it has semantics similar to printf. A newline is automatically appended to all log messages so none is required in the message content.

Log levels are set on the command line of ocpirun or ocpiserve, and may also be set in the ACI. Using a log level of zero in this method will nearly always show in the system log. Log levels of 10 and above are used and shown only for detailed debugging output.

Symbolic constants for log levels are not available in RCC workers (yet).

Synopsis

void (*log)(unsigned level, const char *fmt, ...); // C language
void log(unsigned level, const char *fmt, ...);    // C++ Language

willLog — Container Method

This method returns whether a certain level of logging will in fact be put into the system log. It can be used to conditionalize code that should only be run if certain level of logging is enabled.

Synopsis

bool (*willLog)(unsigned level); // C Language
bool willLog(unsigned level);    // C++ Language

Returns

This method returns true if the logging level supplied in its argument is enabled for inclusion in the system in the current environment.

time/getTime — Container Method

This method retrieves the time of day as a 64 bit unsigned number that represents GPS time in units of 2^-32 seconds (~233 ps). The most significant 32 bits hold GPS time in seconds. The accuracy of this time is dependent on the container implementation.

Note that GPS time is monotonic, as opposed to UTC time (the POSIX standard), which is subject to leap seconds etc.

Synopsis

RCCTime (*time)(); // C Language
RCCTime getTime(); // C++ Language

Returns

This function returns GPS time in units of 2^-32 seconds.

The RCC::RunCondition C++ Class and RCCRunCondition C Structure

This class is used by the container to determine when it is appropriate to invoke the run method. The RCC::RunCondition class has convenience constructors, methods, and worker-writable data members. In most cases the convenience constructors are sufficient and no member data or method call is required. The getRunCondition and setRunCondition container methods described below are used to set and get the current run condition objects for the worker. This class, and those container methods, are only needed when the default run condition is not appropriate.

The RCC::PortMask (C++)/ RCCPortMask (C) type used with run conditions specifies a set of ports that must be ready at the same time. A mask is considered “true” when all ports whose bit is set in the mask (i.e. mask & (1 << port_ordinal) != 0) are ready or are not connected. The RCC_ALL_PORTS mask constant indicates all a worker’s ports.

Table 69: RunCondition Constructors

RunCondition()

Specify the default run condition: no timeout, and all connected ports must be ready. If the worker has no ports, it will always run. The constructor is equivalent to: RunCondition(RCC_ALL_PORTS, RCC_NO_PORTS)

RunCondition(RCC::PortMask first, ...)

Specify a list of port masks as variable arguments that is terminated by the value RCC_NO_PORTS. No timeouts are indicated. Each mask indicates a set of ports. The run condition is considered true when any of the masks is true. RunCondition(RCC_NO_PORTS) indicates: never run.

RunCondition(RCC::PortMask *masks, uint32_t usecs = 0, bool timeout = false)

Specify a pointer to an array of masks (terminated by RCC_NO_PORTS) as well as the timeout, with defaults for timeout arguments. The masks in the array are copied during construction. The first argument must be pointer type, and not RCC_NO_PORTS itself or NULL. A value of nullptr may be used to indicate that the run condition is always true.

In all cases, a port’s readiness is based on a single buffer being available, even if more than one buffer exists, and even if the MinBufferCount attribute for the port in the OWD is set to a value greater than 1. Knowing that more than one buffer exists at a port does not imply that more than one will be available in any given invocation of the run method.

A worker using RunCondition objects typically declares them as member data that are configured in member initializers in the worker’s constructor. For example, in the following code a run condition data member is declared, and initialized appropriately. Then it is used dynamically as conditions warrant.

class XyzWorker : public XyzWorkerBase {
  RunCondition m_aRunCondition;
  XyzWorker() : m_aRunCondition(1 << Xyz_Inport, RCC_NO_PORTS) {
  }
  RCCResult run(bool timedOut) {
    if (need_to_change_run_condition)
      setRunCondition(&m_aRunCondition);
    else if (need_default_run_condition)
      setRunCondition(NULL); // restore default run condition
  }
}

The following table describes the accessible methods that can be used when the convenience constructors above are not sufficient to specify or control RunConditions. All have no return value (i.e. are void).

Table 70: RunCondition Methods

Method Name and

Arguments

Member

Description

disableTimeout()

Disable the timeout aspect of the run condition. The timeout time value (usecs) is unchanged.

enableTimeout()

Enable the timeout aspect of the run condition, using the previously specified usecs value.

enableTimeout (uint32_t usecs)

Enable the timeout aspect of the run condition, supplying a new usecs value, which will be stored in the object.

setTimeout (uint32_t usecs)

Set the timeout usecs value of the run condition, which will not necessarily enable the timeout if not enabled.

setPortMasks (RCCPortMask *)

Set the port masks for the run condition from the array pointed to by the argument, which must be terminated by RCC_NO_PORTS. The array is copied into the run condition. This argument itself must not be NULL or RCC_NO_PORTS since they are not pointers. The nullptr value may be used to indicate an aways-run condition.

setPortMasks (RCCPortMask, ...)

Use a variable argument list to set the port masks of the run condition. The arguments must be terminated by RCC_NO_PORTS.

The following table describes the accessible data members of the RunCondition structure. They apply to the C RCCRunCondition structure as well as the C++ OCPI::RCC::RunCondition class.

Table 71: RunCondition Members — C and C++

Member

Name

Member

Data Type

Member

Description

portMasks

RCCPortMask *

A pointer to an array of port masks, terminated by RCC_NO_PORTS, each of which indicates a bit-mask of port readiness. The run condition is considered true when any of the masks is true. A mask is true when all indicated ports are ready (logical AND of port readiness). If the pointer itself is NULL or nullptr, the run condition is always true. A mask bit set for an unconnected port is ignored: the default run condition can be used with unconnected ports.

timeout

bool

Indicates that the usecs member determines when enough time has passed to make the run condition true. This value is used to enable or disable the timeout, without changing usecs.

usecs

uint32_t

When enabled by the timeout member, if this amount of time has passed (in microseconds) since the run method was last entered, the run condition is true.

The run condition is the logical OR of the portMasks and the timeout. For C language workers, if the worker offers no run method in its RCCDispatch structure (see below), run conditions are ignored. If the portMasks member is nullptr, it indicates that no port readiness check is performed and the run condition is always true. The default run condition is a single port mask with all ports enabled. Typical combinations are:

Table 72: Run Condition Combinations

Shorthand

Port mask

Timeout

RunCondition Description

Always run

portMasks == NULL

ignored

The worker is always ready, no timeout

Run when data ports are ready

portMasks != NULL

False

Run condition is TRUE when any mask is true. If there are no masks, i.e. portMasks[0] == RCC_NO_PORTS, then the run condition is FALSE.

Run when data ports are ready or timeout.

portMasks != NULL and portMasks[0] != RCC_NO_PORTS

True

Run condition is TRUE when any mask is true OR if the timeout expires. The timeout will take effect if time passes without any mask being satisfied.

Periodic execution

portMasks != NULL and portMasks[0] == RCC_NO_PORTS

True

Since portMasks[0] == RCC_NO_PORTS, port masks can never be true, thus this establishes periodic execution independent of port readiness.

getRunCondition — Container Method — C++ Language Only

This method is used by the worker to retrieve its current run condition. If the current run condition is specified to be the default, NULL is returned. Otherwise the pointer returned is the most recent one passed from the worker to the container in the setRunCondition method. Because the value returned is the pointer that the worker provided in a previous call to setRunCondition, it can be used as a state variable in the worker, e.g.:

if (getRunCondition() == &m_myruncond2)
  // do things for state2

Synopsis

RCC::RunCondition *getRunCondition();

Returns

This function returns a pointer to the run condition most recently set by the worker using the setRunCondition call, or NULL.

setRunCondition — Container Method — C++ Language Only

This container method is used by the worker to set a new run condition. If the run condition supplied is NULL, the default run condition becomes active, which is to wait for all ports to be ready, with no timeout. If the worker needs to start execution with a non-default run condition, it can call this container method in its constructor, or in its initialize or start methods.

When not NULL, the RCC::RunCondition pointer supplied as the argument is stored in the container as a pointer and is expected to remain valid until a subsequent call to this method or until the worker is destroyed. The object referenced by this pointer is not copied and must not be modified while it is the current run condition.

Synopsis

void setRunCondition(RCC::RunCondition *rc);

Returns

No return value.

Port Management Data Members and Methods

This section describes port management methods. The current buffer at a port is in fact part of the port object. This means methods on the current buffer are in fact methods on the port itself. Otherwise buffer methods are used on specific buffer objects. Those are described in the next section.

In the C language, port management methods are directly accessed function pointer members in the RCCWorker data structure, like all container methods. Each has a RCCPort pointer as an argument.

In C++, each port is a member data object (of the worker object) whose name is the name of port (from the OCS). Where port management methods are methods on that object.

As an example, to advance a port in C, and optionally access the data of the now-current buffer, is below:

RCCPort *inport = &self->ports[MY_IN_PORT];

if (self->container.advance(inport)) {
  void *p = inport->current.data;
  ...
}

In C++ this would be:

if (inport.advance()) {
  void *p = inport.data();
  ...
}

Note that in C++, the current buffer object is inherited by the port object, so the port data member is the object used to access the current buffer’s data() method.

The run method can indicate that all ports should be advanced by the special return value RCC_ADVANCE. It can also indicate disposition of buffers and ports by using the release, send, request, take, or advance container methods.

C Language: The run method accesses information about current buffers through members of each port’s RCCPort structure member in the ports array in the RCCWorker structure (e.g. self->ports[n], where n is the ordinal for the port). The current.data member is the pointer to the message data for both input and output ports. For input ports, the input.length structure member of the RCCPort is the length of bytes of the message in the current buffer, and input.u.operation is the opcode for the current input message. For output ports, the output.length is the length of bytes of the message in the current buffer, and output.u.operation is the opcode for the current output message.

C++ Language: The run method accesses port status and buffer contents using the worker object’s port data member methods.

If a port is in the worker’s run condition, then it can assume a current buffer is present at that port when the run method is entered. In this case checking for availability of data is unnecessary and redundant. If the port’s readiness is not implied by the run condition, the worker can test whether there is a current buffer; in C, the port’s current.data member is non-NULL, in C++, the port’s hasBuffer() returns true. In either case, a port’s readiness only depends on a single buffer being available. Even if multiple buffers exist, the port readiness is only based on the availability of a single buffer.

In the C language, and in some cases for C++, the message buffers are accessed as a raw array of bytes, where the worker is responsible for correctly formatting the message in the buffer.

In C++, there is alternative set of methods that format the message correctly for the worker, and these methods should be preferred in most cases. These are described in the Accessing Messages in C++ Language Workers topic.

advance — Release Current Buffer, and Request Another

This method releases the current buffer at a port, and requests that a new buffer be made available as the current buffer on the port. This is a convenience/efficiency combination of the release (the current buffer) and request (a new current buffer) methods described below.

An optional minimum size in bytes may be requested (0 is the default in C++, 0 can be supplied in C). Zero means no minimum size is requested. The default size is normally based on the protocol at the port. When there is no protocol this argument allows the worker to specify a size that must be satisfied, or an exception is thrown.

Synopsis

RCCBoolean (*advance)(RCCPort *port, size_t minSize); // C language
bool Port::advance(size_t minSize = 0);               // C++

Returns

A boolean value is returned indicating whether the request aspect of this method was immediately satisfied. If so hasBuffer will now return true.

hasBuffer — Query a Port for Whether It Has a Current Buffer

This method returns whether a port has a current buffer. If a port is already part of a workers’s run condition, and it is in every port mask of the run condition, it can be assumed to have a current buffer whenever the worker is run. This is the case for 90% of all workers. This method is only needed and used when a port is not specified in one or more port masks of the run condition, as might be the case for a port that received some exceptional condition message. No such testing is needed when the port is in the run condition since port readiness is implicit when the run condition is satisfied.

In C, this is a port structure member access. In C++ it is a port method.

Synopsis

Port->current.data != NULL     // C Language
bool RCCUserPort::hasBuffer(); // C++ Language

Returns

For C++, a bool value is returned indicating whether the port has a current buffer.

isConnected — Query a Port for Being Connected

This method returns whether a port is currently connected or not. Component specifications (OCS) indicate whether each port of a component (and thus all workers) must be connected before workers are started or run. A port is considered “optional” for a worker if the worker can tolerate the port not being connected. If a port is not optional, the worker code can assume it is connected, and this method would not be needed or used.

In C, this is a Worker (self) structure member access. In C++ it is a port method.

Synopsis

self->connectedPorts & (1 << PortOrdinal); // C Language
bool RCCUserPort::isConnected();           // C++ Language

Returns

For C++, a bool value is returned indicating whether the port is connected.

ordinal — Obtain an Ordinal for a Port

This method returns the ordinal of a port, which may be used wherever port ordinals are required such as in port masks for run conditions.

In C, this is simply an enumeration value. In C++ it is a port method.

Synopsis

<WORKER>_<PORTNAME>;                     // C Language
RCCOrdinal RCCUserPort::ordinal() const; // C++ Language

Returns

For C++, an RCCOrdinal non-negative integer value is returned.

request — Request a New Buffer

This method requests that a new buffer be made available as the current buffer on a port. If the port already has a current buffer, the request is considered satisfied. This request indicates to the container that it should make a buffer available when possible. Without an explicit request, the container may not make a buffer available since that would dedicate resources when they may not be needed. An implicit request is made for all ports that are part of the current run condition.

An optional minimum size may be requested.

Note that this method is not used or needed when ports are advanced, only when buffers are being explicitly managed (e.g. released, etc.).

Synopsis

RCCBoolean (*request)(RCCPort *port, size_t minSize); // C Language
bool Port(size_t minSize = 0);                        //C++ language

Returns

A boolean value is returned indicating whether the request was immediately satisfied.

send — Send an Input Buffer on an Output Port

This method sends an input buffer on an output port. If the buffer is a current buffer for a port, it will no longer be that port’s current buffer. Buffer ownership passes back to the container. This method is used to effect zero-copy transfer of a message from an input port to an output port.

C Language: The operation and message length are supplied as arguments.

C++ Language: The operation and message length are attributes of the buffer object and can be changed before it is sent using the setOpCode and/or setLength methods.

The sent buffer is either a current buffer of an input port or a buffer taken from an input port.

Note: when a buffer is sent to an output port using send, it is bypassing (and not affecting) any current buffer on that port. Thus after send, the port should typically not be advanced (e.g. by returning RCC_ADVANCE), since that would essentially also send the current buffer in addition to the buffer that was explicitly sent. Furthermore, when send is used, the output port is normally not included in the run condition, since the availability of the output port’s current buffer is irrelevant. When using send on an output port the default run condition for the worker is inappropriate.

Synopsis

void (*send)(RCCPort* port,
             RCCBuffer* buffer,
             RCCOrdinal op,
             uint32_t length);           // C Language
void RCCPort::send(RCC::Buffer &buffer); // C++ Language

Returns

Nothing is returned.

setDefaultLength — Set Default Message Length at an Output Port — C++ Language Only

This method sets the default message length in bytes for a port. It is useful when messages produced at a port will always be the same size.

This method is used only when the message content is manipulated as a raw untyped buffer, rather than using the message content access methods described below.

The initial setting of the default message length is the length of the buffer.

This method is only available in C++.

Synopsis

void RCCUserPort::setDefaultLength(size_t length); // C++ Language

Returns

Nothing.

setDefaultOpCode — Set Default Opcode at Output Port — C++ Language Only

This method sets the default opCode for an output port. It is useful when messages produced at a port will typically have the same opcode, among those defined in the protocol.

The initial setting of the default opcode is zero (indicating the first operation specified in the associated protocol). Setting the default opCode does not prevent setting the opCode on a per-message basis at any time during execution.

This method is only available in C++.

Synopsis

void RCCUserPort::setDefaultOpCode(RCCOpCode opcode); // C++ only

Returns

Nothing.

take — Take a Buffer from an Input Port

This method takes the current buffer from a port, and optionally releases a previously taken buffer. This method is used when workers need to maintain a history of one or more previous buffers while still requesting new buffers, e.g. for sliding window algorithms. This method only applies to input ports.

The optional buffer to be released is provided by a possibly NULL pointer to a buffer. This buffer-to-release argument is a convenience feature of the API to allow cycling of buffers in a single call, e.g.:

class XyzWorker {
  RCCUserBuffer *m_prev;        // remember buff from previous run
  XyzWorker() : m_prev(NULL) {}
  void process_curr_and_prev(RCCUserBuffer *curr,
                             RCCUserBuffer *prev) {}
  run() {
    process_curr_and_prev(&in, m_prev);
    m_prev = &in.take(m_prev);
  }
}

In C, while we logically consider this a port method, it is actually a container method with three arguments:

  • an RCCPort pointer indicating the port from which the buffer is taken

  • an RCCBuffer pointer indicating, if not NULL, a buffer to release

  • an RCCBuffer pointer as an output argument where the taken buffer structure will be copied (not the data, just the structure).

In C++, take is a port method with a single buffer-to-release argument and the taken buffer is simply returned via a reference.

Ownership of the taken buffer is passed to the worker. The current buffer now taken is no longer the current buffer. This method is used when the worker needs access to more than one buffer at a time from an input port. Take implies a request (to get another current buffer).

It is an error to call this method when the port does not have a current buffer.

Synopsis

void (*take)
  (RCCPort* port, RCCBuffer* releaseBuffer,
   RCCBuffer* takenBuffer);                // C language
RCCUserBuffer &Port::take
  (RCCBuffer *release = NULL);             // C++ Language

Returns

Nothing is returned in C, since the taken buffer object is copied to the location supplied by the takenBuffer argument. In C++, a reference to the taken buffer is returned.

Buffer Management Data Members and Methods

This section describes methods that apply to buffers separate from ports. As mentioned above, when a buffer is the current buffer at a port, the port object itself is normally used with these buffer methods. In C++, the port object inherits the buffer object that is the current buffer. In C, the current buffer is the current member of the RCCPort structure.

When methods deal with opCodes, they are dealing with an ordinal for the message in a buffer. The opCode identifies which type of message is in the buffer, among those defined by the protocol. OpCode values are zero-origin in the order of how the operations are defined in the protocol (OPS) XML file. Enumeration constants for the messages in a protocol are generated and are described in the Code Generation for RCC Workers topic.

In C++ buffer management is via buffer methods — member functions of buffer objects. In C, buffer management is either via container methods or direct access to structure members.

In the C language, and optionally for C++, the message buffers are accessed as a raw array of bytes, where the worker is responsible for correctly formatting the message in the buffer. The methods in this section other than setOpCode, are used only for this raw buffer access.

In C++, there is alternative set of methods that format the message correctly for the worker, and these methods should be preferred in nearly all cases. These are described in the Accessing Messages in C++ Language Workers topic below.

EOF indications may be received at input ports and sent at output ports. The role and purpose of EOFs is described for all types of workers in the End-of-File (EOF) Indications on Data Ports topic. However, most workers never see or generate EOFs because the default behavior for EOFs is that the container handles them without ever showing them to the worker. Unless a worker explicitly asks to handle EOFs by setting the workerEOF attribute at some of its ports to true, the container propagates the EOF indication from input to output without exposing EOFs to the worker at all.

The default propagation behavior is: when an EOF arrives at the first input port, it is propagated to all output ports automatically and the worker is not run any more. Workers can always send EOFs on output ports regardless, if that is useful. To suppress the automatic propagation of EOFs “behind the worker’s back”, set the workerEOF attribute to true on the first input port, and EOFs will be delivered to that port and propagation is suppressed.

checkLength — Check Size of the Buffer — C++ Language Only

Check that a message of a given size in bytes will fit into the buffer, usually associated with an output port. An exception is thrown if the message will not fit. This is useful when the worker is creating a variable length message and wants to ensure it will fit into the available buffer. This check results in an error that the worker cannot check, and is intended to avoid unexpected buffer size mismatches, similar to the “assert” standard library function.

Synopsis

void RCCUserPort::checkLength(size_t neededSize); // C++ Language

Returns

Nothing.

data — Access the Raw Contents of the Buffer

This method returns a pointer to the raw data in a buffer. It is analogous to the data method in C++ STL container classes, although it has no type.

Synopsis

void *p = buffer->data;       // C Language
void *RCCUserBuffer::data();  // C++ Language

Returns

The value returned is a void pointer to the contents of the buffer.

length — Retrieve the Length of the Message in a Buffer – C++ Language Only

This method returns the number of bytes of the message in the buffer, which is typically an input buffer. This is C++ only. When using C, access to the length is via the current buffer at a port, e.g. port->input.length.

Synopsis

size_t RCCUserBuffer::length() const; // C++ Language

Returns

The method returns the length in bytes of the message in the buffer.

maxLength — Retrieve the Maximum Available Space in the Buffer.

This method allows the worker to retrieve the actual size of the buffer, typically used on output buffers.

Synopsis

size_t len = buffer->maxLength;           // C language
size_t RCCUserBuffer::maxLength() const;  // C++ language

Returns

The size of the buffer in bytes is returned.

opCode — Retrieve the OpCode of the Message in a Buffer

This method retrieves the opCode of the message in the buffer, typically an input buffer. This is C++ only. When using C, access to the opCode is via the current buffer at a port, e.g. port->input.u.operation

Synopsis

RCCOpcode op = port->input.u.operation;   // C language
RCCOpCode RCCUserBuffer::opCode() const;  // C++ Language

Returns

This method returns the opCode of the message in the buffer.

eof — Retrieve the EOF status of the Buffer

This method retrieves the EOF status of the buffer (and port), for an input buffer. This is C++ only. In C, access to the EOF status is via the current buffer at a port, e.g.:

port->input.eof

Synopsis

RCCBool op = port->input.eof;     // C language
bool RCCUserBuffer::eof() const;  // C++ Language

Returns

This method returns the EOF status as a boolean value.

release — Release a Buffer

This method releases a buffer for reuse. If the buffer is the current buffer for a port, it will no longer be the current buffer. Buffer ownership passes back to the container. Buffers for a port must be released in the order obtained, per port. Note that this method is not used or needed when ports are “advanced”, only when buffers are obtained from a port using other port management functions such as take.

Releasing a current buffer does not imply requesting a new current buffer: that request must be explicit. A release without a request might be useful if a worker is entering a mode where it no longer needs any more data from an input port or it no longer needs to send any more buffers on an output port.

In C, this is a function with the buffer pointer as an argument, e.g. called using:

self->container.release(buffer);

In C++ it is a buffer method invoked on the buffer object itself.

Synopsis

void (*release)(RCCBuffer* buffer); // C Language
void RCCUserBuffer::release();      // C++ Language

Returns

Nothing.

setLength — Set the Length of the Message in a Buffer — C++ Language Only

This method sets the length in bytes of the message in a buffer.

This is C++ only. When using C, access to the length of an output buffer is via the current buffer at a port, e.g. port->output.u.length, or via the send port method.

Synopsis

void RCCUserBuffer::setLength(size_t length); // C++ Language

Returns

Nothing.

setOpCode — Set the OpCode for the Message in a Buffer — C++ Language Only

This method sets the opCode of the message in a buffer.

This is C++ only. When using C, access to the opCode of an output buffer is via the current buffer at a port, e.g. port->output.u.operation, or via the send port method.

Synopsis

void RCCUserBuffer::setOpCode(RCCOpCode op); // C++ Language

Returns

Nothing.

setInfo — Set the Metadata Associated with a Buffer — C++ Language Only

This method is a convenient combination of setting both the opCode and the length of the message in a buffer. It is typically used on output buffers, when both opCode and length are being set at the same time.

Synopsis

void RCCUserBuffer::setInfo(RCCOpCode op, size_t length); // C++

Returns

Nothing.

setEOF — Set the EOF status of the Buffer

This method sets the EOF status of the buffer (and port), for an output buffer. This is C++ only. In C, setting the EOF status is via the current buffer at a port, e.g.: port->output.eof

Synopsis

port->output.eof = true;            // C language
void RCCUserBuffer::setEOF() const; // C++ Language

Returns

This method sets the EOF status as a boolean value.

topLength — Retrieve the Size of the Single Sequence in a Message — C++ Language Only

Normally, sequence lengths are retrieved using the message/operation access methods described below in the Accessing the Contents of Messages topic. However, when message contents are accessed as an untyped raw buffer, this method allows the worker to retrieve the number of elements of a given size in the message without the container knowing the data type of the message. It performs the size conversion and error check in one API.

Untyped raw buffers are used when no protocol is defined for the port, e.g. when a worker needs to process any protocol or when it has some private mechanism for determining the contents of a message.

This method only applies to input buffers that contain messages consisting of a single sequence argument. It retrieves the length, in elements, of that sequence. The elemSize argument to topLength is the size in bytes of the elements of the sequence in the buffer. An error check is made to ensure that the size of the message in bytes is divisible by the elemSize value provided.

The message must consist of exactly one sequence for this method to be valid.

This method is only available in C++.

Synopsis

size_t RCCUserBuffer::topLength(size_t elemSize); // C++ Language

Returns

For C++, this method returns a size_t value indicating the number of sequence elements in the message, given the size of elements known by the worker.

Accessing the Contents of Messages

The sections above described methods used to access the raw content and metadata (length and opcode) of messages in buffers. Those methods operate independent of the actual types of the data in the messages, and put the burden of accessing typed data on the worker code. I.e. the worker code would have to cast the pointer types and manually deal with the multiple data types for the arguments in a message.

The actual format of messages in the buffer is described in the section Message Payloads on Data Ports.

Accessing the individual arguments (fields) in the payload for an input message (or setting the fields of an output message) is facilitated differently in C vs. C++.

Accessing Messages in C Language Workers

In C, data structures are generated that can be overlaid on buffers to access fields of a message up to and including the first variable length field (sequence or string). Each port has a set of data structures defined for the messages that may be in its buffers. These structures are described in the Code Generation for RCC Workers topic, but the basic pattern, for worker WXY, port PXY, with protocol PRXY, with operations Op1 and Op2, is:

typedef struct {
  ... fields in the message for Op1 messages ...
} WxyPxyOp1;
typedef struct {
  ... fields in the message for Op2 messages ...
} WxyPxyOp2;

Note that the structure typedefs are CamelCase, the opcode enumeration values are upper case, and the structure members are the case of the argument names in the protocol. In C, messages are accessed based on opCode like this:

switch (port->input.u.operation) {
case PRXY_OP1:
  {
    WxyPxyOp1 *p = port->current.data;
    // use p-> to access the message structure
    break;
  }
case PRXY_OP2:
  {
    WxyPxyOp2 *p = port->current.data;
    // use p-> to access the message structure
    break;
  }
}

If the messages are simply a sequence or array of one basic type (e.g. float), directly assigning the buffer pointer to that pointer type is sensible, e.g.:

switch (port->input.u.operation) {
case PRXY_OP1:
  {
    float *p = port->current.data;
    // use p-> to access the message data
    break;
  }
...

The major limitation of this scheme is that the structures only cover the fields of messages up to and including the first variable length field. After that, the worker must manually access the remaining fields using pointer arithmetic and casting. This covers most protocols used in simple systems, but is painful when the limitation is exceeded.

A second limitation for C language workers is that there is no error checking that the correct message structure is being used with its corresponding opCode.

Accessing Messages in C++ Language Workers

C++ workers have data members for each port whose name is the port’s name in the OCS. Each such port data member has an accessor for the information for each possible message in the port’s protocol. Each field of the payload for each operation also has an accessor. Accessing the arg1 field of the op1 operation message on the protocol for port in, would simply be:

in.op1().arg1()

These C++ message argument access methods return a const reference to the field for input ports, and return a non-const reference for output ports. They will generate an exception if used when the message in the buffer has the wrong opCode for the operation accessor used. E.g., if in.op1() is used when the current opcode is not op1, an exception occurs. If the argument type was float, then the accessors defined for in.op1() would be:

const float &arg1() const;  // for arguments in an input buffer
float &arg1();              // for arguments in an output buffer

When the argument type is a sequence or array, the field accessor returns a reference to an object that has the following methods, which act like the same-named methods defined in the ISO C++ STL container types (assuming the data type is float):

const float *data() const;  // pointer to access the floats on input
size_t size() const;        // number of floats available on input
float *data();              // pointer to access the floats on output
size_t resize(size_t n);    // to set the number of floats on output
size_t capacity() const;    // to get the available space on output

For arrays or sequences in output buffers, the resize method must be called for each message.

If arg1 in the above example was a sequence of unsigned shorts, access to this field is:

uint16_t *vals = in.op1().arg1().data(); // pointer to data
size_t nvals = in.op1().arg1().size();   // number of elements

How a Worker Accesses its Properties

The Worker Property Structure

A structure type (e.g. XyzProperties) is generated for the worker, and placed in the gen/XYZ_Worker.h (for C) or gen/xyz-worker.hh (for C++) file, and contains members for each property that is not a parameter. Structure members for properties are declared const, unless the property is defined as either:

  • volatile — meaning the worker can change it at any time, or

  • none of initial, writable or parameter — meaning the worker can set it during initialization only and not after start.

Thus setting the value of a property that is neither of the above will result in a compilation error.

The name of the structure type (typedef name in C) is XyzProperties where “Xyz” is the capitalized worker name.

Properties that are scalar values (not sequences, arrays, structures or strings), are simple scalar structure members with the property’s name. Properties that are arrays are declared as such in this structure, including multi-dimensional arrays. The structure member for properties that are sequences are themselves structures, with two members: a uint32_t length member indicating the number of valid elements present, and a data member which is an array of the maximum number of elements declared for the property in the sequenceLength attribute that defines the property (its maximum number of elements). Similarly, structure members for properties that are of type string, are arrays of type char of dimension stringLength+1, with the +1 to leave room for the terminating zero.

Remember that for properties (as opposed to protocols), sequences and strings always have a maximum size (i.e. are bounded).

All property values are initialized to zero when the worker is created at runtime and the worker’s code can always assume this. This initialization is in effect prior to the initialize control operation (and prior to the C++ constructor). After the initialize operation, but before the start operation (or the first invocation of the run method if there is no start method) any application-supplied initial values are set by control software.

For volatile properties or those that are not declared as writable or initial (and thus are read-only for control software), the worker is expected to set any non-zero initial value before the worker is started. It may do this initialization either in the initialize method, in the C++ constructor, or perhaps by a notification that some other property has been written. Note that control software may cache the non-volatile property values since they are not expected to change after the worker is started (i.e. they are not volatile).

The Accessing Worker Properties in C

The properties member of the RCCWorker structure (which is supplied as the *self argument to all worker methods), points to the memory that holds the worker’s properties, with type void *. When needed by the worker to access property values, it is first converted to the structure pointer type that defines the layout of the worker’s properties, and then used to access individual properties, e.g.:

XyzProperties *p = self->properties; // implicit cast from void*
p->myprop1;          // scalar property
p->myprop2.length;   // number of elements in sequence for myprop2
p->myprop2.data[n];  // the nth element of the myprop2 sequence
p->myprop2.data;     // the address of the elements in myprop2
p->myprop3[a][b][c]; // an element of a 3d array property
p->myprop4[0];       // the first character of the myprop4 string
strlen(p->myprop4)   // the length of the myprop4 string.

Accessing Worker Properties in C++

The worker object has a member function, properties(), that returns a reference to the XyzProperty structure. Worker code accesses properties that are not parameters by simply using this member function. The accesses similar to the C example above is:

XyzProperties &p = properties();
p.myprop1;          // scalar property
p.myprop2.length;   // number of elements in sequence for myprop2
p.myprop2.data[n];  // the nth element of the myprop2 sequence
p.myprop2.data;     // the address of the elements in myprop2
p.myprop3[a][b][c]; // an element of a 3d array property
p.myprop4[0];       // the first character of the myprop4 string
strlen(p.myprop4)   // the length of the myprop4 string.

Property Access Notifications in C++

Workers sometimes need dynamic notifications when properties are written or read by control software. E.g. the worker would like to run some code just before a property’s value is returned to control software, (when control software reads the property), to create the correct dynamic value to return. Similarly, a worker would run some code after control software writes a property, to cause some other side affect of the new written value. For written values, this eliminates the need for checking by the worker to determine when the value has changed.

These capabilities are enabled when the property in the OWD has the readSync or writeSync attributes set to true. The readSync attribute being true indicates that the worker would like to run some code before the value of the property is passed back to the control software reading the property. The writeSync attribute being true indicates that the worker would like to run some code after a new value is written, in order to implement some side effect when the property value is changed.

An example of using readSync is when the value is computed based on some dynamic condition, e.g. reading a real-time or physical sensor value. An example of writeSync is when writing a new value should atomically affect some other state of the worker.

Using the C language, there is no notification mechanism at this time.

In C++, when these attributes are set, the worker skeleton code includes empty implementations of notification methods for each such property that the worker author can fill out. For readSync properties, a worker member function called <property>_read must be present. For writeSync properties, a member function named <property>_written must be present. Both notification member functions take no arguments and return RCCResult, which allows these functions to indicate errors. A readSync notification function can set the property value locally and then return, and that value will then be conveyed back to control software.

A writeSync notification is called after the new value has been written, so the function can locally access the new value when it is called. The previous value is no longer available. If a worker needs to access the previous value it could declare a data member and assign it at the end of the notification method, and perhaps initialize this “previous value” data member using firstRun or start. E.g. assuming predicate is a boolean writable property with the writeSync attribute set to true:

class XyzWorker {
  bool m_prevPredicate;        // remember previous value
  RCCResult predicate_written() {
    // use both properties().predicate and m_prevPredicate
    m_prevPredicate = properties().predicate;
  }
  run() {
    // we know that as of firstRun, initial setting is valid
    if (firstRun()) m_prevPredicate = properties().predicate;
    ...
  }
}

Accessing the Values of Parameter Properties

Properties which are indicated as parameters by setting the parameter attribute to true in the OCS or OWD are accessed differently than other properties. They are defined as static const values whose name is all upper case <WORKERNAME>_<PROPERTYNAME>. Thus for worker wxyz and parameter property pqr, of type float, the value is:

static const float WXYZ_PQR;

These values are initialized at compile time and can be used accordingly. Internally, the parameter values are also defined as preprocessor macros of the form:

OCPI_PARAM_<worker-name>_<property-name>()

If the parameter’s value must be used in a preprocessor conditional e.g. #if <expr>, these macros can be used instead of the static const variables.

Controlling Slave Workers from Proxies — C++ Language Only

When the RCC OWD indicates that a worker is a proxy for other workers, as its slaves, it gives the proxy worker convenient access to each slave’s control operations and property accesses. Proxy workers typically have no data ports and no code in their run method (in C++, no run method at all). This allows proxy workers to standardize and/or simplify the control of the slave worker. The slave worker is usually device-specific, with device-level properties or configuration requirements that applications should not be concerned with.

An example would be a device worker that controls a clock generator device. This device worker typically exposes the native functionality of the device in a high performance and/or resource-conservative fashion. However, most applications would only want a subset of those options, that are uniform across all clock generators they might use.

Implementing normalization of the control/configuration interface in the RCC proxy worker provides users with a higher level standardized configuration interface. This relieves this burden from the embedded device worker which may be in an environment (e.g. on a small FPGA) that makes this normalization difficult or expensive. This also enables the device worker to faithfully and simply implement the device’s native control interface. This enables applications to exploit lower level device-specific features. Such a low level device worker might be controlled in different ways using different proxies.

A proxy worker, acting as the software module that implements specialized configuration and control sequencing of the device, can eliminate the need for control applications to use special non-component APIs: the proxy is just another component in the application’s XML, and its generic properties can be translated to actual device configuration settings.

For each slave worker mentioned in a slave attribute or in the <slaves> element, there is a member data object in the (proxy) worker’s class that is used to control the slave. Each slave is controlled using the slaves.<slave-name> data member, which is a class specifically generated per slave. The first or only slave can also be accessed using a data member whose name is simply slave.

These slave objects have methods to perform control actions or property accesses on the slave workers. The control methods have fixed method names, while the property access methods have names based on the property names of the slave’s properties. The property access methods are the roughly the same as the property access methods at the application level in the ACI except that properties are specified by the method name rather than a string name argument. As in the ACI, there are methods for accessing properties using text values in strings or using the underlying binary values.

These are all methods on the slave or slaves.<slave> member objects.

start — Slave Method

This method performs the start control operation on the slave worker, which may start it after initialization or resume it after suspension (after it has been stopped). If the proxy does not call this method in its own start method, then the slave will be started after all proxies have been started in any case. Thus use of this method is optional.

Synopsis

void start();     // C++ Language

stop — Slave Method

This method performs the stop control operation on the slave worker. If the proxy does not call this method in its own stop method, then the slave will be stopped after all proxies have been stopped in any case. Thus use of this method is optional. A proxy may stop (suspend) or start (resume) its slaves during its own execution, independent of its own start or stop methods.

Synopsis

void stop();      // C++ Language

isOperating, isFinished, isUnusable, isInitialized, isSuspended — Slave Methods

These methods query the slave and returns whether it is currently in the indicated control state. Slaves are not started before their proxies, and if a proxy does not start a slave in its own start method, then the slave will be started after all proxies are started and enter the operating state. See the Control Plane Introduction section of the CDG.

Synopsis

bool isOperating();       // C++ Language
bool isFinished();        // C++ Language
bool isUnusable();        // C++ Language
bool isInitialized();     // C++ Language
bool isSuspended();       // C++ Language

Returns

These methods return a boolean which is true when the slave is in the indicated control state.

getProperty_<property> — Slave Method

This method retrieves the value of a property in text format in a string passed as an output argument by reference. It has three optional arguments for controlling which part of the property to retrieve (if it is an array/sequence/struct) as well as other options. See the similar method in the ACI for the explanation of those optional arguments.

Synopsis

const char *
getProperty_<property>(std::string &val,
                       OA::AccessList &list = <none>,
                       OA::PropertyOptionList &options = <none>,
                       OA::PropertyAttributes *attrs = NULL);

Returns

This method returns a pointer to the c_str() contents of the first argument allowing the return value as a convenience to be used both in typical C++ output using <<, as well as C-style formatting operations (e.g. %s in printf).

setProperty_<property> — Slave Method

This method sets the value of a property in text format from a const char * or std::string & passed as an argument. It has an optional argument for controlling which part of the property to retrieve (if it is an array/sequence/struct etc.). See the similar method in the ACI for the explanation of the optional arguments.

Synopsis

void
setProperty_<property>(const std::string &val,
                       OA::AccessList &list = <none>);
void
setProperty_<property>(const char *val,
                       OA::AccessList &list = <none>);

get_<property> — Slave Method

This method retrieves a scalar value of the property and returns it as its native binary type. It is only available for properties that are scalar types or arrays of scalar types. If the type is an array, there are arguments for indices of each array dimension.

If the scalar type is string, there are extra arguments (after index arguments for arrays), that either reference a std::string to fill or a const char * pointer and size_t length to use as a buffer for the string’s value. In both these cases, the return value is a const char * which simply points to the provided buffer. As with the getProperty_<property> method, this return value is for convenient use for C++ << output or C formatted output.

Synopsis

// E.g. for a scalar boolean property
bool get_<property>();
// For a float property with 2 dimensions
float get_<property>(unsigned idx0, unsigned idx1);
// For a string property
const char *get_<property>(char *buf, size_t length);
const char *get_<property>(std::string &buf);
// For a string property that is a single-dimension array
const char *get_<property>(unsigned idx0, char *buf, size_t length);
const char *get_<property>(unsigned idx0, std::string &buf);

Returns

This method returns a scalar value from the property value in its native binary type, except for string properties which return a pointer to the provided buffer.

set_<property> — Slave Method

This method set a scalar value in the property. It is only available for properties that are scalar types or arrays of scalar types. If the type is an array, there are arguments for indices of each array dimension. After any index arguments, there is an argument for the value in its native binary type.

If the scalar type is string, the value is either a const char * argument or a const std::string & argument.

Synopsis

// E.g. for a scalar boolean property
void get_<property>(bool value);
// For a float property with 2 dimensions
void set_<property>(unsigned idx0, unsigned idx1, float value);
// For a string property
void set_<property>(const char *value);
void set_<property>(const std::string &value);
// For a string property that is a single-dimension array
void set_<property>(unsigned idx0, const char *value);
void set_<property>(unsigned idx0, const std::string &buf);

getLength_<property> — Slave Method

This method retrieves the maximum allowable length for a string property (which could be an array).

Synopsis

size_t getLength_<property>();

Returns

This method returns a the maximum length of a string for this property.

Examples

Here are some examples when one or more slaves is specified using the slave element:

uint8_t x = slaves.mydev.get_byteregA();
slaves.otherslave.set_byteregA(3);
x = slaves.specialslave.get_arrayprop(2);
slaves.mydev.set_arrayprop(3, x);
x = slaves.mydev.get_array2d(1, 2);
slaves.mydev.set_array2d(1, 2, x);
slaves.mydev.stop();
slaves.mydev.start();

Worker Dispatch Structures — C Language Only

When workers are loaded for execution by the container, the container finds the worker by getting access to its RCCDispatch structure. The only external symbol the worker code needs to define is the symbol that holds this structure.

The code generator generates this initialization in the skeleton file, and it can be further customized by the worker author.

RCCDispatch Structure Type

This type is the dispatch table for the operations of the C-language worker interface. It represents the functionality a worker provides to a container when it is loaded. The container must gain access to this structure when the worker is loaded and executed. All members are statically initialized by the worker source code. This structure also contains other descriptive information required by the container to use the worker.

The RCC C-language skeleton will contain a default initialization for this structure based on what is in the OWD, which looks like (for worker xyz.rcc):

XYZ_METHOD_DECLARATIONS;
RCCDispatch bias = {
  /* insert any custom initializations here */
  XYZ_DISPATCH
};

Any non-default member initializations must be placed after the XYZ_DISPATCH line, and be specified using the named member syntax, e.g.:

RCCDispatch bias = {
  /* insert any custom initializations here */
  XYZ_DISPATCH
  .memSize = 16*sizeof(MyData),
  .runCondition = &myRunCondition
};

The members of this structure that may be set this way are defined in the following table:

Table 73: Members of the RCCDispatch Structure — C Language Only

RCCDispatch

Member

Member Data Type

Member Description

memSizes

uint32_t*

This zero-terminated array of memory sizes indicates allocations required by the worker. Multiple allocations allow the worker to avoid aggregating its requirements in a single allocation. May be NULL, when no allocations are required. May use memSize below when there is only one allocation required.

Result will be in self->memories.

runCondition

RCCRunCondition*

The initial run condition used. If this pointer is NULL it implies a run condition of all connected ports being ready and no timeout. If there are no ports, the default is no port masks to check, indicating always ready to run.

optionalPorts

RCCPortMask

A mask indicating ports that may be unconnected. The default, 0, means all ports must be connected before the worker is started.

memSize

size_t

This size indicates a required memory allocation for the lifetime of the worker. May be zero, indicating no allocation is required. Result will be in self->memory.