Asyncio loop kicked via modal operator.
The old way (using a scene_update_pre handler) turned out to work due to a bug in Blender, where scene_update_pre was called too frequently.
This commit is contained in:
parent
ed9821afa6
commit
09e9c02d65
@ -77,14 +77,16 @@ def register():
|
|||||||
from . import blender, gui, async_loop
|
from . import blender, gui, async_loop
|
||||||
|
|
||||||
async_loop.setup_asyncio_executor()
|
async_loop.setup_asyncio_executor()
|
||||||
|
async_loop.register()
|
||||||
|
|
||||||
blender.register()
|
blender.register()
|
||||||
gui.register()
|
gui.register()
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
from . import blender, gui
|
from . import blender, gui, async_loop
|
||||||
|
|
||||||
gui.unregister()
|
gui.unregister()
|
||||||
blender.unregister()
|
blender.unregister()
|
||||||
|
async_loop.unregister()
|
||||||
|
|
||||||
|
@ -9,6 +9,9 @@ import bpy
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Keeps track of whether a loop-kicking operator is already running.
|
||||||
|
_loop_kicking_operator_running = False
|
||||||
|
|
||||||
|
|
||||||
def setup_asyncio_executor():
|
def setup_asyncio_executor():
|
||||||
"""Sets up AsyncIO to run on a single thread.
|
"""Sets up AsyncIO to run on a single thread.
|
||||||
@ -24,16 +27,21 @@ def setup_asyncio_executor():
|
|||||||
# loop.set_debug(True)
|
# loop.set_debug(True)
|
||||||
|
|
||||||
|
|
||||||
def kick_async_loop(*args):
|
def kick_async_loop(*args) -> bool:
|
||||||
|
"""Performs a single iteration of the asyncio event loop.
|
||||||
|
|
||||||
|
:return: whether the asyncio loop should stop after this kick.
|
||||||
|
"""
|
||||||
|
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
|
||||||
# We always need to do one more 'kick' to handle task-done callbacks.
|
# Even when we want to stop, we always need to do one more
|
||||||
|
# 'kick' to handle task-done callbacks.
|
||||||
stop_after_this_kick = False
|
stop_after_this_kick = False
|
||||||
|
|
||||||
if loop.is_closed():
|
if loop.is_closed():
|
||||||
log.warning('loop closed, stopping immediately.')
|
log.warning('loop closed, stopping immediately.')
|
||||||
stop_async_loop()
|
return True
|
||||||
return
|
|
||||||
|
|
||||||
all_tasks = asyncio.Task.all_tasks()
|
all_tasks = asyncio.Task.all_tasks()
|
||||||
if not len(all_tasks):
|
if not len(all_tasks):
|
||||||
@ -63,33 +71,61 @@ def kick_async_loop(*args):
|
|||||||
loop.stop()
|
loop.stop()
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
if stop_after_this_kick:
|
return stop_after_this_kick
|
||||||
stop_async_loop()
|
|
||||||
|
|
||||||
|
|
||||||
def async_loop_handler() -> callable:
|
|
||||||
"""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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
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():
|
def ensure_async_loop():
|
||||||
if async_loop_handler() is not None:
|
log.debug('Starting asyncio loop')
|
||||||
return
|
result = bpy.ops.asyncio.loop()
|
||||||
bpy.app.handlers.scene_update_pre.append(kick_async_loop)
|
log.debug('Result of starting modal operator is %r', result)
|
||||||
|
|
||||||
|
|
||||||
def stop_async_loop():
|
class AsyncLoopModalOperator(bpy.types.Operator):
|
||||||
handler = async_loop_handler()
|
bl_idname = 'asyncio.loop'
|
||||||
if handler is None:
|
bl_label = 'Runs the asyncio main loop'
|
||||||
return
|
|
||||||
bpy.app.handlers.scene_update_pre.remove(handler)
|
timer = None
|
||||||
log.debug('stopped async loop.')
|
log = logging.getLogger(__name__ + '.AsyncLoopModalOperator')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return self.invoke(context, None)
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
global _loop_kicking_operator_running
|
||||||
|
|
||||||
|
if _loop_kicking_operator_running:
|
||||||
|
self.log.debug('Another loop-kicking operator is already running.')
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
context.window_manager.modal_handler_add(self)
|
||||||
|
_loop_kicking_operator_running = True
|
||||||
|
|
||||||
|
wm = context.window_manager
|
||||||
|
self.timer = wm.event_timer_add(0.00001, context.window)
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
def modal(self, context, event):
|
||||||
|
global _loop_kicking_operator_running
|
||||||
|
|
||||||
|
if event.type != 'TIMER':
|
||||||
|
return {'PASS_THROUGH'}
|
||||||
|
|
||||||
|
# self.log.debug('KICKING LOOP')
|
||||||
|
stop_after_this_kick = kick_async_loop()
|
||||||
|
if stop_after_this_kick:
|
||||||
|
context.window_manager.event_timer_remove(self.timer)
|
||||||
|
_loop_kicking_operator_running = False
|
||||||
|
|
||||||
|
self.log.debug('Stopped asyncio loop kicking')
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
return {'RUNNING_MODAL'}
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bpy.utils.register_class(AsyncLoopModalOperator)
|
||||||
|
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
bpy.utils.unregister_class(AsyncLoopModalOperator)
|
||||||
|
Reference in New Issue
Block a user