#include #include #include #include #include #include #include struct Slot { void *payload; std::type_info type_info; }; class Node; struct Connection { Node *src = nullptr; std::string src_key; Node *dst = nullptr; std::string dst_key; }; using ConnectionList = std::list; using ConnectionMap = std::unordered_map>; using SlotMap = std::unordered_map; class Node { public: virtual const char *name() const = 0; virtual const SlotMap &inputs() const = 0; virtual const SlotMap &outputs() const = 0; ConnectionMap in_connections_; ConnectionMap out_connections_; void attach(const std::string &key_out, Node &in, const std::string &key_in) { std::optional out_type = type_index_from_slot(name(), "output", outputs(), key_out); if (!out_type) { return; } std::optional in_type = type_index_from_slot(in.name(), "input", in.inputs(), key_in); if (!in_type) { return; } std::unordered_map type_names = { {typeid(int), "int"}, {typeid(double), "double"}, {typeid(std::string), "std::string"}}; if (out_type->hash_code() != in_type->hash_code()) { std::cerr << "Incompatible type: \"" << type_names[*out_type] << "\" (out) != \"" << type_names[*in_type] << "\" (in)\n"; return; } Connection c{ .src = this, .src_key = key_out, .dst = &in, .dst_key = key_in}; // TODO: check for duplicates in.in_connections_[key_in].push_back(c); out_connections_[key_out].push_back(c); }; void fire(const std::string &key, void *payload) const { const auto &connections = out_connections_.at(key); for (auto &c : connections) { c.dst->incoming_payload(c.dst_key, payload); } } virtual void run() = 0; virtual void incoming_payload(const std::string &key, void *payload) = 0; private: std::optional type_index_from_slot(const char *node_name, const char *slot_name, const SlotMap &slots, const std::string &key) const { try { return slots.at(key); } catch (std::out_of_range) { std::cerr << "Node \"" << node_name << "\" does not have an " << slot_name << " input slot named \"" << key << "\"\n"; return {}; } }; }; class Foo : public Node { public: virtual const char *name() const override { return "Foo"; }; virtual const SlotMap &inputs() const override { return inputs_; }; virtual const SlotMap &outputs() const override { return outputs_; }; virtual void run() override { fire("str", (void *)"kikoo les pipous"); }; virtual void incoming_payload(const std::string &key, void *payload) override { if (key == "integer") { this->integer = *(reinterpret_cast(payload)); } else { std::cerr << "unsupported incoming payload: " << key << "\n"; } }; private: static inline const SlotMap inputs_ = {{"integer", typeid(int)}}; static inline const SlotMap outputs_ = {{"str", typeid(std::string)}}; int integer; }; class Bar : public Node { public: virtual const char *name() const override { return "Bar"; }; virtual const SlotMap &inputs() const override { return inputs_; }; virtual const SlotMap &outputs() const override { return outputs_; }; virtual void run() override { std::cout << in_str << "\n"; }; virtual void incoming_payload(const std::string &key, void *payload) override { if (key == "in_str") { this->in_str = *(reinterpret_cast(payload)); } else if (key == "in_int") { this->in_int = *(reinterpret_cast(payload)); } else { std::cerr << "unsupported incoming payload: " << key << "\n"; } }; private: static inline const SlotMap inputs_ = {{"in_str", typeid(std::string)}, {"in_int", typeid(int)}}; static inline const SlotMap outputs_ = {{"final", typeid(std::string)}}; std::string in_str; int in_int; }; int main(int argc, char *argv[]) { auto root = Foo(); auto end = Bar(); root.attach("invalid", end, "xxx"); root.attach("str", end, "in_int"); root.attach("str", end, "in_str"); root.run(); return 0; }