1//
2// AbstractEvent.h
3//
4// Library: Foundation
5// Package: Events
6// Module: AbstractEvent
7//
8// Definition of the AbstractEvent class.
9//
10// Copyright (c) 2006-2011, Applied Informatics Software Engineering GmbH.
11// and Contributors.
12//
13// SPDX-License-Identifier: BSL-1.0
14//
15
16
17#ifndef Foundation_AbstractFoundation_INCLUDED
18#define Foundation_AbstractFoundation_INCLUDED
19
20
21#include "Poco/Foundation.h"
22#include "Poco/SingletonHolder.h"
23#include "Poco/SharedPtr.h"
24#include "Poco/ActiveResult.h"
25#include "Poco/ActiveMethod.h"
26#include "Poco/Mutex.h"
27
28
29namespace Poco {
30
31
32template <class TArgs, class TStrategy, class TDelegate, class TMutex = FastMutex>
33class AbstractEvent
34 /// An AbstractEvent is the base class of all events.
35 /// It works similar to the way C# handles notifications (aka events in C#).
36 ///
37 /// Events can be used to send information to a set of delegates
38 /// which are registered with the event. The type of the data is specified with
39 /// the template parameter TArgs. The TStrategy parameter must be a subclass
40 /// of NotificationStrategy. The parameter TDelegate can either be a subclass of AbstractDelegate
41 /// or of AbstractPriorityDelegate.
42 ///
43 /// Note that AbstractEvent should never be used directly. One ought to use
44 /// one of its subclasses which set the TStrategy and TDelegate template parameters
45 /// to fixed values. For most use-cases the BasicEvent template will be sufficient:
46 ///
47 /// #include "Poco/BasicEvent.h"
48 /// #include "Poco/Delegate.h"
49 ///
50 /// Note that as of release 1.4.2, the behavior of BasicEvent equals that of FIFOEvent,
51 /// so the FIFOEvent class is no longer necessary and provided for backwards compatibility
52 /// only.
53 ///
54 /// BasicEvent works with a standard delegate. They allow one object to register
55 /// one or more delegates with an event. In contrast, a PriorityDelegate comes with an attached priority value
56 /// and allows one object to register for one priority value one or more delegates. Note that PriorityDelegates
57 /// only work with PriorityEvents:
58 ///
59 /// #include "Poco/PriorityEvent.h"
60 /// #include "Poco/PriorityDelegate.h"
61 ///
62 /// Use events by adding them as public members to the object which is throwing notifications:
63 ///
64 /// class MyData
65 /// {
66 /// public:
67 /// Poco::BasicEvent<int> dataChanged;
68 ///
69 /// MyData();
70 /// ...
71 /// void setData(int i);
72 /// ...
73 /// private:
74 /// int _data;
75 /// };
76 ///
77 /// Firing the event is done either by calling the event's notify() or notifyAsync() method:
78 ///
79 /// void MyData::setData(int i)
80 /// {
81 /// this->_data = i;
82 /// dataChanged.notify(this, this->_data);
83 /// }
84 ///
85 /// Alternatively, instead of notify(), operator () can be used.
86 ///
87 /// void MyData::setData(int i)
88 /// {
89 /// this->_data = i;
90 /// dataChanged(this, this->_data);
91 /// }
92 ///
93 /// Note that operator (), notify() and notifyAsync() do not catch exceptions, i.e. in case a
94 /// delegate throws an exception, notifying is immediately aborted and the exception is propagated
95 /// back to the caller.
96 ///
97 /// Delegates can register methods at the event. In the case of a BasicEvent
98 /// the Delegate template is used, in case of an PriorityEvent a PriorityDelegate is used.
99 /// Mixing of delegates, e.g. using a PriorityDelegate with a BasicEvent is not allowed and
100 /// can lead to compile-time and/or run-time errors. The standalone delegate() functions
101 /// can be used to construct Delegate objects.
102 ///
103 /// Events require the observers to have one of the following method signatures:
104 ///
105 /// void onEvent(const void* pSender, TArgs& args);
106 /// void onEvent(TArgs& args);
107 /// static void onEvent(const void* pSender, TArgs& args);
108 /// static void onEvent(void* pSender, TArgs& args);
109 /// static void onEvent(TArgs& args);
110 ///
111 /// For performance reasons arguments are always sent by reference. This also allows observers
112 /// to modify the event argument. To prevent that, use <[const TArg]> as template
113 /// parameter. A non-conformant method signature leads to compile errors.
114 ///
115 /// Assuming that the observer meets the method signature requirement, it can register
116 /// this method with the += operator:
117 ///
118 /// class MyController
119 /// {
120 /// protected:
121 /// MyData _data;
122 ///
123 /// void onDataChanged(void* pSender, int& data);
124 /// ...
125 /// };
126 ///
127 /// MyController::MyController()
128 /// {
129 /// _data.dataChanged += delegate(this, &MyController::onDataChanged);
130 /// }
131 ///
132 /// In some cases it might be desirable to work with automatically expiring registrations. Simply add
133 /// to delegate as 3rd parameter a expireValue (in milliseconds):
134 ///
135 /// _data.dataChanged += delegate(this, &MyController::onDataChanged, 1000);
136 ///
137 /// This will add a delegate to the event which will automatically be removed in 1000 millisecs.
138 ///
139 /// Unregistering happens via the -= operator. Forgetting to unregister a method will lead to
140 /// segmentation faults later, when one tries to send a notify to a no longer existing object.
141 ///
142 /// MyController::~MyController()
143 /// {
144 /// _data.dataChanged -= delegate(this, &MyController::onDataChanged);
145 /// }
146 ///
147 /// Working with PriorityDelegate's as similar to working with BasicEvent.
148 /// Instead of delegate(), the priorityDelegate() function must be used
149 /// to create the PriorityDelegate.
150{
151public:
152 typedef TDelegate* DelegateHandle;
153 typedef TArgs Args;
154
155 AbstractEvent():
156 _executeAsync(this, &AbstractEvent::executeAsyncImpl),
157 _enabled(true)
158 {
159 }
160
161 AbstractEvent(const TStrategy& strat):
162 _executeAsync(this, &AbstractEvent::executeAsyncImpl),
163 _strategy(strat),
164 _enabled(true)
165 {
166 }
167
168 virtual ~AbstractEvent()
169 {
170 }
171
172 void operator += (const TDelegate& aDelegate)
173 /// Adds a delegate to the event.
174 ///
175 /// Exact behavior is determined by the TStrategy.
176 {
177 typename TMutex::ScopedLock lock(_mutex);
178 _strategy.add(aDelegate);
179 }
180
181 void operator -= (const TDelegate& aDelegate)
182 /// Removes a delegate from the event.
183 ///
184 /// If the delegate is not found, this function does nothing.
185 {
186 typename TMutex::ScopedLock lock(_mutex);
187 _strategy.remove(aDelegate);
188 }
189
190 DelegateHandle add(const TDelegate& aDelegate)
191 /// Adds a delegate to the event.
192 ///
193 /// Exact behavior is determined by the TStrategy.
194 ///
195 /// Returns a DelegateHandle which can be used in call to
196 /// remove() to remove the delegate.
197 {
198 typename TMutex::ScopedLock lock(_mutex);
199 return _strategy.add(aDelegate);
200 }
201
202 void remove(DelegateHandle delegateHandle)
203 /// Removes a delegate from the event using a DelegateHandle
204 /// returned by add().
205 ///
206 /// If the delegate is not found, this function does nothing.
207 {
208 typename TMutex::ScopedLock lock(_mutex);
209 _strategy.remove(delegateHandle);
210 }
211
212 void operator () (const void* pSender, TArgs& args)
213 /// Shortcut for notify(pSender, args);
214 {
215 notify(pSender, args);
216 }
217
218 void operator () (TArgs& args)
219 /// Shortcut for notify(args).
220 {
221 notify(0, args);
222 }
223
224 void notify(const void* pSender, TArgs& args)
225 /// Sends a notification to all registered delegates. The order is
226 /// determined by the TStrategy. This method is blocking. While executing,
227 /// the list of delegates may be modified. These changes don't
228 /// influence the current active notifications but are activated with
229 /// the next notify. If a delegate is removed during a notify(), the
230 /// delegate will no longer be invoked (unless it has already been
231 /// invoked prior to removal). If one of the delegates throws an exception,
232 /// the notify method is immediately aborted and the exception is propagated
233 /// to the caller.
234 {
235 Poco::ScopedLockWithUnlock<TMutex> lock(_mutex);
236 if (!_enabled) return;
237
238 // thread-safeness:
239 // copy should be faster and safer than blocking until
240 // execution ends
241 TStrategy strategy(_strategy);
242 lock.unlock();
243 strategy.notify(pSender, args);
244 }
245
246 bool hasDelegates() const
247 /// Returns true if there are registered delegates.
248 {
249 return !empty();
250 }
251
252 ActiveResult<TArgs> notifyAsync(const void* pSender, const TArgs& args)
253 /// Sends a notification to all registered delegates. The order is
254 /// determined by the TStrategy. This method is not blocking and will
255 /// immediately return. The delegates are invoked in a separate thread.
256 /// Call activeResult.wait() to wait until the notification has ended.
257 /// While executing, other objects can change the delegate list. These changes don't
258 /// influence the current active notifications but are activated with
259 /// the next notify. If a delegate is removed during a notify(), the
260 /// delegate will no longer be invoked (unless it has already been
261 /// invoked prior to removal). If one of the delegates throws an exception,
262 /// the execution is aborted and the exception is propagated to the caller.
263 {
264 NotifyAsyncParams params(pSender, args);
265 {
266 typename TMutex::ScopedLock lock(_mutex);
267
268 // thread-safeness:
269 // copy should be faster and safer than blocking until
270 // execution ends
271 // make a copy of the strategy here to guarantee that
272 // between notifyAsync and the execution of the method no changes can occur
273
274 params.ptrStrat = SharedPtr<TStrategy>(new TStrategy(_strategy));
275 params.enabled = _enabled;
276 }
277 ActiveResult<TArgs> result = _executeAsync(params);
278 return result;
279 }
280
281 void enable()
282 /// Enables the event.
283 {
284 typename TMutex::ScopedLock lock(_mutex);
285 _enabled = true;
286 }
287
288 void disable()
289 /// Disables the event. notify and notifyAsnyc will be ignored,
290 /// but adding/removing delegates is still allowed.
291 {
292 typename TMutex::ScopedLock lock(_mutex);
293 _enabled = false;
294 }
295
296 bool isEnabled() const
297 /// Returns true if event is enabled.
298 {
299 typename TMutex::ScopedLock lock(_mutex);
300 return _enabled;
301 }
302
303 void clear()
304 /// Removes all delegates.
305 {
306 typename TMutex::ScopedLock lock(_mutex);
307 _strategy.clear();
308 }
309
310 bool empty() const
311 /// Checks if any delegates are registered at the delegate.
312 {
313 typename TMutex::ScopedLock lock(_mutex);
314 return _strategy.empty();
315 }
316
317protected:
318 struct NotifyAsyncParams
319 {
320 SharedPtr<TStrategy> ptrStrat;
321 const void* pSender;
322 TArgs args;
323 bool enabled;
324
325 NotifyAsyncParams(const void* pSend, const TArgs& a):ptrStrat(), pSender(pSend), args(a), enabled(true)
326 /// Default constructor reduces the need for TArgs to have an empty constructor, only copy constructor is needed.
327 {
328 }
329 };
330
331 ActiveMethod<TArgs, NotifyAsyncParams, AbstractEvent> _executeAsync;
332
333 TArgs executeAsyncImpl(const NotifyAsyncParams& par)
334 {
335 if (!par.enabled)
336 {
337 return par.args;
338 }
339
340 NotifyAsyncParams params = par;
341 TArgs retArgs(params.args);
342 params.ptrStrat->notify(params.pSender, retArgs);
343 return retArgs;
344 }
345
346 TStrategy _strategy; /// The strategy used to notify observers.
347 bool _enabled; /// Stores if an event is enabled. Notifies on disabled events have no effect
348 /// but it is possible to change the observers.
349 mutable TMutex _mutex;
350
351private:
352 AbstractEvent(const AbstractEvent& other);
353 AbstractEvent& operator = (const AbstractEvent& other);
354};
355
356
357template <class TStrategy, class TDelegate, class TMutex>
358class AbstractEvent<void, TStrategy, TDelegate, TMutex>
359{
360public:
361 typedef TDelegate* DelegateHandle;
362
363 AbstractEvent():
364 _executeAsync(this, &AbstractEvent::executeAsyncImpl),
365 _enabled(true)
366 {
367 }
368
369 AbstractEvent(const TStrategy& strat):
370 _executeAsync(this, &AbstractEvent::executeAsyncImpl),
371 _strategy(strat),
372 _enabled(true)
373 {
374 }
375
376 virtual ~AbstractEvent()
377 {
378 }
379
380 void operator += (const TDelegate& aDelegate)
381 /// Adds a delegate to the event.
382 ///
383 /// Exact behavior is determined by the TStrategy.
384 {
385 typename TMutex::ScopedLock lock(_mutex);
386 _strategy.add(aDelegate);
387 }
388
389 void operator -= (const TDelegate& aDelegate)
390 /// Removes a delegate from the event.
391 ///
392 /// If the delegate is not found, this function does nothing.
393 {
394 typename TMutex::ScopedLock lock(_mutex);
395 _strategy.remove(aDelegate);
396 }
397
398 DelegateHandle add(const TDelegate& aDelegate)
399 /// Adds a delegate to the event.
400 ///
401 /// Exact behavior is determined by the TStrategy.
402 ///
403 /// Returns a DelegateHandle which can be used in call to
404 /// remove() to remove the delegate.
405 {
406 typename TMutex::ScopedLock lock(_mutex);
407 return _strategy.add(aDelegate);
408 }
409
410 void remove(DelegateHandle delegateHandle)
411 /// Removes a delegate from the event using a DelegateHandle
412 /// returned by add().
413 ///
414 /// If the delegate is not found, this function does nothing.
415 {
416 typename TMutex::ScopedLock lock(_mutex);
417 _strategy.remove(delegateHandle);
418 }
419
420 void operator () (const void* pSender)
421 /// Shortcut for notify(pSender, args);
422 {
423 notify(pSender);
424 }
425
426 void operator () ()
427 /// Shortcut for notify(args).
428 {
429 notify(0);
430 }
431
432 void notify(const void* pSender)
433 /// Sends a notification to all registered delegates. The order is
434 /// determined by the TStrategy. This method is blocking. While executing,
435 /// the list of delegates may be modified. These changes don't
436 /// influence the current active notifications but are activated with
437 /// the next notify. If a delegate is removed during a notify(), the
438 /// delegate will no longer be invoked (unless it has already been
439 /// invoked prior to removal). If one of the delegates throws an exception,
440 /// the notify method is immediately aborted and the exception is propagated
441 /// to the caller.
442 {
443 Poco::ScopedLockWithUnlock<TMutex> lock(_mutex);
444
445 if (!_enabled) return;
446
447 // thread-safeness:
448 // copy should be faster and safer than blocking until
449 // execution ends
450 TStrategy strategy(_strategy);
451 lock.unlock();
452 strategy.notify(pSender);
453 }
454
455 ActiveResult<void> notifyAsync(const void* pSender)
456 /// Sends a notification to all registered delegates. The order is
457 /// determined by the TStrategy. This method is not blocking and will
458 /// immediately return. The delegates are invoked in a separate thread.
459 /// Call activeResult.wait() to wait until the notification has ended.
460 /// While executing, other objects can change the delegate list. These changes don't
461 /// influence the current active notifications but are activated with
462 /// the next notify. If a delegate is removed during a notify(), the
463 /// delegate will no longer be invoked (unless it has already been
464 /// invoked prior to removal). If one of the delegates throws an exception,
465 /// the execution is aborted and the exception is propagated to the caller.
466 {
467 NotifyAsyncParams params(pSender);
468 {
469 typename TMutex::ScopedLock lock(_mutex);
470
471 // thread-safeness:
472 // copy should be faster and safer than blocking until
473 // execution ends
474 // make a copy of the strategy here to guarantee that
475 // between notifyAsync and the execution of the method no changes can occur
476
477 params.ptrStrat = SharedPtr<TStrategy>(new TStrategy(_strategy));
478 params.enabled = _enabled;
479 }
480 ActiveResult<void> result = _executeAsync(params);
481 return result;
482 }
483
484 void enable()
485 /// Enables the event.
486 {
487 typename TMutex::ScopedLock lock(_mutex);
488 _enabled = true;
489 }
490
491 void disable()
492 /// Disables the event. notify and notifyAsnyc will be ignored,
493 /// but adding/removing delegates is still allowed.
494 {
495 typename TMutex::ScopedLock lock(_mutex);
496 _enabled = false;
497 }
498
499 bool isEnabled() const
500 {
501 typename TMutex::ScopedLock lock(_mutex);
502 return _enabled;
503 }
504
505 void clear()
506 /// Removes all delegates.
507 {
508 typename TMutex::ScopedLock lock(_mutex);
509 _strategy.clear();
510 }
511
512 bool empty() const
513 /// Checks if any delegates are registered at the delegate.
514 {
515 typename TMutex::ScopedLock lock(_mutex);
516 return _strategy.empty();
517 }
518
519protected:
520 struct NotifyAsyncParams
521 {
522 SharedPtr<TStrategy> ptrStrat;
523 const void* pSender;
524 bool enabled;
525
526 NotifyAsyncParams(const void* pSend):ptrStrat(), pSender(pSend), enabled(true)
527 /// Default constructor reduces the need for TArgs to have an empty constructor, only copy constructor is needed.
528 {
529 }
530 };
531
532 ActiveMethod<void, NotifyAsyncParams, AbstractEvent> _executeAsync;
533
534 void executeAsyncImpl(const NotifyAsyncParams& par)
535 {
536 if (!par.enabled)
537 {
538 return;
539 }
540
541 NotifyAsyncParams params = par;
542 params.ptrStrat->notify(params.pSender);
543 return;
544 }
545
546 TStrategy _strategy; /// The strategy used to notify observers.
547 bool _enabled; /// Stores if an event is enabled. Notifies on disabled events have no effect
548 /// but it is possible to change the observers.
549 mutable TMutex _mutex;
550
551private:
552 AbstractEvent(const AbstractEvent& other);
553 AbstractEvent& operator = (const AbstractEvent& other);
554};
555
556
557} // namespace Poco
558
559
560#endif // Foundation_AbstractFoundation_INCLUDED
561