Skip to content

Commit

Permalink
refactored port<T> interface - WIP
Browse files Browse the repository at this point in the history
... as outlined by GR Architecture WG and #148

tackled items:
 * refactored port structure
 * added optional domain argument

Signed-off-by: Ralph J. Steinhagen <[email protected]>
  • Loading branch information
RalphSteinhagen committed Sep 18, 2023
1 parent a949de8 commit 6e9c05d
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 87 deletions.
5 changes: 3 additions & 2 deletions include/graph.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -738,9 +738,10 @@ operator<<(std::ostream &os, const port_direction_t &value) {
return os << static_cast<int>(value);
}

template<PortDomain T>
inline std::ostream &
operator<<(std::ostream &os, const port_domain_t &value) {
return os << static_cast<int>(value);
operator<<(std::ostream &os, const T &value) {
return os << value.Name;
}

#if HAVE_SOURCE_LOCATION
Expand Down
5 changes: 4 additions & 1 deletion include/node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1321,7 +1321,10 @@ merge(A &&a, B &&b) {

#if !DISABLE_SIMD
namespace test {
struct copy : public node<copy, IN<float, 0, std::numeric_limits<std::size_t>::max(), "in">, OUT<float, 0, std::numeric_limits<std::size_t>::max(), "out">> {
struct copy : public node<copy, IN<float>, OUT<float>> {
IN<float> in;
OUT<float> out;

public:
template<meta::t_or_simd<float> V>
[[nodiscard]] constexpr V
Expand Down
175 changes: 137 additions & 38 deletions include/port.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,32 @@ using namespace fair::literals;
using supported_type = std::variant<uint8_t, uint32_t, int8_t, int16_t, int32_t, float, double, std::complex<float>, std::complex<double>, DataSet<double>, DataSet<float> /*, ...*/>;

enum class port_direction_t { INPUT, OUTPUT, ANY }; // 'ANY' only for query and not to be used for port declarations

enum class connection_result_t { SUCCESS, FAILED };

enum class port_type_t {
STREAM, /*!< used for single-producer-only ond usually synchronous one-to-one or one-to-many communications */
MESSAGE /*!< used for multiple-producer one-to-one, one-to-many, many-to-one, or many-to-many communications */
};
enum class port_domain_t { CPU, GPU, NET, FPGA, DSP, MLU };

template<fixed_string PortDomainName>
struct port_domain {
inline static constexpr fixed_string Name = PortDomainName;
};

template<typename T>
concept PortDomain = requires { T::Name; } && std::is_base_of_v<port_domain<T::Name>, T>;

template<typename T>
using is_port_domain = std::bool_constant<PortDomain<T>>;

struct CPU : public port_domain<"CPU"> {};

struct GPU : public port_domain<"GPU"> {};

static_assert(is_port_domain<CPU>::value);
static_assert(is_port_domain<GPU>::value);
static_assert(!is_port_domain<int>::value);

template<class T>
concept Port = requires(T t, const std::size_t n_items) { // dynamic definitions
Expand All @@ -37,6 +57,7 @@ concept Port = requires(T t, const std::size_t n_items) { // dynamic definitions
{ t.max_samples } -> std::convertible_to<std::size_t>;
{ t.type() } -> std::same_as<port_type_t>;
{ t.direction() } -> std::same_as<port_direction_t>;
{ t.domain() } -> std::same_as<std::string_view>;
{ t.resize_buffer(n_items) } -> std::same_as<connection_result_t>;
{ t.disconnect() } -> std::same_as<connection_result_t>;
};
Expand All @@ -51,6 +72,56 @@ struct internal_port_buffers {
void *tagHandler;
};

template<std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent>
struct RequiredSamples {
static constexpr std::size_t MinSamples = MIN_SAMPLES;
static constexpr std::size_t MaxSamples = MAX_SAMPLES;
};

template<typename T>
concept IsRequiredSamples = requires {
T::MinSamples;
T::MaxSamples;
} && std::is_base_of_v<RequiredSamples<T::MinSamples, T::MaxSamples>, T>;

template<typename T>
using is_required_samples = std::bool_constant<IsRequiredSamples<T>>;

static_assert(is_required_samples<RequiredSamples<1, 1024>>::value);
static_assert(!is_required_samples<int>::value);

template<gr::Buffer T>
struct StreamBuffer {
using type = T;
};

template<typename T>
concept IsStreamBuffer = requires { typename T::type; } && gr::Buffer<typename T::type>;

template<typename T>
using is_stream_buffer = std::bool_constant<IsStreamBuffer<T>>;

template<gr::Buffer T>
struct TagBuffer {
using type = T;
};

template<typename T>
concept IsTagBuffer = requires { typename T::type; } && gr::Buffer<typename T::type>;

template<typename T>
using is_tag_buffer = std::bool_constant<IsTagBuffer<T>>;

template<typename T>
struct DefaultStreamBuffer : StreamBuffer<gr::circular_buffer<T>> {};

struct DefaultTagBuffer : TagBuffer<gr::circular_buffer<tag_t>> {};

static_assert(is_stream_buffer<DefaultStreamBuffer<int>>::value);
// static_assert(!is_stream_buffer<DefaultTagBuffer>::value);
// static_assert(!is_tag_buffer<DefaultStreamBuffer<int>>::value);
static_assert(is_tag_buffer<DefaultTagBuffer>::value);

/**
* @brief 'ports' are interfaces that allows data to flow between blocks in a graph, similar to RF connectors.
* Each block can have zero or more input/output ports. When connecting ports, either a single-step or a two-step
Expand All @@ -73,25 +144,24 @@ struct internal_port_buffers {
* @tparam PortName a string to identify the port, notably to be used in an UI- and hand-written explicit code context.
* @tparam PortType STREAM or MESSAGE
* @tparam PortDirection either input or output
* @tparam MIN_SAMPLES specifies the minimum number of samples the port/block requires for processing in one scheduler iteration
* @tparam MAX_SAMPLES specifies the maximum number of samples the port/block can process in one scheduler iteration
* @tparam BufferType user-extendable buffer implementation for the streaming data
* @tparam TagBufferType user-extendable buffer implementation for the tag data
* @tparam Arguments optional: default to 'DefaultStreamBuffer' and DefaultTagBuffer' based on 'gr::circular_buffer', and CPU domain
*/
template<typename T, fixed_string PortName, port_type_t PortType, port_direction_t PortDirection, // TODO: sort default arguments
std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent, gr::Buffer BufferType = gr::circular_buffer<T>,
gr::Buffer TagBufferType = gr::circular_buffer<tag_t>>
template<typename T, fixed_string PortName, port_type_t PortType, port_direction_t PortDirection, typename... Arguments>
class port {
public:
static_assert(PortDirection != port_direction_t::ANY, "ANY reserved for queries and not port direction declarations");

using value_type = T;

static constexpr bool IS_INPUT = PortDirection == port_direction_t::INPUT;
static constexpr bool IS_OUTPUT = PortDirection == port_direction_t::OUTPUT;
using value_type = T;
using Domain = typename fair::meta::typelist<Arguments...>::template find_or_default<is_port_domain, CPU>;
using Required = typename fair::meta::typelist<Arguments...>::template find_or_default<is_required_samples, RequiredSamples<std::dynamic_extent, std::dynamic_extent>>;
using BufferType = typename fair::meta::typelist<Arguments...>::template find_or_default<is_stream_buffer, DefaultStreamBuffer<T>>::type;
using TagBufferType = typename fair::meta::typelist<Arguments...>::template find_or_default<is_tag_buffer, DefaultTagBuffer>::type;
static constexpr port_direction_t Direction = PortDirection;
static constexpr bool IS_INPUT = PortDirection == port_direction_t::INPUT;
static constexpr bool IS_OUTPUT = PortDirection == port_direction_t::OUTPUT;

template<fixed_string NewName>
using with_name = port<T, NewName, PortType, PortDirection, MIN_SAMPLES, MAX_SAMPLES, BufferType>;
using with_name = port<T, NewName, PortType, PortDirection, Arguments...>;

using ReaderType = decltype(std::declval<BufferType>().new_reader());
using WriterType = decltype(std::declval<BufferType>().new_writer());
Expand All @@ -103,8 +173,8 @@ class port {
// public properties
const std::string name = static_cast<std::string>(PortName);
std::int16_t priority = 0; // → dependents of a higher-prio port should be scheduled first (Q: make this by order of ports?)
std::size_t min_samples = (MIN_SAMPLES == std::dynamic_extent ? 1 : MIN_SAMPLES);
std::size_t max_samples = MAX_SAMPLES;
std::size_t min_samples = (Required::MinSamples == std::dynamic_extent ? 1 : Required::MinSamples);
std::size_t max_samples = Required::MaxSamples;

private:
bool _connected = false;
Expand Down Expand Up @@ -158,7 +228,7 @@ class port {
}

public:
port() = default;
constexpr port() = default;
port(const port &) = delete;
auto
operator=(const port &)
Expand Down Expand Up @@ -195,6 +265,11 @@ class port {
return PortDirection;
}

[[nodiscard]] constexpr static std::string_view
domain() noexcept {
return std::string_view(Domain::Name);
}

[[nodiscard]] constexpr static decltype(PortName)
static_name() noexcept
requires(!PortName.empty())
Expand All @@ -219,19 +294,19 @@ class port {

[[nodiscard]] constexpr std::size_t
min_buffer_size() const noexcept {
if constexpr (MIN_SAMPLES == std::dynamic_extent) {
if constexpr (Required::MinSamples == std::dynamic_extent) {
return min_samples;
} else {
return MIN_SAMPLES;
return Required::MinSamples;
}
}

[[nodiscard]] constexpr std::size_t
max_buffer_size() const noexcept {
if constexpr (MAX_SAMPLES == std::dynamic_extent) {
if constexpr (Required::MaxSamples == std::dynamic_extent) {
return max_samples;
} else {
return MAX_SAMPLES;
return Required::MaxSamples;
}
}

Expand Down Expand Up @@ -354,31 +429,41 @@ repeated_ports_impl(std::index_sequence<Is...>) {
} // namespace detail

// TODO: Add port index to BaseName
template<std::size_t Count, typename T, fixed_string BaseName, port_type_t PortType, port_direction_t PortDirection, std::size_t MIN_SAMPLES = std::dynamic_extent,
std::size_t MAX_SAMPLES = std::dynamic_extent>
using repeated_ports = decltype(detail::repeated_ports_impl<port<T, BaseName, PortType, PortDirection, MIN_SAMPLES, MAX_SAMPLES>>(std::make_index_sequence<Count>()));

template<typename T, std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent, fixed_string PortName = "">
using IN = port<T, PortName, port_type_t::STREAM, port_direction_t::INPUT, MIN_SAMPLES, MAX_SAMPLES>;
template<typename T, std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent, fixed_string PortName = "">
using OUT = port<T, PortName, port_type_t::STREAM, port_direction_t::OUTPUT, MIN_SAMPLES, MAX_SAMPLES>;
template<typename T, std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent, fixed_string PortName = "">
using IN_MSG = port<T, PortName, port_type_t::MESSAGE, port_direction_t::INPUT, MIN_SAMPLES, MAX_SAMPLES>;
template<typename T, std::size_t MIN_SAMPLES = std::dynamic_extent, std::size_t MAX_SAMPLES = std::dynamic_extent, fixed_string PortName = "">
using OUT_MSG = port<T, PortName, port_type_t::MESSAGE, port_direction_t::OUTPUT, MIN_SAMPLES, MAX_SAMPLES>;
template<std::size_t Count, typename T, fixed_string BaseName, port_type_t PortType, port_direction_t PortDirection, typename... Arguments>
using repeated_ports = decltype(detail::repeated_ports_impl<port<T, BaseName, PortType, PortDirection, Arguments...>>(std::make_index_sequence<Count>()));

template<typename T, typename... Arguments>
using IN = port<T, "PortName", port_type_t::STREAM, port_direction_t::INPUT, Arguments...>;
template<typename T, typename... Arguments>
using OUT = port<T, "PortName", port_type_t::STREAM, port_direction_t::OUTPUT, Arguments...>;
template<typename T, typename... Arguments>
using IN_MSG = port<T, "PortName", port_type_t::MESSAGE, port_direction_t::INPUT, Arguments...>;
template<typename T, typename... Arguments>
using OUT_MSG = port<T, "PortName", port_type_t::MESSAGE, port_direction_t::OUTPUT, Arguments...>;

template<typename T, fixed_string PortName = "", typename... Arguments>
using InNamed = port<T, PortName, port_type_t::STREAM, port_direction_t::INPUT, Arguments...>;
template<typename T, fixed_string PortName = "", typename... Arguments>
using OutNamed = port<T, PortName, port_type_t::STREAM, port_direction_t::OUTPUT, Arguments...>;
template<typename T, fixed_string PortName = "", typename... Arguments>
using MsgInNamed = port<T, PortName, port_type_t::STREAM, port_direction_t::INPUT, Arguments...>;
template<typename T, fixed_string PortName = "", typename... Arguments>
using MsgOutNamed = port<T, PortName, port_type_t::STREAM, port_direction_t::OUTPUT, Arguments...>;

static_assert(Port<IN<float>>);
static_assert(Port<decltype(IN<float>())>);
static_assert(Port<OUT<float>>);
static_assert(Port<IN_MSG<float>>);
static_assert(Port<OUT_MSG<float>>);

static_assert(IN<float, 0, 0, "in">::static_name() == fixed_string("in"));
static_assert(requires { IN<float>("in").name; });
static_assert(IN<float, RequiredSamples<1, 2>>::Required::MinSamples == 1);
static_assert(IN<float, RequiredSamples<1, 2>>::Required::MaxSamples == 2);
static_assert(std::same_as<IN<float, RequiredSamples<1, 2>>::Domain, CPU>);
static_assert(std::same_as<IN<float, RequiredSamples<1, 2>, GPU>::Domain, GPU>);

static_assert(OUT_MSG<float, 0, 0, "out_msg">::static_name() == fixed_string("out_msg"));
static_assert(!(OUT_MSG<float, 0, 0, "out_msg">::with_name<"out_message">::static_name() == fixed_string("out_msg")));
static_assert(OUT_MSG<float, 0, 0, "out_msg">::with_name<"out_message">::static_name() == fixed_string("out_message"));
static_assert(MsgOutNamed<float, "out_msg">::static_name() == fixed_string("out_msg"));
static_assert(!(MsgOutNamed<float, "out_msg">::with_name<"out_message">::static_name() == fixed_string("out_msg")));
static_assert(MsgOutNamed<float, "out_msg">::with_name<"out_message">::static_name() == fixed_string("out_message"));

/**
* Runtime capable wrapper to be used within a block. It's primary purpose is to allow the runtime
Expand Down Expand Up @@ -415,6 +500,10 @@ class dynamic_port {
direction() const noexcept
= 0;

[[nodiscard]] virtual std::string_view
domain() const noexcept
= 0;

[[nodiscard]] virtual connection_result_t
resize_buffer(std::size_t min_size) noexcept
= 0;
Expand Down Expand Up @@ -489,7 +578,7 @@ class dynamic_port {
}

~wrapper() override = default;

// TODO revisit: constexpr was removed because emscripten does not support constexpr function for non literal type, like DataSet<T>
#if defined(__EMSCRIPTEN__)
[[nodiscard]] supported_type
Expand All @@ -510,6 +599,11 @@ class dynamic_port {
return _value.direction();
}

[[nodiscard]] constexpr std::string_view
domain() const noexcept override {
return _value.domain();
}

[[nodiscard]] connection_result_t
resize_buffer(std::size_t min_size) noexcept override {
return _value.resize_buffer(min_size);
Expand Down Expand Up @@ -576,6 +670,11 @@ class dynamic_port {
return _accessor->direction();
}

[[nodiscard]] std::string_view
domain() const noexcept {
return _accessor->domain();
}

[[nodiscard]] connection_result_t
resize_buffer(std::size_t min_size) {
if (direction() == port_direction_t::OUTPUT) {
Expand Down
28 changes: 9 additions & 19 deletions include/port_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ namespace fair::graph::traits::port {

template<typename T>
concept has_fixed_info_v = requires {
typename T::value_type;
{ T::static_name() };
{ T::direction() } -> std::same_as<port_direction_t>;
{ T::type() } -> std::same_as<port_type_t>;
};
typename T::value_type;
{ T::static_name() };
{ T::direction() } -> std::same_as<port_direction_t>;
{ T::type() } -> std::same_as<port_type_t>;
};

template<typename T>
using has_fixed_info = std::integral_constant<bool, has_fixed_info_v<T>>;
Expand Down Expand Up @@ -43,25 +43,15 @@ using is_output = std::integral_constant<bool, Port::direction() == port_directi
template<typename Port>
concept is_output_v = is_output<Port>::value;

template <typename Type>
template<typename Type>
concept is_port_v = is_output_v<Type> || is_input_v<Type>;

template<typename... Ports>
struct min_samples : std::integral_constant<std::size_t, std::max({ min_samples<Ports>::value... })> {};

template<typename T, fixed_string PortName, port_type_t PortType, port_direction_t PortDirection,
std::size_t MIN_SAMPLES, std::size_t MAX_SAMPLES, gr::Buffer BufferType>
struct min_samples<fair::graph::port<T, PortName, PortType, PortDirection, MIN_SAMPLES, MAX_SAMPLES, BufferType>>
: std::integral_constant<std::size_t, MIN_SAMPLES> {};
struct min_samples : std::integral_constant<std::size_t, std::max({ Ports::RequiredSamples::MinSamples... })> {};

template<typename... Ports>
struct max_samples : std::integral_constant<std::size_t, std::min({ max_samples<Ports>::value... })> {};

template<typename T, fixed_string PortName, port_type_t PortType, port_direction_t PortDirection,
std::size_t MIN_SAMPLES, std::size_t MAX_SAMPLES, gr::Buffer BufferType>
struct max_samples<fair::graph::port<T, PortName, PortType, PortDirection, MIN_SAMPLES, MAX_SAMPLES, BufferType>>
: std::integral_constant<std::size_t, MAX_SAMPLES> {};
struct max_samples : std::integral_constant<std::size_t, std::max({ Ports::RequiredSamples::MaxSamples... })> {};

} // namespace port
} // namespace fair::graph::traits::port

#endif // include guard
3 changes: 3 additions & 0 deletions include/typelist.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,9 @@ struct typelist {
}())>;
};

template<typename T, typename... Ts>
constexpr bool is_any_of_v = std::disjunction_v<std::is_same<T, Ts>...>;

namespace detail {
template<template<typename...> typename OtherTypelist, typename... Args>
meta::typelist<Args...>
Expand Down
Loading

0 comments on commit 6e9c05d

Please sign in to comment.