diff options
author | Ashish Chaudhari <ashish@ettus.com> | 2015-08-28 18:52:42 -0700 |
---|---|---|
committer | Ashish Chaudhari <ashish@ettus.com> | 2016-02-11 14:36:20 -0800 |
commit | 834acb8b6ccaaab1701d0c48ead443b0fb17b8cf (patch) | |
tree | 07a51df4c2a1e6b6f08ab35277acdce920fb9807 | |
parent | 27a08ccddc94c4945d48445b14c23fe6d186f9ef (diff) | |
download | uhd-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.hpp | 106 | ||||
-rw-r--r-- | host/include/uhd/property_tree.ipp | 50 | ||||
-rw-r--r-- | host/lib/property_tree.cpp | 2 | ||||
-rw-r--r-- | host/tests/property_test.cpp | 53 |
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>("/"); |