Merge pull request #2238 from MoritzBrueckner/background-mode

Fix threading and publishing in background mode
This commit is contained in:
Lubos Lenco 2021-06-25 08:57:16 +02:00 committed by GitHub
commit 20540ccb11
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 77 additions and 15 deletions

View file

@ -1,5 +1,6 @@
import importlib
import os
import queue
import sys
import bpy
@ -89,19 +90,38 @@ def send_operator(op):
else: # Rebuild
make.patch()
def always():
def always() -> float:
# Force ui redraw
if state.redraw_ui and context_screen != None:
if state.redraw_ui and context_screen is not None:
for area in context_screen.areas:
if area.type == 'VIEW_3D' or area.type == 'PROPERTIES':
area.tag_redraw()
state.redraw_ui = False
# TODO: depsgraph.updates only triggers material trees
space = arm.utils.logic_editor_space(context_screen)
if space != None:
if space is not None:
space.node_tree.arm_cached = False
return 0.5
def poll_threads() -> float:
"""Polls the thread callback queue and if a thread has finished, it
is joined with the main thread and the corresponding callback is
executed in the main thread.
"""
try:
thread, callback = make.thread_callback_queue.get(block=False)
except queue.Empty:
return 0.25
thread.join()
callback()
# Quickly check if another thread has finished
return 0.01
appended_py_paths = []
context_screen = None
@ -181,7 +201,9 @@ def register():
bpy.app.handlers.load_post.append(on_load_post)
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)
# bpy.app.handlers.undo_post.append(on_undo_post)
bpy.app.timers.register(always, persistent=True)
bpy.app.timers.register(poll_threads, persistent=True)
if arm.utils.get_fp() != '':
appended_py_paths = []
@ -198,6 +220,9 @@ def register():
def unregister():
bpy.app.timers.unregister(poll_threads)
bpy.app.timers.unregister(always)
bpy.app.handlers.load_post.remove(on_load_post)
bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post)
# bpy.app.handlers.undo_post.remove(on_undo_post)

View file

@ -1,15 +1,16 @@
import errno
import glob
import json
import os
from queue import Queue
import shlex
import shutil
import time
import stat
import subprocess
import threading
import time
from typing import Callable
import webbrowser
import shlex
import errno
import math
import bpy
@ -28,15 +29,42 @@ import arm.write_data as write_data
scripts_mtime = 0 # Monitor source changes
profile_time = 0
def run_proc(cmd, done):
def fn(p, done):
p.wait()
if done != None:
# Queue of threads and their done callbacks. Item format: [thread, done]
thread_callback_queue = Queue(maxsize=0)
def run_proc(cmd, done: Callable) -> subprocess.Popen:
"""Creates a subprocess with the given command and returns it.
If Blender is not running in background mode, a thread is spawned
that waits until the subprocess has finished executing to not freeze
the UI, otherwise (in background mode) execution is blocked until
the subprocess has finished.
If `done` is not `None`, it is called afterwards in the main thread.
"""
use_thread = not bpy.app.background
def wait_for_proc(proc: subprocess.Popen):
proc.wait()
if use_thread:
# Put the done callback into the callback queue so that it
# can be received by a polling function in the main thread
thread_callback_queue.put([threading.current_thread(), done], block=True)
else:
done()
p = subprocess.Popen(cmd)
threading.Thread(target=fn, args=(p, done)).start()
if use_thread:
threading.Thread(target=wait_for_proc, args=(p,)).start()
else:
wait_for_proc(p)
return p
def compile_shader_pass(res, raw_shaders_path, shader_name, defs, make_variants):
os.chdir(raw_shaders_path + '/' + shader_name)

View file

@ -626,7 +626,6 @@ class ARM_PT_ArmoryExporterPanel(bpy.types.Panel):
row = layout.row(align=True)
row.alignment = 'EXPAND'
row.scale_y = 1.3
row.enabled = wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
row.operator("arm.build_project", icon="MOD_BUILD")
# row.operator("arm.patch_project")
row.operator("arm.publish_project", icon="EXPORT")
@ -1053,10 +1052,15 @@ class ArmoryStopButton(bpy.types.Operator):
return{'FINISHED'}
class ArmoryBuildProjectButton(bpy.types.Operator):
'''Build and compile project'''
"""Build and compile project"""
bl_idname = 'arm.build_project'
bl_label = 'Build'
@classmethod
def poll(cls, context):
wrd = bpy.data.worlds['Arm']
return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
def execute(self, context):
# Compare version Blender and Armory (major, minor)
if not arm.utils.compare_version_blender_arm():
@ -1093,10 +1097,15 @@ class ArmoryBuildProjectButton(bpy.types.Operator):
return{'FINISHED'}
class ArmoryPublishProjectButton(bpy.types.Operator):
'''Build project ready for publishing'''
"""Build project ready for publishing."""
bl_idname = 'arm.publish_project'
bl_label = 'Publish'
@classmethod
def poll(cls, context):
wrd = bpy.data.worlds['Arm']
return wrd.arm_exporterlist_index >= 0 and len(wrd.arm_exporterlist) > 0
def execute(self, context):
# Compare version Blender and Armory (major, minor)
if not arm.utils.compare_version_blender_arm():