Does adding Delegates/Function pointers to an entity break ECS ideology?

Started by
9 comments, last by DemonDar 7 years, 9 months ago

If I don't use function pointers what is the alternative? Subclassess of components?

Advertisement

What ECS ideology? There's no one correct way to build an entity system.

It's unclear what you're quite asking about or what you consider to be the potential problem here; it would help if you could clarify. That said, if it works for you and solves a problem in your system, that's a good thing, so you should probably use it rather than worry about some sort of ethereal purism.

If I don't use function pointers what is the alternative?


IDs (integers, enums, types, etc) which somehow map to functions or the behavior you want to change (via switch statements, lookup array, etc).

I'd say "pure ECS ideology" means: Entity doesn't has any logic, and components either. Logic goes into your systems. Entities are IDs, components are data, systems have all the logic. End of story.

So you wouldn't do any of what you mentioned (neither adding functions to an entity nor adding functions to a component).

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

If the OP is talking about function pointers (e.g. to offer pluggable functionality in a component), I don't see anything necessarily conceptually wrong with that. But it's hard to know without knowing your scenario. Technically, that's not code in a component, it's exposing a property of a component that a system can then invoke (whether it's a function ptr, or some enum or numerical identifier that maps to a function).

If you ever want to be able to serialize your components (e.g. for save games, or editor functionality), then it would easier if you used some numerical identifier for the function.

As for your mention of delegates, it's hard to know how you're intending to use them. But it seems like a potential code smell, and there's probably a better way to do what you want that fits more with "ECS ideology", so to speak.

No one can really answer your question without knowing your scenario. Describe what you're trying to accomplish, rather than a particular implementation.

Apply anything with caution.

It should suit your needs, if you don't know them, try to think of what you need in your system.

Because of the popularity of some patterns, there are best practices and guidelines, but again, they are just guidelines.

Function pointers should not be a problem, depending on your actual need.

You could describe a pointer as a component to get data, if it does a task that a system should do, it breaks the ECS pattern.

+ It is not data-friendly. Since you can't serialize it properly.

-----

I don't see why you need to store functino pointers.

All you need to do is describe an interface that does something:

class ISomething{ virtual void DoSomething()=0; }

Then it's basic composition and strategy.

If I don't use function pointers what is the alternative? Subclassess of components?

Like the others, I'm not sure what precisely you are talking about.

My guess is, you're saying that you have a bunch of entities that all have SpecialComponent, and that sometimes SpecialComponent needs to do BehaviorA, and other times SpecialComponent needs to do BehaviorB.

If so, one of the key things of some ECS architecture (which vary incredibly widely) is that components don't have logic, they are just data, and the systems handle all the logic.

This means, I'd write the solution like this:


class SpecialComponentSystem
{
private:
   container<SpecialComponent> components;
   
public:
   void ProcessEntities()
   {
       //Process all BehaviorA components...
       for(size_t i = startOfBehaviorAComponents; i < endOfBehaviorAComponents; ++i) //(Actually I'd write the for() statement itself differently too =P)
       {
           components[i].value = (x + q * 3.57f);
           components[i].kittens = (Moar / meow);
       }

       //Process all BehaviorB components...
       for(size_t i = startOfBehaviorBComponents; i < endOfBehaviorBComponents; ++i)
       {
           components[i].value = x;
           components[i].kittens = None;
       }
   }
}

I'd guarantee ahead of time (without having to sort every frame) that all components are grouped in contiguous memory by their behavior (i.e. all BehaviorA's in a row, then all BehaviorB's). This can occur, because each System can store its components in a different order: each System has control (hidden away, unexposed) of how its entities are stored. This means the System can reorder the components differently then the order of EntityIDs.

It can even store them like this:


class SpecialComponentSystem
{
private: //Privacy is important for System-specific memory layout of components.
   std::vector<SpecialComponent> componentsWithBehaviorA;
   std::vector<SpecialComponent> componentsWithBehaviorB;
};

Essentially, you want to have short compact code, without zero branching, and to reduce function call overhead and other operations as much as possible (ofcourse balancing that 'requirement' with all the other requirements of programming, like DRY, single-responsibility, and clean legible ease-to-maintain-and-expand code).

Depending on what you are doing: no function calls, zero branching, and so on, isn't always possible.

For things like game-related logic, it's understandable that things are going to branch alot more, and probably make calls into scripts or other pluggable behavior. In that case, one solution is just to reconsider how often you need to run that particular logic.
Maybe scripted logic only needs to be run 10 times a second, and maybe pathfinding only needs to be updated once a second.

Not every subsystem needs to be called every tick.

Sometimes if you twist the way you look at your entities, looking at them as a collection of the same data, you can more easily spot redundant processing.
And ofcourse, don't optimize prematurely, unless you're pretty confident the optimization is actually needed - and even then, profile. :)

I assume you want to treat your entities in a similar way to javascript objects where you can just connect up functions to them that handle their specific functionality.

The real problem as others have identified is that of when it comes to persist the objects as function pointers/delegates will break that functionality. You will not be able to persist the delegates and hence have to work out what ones to plug in as you load the data again.

As long as you go in with this knowledge and understand the ramifications of your choices then you will be fine. That said I prefer data separate from the operations given the choice :)

^What Josh said. ECS is not one particular pattern.

I'd say "pure ECS ideology" means: Entity doesn't has any logic, and components either. Logic goes into your systems. Entities are IDs, components are data, systems have all the logic. End of story.

So you wouldn't do any of what you mentioned (neither adding functions to an entity nor adding functions to a component).

Well a delegate is data, so that fits your description. A "CallDelegateOnConditionMet" system would have components containing game-state conditions to check, and delegates for the system to call when those conditions have been met :lol:

Well a delegate is data, so that fits your description. A "CallDelegateOnConditionMet" system would have components containing game-state conditions to check, and delegates for the system to call when those conditions have been met :lol:

And the next logical step is to have a "VTable" component to stuff your function pointers in, then re-implement inheritance through it :P

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

This topic is closed to new replies.

Advertisement