diff --git a/.efrocachemap b/.efrocachemap index a1eef795..36dffc11 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -420,18 +420,18 @@ "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/dc/d2/160fc27fcaff10793327ac2c70fd", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/11/7a/87d6bca0acfb877fd4fd8fe3a598", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/44/f5/c943c9075abb3e1835d2408a1ef8", - "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/c4/e7/432f66c84194bd6d04ecc485e308", - "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/4c/fd/67a9dcdecb85dbaf549a8fbbdc78", + "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/9d/0a/e98c8e794fe9cd741b5f8e5642c7", + "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/db/6a/02249b1d9bdc66f603a33e91d118", "assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503", - "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/32/a7/b9208ab804dfe2d1c3960256e9f8", + "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/18/7f/03acf6a5fcc9d3958f9746eadaf4", "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/61/e6/caf06ce99017fdf5d2da0c038445", "assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/f5/d4/683ef2d47b669e5f177bc1ae4ff0", "assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/87/84/9f3d39610453b3bf350698a23316", "assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28", "assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/68/93/da8e9874f41a786edf52ba4ccaad", - "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/b9/72/96f5843dffe76fd16fa691845497", + "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/2c/41/2c43429b95bef4e88fc279071470", "assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb", - "assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/f8/ac/ade62ff2a8c13550f56c918d55fb", + "assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/4b/d5/7c877f9550c0154c52f410b93315", "assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/b6/e0/37dd30b686f475733ccc4b3cab49", "assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/c0/26/04875251b9237f3133c84a910afd", "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/6b/f3/5ca25b080c7f04403ffd1c918787", @@ -439,22 +439,22 @@ "assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/08/3b/68cea4d16f7020d932829af85323", "assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/b0/48/e1ebe08bfdfc94fcb61a16b851e5", "assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/75/70/e33e6ee95830052e8f36cd2135f7", - "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/94/2d/4f17fc4b73260e99453ee3122c0c", + "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/f3/11/d81cc9f0c76b81b0b8dcf64fe292", "assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/07/37/ab65ccee3a555bd40e9661860c58", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/02/ab/e310f81582b6dc2ae93348d45166", "assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/76/be/84e567de0aabd9f9145b62179c2c", - "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/e2/cd/8738b4ac572fc3f98714a4a4be67", - "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/c9/5c/ccbae41f2baa11599563b4aabe00", - "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/8c/a7/1dea3643720ced9fa40a0b3d1add", + "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/fa/21/62f7d0885c1ddf8cbdaf13a46685", + "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/20/35/19ab623ce299a61e325f2ac5354b", + "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/92/a7/a6d22213dd30f5f57919d6cc2e94", "assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/13/19/828be486951be254445263f36c6e", "assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/f9/4b/d9f01814224066856695452ef57c", - "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/b3/a3/3e1befdc621a503219fa78ba6d8b", + "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/a3/20/bf03c91445f7083378d55a476415", "assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/91/0a/35c4baf539d5951fc03a794c0e0b", - "assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/75/b3/c344f382251cb7e696bedb712421", + "assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/9a/7f/39c9ac42c22776a65e59503b189e", "assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/9d/51/f699dbd4beb88bc3cff699a287a7", - "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/6f/2e/20bed8fefb23ca42c18a7b6c75b2", + "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/d3/41/bef1112708d7ee012c7187967dcd", "assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/7f/bb/6239adeb551be5e09f3457d7b411", - "assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/d9/97/e3ec72c08fcaeb2f69569c20f085", + "assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/68/77/cc871b6ed611cb8d6f74cf1a9d00", "assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/f2/af/afd1503c7a10cacaa15bc02369b2", "assets/build/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/47/0a/a617cc85d927b576c4e6fc1091ed", "assets/build/ba_data/data/maps/bridgit.json": "https://files.ballistica.net/cache/ba1/03/4b/57ee9b42854b26f23f81bd8c58ef", @@ -3992,50 +3992,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/14/32/34006cd68ca39d27add8c36839a9", - "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f9/27/943498c90fd48490581721549831", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/99/ee/a7e516ce3d295c32a9fae1f61837", - "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/1c/c84d7e2f9108787614e8fa47b178", - "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/66/ca/89977fb3b87de266a9da81655879", - "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/50/ed/fa670d5646074507df2bd11cc9f3", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5d/e1/7dde73d49d729d4ad722d7042821", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3a/57/a994445b5ba27e8b21df9218f05f", - "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3b/db/742462576b3e78cc42cbe8c15b4d", - "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/98/52/3dd887570176346b2b0a9c40658e", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ad/26/9bc36871955e6bafc5a5c6f21e74", - "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5e/94/f021148e84ed3ba2997c51c701bf", - "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/2b/11/63ecf6112d1a3ea71ee31c192d32", - "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ae/5a/bc6c4c10a64fb230edb1d16ec0e7", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/76/ce/ba8b95c2ea1fbef5ee6fc22fc421", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bf/b3/6b576d8587719ec0f9dcdc851613", - "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a6/25/6c769b43530cc0f5870fde6b01f4", - "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4f/b2/915fa6ee32e67839fdab20b20e9f", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/54/a0/02d658b28cee638eb2587e5800af", - "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/e7/56/3aa98841dab173ddfe3e4c7dd19e", - "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/60/5fb1cb5bd1ee656517914df2eb26", - "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/95/92/1bac3bcac3e1f71417b7ff053b52", - "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/55/b16bb20de7c3ab30311e73707e17", - "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6d/79/9b588be3e8d74c8c8bd761899be5", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e2/17/168f29b83ee04a412c0fa402258b", - "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a5/60/8d865d088dbc8d34d739b2c16fee", - "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/38/4d/101a78df34eada8ffa73636cd178", - "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/4b/4f8648af8ea56398da8b56babffa", - "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3a/08/e5955806784ca0dbc2e9e679136a", - "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e2/7c/6d600a764587eb8d74b9701fe73f", - "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/96/4f/a761ffd6cbcc9d19015e8a2ebbd2", - "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4a/18/c94f7cf05127e82cd71f46dc7dd6", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/39/d1/0bd6a2cf0f447642d8e58b538243", - "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bc/c0/1230a5a3ff3786fc335b57800324", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b5/7f/0e2c1cf8683876727192587148ac", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fe/04/375a70cb1510e2ec904a8d413a84", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/09/77/21bbba5f4a88b3534e133dc156d5", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/72/95/fc9be2f3b29b4bcc19079269d4b3", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/19/d8/d2728c0e48b220b7c20e2690ecae", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/07/d6/d4305dab6a066c0a1459efcec85e", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/75/26/dc45c1c15b7727a013fd98ce1796", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/ee/39/9498eb5c1103b27df3454b52ba98", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/61/b1/3ab8780ac812d4cdedf19f504aaa", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/43/90/175c959c383fe983a614f92b6dec", + "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/70/3b/0c7d541b3856884a747ff4606125", + "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/af/8b/9191ae1c05ed11d77bf9099abdaf", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8f/88/e96a5cf5728280557a229c3cd196", + "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/40/8c/e3932b01bfb03bf3d930fa6b0075", + "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4e/dd/cceaa79ecc33d116254129115ab6", + "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bc/d5/2992911e42d4019062a5ee7922be", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2d/75/b81d14e56fac5cb36204b99d438b", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bd/35/af73fd25f90b260a7e1f2b45604a", + "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a4/b9/2d9d7fdf7f8fc36a395396c17396", + "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ac/ec/cbd0cd6b30ffc5e9899df476ff75", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fb/db/28557fffa11dc30193dfdf2bc1cc", + "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cb/34/83e11c5a79dcb4a925b05a65741c", + "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d5/12/cd06cac7c7bb070397da839850df", + "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/da/87/5df008ea5c203eb4a97bfa9648fe", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/93/ca/2afe9fc10c37c16a91a556350be0", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/19/cc/c1bb1e323387f590c3ad649cf2b1", + "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a0/31/1f941c66e9290a4ae69f5f4b0aa4", + "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/74/15/89bb4aa1472f0848808b215fbab6", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/25/a3/4c7334e53c11997b7cf292c96da8", + "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/64/e0/f9f926a34c2e0243419b20a28138", + "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/11/a6/7db8addcd876b1933e16f380696a", + "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c7/8a/3d8501efecca4f20b9702d24f9db", + "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/fb/8ba0b3bddf4e0f1184a68e648959", + "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/ab/393d381b77a85cdbba6c47d8a67a", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/da/23/026d70f770273a92def633d57935", + "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/e8/37eeb762bbae9daffae6d5d69b7e", + "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3a/6b/0e26e2ab6eab60258ddcab0601fd", + "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/70/38/7ff58456d824e3461c6a1a3f3f5a", + "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/10/39/9501e3afd9a1a0af817989e18c7d", + "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/42/f6/e8943073f00d93b2e2cd1822916a", + "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/67/82/220d9a12ed10c885aadc02b9919b", + "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/c5/dc64be0a0f9d09630074e630312b", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f2/5f/e75027e1f6f31fae1f1d20a8b2ee", + "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/11/18a94007cbcaf2e10c46fc367f7a", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/51/a8/414d1a0ad00982cd961f0a29a501", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/0c/26593bd6f6191a19531ce1dd492b", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/62/9e/e8679467880812a2a31266704517", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/56/f1/af68b9666c2ce5038b813719c458", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/d2/a9/9087249ba5a8873f59503a7cb3e4", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/fd/3f/57f64be3b335dbbe0f261edf0861", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/8b/cf/cc26378a8800b0efd14455e7f6b5", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/64/34/18f21d11fda7f1472e0212169904", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/24/71/93680835ec6af90acf9d4e2fd63f", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/af/ae/86b7f88249a684530c6d30831889", "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/6e/6f/004b696e9a13b083069374e4bb6a", "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a8793f..7e2075e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.3 (20627, 2022-07-01) +### 1.7.3 (20629, 2022-07-05) - Fixed an issue with King of the Hill flag regions not working when players entered them (Thanks itsre3!) - Fixed an issue in Chosen One where the flag resetting on top of a player would not cause them to become the chosen one (Thanks Dliwk!) - Fixed an issue where triple-bomb powerup would not flash before wearing off (Thanks Juleskie!). @@ -7,6 +7,14 @@ - The app now issues a gentle notice if plugins are removed instead of erroring and continuing to look for them on subsequent launches. This makes things much smoother when switching between workspaces or users. - Added new translation entries for Workspace/Plugin stuff. - tools/bacloud workspace get/put commands are now functional (wiki page with instructions coming soon). +- `_ba.android_get_external_storage_path` is now `_ba.android_get_external_files_dir` which maps to the actual call it makes under the hood these days. +- Android logging now breaks up long entries such as stack-traces into multiple log entries so they should not get truncated. +- The app now issues a warning if device time varies significantly from actual world time. This can lead to things like the app incorrectly treating SSL certificates as not yet valid and network functionality failing. +- The app now issues a warning if unable to establish secure connections to cloud servers (which can be due to aforementioned issue, but could also stem from other network problems). +- The Network Testing utility (Settings->Advanced->Network Testing) now tests for more potential issues including ones mentioned above. +- The Android version now stores files such as extracted assets and audio caches in the non-backed-up files dir (Android's Context.getNoBackupFilesDir()). These files can always be recreated by the app so they don't need backups, and this makes it more likely that Android will back up what's left in the regular files dir (the app config, etc). + + ### 1.7.2 (20620, 2022-06-25) - Minor fixes in some minigames (Thanks Droopy!) diff --git a/assets/src/ba_data/python/ba/_asyncio.py b/assets/src/ba_data/python/ba/_asyncio.py index e10333bf..2705d284 100644 --- a/assets/src/ba_data/python/ba/_asyncio.py +++ b/assets/src/ba_data/python/ba/_asyncio.py @@ -11,6 +11,8 @@ from __future__ import annotations from typing import TYPE_CHECKING import asyncio +import logging +import time if TYPE_CHECKING: import ba @@ -53,7 +55,17 @@ def setup_asyncio() -> asyncio.AbstractEventLoop: def run_cycle() -> None: assert _asyncio_event_loop is not None _asyncio_event_loop.call_soon(_asyncio_event_loop.stop) + starttime = time.monotonic() _asyncio_event_loop.run_forever() + endtime = time.monotonic() + + # We'd like to keep the app running at a smooth 120hz, so + # complain if we're taking more than half that time per update. + warn_time = 1.0 / 240 + duration = endtime - starttime + if duration > warn_time: + logging.warning('Asyncio loop step took %.4fs; ideal max is %.4f', + duration, warn_time) global _asyncio_timer # pylint: disable=invalid-name _asyncio_timer = _ba.Timer(1.0 / 30.0, diff --git a/assets/src/ba_data/python/ba/_cloud.py b/assets/src/ba_data/python/ba/_cloud.py index 3e79537f..e2149a32 100644 --- a/assets/src/ba_data/python/ba/_cloud.py +++ b/assets/src/ba_data/python/ba/_cloud.py @@ -56,6 +56,14 @@ class CloudSubsystem: ) -> None: ... + @overload + def send_message_cb( + self, + msg: bacommon.cloud.PingMessage, + on_response: Callable[[bacommon.cloud.PingResponse | Exception], None], + ) -> None: + ... + def send_message_cb( self, msg: Message, diff --git a/assets/src/ba_data/python/ba/_music.py b/assets/src/ba_data/python/ba/_music.py index 0c5bbaed..4940e4c3 100644 --- a/assets/src/ba_data/python/ba/_music.py +++ b/assets/src/ba_data/python/ba/_music.py @@ -211,7 +211,7 @@ class MusicSubsystem: return 'Mac' in uas if entry_type in ('musicFile', 'musicFolder'): return ('android' in uas - and _ba.android_get_external_storage_path() is not None) + and _ba.android_get_external_files_dir() is not None) if entry_type == 'default': return True return False diff --git a/assets/src/ba_data/python/ba/_net.py b/assets/src/ba_data/python/ba/_net.py index 940b911f..2972a90c 100644 --- a/assets/src/ba_data/python/ba/_net.py +++ b/assets/src/ba_data/python/ba/_net.py @@ -29,7 +29,7 @@ class NetworkSubsystem: # as it is updated by a background thread. self.zone_pings_lock = threading.Lock() - # Region IDs mapped to average pings. This will remain empty + # Zone IDs mapped to average pings. This will remain empty # until enough pings have been run to be reasonably certain # that a nearby server has been pinged. self.zone_pings: dict[str, float] = {} @@ -37,6 +37,7 @@ class NetworkSubsystem: # For debugging. self.v1_test_log: str = '' self.v1_ctest_results: dict[int, str] = {} + self.server_time_offset_hours: float | None = None def get_ip_address_type(addr: str) -> socket.AddressFamily: diff --git a/assets/src/ba_data/python/ba/_plugin.py b/assets/src/ba_data/python/ba/_plugin.py index 6a3f2e84..b71c3c60 100644 --- a/assets/src/ba_data/python/ba/_plugin.py +++ b/assets/src/ba_data/python/ba/_plugin.py @@ -112,10 +112,11 @@ class PluginSubsystem: # or workspaces. if disappeared_plugs: _ba.playsound(_ba.getsound('shieldDown')) - _ba.screenmessage(Lstr(resource='pluginsRemovedText', - subs=[('${NUM}', - str(len(disappeared_plugs)))]), - color=(1, 1, 0)) + _ba.screenmessage( + Lstr(resource='pluginsRemovedText', + subs=[('${NUM}', str(len(disappeared_plugs)))]), + color=(1, 1, 0), + ) _ba.log( f'{len(disappeared_plugs)} plugin(s) no longer found:' f' {disappeared_plugs}', diff --git a/assets/src/ba_data/python/ba/modutils.py b/assets/src/ba_data/python/ba/modutils.py index e5d1f74d..6490e457 100644 --- a/assets/src/ba_data/python/ba/modutils.py +++ b/assets/src/ba_data/python/ba/modutils.py @@ -27,8 +27,7 @@ def get_human_readable_user_scripts_path() -> str: # only visible to the user's processes and thus not really useful printed # in its entirety; lets print it as /myfilepath. if app.platform == 'android': - ext_storage_path: str | None = ( - _ba.android_get_external_storage_path()) + ext_storage_path: str | None = (_ba.android_get_external_files_dir()) if (ext_storage_path is not None and app.python_directory_user.startswith(ext_storage_path)): path = ('<' + diff --git a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py index 04355844..b0227ca9 100644 --- a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py +++ b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py @@ -10,6 +10,7 @@ import weakref from threading import Thread from typing import TYPE_CHECKING +from efro.error import CleanError import _ba import ba from bastd.ui.settings.testing import TestingWindow @@ -148,10 +149,12 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: call() duration = time.monotonic() - starttime _print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0)) - except Exception: + except Exception as exc: import traceback duration = time.monotonic() - starttime - _print(traceback.format_exc(), color=(1.0, 1.0, 0.3)) + msg = (str(exc) + if isinstance(exc, CleanError) else traceback.format_exc()) + _print(msg, color=(1.0, 1.0, 0.3)) _print(f'Failed in {duration:.2f}s.', color=(1, 0, 0)) have_error[0] = True @@ -193,6 +196,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: _print(f'\nContacting V2 master-server ({baseaddr})...') _print_test_results(lambda: _test_fetch(baseaddr)) + _print('\nComparing local time to V2 server...') + _print_test_results(_test_v2_time) + # Get V2 nearby zone with ba.app.net.zone_pings_lock: zone_pings = copy.deepcopy(ba.app.net.zone_pings) @@ -206,6 +212,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None: _print(f'\nChecking nearest V2 zone ping ({nearstr})...') _print_test_results(lambda: _test_nearby_zone_ping(nearest_zone)) + _print('\nSending V2 cloud message...') + _print_test_results(_test_v2_cloud_message) + if have_error[0]: _print('\nDiagnostics complete. Some diagnostics failed.', color=(10, 0, 0)) @@ -271,6 +280,58 @@ def _test_v1_transaction() -> None: raise RuntimeError(results[0]) +def _test_v2_cloud_message() -> None: + from dataclasses import dataclass + import bacommon.cloud + + @dataclass + class _Results: + errstr: str | None = None + send_time: float | None = None + response_time: float | None = None + + results = _Results() + + def _cb(response: bacommon.cloud.PingResponse | Exception) -> None: + # Note: this runs in another thread so need to avoid exceptions. + results.response_time = time.monotonic() + if isinstance(response, Exception): + results.errstr = str(response) + if not isinstance(response, bacommon.cloud.PingResponse): + results.errstr = f'invalid response type: {type(response)}.' + + def _send() -> None: + # Note: this runs in another thread so need to avoid exceptions. + results.send_time = time.monotonic() + ba.app.cloud.send_message_cb(bacommon.cloud.PingMessage(), _cb) + + # This stuff expects to be run from the logic thread. + ba.pushcall(_send, from_other_thread=True) + + wait_start_time = time.monotonic() + while True: + if results.response_time is not None: + break + time.sleep(0.01) + if time.monotonic() - wait_start_time > 10.0: + raise RuntimeError('Timeout waiting for cloud message response') + if results.errstr is not None: + raise RuntimeError(results.errstr) + + +def _test_v2_time() -> None: + offset = ba.app.net.server_time_offset_hours + if offset is None: + raise RuntimeError('no time offset found;' + ' perhaps unable to communicate with v2 server?') + if abs(offset) >= 2.0: + raise CleanError( + f'Your device time is off from world time by {offset:.1f} hours.\n' + 'This may cause network operations to fail due to your device\n' + ' incorrectly treating SSL certificates as not-yet-valid, etc.\n' + 'Check your device time and time-zone settings to fix this.\n') + + def _test_fetch(baseaddr: str) -> None: # pylint: disable=consider-using-with import urllib.request diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py b/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py index e3bc6503..acfdef22 100644 --- a/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py +++ b/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py @@ -160,7 +160,7 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): from ba.osmusic import OSMusicPlayer from bastd.ui.fileselector import FileSelectorWindow ba.containerwidget(edit=self._root_widget, transition='out_left') - base_path = _ba.android_get_external_storage_path() + base_path = _ba.android_get_external_files_dir() ba.app.ui.set_main_menu_window( FileSelectorWindow( base_path, @@ -173,7 +173,7 @@ class SoundtrackEntryTypeSelectWindow(ba.Window): def _on_music_folder_press(self) -> None: from bastd.ui.fileselector import FileSelectorWindow ba.containerwidget(edit=self._root_widget, transition='out_left') - base_path = _ba.android_get_external_storage_path() + base_path = _ba.android_get_external_files_dir() ba.app.ui.set_main_menu_window( FileSelectorWindow(base_path, callback=self._music_folder_selector_cb, diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index 45c608fc..196f7d59 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -21,7 +21,7 @@ namespace ballistica { // These are set automatically via script; don't modify them here. -const int kAppBuildNumber = 20627; +const int kAppBuildNumber = 20629; const char* kAppVersion = "1.7.3"; // Our standalone globals. diff --git a/src/ballistica/platform/platform.cc b/src/ballistica/platform/platform.cc index 0dd1c53e..be98dc4c 100644 --- a/src/ballistica/platform/platform.cc +++ b/src/ballistica/platform/platform.cc @@ -269,13 +269,18 @@ auto Platform::GetUserPythonDirectory() -> std::string { auto Platform::GetVolatileDataDirectory() -> std::string { if (!made_volatile_data_dir_) { - volatile_data_dir_ = GetConfigDirectory() + BA_DIRSLASH + "vdata"; + volatile_data_dir_ = GetDefaultVolatileDataDirectory(); MakeDir(volatile_data_dir_); made_volatile_data_dir_ = true; } return volatile_data_dir_; } +auto Platform::GetDefaultVolatileDataDirectory() -> std::string { + // By default, stuff this in a subdir under our config dir. + return GetConfigDirectory() + BA_DIRSLASH + "vdata"; +} + auto Platform::GetAppPythonDirectory() -> std::string { static bool checked_dir = false; if (!checked_dir) { @@ -437,8 +442,8 @@ void Platform::MakeDir(const std::string& dir, bool quiet) { } } -auto Platform::GetExternalStoragePath() -> std::string { - throw Exception("GetExternalStoragePath() unimplemented"); +auto Platform::AndroidGetExternalFilesDir() -> std::string { + throw Exception("AndroidGetExternalFilesDir() unimplemented"); } auto Platform::DoGetUserPythonDirectory() -> std::string { @@ -708,7 +713,7 @@ auto Platform::GetKeyName(int keycode) -> std::string { void Platform::CreateAuxiliaryModules() { #if !BA_HEADLESS_BUILD - auto bg_dynamics_thread = new Thread(ThreadIdentifier::kBGDynamics); + auto* bg_dynamics_thread = new Thread(ThreadIdentifier::kBGDynamics); g_app_globals->pausable_threads.push_back(bg_dynamics_thread); #endif #if !BA_HEADLESS_BUILD @@ -719,7 +724,7 @@ void Platform::CreateAuxiliaryModules() { // Start listening for stdin commands (on platforms where that makes sense). // Note: this thread blocks indefinitely for input so we don't add it to the // pausable list. - auto std_input_thread = new Thread(ThreadIdentifier::kStdin); + auto* std_input_thread = new Thread(ThreadIdentifier::kStdin); std_input_thread->AddModule(); g_std_input_module->PushBeginReadCall(); } @@ -1209,7 +1214,7 @@ auto Platform::GetBroadcastAddrs() -> std::vector { throw Exception(); #else std::vector addrs; - struct ifaddrs* ifaddr; + struct ifaddrs* ifaddr{}; if (getifaddrs(&ifaddr) != -1) { int i = 0; for (ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { @@ -1252,7 +1257,7 @@ auto Platform::SetSocketNonBlocking(int sd) -> bool { #endif } -auto Platform::GetTicks() -> millisecs_t { +auto Platform::GetTicks() const -> millisecs_t { return GetCurrentMilliseconds() - starttime_; } diff --git a/src/ballistica/platform/platform.h b/src/ballistica/platform/platform.h index af137204..f4872c04 100644 --- a/src/ballistica/platform/platform.h +++ b/src/ballistica/platform/platform.h @@ -239,9 +239,6 @@ class Platform { /// Are we running on fireTV hardware? virtual auto IsRunningOnFireTV() -> bool; - /// Return the external storage path (currently only relevant on Android). - virtual auto GetExternalStoragePath() -> std::string; - // For enabling some special hardware optimizations for nvidia. auto is_tegra_k1() const -> bool { return is_tegra_k1_; } auto set_is_tegra_k1(bool val) -> void { is_tegra_k1_ = val; } @@ -276,6 +273,7 @@ class Platform { const std::string& code) -> void; virtual auto AndroidRefreshFile(const std::string& file) -> void; virtual auto AndroidShowWifiSettings() -> void; + virtual auto AndroidGetExternalFilesDir() -> std::string; #pragma mark PERMISSIONS ------------------------------------------------------- @@ -462,7 +460,7 @@ class Platform { // Return a monotonic time measurement in milliseconds since launch. // To get a time value that is guaranteed to not jump around or go backwards, // use ballistica::GetRealTime() (which is an abstraction around this). - auto GetTicks() -> millisecs_t; + auto GetTicks() const -> millisecs_t; // A raw milliseconds value (not relative to launch time). static auto GetCurrentMilliseconds() -> millisecs_t; @@ -552,8 +550,15 @@ class Platform { virtual auto DoGetUserPythonDirectory() -> std::string; /// Return the default config directory for this platform. + /// This will be used as the config dir if not overridden via command + /// line options, etc. virtual auto GetDefaultConfigDirectory() -> std::string; + /// Return the default Volatile data dir for this platform. + /// This will be used as the volatile-data-dir if not overridden via command + /// line options/etc. + virtual auto GetDefaultVolatileDataDirectory() -> std::string; + /// Generate a random UUID string. virtual auto GenerateUUID() -> std::string; diff --git a/src/ballistica/python/methods/python_methods_system.cc b/src/ballistica/python/methods/python_methods_system.cc index 4fc935f1..fb023c32 100644 --- a/src/ballistica/python/methods/python_methods_system.cc +++ b/src/ballistica/python/methods/python_methods_system.cc @@ -729,17 +729,17 @@ auto PyAndroidMediaScanFile(PyObject* self, PyObject* args, PyObject* keywds) #pragma clang diagnostic push #pragma ide diagnostic ignored "ConstantFunctionResult" -auto PyAndroidGetExternalStoragePath(PyObject* self, PyObject* args, - PyObject* keywds) -> PyObject* { +auto PyAndroidGetExternalFilesDir(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; - Platform::SetLastPyCall("android_get_external_storage_path"); + Platform::SetLastPyCall("android_get_external_files_dir"); static const char* kwlist[] = {nullptr}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "", const_cast(kwlist))) { return nullptr; } #if BA_OSTYPE_ANDROID - std::string path = g_platform->GetExternalStoragePath(); + std::string path = g_platform->AndroidGetExternalFilesDir(); if (path.empty()) { Py_RETURN_NONE; } else { @@ -883,10 +883,9 @@ auto PythonMethodsSystem::GetMethods() -> std::vector { "Refreshes Android MTP Index for a file; use this to get file\n" "modifications to be reflected in Android File Transfer."}, - {"android_get_external_storage_path", - (PyCFunction)PyAndroidGetExternalStoragePath, - METH_VARARGS | METH_KEYWORDS, - "android_get_external_storage_path() -> str\n" + {"android_get_external_files_dir", + (PyCFunction)PyAndroidGetExternalFilesDir, METH_VARARGS | METH_KEYWORDS, + "android_get_external_files_dir() -> str\n" "\n" "(internal)\n" "\n" diff --git a/tests/test_efro/test_message.py b/tests/test_efro/test_message.py index 8ef430ab..a4d076fc 100644 --- a/tests/test_efro/test_message.py +++ b/tests/test_efro/test_message.py @@ -423,8 +423,8 @@ TEST_PROTOCOL = MessageProtocol( 0: _TResp1, 1: _TResp2, }, - trusted_sender=True, - log_remote_exceptions=False, + receiver_returns_stack_traces=True, + receiver_logs_exceptions=False, ) # Represents an 'evolved' TEST_PROTOCOL (one extra message type added). @@ -440,8 +440,8 @@ TEST_PROTOCOL_B = MessageProtocol( 0: _TResp1, 1: _TResp2, }, - trusted_sender=True, - log_remote_exceptions=False, + receiver_returns_stack_traces=True, + receiver_logs_exceptions=False, ) TEST_PROTOCOL_SINGLE = MessageProtocol( @@ -451,8 +451,8 @@ TEST_PROTOCOL_SINGLE = MessageProtocol( response_types={ 0: _TResp1, }, - trusted_sender=True, - log_remote_exceptions=False, + receiver_returns_stack_traces=True, + receiver_logs_exceptions=False, ) diff --git a/tools/bacommon/cloud.py b/tools/bacommon/cloud.py index 197a1de5..34e9ccaa 100644 --- a/tools/bacommon/cloud.py +++ b/tools/bacommon/cloud.py @@ -76,6 +76,22 @@ class LoginProxyCompleteMessage(Message): proxyid: Annotated[str, IOAttrs('p')] +@ioprepped +@dataclass +class PingMessage(Message): + """Standard ping.""" + + @classmethod + def get_response_types(cls) -> list[type[Response]]: + return [PingResponse] + + +@ioprepped +@dataclass +class PingResponse(Response): + """pong.""" + + @ioprepped @dataclass class TestMessage(Message): diff --git a/tools/bacommon/net.py b/tools/bacommon/net.py index 3409ba61..a22a9c5c 100644 --- a/tools/bacommon/net.py +++ b/tools/bacommon/net.py @@ -4,6 +4,7 @@ from __future__ import annotations +import datetime from typing import TYPE_CHECKING, Any, Annotated from dataclasses import dataclass, field @@ -27,6 +28,9 @@ class ServerNodeEntry: class ServerNodeQueryResponse: """A response to a query about server-nodes.""" + # The current utc time on the master server. + time: Annotated[datetime.datetime, IOAttrs('t')] + # If present, something went wrong, and this describes it. error: Annotated[str | None, IOAttrs('e', store_default=False)] = None diff --git a/tools/efro/message/_protocol.py b/tools/efro/message/_protocol.py index a1171023..e1e5cfc0 100644 --- a/tools/efro/message/_protocol.py +++ b/tools/efro/message/_protocol.py @@ -25,28 +25,40 @@ class MessageProtocol: """Wrangles a set of message types, formats, and response types. Both endpoints must be using a compatible Protocol for communication to succeed. To maintain Protocol compatibility between revisions, - all message types must retain the same id, message attr storage names must - not change, newly added attrs must have default values, etc. + all message types must retain the same id, message attr storage + names must not change, newly added attrs must have default values, + etc. """ def __init__(self, message_types: dict[int, type[Message]], response_types: dict[int, type[Response]], preserve_clean_errors: bool = True, - log_remote_exceptions: bool = True, - trusted_sender: bool = False) -> None: + receiver_logs_exceptions: bool = True, + receiver_returns_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 errors - on the remote end will result in the same error raised locally. - All other Exception types come across as efro.error.RemoteError. + If 'preserve_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. - If 'trusted_sender' is True, stringified remote stack traces will - be included in the responses if errors occur. + 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 + 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. """ self.message_types_by_id: dict[int, type[Message]] = {} self.message_ids_by_type: dict[type[Message], int] = {} @@ -102,9 +114,9 @@ class MessageProtocol: assert is_ioprepped_dataclass(cls) assert issubclass(cls, Response) if cls not in self.response_ids_by_type: - raise ValueError(f'Possible response type {cls}' - f' needs to be included in response_types' - f' for this protocol.') + raise ValueError( + f'Possible response type {cls} needs to be included' + f' in response_types for this protocol.') # Make sure all registered types have unique base names. # We can take advantage of this to generate cleaner looking @@ -116,8 +128,8 @@ class MessageProtocol: ' all types are required to have unique names.') self.preserve_clean_errors = preserve_clean_errors - self.log_remote_exceptions = log_remote_exceptions - self.trusted_sender = trusted_sender + self.receiver_logs_exceptions = receiver_logs_exceptions + self.receiver_returns_stack_traces = receiver_returns_stack_traces @staticmethod def encode_dict(obj: dict) -> str: @@ -134,7 +146,9 @@ class MessageProtocol: def error_to_response(self, exc: Exception) -> Response: """Translate an error to a response.""" - if self.log_remote_exceptions: + + # Log any errors we got during handling if so desired. + if self.receiver_logs_exceptions: logging.exception('Error handling message.') # If anything goes wrong, return a ErrorResponse instead. @@ -142,8 +156,9 @@ class MessageProtocol: return ErrorResponse(error_message=str(exc), error_type=ErrorResponse.ErrorType.CLEAN) return ErrorResponse( - error_message=(traceback.format_exc() if self.trusted_sender else - 'An unknown error has occurred.'), + error_message=(traceback.format_exc() + if self.receiver_returns_stack_traces else + 'An internal error has occurred.'), error_type=ErrorResponse.ErrorType.OTHER) def _to_dict(self, message: Any, ids_by_type: dict[type, int], diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py index 2368d5ad..fa4d15f8 100644 --- a/tools/efrotools/pybuild.py +++ b/tools/efrotools/pybuild.py @@ -71,12 +71,6 @@ def build_apple(arch: str, debug: bool = False) -> None: txt = readfile('Makefile') - # Fix a bug where spaces in PATH cause errors (darn you vmware fusion!) - txt = replace_exact( - txt, - '\t\tPATH=$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/_install/bin:$(PATH)', - '\t\tPATH="$(PROJECT_DIR)/$(PYTHON_DIR-macOS)/_install/bin:$(PATH)"') - # Turn doc strings on; looks like it only adds a few hundred k. txt = replace_exact(txt, '--without-doc-strings',