2016-03-11 17:52:12 +01:00
|
|
|
"""Manages the asyncio loop."""
|
|
|
|
|
|
|
|
import asyncio
|
2016-03-14 17:23:56 +01:00
|
|
|
import traceback
|
2016-03-15 14:05:54 +01:00
|
|
|
import concurrent.futures
|
2016-03-15 13:47:21 +01:00
|
|
|
import logging
|
2016-03-14 17:23:56 +01:00
|
|
|
|
2016-03-11 17:52:12 +01:00
|
|
|
import bpy
|
|
|
|
|
2016-03-15 13:47:21 +01:00
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
2016-03-11 17:52:12 +01:00
|
|
|
|
2016-03-15 14:05:54 +01:00
|
|
|
def setup_asyncio_executor():
|
|
|
|
"""Sets up AsyncIO to run on a single thread.
|
|
|
|
|
|
|
|
This ensures that only one Pillar HTTP call is performed at the same time. Other
|
|
|
|
calls that could be performed in parallel are queued, and thus we can
|
|
|
|
reliably cancel them.
|
|
|
|
"""
|
|
|
|
|
|
|
|
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
loop.set_default_executor(executor)
|
|
|
|
# loop.set_debug(True)
|
|
|
|
|
|
|
|
|
2016-03-11 17:52:12 +01:00
|
|
|
def kick_async_loop(*args):
|
|
|
|
loop = asyncio.get_event_loop()
|
|
|
|
|
|
|
|
if loop.is_closed():
|
2016-03-15 13:47:21 +01:00
|
|
|
log.warning('loop closed, stopping')
|
2016-03-11 17:52:12 +01:00
|
|
|
stop_async_loop()
|
|
|
|
return
|
|
|
|
|
2016-03-14 17:23:56 +01:00
|
|
|
all_tasks = asyncio.Task.all_tasks()
|
2016-03-15 15:34:30 +01:00
|
|
|
if not len(all_tasks):
|
2016-03-15 13:47:21 +01:00
|
|
|
log.debug('no more scheduled tasks, stopping')
|
2016-03-11 17:52:12 +01:00
|
|
|
stop_async_loop()
|
|
|
|
return
|
|
|
|
|
2016-03-14 17:23:56 +01:00
|
|
|
if all(task.done() for task in all_tasks):
|
2016-03-15 15:34:30 +01:00
|
|
|
log.info('all %i tasks are done, fetching results and stopping.', len(all_tasks))
|
|
|
|
for task_idx, task in enumerate(all_tasks):
|
2016-03-14 17:23:56 +01:00
|
|
|
# noinspection PyBroadException
|
|
|
|
try:
|
2016-03-15 15:34:30 +01:00
|
|
|
res = task.result()
|
|
|
|
log.debug(' task #%i: result=%r', task_idx, res)
|
2016-03-14 17:23:56 +01:00
|
|
|
except asyncio.CancelledError:
|
|
|
|
# No problem, we want to stop anyway.
|
2016-03-15 15:34:30 +01:00
|
|
|
log.debug(' task #%i: cancelled', task_idx)
|
2016-03-14 17:23:56 +01:00
|
|
|
except Exception:
|
|
|
|
print('{}: resulted in exception'.format(task))
|
|
|
|
traceback.print_exc()
|
|
|
|
stop_async_loop()
|
|
|
|
return
|
|
|
|
|
2016-03-11 17:52:12 +01:00
|
|
|
# Perform a single async loop step
|
2016-03-15 15:34:30 +01:00
|
|
|
def stop_loop(future):
|
|
|
|
future.set_result('done')
|
2016-03-11 17:52:12 +01:00
|
|
|
|
2016-03-15 15:34:30 +01:00
|
|
|
future = asyncio.Future()
|
|
|
|
loop.call_later(0.005, stop_loop, future)
|
|
|
|
loop.run_until_complete(future)
|
2016-03-11 17:52:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
def async_loop_handler() -> callable:
|
2016-03-15 13:47:21 +01:00
|
|
|
"""Returns the asynchronous loop handler `kick_async_loop`
|
|
|
|
|
|
|
|
Only returns the function if it is installed as scene_update_pre handler, otherwise
|
|
|
|
it returns None.
|
|
|
|
"""
|
|
|
|
|
2016-03-11 17:52:12 +01:00
|
|
|
name = kick_async_loop.__name__
|
|
|
|
for handler in bpy.app.handlers.scene_update_pre:
|
|
|
|
if getattr(handler, '__name__', '') == name:
|
|
|
|
return handler
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_async_loop():
|
|
|
|
if async_loop_handler() is not None:
|
|
|
|
return
|
|
|
|
bpy.app.handlers.scene_update_pre.append(kick_async_loop)
|
|
|
|
|
|
|
|
|
|
|
|
def stop_async_loop():
|
|
|
|
handler = async_loop_handler()
|
|
|
|
if handler is None:
|
|
|
|
return
|
|
|
|
bpy.app.handlers.scene_update_pre.remove(handler)
|
2016-03-15 13:47:21 +01:00
|
|
|
log.debug('stopped async loop.')
|