diff --git a/.efrocachemap b/.efrocachemap index 78afb51e..7a2269bf 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4064,50 +4064,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "4542f7820b33b8f1f6719b17efa26453", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "c94898207acb63e3e09cb08b50ebd287", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ad606ee2ee1367b906c9cbb18c53baf6", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "776e2461a852753010278d5b90ff32df", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "aefcfd8774114e7cebc418d0adad56f2", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "d21df3fc06db87786d68ccc1417d043e", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "7b218f79079e8e0d92b5f0a4afb0599d", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "94738f2f6fcbea9dad60af4bd6a7cd5d", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "033b39b34a10d5bce6e53572a816d0ef", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "f4eb1cc72a16eafcaba2d55c65f19a7b", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "fec5b1818aa97bc0dff1ddf127742574", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "76930acbbd2e8dd35f35639d85ce21ac", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "d5ab5ad85dd8d113125d15f03c221db3", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "29fbcf76221d71ea3ecb73d8ab353f7b", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "3d8b4b5d6ba376f2d280209e0aa3bcf2", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "fb9da4455aa7c0d8e5120a75ccaeabcb", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "056f8137ba8b1179b66eea59944aefb2", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "78f53224dd6052c93da31bda7ee1c84c", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "82128374a7eabc651061d778e099b923", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "2cd60cbebfbe25447791284b42c3caf9", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a589af5b31246539eac3264c829c41a0", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "3dfaf945474294cb9f3808a835fb667c", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a589af5b31246539eac3264c829c41a0", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "3dfaf945474294cb9f3808a835fb667c", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "47e08d2f265f4dda15b309fa67ba163b", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "c9a5225be07b4456e073014e1db2cafe", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "47e08d2f265f4dda15b309fa67ba163b", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "c9a5225be07b4456e073014e1db2cafe", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "f79382e5342db6f38f4c07170589d62d", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "b2d5386301891813a790f1a19d442022", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "f79382e5342db6f38f4c07170589d62d", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "b2d5386301891813a790f1a19d442022", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "899eda04958efce6903b7dd2abe6c76f", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "e98d76fe2d0af7e775801998d8591340", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "e9d551c0bfbb330470b1e0784028e4d3", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "e98d76fe2d0af7e775801998d8591340", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "77f25ad3348eb212969725c0ec7ebec8", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "df8038790ce73124950dc443c1e972ad", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "f184798b7973343cdc42d16e05aa335f", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "2e3ed4ea52261efd17d0927b21cf4075", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "1a5c374cfdd07ccbb2f1e9a44b886131", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "66da72816faa1d87d0b3fe677c6eb9c8", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "fbef69c5fba9b7c94d37e233eb6c9642", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c37e1dc4f95a8db0b5059dae0b5f5241", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "3403caf882f5efc1c5a62bf452994bd1", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "c046dcf69b1d3cb122869ee1f8df06d6", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "10f90e814342ba8553c193d8ce3221d1", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "2827cb64562372b70fe78c30cc4dbf1e", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "37e25714c6b19f5edc1135820edb0ef4", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "bacec90936f644c739eb9230b61c1528", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "51e044f01c1f783f2c88d8ba490370a6", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "01aff77ee4c84b06b54664e26aaed0cf", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "7430ba5eadfdd15310d0b140d5ef3f2b", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "de88562b3c51a56a5a89bc35a7888e58", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "74b6f29023a4a3e90b2233d77c0c38bb", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "78150d99f0e8dfd2f6785af4335b3f49", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "ccc2f72a1a12a3314f6ffea3ea20875c", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "bab389beb2e52641e5a7e5cf9a62bf69", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "a3de45a3355c610719a477077babf451", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "cf35da279c2ff3d4a8241e51199fea5d", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8fcb6c2f3be27b2aadee60a436ee5c82", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "695c693d94935c99e3990c885b882091", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "38a69802cca5b4bc3a344777f12679bd", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4e24b9c6b980844de8c482dd1034fb97", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4811585805942428ddb217917e4ad843", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "4eb48f28a678a7f2eae8c10d4d86b879", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "675e12e534ebec6cb9760b066c158747", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "5d9bb2f2835069e186418b1b02598168", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "7fa0439993cdd80f098a2cf37d4e6b31", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "1bac9e9d670f8dfac75398085820d039", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "9ecb0b936f758250417f7c014f89d683", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "912935f68da6dfca6312b3859c14ee27", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "aa9ca81cff1cd3135af6fa81cb16851a", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9", diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 993d45c3..77c090b9 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -823,6 +823,7 @@ dstent dstfile dstfin + dstfs dstjson dstlines dstname @@ -1085,6 +1086,7 @@ flycheck fmod fname + fnamefilt fnamefull fnames fnmatch @@ -2669,6 +2671,7 @@ shobs shortname shouldn + shouldnt showbuffer showpoints showstats @@ -2753,11 +2756,13 @@ splitnumstr spwd squadcore + src's srcabs srcattr srcdata srcdir srcfolder + srcfs srcgrp srcid srcjson @@ -2840,6 +2845,7 @@ subdep subdeps subdirs + subdst subfieldpath subfolders submpath @@ -2852,6 +2858,7 @@ subprocesses subrepos subsel + subsrc subsys subtypestr subval diff --git a/CHANGELOG.md b/CHANGELOG.md index 101b2aa0..bbb43be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ -### 1.7.28 (build 21289, api 8, 2023-08-31) +### 1.7.28 (build 21293, api 8, 2023-08-31) +- Added some high level functionality for copying and deleting feature-sets to + the `tools/spinoff` tool. For example, to create your own `poo` feature-set, + do `tools/spinoff fset-copy template_fs poo`. Then do `make update` and `make + cmake` to build and run the app, and from within it you should be able to do + `import bapoo` to get at your nice shiny poo feature-set. When you are done + playing, you can do `tools/spinoff fset-delete poo` to blow away any traces of + it. + ### 1.7.27 (build 21282, api 8, 2023-08-30) - Fixed a rare crash that could occur if the app shuts down while a background diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index 88a7f7dc..f1cbefcf 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -512,6 +512,7 @@ dstdir dstdirfull dstent + dstfs dstnode dstpath dstr @@ -662,6 +663,7 @@ flopsy flushhhhh fname + fnamefilt fnode fnsu fnumc @@ -1619,9 +1621,11 @@ spinups spivak spwd + src's srcabs srcattr srcfolder + srcfs srcgrp srcid srcname @@ -1683,6 +1687,7 @@ subargs subc subclsssing + subdst subentities subfieldpath subitems @@ -1691,6 +1696,7 @@ subplatform subscale subscr + subsrc subsys subtypestr successmsg diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 0dccbe5f..cdf8c69d 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -109,6 +109,7 @@ from babase._apputils import ( is_browser_likely_available, garbage_collect, get_remote_app_name, + AppHealthMonitor, ) from babase._cloud import CloudSubsystem from babase._emptyappmode import EmptyAppMode @@ -176,6 +177,7 @@ __all__ = [ 'app', 'App', 'AppConfig', + 'AppHealthMonitor', 'AppIntent', 'AppIntentDefault', 'AppIntentExec', diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 9c1a2c6e..ab519610 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -13,6 +13,7 @@ from concurrent.futures import ThreadPoolExecutor from functools import cached_property from efro.call import tpartial + import _babase from babase._language import LanguageSubsystem from babase._plugin import PluginSubsystem diff --git a/src/assets/ba_data/python/baclassic/_net.py b/src/assets/ba_data/python/baclassic/_net.py index 7fd41d8f..a76258df 100644 --- a/src/assets/ba_data/python/baclassic/_net.py +++ b/src/assets/ba_data/python/baclassic/_net.py @@ -85,7 +85,9 @@ class MasterServerV1CallThread(threading.Thread): # Tearing the app down while this is running can lead to # rare crashes in LibSSL, so avoid that if at all possible. - babase.shutdown_suppress_begin() + if not babase.shutdown_suppress_begin(): + # App is already shutting down, so we're a no-op. + return try: classic = babase.app.classic diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index b16b7924..c13351be 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21289 +TARGET_BALLISTICA_BUILD = 21293 TARGET_BALLISTICA_VERSION = '1.7.28' @@ -461,11 +461,12 @@ def _modular_main() -> None: # First baenv sets up things like Python paths the way the engine # needs them, and then we import and run the engine. # - # Below we're doing a slightly fancier version of that. Namely we do - # some processing of command line args to allow overriding of paths - # or running explicit commands or whatever else. Our goal is that - # this modular form of the app should be basically indistinguishable - # from the monolithic form when used from the command line. + # Below we're doing a slightly fancier version of that. Namely, we + # do some processing of command line args to allow overriding of + # paths or running explicit commands or whatever else. Our goal is + # that this modular form of the app should be basically + # indistinguishable from the monolithic form when used from the + # command line. try: # Take note that we're running via modular-main. The native diff --git a/src/assets/ba_data/python/batemplatefs/__init__.py b/src/assets/ba_data/python/batemplatefs/__init__.py index 387a84de..58ad2cd3 100644 --- a/src/assets/ba_data/python/batemplatefs/__init__.py +++ b/src/assets/ba_data/python/batemplatefs/__init__.py @@ -4,8 +4,12 @@ # ba_meta require api 8 +# Package up various private bits (including stuff from our native +# module) into a nice clean public API. +from _batemplatefs import hello_again_world from batemplatefs._subsystem import TemplateFsSubsystem __all__ = [ 'TemplateFsSubsystem', + 'hello_again_world', ] diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index e66d83e9..6a3b61e4 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -715,11 +715,33 @@ void BaseFeatureSet::DoPushObjCall(const PythonObjectSetBase* objset, int id, } auto BaseFeatureSet::IsAppStarted() const -> bool { return app_started_; } -void BaseFeatureSet::ShutdownSuppressBegin() { shutdown_suppress_count_++; } + +auto BaseFeatureSet::ShutdownSuppressBegin() -> bool { + std::scoped_lock lock(shutdown_suppress_lock_); + if (!shutdown_suppress_disallowed_) { + return false; + } + shutdown_suppress_count_++; + return true; +} + void BaseFeatureSet::ShutdownSuppressEnd() { + std::scoped_lock lock(shutdown_suppress_lock_); shutdown_suppress_count_--; assert(shutdown_suppress_count_ >= 0); } + +auto BaseFeatureSet::ShutdownSuppressGetCount() -> int { + std::scoped_lock lock(shutdown_suppress_lock_); + return shutdown_suppress_count_; +} + +void BaseFeatureSet::ShutdownSuppressDisallow() { + std::scoped_lock lock(shutdown_suppress_lock_); + assert(!shutdown_suppress_disallowed_); + shutdown_suppress_disallowed_ = true; +} + auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); } } // namespace ballistica::base diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index 34aab1d1..c828b325 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -3,6 +3,7 @@ #ifndef BALLISTICA_BASE_BASE_H_ #define BALLISTICA_BASE_BASE_H_ +#include #include #include @@ -690,9 +691,18 @@ class BaseFeatureSet : public FeatureSetNativeComponent, void DoPushObjCall(const PythonObjectSetBase* objset, int id, const std::string& arg) override; void OnReachedEndOfBaBaseImport(); - void ShutdownSuppressBegin(); + + /// Begin a shutdown-suppressing operation. Returns true if the operation + /// can proceed; otherwise shutdown has already begun and the operation + /// should be aborted. + auto ShutdownSuppressBegin() -> bool; + + /// End a shutddown-suppressing operation. Should only be called after a + /// successful begin. void ShutdownSuppressEnd(); - auto shutdown_suppress_count() const { return shutdown_suppress_count_; } + + auto ShutdownSuppressGetCount() -> int; + void ShutdownSuppressDisallow(); /// Called in the logic thread once our screen is up and assets are /// loading. @@ -756,6 +766,8 @@ class BaseFeatureSet : public FeatureSetNativeComponent, StressTest* stress_test_; std::string console_startup_messages_; + std::mutex shutdown_suppress_lock_; + bool shutdown_suppress_disallowed_; int shutdown_suppress_count_{}; bool tried_importing_plus_{}; bool tried_importing_classic_{}; diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 75b62fa1..884c8190 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -226,7 +226,10 @@ void Logic::OnAppShutdown() { // Nuke the app from orbit if we get stuck while shutting down. g_core->StartSuicideTimer("shutdown", 10000); - // Let our subsystems know we're shutting down. + // Tell base to disallow shutdown-suppressors from here on out. + g_base->ShutdownSuppressDisallow(); + + // Let our logic thread subsystems know we're shutting down. // Note: Keep these in opposite order of OnAppStart. // Note2: Any shutdown processes that take a non-zero amount of time // should be registered as shutdown-tasks diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index e6f40764..94a959df 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -1495,8 +1495,12 @@ static PyMethodDef PyGetImmediateReturnCodeDef = { static auto PyShutdownSuppressBegin(PyObject* self) -> PyObject* { BA_PYTHON_TRY; assert(g_base); - g_base->ShutdownSuppressBegin(); - Py_RETURN_NONE; + auto val = g_base->ShutdownSuppressBegin(); + if (val) { + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } BA_PYTHON_CATCH; } @@ -1505,7 +1509,7 @@ static PyMethodDef PyShutdownSuppressBeginDef = { (PyCFunction)PyShutdownSuppressBegin, // method METH_NOARGS, // flags - "shutdown_suppress_begin() -> None\n" + "shutdown_suppress_begin() -> bool\n" "\n" "(internal)\n", }; @@ -1536,7 +1540,7 @@ static PyMethodDef PyShutdownSuppressEndDef = { static auto PyShutdownSuppressCount(PyObject* self) -> PyObject* { BA_PYTHON_TRY; assert(g_base); - return PyLong_FromLong(g_base->shutdown_suppress_count()); + return PyLong_FromLong(g_base->ShutdownSuppressGetCount()); Py_RETURN_NONE; BA_PYTHON_CATCH; } diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index 1374988e..07a3b442 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -15,9 +15,8 @@ CoreFeatureSet* g_core{}; BaseSoftInterface* g_base_soft{}; auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { - // In monolithic builds we can accept an explicit core-config the first - // time we're imported. In this case, Python is not even spun up yet so - // it can influence even that. + // In monolithic builds, we accept an explicit core-config the first time + // we're imported. It is fully up to the caller to build the config. if (g_buildconfig.monolithic_build()) { if (config != nullptr) { if (g_core != nullptr) { @@ -29,17 +28,17 @@ auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { DoImport(*config); } } else { - // No config passed; use a default. + // If no config is passed, use a default. If the user wants env vars + // or anything else factored in, they should do so themselves in the + // config they pass (CoreConfig::ForEnvVars(), etc.). if (g_core == nullptr) { DoImport({}); } } } else { - // In modular builds we autogenerate a CoreConfig that takes into - // account only env-vars (or env-vars plus Python args if we're being - // run via the baenv script). In this case, Python is already spun up - // and baenv already handled any Python environment stuff so we have - // less to do. + // In modular builds, we generate a CoreConfig *after* Python is spun + // up, implicitly using Python's sys args and/or env vars when + // applicable. if (config != nullptr) { FatalError("CoreConfig can't be explicitly passed in modular builds."); } @@ -57,6 +56,7 @@ auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { DoImport(CoreConfig::ForArgsAndEnvVars(static_cast(argv.size()), argv.data())); } else { + // Not using Python sys args but we still want to process env vars. DoImport(CoreConfig::ForEnvVars()); } } diff --git a/src/ballistica/core/core.h b/src/ballistica/core/core.h index 49f0c342..08ad6331 100644 --- a/src/ballistica/core/core.h +++ b/src/ballistica/core/core.h @@ -24,27 +24,26 @@ class CoreFeatureSet; class BaseSoftInterface; // Our feature-set's globals. -// Feature-sets should NEVER directly access globals in another feature-set's -// namespace. All functionality we need from other feature-sets should be -// imported into globals in our own namespace. Generally we do this when we -// are initially imported (just as regular Python modules do). +// +// Feature-sets should NEVER directly access globals in another +// feature-set's namespace. All functionality we need from other +// feature-sets should be imported into globals in our own namespace. +// Generally we do this when we are initially imported (just as regular +// Python modules do). + +// Our pointer to our own feature-set. extern CoreFeatureSet* g_core; -// We don't require the base feature-set but can use it if present. -// Base will supply us with this pointer if/when it spins up. -// So we must never assume this pointer is valid and must check for it -// with each use. +// We don't require the base feature-set but can use it if present. Base +// will supply us with this pointer if/when it spins up. So we must never +// assume this pointer is valid and must check for it with each use. extern BaseSoftInterface* g_base_soft; -/// Platform-agnostic global state for our overall system. -/// This gets created whenever we are used in any capacity, even if -/// we don't create/run an app. -/// Ideally most things here should be migrated to more specific -/// subsystems. +/// Core engine functionality. class CoreFeatureSet { public: - /// Import the core feature set. A core-config can be passed ONLY - /// in monolithic builds when it is guaranteed that the Import will be + /// Import the core feature set. A core-config can be passed ONLY in + /// monolithic builds when it is guaranteed that the Import will be /// allocating the CoreFeatureSet singleton. static auto Import(const CoreConfig* config = nullptr) -> CoreFeatureSet*; @@ -76,32 +75,35 @@ class CoreFeatureSet { auto HeadlessMode() -> bool; /// Return current app-time in milliseconds. + /// /// App-time is basically the total time that the engine has been actively - /// running. (The 'App' here is a slight misnomer). It will stop progressing - /// while the app is suspended and will never go backwards. + /// running. (The 'App' here is a slight misnomer). It will stop + /// progressing while the app is suspended and will never go backwards. auto GetAppTimeMillisecs() -> millisecs_t; /// Return current app-time in microseconds. + /// /// App-time is basically the total time that the engine has been actively - /// running. (The 'App' here is a slight misnomer). It will stop progressing - /// while the app is suspended and will never go backwards. + /// running. (The 'App' here is a slight misnomer). It will stop + /// progressing while the app is suspended and will never go backwards. auto GetAppTimeMicrosecs() -> microsecs_t; /// Return current app-time in seconds. + /// /// App-time is basically the total time that the engine has been actively - /// running. (The 'App' here is a slight misnomer). It will stop progressing - /// while the app is suspended and will never go backwards. + /// running. (The 'App' here is a slight misnomer). It will stop + /// progressing while the app is suspended and will never go backwards. auto GetAppTimeSeconds() -> double; - /// Are we in the thread the main event loop is running on? - /// Generally this is the thread that runs graphics and os event processing. + /// Are we in the thread the main event loop is running on? Generally this + /// is the thread that runs graphics and os event processing. auto InMainThread() -> bool; /// Log a boot-related message (only if core_config.lifecycle_log is true). void LifecycleLog(const char* msg, double offset_seconds = 0.0); - /// Base path of build src dir so we can attempt to remove it from - /// any source file paths we print. + /// Base path of build src dir so we can attempt to remove it from any + /// source file paths we print. auto build_src_dir() const { return build_src_dir_; } const auto& legacy_user_agent_string() const { @@ -113,8 +115,8 @@ class CoreFeatureSet { } /// Return true if baenv values have been locked in: python paths, log - /// handling, etc. Early-running code may wish to explicitly avoid making log - /// calls until this condition is met to ensure predictable behavior. + /// handling, etc. Early-running code may wish to explicitly avoid making + /// log calls until this condition is met to ensure predictable behavior. auto have_ba_env_vals() const { return have_ba_env_vals_; } /// Return the directory where the app expects to find its bundled Python @@ -138,8 +140,8 @@ class CoreFeatureSet { /// Return the directory where bundled 3rd party Python files live. auto GetSitePythonDirectory() -> std::optional; - // Are we using a non-standard app python dir (such as a 'sys' dir within a - // user-python-dir). + // Are we using a non-standard app python dir (such as a 'sys' dir within + // a user-python-dir). auto using_custom_app_python_dir() const { return using_custom_app_python_dir_; } @@ -165,7 +167,6 @@ class CoreFeatureSet { bool reset_vr_orientation{}; bool user_ran_commands{}; std::thread::id main_thread_id{}; - bool vr_mode; std::mutex thread_name_map_mutex; std::unordered_map thread_name_map; @@ -177,11 +178,11 @@ class CoreFeatureSet { #endif private: + explicit CoreFeatureSet(CoreConfig config); + static void DoImport(const CoreConfig& config); static auto CalcBuildSrcDir() -> std::string; void RunSanityChecks(); - static void DoImport(const CoreConfig& config); void UpdateAppTime(); - explicit CoreFeatureSet(CoreConfig config); void PostInit(); bool tried_importing_base_{}; EventLoop* main_event_loop_{}; diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 9f201691..899aaff5 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -27,8 +27,9 @@ // ------------------------- PLATFORM SELECTION -------------------------------- -// This ugly chunk of macros simply pulls in the correct platform class header -// for each platform and defines the actual class g_core->platform will be. +// This ugly chunk of macros simply pulls in the correct platform class +// header for each platform and defines the actual class g_core->platform +// will be. // Android --------------------------------------------------------------------- @@ -85,6 +86,7 @@ // A call that can be used by custom built native libraries (Python, etc.) // to forward along debug messages to us. +// // FIXME: Reconcile this with our existing C++ version. This one does not // require the engine to be spun up so it better suited for things like // debugging native libs. diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index d8ce5baf..133ae48c 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 = 21289; +const int kEngineBuildNumber = 21293; const char* kEngineVersion = "1.7.28"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/template_fs/README.md b/src/ballistica/template_fs/README.md index 161fff86..138d5097 100644 --- a/src/ballistica/template_fs/README.md +++ b/src/ballistica/template_fs/README.md @@ -1,4 +1,4 @@ -# Template Feature Set +# Template Fs Feature Set This is an empty feature-set for use as reference or as a starting point when implementing new ones. diff --git a/src/ballistica/template_fs/python/template_fs_python.cc b/src/ballistica/template_fs/python/template_fs_python.cc index bd5b2b13..d179bab7 100644 --- a/src/ballistica/template_fs/python/template_fs_python.cc +++ b/src/ballistica/template_fs/python/template_fs_python.cc @@ -9,16 +9,18 @@ namespace ballistica::template_fs { -// Declare a plain c PyInit_XXX function for our Python module; -// this is how Python inits our binary module (and by extension, our -// entire feature-set). +// Declare a plain C PyInit_XXX function for our Python module. This is how +// Python inits our binary module (and by extension, our entire +// feature-set). extern "C" auto PyInit__batemplatefs() -> PyObject* { auto* builder = new PythonModuleBuilder( "_batemplatefs", - // Native methods to add. + + // Our native methods. {PythonMethodsTemplateFs::GetMethods()}, - // Our module exec. Here we can add classes, import other modules, - // or whatever else (same as a regular Python script module). + + // Our module exec. Here we can add classes, import other modules, or + // whatever else (same as a regular Python script module). [](PyObject* module) -> int { BA_PYTHON_TRY; TemplateFsFeatureSet::OnModuleExec(module); @@ -38,14 +40,16 @@ void TemplateFsPython::ImportPythonObjs() { void TemplateFsPython::HelloWorld() { // Hold the GIL throughout this call so we can run in any thread. - // Alternately we could limit this function to the logic thread - // which always holds the GIL. In that case we'd want to - // stick a BA_PRECONDITION(InLogicThread()) here to be sure. + // Alternately, we could limit this function to the logic thread which + // always holds the GIL. In that case we'd want to stick a + // BA_PRECONDITION(InLogicThread()) here to so we'd raise an Exception if + // someone called us from another thread. auto gil{Python::ScopedInterpreterLock()}; - // Run the Python callable we grabbed. This will simply print any - // errors, but we could disable that print and look at the call - // results if any logic depended on this code running successfully. + // Run the Python callable we grabbed in our binding code. By default, + // this Call() will simply print any errors, but we could disable that + // print and look at the call results if any logic depended on this code + // running successfully. objs_.Get(ObjID::kHelloWorldCall).Call(); } diff --git a/tools/batools/spinoff/_main.py b/tools/batools/spinoff/_main.py index 5eb5aac4..ec394a4b 100644 --- a/tools/batools/spinoff/_main.py +++ b/tools/batools/spinoff/_main.py @@ -9,13 +9,17 @@ import sys import subprocess from enum import Enum from pathlib import Path -from typing import assert_never +from typing import assert_never, TYPE_CHECKING from efro.error import CleanError from efro.terminal import Clr from efrotools import replace_exact + from batools.spinoff._context import SpinoffContext +if TYPE_CHECKING: + from batools.featureset import FeatureSet + class Command(Enum): """Our top level commands.""" @@ -32,6 +36,8 @@ class Command(Enum): FEATURESETS = 'featuresets' CREATE = 'create' ADD_SUBMODULE_PARENT = 'add-submodule-parent' + FEATURE_SET_COPY = 'fset-copy' + FEATURE_SET_DELETE = 'fset-delete' def spinoff_main() -> None: @@ -101,6 +107,10 @@ def _main() -> None: public = getprojectconfig(Path(dst_root))['public'] _do_add_submodule_parent(dst_root, is_new=False, public=public) + elif cmd is Command.FEATURE_SET_COPY: + _do_featureset_copy() + elif cmd is Command.FEATURE_SET_DELETE: + _do_featureset_delete() else: assert_never(cmd) @@ -250,6 +260,251 @@ def _do_featuresets(dst_root: str) -> None: print(f' {Clr.BLU}{fset.name}{Clr.RST}') +def _fset_paths() -> list[str]: + """Given a feature-set, return all paths associated with it.""" + return [ + 'config/featuresets/featureset_$(NAME).py', + 'src/assets/ba_data/python/$(NAME_PY_PKG)', + 'src/ballistica/$(NAME)', + 'src/meta/$(NAME_PY_PKG_META)', + ] + + +def _filter_fset_path(path: str, fset: FeatureSet) -> str: + return ( + path.replace('$(NAME)', fset.name) + .replace('$(NAME_PY_PKG)', fset.name_python_package) + .replace('$(NAME_PY_PKG_META)', fset.name_python_package_meta) + ) + + +def _do_featureset_delete() -> None: + from batools.featureset import FeatureSet + + args = sys.argv[2:] + if len(args) != 1: + raise CleanError('Expected a featureset name.') + + name = args[0] + + # Just make a theoretical new featureset in case only parts of it + # exist. (custom name formatting shouldnt matter here anyway) + fset = FeatureSet(name) + + if not os.path.exists('config/featuresets'): + raise CleanError('Cannot run from this directory.') + + paths_to_delete: list[str] = [] + for path in _fset_paths(): + paths_to_delete.append(_filter_fset_path(path, fset)) + + print( + '\n' + '⎯' * 80 + f'\n{Clr.BLD}Deleting feature-set{Clr.RST}' + f' {Clr.SMAG}{Clr.BLD}{name}{Clr.RST}{Clr.BLD}...{Clr.RST}\n' + + '⎯' * 80 + + '\n' + ) + + found_something = False + for path in paths_to_delete: + if os.path.exists(path): + found_something = True + print(f' Deleting {Clr.MAG}{path}{Clr.RST}') + subprocess.run(['rm', '-rf', path], check=True) + if not found_something: + print( + f' {Clr.WHT}No feature-set components found;' + f' nothing to be done.{Clr.RST}' + ) + + print( + f"\n{Clr.GRN}{Clr.BLD}Job's done!{Clr.RST}\n" + f'{Clr.BLD}Next, run' + f' {Clr.BLU}`make update`{Clr.RST}{Clr.BLD} to update project' + f' files to reflect these changes.{Clr.RST}' + ) + + +def _do_featureset_copy() -> None: + # pylint: disable=too-many-locals + from efrotools import extract_flag + + from batools.featureset import FeatureSet + + args = sys.argv[2:] + + force = extract_flag(args, '--force') + + if len(args) != 2: + raise CleanError('Expected a src and dst featureset name.') + + src = args[0] + dst = args[1] + + if not os.path.exists('config/featuresets'): + raise CleanError('Cannot run from this directory.') + + # This will make sure both feature-set names are valid and give us + # name variations. Load src from the project to pick up custom title + # variations/etc. + fsets = {f.name: f for f in FeatureSet.get_all_for_project('.')} + if src not in fsets: + raise CleanError('src feature-set {src} not found.') + srcfs = fsets[src] + # Just go with defaults for dst. Note that this means any custom + # title forms in src's config script will get filtered to be setting + # the default form of dst, which is redundant. Maybe we could filter that + # out. + dstfs = FeatureSet(dst) + + # Make sure src *does* exist. + if not os.path.exists(f'config/featuresets/featureset_{src}.py'): + raise CleanError(f"Src feature-set '{src}' not found.") + + # Make sure dst does *not* exist (unless we're forcing). + if os.path.exists(f'config/featuresets/featureset_{dst}.py') and not force: + raise CleanError( + f"Dst feature-set '{dst}' already exists." + ' Use --force to blow it away.' + ) + + paths_to_copy: list[tuple[str, str]] = [] + for path in _fset_paths(): + paths_to_copy.append( + (_filter_fset_path(path, srcfs), _filter_fset_path(path, dstfs)) + ) + + # Replace variations of our name. Note that we don't have to include + # stuff like name_python_package_meta here because that is covered + # by our base name replacement. Also note that we include upper() + # for C/C++ header #ifndefs. + subs = [ + (srcfs.name, dstfs.name), + (srcfs.name_compact, dstfs.name_compact), + (srcfs.name_title, dstfs.name_title), + (srcfs.name_camel, dstfs.name_camel), + (srcfs.name.upper(), dstfs.name.upper()), + ] + + # Sanity check: we don't currently support renaming subdirs, so error + # if that would happen. + for srcpath, _dstpath in paths_to_copy: + for root, dirs, _fnames in os.walk(srcpath): + for dname in dirs: + if any(sub[0] in dname for sub in subs): + raise CleanError( + 'Directory name filtering is not supported' + f" (would filter '{root}/{dname}')." + ) + + # ------------------------------------------------------------------------ + # Ok, at this point we get started working and assume things will succeed. + # If anything fails at this point we should add a pre-check for it above. + print( + '\n' + '⎯' * 80 + f'\n{Clr.BLD}Copying feature-set{Clr.RST}' + f' {Clr.SMAG}{Clr.BLD}{src}{Clr.RST}' + f' {Clr.BLD}to{Clr.RST}' + f' {Clr.SMAG}{Clr.BLD}{dst}{Clr.RST}' + f'{Clr.BLD}...{Clr.RST}\n' + '⎯' * 80 + ) + + print(f'\n{Clr.BLD}Will filter the following text:{Clr.RST}') + for subsrc, subdst in subs: + print( + f' {Clr.MAG}{subsrc}{Clr.RST}' + f' {Clr.BLD}->{Clr.RST} {Clr.MAG}{subdst}{Clr.RST}' + ) + + print(f'\n{Clr.BLD}Copying/filtering files...{Clr.RST}') + + for srcpath, dstpath in paths_to_copy: + _do_featureset_copy_dir(srcpath, dstpath, subs, force) + + print( + f"\n{Clr.GRN}{Clr.BLD}Job's done!{Clr.RST}\n" + f'{Clr.BLD}Next, run' + f' {Clr.BLU}`make update`{Clr.RST}{Clr.BLD} to update project' + f' files to reflect these changes.{Clr.RST}' + ) + + +def _do_featureset_copy_dir( + srcpath: str, dstpath: str, subs: list[tuple[str, str]], force: bool +) -> None: + # pylint: disable=too-many-locals + # pylint: disable=too-many-branches + + # This feature-set might not have this component. No biggie. + if not os.path.exists(srcpath): + return + + if force: + subprocess.run(['rm', '-rf', dstpath], check=True) + + if not os.path.exists(srcpath): + raise CleanError(f'src path {srcpath} is not a dir.') + if os.path.exists(dstpath): + raise CleanError(f'dst path {srcpath} already exists.') + + filtered_exts = ['.cc', '.h', '.py', '.md', '.inc'] + + # Eww; reinventing the wheel here; should tap into existing + # spinoff logic or something. + cruft_names = ['.DS_Store', 'mgen', '_mgen'] + + # We currently just copy the full dir and then rename/filter + # individual files. If we need to filter subdir names at some point + # we'll need fancier code. + subprocess.run(['cp', '-r', srcpath, dstpath], check=True) + for root, dnames, fnames in os.walk(dstpath, topdown=True): + for dname in dnames: + if dname in cruft_names: + # Prevent us from recursing into it and blow it away. + dnames.remove(dname) + subprocess.run( + ['rm', '-rf', os.path.join(root, dname)], check=True + ) + for fname in fnames: + if fname in cruft_names: + os.unlink(os.path.join(root, fname)) + continue + fnamefilt = fname + for subsrc, subdst in subs: + fnamefilt = fnamefilt.replace(subsrc, subdst) + if fnamefilt != fname: + subprocess.run( + [ + 'mv', + os.path.join(root, fname), + os.path.join(root, fnamefilt), + ], + check=True, + ) + # Now filter contents. + if not any(fname.endswith(ext) for ext in filtered_exts): + print( + f'{Clr.YLW}WARNING:' + f' not filtering file with unrecognized extension:' + f" '{fname}'{Clr.RST}" + ) + continue + with open( + os.path.join(root, fnamefilt), encoding='utf-8' + ) as infile: + contents = infile.read() + for subsrc, subdst in subs: + contents = contents.replace(subsrc, subdst) + with open( + os.path.join(root, fnamefilt), 'w', encoding='utf-8' + ) as outfile: + outfile.write(contents) + + print( + f' {Clr.MAG}{srcpath}{Clr.RST} {Clr.BLD}->{Clr.RST}' + f' {Clr.MAG}{dstpath}{Clr.RST}' + ) + + def _do_override(src_root: str | None, dst_root: str) -> None: if src_root is None: raise CleanError('This only works on dst projects.') @@ -364,7 +619,14 @@ def _print_available_commands() -> None: ' The same can be\n' ' achieved by passing --submodule-parent to' ' the \'create\'\n' - ' command.' + ' command.\n' + f' {bgn}fset-copy [src, dst]{end} Copy feature-set src to dst.' + ' Replaces variations of src\n' + ' feature-set name with dst equivalent,' + ' though may need\n' + ' some manual correction afterwards to be' + ' functional.\n' + f' {bgn}fset-delete [name]{end} Delete a feature-set.' ), file=sys.stderr, )