aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2015-08-28 18:52:42 -0700
committerAshish Chaudhari <ashish@ettus.com>2016-02-11 14:36:20 -0800
commit834acb8b6ccaaab1701d0c48ead443b0fb17b8cf (patch)
tree07a51df4c2a1e6b6f08ab35277acdce920fb9807
parent27a08ccddc94c4945d48445b14c23fe6d186f9ef (diff)
downloaduhd-834acb8b6ccaaab1701d0c48ead443b0fb17b8cf.tar.gz
uhd-834acb8b6ccaaab1701d0c48ead443b0fb17b8cf.tar.bz2
uhd-834acb8b6ccaaab1701d0c48ead443b0fb17b8cf.zip
prop_tree: Added advanced coercion capability to property
- Added auto and manual coerce modes - Added set_coerced API for manual coercion - Added detailed doxy comments describing behavior of property class
-rw-r--r--host/include/uhd/property_tree.hpp106
-rw-r--r--host/include/uhd/property_tree.ipp50
-rw-r--r--host/lib/property_tree.cpp2
-rw-r--r--host/tests/property_test.cpp53
4 files changed, 182 insertions, 29 deletions
diff --git a/host/include/uhd/property_tree.hpp b/host/include/uhd/property_tree.hpp
index ec65c73a0..93353568a 100644
--- a/host/include/uhd/property_tree.hpp
+++ b/host/include/uhd/property_tree.hpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011,2014 Ettus Research LLC
+// Copyright 2011,2014-2016 Ettus Research
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -27,8 +27,51 @@
namespace uhd{
/*!
- * A templated property interface for holding a value
+ * A templated property interface for holding the state
+ * associated with a property in a uhd::property_tree
* and registering callbacks when that value changes.
+ *
+ * A property is defined to have two separate vales:
+ * - Desired value: Value requested by the user
+ * - Coerced value: Value that was actually possible
+ * given HW and other requirements
+ *
+ * By default, the desired and coerced values are
+ * identical as long as the property is not coerced.
+ * A property can be coerced in two way:
+ * 1. Using a coercer: A callback function that takes
+ * in a desired value and produces a coerced value.
+ * A property must have *exactly one* coercer.
+ * 2. Manual coercion: Manually calling the set_coerced
+ * API fnction to coerce the value of the propery. In
+ * order to use manual coercion, the propery must be
+ * created with the MANUAL_COERCE mode.
+ * If the coerce mode for a property is AUTO_COERCE then
+ * it always has a coercer. If the set_coercer API is
+ * never used, then the default coercer is used which
+ * simply set the coerced value to the desired value.
+ *
+ * It is possible to get notified every time the desired
+ * or coerced values of a property potentially change
+ * using subscriber callbacks. Every property can have
+ * zero or more desired and coerced subscribers.
+ *
+ * If storing the property readback state in software is
+ * not appropriate (for example if it needs to be queried
+ * from hardware) then it is possible to use a publisher
+ * callback to get the value of the property. Calling
+ * get on the property will always call the publisher and
+ * the cached desired and coerced values are updated only
+ * using set* calls. A preprty must have *at most one*
+ * publisher. It is legal to have both a coercer
+ * and publisher for a property but the only way to access
+ * the desired and coerced values in that case would be by
+ * notification using the desired and coerced subscribers.
+ * Publishers are useful for creating read-only properties.
+ *
+ * Requirements for the template type T:
+ * - T must have a copy constructor
+ * - T must have an assignment operator
*/
template <typename T> class property : boost::noncopyable{
public:
@@ -40,26 +83,33 @@ public:
/*!
* Register a coercer into the property.
- * A coercer is a special subscriber that coerces the value.
+ * A coercer is a callback function that updates the
+ * coerced value of a property.
+ *
* Only one coercer may be registered per property.
* \param coercer the coercer callback function
* \return a reference to this property for chaining
+ * \throws uhd::assertion_error if called more than once
*/
virtual property<T> &set_coercer(const coercer_type &coercer) = 0;
/*!
* Register a publisher into the property.
- * A publisher is a special callback the provides the value.
- * Publishers are useful for creating read-only properties.
+ * A publisher is a callback function the provides the value
+ * for a property.
+ *
* Only one publisher may be registered per property.
* \param publisher the publisher callback function
* \return a reference to this property for chaining
+ * \throws uhd::assertion_error if called more than once
*/
virtual property<T> &set_publisher(const publisher_type &publisher) = 0;
/*!
* Register a subscriber into the property.
- * All desired subscribers are called when the value changes.
+ * All desired subscribers are called when the desired value
+ * potentially changes.
+ *
* Once a subscriber is registered, it cannot be unregistered.
* \param subscriber the subscriber callback function
* \return a reference to this property for chaining
@@ -68,7 +118,9 @@ public:
/*!
* Register a subscriber into the property.
- * All coerced subscribers are called when the value changes.
+ * All coerced subscribers are called when the coerced value
+ * potentially changes.
+ *
* Once a subscriber is registered, it cannot be unregistered.
* \param subscriber the subscriber callback function
* \return a reference to this property for chaining
@@ -77,37 +129,61 @@ public:
/*!
* Update calls all subscribers w/ the current value.
+ *
* \return a reference to this property for chaining
+ * \throws uhd::assertion_error
*/
virtual property<T> &update(void) = 0;
/*!
- * Set the new value and call all subscribers.
- * The coercer (when provided) is called initially,
- * and the coerced value is used to set the subscribers.
+ * Set the new value and call all the necessary subscribers.
+ * Order of operations:
+ * - The desired value of the property is updated
+ * - All desired subscribers are called
+ * - If coerce mode is AUTO then the coercer is called
+ * - If coerce mode is AUTO then all coerced subscribers are called
+ *
* \param value the new value to set on this property
* \return a reference to this property for chaining
+ * \throws uhd::assertion_error
*/
virtual property<T> &set(const T &value) = 0;
/*!
+ * Set a coerced value and call all subscribers.
+ * The coercer is bypassed, and the specified value is
+ * used as the coerced value. All coerced subscribers
+ * are called. This function can only be used when the
+ * coerce mode is set to MANUAL_COERCE.
+ *
+ * \param value the new value to set on this property
+ * \return a reference to this property for chaining
+ * \throws uhd::assertion_error
+ */
+ virtual property<T> &set_coerced(const T &value) = 0;
+
+ /*!
* Get the current value of this property.
* The publisher (when provided) yields the value,
- * otherwise an internal shadow is used for the value.
+ * otherwise an internal coerced value is returned.
+ *
* \return the current value in the property
+ * \throws uhd::assertion_error
*/
virtual const T get(void) const = 0;
/*!
* Get the current desired value of this property.
- * A desired value does not defined if a property has a publisher.
+ *
* \return the current desired value in the property
+ * \throws uhd::assertion_error
*/
virtual const T get_desired(void) const = 0;
/*!
* A property is empty if it has never been set.
* A property with a publisher is never empty.
+ *
* \return true if the property is empty
*/
virtual bool empty(void) const = 0;
@@ -143,6 +219,8 @@ class UHD_API property_tree : boost::noncopyable{
public:
typedef boost::shared_ptr<property_tree> sptr;
+ enum coerce_mode_t { AUTO_COERCE, MANUAL_COERCE };
+
virtual ~property_tree(void) = 0;
//! Create a new + empty property tree
@@ -161,7 +239,9 @@ public:
virtual std::vector<std::string> list(const fs_path &path) const = 0;
//! Create a new property entry in the tree
- template <typename T> property<T> &create(const fs_path &path);
+ template <typename T> property<T> &create(
+ const fs_path &path,
+ coerce_mode_t coerce_mode = AUTO_COERCE);
//! Get access to a property in the tree
template <typename T> property<T> &access(const fs_path &path);
diff --git a/host/include/uhd/property_tree.ipp b/host/include/uhd/property_tree.ipp
index 171437450..54c81870c 100644
--- a/host/include/uhd/property_tree.ipp
+++ b/host/include/uhd/property_tree.ipp
@@ -1,5 +1,5 @@
//
-// Copyright 2011,2014 Ettus Research LLC
+// Copyright 2011,2014-2016 Ettus Research
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -30,6 +30,11 @@ namespace uhd{ namespace /*anon*/{
template <typename T> class property_impl : public property<T>{
public:
+ property_impl<T>(property_tree::coerce_mode_t mode) : _coerce_mode(mode){
+ if (_coerce_mode == property_tree::AUTO_COERCE) {
+ _coercer = DEFAULT_COERCER;
+ }
+ }
~property_impl<T>(void){
/* NOP */
@@ -37,7 +42,7 @@ public:
property<T> &set_coercer(const typename property<T>::coercer_type &coercer){
if (not _coercer.empty()) uhd::assertion_error("cannot register more than one coercer for a property");
- if (not _publisher.empty()) uhd::assertion_error("cannot register a coercer and publisher for the same property");
+ if (_coerce_mode == property_tree::MANUAL_COERCE) uhd::assertion_error("cannot register coercer for a manually coerced property");
_coercer = coercer;
return *this;
@@ -45,7 +50,6 @@ public:
property<T> &set_publisher(const typename property<T>::publisher_type &publisher){
if (not _publisher.empty()) uhd::assertion_error("cannot register more than one publisher for a property");
- if (not _coercer.empty()) uhd::assertion_error("cannot register a coercer and publisher for the same property");
_publisher = publisher;
return *this;
@@ -66,32 +70,45 @@ public:
return *this;
}
+ void _set_coerced(const T &value){
+ init_or_set_value(_coerced_value, value);
+ BOOST_FOREACH(typename property<T>::subscriber_type &csub, _coerced_subscribers){
+ csub(get_value_ref(_coerced_value)); //let errors propagate
+ }
+ }
+
property<T> &set(const T &value){
init_or_set_value(_value, value);
BOOST_FOREACH(typename property<T>::subscriber_type &dsub, _desired_subscribers){
dsub(get_value_ref(_value)); //let errors propagate
}
if (not _coercer.empty()) {
- init_or_set_value(_coerced_value, _coercer(get_value_ref(_value)));
- }
- BOOST_FOREACH(typename property<T>::subscriber_type &csub, _coerced_subscribers){
- csub(get_value_ref(_coercer.empty() ? _value : _coerced_value)); //let errors propagate
+ _set_coerced(_coercer(get_value_ref(_value)));
+ } else {
+ if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("coercer missing for an auto coerced property");
}
return *this;
}
+ property<T> &set_coerced(const T &value){
+ if (_coerce_mode == property_tree::AUTO_COERCE) uhd::assertion_error("cannot set coerced value an auto coerced property");
+ _set_coerced(value);
+ return *this;
+ }
+
const T get(void) const{
- if (empty()) throw uhd::runtime_error("Cannot get() on an empty property");
+ if (empty()) throw uhd::runtime_error("Cannot get() on an uninitialized (empty) property");
if (not _publisher.empty()) {
return _publisher();
} else {
- return get_value_ref(_coercer.empty() ? _value : _coerced_value);
+ if (_coerced_value.get() == NULL and _coerce_mode == property_tree::MANUAL_COERCE)
+ throw uhd::runtime_error("uninitialized coerced value for manually coerced attribute");
+ return get_value_ref(_coerced_value);
}
}
const T get_desired(void) const{
- if (_value.get() == NULL) throw uhd::runtime_error("Cannot get_desired() on an empty property");
- if (not _publisher.empty()) throw uhd::runtime_error("Cannot get_desired() on a property with a publisher");
+ if (_value.get() == NULL) throw uhd::runtime_error("Cannot get_desired() on an uninitialized (empty) property");
return get_value_ref(_value);
}
@@ -101,6 +118,10 @@ public:
}
private:
+ static T DEFAULT_COERCER(const T& value) {
+ return value;
+ }
+
static void init_or_set_value(boost::scoped_ptr<T>& scoped_value, const T& init_val) {
if (scoped_value.get() == NULL) {
scoped_value.reset(new T(init_val));
@@ -111,9 +132,10 @@ private:
static const T& get_value_ref(const boost::scoped_ptr<T>& scoped_value) {
if (scoped_value.get() == NULL) throw uhd::assertion_error("Cannot use uninitialized property data");
- return *static_cast<const T*>(scoped_value.get());
+ return *scoped_value.get();
}
+ const property_tree::coerce_mode_t _coerce_mode;
std::vector<typename property<T>::subscriber_type> _desired_subscribers;
std::vector<typename property<T>::subscriber_type> _coerced_subscribers;
typename property<T>::publisher_type _publisher;
@@ -129,8 +151,8 @@ private:
**********************************************************************/
namespace uhd{
- template <typename T> property<T> &property_tree::create(const fs_path &path){
- this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>()));
+ template <typename T> property<T> &property_tree::create(const fs_path &path, coerce_mode_t coerce_mode){
+ this->_create(path, typename boost::shared_ptr<property<T> >(new property_impl<T>(coerce_mode)));
return this->access<T>(path);
}
diff --git a/host/lib/property_tree.cpp b/host/lib/property_tree.cpp
index 039f05f12..76d7bccba 100644
--- a/host/lib/property_tree.cpp
+++ b/host/lib/property_tree.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2011,2014 Ettus Research LLC
+// Copyright 2011,2014-2016 Ettus Research
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
diff --git a/host/tests/property_test.cpp b/host/tests/property_test.cpp
index b7aa29e4b..61d1de9a6 100644
--- a/host/tests/property_test.cpp
+++ b/host/tests/property_test.cpp
@@ -28,18 +28,26 @@ struct coercer_type{
};
struct setter_type{
+ setter_type() : _count(0), _x(0) {}
+
void doit(int x){
+ _count++;
_x = x;
}
+ int _count;
int _x;
};
struct getter_type{
+ getter_type() : _count(0), _x(0) {}
+
int doit(void){
+ _count++;
return _x;
}
+ int _count;
int _x;
};
@@ -57,7 +65,25 @@ BOOST_AUTO_TEST_CASE(test_prop_simple){
BOOST_CHECK_EQUAL(prop.get(), 34);
}
-BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){
+BOOST_AUTO_TEST_CASE(test_prop_with_desired_subscriber){
+ uhd::property_tree::sptr tree = uhd::property_tree::make();
+ uhd::property<int> &prop = tree->create<int>("/");
+
+ setter_type setter;
+ prop.add_desired_subscriber(boost::bind(&setter_type::doit, &setter, _1));
+
+ prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(prop.get(), 42);
+ BOOST_CHECK_EQUAL(setter._x, 42);
+
+ prop.set(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 34);
+ BOOST_CHECK_EQUAL(prop.get(), 34);
+ BOOST_CHECK_EQUAL(setter._x, 34);
+}
+
+BOOST_AUTO_TEST_CASE(test_prop_with_coerced_subscriber){
uhd::property_tree::sptr tree = uhd::property_tree::make();
uhd::property<int> &prop = tree->create<int>("/");
@@ -65,14 +91,39 @@ BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){
prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1));
prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
BOOST_CHECK_EQUAL(prop.get(), 42);
BOOST_CHECK_EQUAL(setter._x, 42);
prop.set(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 34);
BOOST_CHECK_EQUAL(prop.get(), 34);
BOOST_CHECK_EQUAL(setter._x, 34);
}
+BOOST_AUTO_TEST_CASE(test_prop_manual_coercion){
+ uhd::property_tree::sptr tree = uhd::property_tree::make();
+ uhd::property<int> &prop = tree->create<int>("/", uhd::property_tree::MANUAL_COERCE);
+
+ setter_type dsetter, csetter;
+ prop.add_desired_subscriber(boost::bind(&setter_type::doit, &dsetter, _1));
+ prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &csetter, _1));
+
+ BOOST_CHECK_EQUAL(dsetter._x, 0);
+ BOOST_CHECK_EQUAL(csetter._x, 0);
+
+ prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(dsetter._x, 42);
+ BOOST_CHECK_EQUAL(csetter._x, 0);
+
+ prop.set_coerced(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(prop.get(), 34);
+ BOOST_CHECK_EQUAL(dsetter._x, 42);
+ BOOST_CHECK_EQUAL(csetter._x, 34);
+}
+
BOOST_AUTO_TEST_CASE(test_prop_with_publisher){
uhd::property_tree::sptr tree = uhd::property_tree::make();
uhd::property<int> &prop = tree->create<int>("/");