graph-nodes/main.cpp

153 lines
4.4 KiB
C++

#include <iostream>
#include <list>
#include <optional>
#include <string>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
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<Connection>;
using ConnectionMap = std::unordered_map<std::string, std::list<Connection>>;
using SlotMap = std::unordered_map<std::string, std::type_index>;
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<std::type_index> out_type =
type_index_from_slot(name(), "output", outputs(), key_out);
if (!out_type) {
return;
}
std::optional<std::type_index> in_type =
type_index_from_slot(in.name(), "input", in.inputs(), key_in);
if (!in_type) {
return;
}
std::unordered_map<std::type_index, std::string> 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<std::type_index>
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<int *>(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<std::string *>(payload));
} else if (key == "in_int") {
this->in_int = *(reinterpret_cast<int *>(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;
}