Skip to main content
GameDev.net gamedev.net
🔒 Locked

Event Aggregator in C++ - Need Help

Started by lochnator Nov 28, 2015 at 2:32 AM 6 replies 5.7k views
Original Post
lochnator
lochnator

I was wanting to take a stab at creating an EventAggregator in c++. It would work like so. Each game object that wanted to be notified of specific game events would subscribe to the event using the event aggregator like so.


struct SomeSubscriber : Subscriber
{
	SomeSubscriber(std::shared_ptr<EventAggregator> ea)
	{
		ea->Subscribe(this, L"EnemySpotted");
	}

};

All Subscribers would derive from the following class


struct Subscriber
{
	virtual ~Subscriber()
	{
	}

	virtual void Handle(Event & e) = 0;
};

The Event Aggregator would look something like this


struct EventAggregator
{
	std::map<std::wstring, std::vector<std::weak_ptr<Subscriber>>> m_subscriptions;
	void Publish(Event & e);
	void Subscribe(std::weak_ptr<Subscriber> subscriber, std::wstring eventType);
};

The problem i have is i would like the gameobjects to subscribe to the event aggregator in their constructor but i'm not sure how to do this since the EventAggregator expects a shared_ptr() in the Subscribe Method. Does anyone have any advice/recommendations?

DLudwig
DLudwig

The problem i have is i would like the gameobjects to subscribe to the event aggregator in their constructor but i'm not sure how to do this since the EventAggregator expects a shared_ptr() in the Subscribe Method. Does anyone have any advice/recommendations?

I don't believe this is possible, not while adhering to standards-compliant C++. There are various ways to have an object's method's get a shared_ptr to itself, but for a variety of reasons, they're really not designed to be called from an object's own constructor. There are a variety of pitfalls in trying to do that!

What about implementing a virtual init() function of sorts? Construct your objects first, wrap them in a std::shared_ptr, then call their init() method. Getting a std::shared_ptr to that method shouldn't be too-too hard. For example, you could either:

- pass it in as a parameter, or

- utilize std::enable_shared_from_this, which allows a struct's/class's member functions to get std::shared_ptrs to itself, *but only if that object is already owned by at least one std::shared_ptr* (This is mandated by the C++ standard!)

Cheers, and best of luck!

-- David L.

Alberth
Alberth

Why do you need a weak pointer?

Since the game objects derive from SubScriber, why not let Subcriber store the aggregators, and notify all of them of descruction in ~Subscriber ?

lochnator
lochnator

Why do you need a weak pointer?

Since the game objects derive from SubScriber, why not let Subcriber store the aggregators, and notify all of them of descruction in ~Subscriber ?

I'm new to all the c++11 stuff but a weak_ptr to the subscriber is held by the event aggregator to prevent it from keeping an object alive when its no longer used.

as for letting the aggregator store the subscribers in not sure what this means

lochnator
lochnator

The problem i have is i would like the gameobjects to subscribe to the event aggregator in their constructor but i'm not sure how to do this since the EventAggregator expects a shared_ptr() in the Subscribe Method. Does anyone have any advice/recommendations?

I don't believe this is possible, not while adhering to standards-compliant C++. There are various ways to have an object's method's get a shared_ptr to itself, but for a variety of reasons, they're really not designed to be called from an object's own constructor. There are a variety of pitfalls in trying to do that!

What about implementing a virtual init() function of sorts? Construct your objects first, wrap them in a std::shared_ptr, then call their init() method. Getting a std::shared_ptr to that method shouldn't be too-too hard. For example, you could either:

- pass it in as a parameter, or

- utilize std::enable_shared_from_this, which allows a struct's/class's member functions to get std::shared_ptrs to itself, *but only if that object is already owned by at least one std::shared_ptr* (This is mandated by the C++ standard!)

rCheers, and best of luck!

-- David L.

i could do that but it seems to be a bit of error prone. It's easy to forgot to call unit. This is why i was hoping to use the ctor. Any other ideas :)?

Alberth
Alberth

as for letting the aggregator store the subscribers

I said it the other way around: "...why not let Subcriber store the aggregators..."

which leads to something like


struct Subscriber {
    Subscriber() { }
    virtual ~Subscriber() {
        for (auto agg : aggregators) agg->UnsubscribeAll(this);
    }

    void Subscribe(Aggregator &agg, std::string &evt_name) {
        agg.Subscribe(this, evt_name);
        aggregators.push_back(&agg); // EDIT: Messed up the shared ptr here :(
    }

    void HandleEvent(const Event &e) = 0;

    std::set<std::shared_ptr<Aggregator> > aggregators;
};

So the subscribed object knows which aggregators it uses, and unsubscribes all of them on destruction.

DLudwig
DLudwig

The problem i have is i would like the gameobjects to subscribe to the event aggregator in their constructor but i'm not sure how to do this since the EventAggregator expects a shared_ptr() in the Subscribe Method. Does anyone have any advice/recommendations?

I don't believe this is possible, not while adhering to standards-compliant C++. There are various ways to have an object's method's get a shared_ptr to itself, but for a variety of reasons, they're really not designed to be called from an object's own constructor. There are a variety of pitfalls in trying to do that!

What about implementing a virtual init() function of sorts? Construct your objects first, wrap them in a std::shared_ptr, then call their init() method. Getting a std::shared_ptr to that method shouldn't be too-too hard. For example, you could either:

- pass it in as a parameter, or

- utilize std::enable_shared_from_this, which allows a struct's/class's member functions to get std::shared_ptrs to itself, *but only if that object is already owned by at least one std::shared_ptr* (This is mandated by the C++ standard!)

rCheers, and best of luck!

-- David L.

i could do that but it seems to be a bit of error prone. It's easy to forgot to call unit. This is why i was hoping to use the ctor. Any other ideas smile.png?

What about creating a small function that would create an object, wrap it in a shared_ptr, then call init? If you don't mind dabbing in templates a bit, it should be possible to write something like this:


std::shared_ptr<Thing> thing = make_thing<Thing>();

... whereby make_thing() operates almost identically to std::make_shared (and probably calls it, under the hood), but it calls Thing's init() method, after constructing it.

-- David L.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.