V4 Data Interface

From EPICSWIKI
Revision as of 20:21, 4 August 2005 by KayKasemir (talk | contribs) (added structure handling to example)

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
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 an underlying Real, 64bit, scalar,
// one can ask for a real64 but also int, string, ...
//
// 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).
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; }
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;
}

The output of this test:

Value = 42.30
value (Real, 1 element, 64 bits) = 42.30
units (String, 1 element, 0 bits) = 'a.u.' (4 chars)
point (Structure, 1 element, 0 bits) = 
{
   x (Real, 1 element, 64 bits) = 10.00
   y (Real, 1 element, 64 bits) = 15.00
}