Help with using a class Template for adding generic 'Systems'

Started by
3 comments, last by Alberth 7 years, 4 months ago

Hi All,

I am trying to make a component based gameObject engine. In this, my gameObjects are created, and then components (like TransformComponent and RenderComponent) are added to these gameObjects.

I want to now add a System.. system, which picks up components and acts on the relevant ones. A bit like ECS, but I am not using any framework, and want to code this all myself to learn. I also don't plan on the 'Entity'.

I want my game to have a SystemManager, which stores all of my systems, and will be iterated through at update() time. I thought this is a good place to use templates, because I will be adding lots of different Systems into my systemManager class. This is the first time I have used templates and I am stuck. Below is an example of my attempt.


#include <vector>
#include <iostream>
#include <memory>
#include <string>

using namespace std;

class System
{
public:
	System() {};
	~System() {};

	virtual void update(float dt) = 0;
};

class MovementSystem : public System
{
public:
	MovementSystem() {};
	~MovementSystem() {};

	void update(float dt) {};
};

class SystemManager
{
private:
	std::vector<std::shared_ptr<System>> m_systems;

public:
	SystemManager() {};
	~SystemManager() {};

	template <class S>
	void add(std::shared_ptr<S> system)
	{
		m_systems.insert(system);
	}
};

int main()
{
	SystemManager systems;	
	systems.add<MovementSystem>();
	getchar();
	return 0;
}

I get errors 'error C2672: 'SystemManager::add': no matching overloaded function found' and 'error C2780: 'void SystemManager::add(std::shared_ptr<_Ty>)': expects 1 arguments - 0 provided'

Firstly what am I doing wrong? Any guidance on how I am going about things or general advice on this design is very much appreciated.

Thanks

Advertisement

template <class S>
    void add(std::shared_ptr<S> system)
    {
        m_systems.insert(system);
    }

This is a template function taking a 'system' parameter, which is has type 'std::shared_ptr<S>'.

The call


systems.add<MovementSystem>()

Has no argument between the "()" to use as 'system' parameter.

One option is to give it a system, like


systems.add<MovementSystem>(std::allocate_shared<MovementSystem>()); // Probably one of the template type of 'add' can be removed.

or make a system object inside the 'add' method.

A few notes:

- no need for "using namespace std", you are writing it anyway(which is a good thing)

- virtual destructor in System

- At some point you will want specify the order in which the systems are updated, currently you can control this only by the order in which they are added

- Its hard to give advice without seeing how you will be handling the components

Alberth: Thanks for your reply. Spelling out what my templates actually are asking for helps... I find them very confusing.


template <class S>
    void add(std::shared_ptr<S> system)
    {
        m_systems.insert(system);
    }

Please check my understanding:

So when I initiate this template, I have to make sure I put a 'Type' inside the angle brackets (< >), but I also need the parameter... a bit like a normal function? So it would be equivalent to me writing (e.g. this is what it would generate):


    void add(std::shared_ptr<MovementSystem> movementSystem)
    {
        m_systems.insert(movementSystem);
    }

I think I can see one problem here, on this example I am receiving the error "no instance of overloaded function "std::vector<_Ty, _Alloc>::insert [with _Ty=std::shared_ptr<System>, _Alloc=std::allocator<std::shared_ptr<System>>]" matches the argument list". I was using insert() wrongly, so I have changed it to push_back()... (I will worry about the fine details later.. just trying to get my head around templates for now hehe...)

When I try your example using std::allocate_shared I receive the error: "no instance of function template "std::allocate_shared" matches the argument list". I will be honest I am a bit lost as to whats going on here.

Am I using templates the right way for this? My idea is to create all my systems (by inheriting from the System base class) then just add them all like:


    systems.add<MovementSystem>();
    systems.add<CollisionSystem>();
    systems.add<AiControlSystem>();//etc etc

(Note this is similar to how the ECS framework entitiyx works)

BloodyEpi: Thanks for your reply.

'no need for "using namespace std", you are writing it anyway(which is a good thing)' I usually use using namespace std just in little demos like this but not in any real project. I forgot I was using both haha...

'virtual destructor in System': Thanks for picking this up. Just to clear my understanding, this will mean that my systems instances are cleaned up when my systems end? (e.g. through changing a level, or ending the game?)

'At some point you will want specify the order in which the systems are updated, currently you can control this only by the order in which they are added' I will eventually get to this, right now just trying to get the basic idea working.

'Its hard to give advice without seeing how you will be handling the components': For a very first build I will be ripping the components I need out like this:


void MovementSystem::update(float dt)
{
    TransformComponent* playerTransformComponent = static_cast<TransformComponent*>(m_gameObjects[0]->GetComponent(COMPONENT_TRANSFORM).get());
    auto playerPosX = playerTransformComponent->position.x;
    auto playerPosY = playerTransformComponent->position.y;
}

(just a little test, hence I am not looping and just using m_gameObjects[0])

Thanks all

Alberth: Thanks for your reply. Spelling out what my templates actually are asking for helps... I find them very confusing.


template <class S>
void add(std::shared_ptr<S> system)
{
    m_systems.insert(system);
}

They are a completely separate layer on top of normal C++.

That is,


void add(std::shared_ptr<S> system)
{
    m_systems.insert(system);
}

Is a perfectly normal C++ function (as you deduced too), where S is an existing class, fully defined etc. So here you must obey all the normal rules you have elsewhere too, for an existing class S used in this code. So if you don't supply an argument to 'add', or use the 'insert' wrong, you get an error as usual.

The 'template' line forms a bridge between the 'S' you can assume to exist here, and a real class that you have. That can happen in 2 forms.

The first form is explicitly saying what 'S' is by adding "<MovementSystem>" behind a function name, like


add<MovementSystem>(..)

A somewhat weird place, but on the other hand, it has to go somewhere, and this is a pretty good place, as it reads nicely as "this is 'add' for class MovementSystem".

It does sort-of a local "typedef MovementSystem S;" for this one call.

The second form is implicitly like


std::shared_ptr<MovementSystem> p = std::make_shared<MovementSystem>(); // Probably incorrect, but it should make a 'p' value with the right type.
add(p);

You just call the function, and let the type system deduce that S of the 'add' is in fact 'MovementSystem' here.

The biggest 'weird' thing here is that an error can now also be caused by template parameters. As a result, errors with templates also dump all info on resolving their type parameters, which takes some time getting used to. That is what your "no instance of overloaded function "std::vector<_Ty, _Alloc>::insert [with _Ty=std::shared_ptr<System>, _Alloc=std::allocator<std::shared_ptr<System>>]" matches the argument list" error says. It dumps all the template parameters it can find with the type it believes should be used there. Since we normally never bother with allocators for the containers, it looks weird.

So when I initiate this template, I have to make sure I put a 'Type' inside the angle brackets (< >), but I also need the parameter... a bit like a normal function? So it would be equivalent to me writing (e.g. this is what it would generate):

Yep, plain simple normal function, just replace S by MovementSystem.

When I try your example using std::allocate_shared I receive the error: "no instance of function template "std::allocate_shared" matches the argument list". I will be honest I am a bit lost as to whats going on here.
Maybe you use a different C++ version, I copied it from the cppreference site, where it said c++11 iirc, so it's quite new. Maybe your compiler doesn't do that, or you didn't say you wanted c++11 or so.

It's however also possible I am wrong somewhere, I don't write enough C++ anymore :(

Am I using templates the right way for this? My idea is to create all my systems (by inheriting from the System base class) then just add them all like:
You have to fix the 'add' parameter for that to work. Unless you have an 'add' without parameter in mind here.

(Note this is similar to how the ECS framework entitiyx works)
I see lots of people here making these complicated ECS systems for some reason. I have never had the need for them yet, and I don't understand why everybody makes these highly complicated things.

Also, I believe doing what some other system does is not a good enough reason to pick that solution too. Your aim is probably different from their aim. Feel free to ignore me though :)

'virtual destructor in System': Thanks for picking this up. Just to clear my understanding, this will mean that my systems instances are cleaned up when my systems end?
It makes sure that "delete myMovementSystem;" will delete all subclasses of System in the proper order. Without it, it would just perform the destructor of MovementSystem, and nothing else.

This topic is closed to new replies.

Advertisement