Pass anything as constructor without varargs

Started by
12 comments, last by SyncViews 8 years, 8 months ago

Hey guys!

I have a simple event-system implemented in my game. To trigger an event I have to create an instance of that specific event; But in order to avoid creating many of them in a short time I wanted to somehow pool them: The only problem is that I don't want to create a pool for each of them, and I also have to pass different variables for each event as constructor arguments. Here is an example showing what I would like to do:

Superclass GameEvent.h:


class GameEvent {}

Poolable


template<typename T> class Poolable {

public:
    virtual void reset() = 0;
    virtual T* new(...) = 0;
} 

ExampleEvent1


class ExampleEvent1 : public GameEvent, public Poolable<ExampleEvent1> {
private:
    ExampleEvent1(SomeArg1 a1, SomeArg2 a2);
public:
    EventSpecificFunction();
    void reset(){//reset this object to default}
    ExampleEvent1* new(...){
        //do the same stuff you would do in the constructor
        return this;}
friend EventHandler;
}

ExampleEvent2


class ExampleEvent2 : public GameEvent, public Poolable<ExampleEvent2> {
private:
    ExampleEvent2(Arg a1, Arg a2, int id, std::string test);
public:
    EventSpecificFunctionLalalala();
    //poolable functions like above...
friend EventHandler;
}

EventHandler


class EventHandler {
public:
    void triggerEvent(EventType type, GameEvent e); //EventType is an enum
    void hookInto(EventType type, fastdelegate::FastDelegate1<GameEvent, void> fun);

    ~EventHandler();
    EventHandler();

    void recycleEvent(EventType type, GameEvent* e){
        e->reset();
        eventPool.at(type).push(e);
    }
    
    GameEvent* newEvent(EventType type, I_Guess_I_Will_Have_To_Use_varargs){
        if(eventPool.at(type).size() < 1)
             return new //somehow construct an event according to the EventType type with valid arguments...
        return eventPool.at(type).pop()->new(I_Guess_I_Will_Have_To_Use_varargs);
    }

private:
    std::map<EventType, A3D::AEvent<FastDelegate<void(GameEvent)>>> gameEvents;
    
    std::map<EventType, queue<GameEvent*>> eventPool;
};

I hope this isn't too messy. My problem lies therein that I can't create a new instance of one of my events by only knowing the according value from the enum. Also I don't like varargs, but that's the smallest of my problems.

Help would be greatly appreciated smile.png.

Kind regards

Edit: Wait, couldn't I just make newEvent a templated function? *mindblown*

Advertisement

If you want to do this at compile time, then c++14, std::forward() and variable template arguments are your friend.

If you want to do it at runtime, or you are stuck with an old C++ compiler, then things get very sticky very quickly, and you likely end up using a map of factory functions and good old fashioned var args, or passing something like a std::list<boost::variant<X, Y, Z>>...

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Thanks for the answer!

Actually I did not even think about which way I want to do it (compile/runtime). I'll ponder a bit over it.

Also, another question, related to the problem (Right now I've improved the example in the first post and I don't see why it wouldn't work; But I want to make it better still):

Is there a way to get a std::map to hold a class-type as key?

Like, my current method "template<class T> GameEvent* newEvent(EventType t, int n_args, ...);" takes an EventType as argument in order to put it accordingly in the map, but what if I could just call it "template<class T> GameEvent* newEvent(int n_args, ...);", and put the "T" in the map? Of course, the problem is that I don't know (Or, actually, I know - But the compiler doesnt) what T is, and thus I can't put it as a key in a map.

In Java there is an Object for symbolizing a class, called "Class", and if C++ had something similar it would be just perfect...

(Java/C++ Mixed example:


typename<class T> void EventHandler::recycleEvent(GameEvent* e){
	e->reset();
	eventPool.at(T).push(e);
}

template<class T> GameEvent* newEvent(int n, ...){
    va_list args;
    va_start(args, n);
    GameEvent* gep = (eventPool.at(type).size() < 1) ? (static_cast<GameEvent*>(new T())) : (eventPool.at(T).pop());
    gep->createNew(args);
    va_end(args);
    return gep;
}

std::map<Class<?>, std::queue<GameEvent*>> eventPool;

I could even make my recycle method a templated method and drop the whole EventType stuff!

Any ideas :D?

Kind regards


Is there a way to get a std::map to hold a class-type as key?

In Java there is an Object for symbolizing a class, called "Class", and if C++ had something similar it would be just perfect...

No. Java is a much more dynamic language than C++.

In general, C++ doesn't not support very much of this type of runtime introspection.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

Is there a way to get a std::map to hold a class-type as key?


Not natively, but depending on the complexity you need, you can just make a public static const integer in each class called something like "TypeID", give it a unique value per-type, and then use that.
You can do this natively in c++ using rtti.

There is an operator called typeid (it's an operator that looks like a function call, like sizeof).

It returns a type_info object. It implements a "before" function that defines an ordering, so you can make a wrapper class to use it as a map index with an overloaded < operator.

C++11 directly provides such a wrapper class, called type_index, that can be constructed from a type_info and can also be used as a key in unordered_map and unordered_set, as there is a specialization of std::hash for it.

typeid can be given a type directly as in typeid( int ) or an expression. If the expression is an object instance with a vtable (such as an interface class), the type will be determined at runtime (by reading a pointer in a special slot of the vtable, so there's virtually no memory overhead). In other cases it is resolved at compilation, just giving a poiner to the appropriate type_info object directly.

Note that it is a common belief that rtti is evil and costly and should be disabled in games. I never understood the rationale for it, in particular in cases where people then go out of their way to reimplement a similar but unwieldy type identification system by hand.

The only problem is that I don't want to create a pool for each of them


std::map> eventPool;

,>

But isn't that exactly what you have done? Is it really such a big step to make a "SimplePool<T>" then explicitly instantiate it for each type in some collective factories? Then just do say "myEventFactory->createKeyEvent(KeyEvent::KEY_DOWN, keyCode, modifierKeys)".

Otherwise for a more complex solution Id just look at getting a pool memory allocator. Since this only actually deals with raw memory of certain sizes, the type does not matter, and you avoid that expensive map lookup. Instead at that level your just getting or freeing a block of initialised memory (potentially with optimisations, like allocating a full page worth of blocks at once). You can have a pool for different sizes, e.g. up to 16 bytes, 32bytes, 64, 128, 256, etc.

The user of the system then deals with constructing and destructing the right object into it (e.g. new/delete). If you have many event types with similar sizes, this also reduces the required size of the pool. And there is no special requirements on your pooled objects (at least at this level, e.g. your other event code might mandate a base class), e.g. they don't even need to be moveable or have any particular constructor signature.

Note that it is a common belief that rtti is evil and costly and should be disabled in games. I never understood the rationale for it, in particular in cases where people then go out of their way to reimplement a similar but unwieldy type identification system by hand.



Because many compilers implement RTTI in such a way that it becomes a serious performance liability. dynamic_cast<> should not need to invoke strcmp, for example. People roll their own type ID systems because they can do far better than the available compilers on several major gaming platforms.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Because many compilers implement RTTI in such a way that it becomes a serious performance liability. dynamic_cast<> should not need to invoke strcmp, for example. People roll their own type ID systems because they can do far better than the available compilers on several major gaming platforms.

Is it that bad on current-gen consoles or on the PC?

Honestly I'm not sure, but I would be happily surprised if VC++ abandoned their implementation of dynamic_cast. The rationale for the strcmp is that it's simple and handles many weird inheritance cases that otherwise could be missed (and typically are handled wrong by Joe Coder's Handmade RTTI Replacement Code).

I haven't worked on the PS4 so I don't know what state their compilers are in first-hand. I understand they're a vast improvement though.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

This topic is closed to new replies.

Advertisement