I have been working on my own project for a while, it's an OOP framework around SDL2. I've been following a shallow depth form of "everything is an object". I have some experience with java/android programming, so it made sense to me to go that route at the time.
I've heard many developers are very much against this approach, but what are the key arguments against it?
My current framework allows for dynamicaly loaded plugin-objects (drag and drop a .dll or .so on the window and a new boss fight is initiated, or a new level is added to the game), is there a "better solution" that would allow that as well?
To put it in the simplest terms possible, unless you're writing a AAA title working on the bleeding edge of technology, don't worry too much about it. Design your code optimizing development effort. Your code will run fine and you won't create additional work for yourself.
OOP is out of fashion in gamedev circles in favor of data-driven design because the latter leads to more efficient hardware utilization, particularly when doing things like applying a function to a large homogeneous collection. Nowadays performance is so abundant that for the majority of games, particular small ones, it just doesn't make sense to do this.
OSO (objects for the sake of objects) is not optimal. Some things (int, double, etc) are not and should not be objects nor objectified by the coder. Some things, like common math routines, should also not be objects. High level things should be mostly objects, yes. But not everything ... when you find yourself making pointless objects with static methods and no good explain as to why it is in a class, you have found a candidate for a simple C style procedure. To put it another way, at first if I ask you if modern c++ has a lot of free standing functions that you use a lot. Like in <algorithm>, where it just takes parameters, but no object needed? The temptation is to think that like sin() etc the majority are just cpu instructions remapped, but that is not the case, we have gcd, we have sort, and many other complex programs that are just functions. You will write some of your own of that type now and again, and I recommend avoiding the temptation to stuff into a class for no reason.
As for your question, gamers want to modify modern games, so a script and common graphics file format (even animations or whatnot) called by the script will open the game beyond fully skilled coders who know how to do a DLL. If you can make an easier interface for the user, do so. If there are reasons not to, that is ok too, but the more open you make it the more use it will see.
@jonnin, I think you completely missed the point.
It's not about turning an int into an object for no good reason.
It's that a vector of objects is heterogeneous.
A homogeneous array of the same basic type can be processed more efficiently.
"Bleeding-edge" games use this technique, making the code uglier, but faster.
Ah, no I did not get THERE from that question.
but we always did it that way, sort of.
more like
vector<double> stuff(#of items* number of data points)
where stuf[0] is x1, stuff[1] is y, stuff[2] is z, stuff[3] is heading, ... pitch .. roll ... stuff[7] is the second entity, ... but it depends on how you need to access it somewhat.
vector<double> stuff(#of items* number of data points)
where stuf[0] is x1, stuff[1] is y, stuff[2] is z, stuff[3] is heading, ... pitch .. roll ... stuff[7] is the second entity, ...
That's actually the worst of both worlds, because you don't have structure and you also don't have efficient access of members (assuming you have a good CPU cache). What you want is to distribute your objects in a "planar" layout.
Interlaced: stuff[object_index * object_size + member_offset] is object.member
Planar: stuff[member_offset * object_count + object_index] is object.member
What am I missing here... say you need distance ... you need x,y,z at one iteration, so double array [0], [1], [2] (followed by 3,4,5 and 6,7,8 and so on) seems pretty cache friendly to me? How is array1[0], array2[0], and array3[0] better cached for that? I see it if you only need 1 of the 3 at a time, but you rarely do?
I also get how if you do it object wise with different types of variables etc it works. I dont see how just a flat access of an array of double is worse though?
If you need to update a specific member on all objects, storing them in a planar layout means you're not loading the entire object into the cache, but only that one member. Your cache looks like this:
[object[0].heading][object[1].heading][object[2].heading]
[object[3].heading][object[4].heading][object[5].heading]
[object[6].heading][object[7].heading][object[8].heading]
...
instead of looking like this:
[object[0].heading][object[0].pitch][object[0].roll]
[object[1].heading][object[1].pitch][object[1].roll]
[object[2].heading][object[2].pitch][object[2].roll]
...
You're not wasting cache space on data you don't care about (the CPU will read-ahead that data assuming you're going to need it). Incidentally, this is exactly how RDBMSs lay out their on-disk format, for the same reason. They store individual columns as contiguous arrays to optimize the disk cache, under the assumption that most queries or updates don't need entire rows.
To Helios: Thank you. So the idea is that there are a lot of functions that are basically loops in a game. Often these loops only deal with one aspect of an object, so passing the entire object is a waste of time and fills the cache memory too fast.
Would passing a vector of pointers to all my objects still place their entirety into cache?
Edit: A vector is now required to be contiguous in memory, so a vector of all X values is definitely faster and more compact than calling all getters on a vector of pointers to objects since those would not be contiguous (being separated by the size of each object).
To Jonnin, I can see how passing data (perhaps an XML file and providing a corresponding mod-tool) rather than full dynamic libs could be an easier way for users to modify/introduce levels/enemies/etc. It would mean that the original program might be a bit more rigid on what is allowed in a mod file, but that would also add to the program security as I wouldn't be handing over control to the incoming file. I like it.
If you need to update a specific member on all objects
Thank you for explaining, I see what I was missing ^^.
and extending it, I think this way is also keeping the statement side cache more optimal, if you are writing loops to only touch one of thing at a time as much as you can?