summaryrefslogtreecommitdiff
path: root/include/CommonAPI/Event.hpp
blob: 90971f54a74519b548e964fd52adb8065d8e743f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright (C) 2013-2015 Bayerische Motoren Werke Aktiengesellschaft (BMW AG)
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#if !defined (COMMONAPI_INTERNAL_COMPILATION)
#error "Only <CommonAPI/CommonAPI.h> can be included directly, this file may disappear or change contents."
#endif

#ifndef COMMONAPI_EVENT_HPP_
#define COMMONAPI_EVENT_HPP_

#include <functional>
#include <mutex>
#include <map>
#include <set>
#include <tuple>

namespace CommonAPI {

/**
 * \brief Class representing an event
 *
 * Class representing an event
 */
template<typename... _Arguments>
class Event {
public:
    typedef std::tuple<_Arguments...> ArgumentsTuple;
    typedef std::function<void(const _Arguments&...)> Listener;
    typedef uint32_t Subscription;
    typedef std::set<Subscription> SubscriptionsSet;
    typedef std::map<Subscription, Listener> ListenersMap;

    /**
     * \brief Constructor
     */
    Event() : nextSubscription_(0) {};

    /**
     * \brief Subscribe a listener to this event
     *
     * Subscribe a listener to this event.
     * ATTENTION: You should not build new proxies or register services in callbacks
     * from events. This can cause a deadlock or assert. Instead, you should set a
     * trigger for your application to do this on the next iteration of your event loop
     * if needed. The preferred solution is to build all proxies you need at the
     * beginning and react to events appropriatly for each.
     *
     * @param listener A listener to be added
     * @return key of the new subscription
     */
    Subscription subscribe(Listener listener);

    /**
     * \brief Remove a listener from this event
     *
     * Remove a listener from this event
     * Note: Do not call this inside a listener notification callback it will deadlock! Use cancellable listeners instead.
     *
     * @param subscription A listener token to be removed
     */
    void unsubscribe(Subscription subscription);

    virtual ~Event() {}

protected:
    void notifyListeners(const _Arguments&... eventArguments);

    virtual void onFirstListenerAdded(const Listener& listener) {}
    virtual void onListenerAdded(const Listener& listener) {}

    virtual void onListenerRemoved(const Listener& listener) {}
    virtual void onLastListenerRemoved(const Listener& listener) {}

//private:
    ListenersMap subscriptions_;
    Subscription nextSubscription_;

    ListenersMap pendingSubscriptions_;
    SubscriptionsSet pendingUnsubscriptions_;

    std::mutex notificationMutex_;
    std::mutex subscriptionMutex_;
};

template<typename ... _Arguments>
typename Event<_Arguments...>::Subscription Event<_Arguments...>::subscribe(Listener listener) {
	Subscription subscription;
	bool isFirstListener;

	subscriptionMutex_.lock();
	subscription = nextSubscription_++;
	// TODO?: check for key/subscription overrun
	listener = pendingSubscriptions_[subscription] = std::move(listener);
	isFirstListener = (0 == subscriptions_.size());
	subscriptionMutex_.unlock();

	if (isFirstListener)
		onFirstListenerAdded(listener);
	onListenerAdded(listener);

	return subscription;
}

template<typename ... _Arguments>
void Event<_Arguments...>::unsubscribe(Subscription subscription) {
	bool isLastListener(false);

	subscriptionMutex_.lock();
	auto listener = subscriptions_.find(subscription);
	if (subscriptions_.end() != listener
			&& pendingUnsubscriptions_.end() == pendingUnsubscriptions_.find(subscription)) {
		if (0 == pendingSubscriptions_.erase(subscription)) {
			pendingUnsubscriptions_.insert(subscription);
			isLastListener = (1 == subscriptions_.size());
		} else {
			isLastListener = (0 == subscriptions_.size());
		}
	}
	subscriptionMutex_.unlock();

	if (subscriptions_.end() != listener) {
		onListenerRemoved(listener->second);
		if (isLastListener)
			onLastListenerRemoved(listener->second);
	}
}

template<typename ... _Arguments>
void Event<_Arguments...>::notifyListeners(const _Arguments&... eventArguments) {
	subscriptionMutex_.lock();
	notificationMutex_.lock();
	for (auto iterator = pendingUnsubscriptions_.begin();
		 iterator != pendingUnsubscriptions_.end();
		 iterator++) {
		subscriptions_.erase(*iterator);
	}
	pendingUnsubscriptions_.clear();

	for (auto iterator = pendingSubscriptions_.begin();
		 iterator != pendingSubscriptions_.end();
		 iterator++) {
		subscriptions_.insert(*iterator);
	}
	pendingSubscriptions_.clear();

	subscriptionMutex_.unlock();
	for (auto iterator = subscriptions_.begin(); iterator != subscriptions_.end(); iterator++) {
        iterator->second(eventArguments...);
    }

    notificationMutex_.unlock();
}

} // namespace CommonAPI

#endif // COMMONAPI_EVENT_HPP_