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/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/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", "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/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9b/14/177c2689a9f00af6d64347a4b985",
"build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a9/63/eabb9fe980c4686ff48c62dc3215", "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/4c/3f/7bc2491bcb678a964043ba7d25dd", "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/ee/12/8addc0aa42af13c2e2903b1f2cb1", "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/00/11/ca95947488543f6e9fcb6ff4a122", "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/d8/d9/e4f7c8454210a1bedef3416d1768", "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/f7/b9/21faa11f5b965f55e548b6485b2c", "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/ac/aa/b1641c07d4539f673445467ccef9", "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/ac/a3/ff8cbd9b0bae767ad4809360e21a", "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/46/ee/4c5599f4b9aef54eff51b7702409", "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/db/ce/aec9518eee7bcd7fdc8ca80b1d76", "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/d8/dc/6b1c63cb28db2ff64554526d5311", "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/a4/fa/8613c74733350c3cd26945823ed0", "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/10/65/62b7f0eb542a037f54ea486be204", "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/89/6e/569b32f6c9401b17ceed5327257f", "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/99/dc/5a55ebdf3e5541c74a59f68f3f65", "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/9c/58/1be66154a04eab5f00f6aa92d165", "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/bd/89/b6210c67baf254ab314317973b5e", "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/93/41/617d1b3436173551780992842b6e", "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/3e/c5/d5a2841dc15918206f8c19b1ce02", "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/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/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", "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/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/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/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/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/4b/97/0b6acf3397515a5fb997b3a81e3d", "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/ee/b0/81fbb6e8996205a16e3d8e912000", "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/64/68/e668be0a349bcdd51efe35703b8a", "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/4b/06/3e80d9b9efa82e28b77d895562b0", "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/34/b1/942b8c6206051dbaa15f225bc8d9", "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/e1/a6/2b3a07d7afe0215ab5843a83809f", "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/48/5d/e8c8deb5b42593c805fa9c181c0c" "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>cfgdir</w>
<w>cfgkey</w> <w>cfgkey</w>
<w>cfgkeys</w> <w>cfgkeys</w>
<w>cfgpath</w>
<w>cfgs</w> <w>cfgs</w>
<w>cfgui</w> <w>cfgui</w>
<w>cflags</w> <w>cflags</w>
@ -483,6 +484,7 @@
<w>dbapi</w> <w>dbapi</w>
<w>dbase</w> <w>dbase</w>
<w>dbgsfx</w> <w>dbgsfx</w>
<w>dbgstr</w>
<w>dbpath</w> <w>dbpath</w>
<w>dcls</w> <w>dcls</w>
<w>dcmake</w> <w>dcmake</w>
@ -1055,6 +1057,7 @@
<w>intex</w> <w>intex</w>
<w>intp</w> <w>intp</w>
<w>introspectable</w> <w>introspectable</w>
<w>intstr</w>
<w>iobj</w> <w>iobj</w>
<w>ipaddress</w> <w>ipaddress</w>
<w>ipos</w> <w>ipos</w>
@ -1418,6 +1421,8 @@
<w>noinspect</w> <w>noinspect</w>
<w>nondeterministic</w> <w>nondeterministic</w>
<w>noninfringement</w> <w>noninfringement</w>
<w>noninteractive</w>
<w>noninteractively</w>
<w>nonmultipart</w> <w>nonmultipart</w>
<w>noone</w> <w>noone</w>
<w>norun</w> <w>norun</w>
@ -1599,6 +1604,7 @@
<w>posixsubprocess</w> <w>posixsubprocess</w>
<w>postinit</w> <w>postinit</w>
<w>postinited</w> <w>postinited</w>
<w>postrun</w>
<w>poststr</w> <w>poststr</w>
<w>powerdown</w> <w>powerdown</w>
<w>powersgiven</w> <w>powersgiven</w>
@ -1631,6 +1637,7 @@
<w>preprocessing</w> <w>preprocessing</w>
<w>prereq</w> <w>prereq</w>
<w>prereqs</w> <w>prereqs</w>
<w>prerun</w>
<w>prevstate</w> <w>prevstate</w>
<w>priceraw</w> <w>priceraw</w>
<w>printcolors</w> <w>printcolors</w>

View File

@ -10,7 +10,6 @@ import signal
import subprocess import subprocess
import sys import sys
import time import time
from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from threading import Lock, Thread, current_thread from threading import Lock, Thread, current_thread
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -33,11 +32,13 @@ if TYPE_CHECKING:
from types import FrameType from types import FrameType
from bacommon.servermanager import ServerCommand from bacommon.servermanager import ServerCommand
VERSION_STR = '1.1.2' VERSION_STR = '1.2'
# Version history: # Version history:
# 1.1.2: # 1.2:
# Added args for setting config path and ba_root path # 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: # 1.1.1:
# Switched config reading to use efro.dataclasses.dataclass_from_dict() # Switched config reading to use efro.dataclasses.dataclass_from_dict()
# 1.1.0: # 1.1.0:
@ -47,10 +48,6 @@ VERSION_STR = '1.1.2'
# 1.0.0: # 1.0.0:
# Initial release # 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: class ServerManagerApp:
"""An app which manages BallisticaCore server execution. """An app which manages BallisticaCore server execution.
@ -59,10 +56,18 @@ class ServerManagerApp:
managing BallisticaCore operating in server mode. managing BallisticaCore operating in server mode.
""" """
def __init__(self, args: Args) -> None: # How many seconds we wait after asking our subprocess to do an immediate
self._config_path = args.config_path # shutdown before bringing down the hammer.
self._ba_root_path = (args.ba_root_path if args.ba_root_path IMMEDIATE_SHUTDOWN_TIME_LIMIT = 5.0
is not None else os.path.abspath('dist/ba_root'))
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: try:
self._config = self._load_config() self._config = self._load_config()
except Exception as exc: except Exception as exc:
@ -73,7 +78,7 @@ class ServerManagerApp:
self._subprocess_commands_lock = Lock() self._subprocess_commands_lock = Lock()
self._subprocess_force_kill_time: Optional[float] = None self._subprocess_force_kill_time: Optional[float] = None
self._restart_minutes: Optional[float] = None self._restart_minutes: Optional[float] = None
self._running_interactive = False self._running = False
self._subprocess: Optional[subprocess.Popen[bytes]] = None self._subprocess: Optional[subprocess.Popen[bytes]] = None
self._launch_time = time.time() self._launch_time = time.time()
self._subprocess_launch_time: Optional[float] = None self._subprocess_launch_time: Optional[float] = None
@ -109,26 +114,18 @@ class ServerManagerApp:
""" """
return self._restart_minutes return self._restart_minutes
def run_interactive(self) -> None: def _prerun(self) -> None:
"""Run the app loop to completion.""" """Common code at the start of any run."""
import code
if self._running_interactive: # Make sure we don't call run multiple times.
raise RuntimeError('Already running interactively.') if self._running:
self._running_interactive = True raise RuntimeError('Already running.')
self._running = True
# Print basic usage info in interactive mode. dbgstr = 'debug' if __debug__ else 'opt'
if sys.stdin.isatty(): intstr = 'interactive' if self._interactive else 'noninteractive'
if __debug__: print(f'{Clr.CYN}{Clr.BLD}BallisticaCore server manager {VERSION_STR}'
modestr = '(debug mode)' f' starting up ({dbgstr}/{intstr} mode)...{Clr.RST}')
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}')
# Python will handle SIGINT for us (as KeyboardInterrupt) but we # Python will handle SIGINT for us (as KeyboardInterrupt) but we
# need to register a SIGTERM handler so we have a chance to clean # 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 = Thread(target=self._bg_thread_main)
self._subprocess_thread.start() 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} context = {'__name__': '__console__', '__doc__': None, 'mgr': self}
# Enable tab-completion if possible. # Enable tab-completion if possible.
@ -150,7 +197,7 @@ class ServerManagerApp:
try: try:
code.interact(local=context, banner='', exitmsg='') code.interact(local=context, banner='', exitmsg='')
except SystemExit: 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 # Need to catch this so we can clean up, otherwise we'll be
# left in limbo with our process thread still running. # left in limbo with our process thread still running.
pass pass
@ -158,14 +205,7 @@ class ServerManagerApp:
print(f'{Clr.SRED}Unexpected interpreter exception:' print(f'{Clr.SRED}Unexpected interpreter exception:'
f' {exc} ({type(exc)}){Clr.RST}') f' {exc} ({type(exc)}){Clr.RST}')
print(f'{Clr.CYN}Server manager shutting down...{Clr.RST}') self._postrun()
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 cmd(self, statement: str) -> None: def cmd(self, statement: str) -> None:
"""Exec a Python command on the current running server subprocess. """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 # If we're asking for an immediate restart but don't get one within
# the grace period, bring down the hammer. # the grace period, bring down the hammer.
if immediate: if immediate:
self._subprocess_force_kill_time = (time.time() + self._subprocess_force_kill_time = (
IMMEDIATE_SHUTDOWN_TIME_LIMIT) time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT)
def shutdown(self, immediate: bool = True) -> None: def shutdown(self, immediate: bool = True) -> None:
"""Shut down the server subprocess and exit the wrapper """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 # If we're asking for an immediate shutdown but don't get one within
# the grace period, bring down the hammer. # the grace period, bring down the hammer.
if immediate: if immediate:
self._subprocess_force_kill_time = (time.time() + self._subprocess_force_kill_time = (
IMMEDIATE_SHUTDOWN_TIME_LIMIT) 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: 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 import yaml
with open(user_config_path) as infile: with open(self._config_path) as infile:
user_config_raw = yaml.safe_load(infile.read()) user_config_raw = yaml.safe_load(infile.read())
# An empty config file will yield None, and that's ok. # 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) return dataclass_from_dict(ServerConfig, user_config_raw)
else: else:
print( 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.', f'; will use default config.',
file=sys.stderr, file=sys.stderr,
flush=True) flush=True)
@ -535,74 +665,10 @@ class ServerManagerApp:
print(f'{Clr.CYN}Subprocess stopped.{Clr.RST}') 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: def main() -> None:
"""Run a BallisticaCore server manager in interactive mode.""" """Run the BallisticaCore server manager."""
try: try:
# Note: we need to parse args before we chdir since we might be ServerManagerApp().run()
# 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()
except CleanError as exc: except CleanError as exc:
# For clean errors, do a simple print and fail; no tracebacks/etc. # For clean errors, do a simple print and fail; no tracebacks/etc.
# Any others will bubble up and give us the usual mess. # Any others will bubble up and give us the usual mess.

View File

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

View File

@ -1,5 +1,5 @@
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND --> <!-- 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, <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> 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> <hr>

View File

@ -21,7 +21,7 @@
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't change here. // These are set automatically via script; don't change here.
const int kAppBuildNumber = 20303; const int kAppBuildNumber = 20307;
const char* kAppVersion = "1.6.0"; const char* kAppVersion = "1.6.0";
// Our standalone globals. // Our standalone globals.

View File

@ -94,21 +94,21 @@ class ServerConfig:
# If present, the server manager will attempt to gracefully exit after # 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 # this amount of time. A graceful exit can occur at the end of a series
# or other opportune time. # 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 # server binary will be restarted periodically to clear any memory
# leaks or other bad state. # leaks or other bad state.
clean_exit_minutes: Optional[float] = None clean_exit_minutes: Optional[float] = None
# If present, the server manager will shut down immediately after this # 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. # 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 # server binary will be restarted periodically to clear any memory
# leaks or other bad state. # leaks or other bad state.
unclean_exit_minutes: Optional[float] = None unclean_exit_minutes: Optional[float] = None
# If present, the server will shut down immediately if this amount of # If present, the server will shut down immediately if this amount of
# time passes with no activity from any players. # 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 # server binary will be restarted periodically to clear any memory
# leaks or other bad state. # leaks or other bad state.
idle_exit_minutes: Optional[float] = None idle_exit_minutes: Optional[float] = None