aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib
diff options
context:
space:
mode:
authorAshish Chaudhari <ashish@ettus.com>2016-01-22 11:50:26 -0800
committerAshish Chaudhari <ashish@ettus.com>2016-02-12 00:21:15 -0800
commit7c7622c1fe4779c84e7c7149caa6dd08400e2774 (patch)
tree8ceef98075ecaca8930f455df5fa0f2f762e3771 /host/lib
parent834acb8b6ccaaab1701d0c48ead443b0fb17b8cf (diff)
downloaduhd-7c7622c1fe4779c84e7c7149caa6dd08400e2774.tar.gz
uhd-7c7622c1fe4779c84e7c7149caa6dd08400e2774.tar.bz2
uhd-7c7622c1fe4779c84e7c7149caa6dd08400e2774.zip
uhd: Added expert dependency tracking infrastructure
- Code location uhd/lib/experts - expert_nodes.hpp contains all node definitions: data and worker - expert_container.hpp contains the memory manager and resolver for expert nodes - expert_factory contains the initializer and modifier for expert_container
Diffstat (limited to 'host/lib')
-rw-r--r--host/lib/CMakeLists.txt1
-rw-r--r--host/lib/experts/CMakeLists.txt34
-rw-r--r--host/lib/experts/expert_container.cpp528
-rw-r--r--host/lib/experts/expert_container.hpp202
-rw-r--r--host/lib/experts/expert_factory.cpp27
-rw-r--r--host/lib/experts/expert_factory.hpp335
-rw-r--r--host/lib/experts/expert_nodes.hpp424
7 files changed, 1551 insertions, 0 deletions
diff --git a/host/lib/CMakeLists.txt b/host/lib/CMakeLists.txt
index 21a979109..a7a1cea3b 100644
--- a/host/lib/CMakeLists.txt
+++ b/host/lib/CMakeLists.txt
@@ -94,6 +94,7 @@ INCLUDE_SUBDIRECTORY(transport)
INCLUDE_SUBDIRECTORY(usrp)
INCLUDE_SUBDIRECTORY(usrp_clock)
INCLUDE_SUBDIRECTORY(utils)
+INCLUDE_SUBDIRECTORY(experts)
########################################################################
# Build info
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..3c4687d66
--- /dev/null
+++ b/host/lib/experts/expert_container.cpp
@@ -0,0 +1,528 @@
+//
+// 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) {
+ delete data_node;
+ throw uhd::runtime_error("Supplied node " + data_node->get_name() + " is not a data/property node.");
+ }
+ if (_datanode_map.find(data_node->get_name()) != _datanode_map.end()) {
+ delete data_node;
+ throw uhd::runtime_error("Data node with name " + data_node->get_name() + " already exists");
+ }
+
+ 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) {
+ delete worker;
+ throw uhd::runtime_error("Supplied node " + worker->get_name() + " is not a worker node.");
+ }
+ if (_worker_map.find(worker->get_name()) != _worker_map.end()) {
+ delete worker;
+ throw uhd::runtime_error("Resolver with name " + worker->get_name() + " already exists.");
+ }
+
+ 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);
+ 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)") % node.get_name() % (node.is_dirty()?"dirty":"clean")));
+ } else {
+ EX_LOG(1, str(boost::format("skipped node %s (%s)") % node.get_name() % (node.is_dirty()?"dirty":"clean")));
+ }
+ }
+
+ //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..2d378535c
--- /dev/null
+++ b/host/lib/experts/expert_factory.hpp
@@ -0,0 +1,335 @@
+//
+// 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.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, auto_resolve_desired ? &container->resolve_mutex() : NULL);
+ data_node_t<data_t>* coerced_node_ptr =
+ new data_node_t<data_t>(coerced_name, init_val, &container->resolve_mutex());
+ 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..69ecfa661
--- /dev/null
+++ b/host/lib/experts/expert_nodes.hpp
@@ -0,0 +1,424 @@
+//
+// 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 <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;
+
+ // 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_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(typeid(data_t).name());
+ return dtype;
+ }
+
+ 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 " + 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();
+ }
+ };
+
+ /*!---------------------------------------------------------
+ * 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>::_node.get();
+ }
+
+ inline operator const data_t&() const {
+ return get();
+ }
+
+ 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;
+ }
+ };
+
+ /*!---------------------------------------------------------
+ * 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;
+ }
+
+ // 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 */