Hello everyone.
I've been working on my game for a while now, and I am beginning to dislike my means of adding new component tables and such to my game. My components at the moment are hard-coded classes formatted as structures of arrays; you can basically think of it as a relational database. Each variable of the component has its own dedicated vector in the table, meaning I can loop through them all in a cache-friendly manner.
When it comes to the function in-game, it works nicely. Development, on the other hand, is a little annoying. As I am trying to remain data-oriented, I am avoiding inheritance as much as possible. So each component table is an entirely standalone class. However, the underlying idea of the component table is the same; some tables do have some other tricks (which I will cover later), but the general idea is similar. So I essentially have to copy a lot of my code, add it to my giant World class that has all the component tables, make sure its garbage collection gets called, etc. I feel like this should be made easier. Especially if I wanted to support modding in the future.
I do have an idea as to how to possibly improve things, but I figured I should bounce it off people who might have more experience in this field. Essentially my plan is to replace all these component table classes I've made with a single class that stores a contiguous array of bytes with some metadata. So if I have a component table that was modeled something like this:
//...
std::vector<Entity> entities;
std::vector<vec3> positions;
std::vector<quat> rotations;
std::vector<vec3> scales;
//...
I would instead model it something like this:
//...
std::vector<uint8_t> data;
size_t entitiesIndex;
size_t positionsIndex;
size_t rotationsIndex;
//...
Essentially, I would flatten my structure to a single array, with indexes into that array indicating where each variable can be accessed. Why would this confer any sort of advantage? Because I could then theoretically create a file format that creates these tables based on my specifications. All data fundamentally can be represented as a contiguous array of bytes; all I need to know is the size of the datatype and how many are present in order to get those offsets to point to the correct memory. It would not need any sort of inheritance based structure; its an array of bytes, and an array of offsets. I can just create them and store them in an array in the World class. If I want to get a specific table, I can just index into that array. If I want to use the data in that table, I can just do a memcpy or a reinterpret_cast and process as desired.
I've been mulling over this idea for a little while, but I still have some reservations. This has to do with the tricks I mentioned earlier. I don't use enums or bools in the component tables. I use the indices of the components to indicate what their condition is (if the index is less than x, the condition is false, for example). This allows contiguous processing with minimal branching. I would need to make the format aware of this sort of trick. Theoretically, not that hard; I just need to indicate a number subdivisions for the component. However, if I were to create an entity in this fashion, I would need to be aware of runtime information in order to put it in the right place. I suppose I could have some intermediary tables that I put those sorts of components into and then handle it as a follow-up step, but that just seems sloppy. The way I've handled that runtime problem in the past was to have a series of factory classes for each component type that would specifically be passed in that information. I am also hoping to get rid of these; if I am thinking of completely redoing my ECS, this would be the time to lose them.
I've yet to think much on how systems would be implemented better; Right now they are implemented as classes that have references to the component tables they are interested in, and a single method. I suppose I could just pass the World a series of function pointers and have them be executed in some given order. But seeing as I am going for something data-oriented, function pointers seem a bit off the path. But maybe I'm being silly.
Has anybody here implemented an ECS in a similar fashion? Is it worth my time to try to rewrite my entire ECS structure in this way? Is what I want even feasible or wise? How would you tackle this problem? I hope I expressed myself clearly enough; the thought of this has wracked my brain for a bit, so I might be a little caught in my own world here.
Thanks for the help.