Data Oriented Programming Practice - 1

The last post concluded with an example. I now will refactor it to satisfy the style that I have found to suit me well. In fact, since I stopped thinking in an object oriented way and started to explore this approach - we could call it data oriented programming - I think I became a more effective and also a happier programmer.

After we refactored it, actually using (i.e. calling) the code looks like this:

...

int main() {
    {
        using namespace ASCIIMapping;
        Data data{};
        data.params.width = 50;
        data.params.height = 20;
        run(data);
    }
}
...

And the refactoring is very simple, we just put everything into a namespace that describes what the code is doing - in our case ASCIIMapping. There we create a struct named Parameters. Then a struct named Data which has one variable of type Parameters and additionally all the data that we need for the code to run. And finally a function called run() that takes a reference to a Data instance:

#include <vector>
#include <array>
#include <cmath>

namespace ASCIIMapping {
    const size_t mpN = 6;
    struct Parameters {
        size_t width{};
        size_t height{};
        std::array<char,mpN> mapping = {' ','.',':','o','=','@'};
    };
    struct Data {
        Parameters params{};
        std::vector<float> inputMem{};
        std::vector<char> outputMem{};
    };
    void run(Data & on) {

        auto width = on.params.width;
        auto height = on.params.height;

        //input data
        on.inputMem.resize(width * height);
        //fill with some values
        for(int hI = 0; hI < height; hI++) {
            for (int wI = 0; wI < width; wI++) {
                on.inputMem[hI * width + wI] =
                std::sin(10.0f * 3.1415f * (float)wI / (float) width) *
                std::sin(12.0f * 3.1415f * (float)hI / (float) height);
            }
        }
        float * input = on.inputMem.data();

        //output data
        size_t outputSize = width * height + height;
        on.outputMem.resize(outputSize);
        char * output = on.outputMem.data();

        //map to ascii art
        for(int hI = 0; hI < height; hI++) {
            for(int wI = 0; wI < width; wI++) {
                auto inputIdx = hI * width + wI;
                auto outputIdx = hI * (width + 1) + wI;
                output[outputIdx] =
                on.params.mapping[
                    (
                        std::max(0.0f,
                        std::min(1.0f, input[inputIdx]))
                        + 0.05f
                    ) *
                    (mpN - 1)
                ];
            }
            output[hI * (width + 1) + width] = '\n';
        }


        //print
        fwrite(output, sizeof(char), outputSize, stdout);

    }
}

This approach might appear old-fashioned to some. I agree on that, maybe sometimes the old way is the better way?

Why call it data oriented ?

Because it emphasizes the separation between functions and the data they operate on. In object oriented programming, an object would encapsulate its data and offer methods to manipulate or handle the data. I have tried to use this approach for quite some time now, and I apparently just happened to mess up almost every time. The separation of data and functions clears the mind and helps me focus to actually solve the problem, instead of debating why the name of this class is inappropriate, or which design pattern to use in that case.

Note on namespaces

I have found namespaces to be very useful in this context: I put all the meaning of what the code does into the naming of the namespace. This method allows for having a struct simply called Data and one called Parameters. Namespaces allow us to set up the context. We can then use the created namespaces conveniently with the using namespace directive if we want to. But we can also be very specific and give the whole chain of namespaces to make sure the reader knows what we are talking about.

In the next post I would like to present more beneficial properties of the given approach.