1

If I have a graph which consists of nodes like this:

class Graph
{
    public:
        ...
        int32_t width();
        int32_t height();
        const Node * getNode(int32_t height, int32_t width);
    private:
        std::vector<std::vector<Node>> data;
}

where each node is a struct with multiple member variables:

struct Node
{
    int width, height;
    int a;  // should be in the top left corner when rendering rectengular node;
    int b;  // top right corner;
    int c;  // bottom left corner;
    int d;  // bottom right corner;
    int e;  // center of the node;
}

Should the renderer know how to render each node or is it nodes job to know how to draw itself?

For example, here is one way where the renderer does all the job:

class Renderer
{
    public:
        void render(const Graph & graph)
        {
            for(int32_t height = 0; height < graph.height(); ++height)
            {
                for(int32_t width = 0; width < graph.width (); ++width )
                {
                    // Drawing rectangle which represents the node;
                    drawNodeRect(getPositionOfNodeRect());
                    // Calculating positions of each member of Node;
                    auto posOfA = doSomeCalculationsForA();
                    ...
                    auto posOfE = doSomeCalculationsForE();
                    // Drawing each member;
                    renderNumber(posOfA, graph.getNode(width, height));
                    ...
                    renderNumber(posOfE, graph.getNode(width, height));
                }
            }
        }  
}

Here what it would look like in the case where the node knows how draw itself:

struct Node
{
    int a;  // should be in the top left corner when rendering rectengular node;
    ...
    void render()
    {
        // Drawing rectangle which represents the node;
        drawNodeRect(getPositionOfNodeRect());
        // Calculating positions of each member of Node;
        auto posOfA = doSomeCalculationsForA();
        ...
        auto posOfE = doSomeCalculationsForE();
        // Drawing each member;
        renderNumber(posOfA, graph.getNode(width, height));
        ...
        renderNumber(posOfE, graph.getNode(width, height));
    }
}

class Renderer
{
    public:
        void render(const Graph & graph)
        {
            for(int32_t height = 0; height < graph.height(); ++height)
            {
                for(int32_t width = 0; width < graph.width (); ++width )
                {
                    graph.getNode(width, height).render();
                }
            }
        }  
}

Maybe to push it further, the graph could also know how to draw itself:

class Graph
{
    public:
        ...
        int32_t width();
        int32_t height();
        const Node * getNode(int32_t height, int32_t width);
        void render()
        {
            for(int32_t height = 0; height < graph.height(); ++height)
            {
                for(int32_t width = 0; width < graph.width (); ++width )
                {
                    graph.getNode(width, height).render();
                }
            }
        }
    private:
        std::vector<std::vector<Node>> data;
}

class Renderer
{
    public:
        void render(const Graph & graph)
        {
            renderGraph(graph);
        }  
}

So basically my question again, Should every entity know how to render itself or is it renderer's job to figure out how to draw stuff? For better understanding but not really necessary, I am using SFML, where you can inherit from sf::Drawable which allows you to overload a function to make an object Drawable.

4
  • 1
  • Does your rendering system prefer big batches? GPUs like big batches (and they cannot lie). If you need to make a big array of all nodes, then treating it as a per-node operation could be inefficient.
    – user253751
    Mar 31, 2022 at 9:37
  • @user253751 I am not that familiar with low level rendering, I am currently using SFML, and each frame, each node calls the window.draw(...) method multiple times(2 rects each node and 3 text), also each frame the method window.display(...) is called once. I am not sure how I can optimize that by using bigger patches. I mean I could save some space by only using one base drawable node and pass every node to it so that it sets up all texts and colors and shapes.
    – a a
    Mar 31, 2022 at 10:02
  • so your rendering system does not do that, so ignore my comment
    – user253751
    Mar 31, 2022 at 10:10

2 Answers 2

2

The answer depends very much on the node's purpose.

If the node is a graphical entity solely meant for drawing (e.g. a geometric shape), then there is no problem for the object to draw itself like you showed.
However, you'd do well to inject a rendering object for the low-level drawing: the node would tell the renderer how to draw it, and if you'd want, you could draw the graph on several windows with different rendering parameters (which you can't in your solution).

But if the node has another prime purpose (e.g. a domain entity such as a city in a GPS), you should consider a proper separation of concerns: the domain object should keep domain knowledge (e.g. latitude and longitude of the city) whereas another object should take care of the rendering. This is basically the concept behind MVC and many other architectural patterns. Letting thh object render itself would not allow to consider different ways of drawing the same graph (e.g. city marking on a satellite photo vs city point on an schematic map vs city name in a list).

0

None of these know how to render themselves. Why? Because none of them implement toString(). That’s the rendering I happen to need.

There’s more than one way to render. And so there is more then one way to present the data in your entities model. When an entity knows how to present itself that makes things easier, so long as you only need that one way to render.

For any long lived project it is wise to make choices that keep the need for redesign to a minimum in the face of change. So long as those choices don’t encumber the project to the point that it doesn’t live that long.

If you have a presentation layer you have a place to put code that doesn’t know everything about the view or everything about the data model. Just enough of each to do a little translating. There are times when that kind of flexibility is worth the trouble.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.