V4 Data Interface

From EPICSWIKI

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?

V4DataInter.jpg

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