mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-26 00:47:10 +08:00
version bump
This commit is contained in:
parent
e0ea183ef4
commit
667da7eef2
@ -420,10 +420,10 @@
|
||||
"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/98/28/2d0235ac9ccd0b800f832ab7fbb3",
|
||||
"assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/9b/ec/d11f3e0238ff648bce3657fe5d50",
|
||||
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/92/43/36b34307575f6d6219bdf4898e18",
|
||||
"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/aa/ed/4bd02af3cffbd4c9c4be532fb1fe",
|
||||
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/96/96/1390940b8457b477113194acbb41",
|
||||
"assets/build/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/f7/b0/191439142c6d6da4a056edc98b38",
|
||||
"assets/build/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/c9/73/01a1343af814131b1ee96af0b687",
|
||||
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/4e/8c/72ddb584856a15dfb11df95f9283",
|
||||
@ -449,11 +449,11 @@
|
||||
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/6c/62/06869ed55a656b6e51b4d22e6fa8",
|
||||
"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/20/a9/163d189884edf802636bf291e432",
|
||||
"assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/f5/35/1dea74424ee613611ae3f7deec99",
|
||||
"assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/0b/21/a4d09ca1fea8bbf347ed7091c8a2",
|
||||
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/3b/9f/d40c1423d260784970fd7364ca30",
|
||||
"assets/build/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/3d/83/e1bb0a664d1c14c41b1a083acf0d",
|
||||
"assets/build/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/d6/16/523c643358880b03b233ed88e557",
|
||||
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/2a/0e/b164149e76efd0e6d591a9bf04bb",
|
||||
"assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/63/c8/6cfbfd6702c80aa9df490e4629d7",
|
||||
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/3e/b6/052f1faed0264bf7135feb5c4cc3",
|
||||
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/a6/ed/416638d46950c9ab4f6155b9c334",
|
||||
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/1f/ae/abe3f105b3c4b51f6b7942773305",
|
||||
@ -4008,50 +4008,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/1c/77/ac670a5118abdf8a7687af0e159b",
|
||||
"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/a0/31/79276810bb7e7a63b16a813762ed",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/ff/ffe14193d7ecc7be7bd4b8f0c1df",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1b/35/085b6877ca6132f22363935c556f",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ea/6d/812e4be178b1bfac568dfbef1cd5",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/01/81/44c58651a270c66621fac233af03",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bc/7f/fcb81a65171d668c02b43ad8a0ca",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/97/d3/a39c9949110c3258662e37a01218",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/34/c6/b9f70ebc862ea8c0824c20c21a8c",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d4/1d/5fce150d3ffb8286eccc3c932070",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7d/22/88ba10738a5fa80022fd4e43cd63",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/14/01/f113a6e70aa0f69753513c890d2e",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4c/16/0a6993916d2b486cf1e3a96b5855",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8d/54/7a2f804e29e8c43de5d17f3fb303",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/98/aa/4decee9932b26500b9db74f68115",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e7/e2/8b1ec39a67b0ac7c23c76ea2b71d",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/59/ab/2756b60a87a798b89450d4ead9c6",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/43/f4/5289b9660fed0381c1d98626c0a6",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7f/9a/fde700fa2c7a32cd9c3695fe8e8d",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/ba/9a/81f247b9f980e7907b28633605e1",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/c1/83/499a6c6d1cf150ce0370680db482",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/1b/a2ab5dd8c59cd90f5cd28f451884",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a5/48/1e25219edd0a78e40c0f3093396d",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/dd/5d/51313d43cafea4898cc2ca7aaf3d",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b3/ca/9a7f4019bf2e57d668171c42c876",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4e/c8/478035d9e79dabc2a0cff7dc5c07",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/e3/5cac56d9f4e96a800cd424ecb01a",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/f2/f3afa4635c55863e2996ee110414",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/52/2e/3fef9d99b533a1ae5e1be3c63e21",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/d4/3578de762544210f09f9ef6b60a4",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/18/44/f6a99fe7cf886f8a1b77b1878c7a",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/3a/a7f7d3b3ee3bd7b1806bf77f2859",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e9/58/63afc99ce47eed08527f2dfc8f4d",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/43/ed/f49af3eb3646c69c5312ef8000dc",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/c5/091b0d55220c1c7a6939e00563d7",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b3/f4/69436953e88c28d027b7c8004ba3",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/37/d3/599f3904f5ee987d540164237703",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/5b/0e/a571f0b1d167f3a0d3f07ff6e633",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/86/3e/abdb5280c7e24e145bcf4cfc73d4",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/15/b7/76299570cd56fac624ec810c575c",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/39/07/b32bb5e8a4458adc0df92dad82f6",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/28/46/700cc357d8c776c3a34dbdeb31c8",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/71/f0/d3d64e44f03a10536649e000c836",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/42/9a/961f50a8d8d7b1d704a2616d76ca",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/4a/da/85bb42cb764293be0b4c515268ad",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/93/f0/feed13859ce5e9ea067b0e0529ff",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/0d/88/056f8877eb83cfa87fb581d09a52",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f3/1f/14bf78a27530eec8d30e54392788",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d5/f9/9acf2f6b0a04f122d937b671337b",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/21/b5/65486e588021c5a551a7faf60490",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4f/3f/39a86bf3e6556628bac7e038eb51",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6c/6f/7efeb16dffd3f1e205feb2ce99fa",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/21/e5/6e5bcfbf995ae8579638fc5d1488",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a0/7c/7a09ecb3beedfa91cde84e964bb9",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/5a/fa/8491b8dac79d172c33747862e77f",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4c/8f/90292472906d259c86c8a38bcbc7",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/18/c3/92d5a2465283e17bcf7af2bf26e8",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/4a/222725049d0619e8c8d97e60d079",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/e4/d6798a2d698da031a1f97b2fd008",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4e/96/d35f6076e1e01b7a959df4379b76",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f9/56/d0f8e1eb5f17c33a99ee032a42ff",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e6/fc/95cab0bca71d81826a30b7621852",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/99/93/23154000c5d940cd943deed2692f",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/ae/5b/3ccd55688d48429a7d2a4bd32919",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/ac/ef/5ed349d3c42d58a98a79c94404b0",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/3b/83e6150eebf4fa7b5e8a7b863219",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/44/70/2878a73f2d55849cd6a75e401575",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/49/4baca475df5c7f87e6ed17d46696",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b9/46/1c6b679ef9db6807100bc0bba261",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/da/b9/427dfd7ae8efbe6009964fe34beb",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ac/2c/fc0a576c3d957896bfd3de792af6",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/39/08/4033ab823798c48b3446c032a72c",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3a/c6/189744027136a7411d5dfa5c5cf4",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/c6/bb533c59368fdf45d65812f37a27",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/6e/88180b8e905df7453e0f989da027",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2c/99/e35a71c9c410da4035a0456314b6",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/b2/bb092304cd5e5f3bdb6e085197de",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9c/ce/523a32e2dce1174df21373ab5765",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b2/ee/b908410d85c763d5bca09a3bc893",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fd/24/8c51e5752064d4b487dff42a7ffa",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/c6/40e9e47dd33c88646510212ff321",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/a1/25/ca39d9309b63ed38bee3be1f9e67",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/b3/ef/a6240c67194508ac7bd58ba73391",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/c2/cc/b081c59aa873304086d2e44c1f30",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/d3/cb/8c1ed9ee3e8f1b0d866160257506",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/f0/f8/fb0aa403b29d5c5a9ebb5aec66fb",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/0b/03/ed35c58e80013f47b57c838c12d4",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/18/1d/cfc42a4939904783c897322207aa",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/68/82/c02d830bdb12f57ae976c5acc4fb",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/23/ce/68396b1b7ec6d2f8425902148140",
|
||||
"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"
|
||||
|
||||
3
.idea/dictionaries/ericf.xml
generated
3
.idea/dictionaries/ericf.xml
generated
@ -185,6 +185,7 @@
|
||||
<w>availplug</w>
|
||||
<w>aval</w>
|
||||
<w>awaitable</w>
|
||||
<w>awaitables</w>
|
||||
<w>axismotion</w>
|
||||
<w>bacfg</w>
|
||||
<w>backgrounded</w>
|
||||
@ -863,6 +864,7 @@
|
||||
<w>fcontents</w>
|
||||
<w>fcount</w>
|
||||
<w>fdata</w>
|
||||
<w>fdcount</w>
|
||||
<w>fdesc</w>
|
||||
<w>fdict</w>
|
||||
<w>fdout</w>
|
||||
@ -1276,6 +1278,7 @@
|
||||
<w>iprof</w>
|
||||
<w>isatty</w>
|
||||
<w>iscale</w>
|
||||
<w>iscoroutinefunction</w>
|
||||
<w>iserverget</w>
|
||||
<w>iserverput</w>
|
||||
<w>ispunch</w>
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
### 1.7.18 (build 20984, api 7, 2023-01-12)
|
||||
### 1.7.18 (build 20991, api 7, 2023-01-17)
|
||||
- Reworked some low level asynchronous messaging functionality in efro.message and efro.rpc. Previously these were a little *too* asynchronous which could lead to messages being received in a different order than they were sent, which is not desirable.
|
||||
- Added a way to suppress 'Your build is outdated' messages at launch (see `ba._hooks.show_client_too_old_error()`).
|
||||
|
||||
### 1.7.17 (build 20983, api 7, 2023-01-09)
|
||||
- V2 accounts now show a 'Unlink Legacy (V1) Accounts' button in account settings if they have any old V1 links present. This can be used to clear out old links to replace them with V2 links which work correctly with V2 accounts.
|
||||
|
||||
@ -36,7 +36,7 @@ class AccountV2Subsystem:
|
||||
# (or lack thereof) has completed. This includes things like
|
||||
# workspace syncing. Completion of this is what flips the app
|
||||
# into 'running' state.
|
||||
self._initial_login_completed = False
|
||||
self._initial_sign_in_completed = False
|
||||
|
||||
self._kicked_off_workspace_load = False
|
||||
|
||||
@ -98,7 +98,7 @@ class AccountV2Subsystem:
|
||||
if account.workspaceid is not None:
|
||||
assert account.workspacename is not None
|
||||
if (
|
||||
not self._initial_login_completed
|
||||
not self._initial_sign_in_completed
|
||||
and not self._kicked_off_workspace_load
|
||||
):
|
||||
self._kicked_off_workspace_load = True
|
||||
@ -121,9 +121,9 @@ class AccountV2Subsystem:
|
||||
return
|
||||
|
||||
# Ok; no workspace to worry about; carry on.
|
||||
if not self._initial_login_completed:
|
||||
self._initial_login_completed = True
|
||||
_ba.app.on_initial_login_completed()
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_ba.app.on_initial_sign_in_completed()
|
||||
|
||||
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
||||
"""Should be called when logins for the active account change."""
|
||||
@ -156,9 +156,9 @@ class AccountV2Subsystem:
|
||||
within a few seconds of app launch; the app can move forward
|
||||
with the startup sequence at that point.
|
||||
"""
|
||||
if not self._initial_login_completed:
|
||||
self._initial_login_completed = True
|
||||
_ba.app.on_initial_login_completed()
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_ba.app.on_initial_sign_in_completed()
|
||||
|
||||
@staticmethod
|
||||
def _hashstr(val: str) -> str:
|
||||
@ -271,7 +271,7 @@ class AccountV2Subsystem:
|
||||
self._implicit_state_changed = False
|
||||
|
||||
# Once we've made a move here we don't want to
|
||||
# do any more automatic ones.
|
||||
# do any more automatic stuff.
|
||||
self._can_do_auto_sign_in = False
|
||||
|
||||
else:
|
||||
@ -290,22 +290,23 @@ class AccountV2Subsystem:
|
||||
' of implicit state change...',
|
||||
)
|
||||
self._implicit_signed_in_adapter.sign_in(
|
||||
self._on_explicit_sign_in_completed
|
||||
self._on_explicit_sign_in_completed,
|
||||
description='implicit state change',
|
||||
)
|
||||
self._implicit_state_changed = False
|
||||
|
||||
# Once we've made a move here we don't want to
|
||||
# do any more automatic ones.
|
||||
# do any more automatic stuff.
|
||||
self._can_do_auto_sign_in = False
|
||||
|
||||
if not self._can_do_auto_sign_in:
|
||||
return
|
||||
|
||||
# If we're not currently signed in, we have connectivity, and
|
||||
# we have an available implicit login, auto-sign-in with it.
|
||||
# we have an available implicit login, auto-sign-in with it once.
|
||||
# The implicit-state-change logic above should keep things
|
||||
# mostly in-sync, but due to connectivity or other issues that
|
||||
# might not always be the case. We prefer to keep people signed
|
||||
# mostly in-sync, but that might not always be the case due to
|
||||
# connectivity or other issues. We prefer to keep people signed
|
||||
# in as a rule, even if there are corner cases where this might
|
||||
# not be what they want (A user signing out and then restarting
|
||||
# may be auto-signed back in).
|
||||
@ -324,7 +325,7 @@ class AccountV2Subsystem:
|
||||
)
|
||||
self._can_do_auto_sign_in = False # Only ATTEMPT once
|
||||
self._implicit_signed_in_adapter.sign_in(
|
||||
self._on_implicit_sign_in_completed
|
||||
self._on_implicit_sign_in_completed, description='auto-sign-in'
|
||||
)
|
||||
|
||||
def _on_explicit_sign_in_completed(
|
||||
@ -337,8 +338,8 @@ class AccountV2Subsystem:
|
||||
|
||||
del adapter # Unused.
|
||||
|
||||
# Make some noise on errors since the user knows
|
||||
# a sign-in attempt is happening in this case.
|
||||
# Make some noise on errors since the user knows a
|
||||
# sign-in attempt is happening in this case (the 'explicit' part).
|
||||
if isinstance(result, Exception):
|
||||
# We expect the occasional communication errors;
|
||||
# Log a full exception for anything else though.
|
||||
@ -347,6 +348,8 @@ class AccountV2Subsystem:
|
||||
'Error on explicit accountv2 sign in attempt.',
|
||||
exc_info=result,
|
||||
)
|
||||
|
||||
# For now just show 'error'. Should do better than this.
|
||||
with _ba.Context('ui'):
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.signInErrorText'),
|
||||
@ -395,9 +398,9 @@ class AccountV2Subsystem:
|
||||
_ba.app.accounts_v2.set_primary_credentials(result.credentials)
|
||||
|
||||
def _on_set_active_workspace_completed(self) -> None:
|
||||
if not self._initial_login_completed:
|
||||
self._initial_login_completed = True
|
||||
_ba.app.on_initial_login_completed()
|
||||
if not self._initial_sign_in_completed:
|
||||
self._initial_sign_in_completed = True
|
||||
_ba.app.on_initial_sign_in_completed()
|
||||
|
||||
|
||||
class AccountV2Handle:
|
||||
|
||||
@ -235,7 +235,7 @@ class App:
|
||||
self.state = self.State.LAUNCHING
|
||||
|
||||
self._launch_completed = False
|
||||
self._initial_login_completed = False
|
||||
self._initial_sign_in_completed = False
|
||||
self._meta_scan_completed = False
|
||||
self._called_on_app_running = False
|
||||
self._app_paused = False
|
||||
@ -511,7 +511,7 @@ class App:
|
||||
self.plugins.on_app_resume()
|
||||
self.health_monitor.on_app_resume()
|
||||
|
||||
if self._initial_login_completed and self._meta_scan_completed:
|
||||
if self._initial_sign_in_completed and self._meta_scan_completed:
|
||||
self.state = self.State.RUNNING
|
||||
if not self._called_on_app_running:
|
||||
self._called_on_app_running = True
|
||||
@ -724,8 +724,8 @@ class App:
|
||||
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
|
||||
def on_initial_login_completed(self) -> None:
|
||||
"""Callback to be run after initial login process (or lack thereof).
|
||||
def on_initial_sign_in_completed(self) -> None:
|
||||
"""Callback to be run after initial sign-in (or lack thereof).
|
||||
|
||||
This period includes things such as syncing account workspaces
|
||||
or other data so it may take a substantial amount of time.
|
||||
@ -736,5 +736,5 @@ class App:
|
||||
# (account workspaces).
|
||||
self.meta.start_extra_scan()
|
||||
|
||||
self._initial_login_completed = True
|
||||
self._initial_sign_in_completed = True
|
||||
self._update_state()
|
||||
|
||||
@ -476,35 +476,18 @@ def on_too_many_file_descriptors() -> None:
|
||||
real_time = _ba.time(TimeType.REAL)
|
||||
|
||||
def _do_log() -> None:
|
||||
import subprocess
|
||||
|
||||
pid = os.getpid()
|
||||
out = f'TOO MANY FDS at {real_time}.\nWe are pid {pid}\n'
|
||||
|
||||
out += (
|
||||
'FD Count: '
|
||||
+ subprocess.run(
|
||||
f'ls -l /proc/{pid}/fd | wc -l',
|
||||
shell=True,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
).stdout.decode()
|
||||
+ '\n'
|
||||
try:
|
||||
fdcount: int | str = len(os.listdir(f'/proc/{pid}/fd'))
|
||||
except Exception as exc:
|
||||
fdcount = f'? ({exc})'
|
||||
logging.warning(
|
||||
'TOO MANY FDS at %.2f. We are pid %d. FDCount is %s.',
|
||||
real_time,
|
||||
pid,
|
||||
fdcount,
|
||||
)
|
||||
|
||||
out += (
|
||||
'lsof output:\n'
|
||||
+ subprocess.run(
|
||||
f'lsof -p {pid}',
|
||||
shell=True,
|
||||
check=False,
|
||||
capture_output=True,
|
||||
).stdout.decode()
|
||||
+ '\n'
|
||||
)
|
||||
|
||||
logging.warning(out)
|
||||
|
||||
Thread(target=_do_log, daemon=True).start()
|
||||
|
||||
# import io
|
||||
|
||||
@ -47,7 +47,7 @@ def bootstrap() -> None:
|
||||
|
||||
# Give a soft warning if we're being used with a different binary
|
||||
# version than we expect.
|
||||
expected_build = 20984
|
||||
expected_build = 20991
|
||||
running_build: int = env['build_number']
|
||||
if running_build != expected_build:
|
||||
print(
|
||||
|
||||
@ -1,13 +1,13 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Snippets of code for use by the internal C++ layer.
|
||||
"""Snippets of code for use by the internal layer.
|
||||
|
||||
History: originally I would dynamically compile/eval bits of Python text
|
||||
from within C++ code, but the major downside there was that none of that was
|
||||
type-checked so if names or arguments changed I would never catch code breakage
|
||||
until the code was next run. By defining all snippets I use here and then
|
||||
capturing references to them all at launch I can immediately verify everything
|
||||
I'm looking for exists and pylint/mypy can do their magic on this file.
|
||||
History: originally the engine would dynamically compile/eval various Python
|
||||
code from within C++ code, but the major downside there was that none of it
|
||||
was type-checked so if names or arguments changed it would go unnoticed
|
||||
until it broke at runtime. By instead defining such snippets here and then
|
||||
capturing references to them all at launch it is possible to allow linting
|
||||
and type-checking magic to happen and most issues will be caught immediately.
|
||||
"""
|
||||
# (most of these are self-explanatory)
|
||||
# pylint: disable=missing-function-docstring
|
||||
@ -470,3 +470,33 @@ def login_adapter_get_sign_in_token_response(
|
||||
adapter = _ba.app.accounts_v2.login_adapters[login_type]
|
||||
assert isinstance(adapter, LoginAdapterNative)
|
||||
adapter.on_sign_in_complete(attempt_id=attempt_id, result=result)
|
||||
|
||||
|
||||
def show_client_too_old_error() -> None:
|
||||
"""Called at launch if the server tells us we're too old to talk to it."""
|
||||
from ba._language import Lstr
|
||||
|
||||
# If you are using an old build of the app and would like to stop
|
||||
# seeing this error at launch, do:
|
||||
# ba.app.config['SuppressClientTooOldErrorForBuild'] = ba.app.build_number
|
||||
# ba.app.config.commit()
|
||||
# Note that you will have to do that again later if you update to
|
||||
# a newer build.
|
||||
if (
|
||||
_ba.app.config.get('SuppressClientTooOldErrorForBuild')
|
||||
== _ba.app.build_number
|
||||
):
|
||||
return
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
translate=(
|
||||
'serverResponses',
|
||||
'Server functionality is no longer supported'
|
||||
' in this version of the game;\n'
|
||||
'Please update to a newer version.',
|
||||
)
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, final
|
||||
@ -57,6 +58,9 @@ class LoginAdapter:
|
||||
# current active primary account.
|
||||
self._active_login_id: str | None = None
|
||||
|
||||
self._last_sign_in_time: float | None = None
|
||||
self._last_sign_in_desc: str | None = None
|
||||
|
||||
def on_app_launch(self) -> None:
|
||||
"""Should be called for each adapter in on_app_launch."""
|
||||
|
||||
@ -142,6 +146,7 @@ class LoginAdapter:
|
||||
def sign_in(
|
||||
self,
|
||||
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
|
||||
description: str,
|
||||
) -> None:
|
||||
"""Attempt an explicit sign in via this adapter.
|
||||
|
||||
@ -151,6 +156,38 @@ class LoginAdapter:
|
||||
"""
|
||||
assert _ba.in_logic_thread()
|
||||
from ba._general import Call
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
# Have been seeing multiple sign-in attempts come through
|
||||
# nearly simultaneously which can be problematic server-side.
|
||||
# Let's error if a sign-in attempt is made within a few seconds
|
||||
# of the last one to address this.
|
||||
now = time.monotonic()
|
||||
appnow = _ba.time(TimeType.REAL)
|
||||
if self._last_sign_in_time is not None:
|
||||
since_last = now - self._last_sign_in_time
|
||||
if since_last < 1.0:
|
||||
logging.warning(
|
||||
'LoginAdapter: %s adapter sign_in() called too soon'
|
||||
' (%.2fs) after last; this-desc="%s", last-desc="%s",'
|
||||
' ba-real-time=%.2f.',
|
||||
self.login_type.name,
|
||||
since_last,
|
||||
description,
|
||||
self._last_sign_in_desc,
|
||||
appnow,
|
||||
)
|
||||
_ba.pushcall(
|
||||
Call(
|
||||
result_cb,
|
||||
self,
|
||||
RuntimeError('sign_in called too soon after last.'),
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self._last_sign_in_desc = description
|
||||
self._last_sign_in_time = now
|
||||
|
||||
if DEBUG_LOG:
|
||||
logging.debug(
|
||||
@ -223,7 +260,12 @@ class LoginAdapter:
|
||||
_ba.pushcall(Call(result_cb, self, result2))
|
||||
|
||||
_ba.app.cloud.send_message_cb(
|
||||
bacommon.cloud.SignInMessage(self.login_type, result),
|
||||
bacommon.cloud.SignInMessage(
|
||||
self.login_type,
|
||||
result,
|
||||
description=description,
|
||||
apptime=appnow,
|
||||
),
|
||||
on_response=_got_sign_in_response,
|
||||
)
|
||||
|
||||
|
||||
@ -1403,7 +1403,8 @@ class AccountSettingsWindow(ba.Window):
|
||||
if adapter is not None:
|
||||
self._signing_in_adapter = adapter
|
||||
adapter.sign_in(
|
||||
result_cb=ba.WeakCall(self._on_adapter_sign_in_result)
|
||||
result_cb=ba.WeakCall(self._on_adapter_sign_in_result),
|
||||
description='account settings button',
|
||||
)
|
||||
# Will get 'Signing in...' to show.
|
||||
self._needs_refresh = True
|
||||
|
||||
@ -94,6 +94,7 @@
|
||||
<w>avel</w>
|
||||
<w>avels</w>
|
||||
<w>awaitable</w>
|
||||
<w>awaitables</w>
|
||||
<w>axismotion</w>
|
||||
<w>backgrounded</w>
|
||||
<w>backgrounding</w>
|
||||
@ -464,6 +465,7 @@
|
||||
<w>fbos</w>
|
||||
<w>fcntl</w>
|
||||
<w>fdata</w>
|
||||
<w>fdcount</w>
|
||||
<w>fdirx</w>
|
||||
<w>fdiry</w>
|
||||
<w>fdirz</w>
|
||||
@ -672,6 +674,7 @@
|
||||
<w>ioprepped</w>
|
||||
<w>ioprepping</w>
|
||||
<w>ioreg</w>
|
||||
<w>iscoroutinefunction</w>
|
||||
<w>iserverget</w>
|
||||
<w>iserverput</w>
|
||||
<w>isinst</w>
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kAppBuildNumber = 20984;
|
||||
const int kAppBuildNumber = 20991;
|
||||
const char* kAppVersion = "1.7.18";
|
||||
|
||||
// Our standalone globals.
|
||||
|
||||
@ -198,9 +198,9 @@ class _BoundTestMessageSenderAsync(BoundMessageSender):
|
||||
async def send_async(self, message: _TMsg3) -> None:
|
||||
...
|
||||
|
||||
async def send_async(self, message: Message) -> Response | None:
|
||||
def send_async(self, message: Message) -> Awaitable[Response | None]:
|
||||
"""Send a message asynchronously."""
|
||||
return await self._sender.send_async(self._obj, message)
|
||||
return self._sender.send_async(self._obj, message)
|
||||
|
||||
|
||||
# SEND_ASYNC_CODE_TEST_END
|
||||
@ -261,9 +261,9 @@ class _BoundTestMessageSenderBBoth(BoundMessageSender):
|
||||
async def send_async(self, message: _TMsg4) -> None:
|
||||
...
|
||||
|
||||
async def send_async(self, message: Message) -> Response | None:
|
||||
def send_async(self, message: Message) -> Awaitable[Response | None]:
|
||||
"""Send a message asynchronously."""
|
||||
return await self._sender.send_async(self._obj, message)
|
||||
return self._sender.send_async(self._obj, message)
|
||||
|
||||
|
||||
# SEND_BOTH_CODE_TEST_END
|
||||
@ -424,11 +424,11 @@ class _TestAsyncMessageReceiver(MessageReceiver):
|
||||
class _BoundTestAsyncMessageReceiver(BoundMessageReceiver):
|
||||
"""Protocol-specific bound receiver."""
|
||||
|
||||
async def handle_raw_message(
|
||||
def handle_raw_message(
|
||||
self, message: str, raise_unregistered: bool = False
|
||||
) -> str:
|
||||
) -> Awaitable[str]:
|
||||
"""Asynchronously handle a raw incoming message."""
|
||||
return await self._receiver.handle_raw_message_async(
|
||||
return self._receiver.handle_raw_message_async(
|
||||
self._obj, message, raise_unregistered
|
||||
)
|
||||
|
||||
|
||||
@ -183,6 +183,10 @@ class SignInMessage(Message):
|
||||
login_type: Annotated[LoginType, IOAttrs('l')]
|
||||
sign_in_token: Annotated[str, IOAttrs('t')]
|
||||
|
||||
# For debugging. Can remove soft_default once build 20988+ is ubiquitous.
|
||||
description: Annotated[str, IOAttrs('d', soft_default='-')]
|
||||
apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)]
|
||||
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [SignInResponse]
|
||||
|
||||
@ -266,6 +266,9 @@ class LogHandler(logging.Handler):
|
||||
# didn't expect to be stringified.
|
||||
msg = self.format(record)
|
||||
|
||||
if __debug__:
|
||||
formattime = time.monotonic()
|
||||
|
||||
# Also immediately print pretty colored output to our echo file
|
||||
# (generally stderr). We do this part here instead of in our bg
|
||||
# thread because the delay can throw off command line prompts or
|
||||
@ -277,6 +280,9 @@ class LogHandler(logging.Handler):
|
||||
else:
|
||||
self._echofile.write(f'{msg}\n')
|
||||
|
||||
if __debug__:
|
||||
echotime = time.monotonic()
|
||||
|
||||
self._event_loop.call_soon_threadsafe(
|
||||
tpartial(
|
||||
self._emit_in_thread,
|
||||
@ -295,6 +301,10 @@ class LogHandler(logging.Handler):
|
||||
now = time.monotonic()
|
||||
# noinspection PyUnboundLocalVariable
|
||||
duration = now - starttime
|
||||
# noinspection PyUnboundLocalVariable
|
||||
format_duration = formattime - starttime
|
||||
# noinspection PyUnboundLocalVariable
|
||||
echo_duration = echotime - formattime
|
||||
if duration > 0.05 and (
|
||||
self._last_slow_emit_warning_time is None
|
||||
or now > self._last_slow_emit_warning_time + 10.0
|
||||
@ -307,8 +317,10 @@ class LogHandler(logging.Handler):
|
||||
tpartial(
|
||||
logging.warning,
|
||||
'efro.log.LogHandler emit took too long'
|
||||
' (%.2f seconds).',
|
||||
' (%.2fs total; %.2fs format, %.2fs echo).',
|
||||
duration,
|
||||
format_duration,
|
||||
echo_duration,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ -282,10 +282,13 @@ class MessageProtocol:
|
||||
def _get_module_header(
|
||||
self,
|
||||
part: Literal['sender', 'receiver'],
|
||||
extra_import_code: str | None = None,
|
||||
extra_import_code: str | None,
|
||||
enable_async_sends: bool,
|
||||
) -> str:
|
||||
"""Return common parts of generated modules."""
|
||||
# pylint: disable=too-many-locals, too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
import textwrap
|
||||
|
||||
tpimports: dict[str, list[str]] = {}
|
||||
@ -342,7 +345,7 @@ class MessageProtocol:
|
||||
|
||||
if part == 'sender':
|
||||
import_lines += (
|
||||
'from efro.message import MessageSender,' ' BoundMessageSender'
|
||||
'from efro.message import MessageSender, BoundMessageSender'
|
||||
)
|
||||
tpimport_typing_extras = ''
|
||||
else:
|
||||
@ -362,11 +365,18 @@ class MessageProtocol:
|
||||
import_lines += f'\n{extra_import_code}\n'
|
||||
|
||||
ovld = ', overload' if not single_message_type else ''
|
||||
ovld2 = (
|
||||
', cast, Awaitable'
|
||||
if (single_message_type and part == 'sender' and enable_async_sends)
|
||||
else ''
|
||||
)
|
||||
tpimport_lines = textwrap.indent(tpimport_lines, ' ')
|
||||
|
||||
baseimps = ['Any']
|
||||
if part == 'receiver':
|
||||
baseimps.append('Callable')
|
||||
if part == 'sender' and enable_async_sends:
|
||||
baseimps.append('Awaitable')
|
||||
baseimps_s = ', '.join(baseimps)
|
||||
out = (
|
||||
'# Released under the MIT License. See LICENSE for details.\n'
|
||||
@ -375,7 +385,7 @@ class MessageProtocol:
|
||||
f'\n'
|
||||
f'from __future__ import annotations\n'
|
||||
f'\n'
|
||||
f'from typing import TYPE_CHECKING{ovld}\n'
|
||||
f'from typing import TYPE_CHECKING{ovld}{ovld2}\n'
|
||||
f'\n'
|
||||
f'{import_lines}\n'
|
||||
f'\n'
|
||||
@ -399,13 +409,16 @@ class MessageProtocol:
|
||||
) -> str:
|
||||
"""Used by create_sender_module(); do not call directly."""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
import textwrap
|
||||
|
||||
msgtypes = list(self.message_ids_by_type.keys())
|
||||
|
||||
ppre = '_' if private else ''
|
||||
out = self._get_module_header(
|
||||
'sender', extra_import_code=protocol_module_level_import_code
|
||||
'sender',
|
||||
extra_import_code=protocol_module_level_import_code,
|
||||
enable_async_sends=enable_async_sends,
|
||||
)
|
||||
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||
out += (
|
||||
@ -438,7 +451,8 @@ class MessageProtocol:
|
||||
continue
|
||||
pfx = 'async ' if async_pass else ''
|
||||
sfx = '_async' if async_pass else ''
|
||||
awt = 'await ' if async_pass else ''
|
||||
# awt = 'await ' if async_pass else ''
|
||||
awt = ''
|
||||
how = 'asynchronously' if async_pass else 'synchronously'
|
||||
|
||||
if len(msgtypes) == 1:
|
||||
@ -451,22 +465,29 @@ class MessageProtocol:
|
||||
rtypevar = ' | '.join(_filt_tp_name(t) for t in rtypes)
|
||||
else:
|
||||
rtypevar = _filt_tp_name(rtypes[0])
|
||||
if async_pass:
|
||||
rtypevar = f'Awaitable[{rtypevar}]'
|
||||
out += (
|
||||
f'\n'
|
||||
f' {pfx}def send{sfx}(self,'
|
||||
f' def send{sfx}(self,'
|
||||
f' message: {msgtypevar})'
|
||||
f' -> {rtypevar}:\n'
|
||||
f' """Send a message {how}."""\n'
|
||||
f' out = {awt}self._sender.'
|
||||
f'send{sfx}(self._obj, message)\n'
|
||||
f' assert isinstance(out, {rtypevar})\n'
|
||||
f' return out\n'
|
||||
)
|
||||
if not async_pass:
|
||||
out += (
|
||||
f' assert isinstance(out, {rtypevar})\n'
|
||||
' return out\n'
|
||||
)
|
||||
else:
|
||||
out += f' return cast({rtypevar}, out)\n'
|
||||
|
||||
else:
|
||||
|
||||
for msgtype in msgtypes:
|
||||
msgtypevar = msgtype.__name__
|
||||
# rtypes = msgtype.get_response_types()
|
||||
rtypes = msgtype.get_response_types()
|
||||
if len(rtypes) > 1:
|
||||
rtypevar = ' | '.join(
|
||||
@ -482,10 +503,13 @@ class MessageProtocol:
|
||||
f' -> {rtypevar}:\n'
|
||||
f' ...\n'
|
||||
)
|
||||
rtypevar = 'Response | None'
|
||||
if async_pass:
|
||||
rtypevar = f'Awaitable[{rtypevar}]'
|
||||
out += (
|
||||
f'\n'
|
||||
f' {pfx}def send{sfx}(self, message: Message)'
|
||||
f' -> Response | None:\n'
|
||||
f' def send{sfx}(self, message: Message)'
|
||||
f' -> {rtypevar}:\n'
|
||||
f' """Send a message {how}."""\n'
|
||||
f' return {awt}self._sender.'
|
||||
f'send{sfx}(self._obj, message)\n'
|
||||
@ -509,7 +533,9 @@ class MessageProtocol:
|
||||
ppre = '_' if private else ''
|
||||
msgtypes = list(self.message_ids_by_type.keys())
|
||||
out = self._get_module_header(
|
||||
'receiver', extra_import_code=protocol_module_level_import_code
|
||||
'receiver',
|
||||
extra_import_code=protocol_module_level_import_code,
|
||||
enable_async_sends=False,
|
||||
)
|
||||
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||
out += (
|
||||
@ -602,11 +628,11 @@ class MessageProtocol:
|
||||
if is_async:
|
||||
out += (
|
||||
'\n'
|
||||
' async def handle_raw_message(\n'
|
||||
' def handle_raw_message(\n'
|
||||
' self, message: str, raise_unregistered: bool = False\n'
|
||||
' ) -> str:\n'
|
||||
' ) -> Awaitable[str]:\n'
|
||||
' """Asynchronously handle a raw incoming message."""\n'
|
||||
' return await self._receiver.'
|
||||
' return self._receiver.'
|
||||
'handle_raw_message_async(\n'
|
||||
' self._obj, message, raise_unregistered\n'
|
||||
' )\n'
|
||||
|
||||
@ -62,12 +62,6 @@ class MessageReceiver:
|
||||
[Any, Message | None, Response | SysResponse, dict], None
|
||||
] | None = None
|
||||
|
||||
# TODO: don't currently have async encode equivalent
|
||||
# or either for sender; can add as needed.
|
||||
self._decode_filter_async_call: Callable[
|
||||
[Any, dict, Message], Awaitable[None]
|
||||
] | None = None
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
def register_handler(
|
||||
self, call: Callable[[Any, Message], Response | None]
|
||||
@ -96,14 +90,17 @@ class MessageReceiver:
|
||||
|
||||
# Make sure we are only given async methods if we are an async handler
|
||||
# and sync ones otherwise.
|
||||
is_async = inspect.iscoroutinefunction(call)
|
||||
if self.is_async != is_async:
|
||||
msg = (
|
||||
'Expected a sync method; found an async one.'
|
||||
if is_async
|
||||
else 'Expected an async method; found a sync one.'
|
||||
)
|
||||
raise ValueError(msg)
|
||||
# UPDATE - can't do this anymore since we now sometimes use
|
||||
# regular functions which return awaitables instead of having
|
||||
# the entire function be async.
|
||||
# is_async = inspect.iscoroutinefunction(call)
|
||||
# if self.is_async != is_async:
|
||||
# msg = (
|
||||
# 'Expected a sync method; found an async one.'
|
||||
# if is_async
|
||||
# else 'Expected an async method; found a sync one.'
|
||||
# )
|
||||
# raise ValueError(msg)
|
||||
|
||||
# Check annotation types to determine what message types we handle.
|
||||
# Return-type annotation can be a Union, but we probably don't
|
||||
@ -189,19 +186,6 @@ class MessageReceiver:
|
||||
self._decode_filter_call = call
|
||||
return call
|
||||
|
||||
def decode_filter_async_method(
|
||||
self, call: Callable[[Any, dict, Message], Awaitable[None]]
|
||||
) -> Callable[[Any, dict, Message], Awaitable[None]]:
|
||||
"""Function decorator for defining a decode filter.
|
||||
|
||||
Decode filters can be used to extract extra data from incoming
|
||||
message dicts. Note that this version will only work with
|
||||
handle_raw_message_async().
|
||||
"""
|
||||
assert self._decode_filter_async_call is None
|
||||
self._decode_filter_async_call = call
|
||||
return call
|
||||
|
||||
def encode_filter_method(
|
||||
self,
|
||||
call: Callable[
|
||||
@ -247,24 +231,6 @@ class MessageReceiver:
|
||||
bound_obj, _msg_dict, msg_decoded = self._decode_incoming_message_base(
|
||||
bound_obj=bound_obj, msg=msg
|
||||
)
|
||||
|
||||
# If they've set an async filter but are calling sync
|
||||
# handle_raw_message() its likely a bug.
|
||||
assert self._decode_filter_async_call is None
|
||||
|
||||
return msg_decoded
|
||||
|
||||
async def _decode_incoming_message_async(
|
||||
self, bound_obj: Any, msg: str
|
||||
) -> Message:
|
||||
bound_obj, msg_dict, msg_decoded = self._decode_incoming_message_base(
|
||||
bound_obj=bound_obj, msg=msg
|
||||
)
|
||||
|
||||
if self._decode_filter_async_call is not None:
|
||||
await self._decode_filter_async_call(
|
||||
bound_obj, msg_dict, msg_decoded
|
||||
)
|
||||
return msg_decoded
|
||||
|
||||
def encode_user_response(
|
||||
@ -346,46 +312,83 @@ class MessageReceiver:
|
||||
logging.exception('Error in efro.message handling.')
|
||||
return rstr
|
||||
|
||||
async def handle_raw_message_async(
|
||||
def handle_raw_message_async(
|
||||
self, bound_obj: Any, msg: str, raise_unregistered: bool = False
|
||||
) -> str:
|
||||
) -> Awaitable[str]:
|
||||
"""Should be called when the receiver gets a message.
|
||||
|
||||
The return value is the raw response to the message.
|
||||
"""
|
||||
|
||||
# Note: This call is synchronous so that the first part of it can
|
||||
# happen synchronously. If the whole call were async we wouldn't be
|
||||
# able to guarantee that messages handlers would be called in the
|
||||
# order the messages were received.
|
||||
|
||||
assert self.is_async, "can't call async handler on sync receiver"
|
||||
msg_decoded: Message | None = None
|
||||
msgtype: type[Message] | None = None
|
||||
try:
|
||||
msg_decoded = await self._decode_incoming_message_async(
|
||||
bound_obj, msg
|
||||
)
|
||||
msg_decoded = self._decode_incoming_message(bound_obj, msg)
|
||||
msgtype = type(msg_decoded)
|
||||
handler = self._handlers.get(msgtype)
|
||||
if handler is None:
|
||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||
response = await handler(bound_obj, msg_decoded)
|
||||
assert isinstance(response, Response | None)
|
||||
return self.encode_user_response(bound_obj, msg_decoded, response)
|
||||
handler_awaitable = handler(bound_obj, msg_decoded)
|
||||
|
||||
except Exception as exc:
|
||||
if raise_unregistered and isinstance(
|
||||
exc, UnregisteredMessageIDError
|
||||
):
|
||||
raise
|
||||
rstr, dolog = self.encode_error_response(
|
||||
bound_obj, msg_decoded, exc
|
||||
return self._handle_raw_message_async_error(
|
||||
bound_obj, msg_decoded, msgtype, exc
|
||||
)
|
||||
|
||||
# Return an awaitable to handle the rest asynchronously.
|
||||
return self._handle_raw_message_async(
|
||||
bound_obj, msg_decoded, msgtype, handler_awaitable
|
||||
)
|
||||
|
||||
async def _handle_raw_message_async_error(
|
||||
self,
|
||||
bound_obj: Any,
|
||||
msg_decoded: Message | None,
|
||||
msgtype: type[Message] | None,
|
||||
exc: Exception,
|
||||
) -> str:
|
||||
rstr, dolog = self.encode_error_response(bound_obj, msg_decoded, exc)
|
||||
if dolog:
|
||||
if msgtype is not None:
|
||||
logging.exception(
|
||||
'Error handling %s.%s message.',
|
||||
msgtype.__module__,
|
||||
msgtype.__qualname__,
|
||||
)
|
||||
else:
|
||||
logging.exception('Error in efro.message handling.')
|
||||
return rstr
|
||||
|
||||
async def _handle_raw_message_async(
|
||||
self,
|
||||
bound_obj: Any,
|
||||
msg_decoded: Message,
|
||||
msgtype: type[Message] | None,
|
||||
handler_awaitable: Awaitable[Response | None],
|
||||
) -> str:
|
||||
"""Should be called when the receiver gets a message.
|
||||
|
||||
The return value is the raw response to the message.
|
||||
"""
|
||||
try:
|
||||
response = await handler_awaitable
|
||||
assert isinstance(response, Response | None)
|
||||
return self.encode_user_response(bound_obj, msg_decoded, response)
|
||||
|
||||
except Exception as exc:
|
||||
return await self._handle_raw_message_async_error(
|
||||
bound_obj, msg_decoded, msgtype, exc
|
||||
)
|
||||
if dolog:
|
||||
if msgtype is not None:
|
||||
logging.exception(
|
||||
'Error handling %s.%s message.',
|
||||
msgtype.__module__,
|
||||
msgtype.__qualname__,
|
||||
)
|
||||
else:
|
||||
logging.exception('Error in efro.message handling.')
|
||||
return rstr
|
||||
|
||||
|
||||
class BoundMessageReceiver:
|
||||
|
||||
@ -78,6 +78,14 @@ class MessageSender:
|
||||
CommunicationErrors raised here will be returned to the sender
|
||||
as such; all other exceptions will result in a RuntimeError for
|
||||
the sender.
|
||||
|
||||
IMPORTANT: Generally async send methods should not be implemented
|
||||
as 'async' methods, but instead should be regular methods that
|
||||
return awaitable objects. This way it can be guaranteed that
|
||||
outgoing messages are synchronously enqueued in the correct
|
||||
order, and then async calls can be returned which finish each
|
||||
send. If the entire call is async, they may be enqueued out of
|
||||
order in rare cases.
|
||||
"""
|
||||
assert self._send_async_raw_message_call is None
|
||||
self._send_async_raw_message_call = call
|
||||
@ -88,8 +96,9 @@ class MessageSender:
|
||||
) -> Callable[[Any, str, Message], Awaitable[str]]:
|
||||
"""Function decorator for extended send-async method.
|
||||
|
||||
This version of the method also is passed the original unencoded
|
||||
message.
|
||||
Version of send_async_method which is also is passed the original
|
||||
unencoded message; can be useful for cases where metadata is sent
|
||||
along with messages referring to their payloads/etc.
|
||||
"""
|
||||
assert self._send_async_raw_message_ex_call is None
|
||||
self._send_async_raw_message_ex_call = call
|
||||
@ -141,17 +150,34 @@ class MessageSender:
|
||||
),
|
||||
)
|
||||
|
||||
async def send_async(
|
||||
def send_async(
|
||||
self, bound_obj: Any, message: Message
|
||||
) -> Response | None:
|
||||
) -> Awaitable[Response | None]:
|
||||
"""Send a message asynchronously."""
|
||||
|
||||
# Note: This call is synchronous so that the first part of it can
|
||||
# happen synchronously. If the whole call were async we wouldn't be
|
||||
# able to guarantee that messages sent in order would actually go
|
||||
# out in order.
|
||||
raw_response_awaitable = self.fetch_raw_response_async(
|
||||
bound_obj=bound_obj,
|
||||
message=message,
|
||||
)
|
||||
# Now return an awaitable that will finish the send.
|
||||
return self._send_async_awaitable(
|
||||
bound_obj, message, raw_response_awaitable
|
||||
)
|
||||
|
||||
async def _send_async_awaitable(
|
||||
self,
|
||||
bound_obj: Any,
|
||||
message: Message,
|
||||
raw_response_awaitable: Awaitable[Response | SysResponse],
|
||||
) -> Response | None:
|
||||
return self.unpack_raw_response(
|
||||
bound_obj=bound_obj,
|
||||
message=message,
|
||||
raw_response=await self.fetch_raw_response_async(
|
||||
bound_obj=bound_obj,
|
||||
message=message,
|
||||
),
|
||||
raw_response=await raw_response_awaitable,
|
||||
)
|
||||
|
||||
def fetch_raw_response(
|
||||
@ -186,19 +212,23 @@ class MessageSender:
|
||||
return response
|
||||
return self._decode_raw_response(bound_obj, message, response_encoded)
|
||||
|
||||
async def fetch_raw_response_async(
|
||||
def fetch_raw_response_async(
|
||||
self, bound_obj: Any, message: Message
|
||||
) -> Response | SysResponse:
|
||||
"""Fetch a raw message response.
|
||||
) -> Awaitable[Response | SysResponse]:
|
||||
"""Fetch a raw message response awaitable.
|
||||
|
||||
The result of this should be passed to unpack_raw_response() to
|
||||
produce the final message result.
|
||||
The result of this should be awaited and then passed to
|
||||
unpack_raw_response() to produce the final message result.
|
||||
|
||||
Generally you can just call send(); calling fetch and unpack
|
||||
manually is for when message sending and response handling need
|
||||
to happen in different contexts/threads.
|
||||
"""
|
||||
|
||||
# Note: This call is synchronous so that the first part of it can
|
||||
# happen synchronously. If the whole call were async we wouldn't be
|
||||
# able to guarantee that messages sent in order would actually go
|
||||
# out in order.
|
||||
if (
|
||||
self._send_async_raw_message_call is None
|
||||
and self._send_async_raw_message_ex_call is None
|
||||
@ -208,14 +238,42 @@ class MessageSender:
|
||||
msg_encoded = self._encode_message(bound_obj, message)
|
||||
try:
|
||||
if self._send_async_raw_message_ex_call is not None:
|
||||
response_encoded = await self._send_async_raw_message_ex_call(
|
||||
send_awaitable = self._send_async_raw_message_ex_call(
|
||||
bound_obj, msg_encoded, message
|
||||
)
|
||||
else:
|
||||
assert self._send_async_raw_message_call is not None
|
||||
response_encoded = await self._send_async_raw_message_call(
|
||||
send_awaitable = self._send_async_raw_message_call(
|
||||
bound_obj, msg_encoded
|
||||
)
|
||||
except Exception as exc:
|
||||
return self._error_awaitable(exc)
|
||||
|
||||
# Now return an awaitable to finish the job.
|
||||
return self._fetch_raw_response_awaitable(
|
||||
bound_obj, message, send_awaitable
|
||||
)
|
||||
|
||||
async def _error_awaitable(self, exc: Exception) -> SysResponse:
|
||||
response = ErrorSysResponse(
|
||||
error_message='Error in MessageSender @send_async_method.',
|
||||
error_type=(
|
||||
ErrorSysResponse.ErrorType.COMMUNICATION
|
||||
if isinstance(exc, CommunicationError)
|
||||
else ErrorSysResponse.ErrorType.LOCAL
|
||||
),
|
||||
)
|
||||
# Can include the actual exception since we'll be looking at
|
||||
# this locally; might be helpful.
|
||||
response.set_local_exception(exc)
|
||||
return response
|
||||
|
||||
async def _fetch_raw_response_awaitable(
|
||||
self, bound_obj: Any, message: Message, send_awaitable: Awaitable[str]
|
||||
) -> Response | SysResponse:
|
||||
|
||||
try:
|
||||
response_encoded = await send_awaitable
|
||||
except Exception as exc:
|
||||
response = ErrorSysResponse(
|
||||
error_message='Error in MessageSender @send_async_method.',
|
||||
@ -378,23 +436,23 @@ class BoundMessageSender:
|
||||
assert self._obj is not None
|
||||
return self._sender.send(bound_obj=self._obj, message=message)
|
||||
|
||||
async def send_async_untyped(self, message: Message) -> Response | None:
|
||||
def send_async_untyped(
|
||||
self, message: Message
|
||||
) -> Awaitable[Response | None]:
|
||||
"""Send a message asynchronously.
|
||||
|
||||
Whenever possible, use the send_async() call provided by generated
|
||||
subclasses instead of this; it will provide better type safety.
|
||||
"""
|
||||
assert self._obj is not None
|
||||
return await self._sender.send_async(
|
||||
bound_obj=self._obj, message=message
|
||||
)
|
||||
return self._sender.send_async(bound_obj=self._obj, message=message)
|
||||
|
||||
async def fetch_raw_response_async_untyped(
|
||||
def fetch_raw_response_async_untyped(
|
||||
self, message: Message
|
||||
) -> Response | SysResponse:
|
||||
) -> Awaitable[Response | SysResponse]:
|
||||
"""Split send (part 1 of 2)."""
|
||||
assert self._obj is not None
|
||||
return await self._sender.fetch_raw_response_async(
|
||||
return self._sender.fetch_raw_response_async(
|
||||
bound_obj=self._obj, message=message
|
||||
)
|
||||
|
||||
|
||||
@ -323,12 +323,12 @@ class RPCEndpoint:
|
||||
if self.debug_print:
|
||||
self.debug_print_call(f'{self._label}: finished.')
|
||||
|
||||
async def send_message(
|
||||
def send_message(
|
||||
self,
|
||||
message: bytes,
|
||||
timeout: float | None = None,
|
||||
close_on_error: bool = True,
|
||||
) -> bytes:
|
||||
) -> Awaitable[bytes]:
|
||||
"""Send a message to the peer and return a response.
|
||||
|
||||
If timeout is not provided, the default will be used.
|
||||
@ -340,7 +340,10 @@ class RPCEndpoint:
|
||||
respect to a given endpoint. Pass close_on_error=False to
|
||||
override this for a particular message.
|
||||
"""
|
||||
# pylint: disable=too-many-branches
|
||||
# Note: This call is synchronous so that the first part of it
|
||||
# (enqueueing outgoing messages) happens synchronously. If it were
|
||||
# a pure async call it could be possible for send order to vary
|
||||
# based on how the async tasks get processed.
|
||||
|
||||
if self.debug_print_io:
|
||||
self.debug_print_call(
|
||||
@ -358,16 +361,6 @@ class RPCEndpoint:
|
||||
f'{self._label}: have peerinfo? {self._peer_info is not None}.'
|
||||
)
|
||||
|
||||
# We need to know their protocol, so if we haven't gotten a handshake
|
||||
# from them yet, just wait.
|
||||
while self._peer_info is None:
|
||||
await asyncio.sleep(0.01)
|
||||
assert self._peer_info is not None
|
||||
|
||||
if self._peer_info.protocol == 1:
|
||||
if len(message) > 65535:
|
||||
raise RuntimeError('Message cannot be larger than 65535 bytes')
|
||||
|
||||
# message_id is a 16 bit looping value.
|
||||
message_id = self._next_message_id
|
||||
self._next_message_id = (self._next_message_id + 1) % 65536
|
||||
@ -420,8 +413,35 @@ class RPCEndpoint:
|
||||
if timeout is None:
|
||||
timeout = self.DEFAULT_MESSAGE_TIMEOUT
|
||||
assert timeout is not None
|
||||
|
||||
bytes_awaitable = msgobj.wait_task
|
||||
|
||||
# Now complete the send asynchronously.
|
||||
return self._send_message(
|
||||
message, timeout, close_on_error, bytes_awaitable, message_id
|
||||
)
|
||||
|
||||
async def _send_message(
|
||||
self,
|
||||
message: bytes,
|
||||
timeout: float | None,
|
||||
close_on_error: bool,
|
||||
bytes_awaitable: asyncio.Task[bytes],
|
||||
message_id: int,
|
||||
) -> bytes:
|
||||
|
||||
# We need to know their protocol, so if we haven't gotten a handshake
|
||||
# from them yet, just wait.
|
||||
while self._peer_info is None:
|
||||
await asyncio.sleep(0.01)
|
||||
assert self._peer_info is not None
|
||||
|
||||
if self._peer_info.protocol == 1:
|
||||
if len(message) > 65535:
|
||||
raise RuntimeError('Message cannot be larger than 65535 bytes')
|
||||
|
||||
try:
|
||||
return await asyncio.wait_for(msgobj.wait_task, timeout=timeout)
|
||||
return await asyncio.wait_for(bytes_awaitable, timeout=timeout)
|
||||
except asyncio.CancelledError as exc:
|
||||
# Question: we assume this means the above wait_for() was
|
||||
# cancelled; how do we distinguish between this and *us* being
|
||||
@ -449,7 +469,7 @@ class RPCEndpoint:
|
||||
)
|
||||
|
||||
# Stop waiting on the response.
|
||||
msgobj.wait_task.cancel()
|
||||
bytes_awaitable.cancel()
|
||||
|
||||
# Remove the record of this message.
|
||||
del self._in_flight_messages[message_id]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user