diff --git a/CHANGELOG.md b/CHANGELOG.md index f73dd01d..60ef4789 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ### 1.5.27 (20218) - Language functionality has been consolidated into a LanguageSubsystem object at ba.app.lang - ba.get_valid_languages() is now an attr: ba.app.lang.available_languages +- Achievement functionality has been consolidated into an AchievementSubsystem object at ba.app.ach +- Plugin functionality has been consolidated into a PluginSubsystem obj at ba.app.plugins ### 1.5.26 (20217) - Simplified licensing header on python scripts. diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 642f4643..79c80daf 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -20,7 +20,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, set_analytics_screen, charstr, textwidget, time, timer, open_url, widget) from ba._activity import Activity -from ba._plugin import PotentialPlugin, Plugin +from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem from ba._actor import Actor from ba._player import PlayerInfo, Player, EmptyPlayer, StandLocation from ba._nodeactor import NodeActor diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index e5b84b90..636de193 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -175,6 +175,7 @@ class App: from ba._language import LanguageSubsystem from ba._ui import UISubsystem from ba._achievement import AchievementSubsystem + from ba._plugin import PluginSubsystem # Config. self.config_file_healthy = False @@ -198,10 +199,6 @@ class App: self.iircade_mode: bool = self._env['iircade_mode'] assert isinstance(self.headless_mode, bool) - # Plugins. - self.potential_plugins: List[ba.PotentialPlugin] = [] - self.active_plugins: Dict[str, ba.Plugin] = {} - # Misc. self.metascan: Optional[_meta.ScanResults] = None self.tips: List[str] = [] @@ -241,6 +238,7 @@ class App: self.last_ad_purpose = 'invalid' self.attempted_first_ad = False + self.plugins = PluginSubsystem() self.music = MusicSubsystem() self.lang = LanguageSubsystem() self.ach = AchievementSubsystem() @@ -291,7 +289,6 @@ class App: (internal)""" # pylint: disable=too-many-locals # pylint: disable=cyclic-import - # pylint: disable=too-many-statements from ba import _apputils from ba import _appconfig from ba import _achievement @@ -401,49 +398,13 @@ class App: _ba.pushcall(do_auto_sign_in) - # Load up our plugins and go ahead and call their on_app_launch calls. - self.load_plugins() - for plugin in self.active_plugins.values(): - try: - plugin.on_app_launch() - except Exception: - from ba import _error - _error.print_exception('Error in plugin on_app_launch()') + self.plugins.on_app_launch() self.ran_on_app_launch = True # from ba._dependency import test_depset # test_depset() - def load_plugins(self) -> None: - """(internal)""" - from ba._general import getclass - from ba._plugin import Plugin - - # Note: the plugins we load is purely based on what's enabled - # in the app config. Our meta-scan gives us a list of available - # plugins, but that is only used to give the user a list of plugins - # that they can enable. (we wouldn't want to look at meta-scan here - # anyway because it may not be done yet at this point in the launch) - plugstates: Dict[str, Dict] = self.config.get('Plugins', {}) - assert isinstance(plugstates, dict) - plugkeys: List[str] = sorted(key for key, val in plugstates.items() - if val.get('enabled', False)) - for plugkey in plugkeys: - try: - cls = getclass(plugkey, Plugin) - except Exception as exc: - _ba.log(f"Error loading plugin class '{plugkey}': {exc}", - to_server=False) - continue - try: - plugin = cls() - assert plugkey not in self.active_plugins - self.active_plugins[plugkey] = plugin - except Exception: - from ba import _error - _error.print_exception(f'Error loading plugin: {plugkey}') - def read_config(self) -> None: """(internal)""" from ba import _appconfig @@ -558,7 +519,6 @@ class App: """Run when the app resumes from a suspended state.""" self.music.on_app_resume() - self.fg_state += 1 # Mark our cached tourneys as invalid so anyone using them knows @@ -657,13 +617,13 @@ class App: # If we're still not signed in and have pending codes, # inform the user that they need to sign in to use them. - if _ba.app.pending_promo_codes: + if self.pending_promo_codes: _ba.screenmessage( Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) - _ba.app.pending_promo_codes.append(code) + self.pending_promo_codes.append(code) _ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL) return _ba.screenmessage(Lstr(resource='submittingPromoCodeText'), @@ -681,7 +641,7 @@ class App: def _test_https(self) -> None: """Testing https support. - (would be nice to get this working on our custom python builds; need + (would be nice to get this working on our custom Python builds; need to wrangle certificates somehow). """ import urllib.request diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py index 7b314d68..28b88aff 100644 --- a/assets/src/ba_data/python/ba/_meta.py +++ b/assets/src/ba_data/python/ba/_meta.py @@ -68,6 +68,7 @@ def handle_scan_results(results: ScanResults) -> None: _ba.log(textwrap.indent(results.errors, 'Error (meta-scan): ')) # Handle plugins. + plugs = _ba.app.plugins config_changed = False found_new = False plugstates: Dict[str, Dict] = _ba.app.config.setdefault('Plugins', {}) @@ -75,7 +76,7 @@ def handle_scan_results(results: ScanResults) -> None: # Create a potential-plugin for each class we found in the scan. for class_path in results.plugins: - _ba.app.potential_plugins.append( + plugs.potential_plugins.append( PotentialPlugin(display_name=Lstr(value=class_path), class_path=class_path, available=True)) @@ -90,12 +91,12 @@ def handle_scan_results(results: ScanResults) -> None: enabled = plugstate.get('enabled', False) assert isinstance(enabled, bool) if enabled and class_path not in results.plugins: - _ba.app.potential_plugins.append( + plugs.potential_plugins.append( PotentialPlugin(display_name=Lstr(value=class_path), class_path=class_path, available=False)) - _ba.app.potential_plugins.sort(key=lambda p: p.class_path) + plugs.potential_plugins.sort(key=lambda p: p.class_path) if found_new: _ba.screenmessage(Lstr(resource='pluginsDetectedText'), diff --git a/assets/src/ba_data/python/ba/_plugin.py b/assets/src/ba_data/python/ba/_plugin.py index 84658219..cf4251cf 100644 --- a/assets/src/ba_data/python/ba/_plugin.py +++ b/assets/src/ba_data/python/ba/_plugin.py @@ -6,11 +6,65 @@ from __future__ import annotations from typing import TYPE_CHECKING from dataclasses import dataclass +import _ba if TYPE_CHECKING: + from typing import List, Dict import ba +class PluginSubsystem: + """Subsystem for plugin handling in the app. + + Category: App Classes + + Access the single shared instance of this class at 'ba.app.plugins'. + """ + + def __init__(self) -> None: + self.potential_plugins: List[ba.PotentialPlugin] = [] + self.active_plugins: Dict[str, ba.Plugin] = {} + + def on_app_launch(self) -> None: + """Should be called at app launch time.""" + # Load up our plugins and go ahead and call their on_app_launch calls. + self.load_plugins() + for plugin in self.active_plugins.values(): + try: + plugin.on_app_launch() + except Exception: + from ba import _error + _error.print_exception('Error in plugin on_app_launch()') + + def load_plugins(self) -> None: + """(internal)""" + from ba._general import getclass + + # Note: the plugins we load is purely based on what's enabled + # in the app config. Our meta-scan gives us a list of available + # plugins, but that is only used to give the user a list of plugins + # that they can enable. (we wouldn't want to look at meta-scan here + # anyway because it may not be done yet at this point in the launch) + plugstates: Dict[str, Dict] = _ba.app.config.get('Plugins', {}) + assert isinstance(plugstates, dict) + plugkeys: List[str] = sorted(key for key, val in plugstates.items() + if val.get('enabled', False)) + for plugkey in plugkeys: + try: + cls = getclass(plugkey, Plugin) + except Exception as exc: + _ba.log(f"Error loading plugin class '{plugkey}': {exc}", + to_server=False) + continue + try: + plugin = cls() + assert plugkey not in self.active_plugins + self.active_plugins[plugkey] = plugin + except Exception: + from ba import _error + _error.print_exception(f'Error loading plugin: {plugkey}') + + @dataclass class PotentialPlugin: """Represents a ba.Plugin which can potentially be loaded. diff --git a/assets/src/ba_data/python/bastd/ui/settings/plugins.py b/assets/src/ba_data/python/bastd/ui/settings/plugins.py index 457eded9..c9d347eb 100644 --- a/assets/src/ba_data/python/bastd/ui/settings/plugins.py +++ b/assets/src/ba_data/python/bastd/ui/settings/plugins.py @@ -97,11 +97,11 @@ class PluginSettingsWindow(ba.Window): ba.screenmessage('Still scanning plugins; please try again.', color=(1, 0, 0)) ba.playsound(ba.getsound('error')) - pluglist = ba.app.potential_plugins + pluglist = ba.app.plugins.potential_plugins plugstates: Dict[str, Dict] = ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) for i, availplug in enumerate(pluglist): - active = availplug.class_path in ba.app.active_plugins + active = availplug.class_path in ba.app.plugins.active_plugins plugstate = plugstates.setdefault(availplug.class_path, {}) checked = plugstate.get('enabled', False) diff --git a/docs/ba_module.md b/docs/ba_module.md index 3fb8309b..f504997b 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -158,6 +158,7 @@
Called when the app is being launched.
+ + +<top level class> +
+Subsystem for plugin handling in the app.
+ +Category: App Classes
+ +Access the single shared instance of this class at 'ba.app.plugins'. +
+ +ba.PluginSubsystem()
+ +on_app_launch(self) -> None
+ +Should be called at app launch time.
+