V4 Design: Assembling Record Support
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. he 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 interfaces 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);
//...
}