Skip to main content
Commonmark migration
Source Link

###Full code:

Full code:

###Full code:

Full code:

added 1 character in body
Source Link
Quentin
  • 1.2k
  • 7
  • 12
Object *target = WorldMap.getObject(pos,facing);
target.dispatch->dispatch([&](components::EnemyTag &, components::Health &h) {
    h.attack(weaponStrength);
});
Object *target = WorldMap.getObject(pos,facing);
target.dispatch([&](components::EnemyTag &, components::Health &h) {
    h.attack(weaponStrength);
});
Object *target = WorldMap.getObject(pos,facing);
target->dispatch([&](components::EnemyTag &, components::Health &h) {
    h.attack(weaponStrength);
});
Source Link
Quentin
  • 1.2k
  • 7
  • 12

Okay, disclaimer: this has gone completely overkill. But it was a lot of fun to put together!

The end product looks like this:

Object *target = WorldMap.getObject(pos,facing);
target.dispatch([&](components::EnemyTag &, components::Health &h) {
    h.attack(weaponStrength);
});

dispatch takes in any function or function object (here a lambda), and inspects its parameter list. The parameters' types are used to call the correponding getSomethingComponent virtual functions. If all components are found, then the lambda is called with them and dispatch returns true. Otherwise, nothing happens and dispatch returns false.

See it live on Coliru

To make the wiring simpler, I have replaced the getSomethingComponent virtual functions with overloads of a virtual get_(tag<SomethingComponent>) function. These are now an implementation detail, and are generated with a macro.

###Full code:

#include <type_traits>
#include <tuple>
#include <iostream>

////////////////////////////////////////////////////////////////////////////////
// Dummy tag to hold one or more types

template <class... T>
struct tag { };

////////////////////////////////////////////////////////////////////////////////
// typename params<F>::type is tag<P1, P2, ... Pn>
// where P's are the parameter types of the function or function-object F.

template <class F>
struct params : params<decltype(&F::operator())> { };

template <class R, class T, class... Params>
struct params<R (T::*)(Params...)> { using type = tag<Params...>; };

template <class R, class T, class... Params>
struct params<R (T::*)(Params...) const> { using type = tag<Params...>; };

template <class R, class... Params>
struct params<R (*)(Params...)> { using type = tag<Params...>; };

template <class R, class... Params>
struct params<R (&)(Params...)> { using type = tag<Params...>; };

////////////////////////////////////////////////////////////////////////////////
// Dereference every pointer in Args and call f with them

template <class F, class Args, std::size_t... Idx>
void derefCall(F &&f, Args &args, std::index_sequence<Idx...>) {
    std::forward<F>(f)(*std::get<Idx>(args)...);
}

////////////////////////////////////////////////////////////////////////////////
// Defining your components

namespace components {
    struct Health {
        int value = 100;
    };
    
    struct EnemyTag { };
}

////////////////////////////////////////////////////////////////////////////////
// Defining the base class

struct Object {
    
    // obj.get<Comp>() : retrieves a component by type (or nullptr)
    template <class CompType>
    CompType *get() {
        return get_(tag<CompType>{});
    }
    
    // dispatch : tries to call f with all of the components it requires
    // Returns whether all components were found and f was called.
    template <class F>
    bool dispatch(F &&f) {
        return dispatch_(std::forward<F>(f), typename params<std::decay_t<F>>::type{});
    }
    
private:

    // Define one virtual function get_(tag<Comp>) for every component type.
#define OBJECT_DECLARE_COMPONENT(CompType) \
    virtual ::components::CompType *get_(tag<::components::CompType>) { return nullptr; }

    OBJECT_DECLARE_COMPONENT(Health)
    OBJECT_DECLARE_COMPONENT(EnemyTag)

#undef OBJECT_DECLARE_COMPONENT

    // Implementation of dispatch, Params are the parameters needed by f.
    template <class F, class... Params>
    bool dispatch_(F &&f, tag<Params...>) {
        
        bool allGood = true;
        
        // Construct a tuple with the component pointers
        // For each component required by f:
        auto ptrs = std::make_tuple([&]() -> std::decay_t<Params>* {
            
            // One component is already missing, skip the rest
            if(!allGood)
                return nullptr;
            
            // Get the component of the adequate type
            auto *comp = get<std::decay_t<Params>>();
            
            // Failure: break the chain
            if(!comp)
                allGood = false;
                
            return comp;
        }()...);
        
        // Missing component, bail out
        if(!allGood)
            return false;
        
        // Call f with the dereferenced pointers
        derefCall(std::forward<F>(f), ptrs, std::index_sequence_for<Params...>{});
        return true;
    }
};

// Helper macro to override the component getters
#define OBJECT_OVERRIDE_COMPONENT(CompType) \
    ::components::CompType *get_(tag<::components::CompType>) override 

// Enemy class
struct Enemy : Object {
    
    OBJECT_OVERRIDE_COMPONENT(EnemyTag) {
        return &enemyTag;
    }
    
    OBJECT_OVERRIDE_COMPONENT(Health) {
        return &health;
    }
    
    components::EnemyTag enemyTag;
    components::Health health;
};

int main() {
    Enemy e;
    Object &o = e;
    
    // Nice call syntax
    bool dispatched = o.dispatch([](components::EnemyTag &, components::Health &h) {
        h.value -= 10;
    });
    
    if(dispatched)
        std::cout << "Dispatched successfully!\n";
    
    std::cout << "Enemy has " << e.get<components::Health>()->value << "hp left.\n";
}