|
1 | 1 | """Maps OSC addresses to handler functions""" |
2 | 2 |
|
| 3 | +import asyncio |
3 | 4 | import collections |
4 | 5 | import inspect |
5 | 6 | import logging |
@@ -80,16 +81,67 @@ def invoke( |
80 | 81 | else: |
81 | 82 | return self.callback(message.address, *message) |
82 | 83 |
|
| 84 | + async def async_invoke( |
| 85 | + self, client_address: Tuple[str, int], message: OscMessage |
| 86 | + ) -> Union[None, AnyStr, Tuple[AnyStr, ArgValue]]: |
| 87 | + """Invokes the associated callback function (asynchronously) |
| 88 | +
|
| 89 | + Args: |
| 90 | + client_address: Address match that causes the invocation |
| 91 | + message: Message causing invocation |
| 92 | + Returns: |
| 93 | + The result of the handler function can be None, a string OSC address, or a tuple of the OSC address |
| 94 | + and arguments. |
| 95 | + """ |
| 96 | + cb = self.callback |
| 97 | + is_async = inspect.iscoroutinefunction(cb) |
| 98 | + |
| 99 | + if self.needs_reply_address: |
| 100 | + if self.args: |
| 101 | + if is_async: |
| 102 | + return await cb( |
| 103 | + client_address, message.address, self.args, *message |
| 104 | + ) |
| 105 | + else: |
| 106 | + return cb(client_address, message.address, self.args, *message) |
| 107 | + else: |
| 108 | + if is_async: |
| 109 | + return await cb(client_address, message.address, *message) |
| 110 | + else: |
| 111 | + return cb(client_address, message.address, *message) |
| 112 | + else: |
| 113 | + if self.args: |
| 114 | + if is_async: |
| 115 | + return await cb(message.address, self.args, *message) |
| 116 | + else: |
| 117 | + return cb(message.address, self.args, *message) |
| 118 | + else: |
| 119 | + if is_async: |
| 120 | + return await cb(message.address, *message) |
| 121 | + else: |
| 122 | + return cb(message.address, *message) |
| 123 | + |
83 | 124 |
|
84 | 125 | class Dispatcher(object): |
85 | 126 | """Maps Handlers to OSC addresses and dispatches messages to the handler on matched addresses |
86 | 127 |
|
87 | 128 | Maps OSC addresses to handler functions and invokes the correct handler when a message comes in. |
88 | 129 | """ |
89 | 130 |
|
90 | | - def __init__(self) -> None: |
| 131 | + def __init__(self, strict_timing: bool = True) -> None: |
| 132 | + """Initialize the dispatcher. |
| 133 | +
|
| 134 | + Args: |
| 135 | + strict_timing: Whether to automatically schedule messages with future timetags. |
| 136 | + If True (default), the dispatcher will wait (using sleep) until the specified |
| 137 | + timetag before invoking handlers. |
| 138 | + If False, messages are dispatched immediately regardless of their timetag. |
| 139 | + Disabling this can prevent memory/thread accumulation issues when receiving |
| 140 | + many future-dated messages. |
| 141 | + """ |
91 | 142 | self._map: DefaultDict[str, List[Handler]] = collections.defaultdict(list) |
92 | 143 | self._default_handler: Optional[Handler] = None |
| 144 | + self._strict_timing = strict_timing |
93 | 145 |
|
94 | 146 | def map( |
95 | 147 | self, |
@@ -272,7 +324,7 @@ def call_handlers_for_packet( |
272 | 324 | if not handlers: |
273 | 325 | continue |
274 | 326 | # If the message is to be handled later, then so be it. |
275 | | - if timed_msg.time > now: |
| 327 | + if self._strict_timing and timed_msg.time > now: |
276 | 328 | time.sleep(timed_msg.time - now) |
277 | 329 | for handler in handlers: |
278 | 330 | result = handler.invoke(client_address, timed_msg.message) |
@@ -309,46 +361,13 @@ async def async_call_handlers_for_packet( |
309 | 361 | if not handlers: |
310 | 362 | continue |
311 | 363 | # If the message is to be handled later, then so be it. |
312 | | - if timed_msg.time > now: |
313 | | - time.sleep(timed_msg.time - now) |
| 364 | + if self._strict_timing and timed_msg.time > now: |
| 365 | + await asyncio.sleep(timed_msg.time - now) |
314 | 366 | for handler in handlers: |
315 | | - if inspect.iscoroutinefunction(handler.callback): |
316 | | - if handler.needs_reply_address: |
317 | | - result = await handler.callback( |
318 | | - client_address, |
319 | | - timed_msg.message.address, |
320 | | - handler.args, |
321 | | - *timed_msg.message, |
322 | | - ) |
323 | | - elif handler.args: |
324 | | - result = await handler.callback( |
325 | | - timed_msg.message.address, |
326 | | - handler.args, |
327 | | - *timed_msg.message, |
328 | | - ) |
329 | | - else: |
330 | | - result = await handler.callback( |
331 | | - timed_msg.message.address, *timed_msg.message |
332 | | - ) |
333 | | - else: |
334 | | - if handler.needs_reply_address: |
335 | | - result = handler.callback( |
336 | | - client_address, |
337 | | - timed_msg.message.address, |
338 | | - handler.args, |
339 | | - *timed_msg.message, |
340 | | - ) |
341 | | - elif handler.args: |
342 | | - result = handler.callback( |
343 | | - timed_msg.message.address, |
344 | | - handler.args, |
345 | | - *timed_msg.message, |
346 | | - ) |
347 | | - else: |
348 | | - result = handler.callback( |
349 | | - timed_msg.message.address, *timed_msg.message |
350 | | - ) |
351 | | - if result: |
| 367 | + result = await handler.async_invoke( |
| 368 | + client_address, timed_msg.message |
| 369 | + ) |
| 370 | + if result is not None: |
352 | 371 | results.append(result) |
353 | 372 | except osc_packet.ParseError: |
354 | 373 | pass |
|
0 commit comments