V4 Data Interface

From EPICSWIKI
Revision as of 22:23, 27 July 2005 by KayKasemir (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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
    Real,      // Floating-point
    String,    // UTF-8
    Enum,      // >=0 Integer with state names
    Struct,
    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
    virtual bool isSigned() = 0;        // Integer might be unsigned
};

// Access to the data. Two options:
// 1) Via basic types of the language, contents will differ for Java, C++.
//    (shown here)
// 2) Locked types:
//    C++ will probably define the full set
//      int_8_t, int16_t, ...
//    and Java can only handle a sub-set.
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 getDouble(double &d) = 0;
    virtual bool getArrayDouble(int element, double &d) = 0;
    virtual bool getMatrixDouble(int dim, int element, double &d) = 0;
    virtual bool getInt(int &d) = 0;
    virtual bool getString(char *buf, int buf_len) = 0;
    virtual bool getCstring(const char *&c_ptr) = 0;
    /// many more, at least one for each supported type.
    /// For arrays, it could include per-element access
    /// as well as accessors that copy pieces 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, it simply here to get
// example code that actually compiles & runs.
#include <string>
using std::string;
#include <string.h>

class EpicsPropertyBase : public EpicsProperty
{
public:
    EpicsPropertyBase(const char *name)
    {   this->name = name; }
    // DataReader
    bool getDouble(double &d)
    {
        int i;
        if (getInt(i))
        {
            d = i;
            return true;
        }
        return false;
    }
    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; }
    bool getInt(int &i)
    {
        double d;
        if (getDouble(d))
        {
            i = (int)d;
            return true;
        }
        return false;
    }    
    bool getString(char *buf, int buf_len)
    {
        const char *ptr;
        if (getCstring(ptr))
        {
            strncpy(buf, ptr, buf_len-1);
            return true;
        }
        return false;
    }    
    bool getCstring(const char *&c_ptr)
    {
        return false;
    }    
    // EpicsProperty
       const char *getName()
       {   return name.c_str(); }
private:
       string name;
};

class EpicsDoubleProperty : public EpicsPropertyBase
{
public:
       EpicsDoubleProperty(const char *name, const double *p)
            : EpicsPropertyBase(name)
    {   this->p = p; }
       // EpicsDataDescriptor
       EpicsDataCharacter getType()
       {   return Real; }
    int getNumDims()
    {   return 1; }
    int getSize(int dim)
    {   return dim==1 ? 1 : 0; }
    int getBitsize()
    {   return sizeof(double)*8; }
    bool isSigned()
    {   return true; }
    // EpicsDataReader
    bool getDouble(double &d)
    {   d = *p; }
private:
       const double *p;
};

class EpicsStringProperty : public EpicsPropertyBase
{
public:
       EpicsStringProperty(const char *name, const string *p)
            : EpicsPropertyBase(name)
    {   this->p = p; }
       // EpicsDataDescriptor
       EpicsDataCharacter getType()
       {   return String; }
    int getNumDims()
    {   return 1; }
    int getSize(int dim)
    {   return dim==1 ? p->length() : 0; }
    int getBitsize()
    {   return p->length()*8; }
    bool isSigned()
    {   return false; }
    // EpicsDataReader
    virtual bool getCstring(const char *&c_ptr)
    {
        c_ptr = p->c_str();
        return true;
    }
private:
    const string *p;
};

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
class AiRecord : public EpicsPropertyCatalog
{
public:
    AiRecord();
    // 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;
    // EpicsPropertyCatalog
    int getNumProperties();
    EpicsProperty *findProperty(const char *name);    
    EpicsProperty *getProperty(int index);
private:
    EpicsProperty *property[2];
};

AiRecord::AiRecord()
{
    property[0] = new EpicsDoubleProperty("value", &this->value);
    property[1] = new EpicsStringProperty("units", &this->units);
}

int AiRecord::getNumProperties()
{   return sizeof(property)/sizeof(EpicsProperty *); }

EpicsProperty *AiRecord::findProperty(const char *name)
{   // could support hashed numeric IDs instead of strings
    int i;
    for (i=0; i<getNumProperties(); ++i)
        if (!strcmp(property[i]->getName(), name))
            return property[i];
    return 0;
}

EpicsProperty *AiRecord::getProperty(int index)
{
    return (index >=0  &&  index <sizeof(property)/sizeof(EpicsProperty *)) ?
        property[index] : 0;
}

Usage Example

#include <stdio.h>

int main()
{
    AiRecord fred;
    // Record/device support might have direct access to the fields:
    fred.value = 42.3;
    fred.units = "a.u.";

    // Other code uses PropertyCatalog
    EpicsPropertyCatalog *pc = &fred;

    // It can access known properties in chosen order
    EpicsProperty *vp = pc->findProperty("value");
    double d;
    if (vp && vp->getDouble(d))
        printf("Value = %.2f\n", d);
    else
        printf("Cannot get value as double\n");

    // Or inspect unknown property catalogs
    int i;
    for (i=0; i<pc->getNumProperties(); ++i)
    {
        printf("%-10s = ", pc->getProperty(i)->getName());
        switch (pc->getProperty(i)->getType())
        {
            case String:
                const char *s;
                if (pc->getProperty(i)->getCstring(s))
                    printf("'%s'\n", s);
                else
                    printf("<cannot get string>\n");
                break;
            case Bool:
            case Integer:
            case Real:
                double d;
                if (pc->getProperty(i)->getDouble(d))
                    printf("%.2f\n", d);
                else
                    printf("<cannot get double>\n");
                break;
            default:
                printf("<unhandled type>\n");
        }
    }
       return 0;
}