Fawkes API  Fawkes Development Version
interface_listener.cpp
1 
2 /***************************************************************************
3  * interface_listener.cpp - BlackBoard event listener
4  *
5  * Created: Wed Nov 08 10:00:34 2007
6  * Copyright 2007-2008 Tim Niemueller [www.niemueller.de]
7  *
8  ****************************************************************************/
9 
10 /* This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version. A runtime exception applies to
14  * this software (see LICENSE.GPL_WRE file mentioned below for details).
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Library General Public License for more details.
20  *
21  * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
22  */
23 
24 #include <blackboard/interface_listener.h>
25 #include <core/exceptions/system.h>
26 #include <core/threading/mutex_locker.h>
27 #include <interface/interface.h>
28 
29 #include <cstdio>
30 #include <cstdlib>
31 #include <cstring>
32 
33 namespace fawkes {
34 
35 /** @class BlackBoardInterfaceListener <blackboard/interface_listener.h>
36  * BlackBoard interface listener.
37  * Derive this class if you want to be notified of specific BlackBoard
38  * events regarding instances of interfaces.
39  *
40  * The bb_interface_* methods are called during the appropriate operation. The
41  * operation that you carry out in this event handler really has to damn fast, or
42  * the performance of the whole system will suffer severely. For this reason use
43  * this notification facility only rarely and only register for the appropriate
44  * events.
45  *
46  * This class provides the basic infrastructure that can be used to build
47  * your own event handler. During the life time of your event handler your
48  * first add all the interfaces to the appropriate structures that you want
49  * to listen for and add the interface types where you want to be notified
50  * of creation events.
51  *
52  * The reader/writer added/removed and data changed notifications act upon a
53  * specific interface. Any modification done with any instance of the interface
54  * is reported to you. The interface creation notification deals only
55  * with types of interfaces. There is no interface deletion notification because
56  * the general idea is that you opened the interface by yourself for reading and
57  * thus the deletion will not happen before you close the interface.
58  *
59  * You will not be notified if you change data of the interface that you registered
60  * for or remove your own reading/writing instance of an interface.
61  *
62  * Here is a simple life cycle of a BlackBoard interface listener:
63  * First you create your interface that you want to listen for.
64  * The protected methods bbil_add_data_interface(), bbil_add_reader_interface(),
65  * bbil_add_writer_interface() and bbil_add_interface_create_type() have to
66  * be called with the appropriate interfaces <i>before</i> the event handler is
67  * actually registered with the interface manager! From
68  * now on will be called for the all registered events.
69  * In the end you unregister the event listener and <i>then</i> close any
70  * interface that you had registered before.
71  *
72  * It is important that you first unregister as an event handler before closing
73  * the interface. Otherwise it could happen that you close the interface and
74  * the instance is deleted and afterwards an event for that very interface
75  * happens. A warning is reported via the LibLogger whenever you forget this.
76  *
77  * @author Tim Niemueller
78  * @see BlackBoardInterfaceManager::register_listener()
79  * @see BlackBoardInterfaceManager::unregister_listener()
80  */
81 
82 /** Constructor.
83  * @param name_format format of name to identify the listener,
84  * see sprintf for supported tokens
85  */
87 {
88  va_list arg;
89  va_start(arg, name_format);
90  if (vasprintf(&name_, name_format, arg) == -1) {
91  throw OutOfMemoryException("BlackBoardInterfaceListener ctor: vasprintf() failed");
92  }
93  va_end(arg);
94 
95  bbil_queue_mutex_ = new Mutex();
96  bbil_maps_mutex_ = new Mutex();
97 }
98 
99 /** Destructor. */
101 {
102  free(name_);
103 
104  delete bbil_queue_mutex_;
105  delete bbil_maps_mutex_;
106 }
107 
108 /** Get BBIL name.
109  * @return BBIL name
110  */
111 const char *
113 {
114  return name_;
115 }
116 
117 /** BlackBoard data refreshed notification.
118  * This is called whenever the data in an interface that you registered for is
119  * refreshed. This happens when a writer calls the Interface::write(), regardless
120  * of whether any data have changed.
121  * @param interface interface instance that you supplied to bbil_add_data_interface()
122  * @see bb_interface_data_changed
123  */
124 void
126 {
127 }
128 
129 /** BlackBoard data changed notification.
130  * This is called whenever the data in an interface that you registered for is
131  * changed. This happens when a writer calls Interface::write() AND any data field
132  * has a different value than it had at the last write().
133  * Note that a change implies a refresh, so when data change, both this method AND
134  * @ref bb_interface_data_refreshed will be called!
135  * @param interface interface instance that you supplied to bbil_add_data_interface()
136  * @see bb_interface_data_refreshed
137  */
138 void
140 {
141 }
142 
143 /** BlackBoard message received notification.
144  * This is called whenever a message is received for this interface. This method is
145  * only called for writing instances of an interface, never on reading instances.
146  * If you have processed the message already, you can order that the message is not
147  * enqueued by returning false. Returning true will enqueue the message as usual.
148  * You should only do very (very!) quick tasks directly in this method, as it is
149  * out of the regular thread context and can harm performance of other plugins and
150  * the system as a whole. Note that if you decide to return false the message is
151  * not referenced. If you want to keep it longer you have to ref() it by yourself.
152  * An example where this would really make sense is a "STOP" message for the motor,
153  * which needs to be processed ASAP and maybe even waiting a couple of miliseconds
154  * for the next cycle is not acceptable.
155  * @param interface interface instance that you supplied to bbil_add_message_interface()
156  * @param message the message that was sent
157  * @return true to get the message enqueued afterwards as usual, false to prevent
158  * queuing of the message.
159  */
160 bool
162  Message * message) noexcept
163 {
164  return true;
165 }
166 
167 /** A reading instance has been opened for a watched interface.
168  * This is called whenever a reading instance of the interface you are watching
169  * is opened.
170  * @param interface interface instance that you supplied to bbil_add_reader_interface()
171  * @param instance_serial the instance serial of the reading instance that has just been
172  * added.
173  */
174 void
176  Uuid instance_serial) noexcept
177 {
178 }
179 
180 /** A reading instance has been closed for a watched interface.
181  * This is called whenever a reading instance of an interface you are watching
182  * is closed.
183  * @param interface interface instance that you supplied to bbil_add_reader_interface()
184  * @param instance_serial the instance serial of the reading instance that has just been
185  * removed.
186  */
187 void
189  Uuid instance_serial) noexcept
190 {
191 }
192 
193 /** A writing instance has been opened for a watched interface.
194  * This is called whenever a writing instance of the interface you are watching
195  * is opened.
196  * @param interface interface instance that you supplied to bbil_add_writer_interface()
197  * @param instance_serial the instance serial of the writing instance that has just been
198  * added.
199  */
200 void
202  Uuid instance_serial) noexcept
203 {
204 }
205 
206 /** A writing instance has been closed for a watched interface.
207  * This is called whenever a writing instance of an interface you are watching
208  * is closed.
209  * @param interface interface instance that you supplied to bbil_add_writer_interface()
210  * @param instance_serial the instance serial of the writing instance that has just been
211  * removed.
212  */
213 void
215  Uuid instance_serial) noexcept
216 {
217 }
218 
219 void
220 BlackBoardInterfaceListener::bbil_queue_add(QueueEntryType type,
221  bool op,
222  InterfaceMap & not_in_map,
223  Interface * interface,
224  const char * hint)
225 {
226  MutexLocker lock(bbil_queue_mutex_);
227 
228  if (op) {
229  if (not_in_map.find(interface->uid()) != not_in_map.end()) {
230  throw Exception("Interface %s already registered (%s)", interface->uid(), hint);
231  }
232  }
233  InterfaceQueue::iterator i;
234  for (i = bbil_queue_.begin(); i != bbil_queue_.end(); ++i) {
235  if ((i->type == type) && (*(i->interface) == *interface)) {
236  bbil_queue_.erase(i);
237  break;
238  }
239  }
240  QueueEntry qe = {type, op, interface};
241  bbil_queue_.push_back(qe);
242 }
243 
244 /** Add an interface to the data modification watch list.
245  * @param interface interface to watch for data modifications.
246  */
247 void
249 {
250  bbil_queue_add(DATA, true, bbil_maps_.data, interface, "data");
251 }
252 
253 /** Add an interface to the message received watch list.
254  * @param interface interface to watch for messages
255  */
256 void
258 {
259  if (!interface->is_writer()) {
260  throw Exception("Message received events can only be watched "
261  "on writing interface instances (%s)",
262  interface->uid());
263  }
264  bbil_queue_add(MESSAGES, true, bbil_maps_.messages, interface, "messages");
265 }
266 
267 /** Add an interface to the reader addition/removal watch list.
268  * This method does not mean that you add interfaces that you opened for reading
269  * but that you add an interface that you want to be informed for when reader
270  * addition/removal happens.
271  * @param interface interface to watch for addition/removal of readers
272  */
273 void
275 {
276  bbil_queue_add(READER, true, bbil_maps_.reader, interface, "reader");
277 }
278 
279 /** Add an interface to the writer addition/removal watch list.
280  * This method does not mean that you add interfaces that you opened for writing
281  * but that you add an interface that you want to be informed for when writer
282  * addition/removal happens.
283  * @param interface interface to watch for addition/removal of writers
284  */
285 void
287 {
288  bbil_queue_add(WRITER, true, bbil_maps_.writer, interface, "writer");
289 }
290 
291 /** Remove an interface to the data modification watch list.
292  * Only remove interfaces from the list when not currently registered to
293  * the BlackBoard or chaos and confusion will come upon you.
294  * @param interface interface to watch for data modifications.
295  */
296 void
298 {
299  bbil_queue_add(DATA, false, bbil_maps_.data, interface, "data");
300 }
301 
302 /** Remove an interface to the message received watch list.
303  * Only remove interfaces from the list when not currently registered to
304  * the BlackBoard or chaos and confusion will come upon you.
305  * @param interface interface to watch for messages
306  */
307 void
309 {
310  bbil_queue_add(MESSAGES, false, bbil_maps_.messages, interface, "messages");
311 }
312 
313 /** Remove an interface to the reader addition/removal watch list.
314  * Only remove interfaces from the list when not currently registered to
315  * the BlackBoard or chaos and confusion will come upon you.
316  * @param interface interface to watch for addition/removal of readers
317  */
318 void
320 {
321  bbil_queue_add(READER, false, bbil_maps_.reader, interface, "reader");
322 }
323 
324 /** Remove an interface to the writer addition/removal watch list.
325  * Only remove interfaces from the list when not currently registered to
326  * the BlackBoard or chaos and confusion will come upon you.
327  * @param interface interface to watch for addition/removal of writers
328  */
329 void
331 {
332  bbil_queue_add(WRITER, false, bbil_maps_.writer, interface, "writer");
333 }
334 
336 BlackBoardInterfaceListener::bbil_acquire_queue() noexcept
337 {
338  bbil_queue_mutex_->lock();
339  return bbil_queue_;
340 }
341 
342 void
343 BlackBoardInterfaceListener::bbil_release_queue(BlackBoard::ListenerRegisterFlag flag) noexcept
344 {
345  bbil_maps_mutex_->lock();
346 
347  InterfaceQueue::iterator i = bbil_queue_.begin();
348  while (i != bbil_queue_.end()) {
349  if (i->op) { // add
350  switch (i->type) {
351  case DATA:
352  if (flag & BlackBoard::BBIL_FLAG_DATA) {
353  bbil_maps_.data[i->interface->uid()] = i->interface;
354  i = bbil_queue_.erase(i);
355  } else
356  ++i;
357  break;
358 
359  case MESSAGES:
360  if (flag & BlackBoard::BBIL_FLAG_MESSAGES) {
361  bbil_maps_.messages[i->interface->uid()] = i->interface;
362  i = bbil_queue_.erase(i);
363  } else
364  ++i;
365  break;
366 
367  case READER:
368  if (flag & BlackBoard::BBIL_FLAG_READER) {
369  bbil_maps_.reader[i->interface->uid()] = i->interface;
370  i = bbil_queue_.erase(i);
371  } else
372  ++i;
373  break;
374 
375  case WRITER:
376  if (flag & BlackBoard::BBIL_FLAG_WRITER) {
377  bbil_maps_.writer[i->interface->uid()] = i->interface;
378  i = bbil_queue_.erase(i);
379  } else
380  ++i;
381  break;
382 
383  default: ++i; break;
384  }
385  } else { // remove
386  switch (i->type) {
387  case DATA:
388  if (flag & BlackBoard::BBIL_FLAG_DATA) {
389  bbil_maps_.data.erase(i->interface->uid());
390  i = bbil_queue_.erase(i);
391  } else
392  ++i;
393  break;
394 
395  case MESSAGES:
396  if (flag & BlackBoard::BBIL_FLAG_MESSAGES) {
397  bbil_maps_.messages.erase(i->interface->uid());
398  i = bbil_queue_.erase(i);
399  } else
400  ++i;
401  break;
402 
403  case READER:
404  if (flag & BlackBoard::BBIL_FLAG_READER) {
405  bbil_maps_.reader.erase(i->interface->uid());
406  i = bbil_queue_.erase(i);
407  } else
408  ++i;
409  break;
410 
411  case WRITER:
412  if (flag & BlackBoard::BBIL_FLAG_WRITER) {
413  bbil_maps_.writer.erase(i->interface->uid());
414  i = bbil_queue_.erase(i);
415  } else
416  ++i;
417  break;
418 
419  default: ++i; break;
420  }
421  }
422  }
423 
424  bbil_maps_mutex_->unlock();
425  bbil_queue_mutex_->unlock();
426 }
427 
428 const BlackBoardInterfaceListener::InterfaceMaps &
429 BlackBoardInterfaceListener::bbil_acquire_maps() noexcept
430 {
431  bbil_maps_mutex_->lock();
432  return bbil_maps_;
433 }
434 
435 void
436 BlackBoardInterfaceListener::bbil_release_maps() noexcept
437 {
438  bbil_queue_mutex_->lock();
439 
440  InterfaceMap::iterator i;
441  for (i = bbil_maps_.data.begin(); i != bbil_maps_.data.end(); ++i) {
442  QueueEntry qe = {DATA, true, i->second};
443  bbil_queue_.push_back(qe);
444  }
445  for (i = bbil_maps_.messages.begin(); i != bbil_maps_.messages.end(); ++i) {
446  QueueEntry qe = {MESSAGES, true, i->second};
447  bbil_queue_.push_back(qe);
448  }
449  for (i = bbil_maps_.reader.begin(); i != bbil_maps_.reader.end(); ++i) {
450  QueueEntry qe = {READER, true, i->second};
451  bbil_queue_.push_back(qe);
452  }
453  for (i = bbil_maps_.writer.begin(); i != bbil_maps_.writer.end(); ++i) {
454  QueueEntry qe = {WRITER, true, i->second};
455  bbil_queue_.push_back(qe);
456  }
457 
458  bbil_maps_.data.clear();
459  bbil_maps_.messages.clear();
460  bbil_maps_.reader.clear();
461  bbil_maps_.writer.clear();
462 
463  bbil_queue_mutex_->unlock();
464  bbil_maps_mutex_->unlock();
465 }
466 
467 Interface *
468 BlackBoardInterfaceListener::bbil_find_interface(const char *iuid, InterfaceMap &map)
469 {
470  MutexLocker lock(bbil_maps_mutex_);
471  InterfaceMap::iterator i;
472  if ((i = map.find((char *)iuid)) != map.end()) {
473  return i->second;
474  } else {
475  return NULL;
476  }
477 }
478 
479 /** Get interface instance for given UID.
480  * A data modification notification is about to be triggered. For this the
481  * interface instance that has been added to the event listener is determined.
482  * @param iuid interface unique ID
483  * @return interface instance, NULL if not in list (non-fatal error)
484  */
485 Interface *
487 {
488  return bbil_find_interface(iuid, bbil_maps_.data);
489 }
490 
491 /** Get interface instance for given UID.
492  * A message received notification is about to be triggered. For this the
493  * interface instance that has been added to the event listener is determined.
494  * @param iuid interface unique ID
495  * @return interface instance, NULL if not in list (non-fatal error)
496  */
497 Interface *
499 {
500  return bbil_find_interface(iuid, bbil_maps_.messages);
501 }
502 
503 /** Get interface instance for given UID.
504  * A reader notification is about to be triggered. For this the
505  * interface instance that has been added to the event listener is determined.
506  * @param iuid interface unique ID
507  * @return interface instance, NULL if not in list (non-fatal error)
508  */
509 Interface *
511 {
512  return bbil_find_interface(iuid, bbil_maps_.reader);
513 }
514 
515 /** Get interface instance for given UID.
516  * A writer notification is about to be triggered. For this the
517  * interface instance that has been added to the event listener is determined.
518  * @param iuid interface unique ID
519  * @return interface instance, NULL if not in list (non-fatal error)
520  */
521 Interface *
523 {
524  return bbil_find_interface(iuid, bbil_maps_.writer);
525 }
526 
527 } // end namespace fawkes
Interface * bbil_reader_interface(const char *iuid) noexcept
Get interface instance for given UID.
virtual void bb_interface_writer_added(Interface *interface, Uuid instance_serial) noexcept
A writing instance has been opened for a watched interface.
@ MESSAGES
Message received event entry.
@ DATA
Data changed event entry.
void bbil_add_reader_interface(Interface *interface)
Add an interface to the reader addition/removal watch list.
void bbil_remove_reader_interface(Interface *interface)
Remove an interface to the reader addition/removal watch list.
void bbil_add_message_interface(Interface *interface)
Add an interface to the message received watch list.
void bbil_remove_writer_interface(Interface *interface)
Remove an interface to the writer addition/removal watch list.
virtual bool bb_interface_message_received(Interface *interface, Message *message) noexcept
BlackBoard message received notification.
virtual void bb_interface_data_refreshed(Interface *interface) noexcept
BlackBoard data refreshed notification.
Interface * bbil_message_interface(const char *iuid) noexcept
Get interface instance for given UID.
void bbil_remove_message_interface(Interface *interface)
Remove an interface to the message received watch list.
virtual void bb_interface_data_changed(Interface *interface) noexcept
BlackBoard data changed notification.
void bbil_add_writer_interface(Interface *interface)
Add an interface to the writer addition/removal watch list.
virtual void bb_interface_writer_removed(Interface *interface, Uuid instance_serial) noexcept
A writing instance has been closed for a watched interface.
virtual void bb_interface_reader_added(Interface *interface, Uuid instance_serial) noexcept
A reading instance has been opened for a watched interface.
const char * bbil_name() const
Get BBIL name.
Interface * bbil_writer_interface(const char *iuid) noexcept
Get interface instance for given UID.
std::list< QueueEntry > InterfaceQueue
Queue of additions/removal of interfaces.
void bbil_remove_data_interface(Interface *interface)
Remove an interface to the data modification watch list.
virtual void bb_interface_reader_removed(Interface *interface, Uuid instance_serial) noexcept
A reading instance has been closed for a watched interface.
Interface * bbil_data_interface(const char *iuid) noexcept
Get interface instance for given UID.
void bbil_add_data_interface(Interface *interface)
Add an interface to the data modification watch list.
BlackBoardInterfaceListener(const char *name_format,...)
Constructor.
virtual ~BlackBoardInterfaceListener()
Destructor.
ListenerRegisterFlag
Flags to constrain listener registration/updates.
Definition: blackboard.h:87
@ BBIL_FLAG_READER
consider reader events
Definition: blackboard.h:90
@ BBIL_FLAG_DATA
consider data events
Definition: blackboard.h:88
@ BBIL_FLAG_WRITER
consider writer events
Definition: blackboard.h:91
@ BBIL_FLAG_MESSAGES
consider message received events
Definition: blackboard.h:89
Base class for exceptions in Fawkes.
Definition: exception.h:36
Base class for all Fawkes BlackBoard interfaces.
Definition: interface.h:80
bool is_writer() const
Check if this is a writing instance.
Definition: interface.cpp:445
const char * uid() const
Get unique identifier of interface.
Definition: interface.cpp:686
Base class for all messages passed through interfaces in Fawkes BlackBoard.
Definition: message.h:44
Mutex locking helper.
Definition: mutex_locker.h:34
Mutex mutual exclusion lock.
Definition: mutex.h:33
void lock()
Lock this mutex.
Definition: mutex.cpp:87
void unlock()
Unlock the mutex.
Definition: mutex.cpp:131
System ran out of memory and desired operation could not be fulfilled.
Definition: system.h:32
A convenience class for universally unique identifiers (UUIDs).
Definition: uuid.h:29
Fawkes library namespace.
InterfaceMap writer
Writer event subscriptions.
InterfaceMap messages
Message received event subscriptions.
InterfaceMap data
Data event subscriptions.
InterfaceMap reader
Reader event subscriptions.