Separated async-task-operator code from texture browser GUI code.
This commit is contained in:
@@ -130,6 +130,84 @@ class AsyncLoopModalOperator(bpy.types.Operator):
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
# noinspection PyAttributeOutsideInit
|
||||
class AsyncModalOperatorMixin:
|
||||
async_task = None # asyncio task for fetching thumbnails
|
||||
signalling_future = None # asyncio future for signalling that we want to cancel everything.
|
||||
log = logging.getLogger('%s.AsyncModalOperatorMixin' % __name__)
|
||||
|
||||
_state = 'INITIALIZING'
|
||||
|
||||
def invoke(self, context, event):
|
||||
context.window_manager.modal_handler_add(self)
|
||||
self.timer = context.window_manager.event_timer_add(1 / 15, context.window)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def modal(self, context, event):
|
||||
task = self.async_task
|
||||
|
||||
if self._state != 'EXCEPTION' and task and task.done() and not task.cancelled():
|
||||
ex = task.exception()
|
||||
if ex is not None:
|
||||
self._state = 'EXCEPTION'
|
||||
self.log.error('Exception while running task: %s', ex)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
if self._state == 'QUIT':
|
||||
self._finish(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def _finish(self, context):
|
||||
self._stop_async_task()
|
||||
context.window_manager.event_timer_remove(self.timer)
|
||||
|
||||
def _new_async_task(self, async_task: asyncio.coroutine, future: asyncio.Future = None):
|
||||
"""Stops the currently running async task, and starts another one."""
|
||||
|
||||
self.log.debug('Setting up a new task %r, so any existing task must be stopped', async_task)
|
||||
self._stop_async_task()
|
||||
|
||||
# Download the previews asynchronously.
|
||||
self.signalling_future = future or asyncio.Future()
|
||||
self.async_task = asyncio.ensure_future(async_task)
|
||||
self.log.debug('Created new task %r', self.async_task)
|
||||
|
||||
# Start the async manager so everything happens.
|
||||
ensure_async_loop()
|
||||
|
||||
def _stop_async_task(self):
|
||||
self.log.debug('Stopping async task')
|
||||
if self.async_task is None:
|
||||
self.log.debug('No async task, trivially stopped')
|
||||
return
|
||||
|
||||
# Signal that we want to stop.
|
||||
self.async_task.cancel()
|
||||
if not self.signalling_future.done():
|
||||
self.log.info("Signalling that we want to cancel anything that's running.")
|
||||
self.signalling_future.cancel()
|
||||
|
||||
# Wait until the asynchronous task is done.
|
||||
if not self.async_task.done():
|
||||
self.log.info("blocking until async task is done.")
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.run_until_complete(self.async_task)
|
||||
except asyncio.CancelledError:
|
||||
self.log.info('Asynchronous task was cancelled')
|
||||
return
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self.async_task.result() # This re-raises any exception of the task.
|
||||
except asyncio.CancelledError:
|
||||
self.log.info('Asynchronous task was cancelled')
|
||||
except Exception:
|
||||
self.log.exception("Exception from asynchronous task")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(AsyncLoopModalOperator)
|
||||
|
||||
|
Reference in New Issue
Block a user