mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2025-10-20 00:00:39 +00:00
update from origin
This commit is contained in:
parent
8beb334d64
commit
bf2f252ee5
91 changed files with 1839 additions and 1281 deletions
29
dist/ba_data/python/babase/__init__.py
vendored
29
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -27,6 +27,7 @@ from _babase import (
|
|||
apptime,
|
||||
apptimer,
|
||||
AppTimer,
|
||||
can_toggle_fullscreen,
|
||||
charstr,
|
||||
clipboard_get_text,
|
||||
clipboard_has_text,
|
||||
|
|
@ -39,6 +40,7 @@ from _babase import (
|
|||
DisplayTimer,
|
||||
do_once,
|
||||
env,
|
||||
Env,
|
||||
fade_screen,
|
||||
fatal_error,
|
||||
get_display_resolution,
|
||||
|
|
@ -48,8 +50,9 @@ from _babase import (
|
|||
get_replays_dir,
|
||||
get_string_height,
|
||||
get_string_width,
|
||||
get_v1_cloud_log_file_path,
|
||||
getsimplesound,
|
||||
has_gamma_control,
|
||||
has_user_run_commands,
|
||||
have_chars,
|
||||
have_permission,
|
||||
in_logic_thread,
|
||||
|
|
@ -83,7 +86,12 @@ from _babase import (
|
|||
set_thread_name,
|
||||
set_ui_input_device,
|
||||
show_progress_bar,
|
||||
shutdown_suppress_begin,
|
||||
shutdown_suppress_end,
|
||||
shutdown_suppress_count,
|
||||
SimpleSound,
|
||||
supports_max_fps,
|
||||
supports_vsync,
|
||||
unlock_all_input,
|
||||
user_agent_string,
|
||||
Vec3,
|
||||
|
|
@ -96,12 +104,14 @@ from babase._appconfig import commit_app_config
|
|||
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
||||
from babase._appmode import AppMode
|
||||
from babase._appsubsystem import AppSubsystem
|
||||
from babase._appmodeselector import AppModeSelector
|
||||
from babase._appconfig import AppConfig
|
||||
from babase._apputils import (
|
||||
handle_leftover_v1_cloud_log_file,
|
||||
is_browser_likely_available,
|
||||
garbage_collect,
|
||||
get_remote_app_name,
|
||||
AppHealthMonitor,
|
||||
)
|
||||
from babase._cloud import CloudSubsystem
|
||||
from babase._emptyappmode import EmptyAppMode
|
||||
|
|
@ -135,7 +145,6 @@ from babase._general import (
|
|||
storagename,
|
||||
getclass,
|
||||
get_type_name,
|
||||
json_prep,
|
||||
)
|
||||
from babase._keyboard import Keyboard
|
||||
from babase._language import Lstr, LanguageSubsystem
|
||||
|
|
@ -153,6 +162,7 @@ from babase._math import normalized_color, is_point_in_box, vec3validate
|
|||
from babase._meta import MetadataSubsystem
|
||||
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
||||
from babase._stringedit import StringEditAdapter, StringEditSubsystem
|
||||
from babase._text import timestring
|
||||
|
||||
_babase.app = app = App()
|
||||
|
|
@ -169,12 +179,14 @@ __all__ = [
|
|||
'app',
|
||||
'App',
|
||||
'AppConfig',
|
||||
'AppHealthMonitor',
|
||||
'AppIntent',
|
||||
'AppIntentDefault',
|
||||
'AppIntentExec',
|
||||
'AppMode',
|
||||
'appname',
|
||||
'appnameupper',
|
||||
'AppModeSelector',
|
||||
'AppSubsystem',
|
||||
'apptime',
|
||||
'AppTime',
|
||||
|
|
@ -182,6 +194,7 @@ __all__ = [
|
|||
'apptimer',
|
||||
'AppTimer',
|
||||
'Call',
|
||||
'can_toggle_fullscreen',
|
||||
'charstr',
|
||||
'clipboard_get_text',
|
||||
'clipboard_has_text',
|
||||
|
|
@ -200,6 +213,7 @@ __all__ = [
|
|||
'do_once',
|
||||
'EmptyAppMode',
|
||||
'env',
|
||||
'Env',
|
||||
'Existable',
|
||||
'existing',
|
||||
'fade_screen',
|
||||
|
|
@ -214,11 +228,12 @@ __all__ = [
|
|||
'get_replays_dir',
|
||||
'get_string_height',
|
||||
'get_string_width',
|
||||
'get_v1_cloud_log_file_path',
|
||||
'get_type_name',
|
||||
'getclass',
|
||||
'getsimplesound',
|
||||
'handle_leftover_v1_cloud_log_file',
|
||||
'has_gamma_control',
|
||||
'has_user_run_commands',
|
||||
'have_chars',
|
||||
'have_permission',
|
||||
'in_logic_thread',
|
||||
|
|
@ -231,7 +246,6 @@ __all__ = [
|
|||
'is_point_in_box',
|
||||
'is_running_on_fire_tv',
|
||||
'is_xcode_build',
|
||||
'json_prep',
|
||||
'Keyboard',
|
||||
'LanguageSubsystem',
|
||||
'lock_all_input',
|
||||
|
|
@ -277,9 +291,16 @@ __all__ = [
|
|||
'set_thread_name',
|
||||
'set_ui_input_device',
|
||||
'show_progress_bar',
|
||||
'shutdown_suppress_begin',
|
||||
'shutdown_suppress_end',
|
||||
'shutdown_suppress_count',
|
||||
'SimpleSound',
|
||||
'SpecialChar',
|
||||
'storagename',
|
||||
'StringEditAdapter',
|
||||
'StringEditSubsystem',
|
||||
'supports_max_fps',
|
||||
'supports_vsync',
|
||||
'TeamNotFoundError',
|
||||
'timestring',
|
||||
'UIScale',
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/_accountv2.py
vendored
10
dist/ba_data/python/babase/_accountv2.py
vendored
|
|
@ -64,7 +64,7 @@ class AccountV2Subsystem:
|
|||
|
||||
def set_primary_credentials(self, credentials: str | None) -> None:
|
||||
"""Set credentials for the primary app account."""
|
||||
raise RuntimeError('This should be overridden.')
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
def have_primary_credentials(self) -> bool:
|
||||
"""Are credentials currently set for the primary app account?
|
||||
|
|
@ -73,7 +73,7 @@ class AccountV2Subsystem:
|
|||
only that they exist. If/when credentials are validated, the 'primary'
|
||||
account handle will be set.
|
||||
"""
|
||||
raise RuntimeError('This should be overridden.')
|
||||
raise NotImplementedError('This should be overridden.')
|
||||
|
||||
@property
|
||||
def primary(self) -> AccountV2Handle | None:
|
||||
|
|
@ -128,7 +128,7 @@ class AccountV2Subsystem:
|
|||
# Ok; no workspace to worry about; carry on.
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_babase.app.on_initial_sign_in_completed()
|
||||
_babase.app.on_initial_sign_in_complete()
|
||||
|
||||
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
||||
"""Should be called when logins for the active account change."""
|
||||
|
|
@ -163,7 +163,7 @@ class AccountV2Subsystem:
|
|||
"""
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_babase.app.on_initial_sign_in_completed()
|
||||
_babase.app.on_initial_sign_in_complete()
|
||||
|
||||
@staticmethod
|
||||
def _hashstr(val: str) -> str:
|
||||
|
|
@ -409,7 +409,7 @@ class AccountV2Subsystem:
|
|||
def _on_set_active_workspace_completed(self) -> None:
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_babase.app.on_initial_sign_in_completed()
|
||||
_babase.app.on_initial_sign_in_complete()
|
||||
|
||||
|
||||
class AccountV2Handle:
|
||||
|
|
|
|||
1127
dist/ba_data/python/babase/_app.py
vendored
1127
dist/ba_data/python/babase/_app.py
vendored
File diff suppressed because it is too large
Load diff
10
dist/ba_data/python/babase/_appcomponent.py
vendored
10
dist/ba_data/python/babase/_appcomponent.py
vendored
|
|
@ -50,7 +50,8 @@ class AppComponentSubsystem:
|
|||
# Currently limiting this to logic-thread use; can revisit if
|
||||
# needed (would need to guard access to our implementations
|
||||
# dict).
|
||||
assert _babase.in_logic_thread()
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('this must be called from the logic thread.')
|
||||
|
||||
if not issubclass(implementation, baseclass):
|
||||
raise TypeError(
|
||||
|
|
@ -73,7 +74,8 @@ class AppComponentSubsystem:
|
|||
If no custom implementation has been set, the provided
|
||||
base-class is returned.
|
||||
"""
|
||||
assert _babase.in_logic_thread()
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('this must be called from the logic thread.')
|
||||
|
||||
del baseclass # Unused.
|
||||
return cast(T, None)
|
||||
|
|
@ -87,7 +89,9 @@ class AppComponentSubsystem:
|
|||
loop. Note that any further setclass calls before the callback
|
||||
runs will not result in additional callbacks.
|
||||
"""
|
||||
assert _babase.in_logic_thread()
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('this must be called from the logic thread.')
|
||||
|
||||
self._change_callbacks.setdefault(baseclass, []).append(callback)
|
||||
|
||||
def _run_change_callbacks(self) -> None:
|
||||
|
|
|
|||
38
dist/ba_data/python/babase/_appconfig.py
vendored
38
dist/ba_data/python/babase/_appconfig.py
vendored
|
|
@ -3,6 +3,7 @@
|
|||
"""Provides the AppConfig class."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import _babase
|
||||
|
|
@ -109,7 +110,7 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||
|
||||
# NOTE: it is assumed that this only gets called once and the
|
||||
# config object will not change from here on out
|
||||
config_file_path = _babase.app.config_file_path
|
||||
config_file_path = _babase.app.env.config_file_path
|
||||
config_contents = ''
|
||||
try:
|
||||
if os.path.exists(config_file_path):
|
||||
|
|
@ -120,33 +121,24 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
|
||||
except Exception as exc:
|
||||
print(
|
||||
(
|
||||
'error reading config file at time '
|
||||
+ str(_babase.apptime())
|
||||
+ ': \''
|
||||
+ config_file_path
|
||||
+ '\':\n'
|
||||
),
|
||||
exc,
|
||||
except Exception:
|
||||
logging.exception(
|
||||
"Error reading config file at time %.3f: '%s'.",
|
||||
_babase.apptime(),
|
||||
config_file_path,
|
||||
)
|
||||
|
||||
# Whenever this happens lets back up the broken one just in case it
|
||||
# gets overwritten accidentally.
|
||||
print(
|
||||
(
|
||||
'backing up current config file to \''
|
||||
+ config_file_path
|
||||
+ ".broken\'"
|
||||
)
|
||||
logging.info(
|
||||
"Backing up current config file to '%s.broken'", config_file_path
|
||||
)
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.copyfile(config_file_path, config_file_path + '.broken')
|
||||
except Exception as exc2:
|
||||
print('EXC copying broken config:', exc2)
|
||||
except Exception:
|
||||
logging.exception('Error copying broken config.')
|
||||
config = AppConfig()
|
||||
|
||||
# Now attempt to read one of our 'prev' backup copies.
|
||||
|
|
@ -159,9 +151,9 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||
else:
|
||||
config = AppConfig()
|
||||
config_file_healthy = True
|
||||
print('successfully read backup config.')
|
||||
except Exception as exc2:
|
||||
print('EXC reading prev backup config:', exc2)
|
||||
logging.info('Successfully read backup config.')
|
||||
except Exception:
|
||||
logging.exception('Error reading prev backup config.')
|
||||
return config, config_file_healthy
|
||||
|
||||
|
||||
|
|
@ -176,7 +168,7 @@ def commit_app_config(force: bool = False) -> None:
|
|||
assert plus is not None
|
||||
|
||||
if not _babase.app.config_file_healthy and not force:
|
||||
print(
|
||||
logging.warning(
|
||||
'Current config file is broken; '
|
||||
'skipping write to avoid losing settings.'
|
||||
)
|
||||
|
|
|
|||
30
dist/ba_data/python/babase/_appmode.py
vendored
30
dist/ba_data/python/babase/_appmode.py
vendored
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bacommon.app import AppExperience
|
||||
from babase._appintent import AppIntent
|
||||
|
||||
|
||||
|
|
@ -17,16 +18,33 @@ class AppMode:
|
|||
"""
|
||||
|
||||
@classmethod
|
||||
def supports_intent(cls, intent: AppIntent) -> bool:
|
||||
"""Return whether our mode can handle the provided intent."""
|
||||
del intent
|
||||
def get_app_experience(cls) -> AppExperience:
|
||||
"""Return the overall experience provided by this mode."""
|
||||
raise NotImplementedError('AppMode subclasses must override this.')
|
||||
|
||||
# Say no to everything by default. Let's make mode explicitly
|
||||
# lay out everything they *do* support.
|
||||
return False
|
||||
@classmethod
|
||||
def can_handle_intent(cls, intent: AppIntent) -> bool:
|
||||
"""Return whether this mode can handle the provided intent.
|
||||
|
||||
For this to return True, the AppMode must claim to support the
|
||||
provided intent (via its _supports_intent() method) AND the
|
||||
AppExperience associated with the AppMode must be supported by
|
||||
the current app and runtime environment.
|
||||
"""
|
||||
return cls._supports_intent(intent)
|
||||
|
||||
@classmethod
|
||||
def _supports_intent(cls, intent: AppIntent) -> bool:
|
||||
"""Return whether our mode can handle the provided intent.
|
||||
|
||||
AppModes should override this to define what they can handle.
|
||||
Note that AppExperience does not have to be considered here; that
|
||||
is handled automatically by the can_handle_intent() call."""
|
||||
raise NotImplementedError('AppMode subclasses must override this.')
|
||||
|
||||
def handle_intent(self, intent: AppIntent) -> None:
|
||||
"""Handle an intent."""
|
||||
raise NotImplementedError('AppMode subclasses must override this.')
|
||||
|
||||
def on_activate(self) -> None:
|
||||
"""Called when the mode is being activated."""
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/_appmodeselector.py
vendored
10
dist/ba_data/python/babase/_appmodeselector.py
vendored
|
|
@ -1,6 +1,6 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides AppMode functionality."""
|
||||
"""Contains AppModeSelector base class."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -18,15 +18,15 @@ class AppModeSelector:
|
|||
The app calls an instance of this class when passed an AppIntent to
|
||||
determine which AppMode to use to handle the intent. Plugins or
|
||||
spinoff projects can modify high level app behavior by replacing or
|
||||
modifying this.
|
||||
modifying the app's mode-selector.
|
||||
"""
|
||||
|
||||
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]:
|
||||
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode] | None:
|
||||
"""Given an AppIntent, return the AppMode that should handle it.
|
||||
|
||||
If None is returned, the AppIntent will be ignored.
|
||||
|
||||
This is called in a background thread, so avoid any calls
|
||||
This may be called in a background thread, so avoid any calls
|
||||
limited to logic thread use/etc.
|
||||
"""
|
||||
raise RuntimeError('app_mode_for_intent() should be overridden.')
|
||||
raise NotImplementedError('app_mode_for_intent() should be overridden.')
|
||||
|
|
|
|||
7
dist/ba_data/python/babase/_appsubsystem.py
vendored
7
dist/ba_data/python/babase/_appsubsystem.py
vendored
|
|
@ -18,8 +18,8 @@ class AppSubsystem:
|
|||
|
||||
An app 'subsystem' is a bit of a vague term, as pieces of the app
|
||||
can technically be any class and are not required to use this, but
|
||||
building one out of this base class provides some conveniences such
|
||||
as predefined callbacks during app state changes.
|
||||
building one out of this base class provides conveniences such as
|
||||
predefined callbacks during app state changes.
|
||||
|
||||
Subsystems must be registered with the app before it completes its
|
||||
transition to the 'running' state.
|
||||
|
|
@ -48,5 +48,8 @@ class AppSubsystem:
|
|||
def on_app_shutdown(self) -> None:
|
||||
"""Called when the app is shutting down."""
|
||||
|
||||
def on_app_shutdown_complete(self) -> None:
|
||||
"""Called when the app is done shutting down."""
|
||||
|
||||
def do_apply_app_config(self) -> None:
|
||||
"""Called when the app config should be applied."""
|
||||
|
|
|
|||
23
dist/ba_data/python/babase/_apputils.py
vendored
23
dist/ba_data/python/babase/_apputils.py
vendored
|
|
@ -48,7 +48,7 @@ def is_browser_likely_available() -> bool:
|
|||
# assume no browser.
|
||||
# FIXME: Might not be the case anymore; should make this definable
|
||||
# at the platform level.
|
||||
if app.vr_mode or (platform == 'android' and not hastouchscreen):
|
||||
if app.env.vr or (platform == 'android' and not hastouchscreen):
|
||||
return False
|
||||
|
||||
# Anywhere else assume we've got one.
|
||||
|
|
@ -103,8 +103,8 @@ def handle_v1_cloud_log() -> None:
|
|||
|
||||
info = {
|
||||
'log': _babase.get_v1_cloud_log(),
|
||||
'version': app.version,
|
||||
'build': app.build_number,
|
||||
'version': app.env.version,
|
||||
'build': app.env.build_number,
|
||||
'userAgentString': classic.legacy_user_agent_string,
|
||||
'session': sessionname,
|
||||
'activity': activityname,
|
||||
|
|
@ -222,8 +222,7 @@ def garbage_collect() -> None:
|
|||
def print_corrupt_file_error() -> None:
|
||||
"""Print an error if a corrupt file is found."""
|
||||
|
||||
# FIXME - filter this out for builds without bauiv1.
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.apptimer(
|
||||
2.0,
|
||||
lambda: _babase.screenmessage(
|
||||
|
|
@ -279,7 +278,8 @@ def dump_app_state(
|
|||
# the dump in that case.
|
||||
try:
|
||||
mdpath = os.path.join(
|
||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_md'
|
||||
os.path.dirname(_babase.app.env.config_file_path),
|
||||
'_appstate_dump_md',
|
||||
)
|
||||
with open(mdpath, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write(
|
||||
|
|
@ -297,7 +297,7 @@ def dump_app_state(
|
|||
return
|
||||
|
||||
tbpath = os.path.join(
|
||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_tb'
|
||||
os.path.dirname(_babase.app.env.config_file_path), '_appstate_dump_tb'
|
||||
)
|
||||
|
||||
tbfile = open(tbpath, 'w', encoding='utf-8')
|
||||
|
|
@ -329,7 +329,8 @@ def log_dumped_app_state() -> None:
|
|||
try:
|
||||
out = ''
|
||||
mdpath = os.path.join(
|
||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_md'
|
||||
os.path.dirname(_babase.app.env.config_file_path),
|
||||
'_appstate_dump_md',
|
||||
)
|
||||
if os.path.exists(mdpath):
|
||||
# We may be hanging on to open file descriptors for use by
|
||||
|
|
@ -354,7 +355,7 @@ def log_dumped_app_state() -> None:
|
|||
f'Time: {metadata.app_time:.2f}'
|
||||
)
|
||||
tbpath = os.path.join(
|
||||
os.path.dirname(_babase.app.config_file_path),
|
||||
os.path.dirname(_babase.app.env.config_file_path),
|
||||
'_appstate_dump_tb',
|
||||
)
|
||||
if os.path.exists(tbpath):
|
||||
|
|
@ -378,6 +379,10 @@ class AppHealthMonitor(AppSubsystem):
|
|||
self._response = False
|
||||
self._first_check = True
|
||||
|
||||
def on_app_loading(self) -> None:
|
||||
# If any traceback dumps happened last run, log and clear them.
|
||||
log_dumped_app_state()
|
||||
|
||||
def _app_monitor_thread_main(self) -> None:
|
||||
try:
|
||||
self._monitor_app()
|
||||
|
|
|
|||
12
dist/ba_data/python/babase/_emptyappmode.py
vendored
12
dist/ba_data/python/babase/_emptyappmode.py
vendored
|
|
@ -5,6 +5,8 @@ from __future__ import annotations
|
|||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bacommon.app import AppExperience
|
||||
|
||||
import _babase
|
||||
from babase._appmode import AppMode
|
||||
from babase._appintent import AppIntentExec, AppIntentDefault
|
||||
|
|
@ -17,7 +19,11 @@ class EmptyAppMode(AppMode):
|
|||
"""An empty app mode that can be used as a fallback/etc."""
|
||||
|
||||
@classmethod
|
||||
def supports_intent(cls, intent: AppIntent) -> bool:
|
||||
def get_app_experience(cls) -> AppExperience:
|
||||
return AppExperience.EMPTY
|
||||
|
||||
@classmethod
|
||||
def _supports_intent(cls, intent: AppIntent) -> bool:
|
||||
# We support default and exec intents currently.
|
||||
return isinstance(intent, AppIntentExec | AppIntentDefault)
|
||||
|
||||
|
|
@ -30,8 +36,8 @@ class EmptyAppMode(AppMode):
|
|||
|
||||
def on_activate(self) -> None:
|
||||
# Let the native layer do its thing.
|
||||
_babase.empty_app_mode_activate()
|
||||
_babase.on_empty_app_mode_activate()
|
||||
|
||||
def on_deactivate(self) -> None:
|
||||
# Let the native layer do its thing.
|
||||
_babase.empty_app_mode_deactivate()
|
||||
_babase.on_empty_app_mode_deactivate()
|
||||
|
|
|
|||
25
dist/ba_data/python/babase/_env.py
vendored
25
dist/ba_data/python/babase/_env.py
vendored
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||
import sys
|
||||
import signal
|
||||
import logging
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.log import LogLevel
|
||||
|
|
@ -103,6 +104,12 @@ def on_main_thread_start_app() -> None:
|
|||
signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling.
|
||||
_babase.setup_sigint()
|
||||
|
||||
# Turn on deprecation warnings. By default these are off for release
|
||||
# builds except for in __main__. However this is a key way to
|
||||
# communicate api changes to modders and most modders are running
|
||||
# release builds so its good to have this on everywhere.
|
||||
warnings.simplefilter('default', DeprecationWarning)
|
||||
|
||||
# Turn off fancy-pants cyclic garbage-collection. We run it only at
|
||||
# explicit times to avoid random hitches and keep things more
|
||||
# deterministic. Non-reference-looped objects will still get cleaned
|
||||
|
|
@ -149,14 +156,15 @@ def on_main_thread_start_app() -> None:
|
|||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
def on_app_launching() -> None:
|
||||
"""Called when the app reaches the launching state."""
|
||||
def on_app_state_initing() -> None:
|
||||
"""Called when the app reaches the initing state."""
|
||||
import _babase
|
||||
import baenv
|
||||
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
# Let the user know if the app Python dir is a 'user' one.
|
||||
# Let the user know if the app Python dir is a 'user' one. This is a
|
||||
# risky thing to be doing so don't let them forget they're doing it.
|
||||
envconfig = baenv.get_config()
|
||||
if envconfig.is_user_app_python_dir:
|
||||
_babase.screenmessage(
|
||||
|
|
@ -192,12 +200,13 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None:
|
|||
# cache. This will feed the engine any logs that happened between
|
||||
# baenv.configure() and now.
|
||||
|
||||
# FIXME: while this works for now, the downside is that these
|
||||
# FIXME: while this setup works for now, the downside is that these
|
||||
# callbacks fire in a bg thread so certain things like android
|
||||
# logging will be delayed compared to code that uses native logging
|
||||
# calls directly. Perhaps we should add some sort of 'immediate'
|
||||
# callback option to better handle such cases (similar to the
|
||||
# immediate echofile stderr print that already occurs).
|
||||
# logging will be delayed relative to code that uses native logging
|
||||
# calls directly. Ideally we should add some sort of 'immediate'
|
||||
# callback option to better handle such cases (analogous to the
|
||||
# immediate echofile stderr print that LogHandler already
|
||||
# supports).
|
||||
log_handler.add_callback(_on_log, feed_existing_logs=True)
|
||||
|
||||
|
||||
|
|
|
|||
59
dist/ba_data/python/babase/_general.py
vendored
59
dist/ba_data/python/babase/_general.py
vendored
|
|
@ -6,12 +6,13 @@ from __future__ import annotations
|
|||
import types
|
||||
import weakref
|
||||
import random
|
||||
import logging
|
||||
import inspect
|
||||
from typing import TYPE_CHECKING, TypeVar, Protocol, NewType
|
||||
|
||||
from efro.terminal import Clr
|
||||
|
||||
import _babase
|
||||
from babase._error import print_error, print_exception
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
|
@ -19,7 +20,8 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
# Declare distinct types for different time measurements we use so the
|
||||
# type-checker can help prevent us from mixing and matching accidentally.
|
||||
# type-checker can help prevent us from mixing and matching accidentally,
|
||||
# even if the *actual* types being used are the same.
|
||||
|
||||
# Our monotonic time measurement that starts at 0 when the app launches
|
||||
# and pauses while the app is suspended.
|
||||
|
|
@ -85,39 +87,6 @@ def getclass(name: str, subclassof: type[T]) -> type[T]:
|
|||
return cls
|
||||
|
||||
|
||||
def json_prep(data: Any) -> Any:
|
||||
"""Return a json-friendly version of the provided data.
|
||||
|
||||
This converts any tuples to lists and any bytes to strings
|
||||
(interpreted as utf-8, ignoring errors). Logs errors (just once)
|
||||
if any data is modified/discarded/unsupported.
|
||||
"""
|
||||
|
||||
if isinstance(data, dict):
|
||||
return dict(
|
||||
(json_prep(key), json_prep(value))
|
||||
for key, value in list(data.items())
|
||||
)
|
||||
if isinstance(data, list):
|
||||
return [json_prep(element) for element in data]
|
||||
if isinstance(data, tuple):
|
||||
print_error('json_prep encountered tuple', once=True)
|
||||
return [json_prep(element) for element in data]
|
||||
if isinstance(data, bytes):
|
||||
try:
|
||||
return data.decode(errors='ignore')
|
||||
except Exception:
|
||||
from babase import _error
|
||||
|
||||
print_error('json_prep encountered utf-8 decode error', once=True)
|
||||
return data.decode(errors='ignore')
|
||||
if not isinstance(data, (str, float, bool, type(None), int)):
|
||||
print_error(
|
||||
'got unsupported type in json_prep:' + str(type(data)), once=True
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def utf8_all(data: Any) -> Any:
|
||||
"""Convert any unicode data in provided sequence(s) to utf8 bytes."""
|
||||
if isinstance(data, dict):
|
||||
|
|
@ -136,7 +105,7 @@ def utf8_all(data: Any) -> Any:
|
|||
|
||||
def get_type_name(cls: type) -> str:
|
||||
"""Return a full type name including module for a class."""
|
||||
return cls.__module__ + '.' + cls.__name__
|
||||
return f'{cls.__module__}.{cls.__name__}'
|
||||
|
||||
|
||||
class _WeakCall:
|
||||
|
|
@ -195,18 +164,12 @@ class _WeakCall:
|
|||
else:
|
||||
app = _babase.app
|
||||
if not self._did_invalid_call_warning:
|
||||
print(
|
||||
(
|
||||
'Warning: callable passed to babase.WeakCall() is not'
|
||||
' weak-referencable ('
|
||||
+ str(args[0])
|
||||
+ '); use babase.Call() instead to avoid this '
|
||||
'warning. Stack-trace:'
|
||||
)
|
||||
logging.warning(
|
||||
'Warning: callable passed to babase.WeakCall() is not'
|
||||
' weak-referencable (%s); use babase.Call() instead'
|
||||
' to avoid this warning.',
|
||||
stack_info=True,
|
||||
)
|
||||
import traceback
|
||||
|
||||
traceback.print_stack()
|
||||
self._did_invalid_call_warning = True
|
||||
self._call = args[0]
|
||||
self._args = args[1:]
|
||||
|
|
@ -320,7 +283,7 @@ def verify_object_death(obj: object) -> None:
|
|||
try:
|
||||
ref = weakref.ref(obj)
|
||||
except Exception:
|
||||
print_exception('Unable to create weak-ref in verify_object_death')
|
||||
logging.exception('Unable to create weak-ref in verify_object_death')
|
||||
return
|
||||
|
||||
# Use a slight range for our checks so they don't all land at once
|
||||
|
|
|
|||
57
dist/ba_data/python/babase/_hooks.py
vendored
57
dist/ba_data/python/babase/_hooks.py
vendored
|
|
@ -20,12 +20,7 @@ from typing import TYPE_CHECKING
|
|||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
def on_app_bootstrapping_complete() -> None:
|
||||
"""Called by C++ layer when bootstrapping finishes."""
|
||||
_babase.app.on_app_bootstrapping_complete()
|
||||
from babase._stringedit import StringEditAdapter
|
||||
|
||||
|
||||
def reset_to_main_menu() -> None:
|
||||
|
|
@ -69,14 +64,6 @@ def open_url_with_webbrowser_module(url: str) -> None:
|
|||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
|
||||
|
||||
def connecting_to_party_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
_babase.screenmessage(
|
||||
Lstr(resource='internal.connectingToPartyText'), color=(1, 1, 1)
|
||||
)
|
||||
|
||||
|
||||
def rejecting_invite_already_in_party_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
|
|
@ -97,7 +84,7 @@ def connection_failed_message() -> None:
|
|||
def temporarily_unavailable_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.unavailableTemporarilyText'),
|
||||
|
|
@ -108,7 +95,7 @@ def temporarily_unavailable_message() -> None:
|
|||
def in_progress_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.inProgressText'),
|
||||
|
|
@ -119,7 +106,7 @@ def in_progress_message() -> None:
|
|||
def error_message() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
|
||||
|
|
@ -127,7 +114,7 @@ def error_message() -> None:
|
|||
def purchase_not_valid_error() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
Lstr(
|
||||
|
|
@ -141,7 +128,7 @@ def purchase_not_valid_error() -> None:
|
|||
def purchase_already_in_progress_error() -> None:
|
||||
from babase._language import Lstr
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
_babase.screenmessage(
|
||||
Lstr(resource='store.purchaseAlreadyInProgressText'),
|
||||
|
|
@ -172,14 +159,6 @@ def orientation_reset_message() -> None:
|
|||
)
|
||||
|
||||
|
||||
def on_app_pause() -> None:
|
||||
_babase.app.pause()
|
||||
|
||||
|
||||
def on_app_resume() -> None:
|
||||
_babase.app.resume()
|
||||
|
||||
|
||||
def show_post_purchase_message() -> None:
|
||||
assert _babase.app.classic is not None
|
||||
_babase.app.classic.accounts.show_post_purchase_message()
|
||||
|
|
@ -208,7 +187,7 @@ def award_dual_wielding_achievement() -> None:
|
|||
|
||||
|
||||
def play_gong_sound() -> None:
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('gong').play()
|
||||
|
||||
|
||||
|
|
@ -272,15 +251,11 @@ def toggle_fullscreen() -> None:
|
|||
cfg.apply_and_commit()
|
||||
|
||||
|
||||
def read_config() -> None:
|
||||
_babase.app.read_config()
|
||||
|
||||
|
||||
def ui_remote_press() -> None:
|
||||
"""Handle a press by a remote device that is only usable for nav."""
|
||||
from babase._language import Lstr
|
||||
|
||||
if _babase.app.headless_mode:
|
||||
if _babase.app.env.headless:
|
||||
return
|
||||
|
||||
# Can be called without a context; need a context for getsound.
|
||||
|
|
@ -300,10 +275,6 @@ def do_quit() -> None:
|
|||
_babase.quit()
|
||||
|
||||
|
||||
def shutdown() -> None:
|
||||
_babase.app.on_app_shutdown()
|
||||
|
||||
|
||||
def hash_strings(inputs: list[str]) -> str:
|
||||
"""Hash provided strings into a short output string."""
|
||||
import hashlib
|
||||
|
|
@ -375,11 +346,11 @@ def show_client_too_old_error() -> None:
|
|||
# a newer build.
|
||||
if (
|
||||
_babase.app.config.get('SuppressClientTooOldErrorForBuild')
|
||||
== _babase.app.build_number
|
||||
== _babase.app.env.build_number
|
||||
):
|
||||
return
|
||||
|
||||
if not _babase.app.headless_mode:
|
||||
if _babase.app.env.gui:
|
||||
_babase.getsimplesound('error').play()
|
||||
|
||||
_babase.screenmessage(
|
||||
|
|
@ -393,3 +364,11 @@ def show_client_too_old_error() -> None:
|
|||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
def string_edit_adapter_can_be_replaced(adapter: StringEditAdapter) -> bool:
|
||||
"""Return whether a StringEditAdapter can be replaced."""
|
||||
from babase._stringedit import StringEditAdapter
|
||||
|
||||
assert isinstance(adapter, StringEditAdapter)
|
||||
return adapter.can_be_replaced()
|
||||
|
|
|
|||
9
dist/ba_data/python/babase/_language.py
vendored
9
dist/ba_data/python/babase/_language.py
vendored
|
|
@ -68,7 +68,10 @@ class LanguageSubsystem(AppSubsystem):
|
|||
try:
|
||||
names = os.listdir(
|
||||
os.path.join(
|
||||
_babase.app.data_directory, 'ba_data', 'data', 'languages'
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
)
|
||||
)
|
||||
names = [n.replace('.json', '').capitalize() for n in names]
|
||||
|
|
@ -121,7 +124,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||
|
||||
with open(
|
||||
os.path.join(
|
||||
_babase.app.data_directory,
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
|
|
@ -139,7 +142,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||
lmodvalues = None
|
||||
else:
|
||||
lmodfile = os.path.join(
|
||||
_babase.app.data_directory,
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_login.py
vendored
1
dist/ba_data/python/babase/_login.py
vendored
|
|
@ -10,6 +10,7 @@ from dataclasses import dataclass
|
|||
from typing import TYPE_CHECKING, final
|
||||
|
||||
from bacommon.login import LoginType
|
||||
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
|
|||
21
dist/ba_data/python/babase/_meta.py
vendored
21
dist/ba_data/python/babase/_meta.py
vendored
|
|
@ -18,11 +18,6 @@ import _babase
|
|||
if TYPE_CHECKING:
|
||||
from typing import Callable
|
||||
|
||||
# The meta api version of this build of the game.
|
||||
# Only packages and modules requiring this exact api version
|
||||
# will be considered when scanning directories.
|
||||
# See: https://ballistica.net/wiki/Meta-Tag-System
|
||||
CURRENT_API_VERSION = 8
|
||||
|
||||
# Meta export lines can use these names to represent these classes.
|
||||
# This is purely a convenience; it is possible to use full class paths
|
||||
|
|
@ -76,14 +71,15 @@ class MetadataSubsystem:
|
|||
"""
|
||||
assert self._scan_complete_cb is None
|
||||
assert self._scan is None
|
||||
env = _babase.app.env
|
||||
|
||||
self._scan_complete_cb = scan_complete_cb
|
||||
self._scan = DirectoryScan(
|
||||
[
|
||||
path
|
||||
for path in [
|
||||
_babase.app.python_directory_app,
|
||||
_babase.app.python_directory_user,
|
||||
env.python_directory_app,
|
||||
env.python_directory_user,
|
||||
]
|
||||
if path is not None
|
||||
]
|
||||
|
|
@ -212,7 +208,7 @@ class MetadataSubsystem:
|
|||
'${NUM}',
|
||||
str(len(results.incorrect_api_modules) - 1),
|
||||
),
|
||||
('${API}', str(CURRENT_API_VERSION)),
|
||||
('${API}', str(_babase.app.env.api_version)),
|
||||
],
|
||||
)
|
||||
else:
|
||||
|
|
@ -220,7 +216,7 @@ class MetadataSubsystem:
|
|||
resource='scanScriptsSingleModuleNeedsUpdatesText',
|
||||
subs=[
|
||||
('${PATH}', results.incorrect_api_modules[0]),
|
||||
('${API}', str(CURRENT_API_VERSION)),
|
||||
('${API}', str(_babase.app.env.api_version)),
|
||||
],
|
||||
)
|
||||
_babase.screenmessage(msg, color=(1, 0, 0))
|
||||
|
|
@ -344,13 +340,16 @@ class DirectoryScan:
|
|||
|
||||
# If we find a module requiring a different api version, warn
|
||||
# and ignore.
|
||||
if required_api is not None and required_api != CURRENT_API_VERSION:
|
||||
if (
|
||||
required_api is not None
|
||||
and required_api != _babase.app.env.api_version
|
||||
):
|
||||
logging.warning(
|
||||
'metascan: %s requires api %s but we are running'
|
||||
' %s. Ignoring module.',
|
||||
subpath,
|
||||
required_api,
|
||||
CURRENT_API_VERSION,
|
||||
_babase.app.env.api_version,
|
||||
)
|
||||
self.results.incorrect_api_modules.append(
|
||||
self._module_name_for_subpath(subpath)
|
||||
|
|
|
|||
38
dist/ba_data/python/babase/_plugin.py
vendored
38
dist/ba_data/python/babase/_plugin.py
vendored
|
|
@ -81,7 +81,7 @@ class PluginSubsystem(AppSubsystem):
|
|||
config_changed = True
|
||||
found_new = True
|
||||
|
||||
# If we're *not* auto-enabling, just let the user know if we
|
||||
# If we're *not* auto-enabling, simply let the user know if we
|
||||
# found new ones.
|
||||
if found_new and not auto_enable_new_plugins:
|
||||
_babase.screenmessage(
|
||||
|
|
@ -131,10 +131,10 @@ class PluginSubsystem(AppSubsystem):
|
|||
disappeared_plugs.add(class_path)
|
||||
continue
|
||||
|
||||
# If plugins disappeared, let the user know gently and remove them
|
||||
# from the config so we'll again let the user know if they later
|
||||
# reappear. This makes it much smoother to switch between users
|
||||
# or workspaces.
|
||||
# If plugins disappeared, let the user know gently and remove
|
||||
# them from the config so we'll again let the user know if they
|
||||
# later reappear. This makes it much smoother to switch between
|
||||
# users or workspaces.
|
||||
if disappeared_plugs:
|
||||
_babase.getsimplesound('shieldDown').play()
|
||||
_babase.screenmessage(
|
||||
|
|
@ -197,6 +197,17 @@ class PluginSubsystem(AppSubsystem):
|
|||
|
||||
_error.print_exception('Error in plugin on_app_shutdown()')
|
||||
|
||||
def on_app_shutdown_complete(self) -> None:
|
||||
for plugin in self.active_plugins:
|
||||
try:
|
||||
plugin.on_app_shutdown_complete()
|
||||
except Exception:
|
||||
from babase import _error
|
||||
|
||||
_error.print_exception(
|
||||
'Error in plugin on_app_shutdown_complete()'
|
||||
)
|
||||
|
||||
def load_plugins(self) -> None:
|
||||
"""(internal)"""
|
||||
|
||||
|
|
@ -217,7 +228,7 @@ class PluginSpec:
|
|||
key. Remember to commit the app-config after making any changes.
|
||||
|
||||
The 'attempted_load' attr will be True if the engine has attempted
|
||||
to load the plugin. If 'attempted_load' is True for a plugin-spec
|
||||
to load the plugin. If 'attempted_load' is True for a PluginSpec
|
||||
but the 'plugin' attr is None, it means there was an error loading
|
||||
the plugin. If a plugin's api-version does not match the running
|
||||
app, if a new plugin is detected with auto-enable-plugins disabled,
|
||||
|
|
@ -249,7 +260,7 @@ class PluginSpec:
|
|||
plugstate['enabled'] = val
|
||||
|
||||
def attempt_load_if_enabled(self) -> Plugin | None:
|
||||
"""Possibly load the plugin and report errors."""
|
||||
"""Possibly load the plugin and log any errors."""
|
||||
from babase._general import getclass
|
||||
from babase._language import Lstr
|
||||
|
||||
|
|
@ -308,8 +319,8 @@ class Plugin:
|
|||
Category: **App Classes**
|
||||
|
||||
Plugins are discoverable by the meta-tag system
|
||||
and the user can select which ones they want to activate.
|
||||
Active plugins are then called at specific times as the
|
||||
and the user can select which ones they want to enable.
|
||||
Enabled plugins are then called at specific times as the
|
||||
app is running in order to modify its behavior in some way.
|
||||
"""
|
||||
|
||||
|
|
@ -317,13 +328,16 @@ class Plugin:
|
|||
"""Called when the app reaches the running state."""
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
"""Called after pausing game activity."""
|
||||
"""Called when the app is switching to a paused state."""
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
"""Called after the game continues."""
|
||||
"""Called when the app is resuming from a paused state."""
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
"""Called before closing the application."""
|
||||
"""Called when the app is beginning the shutdown process."""
|
||||
|
||||
def on_app_shutdown_complete(self) -> None:
|
||||
"""Called when the app has completed the shutdown process."""
|
||||
|
||||
def has_settings_ui(self) -> bool:
|
||||
"""Called to ask if we have settings UI we can show."""
|
||||
|
|
|
|||
146
dist/ba_data/python/babase/_stringedit.py
vendored
Normal file
146
dist/ba_data/python/babase/_stringedit.py
vendored
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality for editing text strings.
|
||||
|
||||
This abstracts native edit dialogs as well as ones implemented via our
|
||||
own ui toolkits.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import logging
|
||||
import weakref
|
||||
from typing import TYPE_CHECKING, final
|
||||
|
||||
from efro.util import empty_weakref
|
||||
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class StringEditSubsystem:
|
||||
"""Full string-edit state for the app."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.active_adapter = empty_weakref(StringEditAdapter)
|
||||
|
||||
|
||||
class StringEditAdapter:
|
||||
"""Represents a string editing operation on some object.
|
||||
|
||||
Editable objects such as text widgets or in-app-consoles can
|
||||
subclass this to make their contents editable on all platforms.
|
||||
|
||||
There can only be one string-edit at a time for the app. New
|
||||
StringEdits will attempt to register themselves as the globally
|
||||
active one in their constructor, but this may not succeed. When
|
||||
creating a StringEditAdapter, always check its 'is_valid()' value after
|
||||
creating it. If this is False, it was not able to set itself as
|
||||
the global active one and should be discarded.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: str,
|
||||
initial_text: str,
|
||||
max_length: int | None,
|
||||
screen_space_center: tuple[float, float] | None,
|
||||
) -> None:
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('This must be called from the logic thread.')
|
||||
|
||||
self.create_time = time.monotonic()
|
||||
|
||||
# Note: these attr names are hard-coded in C++ code so don't
|
||||
# change them willy-nilly.
|
||||
self.description = description
|
||||
self.initial_text = initial_text
|
||||
self.max_length = max_length
|
||||
self.screen_space_center = screen_space_center
|
||||
|
||||
# Attempt to register ourself as the active edit.
|
||||
subsys = _babase.app.stringedit
|
||||
current_edit = subsys.active_adapter()
|
||||
if current_edit is None or current_edit.can_be_replaced():
|
||||
subsys.active_adapter = weakref.ref(self)
|
||||
|
||||
@final
|
||||
def can_be_replaced(self) -> bool:
|
||||
"""Return whether this adapter can be replaced by a new one.
|
||||
|
||||
This is mainly a safeguard to allow adapters whose drivers have
|
||||
gone away without calling apply or cancel to time out and be
|
||||
replaced with new ones.
|
||||
"""
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('This must be called from the logic thread.')
|
||||
|
||||
# Allow ourself to be replaced after a bit.
|
||||
if time.monotonic() - self.create_time > 5.0:
|
||||
if _babase.do_once():
|
||||
logging.warning(
|
||||
'StringEditAdapter can_be_replaced() check for %s'
|
||||
' yielding True due to timeout; ideally this should'
|
||||
' not be possible as the StringEditAdapter driver'
|
||||
' should be blocking anything else from kicking off'
|
||||
' new edits.',
|
||||
self,
|
||||
)
|
||||
return True
|
||||
|
||||
# We also are always considered replaceable if we're not the
|
||||
# active global adapter.
|
||||
current_edit = _babase.app.stringedit.active_adapter()
|
||||
if current_edit is not self:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@final
|
||||
def apply(self, new_text: str) -> None:
|
||||
"""Should be called by the owner when editing is complete.
|
||||
|
||||
Note that in some cases this call may be a no-op (such as if
|
||||
this StringEditAdapter is no longer the globally active one).
|
||||
"""
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('This must be called from the logic thread.')
|
||||
|
||||
# Make sure whoever is feeding this adapter is honoring max-length.
|
||||
if self.max_length is not None and len(new_text) > self.max_length:
|
||||
logging.warning(
|
||||
'apply() on %s was passed a string of length %d,'
|
||||
' but adapter max_length is %d; this should not happen'
|
||||
' (will truncate).',
|
||||
self,
|
||||
len(new_text),
|
||||
self.max_length,
|
||||
stack_info=True,
|
||||
)
|
||||
new_text = new_text[: self.max_length]
|
||||
|
||||
self._do_apply(new_text)
|
||||
|
||||
@final
|
||||
def cancel(self) -> None:
|
||||
"""Should be called by the owner when editing is cancelled."""
|
||||
if not _babase.in_logic_thread():
|
||||
raise RuntimeError('This must be called from the logic thread.')
|
||||
self._do_cancel()
|
||||
|
||||
def _do_apply(self, new_text: str) -> None:
|
||||
"""Should be overridden by subclasses to handle apply.
|
||||
|
||||
Will always be called in the logic thread.
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must override this.')
|
||||
|
||||
def _do_cancel(self) -> None:
|
||||
"""Should be overridden by subclasses to handle cancel.
|
||||
|
||||
Will always be called in the logic thread.
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must override this.')
|
||||
32
dist/ba_data/python/babase/_ui.py
vendored
Normal file
32
dist/ba_data/python/babase/_ui.py
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UI related bits of babase."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from babase._stringedit import StringEditAdapter
|
||||
import _babase
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
|
||||
class DevConsoleStringEditAdapter(StringEditAdapter):
|
||||
"""Allows editing dev-console text."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
description = 'Dev Console Input'
|
||||
initial_text = _babase.get_dev_console_input_text()
|
||||
max_length = None
|
||||
screen_space_center = None
|
||||
super().__init__(
|
||||
description, initial_text, max_length, screen_space_center
|
||||
)
|
||||
|
||||
def _do_apply(self, new_text: str) -> None:
|
||||
_babase.set_dev_console_input_text(new_text)
|
||||
_babase.dev_console_input_adapter_finish()
|
||||
|
||||
def _do_cancel(self) -> None:
|
||||
_babase.dev_console_input_adapter_finish()
|
||||
32
dist/ba_data/python/babase/modutils.py
vendored
32
dist/ba_data/python/babase/modutils.py
vendored
|
|
@ -18,7 +18,7 @@ def get_human_readable_user_scripts_path() -> str:
|
|||
This is NOT a valid filesystem path; may be something like "(SD Card)".
|
||||
"""
|
||||
app = _babase.app
|
||||
path: str | None = app.python_directory_user
|
||||
path: str | None = app.env.python_directory_user
|
||||
if path is None:
|
||||
return '<Not Available>'
|
||||
|
||||
|
|
@ -66,19 +66,20 @@ def _request_storage_permission() -> bool:
|
|||
def show_user_scripts() -> None:
|
||||
"""Open or nicely print the location of the user-scripts directory."""
|
||||
app = _babase.app
|
||||
env = app.env
|
||||
|
||||
# First off, if we need permission for this, ask for it.
|
||||
if _request_storage_permission():
|
||||
return
|
||||
|
||||
# If we're running in a nonstandard environment its possible this is unset.
|
||||
if app.python_directory_user is None:
|
||||
if env.python_directory_user is None:
|
||||
_babase.screenmessage('<unset>')
|
||||
return
|
||||
|
||||
# Secondly, if the dir doesn't exist, attempt to make it.
|
||||
if not os.path.exists(app.python_directory_user):
|
||||
os.makedirs(app.python_directory_user)
|
||||
if not os.path.exists(env.python_directory_user):
|
||||
os.makedirs(env.python_directory_user)
|
||||
|
||||
# On android, attempt to write a file in their user-scripts dir telling
|
||||
# them about modding. This also has the side-effect of allowing us to
|
||||
|
|
@ -88,7 +89,7 @@ def show_user_scripts() -> None:
|
|||
# they can see it.
|
||||
if app.classic is not None and app.classic.platform == 'android':
|
||||
try:
|
||||
usd: str | None = app.python_directory_user
|
||||
usd: str | None = env.python_directory_user
|
||||
if usd is not None and os.path.isdir(usd):
|
||||
file_name = usd + '/about_this_folder.txt'
|
||||
with open(file_name, 'w', encoding='utf-8') as outfile:
|
||||
|
|
@ -105,7 +106,7 @@ def show_user_scripts() -> None:
|
|||
|
||||
# On a few platforms we try to open the dir in the UI.
|
||||
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
|
||||
_babase.open_dir_externally(app.python_directory_user)
|
||||
_babase.open_dir_externally(env.python_directory_user)
|
||||
|
||||
# Otherwise we just print a pretty version of it.
|
||||
else:
|
||||
|
|
@ -120,18 +121,19 @@ def create_user_system_scripts() -> None:
|
|||
import shutil
|
||||
|
||||
app = _babase.app
|
||||
env = app.env
|
||||
|
||||
# First off, if we need permission for this, ask for it.
|
||||
if _request_storage_permission():
|
||||
return
|
||||
|
||||
# Its possible these are unset in non-standard environments.
|
||||
if app.python_directory_user is None:
|
||||
if env.python_directory_user is None:
|
||||
raise RuntimeError('user python dir unset')
|
||||
if app.python_directory_app is None:
|
||||
if env.python_directory_app is None:
|
||||
raise RuntimeError('app python dir unset')
|
||||
|
||||
path = app.python_directory_user + '/sys/' + app.version
|
||||
path = f'{env.python_directory_user}/sys/{env.version}'
|
||||
pathtmp = path + '_tmp'
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
|
|
@ -147,8 +149,8 @@ def create_user_system_scripts() -> None:
|
|||
# /Knowledge-Nuggets#python-cache-files-gotcha
|
||||
return ('__pycache__',)
|
||||
|
||||
print(f'COPYING "{app.python_directory_app}" -> "{pathtmp}".')
|
||||
shutil.copytree(app.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||
print(f'COPYING "{env.python_directory_app}" -> "{pathtmp}".')
|
||||
shutil.copytree(env.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||
|
||||
print(f'MOVING "{pathtmp}" -> "{path}".')
|
||||
shutil.move(pathtmp, path)
|
||||
|
|
@ -168,12 +170,12 @@ def delete_user_system_scripts() -> None:
|
|||
"""Clean out the scripts created by create_user_system_scripts()."""
|
||||
import shutil
|
||||
|
||||
app = _babase.app
|
||||
env = _babase.app.env
|
||||
|
||||
if app.python_directory_user is None:
|
||||
if env.python_directory_user is None:
|
||||
raise RuntimeError('user python dir unset')
|
||||
|
||||
path = app.python_directory_user + '/sys/' + app.version
|
||||
path = f'{env.python_directory_user}/sys/{env.version}'
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
print(
|
||||
|
|
@ -185,6 +187,6 @@ def delete_user_system_scripts() -> None:
|
|||
print(f"User system scripts not found at '{path}'.")
|
||||
|
||||
# If the sys path is empty, kill it.
|
||||
dpath = app.python_directory_user + '/sys'
|
||||
dpath = env.python_directory_user + '/sys'
|
||||
if os.path.isdir(dpath) and not os.listdir(dpath):
|
||||
os.rmdir(dpath)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue