V4 Design: Assembling Record Support

From EPICSWIKI

I have been asked to present an example of how record type definitions and the corresponding record support can be assembled from small general purpose elements. My example is the IVOA/IVOV block of functionality for output records; the example record type is the aoRecord.

I am leaving out all the non-essential fields and methods. Nevertheless, all example code compiles (with gcc -pedantic) and is (at least rudimentarily) tested.

The dbd definition for the aoRecord:

record(ao) extends iocRecord {
    field(value, float64) {
        asl(0)
        dynamic(yes)
    }
    #...
    field(invalidOutput,struct(invalidOutput)<float64>)
    #...
}

It uses the following menu and struct definitions:

menu(invalidAction) {
    choice(invalidActionContinue_normally,"Continue normally")
    choice(invalidActionDon_t_drive_outputs,"Don't drive outputs")
    choice(invalidActionSet_output_to_IVOV,"Set output to IVOV")
}
struct(invalidOutput)<Value> {
    field(invalidAction,menu(menuIvoa))
    field(invalidValue,Value)
}

Note the type argument Value in angle brackets. The notation is similar to the one in C++.

From these two dbd files, we generate the following header files. Note: I do not consider generated DA implementation here.

First aoRecord.h:

// header file generated from aoRecord.dbd
#ifndef aoRecordH
#define aoRecordH
struct aoRecord // : iocBaseRecord
{
    //...
    alarmSeverityMenu newAlarmSeverity;
    //...
    float64 value;
    //...
    invalidOutputStruct<float64> invalidOutput;
    //...
    // virtual method overrides
    void process();
};
#endif

We see that the sub structure invalidOutputStruct gets the value type as template argument. No to the struct itself:

// header file generated from InvalidOutput.dbd
#ifndef invalidOutputStructH
#define invalidOutputStructH
enum invalidActionMenu {
    invalidActionContinue_normally,
    invalidActionDon_t_drive_outputs,
    invalidActionSet_output_to_IVOV
};

// String invalidActionMenuStrings[3] = {
//     "Continue normally",
//     "Don't drive outputs",
//     "Set output to IVOV"
// };

template <typename Value>
struct invalidOutputStruct {
    invalidActionMenu invalidAction;
    Value invalidValue;
};
#endif

The type parameter in the dbd definition gets translated as a template parameter for the struct.

No to the interesting part: the struct and record support. For the struct support for invalidOutputStruct I present two variants. The first one uses function objects, whereas the second one doesn't. In both cases, the struct suport consists of exactly one C function:

// support routines for invalidOutputStruct
// definitions are inline only for (my) convenience
#ifndef invalidOutputStructSupportH
#define invalidOutputStructSupportH

// #include <stdexcept>
#include "invalidOutputStruct.h"

template <typename Value, typename WriteOutput>
inline void processInvalidOutput(
    invalidOutputStruct<Value>& ios,
    alarmSeverityMenu sevr, Value& value, WriteOutput write)
{
    if (sevr < alarmSeverityINVALID) {
        write(value);
        return;
    }
    switch (ios.invalidAction) {
    case invalidActionContinue_normally:
        write(value);
        break;
    case invalidActionDon_t_drive_outputs:
        break;
    case invalidActionSet_output_to_IVOV:
        write(ios.invalidValue);
        break;
// not really necessary, since C++ checks this statically
//     default:
//         throw std::out_of_range("invalidAction");
    }
}

// alternative interface without functor:
// return the value that should be written or
// a null pointer to indicate "don't write"

template <typename Value>
inline Value *processInvalidOutput_alt(
    invalidOutputStruct<Value>& ios,
    alarmSeverityMenu sevr, Value& value)
{
    if (sevr < alarmSeverityINVALID) {
        return &value;
    }
    switch (ios.invalidAction) {
    case invalidActionContinue_normally:
        return &value;
    case invalidActionDon_t_drive_outputs:
        return 0;
    case invalidActionSet_output_to_IVOV:
        return &ios.invalidValue;
    }
}
#endif

Now, aoRecord support can use this. Note that process() is now a method of struct aoRecord, see generated header file above.

#include <stdio.h>
#include <functional>

#include "epicsTypes.h"
#include "alarmSeverityMenu.h"
#include "invalidOutputStruct.h"
#include "aoRecord.h"

#include "invalidOutputStructSupport.h"

void writeValue(aoRecord *prec, float64 value)
{
    prec->value = value;
    //...write output...
    // test:
    printf("writing %lf\n", value);
}

void aoRecord::process()
{
    //...
    // handle invalidOutput
    processInvalidOutput( invalidOutput, newAlarmSeverity, value, 
        std::bind1st( std::ptr_fun(writeValue), this ) );
    // alternative for those who don't like functors:
    // float64 *pvalue = processInvalidOutput_alt( 
    //   invalidOutput, newAlarmSeverity, value );
    // if (pvalue) writeValue(this, *pvalue);
    //...
}