From af3c8f1ae410052735aea0114fe1d4c2ee3afb8a Mon Sep 17 00:00:00 2001 From: smathis Date: Wed, 29 Apr 2026 09:04:06 +0200 Subject: [PATCH] Removal of code duplication - Collapsed putRaw and getRaw into single, templated methods - Simplified readout of status and severity in callback. --- bus/m_epics_ca.h | 22 +-- bus/m_epics_ca.tpp | 333 +++++++++++---------------------------------- 2 files changed, 84 insertions(+), 271 deletions(-) diff --git a/bus/m_epics_ca.h b/bus/m_epics_ca.h index 1b604ec..ac64ad1 100644 --- a/bus/m_epics_ca.h +++ b/bus/m_epics_ca.h @@ -153,15 +153,15 @@ template class mEpicsCa { double _timeout; /** - * @brief When using the subscription mechanism, this method returns the - * cached value, if there is any. + * @brief When using the subscription mechanism, this method returns a copy + * of the cached value, if there is any. * * If the subscription mechanism is not used or the driver is not connected * to an EPICS channel, this returns `std::nullopt`. * - * @return const std::optional& + * @return const std::optional */ - const std::optional &cached() const; + const std::optional cached() const; /** * @brief Returns the last read value of the `STAT` field belonging to the @@ -208,22 +208,12 @@ template class mEpicsCa { // The following methods hold the actual implementations of `mEpicsCa::get` // for different types of `T`. int getRaw(char *buf, size_t len); - int getRaw(int *value); - int getRaw(short *value); - int getRaw(long *value); - int getRaw(float *value); - int getRaw(double *value); - int getRaw(uint16_t *value); + template int getRaw(V *value); // The following methods hold the actual implementations of `mEpicsCa::put` // for different types of `T`. int putRaw(const char *buf, size_t len); - int putRaw(int *value); - int putRaw(short *value); - int putRaw(long *value); - int putRaw(float *value); - int putRaw(double *value); - int putRaw(uint16_t *value); + template int putRaw(V *value); /** * @brief Adjusts the status argument according to the values of the diff --git a/bus/m_epics_ca.tpp b/bus/m_epics_ca.tpp index c8e6529..1e9ae13 100644 --- a/bus/m_epics_ca.tpp +++ b/bus/m_epics_ca.tpp @@ -11,59 +11,88 @@ /** * @brief Struct for type checking the `T` of `mEpicsCa` * - * This struct is instantiated in the `dbfFromType` method and makes sure that - * `T` (and `V` in case of `mEpicsCa::get` / `mEpicsCa::put`) can only be an - * EPICS-compatible type. The `type` field maps `T` to the corresponding EPICS - * type. + * This struct is instantiated in the `dbfFromType` method and makes + * sure that `T` (and `V` in case of `mEpicsCa::get` / `mEpicsCa::put`) can only + * be an EPICS-compatible type. The `type` field maps `T` to the corresponding + * EPICS type. * * @tparam T */ -template struct dbf_type; +template struct channelType; -template <> struct dbf_type { +template <> struct channelType { // On modern systems, int is long - static constexpr int type = DBF_LONG; + static constexpr int dbf = DBF_LONG; + static constexpr int dbr = DBR_LONG; + static constexpr int dbr_time = DBR_TIME_LONG; + using dbr_time_t = dbr_time_long; }; -template <> struct dbf_type { - static constexpr int type = DBF_SHORT; +template <> struct channelType { + static constexpr int dbf = DBF_SHORT; + static constexpr int dbr = DBR_SHORT; + static constexpr int dbr_time = DBR_TIME_SHORT; + using dbr_time_t = dbr_time_short; }; -template <> struct dbf_type { - static constexpr int type = DBF_LONG; +template <> struct channelType { + static constexpr int dbf = DBF_LONG; + static constexpr int dbr = DBR_LONG; + static constexpr int dbr_time = DBR_TIME_LONG; + using dbr_time_t = dbr_time_long; }; -template <> struct dbf_type { - static constexpr int type = DBF_FLOAT; +template <> struct channelType { + static constexpr int dbf = DBF_FLOAT; + static constexpr int dbr = DBR_FLOAT; + static constexpr int dbr_time = DBR_TIME_FLOAT; + using dbr_time_t = dbr_time_float; }; -template <> struct dbf_type { - static constexpr int type = DBF_DOUBLE; +template <> struct channelType { + static constexpr int dbf = DBF_DOUBLE; + static constexpr int dbr = DBR_DOUBLE; + static constexpr int dbr_time = DBR_TIME_DOUBLE; + using dbr_time_t = dbr_time_double; }; -template <> struct dbf_type { - static constexpr int type = DBF_STRING; +template <> struct channelType { + static constexpr int dbf = DBF_STRING; + static constexpr int dbr = DBR_STRING; + static constexpr int dbr_time = DBR_TIME_STRING; + using dbr_time_t = dbr_time_string; }; -template <> struct dbf_type { - static constexpr int type = DBF_STRING; +template <> struct channelType { + static constexpr int dbf = DBF_STRING; + static constexpr int dbr = DBR_STRING; + static constexpr int dbr_time = DBR_TIME_STRING; + using dbr_time_t = dbr_time_string; }; -template <> struct dbf_type { - static constexpr int type = DBF_STRING; +template <> struct channelType { + static constexpr int dbf = DBF_STRING; + static constexpr int dbr = DBR_STRING; + static constexpr int dbr_time = DBR_TIME_STRING; + using dbr_time_t = dbr_time_string; }; -template <> struct dbf_type { - static constexpr int type = DBF_ENUM; +template <> struct channelType { + static constexpr int dbf = DBF_ENUM; + static constexpr int dbr = DBR_ENUM; + static constexpr int dbr_time = DBR_TIME_ENUM; + using dbr_time_t = dbr_time_enum; }; /** - * @brief Returns the `type` field of `dbf_type` + * @brief Returns the `dbf` field of `channelType` * * @tparam T * @return constexpr int */ -template constexpr int dbfFromType() { return dbf_type::type; } +template constexpr int dbfFromType() { + return channelType::dbf; +} template constexpr void assertEqual() { static_assert(dbfFromType() == dbfFromType()); @@ -195,51 +224,18 @@ void mEpicsCa::eventCallback(struct event_handler_args args) { if (args.status != ECA_NORMAL) return; - // Read out status and severity - switch (args.type) { - case DBR_TIME_STRING: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } - // DBR_TIME_INT == DBR_TIME_SHORT - case DBR_TIME_SHORT: { + /* + Independently of the value type (dbr_time_string, dbr_time_short, + dbr_time_float, ...), status and severity are always the first two fields + of the structs. This allows us to interpret args.dbr as any one of these + types (because the memory layout at the start of the struct is always the + same). However, we must not read any other fields of v! Therefore, the + lifetime of v is aggressively scoped. + */ + { auto *v = static_cast(args.dbr); self->_status = static_cast(v->status); self->_severity = static_cast(v->severity); - break; - } - case DBR_TIME_FLOAT: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } - case DBR_TIME_ENUM: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } - case DBR_TIME_CHAR: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } - case DBR_TIME_LONG: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } - case DBR_TIME_DOUBLE: { - auto *v = static_cast(args.dbr); - self->_status = static_cast(v->status); - self->_severity = static_cast(v->severity); - break; - } } // Read out the actual value and cache it. @@ -267,7 +263,7 @@ void mEpicsCa::eventCallback(struct event_handler_args args) { } } -template const std::optional &mEpicsCa::cached() const { +template const std::optional mEpicsCa::cached() const { return _cached; } @@ -294,7 +290,8 @@ template template int mEpicsCa::get(V *value) { (t == DBF_STRING && v == DBF_ENUM), "Incompatible EPICS types"); } else { - // Assert T and V are compatible (dbfFromType == dbfFromType) + // Assert T and V are compatible (dbfFromType == + // dbfFromType) assertEqual(); } @@ -367,144 +364,29 @@ template int mEpicsCa::get(char *buf, u_long len) { return getRaw(buf, len); } -template int mEpicsCa::getRaw(int *value) { - dbr_time_short dbr = {}; +template template int mEpicsCa::getRaw(V *value) { + using traits = channelType; + using dbr_t = typename traits::dbr_time_t; - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_INT, this->_pChanID, &dbr), this->_chanName.c_str()); + dbr_t dbr{}; + + int status = + convertAndHandleEcaCode(ca_get(traits::dbr_time, this->_pChanID, &dbr), + this->_chanName.c_str()); if (status != CM_SUCCESS) return status; - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL status = convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); + if (status != CM_SUCCESS) return status; - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). *value = dbr.value; _status = static_cast(dbr.status); _severity = static_cast(dbr.severity); - return processStatAndSevr(status); -} -template int mEpicsCa::getRaw(short *value) { - dbr_time_short dbr = {}; - - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_SHORT, this->_pChanID, &dbr), this->_chanName.c_str()); - - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - status = - convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). - *value = dbr.value; - _status = static_cast(dbr.status); - _severity = static_cast(dbr.severity); - return processStatAndSevr(status); -} - -template int mEpicsCa::getRaw(long *value) { - dbr_time_long dbr = {}; - - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_LONG, this->_pChanID, &dbr), this->_chanName.c_str()); - - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - status = - convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). - *value = dbr.value; - _status = static_cast(dbr.status); - _severity = static_cast(dbr.severity); - return processStatAndSevr(status); -} - -template int mEpicsCa::getRaw(float *value) { - dbr_time_float dbr = {}; - - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_FLOAT, this->_pChanID, &dbr), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - status = - convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). - *value = dbr.value; - _status = static_cast(dbr.status); - _severity = static_cast(dbr.severity); - return processStatAndSevr(status); -} - -template int mEpicsCa::getRaw(double *value) { - dbr_time_double dbr = {}; - - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_DOUBLE, this->_pChanID, &dbr), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - status = - convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). - *value = dbr.value; - _status = static_cast(dbr.status); - _severity = static_cast(dbr.severity); - return processStatAndSevr(status); -} - -template int mEpicsCa::getRaw(uint16_t *value) { - dbr_time_enum dbr = {}; - - int status = convertAndHandleEcaCode( - ca_get(DBR_TIME_ENUM, this->_pChanID, &dbr), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - status = - convertAndHandleEcaCode(ca_pend_io(_timeout), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // Interpret answer AFTER caget actually populated dbr (before that, it is - // uninitialized). - *value = dbr.value; - _status = static_cast(dbr.status); - _severity = static_cast(dbr.severity); return processStatAndSevr(status); } @@ -624,69 +506,10 @@ template int mEpicsCa::put(const char *buf, u_long len) { return putRaw(buf, len); } -template int mEpicsCa::putRaw(int *value) { - int status = convertAndHandleEcaCode(ca_put(DBR_INT, this->_pChanID, value), - this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - return convertAndHandleEcaCode(ca_pend_io(_timeout), - this->_chanName.c_str()); -} - -template int mEpicsCa::putRaw(short *value) { +template template int mEpicsCa::putRaw(V *value) { int status = convertAndHandleEcaCode( - ca_put(DBR_SHORT, this->_pChanID, value), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - return convertAndHandleEcaCode(ca_pend_io(_timeout), - this->_chanName.c_str()); -} - -template int mEpicsCa::putRaw(long *value) { - int status = convertAndHandleEcaCode( - ca_put(DBR_LONG, this->_pChanID, value), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - return convertAndHandleEcaCode(ca_pend_io(_timeout), - this->_chanName.c_str()); -} - -template int mEpicsCa::putRaw(float *value) { - int status = convertAndHandleEcaCode( - ca_put(DBR_FLOAT, this->_pChanID, value), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - return convertAndHandleEcaCode(ca_pend_io(_timeout), - this->_chanName.c_str()); -} - -template int mEpicsCa::putRaw(double *value) { - int status = convertAndHandleEcaCode( - ca_put(DBR_DOUBLE, this->_pChanID, value), this->_chanName.c_str()); - if (status != CM_SUCCESS) - return status; - - // caget is asynchronous, so the pointer value is only updated when - // ca_pend_io returns ECA_NORMAL - return convertAndHandleEcaCode(ca_pend_io(_timeout), - this->_chanName.c_str()); -} - -template int mEpicsCa::putRaw(u_int16_t *value) { - int status = convertAndHandleEcaCode( - ca_put(DBR_ENUM, this->_pChanID, value), this->_chanName.c_str()); + ca_put(channelType::dbr, this->_pChanID, value), + this->_chanName.c_str()); if (status != CM_SUCCESS) return status;