Latest public/internal sync.

This commit is contained in:
Eric 2022-10-07 09:16:48 -07:00
parent 0ab52dc448
commit 08b9d07e5e
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
19 changed files with 395 additions and 140 deletions

View File

@ -420,7 +420,7 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/60/ad/38269b7f1c7dc20cb9a506cd0681",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/72/85/d6fc4d16b7081d91fba2850b5b10",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/e9/ae/1d674d0c086eaa0bd1c3b1db0505",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/7d/d4/6a32da2a6a5d1f8d71f65ac65792",
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/36/57/76a60b261de061886357dbf2c0db",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/e2/24/5e7ea9ca5c9de4d3b7a28e53564d",
"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/f8/15/e1a2fa38697417bcf2cf19cd34ef",
@ -454,7 +454,7 @@
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/f7/df/7ba5f99c5c2c4c86fc0503fcf0b7",
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/9a/90/8e2ed626def09f88c3b9ab5215a3",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/ab/35/644e4239cfa62a597a905412b90c",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/53/9e/068074156b38bab7f732977a4031",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/22/8e/3fccd3a8c9761c9e60ee4f5ecd85",
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/1f/ae/abe3f105b3c4b51f6b7942773305",
"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",
@ -3995,50 +3995,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b4/b2/d9d81b227c329f77198d96ee2ae1",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/06/0d/c5503c7e9b4e8c5465b7df36c3ab",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2e/57/6c8e84496af8bcfc60b6030f9008",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f1/37/e421b0f64743dd33ff9e3db54838",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/83/153f01e88ee01db4e2dd234ce189",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/0f/9b/66b3b2c089137e81496868f9c829",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5a/2e/0b387964a1cbe7658f353b0c3d58",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/77/52/ae35cb8cfea02c60417968702b3f",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/cf/55/d502cfa9bef1142b7cc240759c07",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c7/59/4d6c76a8e57478a81804b446cec7",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/5c/68871f87dd6353ca6693b24c895c",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/69/34/d573542acf00286337a8a7d4070e",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/54/21/ad891fc900eb06eb307401887479",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/10/6d/b079707d97db261651539cf42be4",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/9f/ec/d6f090057a354d6f27778a79151d",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2f/8d/7076055efd990269b20af15d092a",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/b6/9b/04419fdb2911f107ba2cf3d1daef",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/89/c0/d3b177978d11d0283b0290e88960",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/32/23/ce5919233ff0438442f6e6e66a62",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/99/51/13dbd2e177dd17314b4bc86fe200",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e2/88/53757bc9fd92d49bd35dc6d3be0e",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/69/078f7eef49a1127a1492db4703f6",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/82/f1/2b13fe77164f72d2bf57453bb8e5",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/b0/a853b61ab794706bbf395ecd2a80",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/76/3d/b0d2913a1650bdc35b2ca0d81154",
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/b3/39a0642a376e1f131172f9500353",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/07/4804a222e0d92f0fab8b279ce4c1",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/55/09/bf8e7d6ce41962163411c6bbd884",
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/be/5e0b2be7272c4e443cc974d5b182",
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5e/ab/075e9137d21e6110d29b67210533",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/2a/c5e1d5ed4328c40821695db2cd84",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b7/dd/55210b3f3c075d9b237e0c6aa733",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a2/00/d01842e8b0777f7e6ea47c912b16",
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/91/f9cb15d0876750e28abe3b0d221c",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b7/11/1ecfe322ae997772b71538664cad",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8d/d6/1e83dba73d581cfb2b2f6eb31f22",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/50/6a/ca5e49b3cad047b541648dc9914d",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/6f/f7/b38282cfbd3cb0cda89d5a458176",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/97/e5/1d2e76fcadbe022d4c522c7d2135",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/54/d6/2d642477837c34c946b70e27014d",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/79/2b/f17b81bdf03719098a558305f0a3",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/9a/5a/3ca5679187f8dd5f89d8bb68ed84",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/4b/88/d016d4059ea7b334e95d1e6ef258",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/1b/35/b11d851fc912b7bcd57766981fa2",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/7c/40/4d11b867dbfdab90a1601f31b792",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c1/64/71a9087e53146cc5377135b42b73",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/32/bd/f82f8059a47fd84e5d86d8814ecb",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/68/a5/24e48c50d5325869c9ce56b691ba",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f5/0b/9694a6942b8f6580eb041040b7f4",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a8/53/ab21700e2be712f046b6c11e6061",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a5/bd/6a29dc921daec7c8508fbf17b24a",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/71/80d7091f262bff97ff18a4bb8948",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f1/0e/c3738bcfce8a0ff51140f95ec9a5",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/7e/3ecd464aa666da920625a8336ccf",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c4/12/244ec02b1e210441832b9265d96b",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1a/42/7ff1c8244886f3a8a3772ffe82e1",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/25/50/b37e83853bc4a5549686f79d4346",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fa/80/4e12c28bd617bcee0b3402f6bfcc",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/dd/64/9c8a9a811af36110da0d875ef694",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/43/ba/6d7956bc670c75758e74615a9f3f",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7c/29/68fdb792ec55c6ba404b09bba49c",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/bb/1b/2d3c1944086c8e70fe396aeb73ff",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/b4/e5/a46580be7e047d2f0d12587b3e30",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/f5/a5/98b120cfcc070679e032ee684ca3",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/a6/c963859c531bb19f507f405cf589",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/df/dc/79349a169d3b00964d9f35853f84",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a7/63/d057e19cb7806302b9570b91c573",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/c0/3da5a81581aa9275b6c32bb03fc8",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/84/d66202e8a15d5518f876dccdd6d6",
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/ba/3a6f95b9e4a9c310ac63d0bc0a8c",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/58/9462e34601e2f442eb429185ee35",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/09/6e8feb718cb60ea80ecaab4ba9a0",
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7d/a3/f8fd8ad1037e5f5b47b72a6d1edf",
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/c6/3902a717b71f9f8781d724c8ca23",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/5a/3b25cbbca51ef2a6036d8d1805aa",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fd/11/f0fb88f01753350f88f068b6c6a0",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/22/d3700b99ec02b9bd99b8801ad69b",
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8b/d8/4b2e840ace5be8dd8fc9d6841cdd",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/07/545eec0e6bde25bba8b3857d7e9b",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b4/ae/d9a2b38dc9824ac6acc79d520404",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/4a/d3/b01764e89eafd5b9a3ffc2e2e540",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/9a/0e/ce412ed759cb5275b647f19b33ff",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/96/6d/d2aa5bbb9d9f2d3f5fd51042d50b",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/bc/ed/250948d00952c6e690e3e24511f6",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/59/2e/2b4b7140eb237e6ce00b7ec975db",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/7f/0a/1ee8835881fb9af7a3c3576de59a",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/b6/3d/6a800860c35bd41f4aad2199b10f",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/d3/7d/3fee0a9e0b2d4ad3601722def1c8",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"

View File

@ -153,6 +153,7 @@
<w>assigninput</w>
<w>astc</w>
<w>astcenc</w>
<w>astr</w>
<w>astroid</w>
<w>asus</w>
<w>asynchat</w>
@ -261,6 +262,8 @@
<w>blocksize</w>
<w>bluetooth</w>
<w>bmag</w>
<w>bmas</w>
<w>bmasl</w>
<w>bname</w>
<w>bndl</w>
<w>boffs</w>
@ -343,6 +346,7 @@
<w>campaignname</w>
<w>cancelbtn</w>
<w>capb</w>
<w>caplog</w>
<w>capturetheflag</w>
<w>carentity</w>
<w>cashregistersound</w>
@ -604,6 +608,8 @@
<w>depsets</w>
<w>depsval</w>
<w>dereferencing</w>
<w>deregistering</w>
<w>deregistration</w>
<w>descpos</w>
<w>desctype</w>
<w>dest</w>
@ -679,6 +685,7 @@
<w>dropdir</w>
<w>drumroll</w>
<w>dsqlite</w>
<w>dstabs</w>
<w>dstattr</w>
<w>dstbase</w>
<w>dstdata</w>
@ -1725,6 +1732,7 @@
<w>nvidia</w>
<w>nyko</w>
<w>obj's</w>
<w>objb</w>
<w>objid</w>
<w>objname</w>
<w>objs</w>
@ -2375,6 +2383,7 @@
<w>splitlen</w>
<w>splitnumstr</w>
<w>squadcore</w>
<w>srcabs</w>
<w>srcattr</w>
<w>srcdata</w>
<w>srcdir</w>
@ -2498,6 +2507,7 @@
<w>sysconfigdata</w>
<w>sysctl</w>
<w>syslogmodule</w>
<w>sysresponse</w>
<w>tabdefs</w>
<w>tabtype</w>
<w>tabtypes</w>
@ -2594,6 +2604,7 @@
<w>this'll</w>
<w>thislinelen</w>
<w>thismodule</w>
<w>threadlocals</w>
<w>threadpool</w>
<w>threadtype</w>
<w>throwiness</w>
@ -2925,6 +2936,7 @@
<w>zlib</w>
<w>zlibmodule</w>
<w>zoneid</w>
<w>zoneids</w>
<w>zoneinfo</w>
<w>zoomtext</w>
<w>zpings</w>

View File

@ -1,9 +1,16 @@
### 1.7.10 (build 20887, api 7, 2022-09-29)
### 1.7.10 (build 20889, api 7, 2022-10-07)
- Added eval support for cloud-console. This means you can type something like '1+1' in the console and see '2' printed. This is how Python behaves in the stdin console or in-game console or the standard Python interpreter.
- Exceptions in the cloud-console now print to stderr instead of logging.exception(). This means they aren't a pretty red color anymore, but this will keep cloud-console behaving well with things like servers where logging.exception() might trigger alarms or otherwise. This is also consistent with standard interactive Python behavior.
- Cloud console now shows the device name at the top instead of simply 'Console' while connected.
- Moved the function that actually runs cloud console code to `ba._cloud.cloud_console_exec()`.
- Added efro.debug which contains useful functionality for debugging object reference issues and memory leaks on live app instances (via cloud shell or whatever).
- Lots of reworking/polishing in general on communication between the game and v2 regional/master servers in preparation of upgrading Google Play accounts to V2. Please holler if anything is not working smoothly with a V2 account.
- When establishing V2 master-server communication, if the closest regional server is down or too busy, will now fall back to farther ones instead of giving up. You can follow this process by setting env var `BA_DEBUG_PRINT_V2_TRANSPORT` to 1 when running the app.
- Network testing now skips the alternate v1 master server addr if the primary succeeded. The alternate often fails which makes things look broken even though the game is ok as long as primary works.
- The v2-transport system will now properly reestablish account connectivity when asked to refresh its connection (the cloud does this periodically so regional cloud servers can be restarted as needed). Practically this means your app won't stop showing up under the ballistica.net devices section after its been running for a while; a problem previous builds had.
- The v2-transport system can now establish more than one connection at a time, which allows the app to gracefully transition to a new connection when the old is about to expire without any period of no connectivity. To test this functionality, set env var `BA_DEBUG_PRINT_V2_TRANSPORT=1` to see transport debug messages and `BA_DEBUG_V2_TRANSPORT_SHORT_DURATION=1` to cause the cloud to request a connection-refresh every 30 seconds or so.
- V2 accounts now consider themselves instantly signed in if they were signed in when the app last ran. They still need to contact the master-server before anything important can happen, but this should help keep things feel faster in general.
- Due to v2-transport improvements, pressing the 'End Session Now' button in ballistica.net account settings should now instantly log you out of all apps using that session.
### 1.7.9 (build 20880, api 7, 2022-09-24)
- Cleaned up the efro.message system to isolate response types that are used purely internally (via a new SysResponse type).

View File

@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import _ba
if TYPE_CHECKING:
pass
from typing import Any
class AccountV2Subsystem:
@ -111,10 +111,21 @@ class AccountV2Subsystem:
class AccountV2Handle:
"""Handle for interacting with a v2 account."""
"""Handle for interacting with a V2 account.
This class supports the 'with' statement, which is how it is
used with some operations such as cloud messaging.
"""
def __init__(self) -> None:
self.tag = '?'
self.workspacename: str | None = None
self.workspaceid: str | None = None
def __enter__(self) -> None:
"""Support for "with" statement.
"""
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
"""Support for "with" statement."""

View File

@ -45,7 +45,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
expected_build = 20887
expected_build = 20889
running_build: int = env['build_number']
if running_build != expected_build:
print(

View File

@ -56,10 +56,11 @@ class MainMenuWindow(ba.Window):
self._gather_button: ba.Widget | None = None
self._start_button: ba.Widget | None = None
self._watch_button: ba.Widget | None = None
self._gc_button: ba.Widget | None = None
self._account_button: ba.Widget | None = None
self._how_to_play_button: ba.Widget | None = None
self._credits_button: ba.Widget | None = None
self._settings_button: ba.Widget | None = None
self._next_refresh_allow_time = 0.0
self._store_char_tex = self._get_store_char_tex()
@ -71,7 +72,7 @@ class MainMenuWindow(ba.Window):
self._account_state_num = ba.internal.get_v1_account_state_num()
self._account_type = (ba.internal.get_v1_account_type()
if self._account_state == 'signed_in' else None)
self._refresh_timer = ba.Timer(1.0,
self._refresh_timer = ba.Timer(0.27,
ba.WeakCall(self._check_refresh),
repeat=True,
timetype=ba.TimeType.REAL)
@ -132,11 +133,15 @@ class MainMenuWindow(ba.Window):
if not self._root_widget:
return
now = ba.time(ba.TimeType.REAL)
if now < self._next_refresh_allow_time:
return
# Don't refresh for the first few seconds the game is up so we don't
# interrupt the transition in.
ba.app.main_menu_window_refresh_check_count += 1
if ba.app.main_menu_window_refresh_check_count < 4:
return
# ba.app.main_menu_window_refresh_check_count += 1
# if ba.app.main_menu_window_refresh_check_count < 4:
# return
store_char_tex = self._get_store_char_tex()
account_state_num = ba.internal.get_v1_account_state_num()
@ -251,11 +256,11 @@ class MainMenuWindow(ba.Window):
size=(self._button_width, self._button_height),
autoselect=self._use_autoselect,
label=ba.Lstr(resource=self._r +
('.endTestText' if self._is_benchmark()
else '.endGameText')),
('.endTestText' if self._is_benchmark(
) else '.endGameText')),
on_activate_call=(self._confirm_end_test
if self._is_benchmark()
else self._confirm_end_game))
if self._is_benchmark() else
self._confirm_end_game))
# Assume we're in a client-session.
else:
ba.buttonwidget(
@ -428,6 +433,7 @@ class MainMenuWindow(ba.Window):
self._tdelay = 2.0
self._t_delay_inc = 0.02
self._t_delay_play = 1.7
self._next_refresh_allow_time = ba.time(ba.TimeType.REAL) + 2.01
ba.app.did_menu_intro = True
self._width = 400.0
self._height = 200.0
@ -609,7 +615,7 @@ class MainMenuWindow(ba.Window):
this_b_width = self._button_width
h, v, scale = positions[self._p_index]
self._p_index += 1
self._gc_button = ba.buttonwidget(
self._account_button = ba.buttonwidget(
parent=self._root_widget,
position=(h - this_b_width * 0.5 * scale, v),
size=(this_b_width, self._button_height),
@ -637,7 +643,7 @@ class MainMenuWindow(ba.Window):
tilt_scale=0.0)
self._tdelay += self._t_delay_inc
else:
self._gc_button = None
self._account_button = None
# How-to-play button.
h, v, scale = positions[self._p_index]
@ -852,7 +858,7 @@ class MainMenuWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
AccountSettingsWindow(
origin_widget=self._gc_button).get_root_widget())
origin_widget=self._account_button).get_root_widget())
def _on_store_pressed(self) -> None:
# pylint: disable=cyclic-import
@ -870,7 +876,7 @@ class MainMenuWindow(ba.Window):
def _is_benchmark(self) -> bool:
session = ba.internal.get_foreground_host_session()
return (getattr(session, 'benchmark_type', None) == 'cpu'
or ba.app.stress_test_reset_timer is not None)
or ba.app.stress_test_reset_timer is not None)
def _confirm_end_game(self) -> None:
# pylint: disable=cyclic-import
@ -984,8 +990,8 @@ class MainMenuWindow(ba.Window):
ba.app.ui.main_menu_selection = 'Credits'
elif sel == self._settings_button:
ba.app.ui.main_menu_selection = 'Settings'
elif sel == self._gc_button:
ba.app.ui.main_menu_selection = 'GameService'
elif sel == self._account_button:
ba.app.ui.main_menu_selection = 'Account'
elif sel == self._store_button:
ba.app.ui.main_menu_selection = 'Store'
elif sel == self._quit_button:
@ -1016,8 +1022,8 @@ class MainMenuWindow(ba.Window):
sel = self._credits_button
elif sel_name == 'Settings':
sel = self._settings_button
elif sel_name == 'GameService':
sel = self._gc_button
elif sel_name == 'Account':
sel = self._account_button
elif sel_name == 'Store':
sel = self._store_button
elif sel_name == 'Quit':

View File

@ -146,13 +146,14 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
ba.pushcall(_print_in_logic_thread, from_other_thread=True)
def _print_test_results(call: Callable[[], Any]) -> None:
"""Run the provided call; return success/fail text & color."""
def _print_test_results(call: Callable[[], Any]) -> bool:
"""Run the provided call, print result, & return success."""
starttime = time.monotonic()
try:
call()
duration = time.monotonic() - starttime
_print(f'Succeeded in {duration:.2f}s.', color=(0, 1, 0))
return True
except Exception as exc:
import traceback
duration = time.monotonic() - starttime
@ -161,6 +162,7 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
_print(msg, color=(1.0, 1.0, 0.3))
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
have_error[0] = True
return False
try:
_print(f'Running network diagnostics...\n'
@ -177,14 +179,23 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
# V1 ping
baseaddr = ba.internal.get_master_server_address(source=0, version=1)
_print(f'\nContacting V1 master-server src0 ({baseaddr})...')
_print_test_results(lambda: _test_fetch(baseaddr))
v1worked = _print_test_results(lambda: _test_fetch(baseaddr))
# V1 alternate ping
baseaddr = ba.internal.get_master_server_address(source=1, version=1)
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
_print_test_results(lambda: _test_fetch(baseaddr))
# V1 alternate ping (only if primary fails since this often fails).
if v1worked:
_print('\nSkipping V1 master-server src1 test since src0 worked.')
else:
baseaddr = ba.internal.get_master_server_address(source=1,
version=1)
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
_print_test_results(lambda: _test_fetch(baseaddr))
_print(f'\nV1-test-log: {ba.app.net.v1_test_log}')
if 'none succeeded' in ba.app.net.v1_test_log:
_print(f'\nV1-test-log failed: {ba.app.net.v1_test_log}',
color=(1, 0, 0))
have_error[0] = True
else:
_print(f'\nV1-test-log ok: {ba.app.net.v1_test_log}')
for srcid, result in sorted(ba.app.net.v1_ctest_results.items()):
_print(f'\nV1 src{srcid} result: {result}')

View File

@ -79,6 +79,7 @@
<w>asci</w>
<w>assetsmakefile</w>
<w>assigninput</w>
<w>astr</w>
<w>atest</w>
<w>athome</w>
<w>attrobj</w>
@ -144,6 +145,8 @@
<w>blockwidth</w>
<w>bluetooth</w>
<w>blurscale</w>
<w>bmas</w>
<w>bmasl</w>
<w>bname</w>
<w>bodyid</w>
<w>bodypart</w>
@ -197,6 +200,7 @@
<w>camerashake</w>
<w>cancelbtn</w>
<w>capitan</w>
<w>caplog</w>
<w>cargs</w>
<w>cbegin</w>
<w>cbgn</w>
@ -329,6 +333,8 @@
<w>demangled</w>
<w>demangling</w>
<w>denom</w>
<w>deregistering</w>
<w>deregistration</w>
<w>dernit</w>
<w>desctype</w>
<w>destdir</w>
@ -364,6 +370,7 @@
<w>drpt</w>
<w>dsize</w>
<w>dsound</w>
<w>dstabs</w>
<w>dstattr</w>
<w>dstnode</w>
<w>dstpath</w>
@ -906,6 +913,7 @@
<w>nval</w>
<w>nvidia</w>
<w>nyffenegger</w>
<w>objb</w>
<w>objexists</w>
<w>objid</w>
<w>objtoyaml</w>
@ -1234,6 +1242,7 @@
<w>sphrand</w>
<w>spinup</w>
<w>spivak</w>
<w>srcabs</w>
<w>srcattr</w>
<w>srcfolder</w>
<w>srcid</w>
@ -1303,6 +1312,7 @@
<w>swiftc</w>
<w>symbolification</w>
<w>syscalls</w>
<w>sysresponse</w>
<w>tabdefs</w>
<w>tabtype</w>
<w>tabtypes</w>
@ -1335,6 +1345,7 @@
<w>theres</w>
<w>thislinelen</w>
<w>thismodule</w>
<w>threadlocals</w>
<w>threadname</w>
<w>threadpool</w>
<w>threadtype</w>
@ -1522,6 +1533,7 @@
<w>zoffset</w>
<w>zomg</w>
<w>zoneid</w>
<w>zoneids</w>
<w>zoneinfo</w>
<w>zoomable</w>
<w>zpings</w>

View File

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

View File

@ -152,6 +152,8 @@ void V1Account::SetLogin(V1AccountType account_type, V1LoginState login_state,
// We call out to Python so need to be in logic thread.
assert(InLogicThread());
// We want redundant sets to be no-ops.
if (login_state_ != login_state || g_app->account_type != account_type
|| login_id_ != login_id || login_name_ != login_name) {
// Special case: if they sent a sign-out for an account type that is

View File

@ -1,10 +1,12 @@
# Released under the MIT License. See LICENSE for details.
#
"""Testing message functionality."""
# pylint: disable=too-many-lines
from __future__ import annotations
import os
import logging
import asyncio
from typing import TYPE_CHECKING, overload
from dataclasses import dataclass
@ -198,7 +200,7 @@ class _TestMessageSenderBBoth(MessageSender):
"""Protocol-specific sender."""
def __init__(self) -> None:
protocol = TEST_PROTOCOL_B
protocol = TEST_PROTOCOL_EVOLVED
super().__init__(protocol)
def __get__(self,
@ -427,12 +429,14 @@ TEST_PROTOCOL = MessageProtocol(
1: _TResp2,
},
forward_clean_errors=True,
forward_communication_errors=True,
remote_errors_include_stack_traces=True,
)
# Represents an 'evolved' TEST_PROTOCOL (one extra message type added).
# (so we can test communication failures talking to older protocols)
TEST_PROTOCOL_B = MessageProtocol(
# Represents an 'evolved' TEST_PROTOCOL (the same as TEST_PROTOCOL; just
# one extra message type added).
# This way we can test communication failures talking to older protocols.
TEST_PROTOCOL_EVOLVED = MessageProtocol(
message_types={
0: _TMsg1,
1: _TMsg2,
@ -444,6 +448,7 @@ TEST_PROTOCOL_B = MessageProtocol(
1: _TResp2,
},
forward_clean_errors=True,
forward_communication_errors=True,
remote_errors_include_stack_traces=True,
)
@ -581,9 +586,9 @@ def test_sender_module_both_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
smod = TEST_PROTOCOL_B.do_create_sender_module(
smod = TEST_PROTOCOL_EVOLVED.do_create_sender_module(
'TestMessageSenderBBoth',
protocol_create_code='protocol = TEST_PROTOCOL_B',
protocol_create_code='protocol = TEST_PROTOCOL_EVOLVED',
enable_sync_sends=True,
enable_async_sends=True,
private=True,
@ -741,7 +746,7 @@ def test_receiver_creation() -> None:
receiver.validate()
def test_full_pipeline() -> None:
def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
"""Test the full pipeline."""
# pylint: disable=too-many-locals
@ -816,6 +821,45 @@ def test_full_pipeline() -> None:
if self.test_sidecar:
setattr(response, '_sidecar_data', indata['_sidecar_data'])
# Alternate sender for testing other protocol options.
class TestClassSAlt:
"""Test class incorporating send functionality."""
msg = _TestMessageSenderSingle()
test_handling_unregistered = False
test_send_method_exceptions = False
test_send_method_exceptions_comm = False
def __init__(self, target: TestClassRAlt) -> None:
self.test_sidecar = False
self._target = target
@msg.send_method
def _send_raw_message(self, data: str) -> str:
"""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.
# (currently only support synchronous receivers)
assert isinstance(self._target, TestClassRAlt)
try:
return self._target.receiver.handle_raw_message(
data, raise_unregistered=self.test_handling_unregistered)
except UnregisteredMessageIDError:
if self.test_handling_unregistered:
# Emulate forwarding unregistered messages on to some
# other handler...
response_dict = self.msg.protocol.response_to_dict(
EmptySysResponse())
return self.msg.protocol.encode_dict(response_dict)
raise
class TestClassRSync:
"""Test class incorporating synchronous receive functionality."""
@ -831,6 +875,8 @@ def test_full_pipeline() -> None:
raise CleanError('Testing Clean Error')
if msg.ival == 2:
raise RuntimeError('Testing Runtime Error')
if msg.ival == 3:
raise CommunicationError('Testing Communication Error')
out = _TResp1(bval=True)
if self.test_sidecar:
setattr(out, '_sidecar_data', getattr(msg, '_sidecar_data'))
@ -864,6 +910,30 @@ def test_full_pipeline() -> None:
receiver.validate()
class TestClassRAlt:
"""Test class incorporating synchronous receive functionality."""
receiver = _TestSingleMessageReceiver()
def __init__(self) -> None:
self.test_sidecar = False
@receiver.handler
def handle_test_message_1(self, msg: _TMsg1) -> _TResp1:
"""Test."""
if msg.ival == 1:
raise CleanError('Testing Clean Error')
if msg.ival == 2:
raise RuntimeError('Testing Runtime Error')
if msg.ival == 3:
raise CommunicationError('Testing Communication Error')
out = _TResp1(bval=True)
if self.test_sidecar:
setattr(out, '_sidecar_data', getattr(msg, '_sidecar_data'))
return out
receiver.validate()
class TestClassRAsync:
"""Test class incorporating asynchronous receive functionality."""
@ -876,6 +946,8 @@ def test_full_pipeline() -> None:
raise CleanError('Testing Clean Error')
if msg.ival == 2:
raise RuntimeError('Testing Runtime Error')
if msg.ival == 3:
raise CommunicationError('Testing Communication Error')
return _TResp1(bval=True)
@receiver.handler
@ -897,35 +969,76 @@ def test_full_pipeline() -> None:
obj = TestClassS(target=obj_r_sync)
obj2 = TestClassS(target=obj_r_async)
obj_rb = TestClassRAlt()
objb = TestClassSAlt(target=obj_rb)
# Test sends (of sync and async varieties).
response1 = obj.msg.send(_TMsg1(ival=0))
response2 = obj.msg.send(_TMsg2(sval='rah'))
response3 = obj.msg.send(_TMsg3(sval='rah'))
response4 = asyncio.run(obj.msg.send_async(_TMsg1(ival=0)))
# Make sure static typing lines up with what we expect.
if os.environ.get('EFRO_TEST_MESSAGE_FAST') != '1':
# assert static_type_equals(response1, _TResp1)
assert_type(response1, _TResp1)
# assert static_type_equals(response3, None)
assert_type(response3, None)
assert_type(response1, _TResp1)
assert isinstance(response1, _TResp1)
response1b = objb.msg.send(_TMsg1(ival=0))
assert_type(response1b, _TResp1)
response2 = obj.msg.send(_TMsg2(sval='rah'))
assert isinstance(response2, (_TResp1, _TResp2))
response3 = obj.msg.send(_TMsg3(sval='rah'))
assert_type(response3, None)
assert response3 is None
response4 = asyncio.run(obj.msg.send_async(_TMsg1(ival=0)))
assert isinstance(response4, _TResp1)
# Remote CleanErrors should come across locally as the same
# (provided our protocol has enabled support for them).
# Nothing up to this point should have logged any warnings/errors/etc.
assert not any(r.levelno >= logging.WARNING for r in caplog.records)
# Remote CleanErrors should come across locally as the same and
# no errors should be logged.
# (since our protocol has forward_clean_errors enabled).
caplog.clear()
try:
_response5 = obj.msg.send(_TMsg1(ival=1))
except Exception as exc:
assert isinstance(exc, CleanError)
assert str(exc) == 'Testing Clean Error'
assert not caplog.records
# Other remote errors should result in RemoteError.
# Same using a protocol *without* forward_clean_errors should
# give us a generic RemoteError and log the error.
caplog.clear()
with pytest.raises(RemoteError):
_response5 = objb.msg.send(_TMsg1(ival=1))
assert (len(caplog.records) == 1
and caplog.records[0].levelno == logging.ERROR)
# Same with CommunicationErrors occurring on the peer; they should
# come back to us intact if forward_communication_errors is enabled
# and no errors should have been logged.
caplog.clear()
try:
_response5 = obj.msg.send(_TMsg1(ival=3))
except Exception as exc:
assert isinstance(exc, CommunicationError)
assert str(exc) == 'Testing Communication Error'
assert not caplog.records
# Same using a protocol *without* forward_clean_errors should
# give us a generic RemoteError and log an error.
caplog.clear()
with pytest.raises(RemoteError):
_response5 = objb.msg.send(_TMsg1(ival=3))
assert (len(caplog.records) == 1
and caplog.records[0].levelno == logging.ERROR)
# Misc other error types happening on peer should result in
# RemoteError and log message.
caplog.clear()
with pytest.raises(RemoteError):
_response5 = obj.msg.send(_TMsg1(ival=2))
# This should have logged a single error message.
assert (len(caplog.records) == 1
and caplog.records[0].levelno == logging.ERROR)
# Now test sends to async handlers.
response6 = asyncio.run(obj2.msg.send_async(_TMsg1(ival=0)))

View File

@ -71,11 +71,14 @@ class _ServerClientCommon:
async def send_message(self,
message: _Message,
timeout: float | None = None) -> _Message:
timeout: float | None = None,
close_on_error: bool = True) -> _Message:
"""Send high level messages."""
assert self._endpoint is not None
response = await self._endpoint.send_message(
dataclass_to_json(message).encode(), timeout=timeout)
dataclass_to_json(message).encode(),
timeout=timeout,
close_on_error=close_on_error)
return dataclass_from_json(_Message, response.decode())
async def handle_message(self, msg: _Message) -> _Message:
@ -364,12 +367,23 @@ def test_message_timeout() -> None:
_Message(_MessageType.TEST_SLOW))
assert resp.messagetype is _MessageType.RESPONSE_SLOW
# This message should time out.
# This message should time out but not close the connection.
with pytest.raises(CommunicationError):
resp = await tester.server.send_message(
_Message(_MessageType.TEST_SLOW),
timeout=0.5,
close_on_error=False,
)
assert not tester.server.endpoint.is_closing()
# This message should time out and close the connection as a result.
with pytest.raises(CommunicationError):
resp = await tester.server.send_message(
_Message(_MessageType.TEST_SLOW),
timeout=0.5,
close_on_error=True,
)
assert tester.server.endpoint.is_closing()
tester.run(_do_it())

View File

@ -61,6 +61,7 @@ class ErrorSysResponse(SysResponse):
REMOTE_CLEAN = 1
LOCAL = 2
COMMUNICATION = 3
REMOTE_COMMUNICATION = 4
error_message: Annotated[str, IOAttrs('m')]
error_type: Annotated[ErrorType, IOAttrs('e')] = ErrorType.REMOTE

View File

@ -8,10 +8,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import traceback
import logging
import json
from efro.error import CleanError
from efro.error import CleanError, CommunicationError
from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict,
dataclass_from_dict)
from efro.message._message import (Message, Response, SysResponse,
@ -34,24 +33,49 @@ class MessageProtocol:
def __init__(self,
message_types: dict[int, type[Message]],
response_types: dict[int, type[Response]],
forward_communication_errors: bool = False,
forward_clean_errors: bool = False,
remote_errors_include_stack_traces: bool = False) -> None:
remote_errors_include_stack_traces: bool = False,
log_remote_errors: bool = True) -> 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 'forward_communication_errors' is True,
efro.error.CommunicationErrors raised on the receiver end will
result in a matching error raised back on the sender. This can
be useful if the receiver will be in some way forwarding
messages along and the sender doesn't need to know where
communication breakdowns occurred; only that they did.
If 'forward_clean_errors' is True, efro.error.CleanError
exceptions raised on the receiver end will result in a matching
CleanError raised back on the sender. All other Exception types
come across as efro.error.RemoteError.
CleanError raised back on the sender.
If 'remote_errors_include_stack_traces' is True, stringified stack
traces will be returned to the sender for exceptions occurring
on the receiver end. This can make debugging easier but should
only be used when the client is trusted to see such info.
When an exception is not covered by the optional forwarding
mechanisms above, it will come across as efro.error.RemoteError
and the exception will be logged on the receiver end.
If 'remote_errors_include_stack_traces' is True, stringified
stack traces will be returned with efro.error.RemoteError
exceptions. This is useful for debugging but should only be
enabled in cases where the sender is trusted to see internal
details of the receiver.
By default, when a message-handling exception will result in an
efro.error.RemoteError being returned to the sender, the
exception will be logged on the receiver. This is because the
goal is usually to avoid returning opaque RemoteErrors and to
instead return something meaningful as part of an expected
response type (even if that value itself represents a logical
error state). If 'log_remote_errors' is False, however, such
exceptions will not be logged on the receiver. This can be
useful in combination with 'remote_errors_include_stack_traces'
and 'forward_clean_errors' in situations where all error
logging/management will be happening on the sender end. Be
aware, however, that in that case
efro.error.CommunicationErrors can possibly prevent such error
messages from ever being seen.
"""
# pylint: disable=too-many-locals
self.message_types_by_id: dict[int, type[Message]] = {}
self.message_ids_by_type: dict[type[Message], int] = {}
self.response_types_by_id: dict[int, type[Response]
@ -123,8 +147,10 @@ class MessageProtocol:
' all types are required to have unique names.')
self.forward_clean_errors = forward_clean_errors
self.forward_communication_errors = forward_communication_errors
self.remote_errors_include_stack_traces = (
remote_errors_include_stack_traces)
self.log_remote_errors = log_remote_errors
@staticmethod
def encode_dict(obj: dict) -> str:
@ -139,23 +165,31 @@ class MessageProtocol:
"""Encode a response to a json ready dict."""
return self._to_dict(response, self.response_ids_by_type, 'response')
def error_to_response(self, exc: Exception) -> SysResponse:
"""Translate an error to a response."""
def error_to_response(self, exc: Exception) -> tuple[SysResponse, bool]:
"""Translate an Exception to a SysResponse.
# Log any errors we got during handling.
logging.exception('Error in efro.message handling.')
Also returns whether the error should be logged if this happened
within handle_raw_message().
"""
# If anything goes wrong, return a ErrorSysResponse instead.
# (either CLEAN or generic REMOTE)
if isinstance(exc, CleanError) and self.forward_clean_errors:
return ErrorSysResponse(
if self.forward_clean_errors and isinstance(exc, CleanError):
return (ErrorSysResponse(
error_message=str(exc),
error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN)
return ErrorSysResponse(
error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN), False)
if self.forward_communication_errors and isinstance(
exc, CommunicationError):
return (ErrorSysResponse(
error_message=str(exc),
error_type=ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION),
False)
return (ErrorSysResponse(
error_message=(traceback.format_exc()
if self.remote_errors_include_stack_traces else
'An internal error has occurred.'),
error_type=ErrorSysResponse.ErrorType.REMOTE)
error_type=ErrorSysResponse.ErrorType.REMOTE),
self.log_remote_errors)
def _to_dict(self, message: Any, ids_by_type: dict[type, int],
opname: str) -> dict:

View File

@ -260,14 +260,14 @@ class MessageReceiver:
return self.protocol.encode_dict(response_dict)
def encode_error_response(self, bound_obj: Any, message: Message | None,
exc: Exception) -> str:
"""Given an error, return a response ready for sending."""
response = self.protocol.error_to_response(exc)
exc: Exception) -> tuple[str, bool]:
"""Given an error, return sysresponse str and whether to log."""
response, dolog = self.protocol.error_to_response(exc)
response_dict = self.protocol.response_to_dict(response)
if self._encode_filter_call is not None:
self._encode_filter_call(bound_obj, message, response,
response_dict)
return self.protocol.encode_dict(response_dict)
return self.protocol.encode_dict(response_dict), dolog
def handle_raw_message(self,
bound_obj: Any,
@ -296,7 +296,11 @@ class MessageReceiver:
if (raise_unregistered
and isinstance(exc, UnregisteredMessageIDError)):
raise
return self.encode_error_response(bound_obj, msg_decoded, exc)
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
exc)
if dolog:
logging.exception('Error in efro.message handling.')
return rstr
async def handle_raw_message_async(
self,
@ -324,7 +328,11 @@ class MessageReceiver:
if (raise_unregistered
and isinstance(exc, UnregisteredMessageIDError)):
raise
return self.encode_error_response(bound_obj, msg_decoded, exc)
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
exc)
if dolog:
logging.exception('Error in efro.message handling.')
return rstr
class BoundMessageReceiver:
@ -348,9 +356,9 @@ class BoundMessageReceiver:
"""Given an error, return a response ready to send.
This should be used for any errors that happen outside of
of standard handle_raw_message calls. Any errors within those
calls should be automatically returned as encoded strings.
standard handle_raw_message calls. Any errors within those
calls will be automatically returned as encoded strings.
"""
# Passing None for Message here; we would only have that available
# for things going wrong in the handler (which this is not for).
return self._receiver.encode_error_response(self._obj, None, exc)
return self._receiver.encode_error_response(self._obj, None, exc)[0]

View File

@ -272,6 +272,11 @@ class MessageSender:
is ErrorSysResponse.ErrorType.REMOTE_CLEAN):
raise CleanError(raw_response.error_message)
if (self.protocol.forward_communication_errors
and raw_response.error_type is
ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION):
raise CommunicationError(raw_response.error_message)
# Everything else gets lumped in as a remote error.
raise RemoteError(raw_response.error_message,
peer_desc=('peer' if self._peer_desc_call is None

View File

@ -290,17 +290,24 @@ class RPCEndpoint:
async def send_message(self,
message: bytes,
timeout: float | None = None) -> bytes:
timeout: float | None = None,
close_on_error: bool = True) -> bytes:
"""Send a message to the peer and return a response.
If timeout is not provided, the default will be used.
Raises a CommunicationError if the round trip is not completed
for any reason.
By default, the entire endpoint will go down in the case of
errors. This allows messages to be treated as 'reliable' with
respect to a given endpoint. Pass close_on_error=False to
override this for a particular message.
"""
# pylint: disable=too-many-branches
self._check_env()
if self._closing:
raise CommunicationError('Endpoint is closed')
raise CommunicationError('Endpoint is closed.')
# We need to know their protocol, so if we haven't gotten a handshake
# from them yet, just wait.
@ -358,6 +365,8 @@ class RPCEndpoint:
if self._debug_print:
self._debug_print_call(
f'{self._label}: message {message_id} was cancelled.')
if close_on_error:
self.close()
raise CommunicationError() from exc
except asyncio.TimeoutError as exc:
if self._debug_print:
@ -370,6 +379,9 @@ class RPCEndpoint:
# Remove the record of this message.
del self._in_flight_messages[message_id]
if close_on_error:
self.close()
# Let the user know something went wrong.
raise CommunicationError() from exc
@ -407,7 +419,11 @@ class RPCEndpoint:
return self._closing
async def wait_closed(self) -> None:
"""I said seagulls; mmmm; stop it now."""
"""I said seagulls; mmmm; stop it now.
Wait for the endpoint to finish closing. This is called by run()
so generally does not need to be explicitly called.
"""
# pylint: disable=too-many-branches
self._check_env()
@ -729,8 +745,9 @@ class RPCEndpoint:
def _check_env(self) -> None:
# I was seeing that asyncio stuff wasn't working as expected if
# created in one thread and used in another, so let's enforce
# a single thread for all use of an instance.
# created in one thread and used in another (and have verified
# that this is part of the design), so let's enforce a single
# thread for all use of an instance.
if current_thread() is not self._thread:
raise RuntimeError('This must be called from the same thread'
' that the endpoint was created in.')

View File

@ -433,11 +433,11 @@ def _filter_tool_config(cfg: str) -> str:
# Short project name.
short_names = {
'ballistica-internal': 'ba-int',
'ballistica-internal': 'ba-i',
'ballistica': 'ba',
'ballistica-master-server': 'bamaster',
'ballistica-master-server-legacy': 'bamasterlegacy',
'ballistica-server-node': 'baservnode',
'ballistica-master-server': 'bmas',
'ballistica-master-server-legacy': 'bmasl',
'ballistica-server-node': 'basn',
}
shortname = short_names.get(PROJROOT.name, PROJROOT.name)
cfg = cfg.replace('__EFRO_PROJECT_SHORTNAME__', shortname)

View File

@ -205,8 +205,10 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
continue
# Src/dst hashes don't match and marker doesn't match either.
# We give up.
srcabs = os.path.abspath(srcfile)
dstabs = os.path.abspath(dstfile)
raise RuntimeError(
f'both src and dst sync files changed: {srcfile} {dstfile}'
f'both src and dst sync files changed: {srcabs} {dstabs}'
'; this must be resolved manually.')
# (if we got here this file should be healthy..)