3
\$\begingroup\$

I'm trying to create a node based scripting/computation system. I've come up with 2 solution that seems to work. One involves template, the other one inheritance. Basically what I want to achieve is: when a node is connected to another node, data computed from the previous node are transferred to the second one. Connection happens through a socket. The template solution uses a Socket Object to store the data, so it must be a template in order to store every type a user wants to store (Socket). Sockets are stored in every node in a map> So in order to initialize the Node, I have to use templates to initialize the variant. In the end, what it will happen is every time a new socket type is created, the user must add this type to every template instance.

The inheritance way, don't use socket at all. Instead, use a sequence of "if" to get the correct type based on socket name.

There is a third "ugly way". It's similar to the second. But there is a socket. A Socket interface without interface... Basically, The user inherits from the socket and use it to store the type it needs. The in the Node, in order to retrieve the data, it downcast to the Socket it should be, always based on its name. Since the node user designer should know what each Socket is.

So, in the end, I've come up with this list of pros and cons:

For Template

Pros:

  • Nice API
  • Simple design
  • It respects how my mind structured the library

Cons:

  • As soon the Socket types increase, it increases also the length of the template name. Plus you should remember every place where the template is instantiated and update each instance.

Inheritance

Pros:

  • Naive design

Cons:

  • I don't like it much, it's not how I thought it
  • A lot of Sockets mean a lot of "if" conditions
  • Node connection seems unnatural (but it's hidden to the user)

Template Version:

#include <map>
#include <string>
#include <variant>

template <typename T>
class Socket
 {
    std::string m_name;
    T mValue;

    public:
        Socket(const std::string &name){m_name = name;}
        void setValue(const T &t){mValue = t;}
        T getValue(const T &t);
        const std::string &getName(){return m_name;}
 };

template <typename ...SocketTypes>
class Node
 {
    using TSocket = std::variant<SocketTypes...>;
    public:       
        template <typename T>
        void addSocket(Socket<T> socket){
            mSockets.insert(std::pair<std::string, TSocket>(socket.getName(), socket));
        }

        template < typename Visitor>
        auto getSocket(const std::string &name, Visitor visitor){
            return std::visit(visitor, mSockets.find(name)->second);
        }
    private:
        std::map<std::string, TSocket> mSockets;

 };


using IntSocket =  Socket<int>;
using FloatSocket = Socket<float>;


struct DefaultVisitor {
    template <typename T>
    void operator ()(T &t) {;}
};

// Skip this... it was just to test if it works.
// connection basically gets the value from one socket and pass it to the other
int main() {

    Node<IntSocket, FloatSocket> data;
    IntSocket i(std::string("value"));
    i.setValue(10);
    data.addSocket(i);
    DefaultVisitor v;
    data.getSocket(std::string("value"), v);

}

Inheritance

#include <vector>
#include <string>

struct Node {
    int number;
    float fNumber;

    template <typename T>
    void inputConnect(std::string &socket, T value){
        if(socket == "r")
            number = value;
    }

    template <typename Object>
    void outputConnect(std::string socketIn, 
                       Object &obj, std::string socketOut)
    {
        if(socketIn == "r")
            obj.inputConnect(socketOut, number);
    }

};


void connect(Node &n1, std::string socket1, 
             Node &n2, std::string socket2){

    n1.outputConnect(socket1, n2, socket2);

}

int main() {
    Node n;
    Node n2;
    n.number = 10;

    connect(n, std::string("r"), n2, std::string("r"));
    return n2.number;
}

Help me decide wich is a better solution for my problem.

\$\endgroup\$

closed as unclear what you're asking by pacmaninbw, Toby Speight, яүυк, Edward, Snowbody Apr 20 at 3:33

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • \$\begingroup\$ Welcome to code review. It is unclear what you are asking for, are you asking for a code review or are you asking for opinion on which is better? Please take a look at how to ask a good question codereview.stackexchange.com/help/how-to-ask. \$\endgroup\$ – pacmaninbw Apr 7 at 10:56
  • \$\begingroup\$ done, I just need to know which is better, the code I've posted is some test code I've done on godbolt.org. Thanks for the tip \$\endgroup\$ – rebellion Apr 7 at 13:44
  • 1
    \$\begingroup\$ Welcome to Code Review! The current question title, which states your concerns about the code, is too general to be useful here. Please edit to the site standard, which is for the title to simply state the task accomplished by the code. Please see How to get the best value out of Code Review: Asking Questions for guidance on writing good question titles. \$\endgroup\$ – Toby Speight Apr 8 at 7:58
  • \$\begingroup\$ My code is so specific that's impossible to use a more "useful title" that can be useful for other people. It's not an algorithm that you can decide it's purpose, but this are two classes abstracting an idea: A Node and a Socket and how to build a framework around them after someone help me decide the best solution for my code. \$\endgroup\$ – rebellion Apr 8 at 12:58
  • 1
    \$\begingroup\$ @Graipher, Ok, I'll try to use it, even if I'm not sure if it really fit. Thanks for the tip! \$\endgroup\$ – rebellion Apr 12 at 12:46