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