diff options
Diffstat (limited to 'host/lib/experts')
| -rw-r--r-- | host/lib/experts/CMakeLists.txt | 34 | ||||
| -rw-r--r-- | host/lib/experts/expert_container.cpp | 531 | ||||
| -rw-r--r-- | host/lib/experts/expert_container.hpp | 202 | ||||
| -rw-r--r-- | host/lib/experts/expert_factory.cpp | 27 | ||||
| -rw-r--r-- | host/lib/experts/expert_factory.hpp | 337 | ||||
| -rw-r--r-- | host/lib/experts/expert_nodes.hpp | 475 | 
6 files changed, 1606 insertions, 0 deletions
| diff --git a/host/lib/experts/CMakeLists.txt b/host/lib/experts/CMakeLists.txt new file mode 100644 index 000000000..db533e7fa --- /dev/null +++ b/host/lib/experts/CMakeLists.txt @@ -0,0 +1,34 @@ +# +# Copyright 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) + +LIBUHD_APPEND_SOURCES( +    ${CMAKE_CURRENT_SOURCE_DIR}/expert_container.cpp +    ${CMAKE_CURRENT_SOURCE_DIR}/expert_factory.cpp +) + +# Verbose Debug output for send/recv +SET( UHD_EXPERT_LOGGING OFF CACHE BOOL "Enable verbose logging for experts" ) +OPTION( UHD_EXPERT_LOGGING "Enable verbose logging for experts" "" ) +IF(UHD_EXPERT_LOGGING) +    MESSAGE(STATUS "Enabling verbose logging for experts") +    ADD_DEFINITIONS(-DUHD_EXPERT_LOGGING) +ENDIF() diff --git a/host/lib/experts/expert_container.cpp b/host/lib/experts/expert_container.cpp new file mode 100644 index 000000000..edfc2ebe3 --- /dev/null +++ b/host/lib/experts/expert_container.cpp @@ -0,0 +1,531 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "expert_container.hpp" +#include <uhd/exception.hpp> +#include <uhd/utils/msg.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/function.hpp> +#include <boost/bind.hpp> +#include <boost/make_shared.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/thread/mutex.hpp> +#include <boost/thread.hpp> +#include <boost/graph/graph_traits.hpp> +#include <boost/graph/depth_first_search.hpp> +#include <boost/graph/topological_sort.hpp> +#include <boost/graph/adjacency_list.hpp> + +#ifdef UHD_EXPERT_LOGGING +#define EX_LOG(depth, str) _log(depth, str) +#else +#define EX_LOG(depth, str) +#endif + +namespace uhd { namespace experts { + +typedef boost::adjacency_list< +    boost::vecS,        //Container used to represent the edge-list for each of the vertices. +    boost::vecS,        //container used to represent the vertex-list of the graph. +    boost::directedS,   //Directionality of graph +    dag_vertex_t*,      //Storage for each vertex +    boost::no_property, //Storage for each edge +    boost::no_property, //Storage for graph object +    boost::listS        //Container used to represent the edge-list for the graph. +> expert_graph_t; + +typedef std::map<std::string, expert_graph_t::vertex_descriptor> vertex_map_t; +typedef std::list<expert_graph_t::vertex_descriptor>             node_queue_t; + +typedef boost::graph_traits<expert_graph_t>::edge_iterator       edge_iter; +typedef boost::graph_traits<expert_graph_t>::vertex_iterator     vertex_iter; + +class expert_container_impl : public expert_container +{ +private:    //Visitor class for cycle detection algorithm +    struct cycle_det_visitor : public boost::dfs_visitor<> +    { +        cycle_det_visitor(std::vector<std::string>& back_edges): +            _back_edges(back_edges) {} + +        template <class Edge, class Graph> +        void back_edge(Edge u, const Graph& g) { +            _back_edges.push_back( +                g[boost::source(u,g)]->get_name() + "->" + g[boost::target(u,g)]->get_name()); +        } +        private: std::vector<std::string>& _back_edges; +    }; + +public: +    expert_container_impl(const std::string& name): +        _name(name) +    { +    } + +    ~expert_container_impl() +    { +        clear(); +    } + +    const std::string& get_name() const +    { +        return _name; +    } + +    void resolve_all(bool force = false) +    { +        boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); +        boost::lock_guard<boost::mutex> lock(_mutex); +        EX_LOG(0, str(boost::format("resolve_all(%s)") % (force?"force":""))); +        _resolve_helper("", "", force); +    } + +    void resolve_from(const std::string& node_name) +    { +        boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); +        boost::lock_guard<boost::mutex> lock(_mutex); +        EX_LOG(0, str(boost::format("resolve_from(%s)") % node_name)); +        _resolve_helper(node_name, "", false); +    } + +    void resolve_to(const std::string& node_name) +    { +        boost::lock_guard<boost::recursive_mutex> resolve_lock(_resolve_mutex); +        boost::lock_guard<boost::mutex> lock(_mutex); +        EX_LOG(0, str(boost::format("resolve_to(%s)") % node_name)); +        _resolve_helper("", node_name, false); +    } + +    dag_vertex_t& retrieve(const std::string& name) const +    { +        try { +            expert_graph_t::vertex_descriptor vertex = _lookup_vertex(name); +            return _get_vertex(vertex); +        } catch(std::exception&) { +            throw uhd::lookup_error("failed to find node " + name + " in expert graph"); +        } +    } + +    const dag_vertex_t& lookup(const std::string& name) const +    { +        return retrieve(name); +    } + +    const node_retriever_t& node_retriever() const +    { +        return *this; +    } + +    std::string to_dot() const +    { +        static const std::string DATA_SHAPE("ellipse"); +        static const std::string WORKER_SHAPE("box"); + +        std::string dot_str; +        dot_str += "digraph uhd_experts_" + _name + " {\n rankdir=LR;\n"; +        // Iterate through the vertices and print them out +        for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); +             vi.first != vi.second; +             ++vi.first +        ) { +            const dag_vertex_t& vertex = _get_vertex(*vi.first); +            if (vertex.get_class() != CLASS_WORKER) { +                dot_str += str(boost::format(" %d [label=\"%s\",shape=%s,xlabel=\"%s\"];\n") % +                               boost::uint32_t(*vi.first) % vertex.get_name() % +                               DATA_SHAPE % vertex.get_dtype()); +            } else { +                dot_str += str(boost::format(" %d [label=\"%s\",shape=%s];\n") % +                               boost::uint32_t(*vi.first) % vertex.get_name() % WORKER_SHAPE); +            } +        } + +        // Iterate through the edges and print them out +        for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); +             ei.first != ei.second; +             ++ei.first +        ) { +            dot_str += str(boost::format(" %d -> %d;\n") % +                           boost::uint32_t(boost::source(*(ei.first), _expert_dag)) % +                           boost::uint32_t(boost::target(*(ei.first), _expert_dag))); +        } +        dot_str += "}\n"; +        return dot_str; +    } + +    void debug_audit() const +    { +#ifdef UHD_EXPERT_LOGGING +        EX_LOG(0, "debug_audit()"); + +        //Test 1: Check for cycles in graph +        std::vector<std::string> back_edges; +        cycle_det_visitor cdet_vis(back_edges); +        boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); +        if (back_edges.empty()) { +            EX_LOG(1, "cycle check ... PASSED"); +        } else { +            EX_LOG(1, "cycle check ... ERROR!!!"); +            BOOST_FOREACH(const std::string& e, back_edges) { +                EX_LOG(2, "back edge: " + e); +            } +        } +        back_edges.clear(); + +        //Test 2: Check data node input and output edges +        std::vector<std::string> data_node_issues; +        BOOST_FOREACH(const vertex_map_t::value_type& v, _datanode_map) { +            size_t in_count = 0, out_count = 0; +            for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); +                 ei.first != ei.second; +                 ++ei.first +            ) { +                if (boost::target(*(ei.first), _expert_dag) == v.second) +                    in_count++; +                if (boost::source(*(ei.first), _expert_dag) == v.second) +                    out_count++; +            } +            bool prop_unused = false; +            if (in_count > 1) { +                data_node_issues.push_back(v.first + ": multiple writers (workers)"); +            } else if (in_count > 0) { +                if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY) { +                    data_node_issues.push_back(v.first + ": multiple writers (worker and property tree)"); +                } +            } else { +                if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { +                    data_node_issues.push_back(v.first + ": unreachable (will always hold initial value)"); +                } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_write_callback()) { +                    if (out_count > 0) { +                        data_node_issues.push_back(v.first + ": needs explicit resolve after write"); +                    } else { +                        data_node_issues.push_back(v.first + ": unused (no readers or writers)"); +                        prop_unused = true; +                    } +                } +            } +            if (out_count < 1) { +                if (_expert_dag[v.second]->get_class() != CLASS_PROPERTY) { +                    data_node_issues.push_back(v.first + ": unused (is not read by any worker)"); +                } else if (_expert_dag[v.second]->get_class() == CLASS_PROPERTY and not _expert_dag[v.second]->has_read_callback()) { +                    if (not prop_unused) { +                        data_node_issues.push_back(v.first + ": needs explicit resolve to read"); +                    } +                } +            } +        } + +        if (data_node_issues.empty()) { +            EX_LOG(1, "data node check ... PASSED"); +        } else { +            EX_LOG(1, "data node check ... WARNING!"); +            BOOST_FOREACH(const std::string& i, data_node_issues) { +                EX_LOG(2, i); +            } +        } +        data_node_issues.clear(); + +        //Test 3: Check worker node input and output edges +        std::vector<std::string> worker_issues; +        BOOST_FOREACH(const vertex_map_t::value_type& v, _worker_map) { +            size_t in_count = 0, out_count = 0; +            for (std::pair<edge_iter, edge_iter> ei = boost::edges(_expert_dag); +                 ei.first != ei.second; +                 ++ei.first +            ) { +                if (boost::target(*(ei.first), _expert_dag) == v.second) +                    in_count++; +                if (boost::source(*(ei.first), _expert_dag) == v.second) +                    out_count++; +            } +            if (in_count < 1) { +                worker_issues.push_back(v.first + ": no inputs (will never resolve)"); +            } +            if (out_count < 1) { +                worker_issues.push_back(v.first + ": no outputs"); +            } +        } +        if (worker_issues.empty()) { +            EX_LOG(1, "worker check ... PASSED"); +        } else { +            EX_LOG(1, "worker check ... WARNING!"); +            BOOST_FOREACH(const std::string& i, worker_issues) { +                EX_LOG(2, i); +            } +        } +        worker_issues.clear(); +#endif +    } + +    inline boost::recursive_mutex& resolve_mutex() { +        return _resolve_mutex; +    } + +protected: +    void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); + +        //Sanity check node pointer +        if (data_node == NULL) { +            throw uhd::runtime_error("NULL data node passed into expert container for registration."); +        } + +        //Sanity check the data node and ensure that it is not already in this graph +        EX_LOG(0, str(boost::format("add_data_node(%s)") % data_node->get_name())); +        if (data_node->get_class() == CLASS_WORKER) { +            throw uhd::runtime_error("Supplied node " + data_node->get_name() + " is not a data/property node."); +            delete data_node; +        } +        if (_datanode_map.find(data_node->get_name()) != _datanode_map.end()) { +            throw uhd::runtime_error("Data node with name " + data_node->get_name() + " already exists"); +            delete data_node; +        } + +        try { +            //Add a vertex in this graph for the data node +            expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(data_node, _expert_dag); +            EX_LOG(1, str(boost::format("added vertex %s") % data_node->get_name())); +            _datanode_map.insert(vertex_map_t::value_type(data_node->get_name(), gr_node)); + +            //Add resolve callbacks +            if (resolve_mode == AUTO_RESOLVE_ON_WRITE or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { +                EX_LOG(2, str(boost::format("added write callback"))); +                data_node->set_write_callback(boost::bind(&expert_container_impl::resolve_from, this, _1)); +            } +            if (resolve_mode == AUTO_RESOLVE_ON_READ or resolve_mode == AUTO_RESOLVE_ON_READ_WRITE) { +                EX_LOG(2, str(boost::format("added read callback"))); +                data_node->set_read_callback(boost::bind(&expert_container_impl::resolve_to, this, _1)); +            } +        } catch (...) { +            clear(); +            throw uhd::assertion_error("Unknown unrecoverable error adding data node. Cleared expert container."); +        } +    } + +    void add_worker(worker_node_t* worker) +    { +        boost::lock_guard<boost::mutex> lock(_mutex); + +        //Sanity check node pointer +        if (worker == NULL) { +            throw uhd::runtime_error("NULL worker passed into expert container for registration."); +        } + +        //Sanity check the data node and ensure that it is not already in this graph +        EX_LOG(0, str(boost::format("add_worker(%s)") % worker->get_name())); +        if (worker->get_class() != CLASS_WORKER) { +            throw uhd::runtime_error("Supplied node " + worker->get_name() + " is not a worker node."); +            delete worker; +        } +        if (_worker_map.find(worker->get_name()) != _worker_map.end()) { +            throw uhd::runtime_error("Resolver with name " + worker->get_name() + " already exists."); +            delete worker; +        } + +        try { +            //Add a vertex in this graph for the worker node +            expert_graph_t::vertex_descriptor gr_node = boost::add_vertex(worker, _expert_dag); +            EX_LOG(1, str(boost::format("added vertex %s") % worker->get_name())); +            _worker_map.insert(vertex_map_t::value_type(worker->get_name(), gr_node)); + +            //For each input, add an edge from the input to this node +            BOOST_FOREACH(const std::string& node_name, worker->get_inputs()) { +                vertex_map_t::const_iterator node = _datanode_map.find(node_name); +                if (node != _datanode_map.end()) { +                    boost::add_edge((*node).second, gr_node, _expert_dag); +                    EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[(*node).second]->get_name() % _expert_dag[gr_node]->get_name())); +                } else { +                    throw uhd::runtime_error("Data node with name " + node_name + " was not found"); +                } +            } + +            //For each output, add an edge from this node to the output +            BOOST_FOREACH(const std::string& node_name, worker->get_outputs()) { +                vertex_map_t::const_iterator node = _datanode_map.find(node_name); +                if (node != _datanode_map.end()) { +                    boost::add_edge(gr_node, (*node).second, _expert_dag); +                    EX_LOG(1, str(boost::format("added edge %s->%s") % _expert_dag[gr_node]->get_name() % _expert_dag[(*node).second]->get_name())); +                } else { +                    throw uhd::runtime_error("Data node with name " + node_name + " was not found"); +                } +            } +        } catch (uhd::runtime_error& ex) { +            clear(); +            //Promote runtime_error to assertion_error +            throw uhd::assertion_error(std::string(ex.what()) + " (Cleared expert container because error is unrecoverable)."); +        } catch (...) { +            clear(); +            throw uhd::assertion_error("Unknown unrecoverable error adding worker. Cleared expert container."); +        } +    } + +    void clear() +    { +        boost::lock_guard<boost::mutex> lock(_mutex); +        EX_LOG(0, "clear()"); + +        // Iterate through the vertices and release their node storage +        typedef boost::graph_traits<expert_graph_t>::vertex_iterator vertex_iter; +        for (std::pair<vertex_iter, vertex_iter> vi = boost::vertices(_expert_dag); +             vi.first != vi.second; +             ++vi.first +        ) { +            try { +                delete _expert_dag[*vi.first]; +                _expert_dag[*vi.first] = NULL; +            } catch (...) { +                //If a dag_vertex is a worker, it has a virtual dtor which +                //can possibly throw an exception. We will not let that +                //terminate clear() and leave things in a bad state. +            } +        } + +        //The following calls will not throw because they all contain +        //intrinsic types. + +        // Release all vertices and edges in the DAG +        _expert_dag.clear(); + +        // Release all nodes in the map +        _worker_map.clear(); +        _datanode_map.clear(); +    } + +private: +    void _resolve_helper(std::string start, std::string stop, bool force) +    { +        //Sort the graph topologically. This ensures that for all dependencies, the dependant +        //is always after all of its dependencies. +        node_queue_t sorted_nodes; +        try { +            boost::topological_sort(_expert_dag, std::front_inserter(sorted_nodes)); +        } catch (boost::not_a_dag&) { +            std::vector<std::string> back_edges; +            cycle_det_visitor cdet_vis(back_edges); +            boost::depth_first_search(_expert_dag, boost::visitor(cdet_vis)); +            if (not back_edges.empty()) { +                std::string edges; +                BOOST_FOREACH(const std::string& e, back_edges) { +                    edges += "* " + e + ""; +                } +                throw uhd::runtime_error("Cannot resolve expert because it has at least one cycle!\n" +                                         "The following back-edges were found:" + edges); +            } +        } +        if (sorted_nodes.empty()) return; + +        //Determine the start and stop node. If one is not explicitly specified then +        //resolve everything +        expert_graph_t::vertex_descriptor start_vertex = sorted_nodes.front(); +        expert_graph_t::vertex_descriptor stop_vertex = sorted_nodes.back(); +        if (not start.empty()) start_vertex = _lookup_vertex(start); +        if (not stop.empty()) stop_vertex = _lookup_vertex(stop); + +        //First Pass: Resolve all nodes if they are dirty, in a topological order +        std::list<dag_vertex_t*> resolved_workers; +        bool start_node_encountered = false; +        for (node_queue_t::iterator node_iter = sorted_nodes.begin(); +             node_iter != sorted_nodes.end(); +             ++node_iter +        ) { +            //Determine if we are at or beyond the starting node +            if (*node_iter == start_vertex) start_node_encountered = true; + +            //Only resolve if the starting node has passed +            if (start_node_encountered) { +                dag_vertex_t& node = _get_vertex(*node_iter); +                std::string node_val; +                if (force or node.is_dirty()) { +                    node.resolve(); +                    if (node.get_class() == CLASS_WORKER) { +                        resolved_workers.push_back(&node); +                    } +                    EX_LOG(1, str(boost::format("resolved node %s (%s) [%s]") % +                                    node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); +                } else { +                    EX_LOG(1, str(boost::format("skipped node %s (%s) [%s]") % +                                    node.get_name() % (node.is_dirty()?"dirty":"clean") % node.to_string())); +                } +            } + +            //Determine if we are beyond the stop node +            if (*node_iter == stop_vertex) break; +        } + +        //Second Pass: Mark all the workers clean. The policy is that a worker will mark all of +        //its dependencies clean so after this step all data nodes that are not consumed by a worker +        //will remain dirty (as they should because no one has consumed their value) +        for (std::list<dag_vertex_t*>::iterator worker = resolved_workers.begin(); +             worker != resolved_workers.end(); +             ++worker +        ) { +            (*worker)->mark_clean(); +        } +    } + +    expert_graph_t::vertex_descriptor _lookup_vertex(const std::string& name) const +    { +        expert_graph_t::vertex_descriptor vertex; +        //Look for node in the data-node map +        vertex_map_t::const_iterator vertex_iter = _datanode_map.find(name); +        if (vertex_iter != _datanode_map.end()) { +            vertex = (*vertex_iter).second; +        } else { +            //If not found, look in the worker-node map +            vertex_iter = _worker_map.find(name); +            if (vertex_iter != _worker_map.end()) { +                vertex = (*vertex_iter).second; +            } else { +                throw uhd::lookup_error("Could not find node with name " + name); +            } +        } +        return vertex; +    } + +    dag_vertex_t& _get_vertex(expert_graph_t::vertex_descriptor desc) const { +        //Requirement: Node must exist in expert graph +        dag_vertex_t* vertex_ptr = _expert_dag[desc]; +        if (vertex_ptr) { +            return *vertex_ptr; +        } else { +            throw uhd::assertion_error("Expert graph malformed. Found a NULL node."); +        } +    } + +    void _log(size_t depth, const std::string& str) const +    { +        std::string indents; +        for (size_t i = 0; i < depth; i++) indents += "- "; +        UHD_MSG(fastpath) << "[expert::" + _name + "] " << indents << str << std::endl; +    } + +private: +    const std::string       _name; +    expert_graph_t          _expert_dag;        //The primary graph data structure as an adjacency list +    vertex_map_t            _worker_map;        //A map from vertex name to vertex descriptor for workers +    vertex_map_t            _datanode_map;      //A map from vertex name to vertex descriptor for data nodes +    boost::mutex            _mutex; +    boost::recursive_mutex  _resolve_mutex; +}; + +expert_container::sptr expert_container::make(const std::string& name) +{ +    return boost::make_shared<expert_container_impl>(name); +} + +}} diff --git a/host/lib/experts/expert_container.hpp b/host/lib/experts/expert_container.hpp new file mode 100644 index 000000000..7e626dcc5 --- /dev/null +++ b/host/lib/experts/expert_container.hpp @@ -0,0 +1,202 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP + +#include "expert_nodes.hpp" +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/thread/recursive_mutex.hpp> + +namespace uhd { namespace experts { + +    enum auto_resolve_mode_t { +        AUTO_RESOLVE_OFF, +        AUTO_RESOLVE_ON_READ, +        AUTO_RESOLVE_ON_WRITE, +        AUTO_RESOLVE_ON_READ_WRITE +    }; + +    class UHD_API expert_container : private boost::noncopyable, public node_retriever_t { +    public: //Methods +        typedef boost::shared_ptr<expert_container> sptr; + +        virtual ~expert_container() {}; + +        /*! +         * Return the name of this container +         */ +        virtual const std::string& get_name() const = 0; + +        /*! +         * Resolves all the nodes in this expert graph. +         * +         * Dependency analysis is performed on the graph and nodes +         * are resolved in a topologically sorted order to ensure +         * that no nodes receive stale data. +         * Nodes and their dependencies are resolved only if they are +         * dirty i.e. their contained values have changed since the +         * last resolve. +         * This call requires an acyclic expert graph. +         * +         * \param force If true then ignore dirty state and resolve all nodes +         * \throws uhd::runtime_error if graph cannot be resolved +         */ +        virtual void resolve_all(bool force = false) = 0; + +        /*! +         * Resolves all the nodes that depend on the specified node. +         * +         * Dependency analysis is performed on the graph and nodes +         * are resolved in a topologically sorted order to ensure +         * that no nodes receive stale data. +         * Nodes and their dependencies are resolved only if they are +         * dirty i.e. their contained values have changed since the +         * last resolve. +         * This call requires an acyclic expert graph. +         * +         * \param node_name Name of the node to start resolving from +         * \throws uhd::lookup_error if node_name not in container +         * \throws uhd::runtime_error if graph cannot be resolved +         * +         */ +        virtual void resolve_from(const std::string& node_name) = 0; + +        /*! +         * Resolves all the specified node and all of its dependencies. +         * +         * Dependency analysis is performed on the graph and nodes +         * are resolved in a topologically sorted order to ensure +         * that no nodes receive stale data. +         * Nodes and their dependencies are resolved only if they are +         * dirty i.e. their contained values have changed since the +         * last resolve. +         * This call requires an acyclic expert graph. +         * +         * \param node_name Name of the node to resolve +         * \throws uhd::lookup_error if node_name not in container +         * \throws uhd::runtime_error if graph cannot be resolved +         * +         */ +        virtual void resolve_to(const std::string& node_name) = 0; + +        /*! +         * Return a node retriever object for this container +         */ +        virtual const node_retriever_t& node_retriever() const = 0; + +        /*! +         * Returns a DOT (graph description language) representation +         * of the expert graph. The output has labels for the node +         * name, node type (data or worker) and the underlying +         * data type for each node. +         * +         */ +        virtual std::string to_dot() const = 0; + +        /*! +         * Runs several sanity checks on the underlying graph to +         * flag dependency issues. Outputs of the checks are +         * logged to the console so UHD_EXPERTS_VERBOSE_LOGGING +         * must be enabled to see the results +         * +         */ +        virtual void debug_audit() const = 0; + +    private: +        /*! +         * Lookup a node with the specified name in the contained graph +         * +         * If the node is found, a reference to the node is returned. +         * If the node is not found, uhd::lookup_error is thrown +         * lookup can return a data or a worker node +         * \implements uhd::experts::node_retriever_t +         * +         * \param name Name of the node to find +         * +         */ +        virtual const dag_vertex_t& lookup(const std::string& name) const = 0; +        virtual dag_vertex_t& retrieve(const std::string& name) const = 0; + +        /*! +         * expert_factory is a friend of expert_container and +         * handles all operations that change the structure of +         * the underlying dependency graph. +         * The expert_container instance owns all data and worker +         * nodes and is responsible for release storage on destruction. +         * However, the expert_factory allocates storage for the +         * node and passes them into the expert_container using the +         * following "protected" API calls. +         * +         */ +        friend class expert_factory; + +        /*! +         * Creates an empty instance of expert_container with the +         * specified name. +         * +         * \param name Name of the container +         */ +        static sptr make(const std::string& name); + +        /*! +         * Returns a reference to the resolver mutex. +         * +         * The resolver mutex guarantees that external operations +         * to data-nodes are serialized with resolves of this +         * container. +         * +         */ +        virtual boost::recursive_mutex& resolve_mutex() = 0; + +        /*! +         * Add a data node to the expert graph +         * +         * \param data_node Pointer to a fully constructed data node object +         * \resolve_mode Auto resolve options: Choose from "disabled" and resolve on "read", "write" or "both" +         * \throws uhd::runtime_error if node already exists or is of a wrong type (recoverable) +         * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) +         * +         */ +        virtual void add_data_node(dag_vertex_t* data_node, auto_resolve_mode_t resolve_mode = AUTO_RESOLVE_OFF) = 0; + +        /*! +         * Add a worker node to the expert graph +         * +         * \param worker Pointer to a fully constructed worker object +         * \throws uhd::runtime_error if worker already exists or is of a wrong type (recoverable) +         * \throws uhd::assertion_error for other failures (unrecoverable. will clear the graph) +         * +         */ +        virtual void add_worker(worker_node_t* worker) = 0; + +        /*! +         * Release all storage for this object. This will delete all contained +         * data and worker nodes and remove all dependency relationship and +         * resolve callbacks. +         * +         * The object will be restored to its newly constructed state. Will not +         * throw. +         */ +        virtual void clear() = 0; +    }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_CONTAINER_HPP */ diff --git a/host/lib/experts/expert_factory.cpp b/host/lib/experts/expert_factory.cpp new file mode 100644 index 000000000..f887ad4a3 --- /dev/null +++ b/host/lib/experts/expert_factory.cpp @@ -0,0 +1,27 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#include "expert_factory.hpp" + +namespace uhd { namespace experts { + +expert_container::sptr expert_factory::create_container(const std::string& name) +{ +    return expert_container::make(name); +} + +}} diff --git a/host/lib/experts/expert_factory.hpp b/host/lib/experts/expert_factory.hpp new file mode 100644 index 000000000..83369117d --- /dev/null +++ b/host/lib/experts/expert_factory.hpp @@ -0,0 +1,337 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP + +#include "expert_container.hpp" +#include <uhd/property_tree.hpp> +#include <uhd/config.hpp> +#include <boost/noncopyable.hpp> +#include <boost/bind.hpp> +#include <memory> + +namespace uhd { namespace experts { + +    /*! +     * expert_factory is a friend of expert_container and +     * handles all operations to create and change the structure of +     * the an expert container. +     * The expert_factory allocates storage for the nodes in the +     * expert_container and passes allocated objects to the container +     * using private APIs. The expert_container instance owns all +     * data and workernodes and is responsible for releasing their +     * storage on destruction. +     * +     */ +    class UHD_API expert_factory : public boost::noncopyable { +    public: + +        /*! +         * Creates an empty instance of expert_container with the +         * specified name. +         * +         * \param name Name of the container +         */ +        static expert_container::sptr create_container( +            const std::string& name +        ); + +        /*! +         * Add a data node to the expert graph. +         * +         * \param container A shared pointer to the container to add the node to +         * \param name The name of the data node +         * \param init_val The initial value of the data node +         * \param mode The auto resolve mode +         * +         * 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<typename data_t> +        inline static void add_data_node( +            expert_container::sptr container, +            const std::string& name, +            const data_t& init_val, +            const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF +        ) { +            container->add_data_node(new data_node_t<data_t>(name, init_val), mode); +        } + +        /*! +         * Add a expert property to a property tree AND an expert graph +         * +         * \param container A shared pointer to the expert container to add the node to +         * \param subtree A shared pointer to subtree to add the property to +         * \param path The path of the property in the subtree +         * \param name The name of the data node in the expert graph +         * \param init_val The initial value of the data node +         * \param mode The auto resolve mode +         * +         * 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<typename data_t> +        inline static property<data_t>& add_prop_node( +            expert_container::sptr container, +            property_tree::sptr subtree, +            const fs_path &path, +            const std::string& name, +            const data_t& init_val, +            const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF +        ) { +            property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); +            data_node_t<data_t>* node_ptr = +                new data_node_t<data_t>(name, init_val, &container->resolve_mutex()); +            prop.set(init_val); +            prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, node_ptr, _1)); +            prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, node_ptr)); +            container->add_data_node(node_ptr, mode); +            return prop; +        } + +        /*! +         * Add a expert property to a property tree AND an expert graph. +         * The property is registered with the path as the identifier for +         * both the property subtree and the expert container +         * +         * \param container A shared pointer to the expert container to add the node to +         * \param subtree A shared pointer to subtree to add the property to +         * \param path The path of the property in the subtree +         * \param init_val The initial value of the data node +         * \param mode The auto resolve mode +         * +         */ +        template<typename data_t> +        inline static property<data_t>& add_prop_node( +            expert_container::sptr container, +            property_tree::sptr subtree, +            const fs_path &path, +            const data_t& init_val, +            const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF +        ) { +            return add_prop_node(container, subtree, path, path, init_val, mode); +        } + +        /*! +         * Add a dual expert property to a property tree AND an expert graph. +         * A dual property is a desired and coerced value pair +         * +         * \param container A shared pointer to the expert container to add the node to +         * \param subtree A shared pointer to subtree to add the property to +         * \param path The path of the property in the subtree +         * \param desired_name The name of the desired data node in the expert graph +         * \param desired_name The name of the coerced data node in the expert graph +         * \param init_val The initial value of both the data nodes +         * \param mode The auto resolve mode +         * +         * 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<typename data_t> +        inline static property<data_t>& add_dual_prop_node( +            expert_container::sptr container, +            property_tree::sptr subtree, +            const fs_path &path, +            const std::string& desired_name, +            const std::string& coerced_name, +            const data_t& init_val, +            const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF +        ) { +            bool auto_resolve_desired = (mode==AUTO_RESOLVE_ON_WRITE or mode==AUTO_RESOLVE_ON_READ_WRITE); +            bool auto_resolve_coerced = (mode==AUTO_RESOLVE_ON_READ or mode==AUTO_RESOLVE_ON_READ_WRITE); + +            property<data_t>& prop = subtree->create<data_t>(path, property_tree::MANUAL_COERCE); +            data_node_t<data_t>* desired_node_ptr = +                new data_node_t<data_t>(desired_name, init_val, &container->resolve_mutex()); +            data_node_t<data_t>* coerced_node_ptr = +                new data_node_t<data_t>(coerced_name, init_val, &container->resolve_mutex()); +            prop.set(init_val); +            prop.set_coerced(init_val); +            prop.add_desired_subscriber(boost::bind(&data_node_t<data_t>::commit, desired_node_ptr, _1)); +            prop.set_publisher(boost::bind(&data_node_t<data_t>::retrieve, coerced_node_ptr)); + +            container->add_data_node(desired_node_ptr, +                auto_resolve_desired ? AUTO_RESOLVE_ON_WRITE : AUTO_RESOLVE_OFF); +            container->add_data_node(coerced_node_ptr, +                auto_resolve_coerced ? AUTO_RESOLVE_ON_READ : AUTO_RESOLVE_OFF); +            return prop; +        } + +        /*! +         * Add a dual expert property to a property tree AND an expert graph. +         * A dual property is a desired and coerced value pair +         * The property is registered with path/desired as the desired node +         * name and path/coerced as the coerced node name +         * +         * \param container A shared pointer to the expert container to add the node to +         * \param subtree A shared pointer to subtree to add the property to +         * \param path The path of the property in the subtree +         * \param init_val The initial value of both the data nodes +         * \param mode The auto resolve mode +         * +         */ +        template<typename data_t> +        inline static property<data_t>& add_dual_prop_node( +            expert_container::sptr container, +            property_tree::sptr subtree, +            const fs_path &path, +            const data_t& init_val, +            const auto_resolve_mode_t mode = AUTO_RESOLVE_OFF +        ) { +            return add_dual_prop_node(container, subtree, path, path + "/desired", path + "/coerced", init_val, mode); +        } + +        /*! +         * Add a worker node to the expert graph. +         * The expert_container owns and manages storage for the worker +         * +         * \tparam worker_t Data type of the worker class +         * +         * \param container A shared pointer to the container to add the node to +         * +         */ +        template<typename worker_t> +        inline static void add_worker_node( +            expert_container::sptr container +        ) { +            container->add_worker(new worker_t()); +        } + +        /*! +         * Add a worker node to the expert graph. +         * The expert_container owns and manages storage for the worker +         * +         * \tparam worker_t Data type of the worker class +         * \tparam arg1_t Data type of the first argument to the constructor +         * \tparam ... +         * \tparam argN_t Data type of the Nth argument to the constructor +         * +         * \param container A shared pointer to the container to add the node to +         * \param arg1 First arg to ctor +         * \param ... +         * \param argN Nth arg to ctor +         * +         */ +        template<typename worker_t, typename arg1_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1 +        ) { +            container->add_worker(new worker_t(arg1)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2 +        ) { +            container->add_worker(new worker_t(arg1, arg2)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3, +            arg4_t const & arg4 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3, arg4)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, +                                    typename arg5_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3, +            arg4_t const & arg4, +            arg5_t const & arg5 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, +                                    typename arg5_t, typename arg6_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3, +            arg4_t const & arg4, +            arg5_t const & arg5, +            arg6_t const & arg6 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, +                                    typename arg5_t, typename arg6_t, typename arg7_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3, +            arg4_t const & arg4, +            arg5_t const & arg5, +            arg6_t const & arg6, +            arg7_t const & arg7 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7)); +        } + +        template<typename worker_t, typename arg1_t, typename arg2_t, typename arg3_t, typename arg4_t, +                                    typename arg5_t, typename arg6_t, typename arg7_t, typename arg8_t> +        inline static void add_worker_node( +            expert_container::sptr container, +            arg1_t const & arg1, +            arg2_t const & arg2, +            arg3_t const & arg3, +            arg4_t const & arg4, +            arg5_t const & arg5, +            arg6_t const & arg6, +            arg7_t const & arg7, +            arg7_t const & arg8 +        ) { +            container->add_worker(new worker_t(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)); +        } +    }; +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_FACTORY_HPP */ diff --git a/host/lib/experts/expert_nodes.hpp b/host/lib/experts/expert_nodes.hpp new file mode 100644 index 000000000..dc5cc934b --- /dev/null +++ b/host/lib/experts/expert_nodes.hpp @@ -0,0 +1,475 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program.  If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP +#define INCLUDED_UHD_EXPERTS_EXPERT_NODES_HPP + +#include <uhd/config.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/dirty_tracked.hpp> +#include <boost/function.hpp> +#include <boost/foreach.hpp> +#include <boost/thread/recursive_mutex.hpp> +#include <boost/thread.hpp> +#include <boost/units/detail/utility.hpp> +#include <memory> +#include <list> + +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<void(std::string)> 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<typename data_t> +        static std::string print(const data_t& val) { +            std::ostringstream os; +            os << val; +            return os.str(); +        } + +        static std::string print(const boost::uint8_t& val) { +            std::ostringstream os; +            os << int(val); +            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<typename data_t> +    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<boost::recursive_mutex> 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<boost::recursive_mutex> 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_t>   _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<typename data_t> +    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<data_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<data_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<typename data_t> +    class data_reader_t : public data_accessor_base<data_t> { +    public: +        data_reader_t(const node_retriever_t& retriever, const std::string& node) : +            data_accessor_base<data_t>( +                retriever, node, ACCESS_READER) {} + +        inline const data_t& get() const { +            return data_accessor_base<data_t>::_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<typename data_t> +    class data_writer_t : public data_accessor_base<data_t> { +    public: +        data_writer_t(const node_retriever_t& retriever, const std::string& node) : +            data_accessor_base<data_t>( +                retriever, node, ACCESS_WRITER) {} + +        inline const data_t& get() const { +            return data_accessor_base<data_t>::_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<data_t>::_datanode->set(value); +        } + +        inline data_writer_t<data_t>& operator=(const data_t& value) { +            set(value); +            return *this; +        } + +        inline data_writer_t<data_t>& operator=(const data_writer_t<data_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 zeroor 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<std::string> get_inputs() const { +            std::list<std::string> retval; +            BOOST_FOREACH(data_accessor_t* acc, _inputs) { +                retval.push_back(acc->node().get_name()); +            } +            return retval; +        } + +        std::list<std::string> get_outputs() const { +            std::list<std::string> retval; +            BOOST_FOREACH(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; +            BOOST_FOREACH(data_accessor_t* acc, _inputs) { +                inputs_dirty |= acc->node().is_dirty(); +            } +            return inputs_dirty; +        } + +        virtual void mark_clean() { +            BOOST_FOREACH(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 = "<worker>"; +            return dtype; +        } + +        virtual std::string to_string() const { +            return "<worker>"; +        } + +        // 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<data_accessor_t*> _inputs; +        std::list<data_accessor_t*> _outputs; +    }; + +}} + +#endif /* INCLUDED_UHD_EXPERTS_EXPERT_NODE_HPP */ | 
