mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-10-20 00:00:39 +00:00
1.7.10
This commit is contained in:
parent
7d3de819bf
commit
59730e19b3
17 changed files with 180 additions and 65 deletions
2
dist/ba_data/data/langdata.json
vendored
2
dist/ba_data/data/langdata.json
vendored
|
|
@ -78,7 +78,7 @@
|
||||||
"adan",
|
"adan",
|
||||||
"Adeel (AdeZ {@adez_})",
|
"Adeel (AdeZ {@adez_})",
|
||||||
"Adel",
|
"Adel",
|
||||||
"Rio adi",
|
"Rio Adi",
|
||||||
"Rayhan Adiansyah",
|
"Rayhan Adiansyah",
|
||||||
"Yonas Adiel",
|
"Yonas Adiel",
|
||||||
"admin",
|
"admin",
|
||||||
|
|
|
||||||
2
dist/ba_data/data/languages/venetian.json
vendored
2
dist/ba_data/data/languages/venetian.json
vendored
|
|
@ -1238,7 +1238,7 @@
|
||||||
"enablePackageModsText": "Ativa pacheti mod łogałi",
|
"enablePackageModsText": "Ativa pacheti mod łogałi",
|
||||||
"enterPromoCodeText": "Insarisi còdaze",
|
"enterPromoCodeText": "Insarisi còdaze",
|
||||||
"forTestingText": "Nota: vałori vàłidi soło par proe. Sortendo da l'apl łi vegnarà perdesti.",
|
"forTestingText": "Nota: vałori vàłidi soło par proe. Sortendo da l'apl łi vegnarà perdesti.",
|
||||||
"helpTranslateText": "Łe tradusion de ${APP_NAME} łe ze curàe da vołontari.\nSe A te vol darghe na ociada a cheła veneta, struca so'l boton\ncuà soto. Curada da Còdaze Veneto: codazeveneto@gmail.com",
|
"helpTranslateText": "Łe tradusion de ${APP_NAME} łe ze curàe da vołontari.\nSe te vołi darghe na ociada a cheła veneta, struca so'l boton\ncuà soto. Curada da VeC: venetianlanguage@gmail.com",
|
||||||
"kickIdlePlayersText": "Para fora zugadori in sonera",
|
"kickIdlePlayersText": "Para fora zugadori in sonera",
|
||||||
"kidFriendlyModeText": "Modałidà bocia (viołensa reduzesta, evc)",
|
"kidFriendlyModeText": "Modałidà bocia (viołensa reduzesta, evc)",
|
||||||
"languageText": "Łengua",
|
"languageText": "Łengua",
|
||||||
|
|
|
||||||
15
dist/ba_data/python/ba/_accountv2.py
vendored
15
dist/ba_data/python/ba/_accountv2.py
vendored
|
|
@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
|
||||||
import _ba
|
import _ba
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
pass
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class AccountV2Subsystem:
|
class AccountV2Subsystem:
|
||||||
|
|
@ -111,10 +111,21 @@ class AccountV2Subsystem:
|
||||||
|
|
||||||
|
|
||||||
class AccountV2Handle:
|
class AccountV2Handle:
|
||||||
"""Handle for interacting with a v2 account."""
|
"""Handle for interacting with a V2 account.
|
||||||
|
|
||||||
|
This class supports the 'with' statement, which is how it is
|
||||||
|
used with some operations such as cloud messaging.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.tag = '?'
|
self.tag = '?'
|
||||||
|
|
||||||
self.workspacename: str | None = None
|
self.workspacename: str | None = None
|
||||||
self.workspaceid: str | None = None
|
self.workspaceid: str | None = None
|
||||||
|
|
||||||
|
def __enter__(self) -> None:
|
||||||
|
"""Support for "with" statement.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
|
||||||
|
"""Support for "with" statement."""
|
||||||
|
|
|
||||||
5
dist/ba_data/python/ba/_benchmark.py
vendored
5
dist/ba_data/python/ba/_benchmark.py
vendored
|
|
@ -60,7 +60,7 @@ def run_stress_test(playlist_type: str = 'Random',
|
||||||
from ba._generated.enums import TimeType
|
from ba._generated.enums import TimeType
|
||||||
_ba.screenmessage(
|
_ba.screenmessage(
|
||||||
'Beginning stress test.. use '
|
'Beginning stress test.. use '
|
||||||
"'End Game' to stop testing.",
|
"'End Test' to stop testing.",
|
||||||
color=(1, 1, 0))
|
color=(1, 1, 0))
|
||||||
with _ba.Context('ui'):
|
with _ba.Context('ui'):
|
||||||
start_stress_test({
|
start_stress_test({
|
||||||
|
|
@ -138,7 +138,8 @@ def _reset_stress_test(args: dict[str, Any]) -> None:
|
||||||
|
|
||||||
def run_gpu_benchmark() -> None:
|
def run_gpu_benchmark() -> None:
|
||||||
"""Kick off a benchmark to test gpu speeds."""
|
"""Kick off a benchmark to test gpu speeds."""
|
||||||
_ba.screenmessage('FIXME: Not wired up yet.', color=(1, 0, 0))
|
# FIXME: Not wired up yet.
|
||||||
|
_ba.screenmessage('Not wired up yet.', color=(1, 0, 0))
|
||||||
|
|
||||||
|
|
||||||
def run_media_reload_benchmark() -> None:
|
def run_media_reload_benchmark() -> None:
|
||||||
|
|
|
||||||
2
dist/ba_data/python/ba/_bootstrap.py
vendored
2
dist/ba_data/python/ba/_bootstrap.py
vendored
|
|
@ -45,7 +45,7 @@ def bootstrap() -> None:
|
||||||
|
|
||||||
# Give a soft warning if we're being used with a different binary
|
# Give a soft warning if we're being used with a different binary
|
||||||
# version than we expect.
|
# version than we expect.
|
||||||
expected_build = 20887
|
expected_build = 20891
|
||||||
running_build: int = env['build_number']
|
running_build: int = env['build_number']
|
||||||
if running_build != expected_build:
|
if running_build != expected_build:
|
||||||
print(
|
print(
|
||||||
|
|
|
||||||
|
|
@ -552,7 +552,7 @@ class AccountSettingsWindow(ba.Window):
|
||||||
position=((self._sub_width - button_width) * 0.5, v + 30),
|
position=((self._sub_width - button_width) * 0.5, v + 30),
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
size=(button_width, 60),
|
size=(button_width, 60),
|
||||||
label=ba.Lstr(resource=self._r + '.manageAccount'),
|
label=ba.Lstr(resource=self._r + '.manageAccountText'),
|
||||||
color=(0.55, 0.5, 0.6),
|
color=(0.55, 0.5, 0.6),
|
||||||
icon=ba.gettexture('settingsIcon'),
|
icon=ba.gettexture('settingsIcon'),
|
||||||
textcolor=(0.75, 0.7, 0.8),
|
textcolor=(0.75, 0.7, 0.8),
|
||||||
|
|
|
||||||
53
dist/ba_data/python/bastd/ui/mainmenu.py
vendored
53
dist/ba_data/python/bastd/ui/mainmenu.py
vendored
|
|
@ -56,10 +56,11 @@ class MainMenuWindow(ba.Window):
|
||||||
self._gather_button: ba.Widget | None = None
|
self._gather_button: ba.Widget | None = None
|
||||||
self._start_button: ba.Widget | None = None
|
self._start_button: ba.Widget | None = None
|
||||||
self._watch_button: ba.Widget | None = None
|
self._watch_button: ba.Widget | None = None
|
||||||
self._gc_button: ba.Widget | None = None
|
self._account_button: ba.Widget | None = None
|
||||||
self._how_to_play_button: ba.Widget | None = None
|
self._how_to_play_button: ba.Widget | None = None
|
||||||
self._credits_button: ba.Widget | None = None
|
self._credits_button: ba.Widget | None = None
|
||||||
self._settings_button: ba.Widget | None = None
|
self._settings_button: ba.Widget | None = None
|
||||||
|
self._next_refresh_allow_time = 0.0
|
||||||
|
|
||||||
self._store_char_tex = self._get_store_char_tex()
|
self._store_char_tex = self._get_store_char_tex()
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@ class MainMenuWindow(ba.Window):
|
||||||
self._account_state_num = ba.internal.get_v1_account_state_num()
|
self._account_state_num = ba.internal.get_v1_account_state_num()
|
||||||
self._account_type = (ba.internal.get_v1_account_type()
|
self._account_type = (ba.internal.get_v1_account_type()
|
||||||
if self._account_state == 'signed_in' else None)
|
if self._account_state == 'signed_in' else None)
|
||||||
self._refresh_timer = ba.Timer(1.0,
|
self._refresh_timer = ba.Timer(0.27,
|
||||||
ba.WeakCall(self._check_refresh),
|
ba.WeakCall(self._check_refresh),
|
||||||
repeat=True,
|
repeat=True,
|
||||||
timetype=ba.TimeType.REAL)
|
timetype=ba.TimeType.REAL)
|
||||||
|
|
@ -132,11 +133,15 @@ class MainMenuWindow(ba.Window):
|
||||||
if not self._root_widget:
|
if not self._root_widget:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
now = ba.time(ba.TimeType.REAL)
|
||||||
|
if now < self._next_refresh_allow_time:
|
||||||
|
return
|
||||||
|
|
||||||
# Don't refresh for the first few seconds the game is up so we don't
|
# Don't refresh for the first few seconds the game is up so we don't
|
||||||
# interrupt the transition in.
|
# interrupt the transition in.
|
||||||
ba.app.main_menu_window_refresh_check_count += 1
|
# ba.app.main_menu_window_refresh_check_count += 1
|
||||||
if ba.app.main_menu_window_refresh_check_count < 4:
|
# if ba.app.main_menu_window_refresh_check_count < 4:
|
||||||
return
|
# return
|
||||||
|
|
||||||
store_char_tex = self._get_store_char_tex()
|
store_char_tex = self._get_store_char_tex()
|
||||||
account_state_num = ba.internal.get_v1_account_state_num()
|
account_state_num = ba.internal.get_v1_account_state_num()
|
||||||
|
|
@ -250,8 +255,12 @@ class MainMenuWindow(ba.Window):
|
||||||
scale=scale,
|
scale=scale,
|
||||||
size=(self._button_width, self._button_height),
|
size=(self._button_width, self._button_height),
|
||||||
autoselect=self._use_autoselect,
|
autoselect=self._use_autoselect,
|
||||||
label=ba.Lstr(resource=self._r + '.endGameText'),
|
label=ba.Lstr(resource=self._r +
|
||||||
on_activate_call=self._confirm_end_game)
|
('.endTestText' if self._is_benchmark(
|
||||||
|
) else '.endGameText')),
|
||||||
|
on_activate_call=(self._confirm_end_test
|
||||||
|
if self._is_benchmark() else
|
||||||
|
self._confirm_end_game))
|
||||||
# Assume we're in a client-session.
|
# Assume we're in a client-session.
|
||||||
else:
|
else:
|
||||||
ba.buttonwidget(
|
ba.buttonwidget(
|
||||||
|
|
@ -424,6 +433,7 @@ class MainMenuWindow(ba.Window):
|
||||||
self._tdelay = 2.0
|
self._tdelay = 2.0
|
||||||
self._t_delay_inc = 0.02
|
self._t_delay_inc = 0.02
|
||||||
self._t_delay_play = 1.7
|
self._t_delay_play = 1.7
|
||||||
|
self._next_refresh_allow_time = ba.time(ba.TimeType.REAL) + 2.01
|
||||||
ba.app.did_menu_intro = True
|
ba.app.did_menu_intro = True
|
||||||
self._width = 400.0
|
self._width = 400.0
|
||||||
self._height = 200.0
|
self._height = 200.0
|
||||||
|
|
@ -605,7 +615,7 @@ class MainMenuWindow(ba.Window):
|
||||||
this_b_width = self._button_width
|
this_b_width = self._button_width
|
||||||
h, v, scale = positions[self._p_index]
|
h, v, scale = positions[self._p_index]
|
||||||
self._p_index += 1
|
self._p_index += 1
|
||||||
self._gc_button = ba.buttonwidget(
|
self._account_button = ba.buttonwidget(
|
||||||
parent=self._root_widget,
|
parent=self._root_widget,
|
||||||
position=(h - this_b_width * 0.5 * scale, v),
|
position=(h - this_b_width * 0.5 * scale, v),
|
||||||
size=(this_b_width, self._button_height),
|
size=(this_b_width, self._button_height),
|
||||||
|
|
@ -633,7 +643,7 @@ class MainMenuWindow(ba.Window):
|
||||||
tilt_scale=0.0)
|
tilt_scale=0.0)
|
||||||
self._tdelay += self._t_delay_inc
|
self._tdelay += self._t_delay_inc
|
||||||
else:
|
else:
|
||||||
self._gc_button = None
|
self._account_button = None
|
||||||
|
|
||||||
# How-to-play button.
|
# How-to-play button.
|
||||||
h, v, scale = positions[self._p_index]
|
h, v, scale = positions[self._p_index]
|
||||||
|
|
@ -848,7 +858,7 @@ class MainMenuWindow(ba.Window):
|
||||||
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
ba.containerwidget(edit=self._root_widget, transition='out_left')
|
||||||
ba.app.ui.set_main_menu_window(
|
ba.app.ui.set_main_menu_window(
|
||||||
AccountSettingsWindow(
|
AccountSettingsWindow(
|
||||||
origin_widget=self._gc_button).get_root_widget())
|
origin_widget=self._account_button).get_root_widget())
|
||||||
|
|
||||||
def _on_store_pressed(self) -> None:
|
def _on_store_pressed(self) -> None:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
|
|
@ -863,6 +873,11 @@ class MainMenuWindow(ba.Window):
|
||||||
StoreBrowserWindow(
|
StoreBrowserWindow(
|
||||||
origin_widget=self._store_button).get_root_widget())
|
origin_widget=self._store_button).get_root_widget())
|
||||||
|
|
||||||
|
def _is_benchmark(self) -> bool:
|
||||||
|
session = ba.internal.get_foreground_host_session()
|
||||||
|
return (getattr(session, 'benchmark_type', None) == 'cpu'
|
||||||
|
or ba.app.stress_test_reset_timer is not None)
|
||||||
|
|
||||||
def _confirm_end_game(self) -> None:
|
def _confirm_end_game(self) -> None:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from bastd.ui.confirm import ConfirmWindow
|
from bastd.ui.confirm import ConfirmWindow
|
||||||
|
|
@ -874,6 +889,16 @@ class MainMenuWindow(ba.Window):
|
||||||
self._end_game,
|
self._end_game,
|
||||||
cancel_is_selected=True)
|
cancel_is_selected=True)
|
||||||
|
|
||||||
|
def _confirm_end_test(self) -> None:
|
||||||
|
# pylint: disable=cyclic-import
|
||||||
|
from bastd.ui.confirm import ConfirmWindow
|
||||||
|
|
||||||
|
# Select cancel by default; this occasionally gets called by accident
|
||||||
|
# in a fit of button mashing and this will help reduce damage.
|
||||||
|
ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'),
|
||||||
|
self._end_game,
|
||||||
|
cancel_is_selected=True)
|
||||||
|
|
||||||
def _confirm_end_replay(self) -> None:
|
def _confirm_end_replay(self) -> None:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
from bastd.ui.confirm import ConfirmWindow
|
from bastd.ui.confirm import ConfirmWindow
|
||||||
|
|
@ -965,8 +990,8 @@ class MainMenuWindow(ba.Window):
|
||||||
ba.app.ui.main_menu_selection = 'Credits'
|
ba.app.ui.main_menu_selection = 'Credits'
|
||||||
elif sel == self._settings_button:
|
elif sel == self._settings_button:
|
||||||
ba.app.ui.main_menu_selection = 'Settings'
|
ba.app.ui.main_menu_selection = 'Settings'
|
||||||
elif sel == self._gc_button:
|
elif sel == self._account_button:
|
||||||
ba.app.ui.main_menu_selection = 'GameService'
|
ba.app.ui.main_menu_selection = 'Account'
|
||||||
elif sel == self._store_button:
|
elif sel == self._store_button:
|
||||||
ba.app.ui.main_menu_selection = 'Store'
|
ba.app.ui.main_menu_selection = 'Store'
|
||||||
elif sel == self._quit_button:
|
elif sel == self._quit_button:
|
||||||
|
|
@ -997,8 +1022,8 @@ class MainMenuWindow(ba.Window):
|
||||||
sel = self._credits_button
|
sel = self._credits_button
|
||||||
elif sel_name == 'Settings':
|
elif sel_name == 'Settings':
|
||||||
sel = self._settings_button
|
sel = self._settings_button
|
||||||
elif sel_name == 'GameService':
|
elif sel_name == 'Account':
|
||||||
sel = self._gc_button
|
sel = self._account_button
|
||||||
elif sel_name == 'Store':
|
elif sel_name == 'Store':
|
||||||
sel = self._store_button
|
sel = self._store_button
|
||||||
elif sel_name == 'Quit':
|
elif sel_name == 'Quit':
|
||||||
|
|
|
||||||
|
|
@ -146,13 +146,14 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
|
||||||
|
|
||||||
ba.pushcall(_print_in_logic_thread, from_other_thread=True)
|
ba.pushcall(_print_in_logic_thread, from_other_thread=True)
|
||||||
|
|
||||||
def _print_test_results(call: Callable[[], Any]) -> None:
|
def _print_test_results(call: Callable[[], Any]) -> bool:
|
||||||
"""Run the provided call; return success/fail text & color."""
|
"""Run the provided call, print result, & return success."""
|
||||||
starttime = time.monotonic()
|
starttime = time.monotonic()
|
||||||
try:
|
try:
|
||||||
call()
|
call()
|
||||||
duration = time.monotonic() - starttime
|
duration = time.monotonic() - starttime
|
||||||
_print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
|
_print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
|
||||||
|
return True
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
import traceback
|
import traceback
|
||||||
duration = time.monotonic() - starttime
|
duration = time.monotonic() - starttime
|
||||||
|
|
@ -161,6 +162,7 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
|
||||||
_print(msg, color=(1.0, 1.0, 0.3))
|
_print(msg, color=(1.0, 1.0, 0.3))
|
||||||
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
|
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
|
||||||
have_error[0] = True
|
have_error[0] = True
|
||||||
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_print(f'Running network diagnostics...\n'
|
_print(f'Running network diagnostics...\n'
|
||||||
|
|
@ -177,14 +179,23 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
|
||||||
# V1 ping
|
# V1 ping
|
||||||
baseaddr = ba.internal.get_master_server_address(source=0, version=1)
|
baseaddr = ba.internal.get_master_server_address(source=0, version=1)
|
||||||
_print(f'\nContacting V1 master-server src0 ({baseaddr})...')
|
_print(f'\nContacting V1 master-server src0 ({baseaddr})...')
|
||||||
_print_test_results(lambda: _test_fetch(baseaddr))
|
v1worked = _print_test_results(lambda: _test_fetch(baseaddr))
|
||||||
|
|
||||||
# V1 alternate ping
|
# V1 alternate ping (only if primary fails since this often fails).
|
||||||
baseaddr = ba.internal.get_master_server_address(source=1, version=1)
|
if v1worked:
|
||||||
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
|
_print('\nSkipping V1 master-server src1 test since src0 worked.')
|
||||||
_print_test_results(lambda: _test_fetch(baseaddr))
|
else:
|
||||||
|
baseaddr = ba.internal.get_master_server_address(source=1,
|
||||||
|
version=1)
|
||||||
|
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
|
||||||
|
_print_test_results(lambda: _test_fetch(baseaddr))
|
||||||
|
|
||||||
_print(f'\nV1-test-log: {ba.app.net.v1_test_log}')
|
if 'none succeeded' in ba.app.net.v1_test_log:
|
||||||
|
_print(f'\nV1-test-log failed: {ba.app.net.v1_test_log}',
|
||||||
|
color=(1, 0, 0))
|
||||||
|
have_error[0] = True
|
||||||
|
else:
|
||||||
|
_print(f'\nV1-test-log ok: {ba.app.net.v1_test_log}')
|
||||||
|
|
||||||
for srcid, result in sorted(ba.app.net.v1_ctest_results.items()):
|
for srcid, result in sorted(ba.app.net.v1_ctest_results.items()):
|
||||||
_print(f'\nV1 src{srcid} result: {result}')
|
_print(f'\nV1 src{srcid} result: {result}')
|
||||||
|
|
|
||||||
|
|
@ -522,6 +522,7 @@ class TournamentEntryWindow(popup.PopupWindow):
|
||||||
if ticket_count is not None and ticket_count < ticket_cost:
|
if ticket_count is not None and ticket_count < ticket_cost:
|
||||||
getcurrency.show_get_tickets_prompt()
|
getcurrency.show_get_tickets_prompt()
|
||||||
ba.playsound(ba.getsound('error'))
|
ba.playsound(ba.getsound('error'))
|
||||||
|
self._transition_out()
|
||||||
return
|
return
|
||||||
|
|
||||||
cur_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
|
cur_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
|
||||||
|
|
|
||||||
1
dist/ba_data/python/efro/message/_message.py
vendored
1
dist/ba_data/python/efro/message/_message.py
vendored
|
|
@ -61,6 +61,7 @@ class ErrorSysResponse(SysResponse):
|
||||||
REMOTE_CLEAN = 1
|
REMOTE_CLEAN = 1
|
||||||
LOCAL = 2
|
LOCAL = 2
|
||||||
COMMUNICATION = 3
|
COMMUNICATION = 3
|
||||||
|
REMOTE_COMMUNICATION = 4
|
||||||
|
|
||||||
error_message: Annotated[str, IOAttrs('m')]
|
error_message: Annotated[str, IOAttrs('m')]
|
||||||
error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.REMOTE
|
error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.REMOTE
|
||||||
|
|
|
||||||
77
dist/ba_data/python/efro/message/_protocol.py
vendored
77
dist/ba_data/python/efro/message/_protocol.py
vendored
|
|
@ -8,10 +8,9 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
import traceback
|
import traceback
|
||||||
import logging
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from efro.error import CleanError
|
from efro.error import CleanError, CommunicationError
|
||||||
from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict,
|
from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict,
|
||||||
dataclass_from_dict)
|
dataclass_from_dict)
|
||||||
from efro.message._message import (Message, Response, SysResponse,
|
from efro.message._message import (Message, Response, SysResponse,
|
||||||
|
|
@ -34,24 +33,50 @@ class MessageProtocol:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
message_types: dict[int, type[Message]],
|
message_types: dict[int, type[Message]],
|
||||||
response_types: dict[int, type[Response]],
|
response_types: dict[int, type[Response]],
|
||||||
|
forward_communication_errors: bool = False,
|
||||||
forward_clean_errors: bool = False,
|
forward_clean_errors: bool = False,
|
||||||
remote_errors_include_stack_traces: bool = False) -> None:
|
remote_errors_include_stack_traces: bool = False,
|
||||||
|
log_remote_errors: bool = True) -> None:
|
||||||
"""Create a protocol with a given configuration.
|
"""Create a protocol with a given configuration.
|
||||||
|
|
||||||
Note that common response types are automatically registered
|
If 'forward_communication_errors' is True,
|
||||||
with (unchanging negative ids) so they don't need to be passed
|
efro.error.CommunicationErrors raised on the receiver end will
|
||||||
explicitly (but can be if a different id is desired).
|
result in a matching error raised back on the sender. This can
|
||||||
|
be useful if the receiver will be in some way forwarding
|
||||||
|
messages along and the sender doesn't need to know where
|
||||||
|
communication breakdowns occurred; only that they did.
|
||||||
|
|
||||||
If 'forward_clean_errors' is True, efro.error.CleanError
|
If 'forward_clean_errors' is True, efro.error.CleanError
|
||||||
exceptions raised on the receiver end will result in a matching
|
exceptions raised on the receiver end will result in a matching
|
||||||
CleanError raised back on the sender. All other Exception types
|
CleanError raised back on the sender.
|
||||||
come across as efro.error.RemoteError.
|
|
||||||
|
|
||||||
If 'remote_errors_include_stack_traces' is True, stringified stack
|
When an exception is not covered by the optional forwarding
|
||||||
traces will be returned to the sender for exceptions occurring
|
mechanisms above, it will come across as efro.error.RemoteError
|
||||||
on the receiver end. This can make debugging easier but should
|
and the exception will be logged on the receiver
|
||||||
only be used when the client is trusted to see such info.
|
end - at least by default (see details below).
|
||||||
|
|
||||||
|
If 'remote_errors_include_stack_traces' is True, stringified
|
||||||
|
stack traces will be returned with efro.error.RemoteError
|
||||||
|
exceptions. This is useful for debugging but should only be
|
||||||
|
enabled in cases where the sender is trusted to see internal
|
||||||
|
details of the receiver.
|
||||||
|
|
||||||
|
By default, when a message-handling exception will result in an
|
||||||
|
efro.error.RemoteError being returned to the sender, the
|
||||||
|
exception will be logged on the receiver. This is because the
|
||||||
|
goal is usually to avoid returning opaque RemoteErrors and to
|
||||||
|
instead return something meaningful as part of the expected
|
||||||
|
response type (even if that value itself represents a logical
|
||||||
|
error state). If 'log_remote_errors' is False, however, such
|
||||||
|
exceptions will not be logged on the receiver. This can be
|
||||||
|
useful in combination with 'remote_errors_include_stack_traces'
|
||||||
|
and 'forward_clean_errors' in situations where all error
|
||||||
|
logging/management will be happening on the sender end. Be
|
||||||
|
aware, however, that in that case it may be possible for
|
||||||
|
communication errors to prevent such error messages from
|
||||||
|
ever being seen.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
self.message_types_by_id: dict[int, type[Message]] = {}
|
self.message_types_by_id: dict[int, type[Message]] = {}
|
||||||
self.message_ids_by_type: dict[type[Message], int] = {}
|
self.message_ids_by_type: dict[type[Message], int] = {}
|
||||||
self.response_types_by_id: dict[int, type[Response]
|
self.response_types_by_id: dict[int, type[Response]
|
||||||
|
|
@ -123,8 +148,10 @@ class MessageProtocol:
|
||||||
' all types are required to have unique names.')
|
' all types are required to have unique names.')
|
||||||
|
|
||||||
self.forward_clean_errors = forward_clean_errors
|
self.forward_clean_errors = forward_clean_errors
|
||||||
|
self.forward_communication_errors = forward_communication_errors
|
||||||
self.remote_errors_include_stack_traces = (
|
self.remote_errors_include_stack_traces = (
|
||||||
remote_errors_include_stack_traces)
|
remote_errors_include_stack_traces)
|
||||||
|
self.log_remote_errors = log_remote_errors
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def encode_dict(obj: dict) -> str:
|
def encode_dict(obj: dict) -> str:
|
||||||
|
|
@ -139,23 +166,31 @@ class MessageProtocol:
|
||||||
"""Encode a response to a json ready dict."""
|
"""Encode a response to a json ready dict."""
|
||||||
return self._to_dict(response, self.response_ids_by_type, 'response')
|
return self._to_dict(response, self.response_ids_by_type, 'response')
|
||||||
|
|
||||||
def error_to_response(self, exc: Exception) -> SysResponse:
|
def error_to_response(self, exc: Exception) -> tuple[SysResponse, bool]:
|
||||||
"""Translate an error to a response."""
|
"""Translate an Exception to a SysResponse.
|
||||||
|
|
||||||
# Log any errors we got during handling.
|
Also returns whether the error should be logged if this happened
|
||||||
logging.exception('Error in efro.message handling.')
|
within handle_raw_message().
|
||||||
|
"""
|
||||||
|
|
||||||
# If anything goes wrong, return a ErrorSysResponse instead.
|
# If anything goes wrong, return a ErrorSysResponse instead.
|
||||||
# (either CLEAN or generic REMOTE)
|
# (either CLEAN or generic REMOTE)
|
||||||
if isinstance(exc, CleanError) and self.forward_clean_errors:
|
if self.forward_clean_errors and isinstance(exc, CleanError):
|
||||||
return ErrorSysResponse(
|
return (ErrorSysResponse(
|
||||||
error_message=str(exc),
|
error_message=str(exc),
|
||||||
error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN)
|
error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN), False)
|
||||||
return ErrorSysResponse(
|
if self.forward_communication_errors and isinstance(
|
||||||
|
exc, CommunicationError):
|
||||||
|
return (ErrorSysResponse(
|
||||||
|
error_message=str(exc),
|
||||||
|
error_type=ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION),
|
||||||
|
False)
|
||||||
|
return (ErrorSysResponse(
|
||||||
error_message=(traceback.format_exc()
|
error_message=(traceback.format_exc()
|
||||||
if self.remote_errors_include_stack_traces else
|
if self.remote_errors_include_stack_traces else
|
||||||
'An internal error has occurred.'),
|
'An internal error has occurred.'),
|
||||||
error_type=ErrorSysResponse.ErrorType.REMOTE)
|
error_type=ErrorSysResponse.ErrorType.REMOTE),
|
||||||
|
self.log_remote_errors)
|
||||||
|
|
||||||
def _to_dict(self, message: Any, ids_by_type: dict[type, int],
|
def _to_dict(self, message: Any, ids_by_type: dict[type, int],
|
||||||
opname: str) -> dict:
|
opname: str) -> dict:
|
||||||
|
|
|
||||||
26
dist/ba_data/python/efro/message/_receiver.py
vendored
26
dist/ba_data/python/efro/message/_receiver.py
vendored
|
|
@ -260,14 +260,14 @@ class MessageReceiver:
|
||||||
return self.protocol.encode_dict(response_dict)
|
return self.protocol.encode_dict(response_dict)
|
||||||
|
|
||||||
def encode_error_response(self, bound_obj: Any, message: Message | None,
|
def encode_error_response(self, bound_obj: Any, message: Message | None,
|
||||||
exc: Exception) -> str:
|
exc: Exception) -> tuple[str, bool]:
|
||||||
"""Given an error, return a response ready for sending."""
|
"""Given an error, return sysresponse str and whether to log."""
|
||||||
response = self.protocol.error_to_response(exc)
|
response, dolog = self.protocol.error_to_response(exc)
|
||||||
response_dict = self.protocol.response_to_dict(response)
|
response_dict = self.protocol.response_to_dict(response)
|
||||||
if self._encode_filter_call is not None:
|
if self._encode_filter_call is not None:
|
||||||
self._encode_filter_call(bound_obj, message, response,
|
self._encode_filter_call(bound_obj, message, response,
|
||||||
response_dict)
|
response_dict)
|
||||||
return self.protocol.encode_dict(response_dict)
|
return self.protocol.encode_dict(response_dict), dolog
|
||||||
|
|
||||||
def handle_raw_message(self,
|
def handle_raw_message(self,
|
||||||
bound_obj: Any,
|
bound_obj: Any,
|
||||||
|
|
@ -296,7 +296,11 @@ class MessageReceiver:
|
||||||
if (raise_unregistered
|
if (raise_unregistered
|
||||||
and isinstance(exc, UnregisteredMessageIDError)):
|
and isinstance(exc, UnregisteredMessageIDError)):
|
||||||
raise
|
raise
|
||||||
return self.encode_error_response(bound_obj, msg_decoded, exc)
|
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
|
||||||
|
exc)
|
||||||
|
if dolog:
|
||||||
|
logging.exception('Error in efro.message handling.')
|
||||||
|
return rstr
|
||||||
|
|
||||||
async def handle_raw_message_async(
|
async def handle_raw_message_async(
|
||||||
self,
|
self,
|
||||||
|
|
@ -324,7 +328,11 @@ class MessageReceiver:
|
||||||
if (raise_unregistered
|
if (raise_unregistered
|
||||||
and isinstance(exc, UnregisteredMessageIDError)):
|
and isinstance(exc, UnregisteredMessageIDError)):
|
||||||
raise
|
raise
|
||||||
return self.encode_error_response(bound_obj, msg_decoded, exc)
|
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
|
||||||
|
exc)
|
||||||
|
if dolog:
|
||||||
|
logging.exception('Error in efro.message handling.')
|
||||||
|
return rstr
|
||||||
|
|
||||||
|
|
||||||
class BoundMessageReceiver:
|
class BoundMessageReceiver:
|
||||||
|
|
@ -348,9 +356,9 @@ class BoundMessageReceiver:
|
||||||
"""Given an error, return a response ready to send.
|
"""Given an error, return a response ready to send.
|
||||||
|
|
||||||
This should be used for any errors that happen outside of
|
This should be used for any errors that happen outside of
|
||||||
of standard handle_raw_message calls. Any errors within those
|
standard handle_raw_message calls. Any errors within those
|
||||||
calls should be automatically returned as encoded strings.
|
calls will be automatically returned as encoded strings.
|
||||||
"""
|
"""
|
||||||
# Passing None for Message here; we would only have that available
|
# Passing None for Message here; we would only have that available
|
||||||
# for things going wrong in the handler (which this is not for).
|
# for things going wrong in the handler (which this is not for).
|
||||||
return self._receiver.encode_error_response(self._obj, None, exc)
|
return self._receiver.encode_error_response(self._obj, None, exc)[0]
|
||||||
|
|
|
||||||
5
dist/ba_data/python/efro/message/_sender.py
vendored
5
dist/ba_data/python/efro/message/_sender.py
vendored
|
|
@ -272,6 +272,11 @@ class MessageSender:
|
||||||
is ErrorSysResponse.ErrorType.REMOTE_CLEAN):
|
is ErrorSysResponse.ErrorType.REMOTE_CLEAN):
|
||||||
raise CleanError(raw_response.error_message)
|
raise CleanError(raw_response.error_message)
|
||||||
|
|
||||||
|
if (self.protocol.forward_communication_errors
|
||||||
|
and raw_response.error_type is
|
||||||
|
ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION):
|
||||||
|
raise CommunicationError(raw_response.error_message)
|
||||||
|
|
||||||
# Everything else gets lumped in as a remote error.
|
# Everything else gets lumped in as a remote error.
|
||||||
raise RemoteError(raw_response.error_message,
|
raise RemoteError(raw_response.error_message,
|
||||||
peer_desc=('peer' if self._peer_desc_call is None
|
peer_desc=('peer' if self._peer_desc_call is None
|
||||||
|
|
|
||||||
27
dist/ba_data/python/efro/rpc.py
vendored
27
dist/ba_data/python/efro/rpc.py
vendored
|
|
@ -290,17 +290,24 @@ class RPCEndpoint:
|
||||||
|
|
||||||
async def send_message(self,
|
async def send_message(self,
|
||||||
message: bytes,
|
message: bytes,
|
||||||
timeout: float | None = None) -> bytes:
|
timeout: float | None = None,
|
||||||
|
close_on_error: bool = True) -> bytes:
|
||||||
"""Send a message to the peer and return a response.
|
"""Send a message to the peer and return a response.
|
||||||
|
|
||||||
If timeout is not provided, the default will be used.
|
If timeout is not provided, the default will be used.
|
||||||
Raises a CommunicationError if the round trip is not completed
|
Raises a CommunicationError if the round trip is not completed
|
||||||
for any reason.
|
for any reason.
|
||||||
|
|
||||||
|
By default, the entire endpoint will go down in the case of
|
||||||
|
errors. This allows messages to be treated as 'reliable' with
|
||||||
|
respect to a given endpoint. Pass close_on_error=False to
|
||||||
|
override this for a particular message.
|
||||||
"""
|
"""
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
self._check_env()
|
self._check_env()
|
||||||
|
|
||||||
if self._closing:
|
if self._closing:
|
||||||
raise CommunicationError('Endpoint is closed')
|
raise CommunicationError('Endpoint is closed.')
|
||||||
|
|
||||||
# We need to know their protocol, so if we haven't gotten a handshake
|
# We need to know their protocol, so if we haven't gotten a handshake
|
||||||
# from them yet, just wait.
|
# from them yet, just wait.
|
||||||
|
|
@ -358,6 +365,8 @@ class RPCEndpoint:
|
||||||
if self._debug_print:
|
if self._debug_print:
|
||||||
self._debug_print_call(
|
self._debug_print_call(
|
||||||
f'{self._label}: message {message_id} was cancelled.')
|
f'{self._label}: message {message_id} was cancelled.')
|
||||||
|
if close_on_error:
|
||||||
|
self.close()
|
||||||
raise CommunicationError() from exc
|
raise CommunicationError() from exc
|
||||||
except asyncio.TimeoutError as exc:
|
except asyncio.TimeoutError as exc:
|
||||||
if self._debug_print:
|
if self._debug_print:
|
||||||
|
|
@ -370,6 +379,9 @@ class RPCEndpoint:
|
||||||
# Remove the record of this message.
|
# Remove the record of this message.
|
||||||
del self._in_flight_messages[message_id]
|
del self._in_flight_messages[message_id]
|
||||||
|
|
||||||
|
if close_on_error:
|
||||||
|
self.close()
|
||||||
|
|
||||||
# Let the user know something went wrong.
|
# Let the user know something went wrong.
|
||||||
raise CommunicationError() from exc
|
raise CommunicationError() from exc
|
||||||
|
|
||||||
|
|
@ -407,7 +419,11 @@ class RPCEndpoint:
|
||||||
return self._closing
|
return self._closing
|
||||||
|
|
||||||
async def wait_closed(self) -> None:
|
async def wait_closed(self) -> None:
|
||||||
"""I said seagulls; mmmm; stop it now."""
|
"""I said seagulls; mmmm; stop it now.
|
||||||
|
|
||||||
|
Wait for the endpoint to finish closing. This is called by run()
|
||||||
|
so generally does not need to be explicitly called.
|
||||||
|
"""
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
self._check_env()
|
self._check_env()
|
||||||
|
|
||||||
|
|
@ -729,8 +745,9 @@ class RPCEndpoint:
|
||||||
|
|
||||||
def _check_env(self) -> None:
|
def _check_env(self) -> None:
|
||||||
# I was seeing that asyncio stuff wasn't working as expected if
|
# I was seeing that asyncio stuff wasn't working as expected if
|
||||||
# created in one thread and used in another, so let's enforce
|
# created in one thread and used in another (and have verified
|
||||||
# a single thread for all use of an instance.
|
# that this is part of the design), so let's enforce a single
|
||||||
|
# thread for all use of an instance.
|
||||||
if current_thread() is not self._thread:
|
if current_thread() is not self._thread:
|
||||||
raise RuntimeError('This must be called from the same thread'
|
raise RuntimeError('This must be called from the same thread'
|
||||||
' that the endpoint was created in.')
|
' that the endpoint was created in.')
|
||||||
|
|
|
||||||
BIN
dist/ba_root/mods/aarch64/BridgitMash.so
vendored
BIN
dist/ba_root/mods/aarch64/BridgitMash.so
vendored
Binary file not shown.
BIN
dist/bombsquad_headless
vendored
BIN
dist/bombsquad_headless
vendored
Binary file not shown.
BIN
dist/bombsquad_headless_aarch64
vendored
BIN
dist/bombsquad_headless_aarch64
vendored
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue