Language updates and minor bug fixes

This commit is contained in:
Eric Froemling 2022-07-14 14:33:48 -07:00
parent 3f427c7747
commit 738bad8852
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
17 changed files with 220 additions and 149 deletions

View File

@ -420,39 +420,39 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/d5/f8/de3b22ac60d0caee44369117c8db", "assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/d5/f8/de3b22ac60d0caee44369117c8db",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/c7/37/660e2ceca8b9b2db2cfce5a9f6f0", "assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/c7/37/660e2ceca8b9b2db2cfce5a9f6f0",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/d9/82/92157cb4f21fdd8c911eec910ad4", "assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/d9/82/92157cb4f21fdd8c911eec910ad4",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/4b/50/f349b757f92f6362a39f70ea7a80", "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/25/20/31a26a4899e68dd9a0c6c272b998",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/45/70/91f386517fa3dc7322e1345ee1ab", "assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/a5/48/9e4424d04b273c8bdc25f9be4b6e",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/61/03/89070ca765e06da3a419a579f503", "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/55/93/1f358b653e9a347593d9b23f0061", "assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/fa/2e/fe2446e2dab7d8c7cf18a39b4b8e",
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/60/48/bf681053e91e38d0f4c4b1f8f1a6", "assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/60/48/bf681053e91e38d0f4c4b1f8f1a6",
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/6c/2b/7a0669cc74fbcebbb76c440c6c04", "assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/6c/2b/7a0669cc74fbcebbb76c440c6c04",
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/61/77/ae85d46474923707f8a7190849a2", "assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/61/77/ae85d46474923707f8a7190849a2",
"assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28", "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/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/13/42/9585944a58dd340611373a48732f", "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/11/26/c3dfae5a52f337529c4ea129237c",
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/4c/c7/0184b8178869d1a3827a1bfcd5bb", "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/a6/43/180ad35f294f7087661479687a12", "assets/build/ba_data/data/languages/filipino.json": "https://files.ballistica.net/cache/ba1/ac/ec/2677e8fd26c395a5032d7d6e7aa1",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/b6/e0/37dd30b686f475733ccc4b3cab49", "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/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/f6/5c/591661f58ee2cf4a78fd8fa54bed", "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/45/5c/abf0567aaea410b43783453eba5f",
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/82/eb/37ff44af76812097f9c98f05c730", "assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/82/eb/37ff44af76812097f9c98f05c730",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/08/3b/68cea4d16f7020d932829af85323", "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/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/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/f3/11/d81cc9f0c76b81b0b8dcf64fe292", "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/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/1b/45/b1e45fe07e0d632e979a9433dadd", "assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/37/90/0350db0bd1e98215fde8ee3f4059",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/76/be/84e567de0aabd9f9145b62179c2c", "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/08/be/543b564010ad45e191d6ff5f130b", "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/a6/ad/7ee52772db14f9008044bb877ed1",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/df/30/9ab2f22cdd54f0393a7d2aba2ae0", "assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/d7/06/9d70642d0a4d1e3b1c2149d7a17c",
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/f2/41/d4f06aabfc3499534e04fedb10dd", "assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/6c/46/292406974d45fd9a92e40022b443",
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/13/19/828be486951be254445263f36c6e", "assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/4e/91/6f2a9a3ce733908e91377a6ddb9a",
"assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/f9/4b/d9f01814224066856695452ef57c", "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/bc/93/b5d78fcd8cdffda7f3d9cc238cef", "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/80/d7/bd62eacc2ec901e145e37168c737",
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/91/0a/35c4baf539d5951fc03a794c0e0b", "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/9f/2c/367db6734a89603ed45534fccb14", "assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/55/46/61fa6c9a93f6c0d57529d7949190",
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/9d/51/f699dbd4beb88bc3cff699a287a7", "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/7e/d0/b21f8bcdc7f2727a6e68af3d0e68", "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/c0/4e/c8a66ed415b8b0ce3ff30462b2a2",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/7f/bb/6239adeb551be5e09f3457d7b411", "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/68/77/cc871b6ed611cb8d6f74cf1a9d00", "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/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/f2/af/afd1503c7a10cacaa15bc02369b2",
@ -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/__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", "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", "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/e2/e0/7e12f13ce528c828ae8c482ac1c5", "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/2f/ec/756d728d0a912a0a0ce1e46b3351",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a4/cd/a23c971748bee4c4726192d892e6", "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/2e/d8/41fb2f73f9b915e809a3d8671c45",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/04/c6/240b3031c13ca7df990e5c867e82", "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/a9/931e229fed568facc7d77b407a9a",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/58/c4/339c89e00d13ed26f2b83ace0053", "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9c/f3/aef423feb4f5e91ce72fb5a25fa0",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d6/fa/9d66b57832c3155899fda3b9ed2d", "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/cd/c2/f5f0a7ae3197fe5f05bfcee3a3e3",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/7f/df248f9fcab1f0a6b3188de679f1", "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4f/dc/2a1a296dddd5d5fef6b2f0bef6e8",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4a/a2/a9e03eabf8dce07d7d150dbe533f", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c6/4c/e1274ac12ae7a0a60d02686abc0b",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/12/c4/672108a20af3efd292f0f19cb6d1", "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6c/b1/f56d3edb7c6efa23518b78b74846",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f9/70/991734c16666f041ec26fd3d24fc", "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/87/b2/0a85ffc1ed64724da5663086a2a9",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3f/1d/5867b2567b0b5092898c79ba13c6", "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bc/95/67636889721c129a697a55fc47c2",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/46/b7/69773f4298626ff327680db9aa26", "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/59/8f/0bfe1878a0941351294b2306b2ad",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/03/75/f2f6bb1c9eebf8c3eecb44fd76b5", "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/de/43/bfab4d3fda82d02e97207f5be4cd",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b7/40/3c89c40a3522c5533bf567b86005", "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/39/d0/6bb26d2fce1d6f8644191217c00c",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6c/0f/982997d664d258a9f2dd7d100447", "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/80/33/fc11bdaba571fc91429518881081",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d0/52/a06b3b9ec34d78c27ece18dfc04c", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/be/9d/9e39fc8085e1061d016e9ea242a6",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/e7/5d1b3acd640935f2d262adf9f56e", "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/aa/fef783198a65c064b1c8be235307",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/57/d4/79d1653f278e083ff3d970b345e5", "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/87/4b/06fca73ff1a1c22f92f462c8e539",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/0b/ce/5aef8c58b6120549ac677c0aa83a", "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/97/32/95378dfe465d38aa3bbebb932669",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/5b/57/68e576ff9ceed03102d2ef199a23", "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/e3/af/8e86cf496985267f69a98613eb15",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/53/77/fb69f8b5475e0ab5fb5c028fda1c", "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/3a/4a/2860a58d8b460ca6ff8243a0f185",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/08/bcb4ce1689de4f0a4ffe1d7c542f", "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c3/a5/d77c0da95027f6c6b2158d554692",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/67/44/8916488c91538d83248f0ec5a2e5", "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/92/93/d343eb8e8783494d8d04949fcd34",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/d7/6ff4bfa3bb6805fa812fb023876e", "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/26/c623cadc48e151bf438579730e33",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c1/f0/a26ed674437c26c8af557c10fc25", "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cc/32/eb7bd9b981c303d59ec32efc46b7",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b9/b0/e3a2098f4324d361135d140c7d16", "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/9a/43e14da4b3ec46f69fd710d4daba",
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/98/d2/25ea99e21ab82489758ad2f4eda1", "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a4/c1/e4548f2abcba4c48e713dd24e2fd",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/18/d3/b3fa835ed6fce67f22513718fcf5", "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/69/79/8e676bba0b03ed522c2475bb07b4",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/37/d65ed9c851e97a4c16f99ce4b996", "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/69/97/6119b7a2dd5c1f3fbcb2d921652d",
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/85/4a/c04a061403b47b26c2fe477bce5b", "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/73/53/98ea7844b411cbc99a347213410f",
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/04/37/dd511de9d333afd4f77c8a37d11c", "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/78/9b5b9048e0fcb3f6283983046cd8",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/40/33c32f48b5ec2ff940d8ed114e0f", "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/1a/503e5789b08c3a85bb34bebccdf8",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/55/f7/3f6a200eae7baf961a9ba3d1857d", "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/04/cf/a70ffac469d5bbc2ba9d56ebffcb",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fd/e8/af76c5dccfe324c6081185f5e8f0", "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/e6/66090d68d391eba428a6b5d146e5",
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/aa/c5/159b01f8dc8a4201dec430c77a5c", "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/4c/d59f4dd0c41e1cb3f32f3864e8cc",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c3/ec/f087eff8388ef3f0a16699bb44b3", "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/16/9c/bda065fbb75646796383ff4c0490",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/64/98/01a2649fa88ad6513e8c36d3ac04", "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/56/b6f1a19cda20dada065f0f43cd83",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ee/67/86f37883c1d82436296c107cb0af", "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/f4/76/36e047c715c69980bf53f47cc03b",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/b6/0a/288ab61d465b4901cf7112a241c9", "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/83/03/e08562ec65eba9577c9fbe1a98b0",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/73/88/561fac00c0eb27f6b451322cf777", "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/26/08/755cd4f6d908359e0a7d33a5f9dd",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e6/11/73127ddbd3a779076770a7448ce1", "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/bd/d7/0de5664c35680f40a9c8efbc264b",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/2a/3c/ef57604bb8e94075d74eafe258e3", "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/e9/65/0693588e1b04bff51d197c77e6c6",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/5d/ca/0e6d9a2ac4d0f750b8295b6d775e", "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/bd/b1/866126ff56ab9c6148b750282295",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/ff/e3/f03bc482b4e305288e7418acb38a", "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/ef/47/19efea33fdbd69c376f35b590e3b",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/8a/33/713bfca4012f0d1b7b7b4e896d46", "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/5b/e6/174649077bcc202ead74e7e5bf4d",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/6e/6f/004b696e9a13b083069374e4bb6a", "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3" "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3"
} }

View File

@ -1,4 +1,8 @@
### 1.7.5 (20648, 2022-07-12) ### 1.7.5 (20650, 2022-07-14)
- Android build now uses the ReLinker library to load the native main.so, which will (hopefully) avoid some random load failures on older Android versions.
- Android Google Play build now prints a message at launch if the billing library isn't available or needs to be updated (explaining why purchases won't work in that case).
- Various minor bug fixes (mostly cleaning up unnecessary error logging)
- Updated Android builds to use the new NDK 25 release
### 1.7.4 (20646, 2022-07-12) ### 1.7.4 (20646, 2022-07-12)
- Fixed the trophies list showing an incorrect total (Thanks itsre3!) - Fixed the trophies list showing an incorrect total (Thanks itsre3!)

View File

@ -204,6 +204,12 @@ def no_game_circle_message() -> None:
_ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0)) _ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0))
def google_play_purchases_not_available_message() -> None:
from ba._language import Lstr
_ba.screenmessage(Lstr(resource='googlePlayPurchasesNotAvailableText'),
color=(1, 0, 0))
def empty_call() -> None: def empty_call() -> None:
pass pass

View File

@ -127,29 +127,33 @@ class MasterServerCallThread(threading.Thread):
self._callback(arg) self._callback(arg)
def run(self) -> None: def run(self) -> None:
# pylint: disable=too-many-branches, consider-using-with # pylint: disable=consider-using-with
import urllib.request import urllib.request
import urllib.parse import urllib.parse
import urllib.error
import json import json
from efro.error import is_urllib_network_error from efro.error import is_urllib_communication_error
from ba import _general from ba import _general
response_data: Any = None
url: str | None = None
try: try:
self._data = _general.utf8_all(self._data) self._data = _general.utf8_all(self._data)
_ba.set_thread_name('BA_ServerCallThread') _ba.set_thread_name('BA_ServerCallThread')
if self._request_type == 'get': if self._request_type == 'get':
url = (_ba.get_master_server_address() + '/' + self._request +
'?' + urllib.parse.urlencode(self._data))
response = urllib.request.urlopen( response = urllib.request.urlopen(
urllib.request.Request( urllib.request.Request(
(_ba.get_master_server_address() + '/' + url, None, {'User-Agent': _ba.app.user_agent_string}),
self._request + '?' +
urllib.parse.urlencode(self._data)), None,
{'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext, context=_ba.app.net.sslcontext,
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
elif self._request_type == 'post': elif self._request_type == 'post':
url = _ba.get_master_server_address() + '/' + self._request
response = urllib.request.urlopen( response = urllib.request.urlopen(
urllib.request.Request( urllib.request.Request(
_ba.get_master_server_address() + '/' + self._request, url,
urllib.parse.urlencode(self._data).encode(), urllib.parse.urlencode(self._data).encode(),
{'User-Agent': _ba.app.user_agent_string}), {'User-Agent': _ba.app.user_agent_string}),
context=_ba.app.net.sslcontext, context=_ba.app.net.sslcontext,
@ -172,29 +176,18 @@ class MasterServerCallThread(threading.Thread):
raise TypeError(f'invalid responsetype: {self._response_type}') raise TypeError(f'invalid responsetype: {self._response_type}')
except Exception as exc: except Exception as exc:
do_print = False
response_data = None
# Ignore common network errors; note unexpected ones. # Ignore common network errors; note unexpected ones.
if is_urllib_network_error(exc): if not is_urllib_communication_error(exc, url=url):
pass
elif (self._response_type == MasterServerResponseType.JSON
and isinstance(exc, json.decoder.JSONDecodeError)):
# FIXME: should handle this better; could mean either the
# server sent us bad data or it got corrupted along the way.
pass
else:
do_print = True
if do_print:
# Any other error here is unexpected,
# so let's make a note of it,
print(f'Error in MasterServerCallThread' print(f'Error in MasterServerCallThread'
f' (response-type={self._response_type},' f' (url={url},'
f' response-type={self._response_type},'
f' response-data={response_data}):') f' response-data={response_data}):')
import traceback import traceback
traceback.print_exc() traceback.print_exc()
response_data = None
if self._callback is not None: if self._callback is not None:
_ba.pushcall(_general.Call(self._run_callback, response_data), _ba.pushcall(_general.Call(self._run_callback, response_data),
from_other_thread=True) from_other_thread=True)

View File

@ -705,8 +705,8 @@ class ManualGatherTab(GatherTab):
from_other_thread=True, from_other_thread=True,
) )
except Exception as exc: except Exception as exc:
from efro.error import is_udp_network_error from efro.error import is_udp_communication_error
if is_udp_network_error(exc): if is_udp_communication_error(exc):
ba.pushcall(ba.Call( ba.pushcall(ba.Call(
_safe_set_text, self._checking_state_text, _safe_set_text, self._checking_state_text,
ba.Lstr(resource='gatherWindow.' ba.Lstr(resource='gatherWindow.'

View File

@ -219,9 +219,9 @@ class AddrFetchThread(threading.Thread):
sock.close() sock.close()
ba.pushcall(ba.Call(self._call, val), from_other_thread=True) ba.pushcall(ba.Call(self._call, val), from_other_thread=True)
except Exception as exc: except Exception as exc:
from efro.error import is_udp_network_error from efro.error import is_udp_communication_error
# Ignore expected network errors; log others. # Ignore expected network errors; log others.
if is_udp_network_error(exc): if is_udp_communication_error(exc):
pass pass
else: else:
ba.print_exception() ba.print_exception()
@ -271,8 +271,8 @@ class PingThread(threading.Thread):
ping if accessible else None), ping if accessible else None),
from_other_thread=True) from_other_thread=True)
except Exception as exc: except Exception as exc:
from efro.error import is_udp_network_error from efro.error import is_udp_communication_error
if is_udp_network_error(exc): if is_udp_communication_error(exc):
pass pass
else: else:
ba.print_exception('Error on gather ping', once=True) ba.print_exception('Error on gather ping', once=True)

View File

@ -10,6 +10,8 @@
;; Projectile indexing and search will ignore the following ;; Projectile indexing and search will ignore the following
;; (in addition to git-ignored stuff which it ignores by default) ;; (in addition to git-ignored stuff which it ignores by default)
;; NOTE TO SELF: this means searches in spinoff projects will mostly fail
;; because everything is gitignored; should search in parent repo instead.
(nil . ((projectile-globally-ignored-directories . ("docs" (nil . ((projectile-globally-ignored-directories . ("docs"
"submodules" "submodules"
"src/external" "src/external"

View File

@ -21,7 +21,7 @@
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20648; const int kAppBuildNumber = 20650;
const char* kAppVersion = "1.7.5"; const char* kAppVersion = "1.7.5";
// Our standalone globals. // Our standalone globals.

View File

@ -25,10 +25,6 @@ auto FatalError::ReportFatalError(const std::string& message,
// error to the user and exiting the app cleanly (so we don't pollute our // error to the user and exiting the app cleanly (so we don't pollute our
// crash records with results of user tinkering). // crash records with results of user tinkering).
// Try to avoid crash reports if we're not a clean blessed build.
// bool exit_cleanly = !IsUnmodifiedBlessedBuild();
// printf("BLESSED %d\n", static_cast<int>(IsUnmodifiedBlessedBuild()));
// Give the platform the opportunity to completely override our handling. // Give the platform the opportunity to completely override our handling.
if (g_platform) { if (g_platform) {
auto handled = auto handled =

View File

@ -195,21 +195,14 @@ auto PyExtraHashValue(PyObject* self, PyObject* args, PyObject* keywds)
const_cast<char**>(kwlist))) { const_cast<char**>(kwlist))) {
return nullptr; return nullptr;
} }
const char* h = (g_app_globals->user_ran_commands ? "cjief3l" : "wofocj8"); const char* h =
((g_app_globals->user_ran_commands || g_app_globals->have_mods)
? "cjief3l"
: "wofocj8");
return PyUnicode_FromString(h); return PyUnicode_FromString(h);
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
auto PySetHaveMods(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("set_have_mods");
int have_mods;
if (!PyArg_ParseTuple(args, "p", &have_mods)) return nullptr;
g_app_globals->have_mods = static_cast<bool>(have_mods);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
auto PyGetIdleTime(PyObject* self, PyObject* args) -> PyObject* { auto PyGetIdleTime(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
Platform::SetLastPyCall("get_idle_time"); Platform::SetLastPyCall("get_idle_time");
@ -1077,11 +1070,6 @@ auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
"\n" "\n"
"Returns the amount of time since any game input has been received."}, "Returns the amount of time since any game input has been received."},
{"set_have_mods", PySetHaveMods, METH_VARARGS,
"set_have_mods(have_mods: bool) -> None\n"
"\n"
"(internal)"},
{"ehv", (PyCFunction)PyExtraHashValue, METH_VARARGS | METH_KEYWORDS, {"ehv", (PyCFunction)PyExtraHashValue, METH_VARARGS | METH_KEYWORDS,
"ehv() -> None\n" "ehv() -> None\n"
"\n" "\n"

View File

@ -309,6 +309,7 @@ class Python {
kSubmitAnalyticsCountsCall, kSubmitAnalyticsCountsCall,
kSetLastAdNetworkCall, kSetLastAdNetworkCall,
kNoGameCircleMessageCall, kNoGameCircleMessageCall,
kGooglePlayPurchasesNotAvailableMessageCall,
kEmptyCall, kEmptyCall,
kLevelIconPressCall, kLevelIconPressCall,
kTrophyIconPressCall, kTrophyIconPressCall,

View File

@ -64,6 +64,7 @@ def get_binding_values() -> tuple[Any, ...]:
_hooks.submit_analytics_counts, # kSubmitAnalyticsCountsCall _hooks.submit_analytics_counts, # kSubmitAnalyticsCountsCall
_hooks.set_last_ad_network, # kSetLastAdNetworkCall _hooks.set_last_ad_network, # kSetLastAdNetworkCall
_hooks.no_game_circle_message, # kNoGameCircleMessageCall _hooks.no_game_circle_message, # kNoGameCircleMessageCall
_hooks.google_play_purchases_not_available_message, # kGooglePlayPurchasesNotAvailableMessageCall
_hooks.empty_call, # kEmptyCall _hooks.empty_call, # kEmptyCall
_hooks.level_icon_press, # kLevelIconPressCall _hooks.level_icon_press, # kLevelIconPressCall
_hooks.trophy_icon_press, # kTrophyIconPressCall _hooks.trophy_icon_press, # kTrophyIconPressCall

View File

@ -12,7 +12,7 @@ from dataclasses import dataclass
import pytest import pytest
from efrotools.statictest import static_type_equals from efrotools.statictest import static_type_equals
from efro.error import CleanError, RemoteError from efro.error import CleanError, RemoteError, CommunicationError
from efro.dataclassio import ioprepped from efro.dataclassio import ioprepped
from efro.message import (Message, Response, MessageProtocol, MessageSender, from efro.message import (Message, Response, MessageProtocol, MessageSender,
BoundMessageSender, MessageReceiver, BoundMessageSender, MessageReceiver,
@ -748,6 +748,8 @@ def test_full_pipeline() -> None:
msg = _TestMessageSenderBBoth() msg = _TestMessageSenderBBoth()
test_handling_unregistered = False test_handling_unregistered = False
test_send_method_exceptions = False
test_send_method_exceptions_comm = False
def __init__(self, target: TestClassRSync | TestClassRAsync) -> None: def __init__(self, target: TestClassRSync | TestClassRAsync) -> None:
self.test_sidecar = False self.test_sidecar = False
@ -756,6 +758,13 @@ def test_full_pipeline() -> None:
@msg.send_method @msg.send_method
def _send_raw_message(self, data: str) -> str: def _send_raw_message(self, data: str) -> str:
"""Handle synchronous sending of raw json message data.""" """Handle synchronous sending of raw json message data."""
# Test throwing exceptions in send methods.
if self.test_send_method_exceptions:
raise (CommunicationError()
if self.test_send_method_exceptions_comm else
RuntimeError())
# Just talk directly to the receiver for this example. # Just talk directly to the receiver for this example.
# (currently only support synchronous receivers) # (currently only support synchronous receivers)
assert isinstance(self._target, TestClassRSync) assert isinstance(self._target, TestClassRSync)
@ -774,6 +783,13 @@ def test_full_pipeline() -> None:
@msg.send_async_method @msg.send_async_method
async def _send_raw_message_async(self, data: str) -> str: async def _send_raw_message_async(self, data: str) -> str:
"""Handle asynchronous sending of raw json message data.""" """Handle asynchronous sending of raw json message data."""
# Test throwing exceptions in async send methods.
if self.test_send_method_exceptions:
raise (CommunicationError()
if self.test_send_method_exceptions_comm else
RuntimeError())
# Just talk directly to the receiver for this example. # Just talk directly to the receiver for this example.
# (we can do sync or async receivers) # (we can do sync or async receivers)
if isinstance(self._target, TestClassRSync): if isinstance(self._target, TestClassRSync):
@ -932,3 +948,21 @@ def test_full_pipeline() -> None:
assert getattr(response1, '_sidecar_data') == 198 assert getattr(response1, '_sidecar_data') == 198
obj.test_sidecar = False obj.test_sidecar = False
obj_r_sync.test_sidecar = False obj_r_sync.test_sidecar = False
# Now test errors in the raw-send function. Errors there should
# come across as either CommunicationErrors or RuntimeErrors
obj.test_send_method_exceptions = True
obj.test_send_method_exceptions_comm = False
with pytest.raises(RuntimeError):
response1 = obj.msg.send(_TMsg1(ival=0))
with pytest.raises(RuntimeError):
response4 = asyncio.run(obj.msg.send_async(_TMsg1(ival=0)))
obj.test_send_method_exceptions_comm = True
with pytest.raises(CommunicationError):
response1 = obj.msg.send(_TMsg1(ival=0))
with pytest.raises(CommunicationError):
response4 = asyncio.run(obj.msg.send_async(_TMsg1(ival=0)))
obj.test_send_method_exceptions = False

View File

@ -4,6 +4,7 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import errno
if TYPE_CHECKING: if TYPE_CHECKING:
pass pass
@ -39,12 +40,13 @@ class CommunicationError(Exception):
"""A communication related error has occurred. """A communication related error has occurred.
This covers anything network-related going wrong in the sending This covers anything network-related going wrong in the sending
of data or receiving of a response. This error does not imply of data or receiving of a response. Basically anything that is out
of our control should get lumped in here. This error does not imply
that data was not received on the other end; only that a full that data was not received on the other end; only that a full
acknowledgement round trip was not completed. acknowledgement round trip was not completed.
These errors should be gracefully handled whenever possible, as These errors should be gracefully handled whenever possible, as
occasional network outages are generally unavoidable. occasional network issues are unavoidable.
""" """
@ -55,9 +57,9 @@ class RemoteError(Exception):
occurs remotely. The error string can consist of a remote stack occurs remotely. The error string can consist of a remote stack
trace or a simple message depending on the context. trace or a simple message depending on the context.
Communication systems should raise more specific error types when Communication systems should raise more specific error types locally
more introspection/control is needed; this is intended somewhat as when more introspection/control is needed; this is intended somewhat
a catch-all. as a catch-all.
""" """
def __str__(self) -> str: def __str__(self) -> str:
@ -69,31 +71,43 @@ class IntegrityError(ValueError):
"""Data has been tampered with or corrupted in some form.""" """Data has been tampered with or corrupted in some form."""
def is_urllib_network_error(exc: BaseException) -> bool: def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool:
"""Is the provided exception from urllib a network-related error? """Is the provided exception from urllib a communication-related error?
Url, if provided can provide extra context for when to treat an error
as such an error.
This should be passed an exception which resulted from opening or This should be passed an exception which resulted from opening or
reading a urllib Request. It returns True for any errors that could reading a urllib Request. It returns True for any errors that could
conceivably arise due to unavailable/poor network connections, conceivably arise due to unavailable/poor network connections,
firewall/connectivity issues, etc. These issues can often be safely firewall/connectivity issues, or other issues out of our control.
ignored or presented to the user as general 'network-unavailable' These errors can often be safely ignored or presented to the user
states. as general 'network-unavailable' states.
""" """
import urllib.error import urllib.error
import http.client import http.client
import errno
import socket import socket
if isinstance( if isinstance(exc, (urllib.error.URLError, ConnectionError,
exc, http.client.IncompleteRead, http.client.BadStatusLine,
(urllib.error.URLError, ConnectionError, http.client.IncompleteRead, http.client.RemoteDisconnected, socket.timeout)):
http.client.BadStatusLine, socket.timeout)):
# Special case: although an HTTPError is a subclass of URLError, # Special case: although an HTTPError is a subclass of URLError,
# we don't return True for it. It means we have successfully # we don't consider it a communication error. It generally means we
# communicated with the server but what we are asking for is # have successfully communicated with the server but what we are asking
# not there/etc. # for is not there/etc.
if isinstance(exc, urllib.error.HTTPError): if isinstance(exc, urllib.error.HTTPError):
# Special sub-case: appspot.com hosting seems to give 403 errors
# (forbidden) to some countries. I'm assuming for legal reasons?..
# Let's consider that a communication error since its out of our
# control so we don't fill up logs with it.
if exc.code == 403 and url is not None and '.appspot.com' in url:
return True
return False return False
return True return True
if isinstance(exc, OSError): if isinstance(exc, OSError):
if exc.errno == 10051: # Windows unreachable network error. if exc.errno == 10051: # Windows unreachable network error.
return True return True
@ -106,18 +120,17 @@ def is_urllib_network_error(exc: BaseException) -> bool:
return False return False
def is_udp_network_error(exc: BaseException) -> bool: def is_udp_communication_error(exc: BaseException) -> bool:
"""Is the provided exception a network-related error? """Should this udp-related exception be considered a communication error?
This should be passed an exception which resulted from creating and This should be passed an exception which resulted from creating and
using a socket.SOCK_DGRAM type socket. It should return True for any using a socket.SOCK_DGRAM type socket. It should return True for any
errors that could conceivably arise due to unavailable/poor network errors that could conceivably arise due to unavailable/poor network
connections, firewall/connectivity issues, etc. These issues can often conditions, firewall/connectivity issues, etc. These issues can often
be safely ignored or presented to the user as general be safely ignored or presented to the user as general
'network-unavailable' states. 'network-unavailable' states.
""" """
import errno if isinstance(exc, ConnectionRefusedError | TimeoutError):
if isinstance(exc, ConnectionRefusedError):
return True return True
if isinstance(exc, OSError): if isinstance(exc, OSError):
if exc.errno == 10051: # Windows unreachable network error. if exc.errno == 10051: # Windows unreachable network error.
@ -140,8 +153,8 @@ def is_udp_network_error(exc: BaseException) -> bool:
return False return False
def is_asyncio_streams_network_error(exc: BaseException) -> bool: def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
"""Is the provided exception a network-related error? """Should this streams error be considered a communication error?
This should be passed an exception which resulted from creating and This should be passed an exception which resulted from creating and
using asyncio streams. It should return True for any errors that could using asyncio streams. It should return True for any errors that could
@ -149,7 +162,6 @@ def is_asyncio_streams_network_error(exc: BaseException) -> bool:
firewall/connectivity issues, etc. These issues can often be safely firewall/connectivity issues, etc. These issues can often be safely
ignored or presented to the user as general 'connection-lost' events. ignored or presented to the user as general 'connection-lost' events.
""" """
import errno
import ssl import ssl
if isinstance(exc, ( if isinstance(exc, (

View File

@ -56,6 +56,7 @@ class ErrorResponse(Response):
OTHER = 0 OTHER = 0
CLEAN = 1 CLEAN = 1
LOCAL = 2 LOCAL = 2
COMMUNICATION = 3
error_message: Annotated[str, IOAttrs('m')] error_message: Annotated[str, IOAttrs('m')]
error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.OTHER error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.OTHER

View File

@ -9,7 +9,7 @@ from __future__ import annotations
import logging import logging
from typing import TYPE_CHECKING, TypeVar from typing import TYPE_CHECKING, TypeVar
from efro.error import CleanError, RemoteError from efro.error import CleanError, RemoteError, CommunicationError
from efro.message._message import EmptyResponse, ErrorResponse from efro.message._message import EmptyResponse, ErrorResponse
if TYPE_CHECKING: if TYPE_CHECKING:
@ -35,7 +35,7 @@ class MessageSender:
def send_raw_message(self, message: str) -> str: def send_raw_message(self, message: str) -> str:
# Actually send the message here. # Actually send the message here.
# MyMessageSender class should provide overloads for send(), send_bg(), # MyMessageSender class should provide overloads for send(), send_async(),
# etc. to ensure all sending happens with valid types. # etc. to ensure all sending happens with valid types.
obj = MyClass() obj = MyClass()
obj.msg.send(SomeMessageType()) obj.msg.send(SomeMessageType())
@ -54,7 +54,12 @@ class MessageSender:
def send_method( def send_method(
self, call: Callable[[Any, str], self, call: Callable[[Any, str],
str]) -> Callable[[Any, str], str]: str]) -> Callable[[Any, str], str]:
"""Function decorator for setting raw send method.""" """Function decorator for setting raw send method.
Send methods take strings and should return strings.
Any Exception raised during the send_method manifests as
a CommunicationError for the message sender.
"""
assert self._send_raw_message_call is None assert self._send_raw_message_call is None
self._send_raw_message_call = call self._send_raw_message_call = call
return call return call
@ -62,7 +67,12 @@ class MessageSender:
def send_async_method( def send_async_method(
self, call: Callable[[Any, str], Awaitable[str]] self, call: Callable[[Any, str], Awaitable[str]]
) -> Callable[[Any, str], Awaitable[str]]: ) -> Callable[[Any, str], Awaitable[str]]:
"""Function decorator for setting raw send-async method.""" """Function decorator for setting raw send-async method.
Send methods take strings and should return strings.
Any Exception raised during the send_method manifests as
a CommunicationError for the message sender.
"""
assert self._send_async_raw_message_call is None assert self._send_async_raw_message_call is None
self._send_async_raw_message_call = call self._send_async_raw_message_call = call
return call return call
@ -123,7 +133,17 @@ class MessageSender:
raise RuntimeError('send() is unimplemented for this type.') raise RuntimeError('send() is unimplemented for this type.')
msg_encoded = self._encode_message(bound_obj, message) msg_encoded = self._encode_message(bound_obj, message)
response_encoded = self._send_raw_message_call(bound_obj, msg_encoded) try:
response_encoded = self._send_raw_message_call(
bound_obj, msg_encoded)
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorResponse(
error_message=f'Error in send async method: {exc}',
error_type=(ErrorResponse.ErrorType.COMMUNICATION
if isinstance(exc, CommunicationError) else
ErrorResponse.ErrorType.LOCAL))
return self._decode_raw_response(bound_obj, message, response_encoded) return self._decode_raw_response(bound_obj, message, response_encoded)
async def send_split_part_1_async(self, bound_obj: Any, async def send_split_part_1_async(self, bound_obj: Any,
@ -139,9 +159,17 @@ class MessageSender:
raise RuntimeError('send_async() is unimplemented for this type.') raise RuntimeError('send_async() is unimplemented for this type.')
msg_encoded = self._encode_message(bound_obj, message) msg_encoded = self._encode_message(bound_obj, message)
response_encoded = await self._send_async_raw_message_call( try:
bound_obj, msg_encoded) response_encoded = await self._send_async_raw_message_call(
bound_obj, msg_encoded)
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorResponse(
error_message=f'Error in send async method: {exc}',
error_type=(ErrorResponse.ErrorType.COMMUNICATION
if isinstance(exc, CommunicationError) else
ErrorResponse.ErrorType.LOCAL))
return self._decode_raw_response(bound_obj, message, response_encoded) return self._decode_raw_response(bound_obj, message, response_encoded)
def send_split_part_2(self, message: Message, def send_split_part_2(self, message: Message,
@ -183,8 +211,8 @@ class MessageSender:
# If we got to this point, we successfully communicated # If we got to this point, we successfully communicated
# with the other end so errors represent protocol mismatches # with the other end so errors represent protocol mismatches
# or other invalid data. For now let's just log it but perhaps # 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 raised # we'd want to somehow embed it in the ErrorResponse to be
# directly to the user later. # available directly to the user later.
logging.exception('Error decoding raw response') logging.exception('Error decoding raw response')
response = ErrorResponse( response = ErrorResponse(
error_message= error_message=
@ -208,6 +236,10 @@ class MessageSender:
# Some error occurred. Raise a local Exception for it. # Some error occurred. Raise a local Exception for it.
if isinstance(raw_response, ErrorResponse): if isinstance(raw_response, ErrorResponse):
if (raw_response.error_type is
ErrorResponse.ErrorType.COMMUNICATION):
raise CommunicationError(raw_response.error_message)
# If something went wrong on our end of the connection, # If something went wrong on our end of the connection,
# don't say it was a remote error. # don't say it was a remote error.
if raw_response.error_type is ErrorResponse.ErrorType.LOCAL: if raw_response.error_type is ErrorResponse.ErrorType.LOCAL:

View File

@ -14,7 +14,8 @@ from threading import current_thread
from typing import TYPE_CHECKING, Annotated from typing import TYPE_CHECKING, Annotated
from efro.util import assert_never from efro.util import assert_never
from efro.error import CommunicationError, is_asyncio_streams_network_error from efro.error import (CommunicationError,
is_asyncio_streams_communication_error)
from efro.dataclassio import (dataclass_to_json, dataclass_from_json, from efro.dataclassio import (dataclass_to_json, dataclass_from_json,
ioprepped, IOAttrs) ioprepped, IOAttrs)
@ -596,7 +597,7 @@ class RPCEndpoint:
if isinstance(exc, _KeepaliveTimeoutError): if isinstance(exc, _KeepaliveTimeoutError):
return True return True
return is_asyncio_streams_network_error(exc) return is_asyncio_streams_communication_error(exc)
def _check_env(self) -> None: def _check_env(self) -> None:
# I was seeing that asyncio stuff wasn't working as expected if # I was seeing that asyncio stuff wasn't working as expected if