diff --git a/.efrocachemap b/.efrocachemap index 53136d71..0126b00d 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4072,50 +4072,50 @@ "build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/c5/09/4f10b8a21ba87aa5509cff7a164b", "build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/ff/0a/a354984f9c074dab0676ac7e4877", "build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/2a/1c/9ee5db6d1bceca7fa6638fb8abde", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/d2/f1/c7ecb66769e793ea1ae2427ec540", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/89/4f/99fb1371ff083d135f3c8251ead1", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/4a/5c/076d47a516a1b9e5d3b7e7930a30", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ab/63/caf01d35edabba0359e0444bf898", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/25/25/e0a9d955037c5375558b5bef7736", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b8/6d/ef564300e7189964e133be1c6d7f", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b4/50/9e1fcab11d5e329750ef64f38a94", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a6/62/3ae503b2027dad675a8a4dae8d75", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4c/8b/44c018a2bf5ff79951d28525d230", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/67/d9/1f49882d9f50051e5a77c8b37856", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2f/26/01c0bb3750c49e8b27032e3506b4", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c7/90/48bccd35318bbf7693417a50c96d", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/13/92/949362dd74ca933d11b8081c366b", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/20/a0/6f048f2a3b9d64851420de719d3a", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3a/f8/bca8437e4ac8403d46efdd9b0173", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d9/3d/5ac299f7f2e7acfdea2a297e1c57", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/82/ff/41b875d2048dee0d47ca5173f8e6", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/18/45/1b8328009e7e7aaf4d840170427a", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/f5/63/2b15e84c989a0f58c8ea6c78c379", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/1f/7f/ed15584c8a49e20a4f081622214f", - "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/de/fd/21a635bb0a73e252885acb7f6628", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/1b/d0/f94119594604e67652924b434416", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/13/18/48dc17a460c2ee0fa4a22b2d8005", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c2/2d/bf75444af834501f9e443618786c", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ec/88/ab5016ebb0ad9bdce10301267793", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/2c/1d/e26f457ba90fdd700dee7cc6252b", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/04/f5/59af762745bef4e8660e95a07226", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0f/32/d374d10065061ee0a6ca67b6a4af", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/94/30/c61c2114828f360d21bf81d2c39a", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4c/2b/c6aa202a104e49c82461e6d084af", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/94/ff/ac435f0ade27ad8220c945e77c2b", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/21/b5/7f010c0be5f14b2b14a823afba57", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/12/75/79f8ec088e1c946e9d60db3e91cf", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/d9/e1/4791926365ecf2a19b9528163ae0", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/e0/99/a86cb331308904b24f1a48342d2d", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/94/7d/aa5e4fb93de4681bb4bf002e28cf", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d1/cf/fa4136e93ded15dbab93fd57d1b8", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/b4/0e/1522c860e33f5001f71cfe12aa7a", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/a5/fb/cb9bb39ac98d0c5b0747094f54c2", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/63/60/cba991152f215d0e6ff1b0801a54", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/2a/29/e323dbaaa208c40a569d85a82527", + "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/0f/d7/034299d84dde9b8a36933c3d6640", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/8c/0c/209d7fef648785021b2442b92a37", - "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3e/d6/f3e8c02c82379d78d15bb1bb9d41", + "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/de/6b/14e766a2d2db860276553b7c3e9d", "build/prefab/lib/linux_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/db/d6/42e603000c13737e60f693336d4e", - "build/prefab/lib/linux_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/dd/e3/c1b35888db082abde395c7197d79", + "build/prefab/lib/linux_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/95/86/052441f8ccaa13006c9d38dbb79d", "build/prefab/lib/linux_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/64/40/f06fe4f0d7537e50b6e880c6590c", - "build/prefab/lib/linux_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/b2/fe/09cba839bcfdc19b29f6acf4c129", + "build/prefab/lib/linux_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/ed/bf/45c4771f7d77985b26f3612591e0", "build/prefab/lib/linux_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/54/10/a2c3d3a1076eb1b9812b736ca34b", - "build/prefab/lib/mac_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/08/40/aaa523c2752e7128368dcbb9746c", + "build/prefab/lib/mac_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/c3/2d/e1e05f01647e92c65cbcc3599705", "build/prefab/lib/mac_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/89/cd/db7719b6851e28d7969d807be3d8", - "build/prefab/lib/mac_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/78/c0/3c8e4b03e3d92675a4c255242ccb", + "build/prefab/lib/mac_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/37/43/84a1fc8169db34054378f11fe4c1", "build/prefab/lib/mac_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/5b/cb/4be9adf2aa44aae442bda6f39112", - "build/prefab/lib/mac_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/7d/4d/e6f90dbe842358db16b060fd3187", + "build/prefab/lib/mac_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3d/92/ea42decffa28fb7540d72248f79e", "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/8a/34/5d1b029d1a393139890f600ff91d", - "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/f6/a3/753d976d43b45062184414098306", + "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/98/e0/0772edc2ae173e7f8ab07d4e1230", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/ba/f0/c7dcdd6472a348663d104d490e77", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/94/a1/3eed726b9bf0dac2e44cca23c1ba", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/09/47/a0d5f2088c43798bc60f72204ba5", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/5f/9a/542f9cb3ddfb702f81756047e7dc", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/c9/57/641e99496162ed453e6a1a8aea2e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/0c/8b/37e6fdd08cb1dc1df818d550f7a3", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/68/82/a261d09580d572c25982b15efaa2", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/b9/91/34ee2461cf291fd765e9ab1cb09e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/8f/00/84bd3b34227305d59202195c2032", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/ef/f4/61c64323efa3fd0b1e6ef2042d2e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/43/58/6797ab58417599aefa2719e9ed70", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/8d/9d/6e683b4a8ff61138041318470352", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/b6/1a/ad49bd349c7c0f59a6dcf2efd70e", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/df/7a/6bdd5096bb65a99d39d922b1743a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/fb/15/19719ece74758579110612e93adb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/80/fe/13417c54861d3bc076c173590175", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/2b/32/3d8e3e131fd27908742aac0df030", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/d5/4a/0e480a855ce83709bd7f6761107d", diff --git a/CHANGELOG.md b/CHANGELOG.md index ec099d94..6c446abc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.20 (build 21045, api 8, 2023-06-03) +### 1.7.20 (build 21046, api 8, 2023-06-04) - This seems like a good time for a `refactoring` release in anticipation of changes coming in 1.8. Basically this means that a lot of things will be diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index 682d700e..c96cd812 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -9,6 +9,7 @@ "ba_data/python/babase/__pycache__/_appintent.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_appmode.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_appmodeselector.cpython-311.opt-1.pyc", + "ba_data/python/babase/__pycache__/_appsubsystem.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc", @@ -34,6 +35,7 @@ "ba_data/python/babase/_appintent.py", "ba_data/python/babase/_appmode.py", "ba_data/python/babase/_appmodeselector.py", + "ba_data/python/babase/_appsubsystem.py", "ba_data/python/babase/_apputils.py", "ba_data/python/babase/_assetmanager.py", "ba_data/python/babase/_asyncio.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index 8cf277c9..c1f47d98 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -142,6 +142,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/_appintent.py \ $(BUILD_DIR)/ba_data/python/babase/_appmode.py \ $(BUILD_DIR)/ba_data/python/babase/_appmodeselector.py \ + $(BUILD_DIR)/ba_data/python/babase/_appsubsystem.py \ $(BUILD_DIR)/ba_data/python/babase/_apputils.py \ $(BUILD_DIR)/ba_data/python/babase/_assetmanager.py \ $(BUILD_DIR)/ba_data/python/babase/_asyncio.py \ @@ -413,6 +414,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appintent.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appmode.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appmodeselector.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appsubsystem.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 58de3015..abf4f2e9 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -35,6 +35,7 @@ from _babase import ( from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec from babase._appmode import AppMode +from babase._appsubsystem import AppSubsystem from babase._accountv2 import AccountV2Handle from babase._plugin import PotentialPlugin, Plugin, PluginSubsystem from babase._app import App @@ -161,6 +162,7 @@ __all__ = [ 'AppIntentDefault', 'AppIntentExec', 'AppMode', + 'AppSubsystem', ] diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 95bde14d..14d632da 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -27,11 +27,9 @@ if TYPE_CHECKING: from efro.log import LogHandler import babase - from babase._cloud import CloudSubsystem + from babase import CloudSubsystem, AppIntent, AppMode, AppSubsystem from babase._accountv2 import AccountV2Subsystem from babase._apputils import AppHealthMonitor - from babase._appintent import AppIntent - from babase._appmode import AppMode # __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__ # This section generated by batools.appmodule; do not edit. @@ -58,6 +56,7 @@ class App: # Implementations for these will be filled in by internal libs. accounts: AccountV2Subsystem cloud: CloudSubsystem + plugins: PluginSubsystem # log_handler: LogHandler health_monitor: AppHealthMonitor @@ -237,6 +236,8 @@ class App: self.state = self.State.INITIAL + self._subsystems: list[AppSubsystem] = [] + self._app_bootstrapping_complete = False self._called_on_app_launching = False self._launch_completed = False @@ -279,7 +280,6 @@ class App: self.components = AppComponentSubsystem() self.meta = MetadataSubsystem() - self.plugins = PluginSubsystem() self.lang = LanguageSubsystem() self.net = NetworkSubsystem() self.workspaces = WorkspaceSubsystem() @@ -299,9 +299,25 @@ class App: """Called after we are inited and assigned to babase.app.""" # NOTE: the reason we need a postinit here is that - # some of this stuff might try importing babase.app and that doesn't + # some of this stuff accesses babase.app and that doesn't # exist yet as of our __init__() call. + self.plugins = PluginSubsystem() + + def register_subsystem(self, subsystem: AppSubsystem) -> None: + """Called by the AppSubsystem class. Do not use directly.""" + + # We only allow registering new subsystems if we've not yet + # reached the 'running' state. This ensures that all subsystems + # receive a consistent set of callbacks starting with + # on_app_running(). + if self._called_on_app_running: + raise RuntimeError( + 'Subsystems cannot be registered after the' + ' app has reached the running state.' + ) + self._subsystems.append(subsystem) + def _threadpool_no_wait_done(self, fut: Future) -> None: try: fut.result() @@ -357,8 +373,8 @@ class App: Intent defines what the app is trying to do at a given time. This call is asynchronous; the intent switch will happen in the - logic thread in the near future. If set_intent is - called repeatedly before the change takes place, the last intent + logic thread in the near future. If set_intent is called + repeatedly before the change takes place, the final intent to be set will be used. """ @@ -467,7 +483,11 @@ class App: self._update_state() def on_app_launching(self) -> None: - """Called when the app is entering the launching state.""" + """Called when the app enters the launching state. + + Here we can put together subsystems and other pieces for the + app, but most things should not be doing any work yet. + """ # pylint: disable=cyclic-import from babase import _asyncio from babase import _appconfig @@ -494,6 +514,9 @@ class App: # Get meta-system scanning built-in stuff in the bg. self.meta.start_scan(scan_complete_cb=self.on_meta_scan_complete) + if self.plus is not None: + self.plus.on_app_launching() + self.accounts.on_app_launching() # Make sure this runs after we init our accounts stuff, since @@ -507,27 +530,47 @@ class App: self._launch_completed = True self._update_state() - def on_app_loading(self) -> None: - """Called when the app is entering the loading state.""" - assert _babase.in_logic_thread() - def on_meta_scan_complete(self) -> None: """Called when meta-scan is done doing its thing.""" assert _babase.in_logic_thread() + + # Now that we know what's out there, build our final plugin set. self.plugins.on_meta_scan_complete() - def on_app_running(self) -> None: - """Called when the app is entering the running state.""" + def on_app_loading(self) -> None: + """Called when the app enters the loading state. + + At this point, all built-in pieces of the app should be in place + and can start doing 'work'. Though at a high level, the goal of + the app at this point is only to sign in to initial accounts, + download workspaces, and otherwise prepare itself to really + 'run'. + """ assert _babase.in_logic_thread() - # Let the native layer know. + def on_app_running(self) -> None: + """Called when the app enters the running state. + + At this point, all workspaces, initial accounts, etc. are in place + and we can actually get started doing whatever we're gonna do. + """ + assert _babase.in_logic_thread() + + # Let our native layer know. _babase.on_app_running() # Set a default app-mode-selector. Plugins can then override - # this if they want. + # this if they want in the on_app_running callback below. self.mode_selector = self.DefaultAppModeSelector() - self.plugins.on_app_running() + # Inform all app subsystems in the same order they were inited. + for subsystem in self._subsystems: + try: + subsystem.on_app_running() + except Exception: + logging.exception( + 'Error in on_app_running for subsystem %s.', subsystem + ) # If 'exec' code was provided to the app, always kick that off # here as an intent. @@ -562,7 +605,7 @@ class App: # This section generated by batools.appmodule; do not edit. # Hmm; need to think about how we auto-construct this; how - # do we determine which app modes to check and in what + # should we determine which app modes to check and in what # order? import bascenev1 @@ -627,27 +670,43 @@ class App: def on_app_pause(self) -> None: """Called when the app goes to a paused state.""" - self.cloud.on_app_pause() - self.plugins.on_app_pause() - self.health_monitor.on_app_pause() - if self.classic is not None: - self.classic.on_app_pause() + assert _babase.in_logic_thread() + + # Pause all app subsystems in the opposite order they were inited. + for subsystem in reversed(self._subsystems): + try: + subsystem.on_app_pause() + except Exception: + logging.exception( + 'Error in on_app_pause for subsystem %s.', subsystem + ) def on_app_resume(self) -> None: """Called when resuming.""" self.fg_state += 1 - self.cloud.on_app_resume() - self.plugins.on_app_resume() - self.health_monitor.on_app_resume() - if self.classic is not None: - self.classic.on_app_resume() + + # Resume all app subsystems in the same order they were inited. + for subsystem in self._subsystems: + try: + subsystem.on_app_resume() + except Exception: + logging.exception( + 'Error in on_app_resume for subsystem %s.', subsystem + ) def on_app_shutdown(self) -> None: """(internal)""" self.state = self.State.SHUTTING_DOWN - self.plugins.on_app_shutdown() - if self.classic is not None: - self.classic.on_app_shutdown() + + # Shutdown all app subsystems in the opposite order they were + # inited. + for subsystem in reversed(self._subsystems): + try: + subsystem.on_app_shutdown() + except Exception: + logging.exception( + 'Error in on_app_shutdown for subsystem %s.', subsystem + ) def read_config(self) -> None: """(internal)""" diff --git a/src/assets/ba_data/python/babase/_appsubsystem.py b/src/assets/ba_data/python/babase/_appsubsystem.py new file mode 100644 index 00000000..4a5a4c2c --- /dev/null +++ b/src/assets/ba_data/python/babase/_appsubsystem.py @@ -0,0 +1,41 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Provides the AppSubsystem base class.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +import _babase + +if TYPE_CHECKING: + from babase._app import App + + +class AppSubsystem: + """Base class for an app subsystem. + + Category: **App Classes** + + 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. + + Subsystems must be registered with the app before it reaches the + 'running' state. + """ + + def __init__(self) -> None: + _babase.app.register_subsystem(self) + + def on_app_running(self) -> None: + """Called when the app reaches the running state.""" + + def on_app_pause(self) -> None: + """Called when the app enters the paused state.""" + + def on_app_resume(self) -> None: + """Called when the app exits the paused state.""" + + def on_app_shutdown(self) -> None: + """Called when the app is shutting down.""" diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py index a5aaa443..c37e015d 100644 --- a/src/assets/ba_data/python/babase/_apputils.py +++ b/src/assets/ba_data/python/babase/_apputils.py @@ -14,6 +14,7 @@ from efro.call import tpartial from efro.log import LogLevel from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json import _babase +from babase._appsubsystem import AppSubsystem if TYPE_CHECKING: from typing import Any, TextIO @@ -78,8 +79,8 @@ def handle_v1_cloud_log() -> None: if app.classic is None or app.plus is None: if _babase.do_once(): logging.warning( - 'handle_v1_cloud_log should not be used' - ' without classic or plus.' + 'handle_v1_cloud_log should not be called' + ' without classic and plus present.' ) return @@ -350,11 +351,12 @@ def log_dumped_app_state() -> None: logging.exception('Error logging dumped app state.') -class AppHealthMonitor: +class AppHealthMonitor(AppSubsystem): """Logs things like app-not-responding issues.""" def __init__(self) -> None: assert _babase.in_logic_thread() + super().__init__() self._running = True self._thread = Thread(target=self._app_monitor_thread_main, daemon=True) self._thread.start() @@ -420,12 +422,10 @@ class AppHealthMonitor: self._first_check = False def on_app_pause(self) -> None: - """Should be called when the app pauses.""" assert _babase.in_logic_thread() self._running = False def on_app_resume(self) -> None: - """Should be called when the app resumes.""" assert _babase.in_logic_thread() self._running = True diff --git a/src/assets/ba_data/python/babase/_cloud.py b/src/assets/ba_data/python/babase/_cloud.py index a925eb79..47f5adda 100644 --- a/src/assets/ba_data/python/babase/_cloud.py +++ b/src/assets/ba_data/python/babase/_cloud.py @@ -8,6 +8,7 @@ import logging from typing import TYPE_CHECKING, overload import _babase +from babase._appsubsystem import AppSubsystem if TYPE_CHECKING: from typing import Callable, Any @@ -22,7 +23,7 @@ DEBUG_LOG = False # internal protocols. -class CloudSubsystem: +class CloudSubsystem(AppSubsystem): """Manages communication with cloud components.""" def is_connected(self) -> bool: @@ -33,12 +34,6 @@ class CloudSubsystem: """ return False # Needs to be overridden - def on_app_pause(self) -> None: - """Should be called when the app pauses.""" - - def on_app_resume(self) -> None: - """Should be called when the app resumes.""" - def on_connectivity_changed(self, connected: bool) -> None: """Called when cloud connectivity state changes.""" if DEBUG_LOG: diff --git a/src/assets/ba_data/python/babase/_plugin.py b/src/assets/ba_data/python/babase/_plugin.py index 121d9922..9890bb59 100644 --- a/src/assets/ba_data/python/babase/_plugin.py +++ b/src/assets/ba_data/python/babase/_plugin.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING from dataclasses import dataclass import _babase +from babase._appsubsystem import AppSubsystem if TYPE_CHECKING: from typing import Any @@ -16,7 +17,7 @@ if TYPE_CHECKING: import babase -class PluginSubsystem: +class PluginSubsystem(AppSubsystem): """Subsystem for plugin handling in the app. Category: **App Classes** @@ -28,6 +29,7 @@ class PluginSubsystem: AUTO_ENABLE_NEW_PLUGINS_DEFAULT = True def __init__(self) -> None: + super().__init__() self.potential_plugins: list[babase.PotentialPlugin] = [] self.active_plugins: dict[str, babase.Plugin] = {} @@ -46,6 +48,13 @@ class PluginSubsystem: results = _babase.app.meta.scanresults assert results is not None + auto_enable_new_plugins = ( + _babase.app.config.get( + self.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY, + self.AUTO_ENABLE_NEW_PLUGINS_DEFAULT, + ) + is True + ) # Create a potential-plugin for each class we found in the scan. for class_path in results.exports_of_class(Plugin): plugs.potential_plugins.append( @@ -55,13 +64,7 @@ class PluginSubsystem: available=True, ) ) - if ( - _babase.app.config.get( - self.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY, - self.AUTO_ENABLE_NEW_PLUGINS_DEFAULT, - ) - is True - ): + if auto_enable_new_plugins: if class_path not in plugstates: # Go ahead and enable new plugins by default, but we'll # inform the user that they need to restart to pick them up. @@ -72,12 +75,9 @@ class PluginSubsystem: plugs.potential_plugins.sort(key=lambda p: p.class_path) - # Note: these days we complete meta-scan and immediately activate - # plugins, so we don't need the message about 'restart to activate' - # anymore. - # FIXME: We actually now have an option to NOT activate new plugins - # so we should selectively re-enable this. - if found_new and bool(False): + # 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: _babase.screenmessage( Lstr(resource='pluginsDetectedText'), color=(0, 1, 0) ) @@ -87,7 +87,6 @@ class PluginSubsystem: _babase.app.config.commit() def on_app_running(self) -> None: - """Should be called when the app reaches the running state.""" # Load up our plugins and go ahead and call their on_app_running calls. self.load_plugins() for plugin in self.active_plugins.values(): @@ -99,7 +98,6 @@ class PluginSubsystem: _error.print_exception('Error in plugin on_app_running()') def on_app_pause(self) -> None: - """Called when the app goes to a suspended state.""" for plugin in self.active_plugins.values(): try: plugin.on_app_pause() @@ -109,7 +107,6 @@ class PluginSubsystem: _error.print_exception('Error in plugin on_app_pause()') def on_app_resume(self) -> None: - """Run when the app resumes from a suspended state.""" for plugin in self.active_plugins.values(): try: plugin.on_app_resume() @@ -119,7 +116,6 @@ class PluginSubsystem: _error.print_exception('Error in plugin on_app_resume()') def on_app_shutdown(self) -> None: - """Called when the app is being closed.""" for plugin in self.active_plugins.values(): try: plugin.on_app_shutdown() diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_subsystem.py index 173efa76..fbc8f670 100644 --- a/src/assets/ba_data/python/baclassic/_subsystem.py +++ b/src/assets/ba_data/python/baclassic/_subsystem.py @@ -10,6 +10,7 @@ from typing import TYPE_CHECKING import _babase import _bauiv1 import bascenev1 +from babase._appsubsystem import AppSubsystem from babase._general import AppTime import _baclassic from baclassic._music import MusicSubsystem @@ -34,7 +35,7 @@ if TYPE_CHECKING: from baclassic._net import MasterServerCallback -class ClassicSubsystem: +class ClassicSubsystem(AppSubsystem): """Subsystem for classic functionality in the app. The single shared instance of this app can be accessed at @@ -55,6 +56,7 @@ class ClassicSubsystem: from baclassic._music import MusicPlayMode # FIXME move 2 subsys def __init__(self) -> None: + super().__init__() self._env = _babase.env() self.accounts = AccountV1Subsystem() @@ -252,16 +254,13 @@ class ClassicSubsystem: self.accounts.on_app_launching() def on_app_pause(self) -> None: - """Called when the app is getting paused.""" self.accounts.on_app_pause() def on_app_resume(self) -> None: - """Called when the app is getting resumed.""" self.accounts.on_app_resume() self.music.on_app_resume() def on_app_shutdown(self) -> None: - """Called when the app is shutting down.""" self.music.on_app_shutdown() def pause(self) -> None: diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index ed9b29be..43ea5286 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -30,7 +30,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21045 +TARGET_BALLISTICA_BUILD = 21046 TARGET_BALLISTICA_VERSION = '1.7.20' _g_env_config: EnvConfig | None = None diff --git a/src/assets/ba_data/python/baplus/_subsystem.py b/src/assets/ba_data/python/baplus/_subsystem.py index 47a036e3..004283bc 100644 --- a/src/assets/ba_data/python/baplus/_subsystem.py +++ b/src/assets/ba_data/python/baplus/_subsystem.py @@ -6,12 +6,15 @@ from __future__ import annotations from typing import TYPE_CHECKING import _baplus +from babase._appsubsystem import AppSubsystem if TYPE_CHECKING: from typing import Callable, Any + from babase import App -class PlusSubsystem: + +class PlusSubsystem(AppSubsystem): """Subsystem for plus functionality in the app. The single shared instance of this app can be accessed at @@ -26,9 +29,6 @@ class PlusSubsystem: # type-checking purposes. Maybe there's some smart way we could skip # the overhead of this wrapper at runtime. - def __init__(self) -> None: - pass - @staticmethod def add_v1_account_transaction( transaction: dict, callback: Callable | None = None @@ -157,7 +157,7 @@ class PlusSubsystem: @staticmethod def on_app_launching() -> None: """(internal)""" - return _baplus.on_app_launching() + _baplus.on_app_launching() @staticmethod def power_ranking_query(callback: Callable, season: Any = None) -> None: diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index a52c21c5..a25d860b 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -460,8 +460,14 @@ void BaseFeatureSet::ScreenMessage(const std::string& s, void BaseFeatureSet::V1CloudLog(const std::string& msg) { // If we've got a fully running app environment, let the Python layer - // handle logs. It will group log messages intelligently and ship them + // handle this. It will group log messages intelligently and ship them // to the master server with various other context info included. + + // We currently need both plus and classic for this system to function. + if (!(HavePlus() && HaveClassic())) { + return; + } + if (app_running_) { python->objs().PushCall(BasePython::ObjID::kHandleV1CloudLogCall); } else { diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc index 37b03b64..53617275 100644 --- a/src/ballistica/base/python/base_python.cc +++ b/src/ballistica/base/python/base_python.cc @@ -53,7 +53,7 @@ void BasePython::AddPythonClasses(PyObject* module) { // FIXME: should be able to do this in Python bootstrapping // code. auto register_call = - PythonRef(PyImport_ImportModule("collections.abc"), PythonRef::kSteal) + PythonRef::Stolen(PyImport_ImportModule("collections.abc")) .GetAttr("Sequence") .GetAttr("register"); PythonRef args(Py_BuildValue("(O)", vec3), PythonRef::kSteal); @@ -68,7 +68,7 @@ void BasePython::ImportPythonObjs() { void BasePython::SoftImportPlus() { auto gil{Python::ScopedInterpreterLock()}; - auto result = PythonRef::Stolen(PyImport_ImportModule("_baplus")); + auto result = PythonRef::StolenSoft(PyImport_ImportModule("_baplus")); if (!result.Exists()) { // Ignore any errors here for now. All that will matter is whether plus // gave us its interface. @@ -78,7 +78,7 @@ void BasePython::SoftImportPlus() { void BasePython::SoftImportClassic() { auto gil{Python::ScopedInterpreterLock()}; - auto result = PythonRef::Stolen(PyImport_ImportModule("_baclassic")); + auto result = PythonRef::StolenSoft(PyImport_ImportModule("_baclassic")); if (!result.Exists()) { // Ignore any errors here for now. All that will matter is whether plus // gave us its interface. @@ -88,7 +88,7 @@ void BasePython::SoftImportClassic() { void BasePython::SoftImportUIV1() { auto gil{Python::ScopedInterpreterLock()}; - auto result = PythonRef::Stolen(PyImport_ImportModule("_bauiv1")); + auto result = PythonRef::StolenSoft(PyImport_ImportModule("_bauiv1")); if (!result.Exists()) { // Ignore any errors here for now. All that will matter is whether plus // gave us its interface. diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc index fcec90e9..12fe711a 100644 --- a/src/ballistica/core/python/core_python.cc +++ b/src/ballistica/core/python/core_python.cc @@ -181,7 +181,7 @@ void CorePython::ReleaseMainThreadGIL() { void CorePython::SoftImportBase() { auto gil{Python::ScopedInterpreterLock()}; - auto result = PythonRef::Stolen(PyImport_ImportModule("_babase")); + auto result = PythonRef::StolenSoft(PyImport_ImportModule("_babase")); if (!result.Exists()) { // Ignore any errors here for now. All that will matter is whether base // gave us its interface. diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index bb666c62..6a85b0bf 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21045; +const int kEngineBuildNumber = 21046; const char* kEngineVersion = "1.7.20"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { diff --git a/src/ballistica/shared/python/python_ref.cc b/src/ballistica/shared/python/python_ref.cc index 37667d95..c05db174 100644 --- a/src/ballistica/shared/python/python_ref.cc +++ b/src/ballistica/shared/python/python_ref.cc @@ -19,56 +19,41 @@ using core::g_core; #pragma clang diagnostic push #pragma ide diagnostic ignored "RedundantCast" -PythonRef::PythonRef(PyObject* obj_in, ReferenceBehavior b) { - assert(Python::HaveGIL()); +static void ClearPythonExceptionAndWarnIfUnset() { + // We're assuming that a nullptr passed to us means a Python call has + // failed and set an exception. So we're clearing that exception since + // we'll be handling it by converting it to a C++ one. However let's warn + // if we were passed nullptr but *no* Python exception is set. We want to + // avoid that situation because it opens up the possibility of us clearing + // exceptions that aren't related to our nullptr. + if (!PyErr_Occurred()) { + Log(LogLevel::kWarning, + "A PythonRef acquire/steal call was passed nullptr but no Python " + "exception is set. This situation should be avoided; only pass " + "acquire/steal if it is directly due to a Python exception."); + } else { + PyErr_Clear(); + } +} + +PythonRef::PythonRef(PyObject* obj, ReferenceBehavior b) { switch (b) { case kSteal: - Steal(obj_in); + Steal(obj); break; case kStealSoft: - if (obj_in) { - Steal(obj_in); - } + StealSoft(obj); break; case kAcquire: - Acquire(obj_in); + Acquire(obj); break; case kAcquireSoft: - if (obj_in) { - Acquire(obj_in); - break; - } + AcquireSoft(obj); break; } } -void PythonRef::Acquire(PyObject* obj_in) { - BA_PRECONDITION(obj_in); - assert(Python::HaveGIL()); - - // Assign and increment the new one before decrementing our old - // (in case its the same one or prev gets deallocated and accesses us - // somehow). - PyObject* prev = obj_; - Py_INCREF(obj_in); - obj_ = obj_in; - if (prev) { - Py_DECREF(prev); - } -} - -void PythonRef::AcquireSoft(PyObject* obj_in) { - if (!obj_in) { - Release(); - return; - } - Acquire(obj_in); -} - -void PythonRef::Steal(PyObject* obj_in) { - BA_PRECONDITION(obj_in); - assert(Python::HaveGIL()); - +void PythonRef::SetObj(PyObject* obj_in) { // Assign before decrementing the old // (in case prev gets deallocated and accesses us somehow). PyObject* prev = obj_; @@ -78,12 +63,46 @@ void PythonRef::Steal(PyObject* obj_in) { } } -void PythonRef::StealSoft(PyObject* obj_in) { +void PythonRef::Steal(PyObject* obj_in) { + assert(Python::HaveGIL()); if (!obj_in) { + ClearPythonExceptionAndWarnIfUnset(); + throw Exception("nullptr passed to PythonRef::Steal."); + } + SetObj(obj_in); +} + +void PythonRef::StealSoft(PyObject* obj_in) { + assert(Python::HaveGIL()); + if (!obj_in) { + // 'Soft' versions don't assume nullptr is due to an exception, + // so we don't touch Python exception state here. Release(); return; } - Steal(obj_in); + SetObj(obj_in); +} + +void PythonRef::Acquire(PyObject* obj_in) { + assert(Python::HaveGIL()); + if (!obj_in) { + ClearPythonExceptionAndWarnIfUnset(); + throw Exception("nullptr passed to PythonRef::Acquire."); + } + Py_INCREF(obj_in); + SetObj(obj_in); +} + +void PythonRef::AcquireSoft(PyObject* obj_in) { + assert(Python::HaveGIL()); + if (!obj_in) { + // 'Soft' versions don't assume nullptr is due to an exception, + // so we don't touch Python exception state here. + Release(); + return; + } + Py_INCREF(obj_in); + SetObj(obj_in); } void PythonRef::Release() { diff --git a/src/ballistica/shared/python/python_ref.h b/src/ballistica/shared/python/python_ref.h index 460e711f..3e16f242 100644 --- a/src/ballistica/shared/python/python_ref.h +++ b/src/ballistica/shared/python/python_ref.h @@ -15,17 +15,24 @@ class PythonRef { public: /// Defines referencing behavior when creating new instances. enum ReferenceBehavior { - /// Steal the provided object reference (and throw an Exception if it is - /// nullptr). + /// Steal the provided object reference. If nullptr is passed, it is + /// assumed to be due to a Python exception occurring. The Python + /// exception is then cleared, a C++ exception is raised, and a warning + /// is logged if no Python exception was set. kSteal, - /// Steal the provided object reference or set as unreferenced if it is - /// nullptr. + /// Steal the provided object reference. If nullptr is passed, set as + /// unreferenced. Does not touch Python exception state, so be sure to + /// clear that yourself if the nullptr case is due to an Exception. kStealSoft, - /// Acquire a new reference to the provided object (and throw an Exception - /// if it is nullptr). + /// Acquire a new reference to the provided object. If nullptr is + /// passed, it is assumed to be due to a Python exception occurring. The + /// Python exception is then cleared, a C++ exception is raised, and a + /// warning is logged if no Python exception was set. kAcquire, - /// Acquire a new reference to the provided object or set as unreferenced if - /// it is nullptr. + /// Acquire a new reference to the provided object. If nullptr is + /// passed, set as unreferenced. Does not touch Python exception state, + /// so be sure to clear that yourself if the nullptr case is due to an + /// exception. kAcquireSoft }; @@ -35,22 +42,22 @@ class PythonRef { /// See ReferenceBehavior docs. PythonRef(PyObject* obj, ReferenceBehavior behavior); - /// Shortcut to create a new PythonRef using ReferenceBehavior::kSteal. + /// Shortcut to create a new PythonRef using ReferenceBehavior::kSteal. static auto Stolen(PyObject* obj) -> PythonRef { - return PythonRef(obj, ReferenceBehavior::kSteal); + return {obj, ReferenceBehavior::kSteal}; } static auto StolenSoft(PyObject* obj) -> PythonRef { - return PythonRef(obj, ReferenceBehavior::kStealSoft); + return {obj, ReferenceBehavior::kStealSoft}; } - /// Shortcut to create a new PythonRef using ReferenceBehavior::kAcquire. + /// Shortcut to create a new PythonRef using ReferenceBehavior::kAcquire. static auto Acquired(PyObject* obj) -> PythonRef { - return PythonRef(obj, ReferenceBehavior::kAcquire); + return {obj, ReferenceBehavior::kAcquire}; } static auto AcquiredSoft(PyObject* obj) -> PythonRef { - return PythonRef(obj, ReferenceBehavior::kAcquireSoft); + return {obj, ReferenceBehavior::kAcquireSoft}; } /// Copy constructor acquires a new reference (or sets as unreferenced) @@ -84,20 +91,28 @@ class PythonRef { return !(*this == other); } - /// Acquire a new reference to the passed object. Throws an exception if - /// nullptr is passed. - void Acquire(PyObject* obj); - - /// Acquire a new reference to the passed object. Sets to null reference - /// if nullptr is passed. - void AcquireSoft(PyObject* obj); - - /// Steal the passed reference. Throws an Exception if nullptr is passed. + /// Steal the provided object reference. If nullptr is passed, it is + /// assumed to be due to a Python exception occurring. The Python + /// exception is then cleared, a C++ exception is raised, and a warning is + /// logged if no Python exception was set. void Steal(PyObject* obj); - /// Steal the passed reference. Sets to null reference if nullptr is passed. + /// Steal the provided object reference. If nullptr is passed, set as + /// unreferenced. Does not touch Python exception state, so be sure to + /// clear that yourself if the nullptr case is due to an Exception. void StealSoft(PyObject* obj); + /// Acquire a new reference to the provided object. If nullptr is passed, + /// it is assumed to be due to a Python exception occurring. The Python + /// exception is then cleared, a C++ exception is raised, and a warning is + /// logged if no Python exception was set. + void Acquire(PyObject* obj); + + /// Acquire a new reference to the provided object. If nullptr is passed, + /// set as unreferenced. Does not touch Python exception state, so be sure + /// to clear that yourself if the nullptr case is due to an exception. + void AcquireSoft(PyObject* obj); + /// Release the held reference (if one is held). void Release(); @@ -162,8 +177,8 @@ class PythonRef { /// Throws an exception if unset. auto UnicodeCheck() const -> bool; - /// Call the PyObject. On error, (optionally) prints errors and returns empty - /// ref. + /// Call the PyObject. On error, (optionally) prints errors and returns + /// empty ref. auto Call(PyObject* args, PyObject* keywds = nullptr, bool print_errors = true) const -> PythonRef; auto Call(const PythonRef& args, const PythonRef& keywds = PythonRef(), @@ -177,6 +192,7 @@ class PythonRef { private: void ThrowIfUnset() const; + void SetObj(PyObject* obj); PyObject* obj_{}; }; diff --git a/tools/batools/appmodule.py b/tools/batools/appmodule.py index 70d7c80b..8a6d6142 100755 --- a/tools/batools/appmodule.py +++ b/tools/batools/appmodule.py @@ -118,7 +118,7 @@ def generate_app_module( # Set default app-mode-selection logic. contents = ( '# Hmm; need to think about how we auto-construct this; how\n' - '# do we determine which app modes to check and in what\n' + '# should we determine which app modes to check and in what\n' '# order?\n' ) if 'scene_v1' in fsets: