You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

event.py 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. # Copyright (c) 2016 Anki, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License in the file LICENSE.txt or at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. '''Event dispatch system.
  15. The SDK is based around the dispatch and observation of events.
  16. Objects inheriting from the :class:`Dispatcher` generate and
  17. dispatch events as the state of the robot and its world are updated.
  18. For example the :class:`cozmo.objects.LightCube` class generates an
  19. :class:`~cozmo.objects.EvtObjectTapped` event anytime the cube the object
  20. represents is tapped.
  21. The event can be observed in a number of different ways:
  22. #. By calling the :meth:`~Dispatcher.wait_for` method on the object to observe.
  23. This will wait until the specific event has been sent to that object and
  24. return the generated event.
  25. #. By calling :meth:`~Dispatcher.add_event_handler` on the object
  26. to observe, which will cause the supplied function to be called every time
  27. the specified event occurs (use the :func:`oneshot` decorator
  28. to only have the handler called once)
  29. #. By sub-classing a type and implementing a receiver method.
  30. For example, subclass the :class:`cozmo.objects.LightCube` type and implement `evt_object_tapped`.
  31. Note that the factory attribute would need to be updated on the
  32. generating class for your type to be used by the SDK.
  33. For example, :attr:`~cozmo.world.World.light_cube_factory` in this example.
  34. #. By subclassing a type and implementing a default receiver method.
  35. Events not dispatched to an explicit receiver method are dispatched to
  36. `recv_default_handler`.
  37. Events are dispatched to a target object (by calling :meth:`dispatch_event`
  38. on the receiving object). In line with the above, upon receiving an event,
  39. the object will:
  40. #. Dispatch the event to any handlers which have explicitly registered interest
  41. in the event (or a superclass of the event) via
  42. :meth:`~Dispatcher.add_event_handler` or via :meth:`Dispatcher.wait_for`
  43. #. Dispatch the event to any "children" of the object (see below)
  44. #. Dispatch the event to method handlers on the receiving object, or the
  45. `recv_default_handler` if it has no matching handler
  46. #. Dispatch the event to the parent of the object (if any), and in turn onto
  47. the parent's parents.
  48. Any handler may raise a :class:`~cozmo.exceptions.StopPropogation` exception
  49. to prevent the event reaching any subsequent handlers (but generally should
  50. have no need to do so).
  51. Child objects receive all events that are sent to the originating object
  52. (which may have multiple children).
  53. Originating objects may have one parent object, which receives all events sent
  54. to its child.
  55. For example, :class:`cozmo.robot.Robot` creates a :class:`cozmo.world.World`
  56. object and sets itself as a parent and the World as the child; both receive
  57. events sent to the other.
  58. The World class creates individual :class:`cozmo.objects.ObservableObject` objects
  59. as they are discovered and makes itself a parent, so as to receive all events
  60. sent to the child. However, it does not make those ObservableObject objects children
  61. for the sake of message dispatch as they only need to receive a small subset
  62. of messages the World object receives.
  63. '''
  64. # __all__ should order by constants, event classes, other classes, functions.
  65. __all__ = ['Event', 'Dispatcher', 'Filter', 'Handler',
  66. 'oneshot', 'filter_handler', 'wait_for_first']
  67. import asyncio
  68. import collections
  69. import inspect
  70. import re
  71. import weakref
  72. from . import base
  73. from . import exceptions
  74. from . import logger
  75. # from https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
  76. _first_cap_re = re.compile('(.)([A-Z][a-z]+)')
  77. _all_cap_re = re.compile('([a-z0-9])([A-Z])')
  78. def _uncamelcase(name):
  79. s1 = _first_cap_re.sub(r'\1_\2', name)
  80. return _all_cap_re.sub(r'\1_\2', s1).lower()
  81. registered_events = {}
  82. active_dispatchers = weakref.WeakSet()
  83. class _rprop:
  84. def __init__(self, value):
  85. self._value = value
  86. def __get__(self, instance, owner):
  87. return self._value
  88. class docstr(str):
  89. @property
  90. def __doc__(self):
  91. return self.__str__()
  92. class _AutoRegister(type):
  93. '''helper to automatically register event classes wherever they're defined
  94. without requiring a class decorator'''
  95. def __new__(mcs, name, bases, attrs, **kw):
  96. if name in ('Event',):
  97. return super().__new__(mcs, name, bases, attrs, **kw)
  98. if not (name.startswith('Evt') or name.startswith('_Evt') or name.startswith('_Msg')):
  99. raise ValueError('Event class names must begin with "Evt (%s)"' % name)
  100. if '__doc__' not in attrs:
  101. raise ValueError('Event classes must have a docstring')
  102. props = set()
  103. for base in bases:
  104. if hasattr(base, '_props'):
  105. props.update(base._props)
  106. newattrs = {'_internal': False}
  107. for k, v in attrs.items():
  108. if k[0] == '_':
  109. newattrs[k] = v
  110. continue
  111. if k in props:
  112. raise ValueError("Event class %s duplicates property %s defined in superclass" % (mcs, k))
  113. props.add(k)
  114. newattrs[k] = docstr(v)
  115. newattrs['_props'] = props
  116. newattrs['_props_sorted'] = sorted(props)
  117. if name[0] == '_':
  118. newattrs['_internal'] = True
  119. name = name[1:]
  120. # create a read only property for the event name
  121. newattrs['event_name'] = _rprop(name)
  122. return super().__new__(mcs, name, bases, newattrs, **kw)
  123. def __init__(cls, name, bases, attrs, **kw):
  124. if name in registered_events:
  125. raise ValueError("Duplicate event name %s (%s duplicated by %s)"
  126. % (name, _full_qual_name(cls), _full_qual_name(registered_events[name])))
  127. registered_events[name] = cls
  128. super().__init__(name, bases, attrs, **kw)
  129. def _full_qual_name(obj):
  130. return obj.__module__ + '.' + obj.__qualname__
  131. class Event(metaclass=_AutoRegister):
  132. '''An event representing an action that has occurred.
  133. Instances of an Event have attributes set to values passed to the event.
  134. For example, :class:`cozmo.objects.EvtObjectTapped` defines obj and tap_count
  135. parameters which can be accessed as ``evt.obj`` and ``evt.tap_count``.
  136. '''
  137. #_first_raised_by = "The object that generated the event"
  138. #_last_raised_by = "The object that last relayed the event to the dispatched handler"
  139. #pylint: disable=no-member
  140. # Event Metaclass raises "no-member" pylint errors in pylint within this scope.
  141. def __init__(self, **kwargs):
  142. unset = self._props.copy()
  143. for k, v in kwargs.items():
  144. if k not in self._props:
  145. raise ValueError("Event %s has no parameter called %s" % (self.event_name, k))
  146. setattr(self, k, v)
  147. unset.remove(k)
  148. for k in unset:
  149. setattr(self, k, None)
  150. self._delivered_to = set()
  151. def __repr__(self):
  152. kvs = {'name': self.event_name}
  153. for k in self._props_sorted:
  154. kvs[k] = getattr(self, k)
  155. return '<%s %s>' % (self.__class__.__name__, ' '.join(['%s=%s' % kv for kv in kvs.items()]),)
  156. def _params(self):
  157. return {k: getattr(self, k) for k in self._props}
  158. @classmethod
  159. def _handler_method_name(cls):
  160. name = 'recv_' + _uncamelcase(cls.event_name)
  161. if cls._internal:
  162. name = '_' + name
  163. return name
  164. def _dispatch_to_func(self, f):
  165. return f(self, **self._params())
  166. def _dispatch_to_obj(self, obj, fallback_to_default=True):
  167. for cls in self._parent_event_classes():
  168. f = getattr(obj, cls._handler_method_name(), None)
  169. if f and not self._is_filtered(f):
  170. return self._dispatch_to_func(f)
  171. if fallback_to_default:
  172. name = 'recv_default_handler'
  173. if self._internal:
  174. name = '_' + name
  175. f = getattr(obj, name, None)
  176. if f and not self._is_filtered(f):
  177. return f(self, **self._params())
  178. def _dispatch_to_future(self, fut):
  179. if not fut.done():
  180. fut.set_result(self)
  181. def _is_filtered(self, f):
  182. filters = getattr(f, '_handler_filters', None)
  183. if filters is None:
  184. return False
  185. for filter in filters:
  186. if filter(self):
  187. return False
  188. return True
  189. def _parent_event_classes(self):
  190. for cls in self.__class__.__mro__:
  191. if cls != Event and issubclass(cls, Event):
  192. yield cls
  193. def _register_dynamic_event_type(event_name, attrs):
  194. return type(event_name, (Event,), attrs)
  195. class Handler(collections.namedtuple('Handler', 'obj evt f')):
  196. '''A Handler is returned by :meth:`Dispatcher.add_event_handler`
  197. The handler can be disabled at any time by calling its :meth:`disable`
  198. method.
  199. '''
  200. __slots__ = ()
  201. def disable(self):
  202. '''Removes the handler from the object it was originally registered with.'''
  203. return self.obj.remove_event_handler(self.evt, self.f)
  204. @property
  205. def oneshot(self):
  206. '''bool: True if the wrapped handler function will only be called once.'''
  207. return getattr(self.f, '_oneshot_handler', False)
  208. class NullHandler(Handler):
  209. def disable(self):
  210. pass
  211. class Dispatcher(base.Base):
  212. '''Mixin to provide event dispatch handling.'''
  213. def __init__(self, *a, dispatch_parent=None, loop=None, **kw):
  214. super().__init__(**kw)
  215. active_dispatchers.add(self)
  216. self._dispatch_parent = dispatch_parent
  217. self._dispatch_children = []
  218. self._dispatch_handlers = collections.defaultdict(list)
  219. if not loop:
  220. raise ValueError("Loop was not supplied to "+self.__class__.__name__)
  221. self._loop = loop or asyncio.get_event_loop()
  222. self._dispatcher_running = True
  223. def _set_parent_dispatcher(self, parent):
  224. self._dispatch_parent = parent
  225. def _add_child_dispatcher(self, child):
  226. self._dispatch_children.append(child)
  227. def _stop_dispatcher(self):
  228. """Stop dispatching events - call before closing the connection to prevent stray dispatched events"""
  229. self._dispatcher_running = False
  230. def add_event_handler(self, event, f):
  231. """Register an event handler to be notified when this object receives a type of Event.
  232. Expects a subclass of Event as the first argument. If the class has
  233. subclasses then the handler will be notified for events of that subclass too.
  234. For example, adding a handler for :class:`~cozmo.action.EvtActionCompleted`
  235. will cause the handler to also be notified for
  236. :class:`~cozmo.anim.EvtAnimationCompleted` as it's a subclass.
  237. Callable handlers (e.g. functions) are called with a first argument
  238. containing an Event instance and the remaining keyword arguments set as
  239. the event parameters.
  240. For example, ``def my_ontap_handler(evt, *, obj, tap_count, **kwargs)``
  241. or ``def my_ontap_handler(evt, obj=None, tap_count=None, **kwargs)``
  242. It's recommended that a ``**kwargs`` parameter be included in the
  243. definition so that future expansion of event parameters do not cause
  244. the handler to fail.
  245. Callable handlers may raise an events.StopProgation exception to prevent
  246. other handlers listening to the same event from being triggered.
  247. :class:`asyncio.Future` handlers are called with a result set to the event.
  248. Args:
  249. event (:class:`Event`): A subclass of :class:`Event` (not an instance of that class)
  250. f (callable): A callable or :class:`asyncio.Future` to execute when the event is received
  251. Raises:
  252. :class:`TypeError`: An invalid event type was supplied
  253. """
  254. if not issubclass(event, Event):
  255. raise TypeError("event must be a subclass of Event (not an instance)")
  256. if not self._dispatcher_running:
  257. return NullHandler(self, event, f)
  258. if isinstance(f, asyncio.Future):
  259. # futures can only be called once.
  260. f = oneshot(f)
  261. handler = Handler(self, event, f)
  262. self._dispatch_handlers[event.event_name].append(handler)
  263. return handler
  264. def remove_event_handler(self, event, f):
  265. """Remove an event handler for this object.
  266. Args:
  267. event (:class:`Event`): The event class, or an instance thereof,
  268. used with register_event_handler.
  269. f (callable or :class:`Handler`): The callable object that was
  270. passed as a handler to :meth:`add_event_handler`, or a
  271. :class:`Handler` instance that was returned by
  272. :meth:`add_event_handler`.
  273. Raises:
  274. :class:`ValueError`: No matching handler found.
  275. """
  276. if not (isinstance(event, Event) or (isinstance(event, type) and issubclass(event, Event))):
  277. raise TypeError("event must be a subclasss or instance of Event")
  278. if isinstance(f, Handler):
  279. for i, h in enumerate(self._dispatch_handlers[event.event_name]):
  280. if h == f:
  281. del self._dispatch_handlers[event.event_name][i]
  282. return
  283. else:
  284. for i, h in enumerate(self._dispatch_handlers[event.event_name]):
  285. if h.f == f:
  286. del self._dispatch_handlers[event.event_name][i]
  287. return
  288. raise ValueError("No matching handler found for %s (%s)" % (event.event_name, f) )
  289. def dispatch_event(self, event, **kw):
  290. '''Dispatches a single event to registered handlers.
  291. Not generally called from user-facing code.
  292. Args:
  293. event (:class:`Event`): An class or instance of :class:`Event`
  294. kw (dict): If a class is passed to event, then the remaining keywords
  295. are passed to it to create an instance of the event.
  296. Returns:
  297. A :class:`asyncio.Task` or :class:`asyncio.Future` that will
  298. complete once all event handlers have been called.
  299. Raises:
  300. :class:`TypeError` if an invalid event is supplied.
  301. '''
  302. if not self._dispatcher_running:
  303. return
  304. event_cls = event
  305. if not isinstance(event, Event):
  306. if not isinstance(event, type) or not issubclass(event, Event):
  307. raise TypeError("events must be a subclass or instance of Event")
  308. # create an instance of the event if passed a class
  309. event = event(**kw)
  310. else:
  311. event_cls = event.__class__
  312. if id(self) in event._delivered_to:
  313. return
  314. event._delivered_to.add(id(self))
  315. handlers = set()
  316. for cls in event._parent_event_classes():
  317. for handler in self._dispatch_handlers[cls.event_name]:
  318. if event._is_filtered(handler.f):
  319. continue
  320. if getattr(handler.f, '_oneshot_handler', False):
  321. # Disable oneshot events prior to actual dispatch
  322. handler.disable()
  323. handlers.add(handler)
  324. return asyncio.ensure_future(self._dispatch_event(event, handlers), loop=self._loop)
  325. async def _dispatch_event(self, event, handlers):
  326. # iterate through events from child->parent
  327. # update the dispatched_to set for each event so each handler
  328. # only receives the most specific event if they are monitoring for both.
  329. try:
  330. # dispatch to local handlers
  331. for handler in handlers:
  332. if isinstance(handler.f, asyncio.Future):
  333. event._dispatch_to_future(handler.f)
  334. else:
  335. result = event._dispatch_to_func(handler.f)
  336. if asyncio.iscoroutine(result):
  337. await result
  338. # dispatch to children
  339. for child in self._dispatch_children:
  340. child.dispatch_event(event)
  341. # dispatch to self methods
  342. result = event._dispatch_to_obj(self)
  343. if asyncio.iscoroutine(result):
  344. await result
  345. # dispatch to parent dispatcher
  346. if self._dispatch_parent:
  347. self._dispatch_parent.dispatch_event(event)
  348. except exceptions.StopPropogation:
  349. pass
  350. def _abort_event_futures(self, exc):
  351. '''Sets an exception on all pending Future handlers
  352. This prevents coroutines awaiting a Future from blocking forever
  353. should a hard failure occur with the connection.
  354. '''
  355. handlers = set()
  356. for evh in self._dispatch_handlers.values():
  357. for h in evh:
  358. handlers.add(h)
  359. for handler in handlers:
  360. if isinstance(handler.f, asyncio.Future):
  361. if not handler.f.done():
  362. handler.f.set_exception(exc)
  363. handler.disable()
  364. async def wait_for(self, event_or_filter, timeout=30):
  365. '''Waits for the specified event to be sent to the current object.
  366. Args:
  367. event_or_filter (:class:`Event`): Either a :class:`Event` class
  368. or a :class:`Filter` instance to wait to trigger
  369. timeout: Maximum time to wait for the event. Pass None to wait indefinitely.
  370. Returns:
  371. The :class:`Event` instance that was dispatched
  372. Raises:
  373. :class:`asyncio.TimeoutError`
  374. '''
  375. f = asyncio.Future(loop=self._loop) # replace with loop.create_future in 3.5.2
  376. # TODO: add a timer that logs every 5 seconds that the event is still being
  377. # waited on. Will help novice programmers realize why their program is hanging.
  378. f = oneshot(f)
  379. if isinstance(event_or_filter, Filter):
  380. f = filter_handler(event_or_filter)(f)
  381. event = event_or_filter._event
  382. else:
  383. event = event_or_filter
  384. self.add_event_handler(event, f)
  385. if timeout:
  386. return await asyncio.wait_for(f, timeout, loop=self._loop)
  387. return await f
  388. def oneshot(f):
  389. '''Event handler decorator; causes the handler to only be dispatched to once.'''
  390. f._oneshot_handler = True
  391. return f
  392. def filter_handler(event, **filters):
  393. '''Decorates a handler function or Future to only be called if a filter is matched.
  394. A handler may apply multiple separate filters; the handlers will be called
  395. if any of those filters matches.
  396. For example::
  397. # Handle only if the anim_majorwin animation completed
  398. @filter_handler(cozmo.anim.EvtAnimationCompleted, animation_name="anim_majorwin")
  399. # Handle only when the observed object is a LightCube
  400. @filter_handler(cozmo.objects.EvtObjectObserved, obj=lambda obj: isinstance(cozmo.objects.LightCube))
  401. Args:
  402. event (:class:`Event`): The event class to match on
  403. filters (dict): Zero or more event parameters to filter on. Values may
  404. be either strings for exact matches, or functions which accept the
  405. value as the first argument and return a bool indicating whether
  406. the value passes the filter.
  407. '''
  408. if isinstance(event, Filter):
  409. if len(filters) != 0:
  410. raise ValueError("Cannot supply filter values when passing a Filter as the first argument")
  411. filter = event
  412. else:
  413. filter = Filter(event, **filters)
  414. def filter_property(f):
  415. if hasattr(f, '_handler_filters'):
  416. f._handler_filters.append(filter)
  417. else:
  418. f._handler_filters = [filter]
  419. return f
  420. return filter_property
  421. class Filter:
  422. """Provides fine-grain filtering of events for dispatch.
  423. See the ::func::`filter_handler` method for further details.
  424. """
  425. def __init__(self, event, **filters):
  426. if not issubclass(event, Event):
  427. raise TypeError("event must be a subclass of Event (not an instance)")
  428. self._event = event
  429. self._filters = filters
  430. for key in self._filters.keys():
  431. if not hasattr(event, key):
  432. raise AttributeError("Event %s does not define property %s", event.__name__, key)
  433. def __setattr__(self, key, val):
  434. if key[0] == '_':
  435. return super().__setattr__(key, val)
  436. if not hasattr(self._event, key):
  437. raise AttributeError("Event %s does not define property %s", self._event.__name__, key)
  438. self._filters[key] = val
  439. def __call__(self, evt):
  440. for prop, filter in self._filters.items():
  441. val = getattr(evt, prop)
  442. if callable(filter):
  443. if not filter(val):
  444. return False
  445. elif val != filter:
  446. return False
  447. return True
  448. async def wait_for_first(*futures, discard_remaining=True, loop=None):
  449. '''Wait the first of a set of futures to complete.
  450. Eg::
  451. event = cozmo.event.wait_for_first(
  452. coz.world.wait_for_new_cube(),
  453. playing_anim.wait_for(cozmo.anim.EvtAnimationCompleted)
  454. )
  455. If more than one completes during a single event loop run, then
  456. if any of those results are not exception, one of them will be selected
  457. (at random, as determined by ``set.pop``) to be returned, else one
  458. of the result exceptions will be raised instead.
  459. Args:
  460. futures (list of :class:`asyncio.Future`): The futures or coroutines to wait on.
  461. discard_remaining (bool): Cancel or discard the results of the futures
  462. that did not return first.
  463. loop (:class:`asyncio.BaseEventLoop`): The event loop to wait on.
  464. Returns:
  465. The first result, or raised exception
  466. '''
  467. done, pending = await asyncio.wait(futures, loop=loop, return_when=asyncio.FIRST_COMPLETED)
  468. # collect the results from all "done" futures; only one will be returned
  469. result = None
  470. for fut in done:
  471. try:
  472. fut_result = fut.result()
  473. if result is None or isinstance(result, BaseException):
  474. result = fut_result
  475. except Exception as exc:
  476. if result is None:
  477. result = exc
  478. if discard_remaining:
  479. # cancel the pending futures
  480. for fut in pending:
  481. fut.cancel()
  482. if isinstance(result, BaseException):
  483. raise result
  484. return result
  485. def _abort_futures(exc):
  486. '''Trigger the exception handler for all pending Future handlers.'''
  487. for obj in active_dispatchers:
  488. obj._abort_event_futures(exc)