From 1dc05bed2154665511eaa37341045bfc6e2141ea Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Thu, 25 Feb 2021 10:37:07 -0600 Subject: [PATCH] Cleaned up and added noninteractive mode to server script --- .efrocachemap | 56 ++-- .idea/dictionaries/ericf.xml | 7 + assets/src/server/ballisticacore_server.py | 296 +++++++++++------- .../.idea/dictionaries/ericf.xml | 7 + docs/ba_module.md | 2 +- src/ballistica/ballistica.cc | 2 +- tools/bacommon/servermanager.py | 6 +- 7 files changed, 228 insertions(+), 148 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 4b2d5e86..18eae297 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3932,26 +3932,26 @@ "assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450", "assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e", "assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f", - "build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/84/a1/420d282610456205a0c7317ef587", - "build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a9/63/eabb9fe980c4686ff48c62dc3215", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4c/3f/7bc2491bcb678a964043ba7d25dd", - "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ee/12/8addc0aa42af13c2e2903b1f2cb1", - "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/00/11/ca95947488543f6e9fcb6ff4a122", - "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/d9/e4f7c8454210a1bedef3416d1768", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/b9/21faa11f5b965f55e548b6485b2c", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/aa/b1641c07d4539f673445467ccef9", - "build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ac/a3/ff8cbd9b0bae767ad4809360e21a", - "build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/46/ee/4c5599f4b9aef54eff51b7702409", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/db/ce/aec9518eee7bcd7fdc8ca80b1d76", - "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d8/dc/6b1c63cb28db2ff64554526d5311", - "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a4/fa/8613c74733350c3cd26945823ed0", - "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/10/65/62b7f0eb542a037f54ea486be204", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/6e/569b32f6c9401b17ceed5327257f", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/99/dc/5a55ebdf3e5541c74a59f68f3f65", - "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/9c/58/1be66154a04eab5f00f6aa92d165", - "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/bd/89/b6210c67baf254ab314317973b5e", - "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/93/41/617d1b3436173551780992842b6e", - "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/3e/c5/d5a2841dc15918206f8c19b1ce02", + "build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9b/14/177c2689a9f00af6d64347a4b985", + "build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7a/bc/6c75d6f16c4bd1bd48ae4e31a304", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/63/98/38d14bf5bbdf01d898bf157e9491", + "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/61/8a/c805d1785f92c242ca038caeebc9", + "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9d/88/c37bd934f664e0af630e671a70e9", + "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/07/99095bfc02f73b9b2590a86f8f93", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6c/a9/22b68d2b6e54bd9cd69fa59cc4e6", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9d/fb/fba081862fc2e42a5a591c095ef3", + "build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7c/24/71fd9231c09bc0584c92bf10c0d5", + "build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/2b/94679b9fe4df5a3f8ea106a8bb26", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0f/f3/5b7f71d5f98d90444f2c1781776c", + "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/9b/5b1aa55452691590f1e0b00dbbe6", + "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/99/8e/0b2157debae0db9e72a9a8224e24", + "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8f/18/0b6e28d94eea96e8e2336713515b", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fa/60/b97a279322a881d860adac050c04", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a0/f0/b98326c4e5f54e5fca9e430822bb", + "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/75/48/8eb219cde7fc49ae97161621178a", + "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/6e/12/904e655eb8e283aee2604e42080d", + "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/1a/2c/2407f7b2b2f756215d26fd713d51", + "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/db/61/fb74c49d524ec0e9eb356f0b0f0a", "build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4c/bf/393694ea67f3d590dd2706c9955e", "build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/78/cb/bb9ae4f896f862074057c8e36e1d", "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ae/bd/39d7b885f7f01e81d0e96f0f85ce", @@ -3960,12 +3960,12 @@ "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/52/d9/563a6949d2c4db5a915c54460fbc", "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/6a/42fe8d2e34f95e1b3282e8422344", "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/cf/bad44b07a4022aee3001002086b5", - "build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fa/8a/a6aedb3b2c74c055005792710be8", - "build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/97/0b6acf3397515a5fb997b3a81e3d", - "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ee/b0/81fbb6e8996205a16e3d8e912000", - "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/64/68/e668be0a349bcdd51efe35703b8a", - "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/06/3e80d9b9efa82e28b77d895562b0", - "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/34/b1/942b8c6206051dbaa15f225bc8d9", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e1/a6/2b3a07d7afe0215ab5843a83809f", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/48/5d/e8c8deb5b42593c805fa9c181c0c" + "build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0e/3e/d730bb6f8cbd419a6f0bc9ec45b5", + "build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ce/4d/33d705994f4f715b5ef86af3b10c", + "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cc/21/6a8ba5a374899e96de6842b456dc", + "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e8/5c/d903596fd08f95bd89c76250e54c", + "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f9/25/6cbd4a35e2d75a61cdb379a7148b", + "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/aa/bd/f5747ffd8cfbef6970051b1714e1", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c9/4b/4da5368fe05606c3717a2fadfbaf", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/44/14887341014ac31de1e8938ef309" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index c3954b6d..072dd990 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -306,6 +306,7 @@ cfgdir cfgkey cfgkeys + cfgpath cfgs cfgui cflags @@ -483,6 +484,7 @@ dbapi dbase dbgsfx + dbgstr dbpath dcls dcmake @@ -1055,6 +1057,7 @@ intex intp introspectable + intstr iobj ipaddress ipos @@ -1418,6 +1421,8 @@ noinspect nondeterministic noninfringement + noninteractive + noninteractively nonmultipart noone norun @@ -1599,6 +1604,7 @@ posixsubprocess postinit postinited + postrun poststr powerdown powersgiven @@ -1631,6 +1637,7 @@ preprocessing prereq prereqs + prerun prevstate priceraw printcolors diff --git a/assets/src/server/ballisticacore_server.py b/assets/src/server/ballisticacore_server.py index 12b2db4e..73c849b5 100755 --- a/assets/src/server/ballisticacore_server.py +++ b/assets/src/server/ballisticacore_server.py @@ -10,7 +10,6 @@ import signal import subprocess import sys import time -from dataclasses import dataclass from pathlib import Path from threading import Lock, Thread, current_thread from typing import TYPE_CHECKING @@ -33,11 +32,13 @@ if TYPE_CHECKING: from types import FrameType from bacommon.servermanager import ServerCommand -VERSION_STR = '1.1.2' +VERSION_STR = '1.2' # Version history: -# 1.1.2: -# Added args for setting config path and ba_root path +# 1.2: +# Added optional --help arg +# Added --config arg for setting config path and --root for ba_root path +# Added noninteractive mode and --interactive/--noninteractive to select # 1.1.1: # Switched config reading to use efro.dataclasses.dataclass_from_dict() # 1.1.0: @@ -47,10 +48,6 @@ VERSION_STR = '1.1.2' # 1.0.0: # Initial release -# How many seconds we wait after asking our subprocess to do an immediate -# shutdown before bringing down the hammer. -IMMEDIATE_SHUTDOWN_TIME_LIMIT = 5.0 - class ServerManagerApp: """An app which manages BallisticaCore server execution. @@ -59,10 +56,18 @@ class ServerManagerApp: managing BallisticaCore operating in server mode. """ - def __init__(self, args: Args) -> None: - self._config_path = args.config_path - self._ba_root_path = (args.ba_root_path if args.ba_root_path - is not None else os.path.abspath('dist/ba_root')) + # How many seconds we wait after asking our subprocess to do an immediate + # shutdown before bringing down the hammer. + IMMEDIATE_SHUTDOWN_TIME_LIMIT = 5.0 + + def __init__(self) -> None: + self._config_path = 'config.yaml' + self._ba_root_path = os.path.abspath('dist/ba_root') + self._interactive = sys.stdin.isatty() + + # This may override the above defaults. + self._parse_command_line_args() + try: self._config = self._load_config() except Exception as exc: @@ -73,7 +78,7 @@ class ServerManagerApp: self._subprocess_commands_lock = Lock() self._subprocess_force_kill_time: Optional[float] = None self._restart_minutes: Optional[float] = None - self._running_interactive = False + self._running = False self._subprocess: Optional[subprocess.Popen[bytes]] = None self._launch_time = time.time() self._subprocess_launch_time: Optional[float] = None @@ -109,26 +114,18 @@ class ServerManagerApp: """ return self._restart_minutes - def run_interactive(self) -> None: - """Run the app loop to completion.""" - import code + def _prerun(self) -> None: + """Common code at the start of any run.""" - if self._running_interactive: - raise RuntimeError('Already running interactively.') - self._running_interactive = True + # Make sure we don't call run multiple times. + if self._running: + raise RuntimeError('Already running.') + self._running = True - # Print basic usage info in interactive mode. - if sys.stdin.isatty(): - if __debug__: - modestr = '(debug mode)' - else: - modestr = '(opt mode)' - print(f'{Clr.CYN}{Clr.BLD}BallisticaCore server' - f' manager {VERSION_STR}' - f' starting up {modestr}...{Clr.RST}\n' - f'{Clr.CYN}Use the "mgr" object to make' - f' live server adjustments.\n' - f'Type "help(mgr)" for more information.{Clr.RST}') + dbgstr = 'debug' if __debug__ else 'opt' + intstr = 'interactive' if self._interactive else 'noninteractive' + print(f'{Clr.CYN}{Clr.BLD}BallisticaCore server manager {VERSION_STR}' + f' starting up ({dbgstr}/{intstr} mode)...{Clr.RST}') # Python will handle SIGINT for us (as KeyboardInterrupt) but we # need to register a SIGTERM handler so we have a chance to clean @@ -140,6 +137,56 @@ class ServerManagerApp: self._subprocess_thread = Thread(target=self._bg_thread_main) self._subprocess_thread.start() + # During a run, we make the assumption that cwd is the dir + # containing this script, so make that so. Up until now that may + # not be the case (we support being called from any location). + os.chdir(os.path.abspath(os.path.dirname(__file__))) + + def _postrun(self) -> None: + """Common code at the end of any run.""" + print(f'{Clr.CYN}Server manager shutting down...{Clr.RST}') + + assert self._subprocess_thread is not None + if self._subprocess_thread.is_alive(): + print(f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}') + + # Mark ourselves as shutting down and wait for the process to wrap up. + self._done = True + self._subprocess_thread.join() + + def run(self) -> None: + """Do the thing.""" + if self._interactive: + self._run_interactive() + else: + self._run_noninteractive() + + def _run_noninteractive(self) -> None: + """Run the app loop to completion noninteractively.""" + self._prerun() + try: + while True: + time.sleep(1.234) + except KeyboardInterrupt: + # Gracefully bow out if we kill ourself via keyboard. + pass + except SystemExit: + # We get this from the builtin quit(), our signal handler, etc. + # Need to catch this so we can clean up, otherwise we'll be + # left in limbo with our process thread still running. + pass + self._postrun() + + def _run_interactive(self) -> None: + """Run the app loop to completion interactively.""" + import code + + self._prerun() + + # Print basic usage info for interactive mode. + print(f'{Clr.CYN}Use the "mgr" object to interact with the server.\n' + f'Type "help(mgr)" for more information.{Clr.RST}') + context = {'__name__': '__console__', '__doc__': None, 'mgr': self} # Enable tab-completion if possible. @@ -150,7 +197,7 @@ class ServerManagerApp: try: code.interact(local=context, banner='', exitmsg='') except SystemExit: - # We get this from the builtin quit(), etc. + # We get this from the builtin quit(), our signal handler, etc. # Need to catch this so we can clean up, otherwise we'll be # left in limbo with our process thread still running. pass @@ -158,14 +205,7 @@ class ServerManagerApp: print(f'{Clr.SRED}Unexpected interpreter exception:' f' {exc} ({type(exc)}){Clr.RST}') - print(f'{Clr.CYN}Server manager shutting down...{Clr.RST}') - - if self._subprocess_thread.is_alive(): - print(f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}') - - # Mark ourselves as shutting down and wait for the process to wrap up. - self._done = True - self._subprocess_thread.join() + self._postrun() def cmd(self, statement: str) -> None: """Exec a Python command on the current running server subprocess. @@ -255,8 +295,8 @@ class ServerManagerApp: # If we're asking for an immediate restart but don't get one within # the grace period, bring down the hammer. if immediate: - self._subprocess_force_kill_time = (time.time() + - IMMEDIATE_SHUTDOWN_TIME_LIMIT) + self._subprocess_force_kill_time = ( + time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) def shutdown(self, immediate: bool = True) -> None: """Shut down the server subprocess and exit the wrapper @@ -276,16 +316,106 @@ class ServerManagerApp: # If we're asking for an immediate shutdown but don't get one within # the grace period, bring down the hammer. if immediate: - self._subprocess_force_kill_time = (time.time() + - IMMEDIATE_SHUTDOWN_TIME_LIMIT) + self._subprocess_force_kill_time = ( + time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT) + + def _parse_command_line_args(self) -> None: + """Parse command line args.""" + + i = 1 + argc = len(sys.argv) + did_set_interactive = False + while i < argc: + arg = sys.argv[i] + if arg == '--help': + self.print_help() + sys.exit(0) + elif arg == '--config': + if i + 1 >= argc: + raise CleanError('Expected a config path as next arg.') + path = sys.argv[i + 1] + if not os.path.exists(path): + raise CleanError( + f"Supplied path does not exist: '{path}'.") + # We need an abs path because we may be in a different + # cwd currently than we will be during the run. + self._config_path = os.path.abspath(path) + i += 2 + elif arg == '--root': + if i + 1 >= argc: + raise CleanError('Expected a path as next arg.') + path = sys.argv[i + 1] + # Unlike config_path, this one doesn't have to exist now. + # We do however need an abs path because we may be in a + # different cwd currently than we will be during the run. + self._ba_root_path = os.path.abspath(path) + i += 2 + elif arg == '--interactive': + if did_set_interactive: + raise CleanError('interactive/noninteractive can only' + ' be specified once.') + self._interactive = True + did_set_interactive = True + i += 1 + elif arg == '--noninteractive': + if did_set_interactive: + raise CleanError('interactive/noninteractive can only' + ' be specified once.') + self._interactive = False + did_set_interactive = True + i += 1 + else: + raise CleanError(f"Invalid arg: '{arg}'.") + + @classmethod + def _par(cls, txt: str) -> str: + import textwrap + ind = ' ' * 2 + out = textwrap.fill(txt, 80, initial_indent=ind, subsequent_indent=ind) + return f'{out}\n' + + @classmethod + def print_help(cls) -> None: + """Print app help.""" + filename = os.path.basename(__file__) + out = ( + f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par( + 'This script handles configuring, launching, re-launching,' + ' and otherwise managing BallisticaCore operating' + ' in server mode. It can be run with no arguments, but' + ' accepts the following optional ones:') + f'\n' + f'{Clr.BLD}--help:{Clr.RST}\n' + f' Show this help.\n' + f'\n' + f'{Clr.BLD}--config [path]{Clr.RST}\n' + cls._par( + 'Set the config file read by the server script. This should' + ' be in yaml format. Note that yaml is backwards compatible' + ' with json so you can just write json if you want to. If' + ' not specified, the script will look for a file named' + ' \'config.yaml\' in the same directory as the script.') + '\n' + f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par( + 'Set the ballistica root directory. This is where the game' + ' will read and write its caches, state files, downloaded' + ' assets, etc. It needs to be a writable directory. If not' + ' specified, the script will use the \'dist/ba_root\'' + ' directory relative to itself.') + '\n' + f'{Clr.BLD}--interactive{Clr.RST}\n' + f'{Clr.BLD}--noninteractive{Clr.RST}\n' + cls._par( + 'Specify whether the script should run interactively.' + ' In interactive mode, the script creates a Python interpreter' + ' and reads commands from stdin, allowing for live interaction' + ' with the server. The server script will then exit when ' + 'end-of-file is reached in stdin. Noninteractive mode creates' + ' no interpreter and is more suited to being run in automated' + ' scenarios. By default, interactive mode will be used if' + ' a terminal is detected and noninteractive mode otherwise.')) + print(out) def _load_config(self) -> ServerConfig: - user_config_path = (self._config_path if self._config_path is not None - else 'config.yaml') - if os.path.exists(user_config_path): + if os.path.exists(self._config_path): import yaml - with open(user_config_path) as infile: + with open(self._config_path) as infile: user_config_raw = yaml.safe_load(infile.read()) # An empty config file will yield None, and that's ok. @@ -293,7 +423,7 @@ class ServerManagerApp: return dataclass_from_dict(ServerConfig, user_config_raw) else: print( - f"Warning: config file not found at '{user_config_path}'" + f"Warning: config file not found at '{self._config_path}'" f'; will use default config.', file=sys.stderr, flush=True) @@ -535,74 +665,10 @@ class ServerManagerApp: print(f'{Clr.CYN}Subprocess stopped.{Clr.RST}') -def _parse_args() -> Optional[str]: - """Parse command line args; return optional config path.""" - if len(sys.argv) > 2: - raise CleanError('Expected no more than 1 arg (config path)') - if len(sys.argv) > 1: - configpath = sys.argv[1] - if not os.path.exists(configpath): - raise CleanError( - f"Supplied config path does not exist: '{configpath}'.") - - # Note that we chdir before running the app so we need an abs path - # to this to be safe. - return os.path.abspath(configpath) - return None - - -@dataclass -class Args: - """Wraps arguments that can be passed from the command line.""" - config_path: Optional[str] = None - ba_root_path: Optional[str] = None - - @classmethod - def from_command_line(cls) -> Args: - """Parse command line args and fill ourself out.""" - args = Args() - - i = 1 - argc = len(sys.argv) - while i < argc: - arg = sys.argv[i] - if arg == '--config': - if i + 1 >= argc: - raise CleanError('Expected a config path as next arg.') - path = sys.argv[i + 1] - if not os.path.exists(path): - raise CleanError( - f"Supplied path does not exist: '{path}'.") - args.config_path = os.path.abspath(path) - i += 2 - elif arg == '--root': - if i + 1 >= argc: - raise CleanError('Expected a path as next arg.') - path = sys.argv[i + 1] - # Note: this one doesn't have to exist. - args.ba_root_path = os.path.abspath(path) - i += 2 - else: - raise CleanError(f"Invalid arg: '{arg}'.") - - return args - - def main() -> None: - """Run a BallisticaCore server manager in interactive mode.""" + """Run the BallisticaCore server manager.""" try: - # Note: we need to parse args before we chdir since we might be - # dealing with relative paths. - args = Args.from_command_line() - - # ServerManager expects cwd to be the server dir (containing - # dist/, config.yaml, etc.) Let's change our working directory to - # the location of this file so we can run this script from anywhere - # and it'll work. - os.chdir(os.path.abspath(os.path.dirname(__file__))) - - ServerManagerApp(args).run_interactive() - + ServerManagerApp().run() except CleanError as exc: # For clean errors, do a simple print and fail; no tracebacks/etc. # Any others will bubble up and give us the usual mess. diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml index e4c2c9ba..c0cb3e3c 100644 --- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml @@ -145,6 +145,7 @@ ccylinder centiseconds cfgdir + cfgpath changeme charn charnum @@ -206,6 +207,7 @@ datas datav datavec + dbgstr dbias dcol ddcaps @@ -452,6 +454,7 @@ intercollide internalformat interuptions + intstr invote iobj iserverget @@ -608,6 +611,8 @@ nointhash nominmax noninfringement + noninteractive + noninteractively nonlint noone nothin @@ -686,6 +691,7 @@ positivey positivez postinit + postrun powerup pptabcom precalc @@ -695,6 +701,7 @@ preloads premult prereq + prerun printf printnodes printobjects diff --git a/docs/ba_module.md b/docs/ba_module.md index a3edcf44..3acb187c 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2021-02-22 for Ballistica version 1.6.0 build 20280

+

last updated on 2021-02-25 for Ballistica version 1.6.0 build 20306

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!


diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index e2a541f5..7e478300 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -21,7 +21,7 @@ namespace ballistica { // These are set automatically via script; don't change here. -const int kAppBuildNumber = 20303; +const int kAppBuildNumber = 20307; const char* kAppVersion = "1.6.0"; // Our standalone globals. diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py index 57cbd7e9..9a6e0930 100644 --- a/tools/bacommon/servermanager.py +++ b/tools/bacommon/servermanager.py @@ -94,21 +94,21 @@ class ServerConfig: # If present, the server manager will attempt to gracefully exit after # this amount of time. A graceful exit can occur at the end of a series # or other opportune time. - # Servers with no exit times set will run indefinitely, though the + # Servers with no exit conditions set will run indefinitely, though the # server binary will be restarted periodically to clear any memory # leaks or other bad state. clean_exit_minutes: Optional[float] = None # If present, the server manager will shut down immediately after this # amount of time. This can be useful as a fallback for clean_exit_time. - # Servers with no exit times set will run indefinitely, though the + # Servers with no exit conditions set will run indefinitely, though the # server binary will be restarted periodically to clear any memory # leaks or other bad state. unclean_exit_minutes: Optional[float] = None # If present, the server will shut down immediately if this amount of # time passes with no activity from any players. - # Servers with no exit times set will run indefinitely, though the + # Servers with no exit conditions set will run indefinitely, though the # server binary will be restarted periodically to clear any memory # leaks or other bad state. idle_exit_minutes: Optional[float] = None