diff --git a/launcher/build.py b/launcher/build.py index eca68b3d..b3fae563 100644 --- a/launcher/build.py +++ b/launcher/build.py @@ -14,6 +14,11 @@ RUN = ('import os, runpy, sys; p = sys.argv[1]; s = sys.argv[2]; sys.argv = sys. 'runpy.run_path(s, run_name="__main__")') CREATE_NO_WINDOW = 0x08000000 CREATE_NEW_PROCESS_GROUP = 0x00000200 +# The build runs below normal priority: a compile on every processor for ten minutes and more otherwise takes the +# PC from whatever the player does meanwhile, which the job screen invites ("Keep using your PC if you like"). On an +# idle PC it costs nothing. Windows hands a below-normal process's class on to the processes it starts, so the +# compilers, the lifter's workers and the shader preparation that make_game.py starts run that way too. +BELOW_NORMAL_PRIORITY_CLASS = 0x00004000 def command(layout, args): @@ -61,7 +66,7 @@ class Run: self.error = None kw = {} if os.name == 'nt': - kw['creationflags'] = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP + kw['creationflags'] = CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP | BELOW_NORMAL_PRIORITY_CLASS else: kw['start_new_session'] = True env = dict(os.environ, PYTHONUNBUFFERED='1', PYTHONIOENCODING='utf-8') diff --git a/tests/launcher_test.py b/tests/launcher_test.py index f0069fcf..73d167a5 100644 --- a/tests/launcher_test.py +++ b/tests/launcher_test.py @@ -2743,6 +2743,24 @@ class JobTest(Tmp): App.job_finished(a) self.assertEqual(a.message, 'The game is built. Select Play.') + @unittest.skipUnless(os.name == 'nt', 'Windows priority classes') + def test_the_build_and_what_it_starts_run_below_normal_priority(self): + """build.Run starts the build below normal priority, and the processes the build starts (the compilers) + get that class from it, so a build on every processor leaves the PC usable.""" + script = self.tmp / 'priority.py' + script.write_text( + 'import ctypes, subprocess, sys\n' + 'k = ctypes.windll.kernel32\n' + 'k.GetCurrentProcess.restype = ctypes.c_void_p\n' + 'k.GetPriorityClass.argtypes = [ctypes.c_void_p]\n' + 'print(sys.argv[1], hex(k.GetPriorityClass(k.GetCurrentProcess())), flush=True)\n' + 'if sys.argv[1] == "build":\n' + ' subprocess.run([sys.executable, __file__, "compiler"])\n') + lines = [] + run = build.Run([sys.executable, str(script), 'build'], lambda ev: None, lines.append) + self.assertEqual(run.wait(), 0, lines) + self.assertEqual(lines, ['build 0x4000', 'compiler 0x4000']) # BELOW_NORMAL_PRIORITY_CLASS + def test_an_install_goes_on_from_the_unpacked_game_files(self): """Begin Installation into a folder whose game files an earlier run unpacked builds on from them (--rebuild for this processor's level), so a moved or deleted dump is not asked for; without them it reads the dump."""