mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-03 14:03:18 +08:00
various plugin subsystem improvements
This commit is contained in:
parent
3a6a35749a
commit
8ef31acc81
44
.efrocachemap
generated
44
.efrocachemap
generated
@ -430,12 +430,12 @@
|
|||||||
"build/assets/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/54/a2/91da0eec3c0820602d779ef24d10",
|
"build/assets/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/54/a2/91da0eec3c0820602d779ef24d10",
|
||||||
"build/assets/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/d6/9080783d5c9dcc0af737f02b6f1e",
|
"build/assets/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/d6/9080783d5c9dcc0af737f02b6f1e",
|
||||||
"build/assets/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/22/b4/4a33bf81142ba2befad14eb5746e",
|
"build/assets/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/22/b4/4a33bf81142ba2befad14eb5746e",
|
||||||
"build/assets/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/0f/a5/14ed3ec7d80bf0a8751c0d764f64",
|
"build/assets/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/d1/6e/8899211693c20d3b00fc198f58c6",
|
||||||
"build/assets/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/0e/39/7cfa5f3fb8cef5f4a64f21cda880",
|
"build/assets/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/0e/39/7cfa5f3fb8cef5f4a64f21cda880",
|
||||||
"build/assets/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/cb/49/1739273c68c82cebca0aee16d6c9",
|
"build/assets/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/cb/49/1739273c68c82cebca0aee16d6c9",
|
||||||
"build/assets/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/51/89/e01389f8153497b56fbf0fa069c2",
|
"build/assets/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/51/89/e01389f8153497b56fbf0fa069c2",
|
||||||
"build/assets/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/22/a4/452043a401252ca66b703ce5d4aa",
|
"build/assets/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/22/a4/452043a401252ca66b703ce5d4aa",
|
||||||
"build/assets/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/42/75/f30546475d6b7aa6599a9251973a",
|
"build/assets/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/23/6f/8547ba09722b7c7f5b8333986984",
|
||||||
"build/assets/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/a6/5d/78f912e9a89f98de004405167a6a",
|
"build/assets/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/a6/5d/78f912e9a89f98de004405167a6a",
|
||||||
"build/assets/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/88/ee/0cda537bab9ac827def5e236fe1a",
|
"build/assets/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/88/ee/0cda537bab9ac827def5e236fe1a",
|
||||||
"build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/00/ba/cf1b8bb9f7914f64647d4665b0a8",
|
"build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/00/ba/cf1b8bb9f7914f64647d4665b0a8",
|
||||||
@ -4068,26 +4068,26 @@
|
|||||||
"build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1",
|
"build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1",
|
||||||
"build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae",
|
"build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae",
|
||||||
"build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599",
|
"build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599",
|
||||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/d1/5f/aa35974d118ac4a45de5d105edd5",
|
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/63/44/27519c2a85ba72d2a4e338ed4650",
|
||||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/85/7b/fa206153e23235bd97eb3abfb45a",
|
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/3a/33/047dbfc05746818a9dfbc7668314",
|
||||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c8/97/a251315351949cbe40914f3f02a3",
|
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0b/64/e2da499eeba056e64bfce5dab20a",
|
||||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/7f/07/1ba1983b2ad7b1061f0c7624ee87",
|
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/76/74/619b268bc7f8609a6f477fc9e6f6",
|
||||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/14/3b/984737685ea813a3a8d587fd9083",
|
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/8d/36/fbde0a1a43724a97e6478e27b3ab",
|
||||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/03/ee/59971d68ce248f89175441bc232c",
|
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b0/82/021314e49a35124a1d52f8f81c09",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/69/0a/1a067c77c85e2a57fccdd553fe44",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/75/3b/0024994a18df5b10272161903d5f",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d8/af/beea487d4cd9b68e323af8190bdb",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f6/ca/a4db280a528841bccb7aaf3baa63",
|
||||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/7f/aa/f77f3330bea584a9710f13485ac0",
|
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/cb/64/d43244656d4310fe0145bef54627",
|
||||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/fa/1e/f0c6f0a6edf00bd4975c30b44957",
|
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/44/22/737933ac2d7caaba5cd5fd13d88f",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/eb/ca/e8aa9629a9da31c838e8d7dd0f9d",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6c/77/715843f46cc2c3c1bdc0e21b7b55",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d0/13/08ed924a1032c25b5404f505398b",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a4/48/33d74871b0c39e60064d0ce267cf",
|
||||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b6/10/dd86496c0e5eac41803e43c552d3",
|
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/a9/61/26f4c31c7b537e2fb557c4d87c0c",
|
||||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/89/d2/087efa3672aa053f6456cf5d263f",
|
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/19/a2/888b33bcdc7f9d87dd2d1ce4c2fd",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/aa/a5/7a4703567c6f754e2248c115fb09",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/67/e0/be7233baecdbe2acc133a7e4b596",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/71/c1/e52972ac5b26273ad841da7d664b",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ec/9f/ba7a1e3d7c34d7a3392f31a9fabb",
|
||||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/b3/2f/1af856ae52297f7952329efc7617",
|
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/22/d7/105197eb744fb4ee35bc99af236e",
|
||||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/84/5c/b58affdbdae8e334f7dcff14c684",
|
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/1c/8c/727910c0c52e8f30c6262e57bd61",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/4b/e7/5964489b7d22e27a413c9edb9eb3",
|
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/ab/79/6a873cc823621d63f79c2a0256ed",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/1e/c9/770a9e209a63de714aff7da7e8ee",
|
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/1f/5a/7a8a2804f0b49076e01081075a49",
|
||||||
"build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f",
|
"build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f",
|
||||||
"build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4e/48/123b806cbe6ddb3d9a8368bbb4f8",
|
"build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4e/48/123b806cbe6ddb3d9a8368bbb4f8",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f",
|
"build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f",
|
||||||
|
|||||||
2
.idea/dictionaries/ericf.xml
generated
2
.idea/dictionaries/ericf.xml
generated
@ -2133,6 +2133,8 @@
|
|||||||
<w>plugkeys</w>
|
<w>plugkeys</w>
|
||||||
<w>pluglist</w>
|
<w>pluglist</w>
|
||||||
<w>plugnames</w>
|
<w>plugnames</w>
|
||||||
|
<w>plugspec</w>
|
||||||
|
<w>plugspecs</w>
|
||||||
<w>plugstate</w>
|
<w>plugstate</w>
|
||||||
<w>plugstates</w>
|
<w>plugstates</w>
|
||||||
<w>plusbutton</w>
|
<w>plusbutton</w>
|
||||||
|
|||||||
23
CHANGELOG.md
23
CHANGELOG.md
@ -1,4 +1,4 @@
|
|||||||
### 1.7.21 (build 21148, api 8, 2023-06-26)
|
### 1.7.21 (build 21150, api 8, 2023-06-26)
|
||||||
|
|
||||||
- Fixed an issue where server builds would not always include collision meshes.
|
- Fixed an issue where server builds would not always include collision meshes.
|
||||||
- Upgraded Python to 3.11.4 on Android builds.
|
- Upgraded Python to 3.11.4 on Android builds.
|
||||||
@ -9,6 +9,27 @@
|
|||||||
code creation will fail. This should keep the images reasonably readable and
|
code creation will fail. This should keep the images reasonably readable and
|
||||||
avoids a crash that could occur when more data was provided than could
|
avoids a crash that could occur when more data was provided than could
|
||||||
physically fit in the qr code.
|
physically fit in the qr code.
|
||||||
|
- `PotentialPlugin` has been renamed to `PluginSpec` and the list of them
|
||||||
|
renamed from `babase.app.plugins.potential_plugins` to
|
||||||
|
`babase.app.plugins.plugin_specs`.
|
||||||
|
- Added a simpler warning message when plugins are found that need to be updated
|
||||||
|
for the new api version.
|
||||||
|
- Previously, the app would only check api version on plugins when initially
|
||||||
|
registering them. This meant that once a plugin was enabled, the app would
|
||||||
|
always try to load it even if api version stopped matching. This has been
|
||||||
|
corrected; now if the api version doesn't match it will never be loaded.
|
||||||
|
- Fixed an error where plugins nested more than one level such as
|
||||||
|
`mypackage.myplugin.MyPlugin` would fail to load.
|
||||||
|
- Removed the `display_name` attr from the `PluginSpec` class, as it was simply
|
||||||
|
set to `class_path`. It seems that referring to plugins simply by their
|
||||||
|
class-paths is a reasonable system for now.
|
||||||
|
- Added `enabled`, `loadable`, `attempted_load` and `plugin` attrs to the
|
||||||
|
`PluginSpec` class. This should make it easier to interact with the overall
|
||||||
|
app plugin situation without having to do hacky raw config wrangling.
|
||||||
|
- Plugins should now show up more sensibly in the plugins UI in some cases. For
|
||||||
|
example, a plugin which was previously loading but no longer is after an
|
||||||
|
api-version change will still show up in the list as red instead of not
|
||||||
|
showing up at all.
|
||||||
|
|
||||||
### 1.7.20 (build 21140, api 8, 2023-06-22)
|
### 1.7.20 (build 21140, api 8, 2023-06-22)
|
||||||
|
|
||||||
|
|||||||
2
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
2
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
@ -1246,6 +1246,8 @@
|
|||||||
<w>plen</w>
|
<w>plen</w>
|
||||||
<w>pluginsettings</w>
|
<w>pluginsettings</w>
|
||||||
<w>plugnames</w>
|
<w>plugnames</w>
|
||||||
|
<w>plugspec</w>
|
||||||
|
<w>plugspecs</w>
|
||||||
<w>plusnet</w>
|
<w>plusnet</w>
|
||||||
<w>pname</w>
|
<w>pname</w>
|
||||||
<w>pnamel</w>
|
<w>pnamel</w>
|
||||||
|
|||||||
@ -148,7 +148,7 @@ from babase._mgen.enums import (
|
|||||||
from babase._math import normalized_color, is_point_in_box, vec3validate
|
from babase._math import normalized_color, is_point_in_box, vec3validate
|
||||||
from babase._meta import MetadataSubsystem
|
from babase._meta import MetadataSubsystem
|
||||||
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
from babase._net import get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||||
from babase._plugin import PotentialPlugin, Plugin, PluginSubsystem
|
from babase._plugin import PluginSpec, Plugin, PluginSubsystem
|
||||||
from babase._text import timestring
|
from babase._text import timestring
|
||||||
|
|
||||||
_babase.app = app = App()
|
_babase.app = app = App()
|
||||||
@ -252,7 +252,7 @@ __all__ = [
|
|||||||
'PlayerNotFoundError',
|
'PlayerNotFoundError',
|
||||||
'Plugin',
|
'Plugin',
|
||||||
'PluginSubsystem',
|
'PluginSubsystem',
|
||||||
'PotentialPlugin',
|
'PluginSpec',
|
||||||
'print_error',
|
'print_error',
|
||||||
'print_exception',
|
'print_exception',
|
||||||
'print_load_info',
|
'print_load_info',
|
||||||
|
|||||||
@ -40,8 +40,8 @@ class ScanResults:
|
|||||||
"""Final results from a meta-scan."""
|
"""Final results from a meta-scan."""
|
||||||
|
|
||||||
exports: dict[str, list[str]] = field(default_factory=dict)
|
exports: dict[str, list[str]] = field(default_factory=dict)
|
||||||
errors: list[str] = field(default_factory=list)
|
incorrect_api_modules: list[str] = field(default_factory=list)
|
||||||
warnings: list[str] = field(default_factory=list)
|
announce_errors_occurred: bool = False
|
||||||
|
|
||||||
def exports_of_class(self, cls: type) -> list[str]:
|
def exports_of_class(self, cls: type) -> list[str]:
|
||||||
"""Return exports of a given class."""
|
"""Return exports of a given class."""
|
||||||
@ -181,8 +181,9 @@ class MetadataSubsystem:
|
|||||||
self._scan.run()
|
self._scan.run()
|
||||||
results = self._scan.results
|
results = self._scan.results
|
||||||
self._scan = None
|
self._scan = None
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
results = ScanResults(errors=[f'Scan exception: {exc}'])
|
logging.exception('metascan: Error running scan in bg.')
|
||||||
|
results = ScanResults(announce_errors_occurred=True)
|
||||||
|
|
||||||
# Place results and tell the logic thread they're ready.
|
# Place results and tell the logic thread they're ready.
|
||||||
self.scanresults = results
|
self.scanresults = results
|
||||||
@ -197,28 +198,44 @@ class MetadataSubsystem:
|
|||||||
results = self.scanresults
|
results = self.scanresults
|
||||||
assert results is not None
|
assert results is not None
|
||||||
|
|
||||||
# Spit out any warnings/errors that happened.
|
do_play_error_sound = False
|
||||||
# Warnings generally only get printed locally for users' benefit
|
|
||||||
# (things like out-of-date scripts being ignored, etc.)
|
|
||||||
# Errors are more serious and will get included in the regular log.
|
|
||||||
if results.warnings or results.errors:
|
|
||||||
import textwrap
|
|
||||||
|
|
||||||
|
# If we found modules needing to be updated to the newer api version,
|
||||||
|
# mention that specifically.
|
||||||
|
if results.incorrect_api_modules:
|
||||||
|
if len(results.incorrect_api_modules) > 1:
|
||||||
|
msg = Lstr(
|
||||||
|
resource='scanScriptsMultipleModulesNeedUpdatesText',
|
||||||
|
subs=[
|
||||||
|
('${PATH}', results.incorrect_api_modules[0]),
|
||||||
|
(
|
||||||
|
'${NUM}',
|
||||||
|
str(len(results.incorrect_api_modules) - 1),
|
||||||
|
),
|
||||||
|
('${API}', str(CURRENT_API_VERSION)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg = Lstr(
|
||||||
|
resource='scanScriptsSingleModuleNeedsUpdatesText',
|
||||||
|
subs=[
|
||||||
|
('${PATH}', results.incorrect_api_modules[0]),
|
||||||
|
('${API}', str(CURRENT_API_VERSION)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
_babase.screenmessage(msg, color=(1, 0, 0))
|
||||||
|
do_play_error_sound = True
|
||||||
|
|
||||||
|
# Let the user know if there's warning/errors in their log
|
||||||
|
# they may want to look at.
|
||||||
|
if results.announce_errors_occurred:
|
||||||
_babase.screenmessage(
|
_babase.screenmessage(
|
||||||
Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)
|
Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)
|
||||||
)
|
)
|
||||||
_babase.getsimplesound('error').play()
|
do_play_error_sound = True
|
||||||
|
|
||||||
if results.warnings:
|
if do_play_error_sound:
|
||||||
allwarnings = textwrap.indent(
|
_babase.getsimplesound('error').play()
|
||||||
'\n'.join(results.warnings), 'Warning (meta-scan): '
|
|
||||||
)
|
|
||||||
logging.warning(allwarnings)
|
|
||||||
if results.errors:
|
|
||||||
allerrors = textwrap.indent(
|
|
||||||
'\n'.join(results.errors), 'Error (meta-scan): '
|
|
||||||
)
|
|
||||||
logging.error(allerrors)
|
|
||||||
|
|
||||||
# Let the game know we're done.
|
# Let the game know we're done.
|
||||||
assert self._scan_complete_cb is not None
|
assert self._scan_complete_cb is not None
|
||||||
@ -262,11 +279,7 @@ class DirectoryScan:
|
|||||||
try:
|
try:
|
||||||
self._scan_module(moduledir, subpath)
|
self._scan_module(moduledir, subpath)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
logging.exception("metascan: Error scanning '%s'.", subpath)
|
||||||
|
|
||||||
self.results.warnings.append(
|
|
||||||
f"Error scanning '{subpath}': " + traceback.format_exc()
|
|
||||||
)
|
|
||||||
|
|
||||||
# Sort our results
|
# Sort our results
|
||||||
for exportlist in self.results.exports.values():
|
for exportlist in self.results.exports.values():
|
||||||
@ -289,9 +302,10 @@ class DirectoryScan:
|
|||||||
except PermissionError:
|
except PermissionError:
|
||||||
# Expected sometimes.
|
# Expected sometimes.
|
||||||
entries = []
|
entries = []
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
# Unexpected; report this.
|
# Unexpected; report this.
|
||||||
self.results.errors.append(str(exc))
|
logging.exception('metascan: Error in _get_path_module_entries.')
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
entries = []
|
entries = []
|
||||||
|
|
||||||
# Now identify python packages/modules out of what we found.
|
# Now identify python packages/modules out of what we found.
|
||||||
@ -331,9 +345,15 @@ class DirectoryScan:
|
|||||||
# If we find a module requiring a different api version, warn
|
# If we find a module requiring a different api version, warn
|
||||||
# and ignore.
|
# and ignore.
|
||||||
if required_api is not None and required_api != CURRENT_API_VERSION:
|
if required_api is not None and required_api != CURRENT_API_VERSION:
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'{subpath} requires api {required_api} but'
|
'metascan: %s requires api %s but we are running'
|
||||||
f' we are running {CURRENT_API_VERSION}. Ignoring module.'
|
' %s. Ignoring module.',
|
||||||
|
subpath,
|
||||||
|
required_api,
|
||||||
|
CURRENT_API_VERSION,
|
||||||
|
)
|
||||||
|
self.results.incorrect_api_modules.append(
|
||||||
|
self._module_name_for_subpath(subpath)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -349,11 +369,13 @@ class DirectoryScan:
|
|||||||
if submodule[1].name != '__init__.py':
|
if submodule[1].name != '__init__.py':
|
||||||
self._scan_module(submodule[0], submodule[1])
|
self._scan_module(submodule[0], submodule[1])
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
logging.exception('metascan: Error scanning %s.', subpath)
|
||||||
|
|
||||||
self.results.warnings.append(
|
def _module_name_for_subpath(self, subpath: Path) -> str:
|
||||||
f"Error scanning '{subpath}': {traceback.format_exc()}"
|
# (should not be getting these)
|
||||||
)
|
assert '__init__.py' not in str(subpath)
|
||||||
|
|
||||||
|
return '.'.join(subpath.parts).removesuffix('.py')
|
||||||
|
|
||||||
def _process_module_meta_tags(
|
def _process_module_meta_tags(
|
||||||
self, subpath: Path, flines: list[str], meta_lines: dict[int, list[str]]
|
self, subpath: Path, flines: list[str], meta_lines: dict[int, list[str]]
|
||||||
@ -363,10 +385,12 @@ class DirectoryScan:
|
|||||||
# meta_lines is just anything containing '# ba_meta '; make sure
|
# meta_lines is just anything containing '# ba_meta '; make sure
|
||||||
# the ba_meta is in the right place.
|
# the ba_meta is in the right place.
|
||||||
if mline[0] != 'ba_meta':
|
if mline[0] != 'ba_meta':
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'Warning: {subpath}:'
|
'metascan: %s:%d: malformed ba_meta statement.',
|
||||||
f' malformed ba_meta statement on line {lindex + 1}.'
|
subpath,
|
||||||
|
lindex + 1,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
elif (
|
elif (
|
||||||
len(mline) == 4 and mline[1] == 'require' and mline[2] == 'api'
|
len(mline) == 4 and mline[1] == 'require' and mline[2] == 'api'
|
||||||
):
|
):
|
||||||
@ -375,15 +399,15 @@ class DirectoryScan:
|
|||||||
elif len(mline) != 3 or mline[1] != 'export':
|
elif len(mline) != 3 or mline[1] != 'export':
|
||||||
# Currently we only support 'ba_meta export FOO';
|
# Currently we only support 'ba_meta export FOO';
|
||||||
# complain for anything else we see.
|
# complain for anything else we see.
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'Warning: {subpath}'
|
'metascan: %s:%d: unrecognized ba_meta statement.',
|
||||||
f': unrecognized ba_meta statement on line {lindex + 1}.'
|
subpath,
|
||||||
|
lindex + 1,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
else:
|
else:
|
||||||
# Looks like we've got a valid export line!
|
# Looks like we've got a valid export line!
|
||||||
modulename = '.'.join(subpath.parts)
|
modulename = self._module_name_for_subpath(subpath)
|
||||||
if subpath.name.endswith('.py'):
|
|
||||||
modulename = modulename[:-3]
|
|
||||||
exporttypestr = mline[2]
|
exporttypestr = mline[2]
|
||||||
export_class_name = self._get_export_class_name(
|
export_class_name = self._get_export_class_name(
|
||||||
subpath, flines, lindex
|
subpath, flines, lindex
|
||||||
@ -395,11 +419,14 @@ class DirectoryScan:
|
|||||||
# classes we need to migrate people to using base
|
# classes we need to migrate people to using base
|
||||||
# class names for them.
|
# class names for them.
|
||||||
if exporttypestr == 'game':
|
if exporttypestr == 'game':
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'{subpath}:'
|
"metascan: %s:%d: '# ba_meta export"
|
||||||
" '# ba_meta export game' tag should be replaced by"
|
" game' tag should be replaced by '# ba_meta"
|
||||||
f" '# ba_meta export bascenev1.GameActivity'."
|
" export bascenev1.GameActivity'.",
|
||||||
|
subpath,
|
||||||
|
lindex + 1,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
else:
|
else:
|
||||||
# If export type is one of our shortcuts, sub in the
|
# If export type is one of our shortcuts, sub in the
|
||||||
# actual class path. Otherwise assume its a classpath
|
# actual class path. Otherwise assume its a classpath
|
||||||
@ -434,10 +461,13 @@ class DirectoryScan:
|
|||||||
classname = cbits[0]
|
classname = cbits[0]
|
||||||
break # Success!
|
break # Success!
|
||||||
if classname is None:
|
if classname is None:
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'Warning: {subpath}: class definition not found below'
|
'metascan: %s:%d: class definition not found below'
|
||||||
f' "ba_meta export" statement on line {lindexorig + 1}.'
|
" 'ba_meta export' statement.",
|
||||||
|
subpath,
|
||||||
|
lindexorig + 1,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
return classname
|
return classname
|
||||||
|
|
||||||
def _get_api_requirement(
|
def _get_api_requirement(
|
||||||
@ -460,23 +490,26 @@ class DirectoryScan:
|
|||||||
and l[3].isdigit()
|
and l[3].isdigit()
|
||||||
]
|
]
|
||||||
|
|
||||||
# We're successful if we find exactly one properly formatted line.
|
# We're successful if we find exactly one properly formatted
|
||||||
|
# line.
|
||||||
if len(lines) == 1:
|
if len(lines) == 1:
|
||||||
return int(lines[0][3])
|
return int(lines[0][3])
|
||||||
|
|
||||||
# Ok; not successful. lets issue warnings for a few error cases.
|
# Ok; not successful. lets issue warnings for a few error cases.
|
||||||
if len(lines) > 1:
|
if len(lines) > 1:
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'Warning: {subpath}: multiple'
|
"metascan: %s: multiple '# ba_meta require api <NUM>'"
|
||||||
' "# ba_meta require api <NUM>" lines found;'
|
' lines found; ignoring module.',
|
||||||
' ignoring module.'
|
subpath,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
elif not lines and toplevel and meta_lines:
|
elif not lines and toplevel and meta_lines:
|
||||||
# If we're a top-level module containing meta lines but
|
# If we're a top-level module containing meta lines but no
|
||||||
# no valid "require api" line found, complain.
|
# valid "require api" line found, complain.
|
||||||
self.results.warnings.append(
|
logging.warning(
|
||||||
f'Warning: {subpath}:'
|
"metascan: %s: no valid '# ba_meta require api <NUM>"
|
||||||
' no valid "# ba_meta require api <NUM>" line found;'
|
' line found; ignoring module.',
|
||||||
' ignoring module.'
|
subpath,
|
||||||
)
|
)
|
||||||
|
self.results.announce_errors_occurred = True
|
||||||
return None
|
return None
|
||||||
|
|||||||
@ -7,7 +7,6 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
import importlib.util
|
import importlib.util
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import _babase
|
import _babase
|
||||||
from babase._appsubsystem import AppSubsystem
|
from babase._appsubsystem import AppSubsystem
|
||||||
@ -31,14 +30,21 @@ class PluginSubsystem(AppSubsystem):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.potential_plugins: list[babase.PotentialPlugin] = []
|
|
||||||
self.active_plugins: dict[str, babase.Plugin] = {}
|
# Info about plugins that we are aware of. This may include
|
||||||
|
# plugins discovered through meta-scanning as well as plugins
|
||||||
|
# registered in the app-config. This may include plugins that
|
||||||
|
# cannot be loaded for various reasons or that have been
|
||||||
|
# intentionally disabled.
|
||||||
|
self.plugin_specs: dict[str, babase.PluginSpec] = {}
|
||||||
|
|
||||||
|
# The set of live active plugin objects.
|
||||||
|
self.active_plugins: list[babase.Plugin] = []
|
||||||
|
|
||||||
def on_meta_scan_complete(self) -> None:
|
def on_meta_scan_complete(self) -> None:
|
||||||
"""Should be called when meta-scanning is complete."""
|
"""Called when meta-scanning is complete."""
|
||||||
from babase._language import Lstr
|
from babase._language import Lstr
|
||||||
|
|
||||||
plugs = _babase.app.plugins
|
|
||||||
config_changed = False
|
config_changed = False
|
||||||
found_new = False
|
found_new = False
|
||||||
plugstates: dict[str, dict] = _babase.app.config.setdefault(
|
plugstates: dict[str, dict] = _babase.app.config.setdefault(
|
||||||
@ -56,146 +62,75 @@ class PluginSubsystem(AppSubsystem):
|
|||||||
)
|
)
|
||||||
is True
|
is True
|
||||||
)
|
)
|
||||||
# Create a potential-plugin for each class we found in the scan.
|
|
||||||
|
assert not self.plugin_specs
|
||||||
|
assert not self.active_plugins
|
||||||
|
|
||||||
|
# Create a plugin-spec for each plugin class we found in the
|
||||||
|
# meta-scan.
|
||||||
for class_path in results.exports_of_class(Plugin):
|
for class_path in results.exports_of_class(Plugin):
|
||||||
plugs.potential_plugins.append(
|
assert class_path not in self.plugin_specs
|
||||||
PotentialPlugin(
|
plugspec = self.plugin_specs[class_path] = PluginSpec(
|
||||||
display_name=Lstr(value=class_path),
|
class_path=class_path, loadable=True
|
||||||
class_path=class_path,
|
|
||||||
available=True,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Auto-enable new ones if desired.
|
||||||
if auto_enable_new_plugins:
|
if auto_enable_new_plugins:
|
||||||
if class_path not in plugstates:
|
if class_path not in plugstates:
|
||||||
# Go ahead and enable new plugins by default, but we'll
|
plugspec.enabled = True
|
||||||
# inform the user that they need to restart to pick them up.
|
|
||||||
# they can also disable them in settings so they never load.
|
|
||||||
plugstates[class_path] = {'enabled': True}
|
|
||||||
config_changed = True
|
config_changed = True
|
||||||
found_new = True
|
found_new = True
|
||||||
|
|
||||||
plugs.potential_plugins.sort(key=lambda p: p.class_path)
|
# If we're *not* auto-enabling, just let the user know if we
|
||||||
|
# found new ones.
|
||||||
# If we're *not* auto-enabling new plugins, at least let the
|
|
||||||
# user know we found something new.
|
|
||||||
if found_new and not auto_enable_new_plugins:
|
if found_new and not auto_enable_new_plugins:
|
||||||
_babase.screenmessage(
|
_babase.screenmessage(
|
||||||
Lstr(resource='pluginsDetectedText'), color=(0, 1, 0)
|
Lstr(resource='pluginsDetectedText'), color=(0, 1, 0)
|
||||||
)
|
)
|
||||||
_babase.getsimplesound('ding').play()
|
_babase.getsimplesound('ding').play()
|
||||||
|
|
||||||
if config_changed:
|
# Ok, now go through all plugins registered in the app-config
|
||||||
_babase.app.config.commit()
|
# that weren't covered by the meta stuff above, either creating
|
||||||
|
# plugin-specs for them or clearing them out. This covers
|
||||||
def on_app_running(self) -> None:
|
# plugins with api versions not matching ours, plugins without
|
||||||
# Load up our plugins and go ahead and call their on_app_running calls.
|
# ba_meta tags, and plugins that have since disappeared.
|
||||||
self.load_plugins()
|
|
||||||
for plugin in self.active_plugins.values():
|
|
||||||
try:
|
|
||||||
plugin.on_app_running()
|
|
||||||
except Exception:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_running()')
|
|
||||||
|
|
||||||
def on_app_pause(self) -> None:
|
|
||||||
for plugin in self.active_plugins.values():
|
|
||||||
try:
|
|
||||||
plugin.on_app_pause()
|
|
||||||
except Exception:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_pause()')
|
|
||||||
|
|
||||||
def on_app_resume(self) -> None:
|
|
||||||
for plugin in self.active_plugins.values():
|
|
||||||
try:
|
|
||||||
plugin.on_app_resume()
|
|
||||||
except Exception:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_resume()')
|
|
||||||
|
|
||||||
def on_app_shutdown(self) -> None:
|
|
||||||
for plugin in self.active_plugins.values():
|
|
||||||
try:
|
|
||||||
plugin.on_app_shutdown()
|
|
||||||
except Exception:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_error.print_exception('Error in plugin on_app_shutdown()')
|
|
||||||
|
|
||||||
def load_plugins(self) -> None:
|
|
||||||
"""(internal)"""
|
|
||||||
from babase._general import getclass
|
|
||||||
from babase._language import Lstr
|
|
||||||
|
|
||||||
# Note: the plugins we load is purely based on what's enabled
|
|
||||||
# in the app config. Its not our job to look at meta stuff here.
|
|
||||||
plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
|
|
||||||
assert isinstance(plugstates, dict)
|
assert isinstance(plugstates, dict)
|
||||||
plugkeys: list[str] = sorted(
|
wrong_api_prefixes = [f'{m}.' for m in results.incorrect_api_modules]
|
||||||
key for key, val in plugstates.items() if val.get('enabled', False)
|
|
||||||
)
|
|
||||||
disappeared_plugs: set[str] = set()
|
disappeared_plugs: set[str] = set()
|
||||||
for plugkey in plugkeys:
|
|
||||||
# Originally I was just catching ModuleNotFoundError on the
|
for class_path in sorted(plugstates.keys()):
|
||||||
# getclass() call to detect plugins disappearing. However
|
# Already have a spec for it; nothing to be done.
|
||||||
# this breaks if the module *does* exist but itself imports
|
if class_path in self.plugin_specs:
|
||||||
# something that does not exist; in that case we would
|
continue
|
||||||
# incorrectly show that the plugin had disappeared.
|
|
||||||
#
|
# If this plugin corresponds to any modules that we've
|
||||||
# So now we're first explicitly asking Python if it can
|
# identified as having incorrect api versions, we'll take
|
||||||
# locate the module, and if it can then we treat any further
|
# note of its existence but we won't try to load it.
|
||||||
# errors including ModuleNotFound as problems with the
|
if any(
|
||||||
# module's code; not ours.
|
class_path.startswith(prefix) for prefix in wrong_api_prefixes
|
||||||
|
):
|
||||||
|
plugspec = self.plugin_specs[class_path] = PluginSpec(
|
||||||
|
class_path=class_path, loadable=False
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ok, it seems to be a class we have no metadata for. Look
|
||||||
|
# to see if it appears to be an actual class we could
|
||||||
|
# theoretically load. If so, we'll try. If not, we consider
|
||||||
|
# the plugin to have disappeared and inform the user as
|
||||||
|
# such.
|
||||||
try:
|
try:
|
||||||
spec = importlib.util.find_spec(plugkey.split('.')[0])
|
spec = importlib.util.find_spec(
|
||||||
|
'.'.join(class_path.split('.')[:-1])
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
spec = None
|
spec = None
|
||||||
|
|
||||||
if spec is None:
|
if spec is None:
|
||||||
disappeared_plugs.add(plugkey)
|
disappeared_plugs.add(class_path)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Ok; it seems that there's *something* there. Now try to load
|
|
||||||
# it and treat any further errors as the module's fault.
|
|
||||||
try:
|
|
||||||
cls = getclass(plugkey, Plugin)
|
|
||||||
except Exception as exc:
|
|
||||||
_babase.getsimplesound('error').play()
|
|
||||||
_babase.screenmessage(
|
|
||||||
Lstr(
|
|
||||||
resource='pluginClassLoadErrorText',
|
|
||||||
subs=[
|
|
||||||
('${PLUGIN}', plugkey),
|
|
||||||
('${ERROR}', str(exc)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
color=(1, 0, 0),
|
|
||||||
)
|
|
||||||
logging.exception("Error loading plugin class '%s'.", plugkey)
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
plugin = cls()
|
|
||||||
assert plugkey not in self.active_plugins
|
|
||||||
self.active_plugins[plugkey] = plugin
|
|
||||||
except Exception as exc:
|
|
||||||
from babase import _error
|
|
||||||
|
|
||||||
_babase.getsimplesound('error').play()
|
|
||||||
_babase.screenmessage(
|
|
||||||
Lstr(
|
|
||||||
resource='pluginInitErrorText',
|
|
||||||
subs=[
|
|
||||||
('${PLUGIN}', plugkey),
|
|
||||||
('${ERROR}', str(exc)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
color=(1, 0, 0),
|
|
||||||
)
|
|
||||||
_error.print_exception(f"Error initing plugin: '{plugkey}'.")
|
|
||||||
|
|
||||||
# If plugins disappeared, let the user know gently and remove them
|
# 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
|
# from the config so we'll again let the user know if they later
|
||||||
# reappear. This makes it much smoother to switch between users
|
# reappear. This makes it much smoother to switch between users
|
||||||
@ -220,22 +155,151 @@ class PluginSubsystem(AppSubsystem):
|
|||||||
del _babase.app.config['Plugins'][goneplug]
|
del _babase.app.config['Plugins'][goneplug]
|
||||||
_babase.app.config.commit()
|
_babase.app.config.commit()
|
||||||
|
|
||||||
|
if config_changed:
|
||||||
|
_babase.app.config.commit()
|
||||||
|
|
||||||
@dataclass
|
def on_app_running(self) -> None:
|
||||||
class PotentialPlugin:
|
# Load up our plugins and go ahead and call their on_app_running
|
||||||
"""Represents a babase.Plugin which can potentially be loaded.
|
# calls.
|
||||||
|
self.load_plugins()
|
||||||
|
for plugin in self.active_plugins:
|
||||||
|
try:
|
||||||
|
plugin.on_app_running()
|
||||||
|
except Exception:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_error.print_exception('Error in plugin on_app_running()')
|
||||||
|
|
||||||
|
def on_app_pause(self) -> None:
|
||||||
|
for plugin in self.active_plugins:
|
||||||
|
try:
|
||||||
|
plugin.on_app_pause()
|
||||||
|
except Exception:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_error.print_exception('Error in plugin on_app_pause()')
|
||||||
|
|
||||||
|
def on_app_resume(self) -> None:
|
||||||
|
for plugin in self.active_plugins:
|
||||||
|
try:
|
||||||
|
plugin.on_app_resume()
|
||||||
|
except Exception:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_error.print_exception('Error in plugin on_app_resume()')
|
||||||
|
|
||||||
|
def on_app_shutdown(self) -> None:
|
||||||
|
for plugin in self.active_plugins:
|
||||||
|
try:
|
||||||
|
plugin.on_app_shutdown()
|
||||||
|
except Exception:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_error.print_exception('Error in plugin on_app_shutdown()')
|
||||||
|
|
||||||
|
def load_plugins(self) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
|
||||||
|
# Load plugins from any specs that are enabled & able to.
|
||||||
|
for _class_path, plug_spec in sorted(self.plugin_specs.items()):
|
||||||
|
plugin = plug_spec.attempt_load_if_enabled()
|
||||||
|
if plugin is not None:
|
||||||
|
self.active_plugins.append(plugin)
|
||||||
|
|
||||||
|
|
||||||
|
class PluginSpec:
|
||||||
|
"""Represents a plugin the engine knows about.
|
||||||
|
|
||||||
Category: **App Classes**
|
Category: **App Classes**
|
||||||
|
|
||||||
These generally represent plugins which were detected by the
|
The 'enabled' attr represents whether this plugin is set to load.
|
||||||
meta-tag scan. However they may also represent plugins which
|
Getting or setting that attr affects the corresponding app-config
|
||||||
were previously set to be loaded but which were unable to be
|
key. Remember to commit the app-config after making any changes.
|
||||||
for some reason. In that case, 'available' will be set to False.
|
|
||||||
|
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
|
||||||
|
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,
|
||||||
|
or if the user has explicitly disabled a plugin, the engine will not
|
||||||
|
even attempt to load it.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
display_name: babase.Lstr
|
def __init__(self, class_path: str, loadable: bool):
|
||||||
class_path: str
|
self.class_path = class_path
|
||||||
available: bool
|
self.loadable = loadable
|
||||||
|
self.attempted_load = False
|
||||||
|
self.plugin: Plugin | None = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def enabled(self) -> bool:
|
||||||
|
"""Whether the user wants this plugin to load."""
|
||||||
|
plugstates: dict[str, dict] = _babase.app.config.get('Plugins', {})
|
||||||
|
assert isinstance(plugstates, dict)
|
||||||
|
val = plugstates.get(self.class_path, {}).get('enabled', False) is True
|
||||||
|
return val
|
||||||
|
|
||||||
|
@enabled.setter
|
||||||
|
def enabled(self, val: bool) -> None:
|
||||||
|
plugstates: dict[str, dict] = _babase.app.config.setdefault(
|
||||||
|
'Plugins', {}
|
||||||
|
)
|
||||||
|
assert isinstance(plugstates, dict)
|
||||||
|
plugstate = plugstates.setdefault(self.class_path, {})
|
||||||
|
plugstate['enabled'] = val
|
||||||
|
|
||||||
|
def attempt_load_if_enabled(self) -> Plugin | None:
|
||||||
|
"""Possibly load the plugin and report errors."""
|
||||||
|
from babase._general import getclass
|
||||||
|
from babase._language import Lstr
|
||||||
|
|
||||||
|
assert not self.attempted_load
|
||||||
|
assert self.plugin is None
|
||||||
|
|
||||||
|
if not self.enabled:
|
||||||
|
return None
|
||||||
|
self.attempted_load = True
|
||||||
|
if not self.loadable:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
cls = getclass(self.class_path, Plugin)
|
||||||
|
except Exception as exc:
|
||||||
|
_babase.getsimplesound('error').play()
|
||||||
|
_babase.screenmessage(
|
||||||
|
Lstr(
|
||||||
|
resource='pluginClassLoadErrorText',
|
||||||
|
subs=[
|
||||||
|
('${PLUGIN}', self.class_path),
|
||||||
|
('${ERROR}', str(exc)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
color=(1, 0, 0),
|
||||||
|
)
|
||||||
|
logging.exception(
|
||||||
|
"Error loading plugin class '%s'.", self.class_path
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
self.plugin = cls()
|
||||||
|
return self.plugin
|
||||||
|
except Exception as exc:
|
||||||
|
from babase import _error
|
||||||
|
|
||||||
|
_babase.getsimplesound('error').play()
|
||||||
|
_babase.screenmessage(
|
||||||
|
Lstr(
|
||||||
|
resource='pluginInitErrorText',
|
||||||
|
subs=[
|
||||||
|
('${PLUGIN}', self.class_path),
|
||||||
|
('${ERROR}', str(exc)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
color=(1, 0, 0),
|
||||||
|
)
|
||||||
|
logging.exception(
|
||||||
|
"Error initing plugin class: '%s'.", self.class_path
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin:
|
||||||
|
|||||||
@ -28,7 +28,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# Build number and version of the ballistica binary we expect to be
|
# Build number and version of the ballistica binary we expect to be
|
||||||
# using.
|
# using.
|
||||||
TARGET_BALLISTICA_BUILD = 21148
|
TARGET_BALLISTICA_BUILD = 21150
|
||||||
TARGET_BALLISTICA_VERSION = '1.7.21'
|
TARGET_BALLISTICA_VERSION = '1.7.21'
|
||||||
|
|
||||||
_g_env_config: EnvConfig | None = None
|
_g_env_config: EnvConfig | None = None
|
||||||
|
|||||||
@ -66,7 +66,7 @@ from babase import (
|
|||||||
NotFoundError,
|
NotFoundError,
|
||||||
Permission,
|
Permission,
|
||||||
Plugin,
|
Plugin,
|
||||||
PotentialPlugin,
|
PluginSpec,
|
||||||
pushcall,
|
pushcall,
|
||||||
quit,
|
quit,
|
||||||
request_permission,
|
request_permission,
|
||||||
@ -187,7 +187,7 @@ __all__ = [
|
|||||||
'open_url',
|
'open_url',
|
||||||
'Permission',
|
'Permission',
|
||||||
'Plugin',
|
'Plugin',
|
||||||
'PotentialPlugin',
|
'PluginSpec',
|
||||||
'pushcall',
|
'pushcall',
|
||||||
'quit',
|
'quit',
|
||||||
'request_permission',
|
'request_permission',
|
||||||
|
|||||||
@ -179,13 +179,13 @@ class PluginWindow(bui.Window):
|
|||||||
'Still scanning plugins; please try again.', color=(1, 0, 0)
|
'Still scanning plugins; please try again.', color=(1, 0, 0)
|
||||||
)
|
)
|
||||||
bui.getsound('error').play()
|
bui.getsound('error').play()
|
||||||
pluglist = bui.app.plugins.potential_plugins
|
plugspecs = bui.app.plugins.plugin_specs
|
||||||
plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
|
plugstates: dict[str, dict] = bui.app.config.get('Plugins', {})
|
||||||
assert isinstance(plugstates, dict)
|
assert isinstance(plugstates, dict)
|
||||||
|
|
||||||
plug_line_height = 50
|
plug_line_height = 50
|
||||||
sub_width = self._scroll_width
|
sub_width = self._scroll_width
|
||||||
sub_height = len(pluglist) * plug_line_height
|
sub_height = len(plugspecs) * plug_line_height
|
||||||
self._subcontainer = bui.containerwidget(
|
self._subcontainer = bui.containerwidget(
|
||||||
parent=self._scrollwidget,
|
parent=self._scrollwidget,
|
||||||
size=(sub_width, sub_height),
|
size=(sub_width, sub_height),
|
||||||
@ -197,9 +197,7 @@ class PluginWindow(bui.Window):
|
|||||||
)
|
)
|
||||||
self._restore_state()
|
self._restore_state()
|
||||||
|
|
||||||
def _check_value_changed(
|
def _check_value_changed(self, plug: bui.PluginSpec, value: bool) -> None:
|
||||||
self, plug: bui.PotentialPlugin, value: bool
|
|
||||||
) -> None:
|
|
||||||
bui.screenmessage(
|
bui.screenmessage(
|
||||||
bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
|
||||||
color=(1.0, 0.5, 0.0),
|
color=(1.0, 0.5, 0.0),
|
||||||
@ -266,7 +264,7 @@ class PluginWindow(bui.Window):
|
|||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
# pylint: disable=too-many-statements
|
# pylint: disable=too-many-statements
|
||||||
pluglist = bui.app.plugins.potential_plugins
|
plugspecs = bui.app.plugins.plugin_specs
|
||||||
plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
|
plugstates: dict[str, dict] = bui.app.config.setdefault('Plugins', {})
|
||||||
assert isinstance(plugstates, dict)
|
assert isinstance(plugstates, dict)
|
||||||
|
|
||||||
@ -275,17 +273,18 @@ class PluginWindow(bui.Window):
|
|||||||
num_enabled = 0
|
num_enabled = 0
|
||||||
num_disabled = 0
|
num_disabled = 0
|
||||||
|
|
||||||
for i, availplug in enumerate(pluglist):
|
plugspecs_sorted = sorted(plugspecs.items())
|
||||||
|
|
||||||
|
for _classpath, plugspec in plugspecs_sorted:
|
||||||
# counting number of enabled and disabled plugins
|
# counting number of enabled and disabled plugins
|
||||||
plugstate = plugstates.setdefault(availplug.class_path, {})
|
# plugstate = plugstates.setdefault(plugspec[0], {})
|
||||||
enabled = plugstate.get('enabled', False)
|
if plugspec.enabled:
|
||||||
if enabled:
|
|
||||||
num_enabled += 1
|
num_enabled += 1
|
||||||
elif availplug.available and not enabled:
|
else:
|
||||||
num_disabled += 1
|
num_disabled += 1
|
||||||
|
|
||||||
if self._category is Category.ALL:
|
if self._category is Category.ALL:
|
||||||
sub_height = len(pluglist) * plug_line_height
|
sub_height = len(plugspecs) * plug_line_height
|
||||||
bui.containerwidget(
|
bui.containerwidget(
|
||||||
edit=self._subcontainer, size=(self._scroll_width, sub_height)
|
edit=self._subcontainer, size=(self._scroll_width, sub_height)
|
||||||
)
|
)
|
||||||
@ -305,20 +304,16 @@ class PluginWindow(bui.Window):
|
|||||||
sub_height = 0
|
sub_height = 0
|
||||||
|
|
||||||
num_shown = 0
|
num_shown = 0
|
||||||
for i, availplug in enumerate(pluglist):
|
for classpath, plugspec in plugspecs_sorted:
|
||||||
plugin = bui.app.plugins.active_plugins.get(availplug.class_path)
|
plugin = plugspec.plugin
|
||||||
active = plugin is not None
|
enabled = plugspec.enabled
|
||||||
|
|
||||||
plugstate = plugstates.setdefault(availplug.class_path, {})
|
|
||||||
checked = plugstate.get('enabled', False)
|
|
||||||
assert isinstance(checked, bool)
|
|
||||||
|
|
||||||
if self._category is Category.ALL:
|
if self._category is Category.ALL:
|
||||||
show = True
|
show = True
|
||||||
elif self._category is Category.ENABLED:
|
elif self._category is Category.ENABLED:
|
||||||
show = checked
|
show = enabled
|
||||||
elif self._category is Category.DISABLED:
|
elif self._category is Category.DISABLED:
|
||||||
show = availplug.available and not checked
|
show = not enabled
|
||||||
else:
|
else:
|
||||||
assert_never(self._category)
|
assert_never(self._category)
|
||||||
show = False
|
show = False
|
||||||
@ -329,25 +324,24 @@ class PluginWindow(bui.Window):
|
|||||||
item_y = sub_height - (num_shown + 1) * plug_line_height
|
item_y = sub_height - (num_shown + 1) * plug_line_height
|
||||||
check = bui.checkboxwidget(
|
check = bui.checkboxwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
text=availplug.display_name,
|
text=bui.Lstr(value=classpath),
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
value=checked,
|
value=enabled,
|
||||||
maxwidth=self._scroll_width - 200,
|
maxwidth=self._scroll_width - 200,
|
||||||
position=(10, item_y),
|
position=(10, item_y),
|
||||||
size=(self._scroll_width - 40, 50),
|
size=(self._scroll_width - 40, 50),
|
||||||
on_value_change_call=bui.Call(
|
on_value_change_call=bui.Call(
|
||||||
self._check_value_changed, availplug
|
self._check_value_changed, plugspec
|
||||||
),
|
),
|
||||||
textcolor=(
|
textcolor=(
|
||||||
(0.8, 0.3, 0.3)
|
(0.8, 0.3, 0.3)
|
||||||
if not availplug.available
|
if (plugspec.attempted_load and plugspec.plugin is None)
|
||||||
else (0, 1, 0)
|
|
||||||
if active and checked
|
|
||||||
else (0.8, 0.3, 0.3)
|
|
||||||
if checked
|
|
||||||
else (0.6, 0.6, 0.6)
|
else (0.6, 0.6, 0.6)
|
||||||
|
if plugspec.plugin is None
|
||||||
|
else (0, 1, 0)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
if plugin is not None and plugin.has_settings_ui():
|
if plugin is not None and plugin.has_settings_ui():
|
||||||
button = bui.buttonwidget(
|
button = bui.buttonwidget(
|
||||||
parent=self._subcontainer,
|
parent=self._subcontainer,
|
||||||
@ -356,6 +350,7 @@ class PluginWindow(bui.Window):
|
|||||||
size=(100, 40),
|
size=(100, 40),
|
||||||
position=(sub_width - 130, item_y + 6),
|
position=(sub_width - 130, item_y + 6),
|
||||||
)
|
)
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
bui.buttonwidget(
|
bui.buttonwidget(
|
||||||
edit=button,
|
edit=button,
|
||||||
on_activate_call=bui.Call(plugin.show_settings_ui, button),
|
on_activate_call=bui.Call(plugin.show_settings_ui, button),
|
||||||
@ -364,7 +359,7 @@ class PluginWindow(bui.Window):
|
|||||||
button = None
|
button = None
|
||||||
|
|
||||||
# Allow getting back to back button.
|
# Allow getting back to back button.
|
||||||
if i == 0:
|
if num_shown == 0:
|
||||||
bui.widget(
|
bui.widget(
|
||||||
edit=check,
|
edit=check,
|
||||||
up_widget=self._back_button,
|
up_widget=self._back_button,
|
||||||
|
|||||||
@ -663,19 +663,19 @@ auto CorePlatform::GetTextTextureData(void* tex) -> uint8_t* {
|
|||||||
void CorePlatform::OnMainThreadStartApp() {}
|
void CorePlatform::OnMainThreadStartApp() {}
|
||||||
|
|
||||||
void CorePlatform::OnAppStart() {
|
void CorePlatform::OnAppStart() {
|
||||||
assert(g_base_soft && g_base_soft->InLogicThread());
|
// assert(g_base_soft && g_base_soft->InLogicThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::OnAppPause() {
|
void CorePlatform::OnAppPause() {
|
||||||
assert(g_base_soft && g_base_soft->InLogicThread());
|
// assert(g_base_soft && g_base_soft->InLogicThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::OnAppResume() {
|
void CorePlatform::OnAppResume() {
|
||||||
assert(g_base_soft && g_base_soft->InLogicThread());
|
// assert(g_base_soft && g_base_soft->InLogicThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::OnAppShutdown() {
|
void CorePlatform::OnAppShutdown() {
|
||||||
assert(g_base_soft && g_base_soft->InLogicThread());
|
// assert(g_base_soft && g_base_soft->InLogicThread());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::OnScreenSizeChange() {
|
void CorePlatform::OnScreenSizeChange() {
|
||||||
|
|||||||
@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
|
|||||||
namespace ballistica {
|
namespace ballistica {
|
||||||
|
|
||||||
// These are set automatically via script; don't modify them here.
|
// These are set automatically via script; don't modify them here.
|
||||||
const int kEngineBuildNumber = 21148;
|
const int kEngineBuildNumber = 21150;
|
||||||
const char* kEngineVersion = "1.7.21";
|
const char* kEngineVersion = "1.7.21";
|
||||||
|
|
||||||
auto MonolithicMain(const core::CoreConfig& core_config) -> int {
|
auto MonolithicMain(const core::CoreConfig& core_config) -> int {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user