// // Copyright 2016 Ettus Research // // SPDX-License-Identifier: GPL-3.0 // #ifndef INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP #define INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP #include #include #include #include #include #include #include #include #include #include #include namespace uhd { namespace experts { enum node_class_t { CLASS_WORKER, CLASS_DATA, CLASS_PROPERTY }; enum node_access_t { ACCESS_READER, ACCESS_WRITER }; enum node_author_t { AUTHOR_NONE, AUTHOR_USER, AUTHOR_EXPERT }; /*!--------------------------------------------------------- * class dag_vertex_t * * This serves as the base class for all nodes in the expert * graph. Data nodes and workers are derived from this class. * --------------------------------------------------------- */ class dag_vertex_t : private boost::noncopyable { public: typedef boost::function callback_func_t; virtual ~dag_vertex_t() {} // Getters for basic info about the node inline node_class_t get_class() const { return _class; } inline const std::string& get_name() const { return _name; } virtual const std::string& get_dtype() const = 0; virtual std::string to_string() const = 0; // Graph resolution specific virtual bool is_dirty() const = 0; virtual void mark_clean() = 0; virtual void resolve() = 0; // External callbacks virtual void set_write_callback(const callback_func_t& func) = 0; virtual bool has_write_callback() const = 0; virtual void clear_write_callback() = 0; virtual void set_read_callback(const callback_func_t& func) = 0; virtual bool has_read_callback() const = 0; virtual void clear_read_callback() = 0; protected: dag_vertex_t(const node_class_t c, const std::string& n): _class(c), _name(n) {} private: const node_class_t _class; const std::string _name; }; class data_node_printer { public: //Generic implementation template static std::string print(const data_t& val) { std::ostringstream os; os << val; return os.str(); } static std::string print(const uint8_t& val) { std::ostringstream os; os << int(val); return os.str(); } static std::string print(const time_spec_t time) { std::ostringstream os; os << time.get_real_secs(); return os.str(); } }; /*!--------------------------------------------------------- * class data_node_t * * The data node class hold a passive piece of data in the * expert graph. A data node is clean if its underlying data * is clean. Access to the underlying data is provided using * two methods: * 1. Special accessor classes (for R/W enforcement) * 2. External clients (via commit and retrieve). This access * is protected by the callback mutex. * * Requirements for data_t * - Must have a default constructor * - Must have a copy constructor * - Must have an assignment operator (=) * - Must have an equality operator (==) * --------------------------------------------------------- */ template class data_node_t : public dag_vertex_t { public: // A data_node_t instance can have a type of CLASS_DATA or CLASS_PROPERTY // In general a data node is a property if it can be accessed and modified // from the outside world (of experts) using read and write callbacks. We // assume that if a callback mutex is passed into the data node that it will // be accessed from the outside and tag the data node as a PROPERTY. data_node_t(const std::string& name, boost::recursive_mutex* mutex = NULL) : dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(), _author(AUTHOR_NONE) {} data_node_t(const std::string& name, const data_t& value, boost::recursive_mutex* mutex = NULL) : dag_vertex_t(mutex?CLASS_PROPERTY:CLASS_DATA, name), _callback_mutex(mutex), _data(value), _author(AUTHOR_NONE) {} // Basic info virtual const std::string& get_dtype() const { static const std::string dtype( boost::units::detail::demangle(typeid(data_t).name())); return dtype; } virtual std::string to_string() const { return data_node_printer::print(get()); } inline node_author_t get_author() const { return _author; } // Graph resolution specific virtual bool is_dirty() const { return _data.is_dirty(); } virtual void mark_clean() { _data.mark_clean(); } void resolve() { //NOP } // Data node specific setters and getters (for the framework) void set(const data_t& value) { _data = value; _author = AUTHOR_EXPERT; } const data_t& get() const { return _data; } // Data node specific setters and getters (for external entities) void commit(const data_t& value) { if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); boost::lock_guard lock(*_callback_mutex); set(value); _author = AUTHOR_USER; if (is_dirty() and has_write_callback()) { _wr_callback(std::string(get_name())); //Put the name on the stack before calling } } const data_t retrieve() const { if (_callback_mutex == NULL) throw uhd::assertion_error("node " + get_name() + " is missing the callback mutex"); boost::lock_guard lock(*_callback_mutex); if (has_read_callback()) { _rd_callback(std::string(get_name())); } return get(); } private: // External callbacks virtual void set_write_callback(const callback_func_t& func) { _wr_callback = func; } virtual bool has_write_callback() const { return not _wr_callback.empty(); } virtual void clear_write_callback() { _wr_callback.clear(); } virtual void set_read_callback(const callback_func_t& func) { _rd_callback = func; } virtual bool has_read_callback() const { return not _rd_callback.empty(); } virtual void clear_read_callback() { _rd_callback.clear(); } boost::recursive_mutex* _callback_mutex; callback_func_t _rd_callback; callback_func_t _wr_callback; dirty_tracked _data; node_author_t _author; }; /*!--------------------------------------------------------- * class node_retriever_t * * Node storage is managed by a framework class so we need * and interface to find and retrieve data nodes to associate * with accessors. * --------------------------------------------------------- */ class node_retriever_t { public: virtual ~node_retriever_t() {} virtual const dag_vertex_t& lookup(const std::string& name) const = 0; private: friend class data_accessor_t; virtual dag_vertex_t& retrieve(const std::string& name) const = 0; }; /*!--------------------------------------------------------- * class data_accessor_t * * Accessors provide protected access to data nodes and help * establish dependency relationships. * --------------------------------------------------------- */ class data_accessor_t { public: virtual ~data_accessor_t() {} virtual bool is_reader() const = 0; virtual bool is_writer() const = 0; virtual dag_vertex_t& node() const = 0; protected: data_accessor_t(const node_retriever_t& r, const std::string& n): _vertex(r.retrieve(n)) {} dag_vertex_t& _vertex; }; template class data_accessor_base : public data_accessor_t { public: virtual ~data_accessor_base() {} virtual bool is_reader() const { return _access == ACCESS_READER; } virtual bool is_writer() const { return _access == ACCESS_WRITER; } inline bool is_dirty() const { return _datanode->is_dirty(); } inline node_class_t get_class() const { return _datanode->get_class(); } inline node_author_t get_author() const { return _datanode->get_author(); } protected: data_accessor_base( const node_retriever_t& r, const std::string& n, const node_access_t a) : data_accessor_t(r, n), _datanode(NULL), _access(a) { _datanode = dynamic_cast< data_node_t* >(&node()); if (_datanode == NULL) { throw uhd::type_error("Expected data type for node " + n + " was " + boost::units::detail::demangle(typeid(data_t).name()) + " but got " + node().get_dtype()); } } data_node_t* _datanode; const node_access_t _access; private: virtual dag_vertex_t& node() const { return _vertex; } }; /*!--------------------------------------------------------- * class data_reader_t * * Accessor to read the value of a data node and to establish * a data node => worker node dependency * --------------------------------------------------------- */ template class data_reader_t : public data_accessor_base { public: data_reader_t(const node_retriever_t& retriever, const std::string& node) : data_accessor_base( retriever, node, ACCESS_READER) {} inline const data_t& get() const { return data_accessor_base::_datanode->get(); } inline operator const data_t&() const { return get(); } inline bool operator==(const data_t& rhs) { return get() == rhs; } inline bool operator!=(const data_t& rhs) { return !(get() == rhs); } }; /*!--------------------------------------------------------- * class data_reader_t * * Accessor to read and write the value of a data node and * to establish a worker node => data node dependency * --------------------------------------------------------- */ template class data_writer_t : public data_accessor_base { public: data_writer_t(const node_retriever_t& retriever, const std::string& node) : data_accessor_base( retriever, node, ACCESS_WRITER) {} inline const data_t& get() const { return data_accessor_base::_datanode->get(); } inline operator const data_t&() const { return get(); } inline bool operator==(const data_t& rhs) { return get() == rhs; } inline bool operator!=(const data_t& rhs) { return !(get() == rhs); } inline void set(const data_t& value) { data_accessor_base::_datanode->set(value); } inline data_writer_t& operator=(const data_t& value) { set(value); return *this; } inline data_writer_t& operator=(const data_writer_t& value) { set(value.get()); return *this; } }; /*!--------------------------------------------------------- * class worker_node_t * * A node class to implement a function that consumes * zero or more input data nodes and emits zero or more output * data nodes. The worker can also operate on other non-expert * interfaces because worker_node_t is abstract and the client * is required to implement the "resolve" method in a subclass. * --------------------------------------------------------- */ class worker_node_t : public dag_vertex_t { public: worker_node_t(const std::string& name) : dag_vertex_t(CLASS_WORKER, name) {} // Worker node specific std::list get_inputs() const { std::list retval; for(data_accessor_t* acc: _inputs) { retval.push_back(acc->node().get_name()); } return retval; } std::list get_outputs() const { std::list retval; for(data_accessor_t* acc: _outputs) { retval.push_back(acc->node().get_name()); } return retval; } protected: // This function is used to bind data accessors // to this worker. Accessors can be read/write // and the binding will ensure proper dependency // handling. void bind_accessor(data_accessor_t& accessor) { if (accessor.is_reader()) { _inputs.push_back(&accessor); } else if (accessor.is_writer()) { _outputs.push_back(&accessor); } else { throw uhd::assertion_error("Invalid accessor type"); } } private: // Graph resolution specific virtual bool is_dirty() const { bool inputs_dirty = false; for(data_accessor_t* acc: _inputs) { inputs_dirty |= acc->node().is_dirty(); } return inputs_dirty; } virtual void mark_clean() { for(data_accessor_t* acc: _inputs) { acc->node().mark_clean(); } } virtual void resolve() = 0; // Basic type info virtual const std::string& get_dtype() const { static const std::string dtype = ""; return dtype; } virtual std::string to_string() const { return ""; } // Workers don't have callbacks so implement stubs virtual void set_write_callback(const callback_func_t&) {} virtual bool has_write_callback() const { return false; } virtual void clear_write_callback() {} virtual void set_read_callback(const callback_func_t&) {} virtual bool has_read_callback() const { return false; } virtual void clear_read_callback() {} std::list _inputs; std::list _outputs; }; }} #endif /* INCLUDED_UHD_EXPERTS_EXPERT_NODE_HPP */