Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Feather Kit - A C++ Game Framework  (Read 46120 times)

0 Members and 1 Guest are viewing this topic.

select_this

  • Full Member
  • ***
  • Posts: 130
  • Current mood: just ate a pinecone
    • View Profile
    • darrenferrie.com
Re: Feather Kit - A C++ Game Framework
« Reply #30 on: February 24, 2014, 01:35:31 pm »
May I ask what your intention was with the following snippet?:

Message(typename std::enable_if<sizeof...(DataTypes) >= 1>) {}

My understanding of template metaprogramming is a little sketchy, so I might be barking up the wrong tree here, but to me it seems like it's a constructor with an argument of type std::enable_if<cond> (struct), which would be pretty much impossible to call and wouldn't do anything even if it was called.
Follow me on Twitter, why don'tcha? @select_this

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #31 on: February 24, 2014, 02:47:10 pm »
May I ask what your intention was with the following snippet?:

Message(typename std::enable_if<sizeof...(DataTypes) >= 1>) {}

My understanding of template metaprogramming is a little sketchy, so I might be barking up the wrong tree here, but to me it seems like it's a constructor with an argument of type std::enable_if<cond> (struct), which would be pretty much impossible to call and wouldn't do anything even if it was called.

Yeah, the enable_if function can be quite confusing. I don't have the best understanding of it myself since I haven't done super much meta programming but I'll explain the best I can and I'll start with the purpose.

Okay, if we start with this:
template<class tag, typename... DataTypes>
    struct Message
    {
        Message() {}
        Message(DataTypes... data) : mData(data...) { }
        Message(const std::tuple<DataTypes...>& data) : mData(data) { }
        std::tuple<DataTypes...> mData;
    };
 

Then we have 3 ways of constructing a message. For example, with the instantiation Message<int, int> we can do:

1. Construct without parameters, making a default message: Message<tag_type, int, int>()
2. Construct with parameters that get directly packed in the tuple:   Message<tag_type, int, int>(34, 45)
3. Construct with another tuple: Message<tag_type, int, int>(otherMessage.mData)

This is good, but it has an unexpected flaw. If you want a message carrying no data, say you would have a ShutDownMessage = Message<tag_type>, then since the variadic parameter DataTypes would not contain anything, both the first and second constructor would resolve to Message<tag_type>() and compilation would fail with a redefinition error.

In other words, it is impossible to have a zero-parameter message. So to solve this problem, we want to have constructor number 1 if and only if DataTypes contain one or more types. That is what the enable_if function lets us do.

How this works is actually by kind of exploiting the behaviour of the compiler using a principle called SFINAE (substitution failure is not an error) which works on the principle that if the compiler cannot find a fitting template instantiation, then it silently ignore that or something along those lines. And that behaviour is wrapped in the enable_if template so that you can enable/disable overloads depending on compile time conditions.

If that sounded messy it is probably because I don't fully understand it myself. I read about it for real just the other day and I still haven't grasped it fully, haha. :D But hopefully it gave some insight anyway.
« Last Edit: February 24, 2014, 02:49:48 pm by therocode »

select_this

  • Full Member
  • ***
  • Posts: 130
  • Current mood: just ate a pinecone
    • View Profile
    • darrenferrie.com
Re: Feather Kit - A C++ Game Framework
« Reply #32 on: February 24, 2014, 03:01:38 pm »
Ah okay, that makes sense, thanks. I must admit I've never seen enable_if used in that way before, only when used in conjunction with a template function:

template <typename T = std::tuple<DATATYPES...>>
Message(typename std::enable_if<std::tuple_size<T>() >= 1, dummy>::type = dummy{}) {
}

Your version seems considerably more concise than the syntax I'm familiar with.
Follow me on Twitter, why don'tcha? @select_this

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Feather Kit - A C++ Game Framework
« Reply #33 on: February 24, 2014, 08:50:41 pm »
May I ask what your intention was with the following snippet?:
Message(typename std::enable_if<sizeof...(DataTypes) >= 1>) {}
My understanding of template metaprogramming is a little sketchy, so I might be barking up the wrong tree here, but to me it seems like it's a constructor with an argument of type std::enable_if<cond> (struct), which would be pretty much impossible to call and wouldn't do anything even if it was called.
The objection is justified; SFINAE won't work like this. There are several problems with that declaration:
  • As stated, there is always one parameter in this constructor, so this overload will never be a default constructor. The Message class would simply not be default-constructible for multiple variadic arguments.
  • SFINAE is an exploit of overload resolution, where only the signatures of the functions are relevant. Once you call the constructor, the class template parameters are already determined and don't affect overload resolution. That is why SFINAE requires function template parameters, on which the condition depends.
  • If I remember correctly, it's not allowed to use typename where there is no dependent type involved (you're not writing ::type). Compilers may be not so strict here, or the rules may have changed recently (possibly to account for template aliases).
  • That's okay in this case, but keep in mind that when using < or > operators in template argument lists, you need extra parenthesis.
Usually, enable_if is employed in one of the following ways:
// Parameter: Use default parameter of type void*
template <typename T>
R function(typename std::enable_if<cond>::type* = nullptr);

// Return value: Use 2nd template paramater of enable_if
template <typename T>
typename std::enable_if<cond, R>::type function();

// Template default parameter
template <typename T, typename Unused = typename std::enable_if<cond>::type>
R function();

Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #34 on: February 25, 2014, 06:21:25 am »
Ah, yeah you're actually right, it doesn't work! :D

I assumed that it worked since the compiler didn't complain, but now when I tried using the empty constructor, it doesn't work indeed. That's what you get when you lack proper understanding I suppose.

\
Usually, enable_if is employed in one of the following ways:
// Parameter: Use default parameter of type void*
template <typename T>
R function(typename std::enable_if<cond>::type* = nullptr);

// Return value: Use 2nd template paramater of enable_if
template <typename T>
typename std::enable_if<cond, R>::type function();

// Template default parameter
template <typename T, typename Unused = typename std::enable_if<cond>::type>
R function();

I tried working out how to apply one of those ways of using it into my case but I can't figure out how to do it with the variadic parameters. If you know how to do it in this case, care to give me some directions?

Also, thanks to you for finding this issue.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Feather Kit - A C++ Game Framework
« Reply #35 on: February 25, 2014, 09:26:42 am »
std::tuple already has a default constructor, so one constructor with a variadic argument list is enough. And I would not copy the arguments, but rather use perfect forwarding and move semantics.
#include <tuple>
#include <utility>

template <typename Tag, typename... DataTypes>
struct Message
{
    // Perfect forwarding
    // This is also the default constructor!
    template <typename... Args>
    Message(Args&&... args)
    : mData(std::forward<Args>(args)...)
    {
    }

    // Pass-by-value and move
    Message(std::tuple<DataTypes...> data)
    : mData(std::move(data))
    {
    }

    std::tuple<DataTypes...> mData;
};
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

select_this

  • Full Member
  • ***
  • Posts: 130
  • Current mood: just ate a pinecone
    • View Profile
    • darrenferrie.com
Re: Feather Kit - A C++ Game Framework
« Reply #36 on: February 25, 2014, 10:21:00 am »
This would appear to invalidate the following syntax though:

typedef Message<struct MessageTag, double, double, double> TMessage;

TMessage message1(double(1), double(40), double(42));

TMessage message2(message1);  // Compilation error
TMessage message3 = message2; // Compilation error

I guess it's not a big issue if you never wanted to copy messages around by themselves.
Follow me on Twitter, why don'tcha? @select_this

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6287
  • Thor Developer
    • View Profile
    • Bromeon
Re: Feather Kit - A C++ Game Framework
« Reply #37 on: February 25, 2014, 12:04:04 pm »
Strange, I'd expect the Big Five to have priority in overload resolution. You can try to declare them with = default, but I don't think it makes a difference.

Otherwise, I would create a class template specialization for the case with zero variadic arguments. That would not only simplify code, but also save memory, since you wouldn't have the tuple member.

By the way: You can use double literals such as 1. or 1.0 instead of double(1). And it's not necessary to write struct MessageTag, MessageTag is enough.
« Last Edit: February 25, 2014, 12:06:52 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #38 on: February 25, 2014, 12:22:33 pm »
Ah yeah, a specialisation would be a better solution indeed.

Thanks again, both of you!

select_this

  • Full Member
  • ***
  • Posts: 130
  • Current mood: just ate a pinecone
    • View Profile
    • darrenferrie.com
Re: Feather Kit - A C++ Game Framework
« Reply #39 on: February 25, 2014, 12:55:01 pm »
By the way: You can use double literals such as 1. or 1.0 instead of double(1). And it's not necessary to write struct MessageTag, MessageTag is enough.

I'm not normally so verbose, I'll admit this is test code adapted from something else that I just tweaked.  :)

However, removing the struct bit gives a not declared in this scope error (as I hadn't defined or declared the type prior to this snippet).

Edit: Turns out the copying issue was simply solved with user defined copy constructors:

template <typename TAG, typename... DATATYPES>
class Message {

    public:

        typedef std::tuple<DATATYPES...> TDataTypes;

        Message (Message& message) : m_data(message.m_data) {
        }

        Message (const Message& message) : m_data(message.m_data) {
        }

        template <typename... ARGS>
        Message (ARGS&&... args) : m_data(std::forward<ARGS>(args)...) {
        }

        Message (TDataTypes data) : m_data(std::move(data)) {
        }

        TDataTypes m_data;
};
« Last Edit: February 25, 2014, 01:34:10 pm by ghostmemory »
Follow me on Twitter, why don'tcha? @select_this

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #40 on: February 26, 2014, 04:59:32 pm »
Alright, time for a little update post!

Since the initial post, I have rewritten large parts of the entity system which was needed since it was the oldest part of the framework and I have learnt a lot since then, and as kind people in this thread pointed out, the system had serious flaws.

These flaws have now been fixed and the API is cleaner and much safer to use, and it has also gotten more features. The system has three main parts: The EntityManager, EntityFactory and the EntityComponent.
   The EntityManager stores all entities, and with it you can register attributes that the entities hold, and also create and delete entities.
   The EntityFactory is a convenience class where you can register entity templates. These are predefined sets of attributes. They can also have default values and inherit from each other. This is shown in the example code below.
   The final part consists of an abstract base class EntityComponent which is inherited to create entity logic. How this works is also shown in the example below.

With these parts working together, Feather Kit provides a full system for handling both entity data and logic.

Here follows a code snippet with a full example on how the entity system is used in practice. I hope it is not too long, and if anything is unclear and you want to know more, just ask here or over in the Feather Kit forums. :)

#include <featherkit/entitysystem.h>
#include <featherkit/util/entity/basictypeadder.h>
#include <featherkit/util/entity/glmtypeadder.h>
#include <featherkit/messaging.h>
#include <iostream>
#include <glm/glm.hpp>

FEA_DECLARE_MESSAGE(EntityDamagedMessage, fea::EntityId, int32_t);          //message to sent when an entity is damaged
FEA_DECLARE_MESSAGE(EntityDiedMessage, fea::EntityId);                      //message is sent when an entity has died
FEA_DECLARE_MESSAGE(EntityMovedMessage, fea::EntityId, const glm::vec2&);   //message is sent when an entity has moved
FEA_DECLARE_MESSAGE(FrameMessage);                                          //message is sent once every frame  

//the physics component simulates physics on physics entities. if entities have fallen further than 300 units, they start taking 5 damage every frame
class PhysicsComponent : public fea::EntityComponent,
    public FrameMessageReceiver
{
    public:
        PhysicsComponent(fea::MessageBus& bus) : mBus(bus)
        {
            //subscribe to frame messages to be able to simulate one physics step every frame
            mBus.addSubscriber<FrameMessage>(*this);
        }
        ~PhysicsComponent()
        {
            mBus.removeSubscriber<FrameMessage>(*this);
        }
        bool keepEntity(fea::WeakEntityPtr e) const override
        {
            fea::EntityPtr entity = e.lock();

            //only simulate on physics entities
            return entity->hasAttribute("position") &&
                entity->hasAttribute("velocity") &&
                entity->hasAttribute("acceleration");
        }
        void handleMessage(const FrameMessage& message) override
        {
            (void)message; //supress warning

            for(auto entityIterator : mEntities)
            {
                fea::EntityPtr entity = entityIterator.second.lock();

                //dumb physics simulation
                entity->addToAttribute("velocity", entity->getAttribute<glm::vec2>("acceleration"));
                entity->addToAttribute("position", entity->getAttribute<glm::vec2>("velocity"));
                const glm::vec2& position = entity->getAttribute<glm::vec2>("position");

                //notify that an entity has moved
                mBus.send(EntityMovedMessage(entity->getId(), position));

                //apply damage if further down than 300
                if(position.y > 300.0f)
                {
                    mBus.send(EntityDamagedMessage(entity->getId(), 5));
                }
            }
        }
    private:
        fea::MessageBus& mBus;
};

//the health component lets entities with health be able to take damage and die
class HealthComponent : public fea::EntityComponent,
    public EntityDamagedMessageReceiver
{
    public:
        HealthComponent(fea::MessageBus& bus) : mBus(bus)
        {
            //subscribe to know when entities are damaged
            bus.addSubscriber<EntityDamagedMessage>(*this);
        }

        ~HealthComponent()
        {
            mBus.removeSubscriber<EntityDamagedMessage>(*this);
        }

        bool keepEntity(fea::WeakEntityPtr e) const override
        {
            fea::EntityPtr entity = e.lock();

            //only deal with entities that have health and can be dead/alive
            return entity->hasAttribute("health") &&
                   entity->hasAttribute("alive");
        }

        //subtract health according to damage, and kill entity if health goes to zero or below
        void handleMessage(const EntityDamagedMessage& message)
        {
            fea::EntityId id;
            int32_t damageAmount;
            std::tie(id, damageAmount) = message.mData;

            if(mEntities.find(id) != mEntities.end())
            {
                fea::EntityPtr entity = mEntities.at(id).lock();

                int32_t health = entity->getAttribute<int32_t>("health");
                health -= damageAmount;

                entity->setAttribute("health", health);

                //if dead, notify about the death of the entity
                if(health <= 0)
                {
                    entity->setAttribute("alive", false);            
                    mBus.send(EntityDiedMessage(entity->getId()));
                }
            }
        }
        private:
            fea::MessageBus& mBus;
};

//simple class to print what is happening in the world
class Logger
    : public EntityDiedMessageReceiver,
      public EntityMovedMessageReceiver
{
    public:
        Logger(fea::MessageBus& bus) : mBus(bus)
        {
            //subscribe to the messages we are interested to print information about
            bus.addSubscriber<EntityDiedMessage>(*this);
            bus.addSubscriber<EntityMovedMessage>(*this);
        }

        ~Logger()
        {
            mBus.removeSubscriber<EntityDiedMessage>(*this);
            mBus.removeSubscriber<EntityMovedMessage>(*this);
        }

        //print when an entity has died
        void handleMessage(const EntityDiedMessage& message) override
        {
            fea::EntityId id = std::get<0>(message.mData);
            std::cout << "Entity id " << id << " died!\n";
        };

        //print when an entity has moved
        void handleMessage(const EntityMovedMessage& message) override
        {
            fea::EntityId id = std::get<0>(message.mData);
            const glm::vec2& position = std::get<1>(message.mData);
            std::cout << "Entity id " << id << " moved to " << position.x << " " << position.y << "\n";
        };
    private:
        fea::MessageBus& mBus;
};

int main()
{
    fea::MessageBus bus;
    Logger logger(bus);
    fea::EntityManager manager;
    fea::EntityFactory factory(manager);

    //use utility functions to register common data types like bool, int32 and vec2
    fea::util::addBasicDataTypes(factory);
    fea::util::addGlmDataTypes(factory);

    //register all attributes that our entities will use
    factory.registerAttribute("position", "vec2");
    factory.registerAttribute("velocity", "vec2");
    factory.registerAttribute("acceleration", "vec2");
    factory.registerAttribute("health", "int32");
    factory.registerAttribute("alive", "bool");
    factory.registerAttribute("hardness", "int32");

    //setup the physics entity template. physics simulation will be done on these
    fea::EntityTemplate physicsEntityTemplate;
    physicsEntityTemplate.mAttributes = {{"position"     , "0.0f, 0.0f" },
                                         {"velocity"     , "0.0f, 0.0f" },
                                         {"acceleration" , "0.0f, 0.8f"}};  //default gravity

    factory.addTemplate("physics_entity", physicsEntityTemplate);

    //setup the living entity template. these entities will be able to take damage and die
    fea::EntityTemplate livingEntityTemplate;
    livingEntityTemplate.mAttributes = {{"health" , "20"},     //default to 20 HP
                                        {"alive"  , "true"}};  //default to be alive

    factory.addTemplate("living_entity", livingEntityTemplate);

    //the rock template will inherit the physics entity but will also have a hardness attribute
    fea::EntityTemplate rockTemplate;
    rockTemplate.mInherits = {"physics_entity"};
    rockTemplate.mAttributes = {{"hardness" , "5"}};

    factory.addTemplate("rock", rockTemplate);

    //the enemy template will inherit both the physics entity and the living entity. so it will both be physics simulated and able to take damage
    fea::EntityTemplate enemyTemplate;
    enemyTemplate.mInherits = {"living_entity", "physics_entity"};

    factory.addTemplate("enemy", enemyTemplate);

    //store our entity components in a list
    std::vector<std::unique_ptr<fea::EntityComponent>> components;
    components.push_back(std::unique_ptr<PhysicsComponent>(new PhysicsComponent(bus)));
    components.push_back(std::unique_ptr<HealthComponent>(new HealthComponent(bus)));

    //create our entity instances. 2 rocks and 2 enemies. we also change the default attributes for some of them for variation
    factory.instantiate("rock").lock()->setAttribute("position", glm::vec2(50.0f, 50.0f));
    factory.instantiate("rock").lock()->setAttribute("position", glm::vec2(20.0f, 500.0f));
    factory.instantiate("enemy");
    factory.instantiate("enemy").lock()->setAttribute("position", glm::vec2(100.0f, 100.0f));

    //get all our created entities and pass them to the components. if they have the right attributes, the components will store them
    for(auto entity : manager.getAll())
    {
        for(auto& component : components)
            component->entityCreated(entity);
    } //(this can be done in a better way but i wanted to keep it simple)

    int32_t counter = 0;

    //our main loop which will terminate after 30 frames
    while(counter < 30)
    {
        //notify that a frame has passed
        bus.send(FrameMessage());

        //go through all entities and look for dead ones. these will be removed
        for(auto e : manager.getAll())
        {
            fea::EntityPtr entity = e.lock();
            if(entity->hasAttribute("alive"))
            {
                //if an entity is dead, tell all components to remove it if they have it, and finally remove it from the entity manager
                if(!entity->getAttribute<bool>("alive"))
                {
                    for(auto& component : components)
                        component->entityRemoved(entity->getId());

                    manager.removeEntity(entity->getId());
                }
            }
        }
       
        counter++;
    }
}

Rexou

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • Email
Re: Feather Kit - A C++ Game Framework
« Reply #41 on: March 05, 2014, 02:07:00 pm »
Hi there,

Again, nice work on the entity system ! The API seems very intuitive to me now especially on the entities template.

I was wondering : Is there a specific reason not to have Systems in your framework ? I mean is that only a design choice to put the behavior in components or are there any constraint that forced you towards that architecture ?

Ty for the sample about entities and keep up the good work.
Rexou

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #42 on: March 06, 2014, 04:56:13 am »
Thanks a lot! :)

Hmm what do you actually mean with "Systems" VS "components"? It seems to me like poeple use a lot of different terminology for these things haha.

Cheers!

Rexou

  • Newbie
  • *
  • Posts: 9
    • View Profile
    • Email
Re: Feather Kit - A C++ Game Framework
« Reply #43 on: March 06, 2014, 10:09:11 am »
Yeah there are a lot of ECS terminology/architecture/implementation hehe

What i mean is that you've put the behavior & data in the components and i wanted to know if that was originally intended and if something prevented you from decoupling data (into components) and behavior (in systems) and let the different Systems/Controllers iterate over the entities it's interested in (using bitmasks or keepEntity()-like fonctions to check if it has the right components like you did).

That's just a bit of curiosity on constraints encountered while implementing something like that, I have done a very simple one for learning purpose but never tried to layout the underlying type in order to avoid cache misses and optimize the performances. Your implementation seems to be very flexible and that's why I am very curious about the difficulties/choices happening during the development.

Maybe i am a little bit off-topic just let me know if that's the case,
Rexou.

EDIT : Nevermind, just saw your post on your blog explaining this part !
« Last Edit: March 06, 2014, 01:25:16 pm by Rexou »

therocode

  • Full Member
  • ***
  • Posts: 125
    • View Profile
    • Development blog
Re: Feather Kit - A C++ Game Framework
« Reply #44 on: March 06, 2014, 04:53:47 pm »
Ah, well there is decoupling. :)

In my system I call it attributes and components where attributes are data and components are logic.

So the attributes are completely detached from any logic and can be freely accessed with the .setAttribute and .getAttribute functions.

Then the logic is a system where you specialise behaviour by inheriting from one EntityComponent class which specifies which entities it should process.

There was before an AlignedEntityBackend which could be used by the entity system to get cache friendly iterable entity data, but that one was coded badly and has now been taken out. I plan however to in a future version rewrite it and incorporate it again. I made a blog post about how the inner workings worked here: http://blog.therocode.net/2013/04/insights-into-the-entity-manager-design-decisions/

Hope that answered your question! :) It is not on topic, discussion is always interesting!