🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Making a Data-Oriented ECS More Extensible

Started by
14 comments, last by DrDeath3191 4 years, 8 months ago

So your approach is similar to this one? Doesn't this version make it difficult to add and remove components at runtime? If your entire system is based on these archetypes, I can't imagine it being particularly tolerant of adding and removing data. That's a pretty big advantage to lose. And if entities share common functionality but are in different archetype groups, you would need to iterate on each group separately, which seems a little bit silly. Also, if multiple systems were to require access to the transform component, you would need to reference it by pointer anyway so that your data is accessed correctly, which is what I would have to do in my version anyway.

The relational database like system I have here handles these cases a bit more elegantly as far as I can see. If I need to add or remove functionality, I can add or remove components by just accessing a single table without having to shuffle around anything else. Yes I may have additional lookups to do, but entities with a component signature that match a system's needs will be operated on without hassle. Although I am not primarily concerned with multi-core performance at the moment, I can also easily divvy entities up for processing.

That's just my take. I am more than willing to have my perspective changed, though.

Advertisement

The value of adding and removing data depends on the application. In a game with rather complex entities (e.g. a FPS with complex animations, AI state, character conditions, etc.) it might be a good idea to let level designers add data to entities with ad-hoc scripts; in a simpler case hardcoding archetypes isn't a big deal (e.g. adding guided bullets in the same FPS, which are like other particles with the addition of a target, and whose ECS administrative requirements would be negligible compared to the new requirements of spatial indexing to find targets and simulation methods that alow aiming at correct positions)

Omae Wa Mou Shindeiru

Sorry for taking so long to get back to you guys; real life got in the way.

Adding and removing components is quite important in my case, as I intend to use them to implement character states and status effects. Ad-hoc scripts feels like a band-aid solution in comparison to the system I have now.

Speaking of the system I have now, I have found a way to store the different component types in the World; I went all-in on template metaprogramming and implemented a type list, I template the world class based on this type list's contents and store a tuple. All the component tables are in one place, they have the same access speed as though they were members of the World class (which is how I implemented it before), and thanks to using C++17 I can access the elements of the tuple by type, making it quite easy to use! And if I want to apply an operation to all tables (for example removing dead entities), I can just use std::apply. Compile times are probably going to get rough, but I knew that going in.

Here's the code for interested parties:


template<typename...> struct list_register;

template<template<typename...> class List, typename T>
struct list_register<List<void>, T> {
	typedef List<T> type;
};

template<template<typename...> class List, typename T, typename... Types>
struct list_register<List<Types...>, T> {
	typedef List<Types..., T> type;
};

template<class... Types>
struct type_list { typedef World_Templated<Types...> worldType; };

template<>
struct type_list<void> {};

#define REGISTERED_COMPONENTS type_list<void>
  
//example of registering a class to the list
typedef typename list_register<REGISTERED_COMPONENTS, TransformComponentTable>::type updated_list;
#undef REGISTERED_COMPONENTS
#define REGISTERED_COMPONENTS updated_list
  
//example of constructing the world with all registered tables
REGISTERED_COMPONENTS::worldType testWorld;

//very basic World class that just shows that the tables are contained in the tuple as desired, I plan on adding more functionality later
//but this is for demonstration purposes
template<typename... RegisteredComponentTables>
class World_Templated {

public:

	World_Templated() {};

	~World_Templated() {};

private:

	std::tuple<RegisteredComponentTables...> tables;

};

As you can see, it uses a macro. Not a huge fan, but it works. If anyone else has a better implementation of a type list I can register to, I'd love to know. Or if you have any more comments on the ECS design I'm working with in general.

I've been dragging my feet on implementing my change since it's a rather large refactoring, so an issue has only just now become apparent. My component tables are defined in separate header files. Since I'm attempting to use a macro to hold the necessary data types for the world template, it is vitally important that all classes I intend to register are so before the first actual use of the macro. Is it possible to enforce some sort of order on macro expansion so that all the types are registered before I need it? Is there another means of handing the registration without the use of a macro? Or is what I'm hoping to do impossible?

To be more clear, I mean is it possible without just including all my component table files in the World file? That would (theoretically) force all the component tables to be compiled first, thus the macro correctly redefined. However, it would be really nice if there were another way to control how that macro were expanded so that I wouldn't need to add those includes at all; less to write and the world would be more easily modified. Not to mention a potentially giant list of includes just looks very imposing.

This topic is closed to new replies.

Advertisement