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>("/"); | 
