Difference between revisions of "V4 Data Interface"
From EPICSWIKI
KayKasemir (talk | contribs) m (Class Layout Image) |
KayKasemir (talk | contribs) (DA code example) |
||
| Line 27: | Line 27: | ||
// Full description of the data | // Full description of the data | ||
// This could be a pure interface, | |||
// while Ben suggests to use a struct/class | |||
// so that the user can easier get it as a whole. | |||
// Ben also suggests that getNumDims throws an | |||
// exception for scalars. | |||
// I think a scalar has dimension 1, size 1, | |||
// no exception. | |||
class EpicsDataDescriptor | class EpicsDataDescriptor | ||
{ | { | ||
| Line 46: | Line 53: | ||
// Read-Access to the data. | // Read-Access to the data. | ||
// This one tries to allow as many conversions as possible | // This one tries to allow as many conversions as possible. | ||
// Given an underlying Real, 64bit, scalar, | // Given for example an underlying Real, 64bit, scalar, | ||
// one can ask for a real64 but also int, string, ... | // one can ask for a real64 but also int, string, ... | ||
// You can also ask for the 5th element of a double array | |||
// or try to access the structure detail, | |||
// but then you have to expect a failed response. | |||
// So a simple client uses | |||
// if (! getDouble()) { .. error .. } | |||
// and a more sopisticated one uses | |||
// getType() == Real or Integer --> getDouble() | |||
// | // | ||
// The alternative would be individual reader interfaces | // The alternative would be individual reader interfaces | ||
// for numerics, strings, structures, numeric arrays, string arrays, ... | // for numerics, strings, structures, numeric arrays, string arrays, ... | ||
// or even separate ones for int8, int16, ... | // or even separate ones for int8, int16, ... | ||
// | // Marty's "network accessable data" email to core talk did the latter. | ||
// JCA also currently goes that way: | |||
// getDBR() == DOUBLE ? --> ( (DBR_Double)getValue() ).getDoubleValue() | |||
// getDBR() == INT ? --> ( (DBR_Int)getValue() ).getIntValue() | |||
class EpicsDataReader : public EpicsDataDescriptor | class EpicsDataReader : public EpicsDataDescriptor | ||
{ | { | ||
| Line 188: | Line 205: | ||
// EpicsDataReader | // EpicsDataReader | ||
bool getDouble(double &d) | bool getDouble(double &d) | ||
{ d = *p; } | { d = *p; | ||
return true; | |||
} | |||
private: | private: | ||
const double *p; | const double *p; | ||
| Line 396: | Line 415: | ||
} | } | ||
== Comparable DataAccess Example? == | |||
This is the result of my attempt to implement the TestRecord | |||
and main() code based on the August 23 snapshot of DataAccess. | |||
#include <iostream> | |||
#include <string> | |||
#include "dataAccess.h" | |||
#include "daPropertyId.h" | |||
using namespace da; | |||
static const propertyId pi_value("value"); | |||
static const propertyId pi_units("units"); | |||
static const propertyId pi_point("point"); | |||
static const propertyId pi_x("x"); | |||
static const propertyId pi_y("y"); | |||
class TestRecord : public propertyCatalog | |||
{ | |||
public: | |||
virtual ~TestRecord() {}; | |||
// Unsure if database code with pointer to the record | |||
// should still directly access the data, | |||
// or if the data should be protected/private. | |||
double value; | |||
string units; | |||
struct | |||
{ | |||
double x, y; | |||
} point; | |||
// propertyCatalog | |||
void traverse ( propertyViewer &v) const | |||
{ | |||
v.reveal(pi_value, this->value); | |||
// Don't know how to turn a std::string into a stringSegment | |||
// v.reveal(pi_units, this->units); ?? | |||
// Don't know how to reveal the 'point' | |||
// without changing the struct point itself | |||
// into a class derived from propertyCatalog | |||
v.reveal(pi_x, this->point.x); | |||
v.reveal(pi_y, this->point.y); | |||
} | |||
// Basically the same, supposedly no longer necessary with latest DA | |||
traverseModifyStatus traverse ( propertyManipulator & m) | |||
{ | |||
m.reveal(pi_value, this->value); | |||
// m.reveal(pi_units, this->units); ?? | |||
m.reveal(pi_x, this->point.x); | |||
m.reveal(pi_y, this->point.y); | |||
return tmsSuccess; | |||
} | |||
bool find ( const propertyId & id, propertyViewer & v) const | |||
{ // Naive, not using the 'locator' helper, but less code | |||
if (id == pi_value) | |||
{ | |||
v.reveal(pi_value, this->value); | |||
return true; | |||
} | |||
if (id == pi_x) | |||
{ | |||
v.reveal(pi_value, this->point.x); | |||
return true; | |||
} | |||
if (id == pi_y) | |||
{ | |||
v.reveal(pi_value, this->point.y); | |||
return true; | |||
} | |||
return false; | |||
} | |||
}; | |||
class Viewer : public propertyViewer | |||
{ | |||
public: | |||
void reveal ( const propertyId &id, const double &d, const propertyCatalog &x) | |||
{ // Don't know how to print the id.getName() stringSegment. | |||
cout << "?? = " << d << "\n"; | |||
} | |||
void reveal ( const propertyId &, const int &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const unsigned int &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const long &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const unsigned long &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const epicsTime &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const stringSegment &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const enumStateSet & ) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const arraySegment &, const propertyCatalog &x) | |||
{ | |||
} | |||
void reveal ( const propertyId &, const propertyCatalog &x) | |||
{ | |||
} | |||
}; | |||
int main () | |||
{ | { | ||
x | TestRecord fred; | ||
// Record/device support might have direct access to the fields: | |||
fred.value = 42.3; | |||
fred.units = "a.u."; | |||
fred.point.x = 10; | |||
fred.point.y = 15; | |||
// Assume other code that uses PropertyCatalog | |||
// without knowing about TestRecord | |||
propertyCatalog *pc = &fred; | |||
// It can access known properties in chosen order | |||
// Unclear how to get the 'value' as a 'double'. | |||
// This will 'reveal' whatever's there. | |||
// I assume one would have to implement a propertyCatalog | |||
// for target = 'double value' and then assign the given pc | |||
// to that target. | |||
Viewer dumper; | |||
pc->find(pi_value, dumper); | |||
// Or inspect unknown property catalogs | |||
pc->traverse(dumper); | |||
return 0; | |||
} | } | ||
== Thoughts == | |||
DataAccess | |||
- does not provide type information | |||
+ property ID handling already optimized | |||
+ allows segmented strings and arrays | |||
- unclear how to interface a std::string or char [] string | |||
- cannot interface the example record without turning 'struct point' into its own class, derived from propertyCatalog? | |||
* might be more complete but doesn't all compile | |||
This Interface | |||
+ does provide type information | |||
- property IDs only strings | |||
- requires contiguous C-String | |||
+ supports C-string | |||
+ can interface the struct point as is | |||
* is incomplete but compiles | |||
Latest revision as of 20:21, 2 September 2005
Issue
I think the following data interface is easier to understand than the data access approach.
Am I wrong, because Data Access is really much easier?
Am I right, but the approach shown here will never work?
Would it work, but performance would be unacceptable?
Data Interface Classes
// Description of basic data character
enum EpicsDataCharacter
{
Octet, // Raw bits,known to a server/client pair, but no one else
Bool,
Integer, // Discrete, available with 16, 32, 64 bits
Real, // Floating-point, available with 32 and 64 bits
String, // UTF-8
Enum, // >=0 Integer with state names
Structure,
TimeStamp // For efficiency?
};
// Full description of the data
// This could be a pure interface,
// while Ben suggests to use a struct/class
// so that the user can easier get it as a whole.
// Ben also suggests that getNumDims throws an
// exception for scalars.
// I think a scalar has dimension 1, size 1,
// no exception.
class EpicsDataDescriptor
{
public:
virtual EpicsDataCharacter getType() = 0;
virtual int getNumDims() = 0; // Scalar, vector: 1, matrix: >1
virtual int getSize(int dim) = 0; // Scalar: 1
virtual int getBitsize() = 0; // bits of integer or real. 0 for rest?
virtual bool isSigned() = 0; // Integer might be unsigned?
};
// When the exact size-locked types are needed,
// use these integer types from stdint.h:
// int8_t, int16_t, int32_t, int64_t
// and the following:
typedef bool bool_t;
typedef float real32_t;
typedef double real64_t;
// Read-Access to the data.
// This one tries to allow as many conversions as possible.
// Given for example an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
// You can also ask for the 5th element of a double array
// or try to access the structure detail,
// but then you have to expect a failed response.
// So a simple client uses
// if (! getDouble()) { .. error .. }
// and a more sopisticated one uses
// getType() == Real or Integer --> getDouble()
//
// The alternative would be individual reader interfaces
// for numerics, strings, structures, numeric arrays, string arrays, ...
// or even separate ones for int8, int16, ...
// Marty's "network accessable data" email to core talk did the latter.
// JCA also currently goes that way:
// getDBR() == DOUBLE ? --> ( (DBR_Double)getValue() ).getDoubleValue()
// getDBR() == INT ? --> ( (DBR_Int)getValue() ).getIntValue()
class EpicsDataReader : public EpicsDataDescriptor
{
public:
// Get data as type XX as best as possible.
// Example: getDouble returns true for
// Bool (d=0.0, 1.0), Integer, Real, Enum.
virtual bool getReal64(real64_t &r) = 0;
virtual bool getDouble(double &d) = 0;
virtual bool getInt(int &d) = 0;
// String
virtual bool getString(char *buf, int buf_len) = 0;
virtual int getStringLength() = 0;
virtual bool getCstring(const char *&c_ptr) = 0;
/// many more, at least one for each supported type.
// A structure is accessed via a (sub-)catalog.
// See below for PropertyCatalog.
virtual class EpicsPropertyCatalog *getStructure() = 0;
// Get array element. getDouble(x) == getArrayDouble(0, x)
virtual bool getArrayDouble(int element, double &d) = 0;
virtual bool getMatrixDouble(int dim, int element, double &d) = 0;
virtual class EpicsPropertyCatalog *getArrayStructure(int element) = 0;
/// For arrays, it could include per-element access
/// as well as accessors that copy sub-arrays into client buffers.
};
// "Property" has a name and data reader interface
class EpicsProperty : public EpicsDataReader
{
public:
virtual const char *getName() = 0;
};
// A catalog of properties
class EpicsPropertyCatalog
{
public:
virtual int getNumProperties() = 0;
virtual EpicsProperty *findProperty(const char *name) = 0;
virtual EpicsProperty *getProperty(int index) = 0;
/// Could also support name <-> ID conversions and
/// access via numeric ID.
};
C++ Implementation
// On the first read, skip to the 'Usage Example' below.
// This code isn't the greatest, but simply here to get
// example code that actually compiles & runs.
#include <string>
using std::string;
#include <string.h>
// Base class for the following EpicsProperty implementations.
// Provides default handlers for everything.
class EpicsPropertyBase : public EpicsProperty
{
public:
EpicsPropertyBase(const char *name)
{ this->name = name; }
// EpicsDataDescriptor
// defaults for scalar
int getNumDims()
{ return 1; }
int getSize(int dim)
{ return dim==0 ? 1 : 0; }
int getBitsize()
{ return 0; }
bool isSigned()
{ return true; }
// DataReader
bool getReal64(real64_t &r)
{ return getDouble(r); }
bool getDouble(double &d)
{
int i;
if (!getInt(i))
return false;
d = i;
return true;
}
bool getInt(int &i)
{
double d;
if (!getDouble(d))
return false;
i = (int)d;
return true;
}
bool getString(char *buf, int buf_len)
{
const char *ptr;
if (!getCstring(ptr))
return false;
strncpy(buf, ptr, buf_len-1);
return true;
}
int getStringLength()
{ return 0; }
bool getCstring(const char *&c_ptr)
{ return false; }
EpicsPropertyCatalog *getStructure()
{ return 0; }
bool getArrayDouble(int element, double &d)
{ return (element == 0) ? getDouble(d) : false; }
bool getMatrixDouble(int dim, int element, double &d)
{ return (dim == 0) ? getArrayDouble(element, d) : false; }
EpicsPropertyCatalog *getArrayStructure(int element)
{ return element==0 ? getStructure() : 0; }
// EpicsProperty
const char *getName()
{ return name.c_str(); }
private:
string name;
};
// EpicsProperty for 'double'. Actual data is outside,
// the EpicsDoubleProperty keeps a pointer to it.
class EpicsDoubleProperty : public EpicsPropertyBase
{
public:
EpicsDoubleProperty(const char *name, const double *p)
: EpicsPropertyBase(name)
{ this->p = p; }
// EpicsDataDescriptor
EpicsDataCharacter getType()
{ return Real; }
int getBitsize()
{ return sizeof(double)*8; }
// EpicsDataReader
bool getDouble(double &d)
{ d = *p;
return true;
}
private:
const double *p;
};
// Handler for string. Data is not owned by this class.
class EpicsStringProperty : public EpicsPropertyBase
{
public:
EpicsStringProperty(const char *name, const string *p)
: EpicsPropertyBase(name)
{ this->p = p; }
// EpicsDataDescriptor
EpicsDataCharacter getType()
{ return String; }
// EpicsDataReader
int getStringLength()
{ return p->length(); }
virtual bool getCstring(const char *&c_ptr)
{
c_ptr = p->c_str();
return true;
}
private:
const string *p;
};
class EpicsPropertyCatalogBase : public EpicsPropertyCatalog
{
public:
EpicsPropertyCatalogBase(int num_properties, EpicsProperty **properties)
: num_properties(num_properties), properties(properties)
{}
// EpicsPropertyCatalog
int getNumProperties()
{ return num_properties; }
EpicsProperty *findProperty(const char *name)
{
int i;
for (i=0; i<num_properties; ++i)
if (!strcmp(properties[i]->getName(), name))
return properties[i];
return 0;
}
EpicsProperty *getProperty(int index)
{
return (index >=0 && index < num_properties) ? properties[index] : 0;
}
private:
int num_properties;
EpicsProperty **properties;
};
// Handler for struct. Keeps pointers to EpicsProperty
// interfaces for the struct's fields.
class EpicsStructureProperty : public EpicsPropertyBase
{
public:
EpicsStructureProperty(const char *name, int num_properties, EpicsProperty **properties)
: EpicsPropertyBase(name), pc(num_properties, properties)
{}
// EpicsDataDescriptor
EpicsDataCharacter getType()
{ return Structure; }
// EpicsDataReader
EpicsPropertyCatalog *getStructure()
{ return &pc; }
private:
EpicsPropertyCatalogBase pc;
};
Example for data known at compile-time
// This is the code that provides a property catalog.
// Again skip on the first read.
//
// All of this could be generated from DBD information.
class TestRecord : public EpicsPropertyCatalogBase
{
public:
TestRecord();
// Unsure if database code with pointer to the record
// should still directly access the data,
// or if the data should be protected/private.
double value;
string units;
struct
{
double x, y;
} point;
private:
EpicsProperty *properties[3];
EpicsProperty *point_properties[2];
};
TestRecord::TestRecord()
: EpicsPropertyCatalogBase(3, properties)
{
point_properties[0] = new EpicsDoubleProperty("x", &point.x);
point_properties[1] = new EpicsDoubleProperty("y", &point.y);
properties[0] = new EpicsDoubleProperty("value", &value);
properties[1] = new EpicsStringProperty("units", &units);
properties[2] = new EpicsStructureProperty("point", 2, point_properties);
}
Example Generic 'Dumper'
class EpicsPropertyDumper
{
public:
static const char *type2string(EpicsDataCharacter c)
{
const char *strings[] =
{
"Octet",
"Bool",
"Integer",
"Real",
"String",
"Enum",
"Structure",
"TimeStamp"
};
if (c < 0 || c >= sizeof(strings)/sizeof(const char *))
return "<unknown>";
return strings[c];
}
static void dump(EpicsPropertyCatalog *pc)
{ dump(0, pc); }
private:
static void indent(int level)
{ for (int i=0; i<level; ++i) printf(" "); }
static void dump(int level, EpicsPropertyCatalog *pc)
{
double d;
int i;
for (i=0; i<pc->getNumProperties(); ++i)
{
indent(level);
EpicsProperty *p = pc->getProperty(i);
EpicsDataCharacter type = p->getType();
printf("%s (%s, %d %s, %d bits) = ",
p->getName(), type2string(type),
p->getSize(0),
(p->getSize(0) == 1 ? "element" : "elements"),
p->getBitsize());
switch (type)
{
case String:
const char *s;
if (p->getCstring(s))
printf("'%s' (%d chars)\n", s, p->getStringLength());
else
printf("<cannot get string>\n");
break;
case Bool:
case Integer:
case Real:
double d;
if (p->getDouble(d))
printf("%.2f\n", d);
else
printf("<cannot get double>\n");
break;
case Structure:
indent(level);
printf("\n{\n");
dump(level+1, p->getStructure());
indent(level);
printf("}\n");
break;
default:
printf("<unhandled type %d>\n", p->getType());
}
}
}
};
Usage Example
#include <stdio.h>
int main()
{
TestRecord fred;
// Record/device support might have direct access to the fields:
fred.value = 42.3;
fred.units = "a.u.";
fred.point.x = 10;
fred.point.y = 15;
// Other code uses PropertyCatalog
EpicsPropertyCatalog *pc = &fred;
// It can access known properties in chosen order
EpicsProperty *p = pc->findProperty("value");
double d;
if (p && p->getDouble(d))
printf("Value = %.2f\n", d);
else
printf("Cannot get value as double\n");
// Or inspect unknown property catalogs
EpicsPropertyDumper::dump(pc);
return 0;
}
Comparable DataAccess Example?
This is the result of my attempt to implement the TestRecord and main() code based on the August 23 snapshot of DataAccess.
#include <iostream> #include <string> #include "dataAccess.h" #include "daPropertyId.h"
using namespace da;
static const propertyId pi_value("value");
static const propertyId pi_units("units");
static const propertyId pi_point("point");
static const propertyId pi_x("x");
static const propertyId pi_y("y");
class TestRecord : public propertyCatalog
{
public:
virtual ~TestRecord() {};
// Unsure if database code with pointer to the record
// should still directly access the data,
// or if the data should be protected/private.
double value;
string units;
struct
{
double x, y;
} point;
// propertyCatalog
void traverse ( propertyViewer &v) const
{
v.reveal(pi_value, this->value);
// Don't know how to turn a std::string into a stringSegment
// v.reveal(pi_units, this->units); ??
// Don't know how to reveal the 'point'
// without changing the struct point itself
// into a class derived from propertyCatalog
v.reveal(pi_x, this->point.x);
v.reveal(pi_y, this->point.y);
}
// Basically the same, supposedly no longer necessary with latest DA
traverseModifyStatus traverse ( propertyManipulator & m)
{
m.reveal(pi_value, this->value);
// m.reveal(pi_units, this->units); ??
m.reveal(pi_x, this->point.x);
m.reveal(pi_y, this->point.y);
return tmsSuccess;
}
bool find ( const propertyId & id, propertyViewer & v) const
{ // Naive, not using the 'locator' helper, but less code
if (id == pi_value)
{
v.reveal(pi_value, this->value);
return true;
}
if (id == pi_x)
{
v.reveal(pi_value, this->point.x);
return true;
}
if (id == pi_y)
{
v.reveal(pi_value, this->point.y);
return true;
}
return false;
}
};
class Viewer : public propertyViewer
{
public:
void reveal ( const propertyId &id, const double &d, const propertyCatalog &x)
{ // Don't know how to print the id.getName() stringSegment.
cout << "?? = " << d << "\n";
}
void reveal ( const propertyId &, const int &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const unsigned int &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const long &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const unsigned long &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const epicsTime &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const stringSegment &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const enumStateSet & )
{
}
void reveal ( const propertyId &, const arraySegment &, const propertyCatalog &x)
{
}
void reveal ( const propertyId &, const propertyCatalog &x)
{
}
};
int main ()
{
TestRecord fred;
// Record/device support might have direct access to the fields:
fred.value = 42.3;
fred.units = "a.u.";
fred.point.x = 10;
fred.point.y = 15;
// Assume other code that uses PropertyCatalog
// without knowing about TestRecord
propertyCatalog *pc = &fred;
// It can access known properties in chosen order
// Unclear how to get the 'value' as a 'double'.
// This will 'reveal' whatever's there.
// I assume one would have to implement a propertyCatalog
// for target = 'double value' and then assign the given pc
// to that target.
Viewer dumper;
pc->find(pi_value, dumper);
// Or inspect unknown property catalogs
pc->traverse(dumper);
return 0; }
Thoughts
DataAccess
- does not provide type information + property ID handling already optimized + allows segmented strings and arrays - unclear how to interface a std::string or char [] string - cannot interface the example record without turning 'struct point' into its own class, derived from propertyCatalog? * might be more complete but doesn't all compile
This Interface
+ does provide type information - property IDs only strings - requires contiguous C-String + supports C-string + can interface the struct point as is * is incomplete but compiles
