diff --git a/.efrocachemap b/.efrocachemap index 73c67ba6..d4f85ea2 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3995,50 +3995,50 @@ "assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e", "assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34", "ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a", - "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/bc/d7/f65513a0b3d5fa8b5f4cfe342052", - "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/06/03/381b1b4470868245225dd4e4bb67", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/52/b0/36011f65de64a547009bef7fe6ec", - "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e2/46/384efcdbfb324b63889d567e7f14", - "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/27/7bab35742af8c101f4aab3ebbd3d", - "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/d0/c1ee5a02a09c1d676a22a20fe8a7", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e9/2e/3ae4fb2983bb36167d1fe3044358", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9e/93/12e8d5357db44c97549fe2fbd7ca", - "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/53/6d/06e5987cfcf47d740ead2e8c3b7f", - "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1b/5b/fd0fa7104c3e2d3a22c180db47cf", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/98/0e12c6aa44fc9e64492b6da67b02", - "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e6/81/aacab39968075487c9f610e3036c", - "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/94/bc/7a2fb10033217cac2fc04ccd0309", - "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a4/da/b7095762fbdef22aff5d77c5d45d", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8b/05/2051b310f7989aa9aa04339de2f3", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2d/6e/3befc00833dc482ef55b70a915fe", - "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/82/ce/77695599588b675fac1e10febeae", - "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/3a/c3/d042e59d36f71c885036ed0e73d4", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/87/48/b1551cd7b5cf44c06a3e3a1a66a4", - "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/6d/e2/3366be4f442e6ef48b5ef5babe87", - "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d6/9a/5e36fb51a797da74db8f366a8e6d", - "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/98/d7/e827cc644d4dde0c1bddb17121b0", - "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9b/e0/a1ae5fdadff116de78727e2a8d30", - "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/76/fd27d293fd73d324ecc433b1e253", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1e/93/b85c7585218b5c86d766a23373c6", - "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/34/0a/4547b5a8a00a35874cfbab25fc11", - "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/68/5a/a4a030424d8d5900433bdb70bd83", - "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e1/4b/278433e2332245313d4fa3390aee", - "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/22/c1/fc9726d56ac62c757e7f7eeb56e2", - "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/23/4f/ee03bed8d42b8e12832efbd17968", - "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/dc/7a/f5bae1a284d2b51e149f12da9e18", - "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2e/75/254680a01a85fcdc19db275cd2ff", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/79/fb/65e1f1851f2dc663eb5d9dd27ae9", - "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/5d/33ca757aa471e81ee2efe83b5c32", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/83/3e/35613478ab4887b7f77126c1d910", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ba/a7/b925b7eb7b2da10fdc9c88e59576", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/0a/ae/daac4e04ef473d0da419ebe2ddf2", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/52/9d/d6c5366d82b56d1c79553ae3258b", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/35/db/9f6868dde4184cb3b33153981548", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/0c/e0/a869977d8ee1dcf4bcbb0853fe3c", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/31/c1/e51379d7cf51aec768855e6e7eb7", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/0f/48/52683efe93f7311f2a81a280a87e", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7c/05/660a71d76685232d9bd91d979ef8", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/30/5a/b2e0ce3986936938c65e4bef2d0e", + "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b3/94/954559711625a0bb6a29d41c35ff", + "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/44/1e/7eec4d37c0c2afb581927efbb2dc", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/92/75/2d6b53a09ce1fe7cbe3b50d832de", + "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cf/eb/2f2119b7c86ecbfb2a8b725b21f6", + "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c2/95/be04f3219b078c92a787627b42b7", + "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/53/f5/3bd3ba9c59b87ff7b8abc4233844", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5e/21/a913309416dde18bc4c9104c3ec5", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/28/25/54314d17a5a12be51a3b340359bd", + "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/07/39/13923a2f10d8e2a8d1d12007cbed", + "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a7/86/8cfb723071ad46355af9166ba59a", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ad/73/6013ee6879fae3c3e06b15396fb0", + "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/da/8b/f33a546e2342591ab84ddcf4053e", + "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f8/b7/fffc4c52e906890b35c936441b60", + "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/27/24/2ca5005df08ea5a7a78cd20b020a", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cd/8d/c59fcc270b637990d12cd8a758e0", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/54/a9/78e4658fd4446898b11633191c01", + "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/53/30/cd915e748f31208239b90f5a7a2c", + "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a9/4e/c367a7a66eb17df7757dc85d59b6", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/5d/13/38ab5bdd9fdb0abca878d0bbfbab", + "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/02/62/c3a8023aa43567204407325d6c4c", + "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/23/17/f0d22a9a91a295ee4a9d94db2af9", + "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d5/4f/bfbcdd18679b4ba0df1b040e8c15", + "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/e9/0f4aa3d10f2d50fe670bb6d89ba0", + "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/26/ef/1b58d3c788441242ed9ca9352499", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/79/77/b35c5f93f62d92fe9cdb548f6e54", + "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c6/53/8e678b75f456378e17e4f639fe35", + "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ab/1d/b514c16cfda2f5840676a4c75ca3", + "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d7/c4/b797fd5365d34fcfb60d6275fb3c", + "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b0/6c/b8f8c1d1d8e2f772208af12e085f", + "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c0/e1/f57102a8da2fe0ca7787c0a7924a", + "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/16/19494ad899b3f1990dd2b5cdfa99", + "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a9/d5/59ba30f21d5583bdc92516c0916e", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e7/82/e29dbff494964b753a08eaa66b66", + "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/06/ec124d5bfce4d3f268b1318755b8", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/47/3322caea5f9a92735076c1017839", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f5/9f/ce9a27aea9a4b7056800cf68afe9", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/6c/56/6180e5d6db6868b7438751320faf", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/71/24/e873efab9c315ec907427bd22327", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/3d/d6/b1a3c97a66d3f5ad38d5f5dda476", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/5a/e9/14593b84a3035a1bd7ce3980818d", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/87/5c/38b4d59ad04f6ba89dae6ca38fa5", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/3c/0a/6b151c9054294db58cf98957a515", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e3/fb/7be0f96c82816af61849aaf4357a", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/23/dc/91a8e0f2123a75b4d72a0d26ab25", "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523", "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02", "src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7940d360..8f475bb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.7 (build 20862, api 7, 2022-09-17) +### 1.7.7 (build 20865, api 7, 2022-09-19) - Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread. - Improved logging of missing playlist game types. - Some ba.Lstr functionality can now be used in background threads. @@ -39,6 +39,8 @@ - If you want to grab recent logs, you can now use `ba.app.log_handler.get_cached()`. This will give you everything that has gone through Python logging, Python stdout/stderr, and the C++ Log() call (up to the max cache size that is). - LogHandler output now ALWAYS goes to stderr. Previously it only would if an interactive terminal was detected. This should make the binary easier to debug if run from scripts/etc. We can add a `--quiet` option if needed or whatnot. - (build 20859) Fixed an error setting up asyncio loops under Windows related to the fact that Python is now inited in the main thread. +- (build 20864) Fatal-error message/traceback now properly prints to stderr again (I think the reject logging rejiggering caused it to stop). +- (build 20864) Fixed an issue where the app could crash when connected to the cloud console while in a network game. ### 1.7.6 (build 20687, api 7, 2022-08-11) diff --git a/assets/src/ba_data/python/._ba_sources_hash b/assets/src/ba_data/python/._ba_sources_hash index 9d8ff3a2..4d3af060 100644 --- a/assets/src/ba_data/python/._ba_sources_hash +++ b/assets/src/ba_data/python/._ba_sources_hash @@ -1 +1 @@ -84155513838284412598729205538639495871 \ No newline at end of file +137071025041513581787580065580079045765 \ No newline at end of file diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py index 602e6e59..936ce53b 100644 --- a/assets/src/ba_data/python/ba/_bootstrap.py +++ b/assets/src/ba_data/python/ba/_bootstrap.py @@ -44,7 +44,7 @@ def bootstrap() -> None: # Give a soft warning if we're being used with a different binary # version than we expect. - expected_build = 20862 + expected_build = 20865 running_build: int = env['build_number'] if running_build != expected_build: print( diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index cdc2facb..912ec63b 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -32,7 +32,7 @@ namespace ballistica { // These are set automatically via script; don't modify them here. -const int kAppBuildNumber = 20862; +const int kAppBuildNumber = 20865; const char* kAppVersion = "1.7.7"; // Our standalone globals. diff --git a/src/ballistica/core/fatal_error.cc b/src/ballistica/core/fatal_error.cc index 7dc75d06..9df548db 100644 --- a/src/ballistica/core/fatal_error.cc +++ b/src/ballistica/core/fatal_error.cc @@ -27,7 +27,7 @@ auto FatalError::ReportFatalError(const std::string& message, // error to the user and exiting the app cleanly (so we don't pollute our // crash records with results of user tinkering). - // Give the platform the opportunity to completely override our handling. + // Give the platform the opportunity to augment or override our handling. if (g_platform) { auto handled = g_platform->ReportFatalError(message, in_top_level_exception_handler); @@ -38,7 +38,8 @@ auto FatalError::ReportFatalError(const std::string& message, std::string dialog_msg = message; if (!dialog_msg.empty()) { - dialog_msg += "\n"; + // Why was this here? + // dialog_msg += "\n"; } auto starttime = time(nullptr); @@ -75,9 +76,10 @@ auto FatalError::ReportFatalError(const std::string& message, g_early_v1_cloud_log_writes = 0; // Add this to our V1CloudLog which we'll be attempting to send momentarily, - // and also try to present it directly to the user. + // and also go to platform-specific logging and good ol' stderr. Logging::V1CloudLog(logmsg); Logging::DisplayLog("root", LogLevel::kCritical, logmsg); + fprintf(stderr, "%s\n", logmsg.c_str()); std::string prefix = "FATAL-ERROR-LOG:"; std::string suffix; diff --git a/src/ballistica/logic/logic.cc b/src/ballistica/logic/logic.cc index 5c5acdb5..ab03be30 100644 --- a/src/ballistica/logic/logic.cc +++ b/src/ballistica/logic/logic.cc @@ -1050,15 +1050,10 @@ void Logic::PushPythonRawCallable(PyObject* callable, bool fg_context) { thread()->PushCall([this, callable, fg_context] { assert(InLogicThread()); - // Lets run this in the UI context. - // (can add other options if we need later) + // Run this in the UI context by default, or foreground if requested. ScopedSetContext cp(fg_context ? GetForegroundContext() : GetUIContext()); - // This event contains a raw python obj with an incremented ref-count. - auto call(Object::New(callable)); - Py_DECREF(callable); // now just held by call - - call->Run(); + PythonRef(callable, PythonRef::kSteal).Call(); }); } diff --git a/src/ballistica/logic/logic.h b/src/ballistica/logic/logic.h index 5bf933e6..4de58019 100644 --- a/src/ballistica/logic/logic.h +++ b/src/ballistica/logic/logic.h @@ -83,8 +83,8 @@ class Logic { auto PushPythonWeakCallArgs(const Object::WeakRef& call, const PythonRef& args) -> void; - // Push a raw Python call, decrements its refcount after running. - // Can be pushed from any thread. + // Push a raw Python call from any thread. Refcount should be incremented + // beforehand and will be decremented after running. auto PushPythonRawCallable(PyObject* callable, bool fg_context = false) -> void; auto PushScreenMessage(const std::string& message, const Vector3f& color) diff --git a/src/ballistica/python/class/python_class_context_call.cc b/src/ballistica/python/class/python_class_context_call.cc index 81cfd2cb..01d7c50b 100644 --- a/src/ballistica/python/class/python_class_context_call.cc +++ b/src/ballistica/python/class/python_class_context_call.cc @@ -102,7 +102,7 @@ auto PythonClassContextCall::tp_new(PyTypeObject* type, PyObject* args, if (!PyArg_ParseTuple(args, "O", &source_obj)) return nullptr; if (!InLogicThread()) { throw Exception( - "ERROR: " + std::string(type_obj.tp_name) + std::string(type_obj.tp_name) + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } diff --git a/src/ballistica/python/python_context_call.cc b/src/ballistica/python/python_context_call.cc index 15fa1327..938e2dd1 100644 --- a/src/ballistica/python/python_context_call.cc +++ b/src/ballistica/python/python_context_call.cc @@ -14,17 +14,17 @@ PythonContextCall* PythonContextCall::current_call_ = nullptr; PythonContextCall::PythonContextCall(PyObject* obj_in) { assert(InLogicThread()); - // as a sanity test, store the current context ptr just to make sure it - // hasn't changed when we run + // As a sanity test, store the current context ptr just to make sure it + // hasn't changed when we run. #if BA_DEBUG_BUILD context_target_sanity_test_ = context_.target.get(); #endif // BA_DEBUG_BUILD BA_PRECONDITION(PyCallable_Check(obj_in)); object_.Acquire(obj_in); GetTrace(); - // ok now we need to register this call with whatever the context is; + // Ok now we need to register this call with whatever the context is; // it can be stored in a host-activity, a host-session, or the UI context. - // whoever it is registered with will explicitly release its contents on + // Whoever it is registered with will explicitly release its contents on // shutdown and ensure that nothing gets run after that point. if (HostActivity* ha = context_.GetHostActivity()) { ha->RegisterCall(this); diff --git a/tests/test_efro/test_message.py b/tests/test_efro/test_message.py index 185ca140..3d04624b 100644 --- a/tests/test_efro/test_message.py +++ b/tests/test_efro/test_message.py @@ -18,7 +18,7 @@ from efro.dataclassio import ioprepped from efro.message import (Message, Response, MessageProtocol, MessageSender, BoundMessageSender, MessageReceiver, BoundMessageReceiver, UnregisteredMessageIDError, - EmptyResponse) + EmptySysResponse) if TYPE_CHECKING: from typing import Any, Callable, Awaitable @@ -424,8 +424,8 @@ TEST_PROTOCOL = MessageProtocol( 0: _TResp1, 1: _TResp2, }, - receiver_returns_stack_traces=True, - receiver_logs_exceptions=False, + forward_clean_errors=True, + remote_errors_include_stack_traces=True, ) # Represents an 'evolved' TEST_PROTOCOL (one extra message type added). @@ -441,8 +441,8 @@ TEST_PROTOCOL_B = MessageProtocol( 0: _TResp1, 1: _TResp2, }, - receiver_returns_stack_traces=True, - receiver_logs_exceptions=False, + forward_clean_errors=True, + remote_errors_include_stack_traces=True, ) TEST_PROTOCOL_SINGLE = MessageProtocol( @@ -452,8 +452,7 @@ TEST_PROTOCOL_SINGLE = MessageProtocol( response_types={ 0: _TResp1, }, - receiver_returns_stack_traces=True, - receiver_logs_exceptions=False, + remote_errors_include_stack_traces=True, ) @@ -463,12 +462,16 @@ def test_protocol_creation() -> None: # This should fail because _TMsg1 can return _TResp1 which # is not given an id here. with pytest.raises(ValueError): - _protocol = MessageProtocol(message_types={0: _TMsg1}, - response_types={0: _TResp2}) + _protocol = MessageProtocol( + message_types={0: _TMsg1}, + response_types={0: _TResp2}, + ) # Now it should work. - _protocol = MessageProtocol(message_types={0: _TMsg1}, - response_types={0: _TResp1}) + _protocol = MessageProtocol( + message_types={0: _TMsg1}, + response_types={0: _TResp1}, + ) def test_sender_module_single_emb() -> None: @@ -777,7 +780,7 @@ def test_full_pipeline() -> None: # Emulate forwarding unregistered messages on to some # other handler... response_dict = self.msg.protocol.response_to_dict( - EmptyResponse()) + EmptySysResponse()) return self.msg.protocol.encode_dict(response_dict) raise @@ -909,7 +912,8 @@ def test_full_pipeline() -> None: assert response3 is None assert isinstance(response4, _TResp1) - # Remote CleanErrors should come across locally as the same. + # Remote CleanErrors should come across locally as the same + # (provided our protocol has enabled support for them). try: _response5 = obj.msg.send(_TMsg1(ival=1)) except Exception as exc: diff --git a/tools/efro/message/__init__.py b/tools/efro/message/__init__.py index 11a82fd3..bce8da5b 100644 --- a/tools/efro/message/__init__.py +++ b/tools/efro/message/__init__.py @@ -11,15 +11,16 @@ from efro.message._protocol import MessageProtocol from efro.message._sender import (MessageSender, BoundMessageSender) from efro.message._receiver import (MessageReceiver, BoundMessageReceiver) from efro.message._module import (create_sender_module, create_receiver_module) -from efro.message._message import (Message, Response, EmptyResponse, - ErrorResponse, StringResponse, BoolResponse, - UnregisteredMessageIDError) +from efro.message._message import (Message, Response, EmptySysResponse, + ErrorSysResponse, StringResponse, + BoolResponse, UnregisteredMessageIDError) __all__ = [ - 'Message', 'Response', 'EmptyResponse', 'ErrorResponse', 'StringResponse', - 'BoolResponse', 'MessageProtocol', 'MessageSender', 'BoundMessageSender', - 'MessageReceiver', 'BoundMessageReceiver', 'create_sender_module', - 'create_receiver_module', 'UnregisteredMessageIDError' + 'Message', 'Response', 'EmptySysResponse', 'ErrorSysResponse', + 'StringResponse', 'BoolResponse', 'MessageProtocol', 'MessageSender', + 'BoundMessageSender', 'MessageReceiver', 'BoundMessageReceiver', + 'create_sender_module', 'create_receiver_module', + 'UnregisteredMessageIDError' ] # Have these things present themselves cleanly as 'thismodule.SomeClass' diff --git a/tools/efro/message/_message.py b/tools/efro/message/_message.py index adc2d190..c2bc8c17 100644 --- a/tools/efro/message/_message.py +++ b/tools/efro/message/_message.py @@ -27,12 +27,12 @@ class Message: def get_response_types(cls) -> list[type[Response]]: """Return all message types this Message can result in when sent. - The default implementation specifies EmptyResponse, so messages with + The default implementation specifies EmptySysResponse, so messages with no particular response needs can leave this untouched. Note that ErrorMessage is handled as a special case and does not need to be specified here. """ - return [EmptyResponse] + return [EmptySysResponse] class Response: @@ -44,7 +44,7 @@ class Response: @ioprepped @dataclass -class ErrorResponse(Response): +class ErrorSysResponse(Response): """Response saying some error has occurred for the send. This type is unique in that it is not returned to the user; it @@ -64,12 +64,12 @@ class ErrorResponse(Response): @ioprepped @dataclass -class EmptyResponse(Response): +class EmptySysResponse(Response): """The response equivalent of None.""" # TODO: could allow handlers to deal in raw values for these -# types similar to how we allow None in place of EmptyResponse. +# types similar to how we allow None in place of EmptySysResponse. # Though not sure if they are widely used enough to warrant the # extra code complexity. @ioprepped diff --git a/tools/efro/message/_protocol.py b/tools/efro/message/_protocol.py index fc7d3d03..5f3b0d80 100644 --- a/tools/efro/message/_protocol.py +++ b/tools/efro/message/_protocol.py @@ -14,8 +14,9 @@ import json from efro.error import CleanError from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict, dataclass_from_dict) -from efro.message._message import (Message, Response, ErrorResponse, - EmptyResponse, UnregisteredMessageIDError) +from efro.message._message import (Message, Response, ErrorSysResponse, + EmptySysResponse, + UnregisteredMessageIDError) if TYPE_CHECKING: from typing import Any, Literal @@ -33,29 +34,20 @@ class MessageProtocol: def __init__(self, message_types: dict[int, type[Message]], response_types: dict[int, type[Response]], - preserve_clean_errors: bool = True, - receiver_logs_exceptions: bool = True, - receiver_returns_stack_traces: bool = False) -> None: + forward_clean_errors: bool = False, + remote_errors_include_stack_traces: bool = False) -> None: """Create a protocol with a given configuration. Note that common response types are automatically registered with (unchanging negative ids) so they don't need to be passed explicitly (but can be if a different id is desired). - If 'preserve_clean_errors' is True, efro.error.CleanError + If 'forward_clean_errors' is True, efro.error.CleanError exceptions raised on the receiver end will result in a matching CleanError raised back on the sender. All other Exception types come across as efro.error.RemoteError. - When 'receiver_logs_exceptions' is True, any uncaught Exceptions - on the receiver end will be logged there via logging.exception() - (in addition to the usual behavior of returning an ErrorResponse - to the sender). This is good to leave enabled if your - intention is to never return ErrorResponses. Looser setups - making routine use of CleanErrors or whatnot may want to - disable this, however. - - If 'receiver_returns_stack_traces' is True, stringified stack + If 'remote_errors_include_stack_traces' is True, stringified stack traces will be returned to the sender for exceptions occurring on the receiver end. This can make debugging easier but should only be used when the client is trusted to see such info. @@ -88,15 +80,20 @@ class MessageProtocol: # Go ahead and auto-register a few common response types # if the user has not done so explicitly. Use unique negative # IDs which will never change or overlap with user ids. - def _reg_if_not(reg_tp: type[Response], reg_id: int) -> None: + def _reg_sys(reg_tp: type[Response], reg_id: int) -> None: + + # If we have a positive id registered already, we still point + # negative sys id at this type but not the opposite. if reg_tp in self.response_ids_by_type: + self.response_types_by_id[reg_id] = reg_tp return + assert self.response_types_by_id.get(reg_id) is None self.response_types_by_id[reg_id] = reg_tp self.response_ids_by_type[reg_tp] = reg_id - _reg_if_not(ErrorResponse, -1) - _reg_if_not(EmptyResponse, -2) + _reg_sys(ErrorSysResponse, -1) + _reg_sys(EmptySysResponse, -2) # Some extra-thorough validation in debug mode. if __debug__: @@ -127,9 +124,9 @@ class MessageProtocol: 'message_types contains duplicate __name__s;' ' all types are required to have unique names.') - self.preserve_clean_errors = preserve_clean_errors - self.receiver_logs_exceptions = receiver_logs_exceptions - self.receiver_returns_stack_traces = receiver_returns_stack_traces + self.forward_clean_errors = forward_clean_errors + self.remote_errors_include_stack_traces = ( + remote_errors_include_stack_traces) @staticmethod def encode_dict(obj: dict) -> str: @@ -147,21 +144,20 @@ class MessageProtocol: def error_to_response(self, exc: Exception) -> Response: """Translate an error to a response.""" - # Log any errors we got during handling if so desired. - if self.receiver_logs_exceptions: - logging.exception('Error in efro.message handling.') + # Log any errors we got during handling. + logging.exception('Error in efro.message handling.') - # If anything goes wrong, return a ErrorResponse instead. + # If anything goes wrong, return a ErrorSysResponse instead. # (either CLEAN or generic REMOTE) - if isinstance(exc, CleanError) and self.preserve_clean_errors: - return ErrorResponse( + if isinstance(exc, CleanError) and self.forward_clean_errors: + return ErrorSysResponse( error_message=str(exc), - error_type=ErrorResponse.ErrorType.REMOTE_CLEAN) - return ErrorResponse( + error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN) + return ErrorSysResponse( error_message=(traceback.format_exc() - if self.receiver_returns_stack_traces else + if self.remote_errors_include_stack_traces else 'An internal error has occurred.'), - error_type=ErrorResponse.ErrorType.REMOTE) + error_type=ErrorSysResponse.ErrorType.REMOTE) def _to_dict(self, message: Any, ids_by_type: dict[type, int], opname: str) -> dict: @@ -236,7 +232,7 @@ class MessageProtocol: rsptypes.append(Response) for rsp_tp in rsptypes: # Skip these as they don't actually show up in code. - if rsp_tp is EmptyResponse or rsp_tp is ErrorResponse: + if rsp_tp is EmptySysResponse or rsp_tp is ErrorSysResponse: continue if (single_message_type and part == 'sender' and rsp_tp is not Response): @@ -344,9 +340,9 @@ class MessageProtocol: f' """Protocol-specific bound sender."""\n') def _filt_tp_name(rtype: type[Response]) -> str: - # We accept None to equal EmptyResponse so reflect that + # We accept None to equal EmptySysResponse so reflect that # in the type annotation. - return 'None' if rtype is EmptyResponse else rtype.__name__ + return 'None' if rtype is EmptySysResponse else rtype.__name__ # Define handler() overloads for all registered message types. if msgtypes: @@ -441,9 +437,9 @@ class MessageProtocol: # Define handler() overloads for all registered message types. def _filt_tp_name(rtype: type[Response]) -> str: - # We accept None to equal EmptyResponse so reflect that + # We accept None to equal EmptySysResponse so reflect that # in the type annotation. - return 'None' if rtype is EmptyResponse else rtype.__name__ + return 'None' if rtype is EmptySysResponse else rtype.__name__ if msgtypes: cbgn = 'Awaitable[' if is_async else '' diff --git a/tools/efro/message/_receiver.py b/tools/efro/message/_receiver.py index f45b2556..b277ff4c 100644 --- a/tools/efro/message/_receiver.py +++ b/tools/efro/message/_receiver.py @@ -11,8 +11,9 @@ import inspect import logging from typing import TYPE_CHECKING -from efro.message._message import (Message, Response, EmptyResponse, - ErrorResponse, UnregisteredMessageIDError) +from efro.message._message import (Message, Response, EmptySysResponse, + ErrorSysResponse, + UnregisteredMessageIDError) if TYPE_CHECKING: from typing import Any, Callable, Awaitable @@ -123,8 +124,8 @@ class MessageReceiver: # types.UnionType above. responsetypes = (ret, ) # type: ignore - # Return type of None translates to EmptyResponse. - responsetypes = tuple(EmptyResponse if r is type(None) else r + # Return type of None translates to EmptySysResponse. + responsetypes = tuple(EmptySysResponse if r is type(None) else r for r in responsetypes) # noqa # Make sure our protocol has this message type registered and our @@ -236,13 +237,13 @@ class MessageReceiver: response: Response | None) -> str: """Encode a response provided by the user for sending.""" - # A return value of None equals EmptyResponse. + # A return value of None equals EmptySysResponse. if response is None: - response = EmptyResponse() + response = EmptySysResponse() assert isinstance(response, Response) # (user should never explicitly return error-responses) - assert not isinstance(response, ErrorResponse) + assert not isinstance(response, ErrorSysResponse) assert type(response) in message.get_response_types() response_dict = self.protocol.response_to_dict(response) if self._encode_filter_call is not None: diff --git a/tools/efro/message/_sender.py b/tools/efro/message/_sender.py index c9d343d2..b176f7f7 100644 --- a/tools/efro/message/_sender.py +++ b/tools/efro/message/_sender.py @@ -10,7 +10,7 @@ import logging from typing import TYPE_CHECKING from efro.error import CleanError, RemoteError, CommunicationError -from efro.message._message import EmptyResponse, ErrorResponse +from efro.message._message import EmptySysResponse, ErrorSysResponse if TYPE_CHECKING: from typing import Any, Callable, Awaitable @@ -139,12 +139,12 @@ class MessageSender: except Exception as exc: # Any error in the raw send call gets recorded as either # a local or communication error. - return ErrorResponse( + return ErrorSysResponse( error_message= f'Error in MessageSender @send_method ({type(exc)}): {exc}', - error_type=(ErrorResponse.ErrorType.COMMUNICATION + error_type=(ErrorSysResponse.ErrorType.COMMUNICATION if isinstance(exc, CommunicationError) else - ErrorResponse.ErrorType.LOCAL)) + ErrorSysResponse.ErrorType.LOCAL)) return self._decode_raw_response(bound_obj, message, response_encoded) async def send_split_part_1_async(self, bound_obj: Any, @@ -166,13 +166,13 @@ class MessageSender: except Exception as exc: # Any error in the raw send call gets recorded as either # a local or communication error. - return ErrorResponse( + return ErrorSysResponse( error_message= f'Error in MessageSender @send_async_method ({type(exc)}):' f' {exc}', - error_type=(ErrorResponse.ErrorType.COMMUNICATION + error_type=(ErrorSysResponse.ErrorType.COMMUNICATION if isinstance(exc, CommunicationError) else - ErrorResponse.ErrorType.LOCAL)) + ErrorSysResponse.ErrorType.LOCAL)) return self._decode_raw_response(bound_obj, message, response_encoded) def send_split_part_2(self, message: Message, @@ -214,13 +214,13 @@ class MessageSender: # If we got to this point, we successfully communicated # with the other end so errors represent protocol mismatches # or other invalid data. For now let's just log it but perhaps - # we'd want to somehow embed it in the ErrorResponse to be + # we'd want to somehow embed it in the ErrorSysResponse to be # available directly to the user later. logging.exception('Error decoding raw response') - response = ErrorResponse( + response = ErrorSysResponse( error_message= 'Error decoding raw response; see log for details.', - error_type=ErrorResponse.ErrorType.LOCAL) + error_type=ErrorSysResponse.ErrorType.LOCAL) return response def _unpack_raw_response(self, raw_response: Response) -> Response | None: @@ -232,25 +232,25 @@ class MessageSender: run such that any raised Exception is active when the callback fires; not on the thread where the message was sent. """ - # EmptyResponse translates to None - if isinstance(raw_response, EmptyResponse): + # EmptySysResponse translates to None + if isinstance(raw_response, EmptySysResponse): return None # Some error occurred. Raise a local Exception for it. - if isinstance(raw_response, ErrorResponse): + if isinstance(raw_response, ErrorSysResponse): if (raw_response.error_type is - ErrorResponse.ErrorType.COMMUNICATION): + ErrorSysResponse.ErrorType.COMMUNICATION): raise CommunicationError(raw_response.error_message) # If something went wrong on *our* end of the connection, # don't say it was a remote error. - if raw_response.error_type is ErrorResponse.ErrorType.LOCAL: + if raw_response.error_type is ErrorSysResponse.ErrorType.LOCAL: raise RuntimeError(raw_response.error_message) # If they want to support clean errors, do those. - if (self.protocol.preserve_clean_errors and raw_response.error_type - is ErrorResponse.ErrorType.REMOTE_CLEAN): + if (self.protocol.forward_clean_errors and raw_response.error_type + is ErrorSysResponse.ErrorType.REMOTE_CLEAN): raise CleanError(raw_response.error_message) # Everything else gets lumped in as a remote error.