Cleaned up and added noninteractive mode to server script

This commit is contained in:
Eric Froemling 2021-02-25 10:37:07 -06:00
parent ca5f45f3a2
commit 1dc05bed21
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
7 changed files with 228 additions and 148 deletions

View File

@ -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"
}

View File

@ -306,6 +306,7 @@
<w>cfgdir</w>
<w>cfgkey</w>
<w>cfgkeys</w>
<w>cfgpath</w>
<w>cfgs</w>
<w>cfgui</w>
<w>cflags</w>
@ -483,6 +484,7 @@
<w>dbapi</w>
<w>dbase</w>
<w>dbgsfx</w>
<w>dbgstr</w>
<w>dbpath</w>
<w>dcls</w>
<w>dcmake</w>
@ -1055,6 +1057,7 @@
<w>intex</w>
<w>intp</w>
<w>introspectable</w>
<w>intstr</w>
<w>iobj</w>
<w>ipaddress</w>
<w>ipos</w>
@ -1418,6 +1421,8 @@
<w>noinspect</w>
<w>nondeterministic</w>
<w>noninfringement</w>
<w>noninteractive</w>
<w>noninteractively</w>
<w>nonmultipart</w>
<w>noone</w>
<w>norun</w>
@ -1599,6 +1604,7 @@
<w>posixsubprocess</w>
<w>postinit</w>
<w>postinited</w>
<w>postrun</w>
<w>poststr</w>
<w>powerdown</w>
<w>powersgiven</w>
@ -1631,6 +1637,7 @@
<w>preprocessing</w>
<w>prereq</w>
<w>prereqs</w>
<w>prerun</w>
<w>prevstate</w>
<w>priceraw</w>
<w>printcolors</w>

View File

@ -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.

View File

@ -145,6 +145,7 @@
<w>ccylinder</w>
<w>centiseconds</w>
<w>cfgdir</w>
<w>cfgpath</w>
<w>changeme</w>
<w>charn</w>
<w>charnum</w>
@ -206,6 +207,7 @@
<w>datas</w>
<w>datav</w>
<w>datavec</w>
<w>dbgstr</w>
<w>dbias</w>
<w>dcol</w>
<w>ddcaps</w>
@ -452,6 +454,7 @@
<w>intercollide</w>
<w>internalformat</w>
<w>interuptions</w>
<w>intstr</w>
<w>invote</w>
<w>iobj</w>
<w>iserverget</w>
@ -608,6 +611,8 @@
<w>nointhash</w>
<w>nominmax</w>
<w>noninfringement</w>
<w>noninteractive</w>
<w>noninteractively</w>
<w>nonlint</w>
<w>noone</w>
<w>nothin</w>
@ -686,6 +691,7 @@
<w>positivey</w>
<w>positivez</w>
<w>postinit</w>
<w>postrun</w>
<w>powerup</w>
<w>pptabcom</w>
<w>precalc</w>
@ -695,6 +701,7 @@
<w>preloads</w>
<w>premult</w>
<w>prereq</w>
<w>prerun</w>
<w>printf</w>
<w>printnodes</w>
<w>printobjects</w>

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
<h4><em>last updated on 2021-02-22 for Ballistica version 1.6.0 build 20280</em></h4>
<h4><em>last updated on 2021-02-25 for Ballistica version 1.6.0 build 20306</em></h4>
<p>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 <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
<hr>

View File

@ -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.

View File

@ -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