mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-05 15:03:33 +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/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/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/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/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/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/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/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",
|
"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/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/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/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/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/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/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/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/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",
|
"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/__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",
|
"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",
|
"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/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/8b/ff/ffe14193d7ecc7be7bd4b8f0c1df",
|
"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/1b/35/085b6877ca6132f22363935c556f",
|
"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/ea/6d/812e4be178b1bfac568dfbef1cd5",
|
"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/01/81/44c58651a270c66621fac233af03",
|
"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/bc/7f/fcb81a65171d668c02b43ad8a0ca",
|
"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/97/d3/a39c9949110c3258662e37a01218",
|
"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/34/c6/b9f70ebc862ea8c0824c20c21a8c",
|
"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/d4/1d/5fce150d3ffb8286eccc3c932070",
|
"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/7d/22/88ba10738a5fa80022fd4e43cd63",
|
"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/14/01/f113a6e70aa0f69753513c890d2e",
|
"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/4c/16/0a6993916d2b486cf1e3a96b5855",
|
"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/8d/54/7a2f804e29e8c43de5d17f3fb303",
|
"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/98/aa/4decee9932b26500b9db74f68115",
|
"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/e7/e2/8b1ec39a67b0ac7c23c76ea2b71d",
|
"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/59/ab/2756b60a87a798b89450d4ead9c6",
|
"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/43/f4/5289b9660fed0381c1d98626c0a6",
|
"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/7f/9a/fde700fa2c7a32cd9c3695fe8e8d",
|
"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/ba/9a/81f247b9f980e7907b28633605e1",
|
"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/c1/83/499a6c6d1cf150ce0370680db482",
|
"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/e4/1b/a2ab5dd8c59cd90f5cd28f451884",
|
"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/a5/48/1e25219edd0a78e40c0f3093396d",
|
"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/dd/5d/51313d43cafea4898cc2ca7aaf3d",
|
"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/b3/ca/9a7f4019bf2e57d668171c42c876",
|
"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/4e/c8/478035d9e79dabc2a0cff7dc5c07",
|
"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/06/e3/5cac56d9f4e96a800cd424ecb01a",
|
"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/2d/f2/f3afa4635c55863e2996ee110414",
|
"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/52/2e/3fef9d99b533a1ae5e1be3c63e21",
|
"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/17/d4/3578de762544210f09f9ef6b60a4",
|
"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/18/44/f6a99fe7cf886f8a1b77b1878c7a",
|
"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/84/3a/a7f7d3b3ee3bd7b1806bf77f2859",
|
"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/e9/58/63afc99ce47eed08527f2dfc8f4d",
|
"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/43/ed/f49af3eb3646c69c5312ef8000dc",
|
"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/e4/c5/091b0d55220c1c7a6939e00563d7",
|
"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/b3/f4/69436953e88c28d027b7c8004ba3",
|
"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/37/d3/599f3904f5ee987d540164237703",
|
"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/5b/0e/a571f0b1d167f3a0d3f07ff6e633",
|
"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/86/3e/abdb5280c7e24e145bcf4cfc73d4",
|
"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/15/b7/76299570cd56fac624ec810c575c",
|
"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/39/07/b32bb5e8a4458adc0df92dad82f6",
|
"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/28/46/700cc357d8c776c3a34dbdeb31c8",
|
"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/71/f0/d3d64e44f03a10536649e000c836",
|
"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/42/9a/961f50a8d8d7b1d704a2616d76ca",
|
"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/4a/da/85bb42cb764293be0b4c515268ad",
|
"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/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.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"
|
"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>availplug</w>
|
||||||
<w>aval</w>
|
<w>aval</w>
|
||||||
<w>awaitable</w>
|
<w>awaitable</w>
|
||||||
|
<w>awaitables</w>
|
||||||
<w>axismotion</w>
|
<w>axismotion</w>
|
||||||
<w>bacfg</w>
|
<w>bacfg</w>
|
||||||
<w>backgrounded</w>
|
<w>backgrounded</w>
|
||||||
@ -863,6 +864,7 @@
|
|||||||
<w>fcontents</w>
|
<w>fcontents</w>
|
||||||
<w>fcount</w>
|
<w>fcount</w>
|
||||||
<w>fdata</w>
|
<w>fdata</w>
|
||||||
|
<w>fdcount</w>
|
||||||
<w>fdesc</w>
|
<w>fdesc</w>
|
||||||
<w>fdict</w>
|
<w>fdict</w>
|
||||||
<w>fdout</w>
|
<w>fdout</w>
|
||||||
@ -1276,6 +1278,7 @@
|
|||||||
<w>iprof</w>
|
<w>iprof</w>
|
||||||
<w>isatty</w>
|
<w>isatty</w>
|
||||||
<w>iscale</w>
|
<w>iscale</w>
|
||||||
|
<w>iscoroutinefunction</w>
|
||||||
<w>iserverget</w>
|
<w>iserverget</w>
|
||||||
<w>iserverput</w>
|
<w>iserverput</w>
|
||||||
<w>ispunch</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)
|
### 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.
|
- 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
|
# (or lack thereof) has completed. This includes things like
|
||||||
# workspace syncing. Completion of this is what flips the app
|
# workspace syncing. Completion of this is what flips the app
|
||||||
# into 'running' state.
|
# into 'running' state.
|
||||||
self._initial_login_completed = False
|
self._initial_sign_in_completed = False
|
||||||
|
|
||||||
self._kicked_off_workspace_load = False
|
self._kicked_off_workspace_load = False
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ class AccountV2Subsystem:
|
|||||||
if account.workspaceid is not None:
|
if account.workspaceid is not None:
|
||||||
assert account.workspacename is not None
|
assert account.workspacename is not None
|
||||||
if (
|
if (
|
||||||
not self._initial_login_completed
|
not self._initial_sign_in_completed
|
||||||
and not self._kicked_off_workspace_load
|
and not self._kicked_off_workspace_load
|
||||||
):
|
):
|
||||||
self._kicked_off_workspace_load = True
|
self._kicked_off_workspace_load = True
|
||||||
@ -121,9 +121,9 @@ class AccountV2Subsystem:
|
|||||||
return
|
return
|
||||||
|
|
||||||
# Ok; no workspace to worry about; carry on.
|
# Ok; no workspace to worry about; carry on.
|
||||||
if not self._initial_login_completed:
|
if not self._initial_sign_in_completed:
|
||||||
self._initial_login_completed = True
|
self._initial_sign_in_completed = True
|
||||||
_ba.app.on_initial_login_completed()
|
_ba.app.on_initial_sign_in_completed()
|
||||||
|
|
||||||
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
|
||||||
"""Should be called when logins for the active account change."""
|
"""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
|
within a few seconds of app launch; the app can move forward
|
||||||
with the startup sequence at that point.
|
with the startup sequence at that point.
|
||||||
"""
|
"""
|
||||||
if not self._initial_login_completed:
|
if not self._initial_sign_in_completed:
|
||||||
self._initial_login_completed = True
|
self._initial_sign_in_completed = True
|
||||||
_ba.app.on_initial_login_completed()
|
_ba.app.on_initial_sign_in_completed()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _hashstr(val: str) -> str:
|
def _hashstr(val: str) -> str:
|
||||||
@ -271,7 +271,7 @@ class AccountV2Subsystem:
|
|||||||
self._implicit_state_changed = False
|
self._implicit_state_changed = False
|
||||||
|
|
||||||
# Once we've made a move here we don't want to
|
# 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
|
self._can_do_auto_sign_in = False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -290,22 +290,23 @@ class AccountV2Subsystem:
|
|||||||
' of implicit state change...',
|
' of implicit state change...',
|
||||||
)
|
)
|
||||||
self._implicit_signed_in_adapter.sign_in(
|
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
|
self._implicit_state_changed = False
|
||||||
|
|
||||||
# Once we've made a move here we don't want to
|
# 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
|
self._can_do_auto_sign_in = False
|
||||||
|
|
||||||
if not self._can_do_auto_sign_in:
|
if not self._can_do_auto_sign_in:
|
||||||
return
|
return
|
||||||
|
|
||||||
# If we're not currently signed in, we have connectivity, and
|
# 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
|
# The implicit-state-change logic above should keep things
|
||||||
# mostly in-sync, but due to connectivity or other issues that
|
# mostly in-sync, but that might not always be the case due to
|
||||||
# might not always be the case. We prefer to keep people signed
|
# connectivity or other issues. We prefer to keep people signed
|
||||||
# in as a rule, even if there are corner cases where this might
|
# 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
|
# not be what they want (A user signing out and then restarting
|
||||||
# may be auto-signed back in).
|
# may be auto-signed back in).
|
||||||
@ -324,7 +325,7 @@ class AccountV2Subsystem:
|
|||||||
)
|
)
|
||||||
self._can_do_auto_sign_in = False # Only ATTEMPT once
|
self._can_do_auto_sign_in = False # Only ATTEMPT once
|
||||||
self._implicit_signed_in_adapter.sign_in(
|
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(
|
def _on_explicit_sign_in_completed(
|
||||||
@ -337,8 +338,8 @@ class AccountV2Subsystem:
|
|||||||
|
|
||||||
del adapter # Unused.
|
del adapter # Unused.
|
||||||
|
|
||||||
# Make some noise on errors since the user knows
|
# Make some noise on errors since the user knows a
|
||||||
# a sign-in attempt is happening in this case.
|
# sign-in attempt is happening in this case (the 'explicit' part).
|
||||||
if isinstance(result, Exception):
|
if isinstance(result, Exception):
|
||||||
# We expect the occasional communication errors;
|
# We expect the occasional communication errors;
|
||||||
# Log a full exception for anything else though.
|
# Log a full exception for anything else though.
|
||||||
@ -347,6 +348,8 @@ class AccountV2Subsystem:
|
|||||||
'Error on explicit accountv2 sign in attempt.',
|
'Error on explicit accountv2 sign in attempt.',
|
||||||
exc_info=result,
|
exc_info=result,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# For now just show 'error'. Should do better than this.
|
||||||
with _ba.Context('ui'):
|
with _ba.Context('ui'):
|
||||||
_ba.screenmessage(
|
_ba.screenmessage(
|
||||||
Lstr(resource='internal.signInErrorText'),
|
Lstr(resource='internal.signInErrorText'),
|
||||||
@ -395,9 +398,9 @@ class AccountV2Subsystem:
|
|||||||
_ba.app.accounts_v2.set_primary_credentials(result.credentials)
|
_ba.app.accounts_v2.set_primary_credentials(result.credentials)
|
||||||
|
|
||||||
def _on_set_active_workspace_completed(self) -> None:
|
def _on_set_active_workspace_completed(self) -> None:
|
||||||
if not self._initial_login_completed:
|
if not self._initial_sign_in_completed:
|
||||||
self._initial_login_completed = True
|
self._initial_sign_in_completed = True
|
||||||
_ba.app.on_initial_login_completed()
|
_ba.app.on_initial_sign_in_completed()
|
||||||
|
|
||||||
|
|
||||||
class AccountV2Handle:
|
class AccountV2Handle:
|
||||||
|
|||||||
@ -235,7 +235,7 @@ class App:
|
|||||||
self.state = self.State.LAUNCHING
|
self.state = self.State.LAUNCHING
|
||||||
|
|
||||||
self._launch_completed = False
|
self._launch_completed = False
|
||||||
self._initial_login_completed = False
|
self._initial_sign_in_completed = False
|
||||||
self._meta_scan_completed = False
|
self._meta_scan_completed = False
|
||||||
self._called_on_app_running = False
|
self._called_on_app_running = False
|
||||||
self._app_paused = False
|
self._app_paused = False
|
||||||
@ -511,7 +511,7 @@ class App:
|
|||||||
self.plugins.on_app_resume()
|
self.plugins.on_app_resume()
|
||||||
self.health_monitor.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
|
self.state = self.State.RUNNING
|
||||||
if not self._called_on_app_running:
|
if not self._called_on_app_running:
|
||||||
self._called_on_app_running = True
|
self._called_on_app_running = True
|
||||||
@ -724,8 +724,8 @@ class App:
|
|||||||
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||||
_ba.playsound(_ba.getsound('error'))
|
_ba.playsound(_ba.getsound('error'))
|
||||||
|
|
||||||
def on_initial_login_completed(self) -> None:
|
def on_initial_sign_in_completed(self) -> None:
|
||||||
"""Callback to be run after initial login process (or lack thereof).
|
"""Callback to be run after initial sign-in (or lack thereof).
|
||||||
|
|
||||||
This period includes things such as syncing account workspaces
|
This period includes things such as syncing account workspaces
|
||||||
or other data so it may take a substantial amount of time.
|
or other data so it may take a substantial amount of time.
|
||||||
@ -736,5 +736,5 @@ class App:
|
|||||||
# (account workspaces).
|
# (account workspaces).
|
||||||
self.meta.start_extra_scan()
|
self.meta.start_extra_scan()
|
||||||
|
|
||||||
self._initial_login_completed = True
|
self._initial_sign_in_completed = True
|
||||||
self._update_state()
|
self._update_state()
|
||||||
|
|||||||
@ -476,35 +476,18 @@ def on_too_many_file_descriptors() -> None:
|
|||||||
real_time = _ba.time(TimeType.REAL)
|
real_time = _ba.time(TimeType.REAL)
|
||||||
|
|
||||||
def _do_log() -> None:
|
def _do_log() -> None:
|
||||||
import subprocess
|
|
||||||
|
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
out = f'TOO MANY FDS at {real_time}.\nWe are pid {pid}\n'
|
try:
|
||||||
|
fdcount: int | str = len(os.listdir(f'/proc/{pid}/fd'))
|
||||||
out += (
|
except Exception as exc:
|
||||||
'FD Count: '
|
fdcount = f'? ({exc})'
|
||||||
+ subprocess.run(
|
logging.warning(
|
||||||
f'ls -l /proc/{pid}/fd | wc -l',
|
'TOO MANY FDS at %.2f. We are pid %d. FDCount is %s.',
|
||||||
shell=True,
|
real_time,
|
||||||
check=False,
|
pid,
|
||||||
capture_output=True,
|
fdcount,
|
||||||
).stdout.decode()
|
|
||||||
+ '\n'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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()
|
Thread(target=_do_log, daemon=True).start()
|
||||||
|
|
||||||
# import io
|
# import io
|
||||||
|
|||||||
@ -47,7 +47,7 @@ def bootstrap() -> None:
|
|||||||
|
|
||||||
# Give a soft warning if we're being used with a different binary
|
# Give a soft warning if we're being used with a different binary
|
||||||
# version than we expect.
|
# version than we expect.
|
||||||
expected_build = 20984
|
expected_build = 20991
|
||||||
running_build: int = env['build_number']
|
running_build: int = env['build_number']
|
||||||
if running_build != expected_build:
|
if running_build != expected_build:
|
||||||
print(
|
print(
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# 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
|
History: originally the engine would dynamically compile/eval various Python
|
||||||
from within C++ code, but the major downside there was that none of that was
|
code from within C++ code, but the major downside there was that none of it
|
||||||
type-checked so if names or arguments changed I would never catch code breakage
|
was type-checked so if names or arguments changed it would go unnoticed
|
||||||
until the code was next run. By defining all snippets I use here and then
|
until it broke at runtime. By instead defining such snippets here and then
|
||||||
capturing references to them all at launch I can immediately verify everything
|
capturing references to them all at launch it is possible to allow linting
|
||||||
I'm looking for exists and pylint/mypy can do their magic on this file.
|
and type-checking magic to happen and most issues will be caught immediately.
|
||||||
"""
|
"""
|
||||||
# (most of these are self-explanatory)
|
# (most of these are self-explanatory)
|
||||||
# pylint: disable=missing-function-docstring
|
# 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]
|
adapter = _ba.app.accounts_v2.login_adapters[login_type]
|
||||||
assert isinstance(adapter, LoginAdapterNative)
|
assert isinstance(adapter, LoginAdapterNative)
|
||||||
adapter.on_sign_in_complete(attempt_id=attempt_id, result=result)
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, final
|
from typing import TYPE_CHECKING, final
|
||||||
@ -57,6 +58,9 @@ class LoginAdapter:
|
|||||||
# current active primary account.
|
# current active primary account.
|
||||||
self._active_login_id: str | None = None
|
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:
|
def on_app_launch(self) -> None:
|
||||||
"""Should be called for each adapter in on_app_launch."""
|
"""Should be called for each adapter in on_app_launch."""
|
||||||
|
|
||||||
@ -142,6 +146,7 @@ class LoginAdapter:
|
|||||||
def sign_in(
|
def sign_in(
|
||||||
self,
|
self,
|
||||||
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
|
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
|
||||||
|
description: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Attempt an explicit sign in via this adapter.
|
"""Attempt an explicit sign in via this adapter.
|
||||||
|
|
||||||
@ -151,6 +156,38 @@ class LoginAdapter:
|
|||||||
"""
|
"""
|
||||||
assert _ba.in_logic_thread()
|
assert _ba.in_logic_thread()
|
||||||
from ba._general import Call
|
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:
|
if DEBUG_LOG:
|
||||||
logging.debug(
|
logging.debug(
|
||||||
@ -223,7 +260,12 @@ class LoginAdapter:
|
|||||||
_ba.pushcall(Call(result_cb, self, result2))
|
_ba.pushcall(Call(result_cb, self, result2))
|
||||||
|
|
||||||
_ba.app.cloud.send_message_cb(
|
_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,
|
on_response=_got_sign_in_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1403,7 +1403,8 @@ class AccountSettingsWindow(ba.Window):
|
|||||||
if adapter is not None:
|
if adapter is not None:
|
||||||
self._signing_in_adapter = adapter
|
self._signing_in_adapter = adapter
|
||||||
adapter.sign_in(
|
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.
|
# Will get 'Signing in...' to show.
|
||||||
self._needs_refresh = True
|
self._needs_refresh = True
|
||||||
|
|||||||
@ -94,6 +94,7 @@
|
|||||||
<w>avel</w>
|
<w>avel</w>
|
||||||
<w>avels</w>
|
<w>avels</w>
|
||||||
<w>awaitable</w>
|
<w>awaitable</w>
|
||||||
|
<w>awaitables</w>
|
||||||
<w>axismotion</w>
|
<w>axismotion</w>
|
||||||
<w>backgrounded</w>
|
<w>backgrounded</w>
|
||||||
<w>backgrounding</w>
|
<w>backgrounding</w>
|
||||||
@ -464,6 +465,7 @@
|
|||||||
<w>fbos</w>
|
<w>fbos</w>
|
||||||
<w>fcntl</w>
|
<w>fcntl</w>
|
||||||
<w>fdata</w>
|
<w>fdata</w>
|
||||||
|
<w>fdcount</w>
|
||||||
<w>fdirx</w>
|
<w>fdirx</w>
|
||||||
<w>fdiry</w>
|
<w>fdiry</w>
|
||||||
<w>fdirz</w>
|
<w>fdirz</w>
|
||||||
@ -672,6 +674,7 @@
|
|||||||
<w>ioprepped</w>
|
<w>ioprepped</w>
|
||||||
<w>ioprepping</w>
|
<w>ioprepping</w>
|
||||||
<w>ioreg</w>
|
<w>ioreg</w>
|
||||||
|
<w>iscoroutinefunction</w>
|
||||||
<w>iserverget</w>
|
<w>iserverget</w>
|
||||||
<w>iserverput</w>
|
<w>iserverput</w>
|
||||||
<w>isinst</w>
|
<w>isinst</w>
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
namespace ballistica {
|
namespace ballistica {
|
||||||
|
|
||||||
// These are set automatically via script; don't modify them here.
|
// These are set automatically via script; don't modify them here.
|
||||||
const int kAppBuildNumber = 20984;
|
const int kAppBuildNumber = 20991;
|
||||||
const char* kAppVersion = "1.7.18";
|
const char* kAppVersion = "1.7.18";
|
||||||
|
|
||||||
// Our standalone globals.
|
// Our standalone globals.
|
||||||
|
|||||||
@ -198,9 +198,9 @@ class _BoundTestMessageSenderAsync(BoundMessageSender):
|
|||||||
async def send_async(self, message: _TMsg3) -> None:
|
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."""
|
"""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
|
# 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: _TMsg4) -> None:
|
||||||
...
|
...
|
||||||
|
|
||||||
async def send_async(self, message: Message) -> Response | None:
|
def send_async(self, message: Message) -> Awaitable[Response | None]:
|
||||||
"""Send a message asynchronously."""
|
"""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
|
# SEND_BOTH_CODE_TEST_END
|
||||||
@ -424,11 +424,11 @@ class _TestAsyncMessageReceiver(MessageReceiver):
|
|||||||
class _BoundTestAsyncMessageReceiver(BoundMessageReceiver):
|
class _BoundTestAsyncMessageReceiver(BoundMessageReceiver):
|
||||||
"""Protocol-specific bound receiver."""
|
"""Protocol-specific bound receiver."""
|
||||||
|
|
||||||
async def handle_raw_message(
|
def handle_raw_message(
|
||||||
self, message: str, raise_unregistered: bool = False
|
self, message: str, raise_unregistered: bool = False
|
||||||
) -> str:
|
) -> Awaitable[str]:
|
||||||
"""Asynchronously handle a raw incoming message."""
|
"""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
|
self._obj, message, raise_unregistered
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -183,6 +183,10 @@ class SignInMessage(Message):
|
|||||||
login_type: Annotated[LoginType, IOAttrs('l')]
|
login_type: Annotated[LoginType, IOAttrs('l')]
|
||||||
sign_in_token: Annotated[str, IOAttrs('t')]
|
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
|
@classmethod
|
||||||
def get_response_types(cls) -> list[type[Response] | None]:
|
def get_response_types(cls) -> list[type[Response] | None]:
|
||||||
return [SignInResponse]
|
return [SignInResponse]
|
||||||
|
|||||||
@ -266,6 +266,9 @@ class LogHandler(logging.Handler):
|
|||||||
# didn't expect to be stringified.
|
# didn't expect to be stringified.
|
||||||
msg = self.format(record)
|
msg = self.format(record)
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
formattime = time.monotonic()
|
||||||
|
|
||||||
# Also immediately print pretty colored output to our echo file
|
# Also immediately print pretty colored output to our echo file
|
||||||
# (generally stderr). We do this part here instead of in our bg
|
# (generally stderr). We do this part here instead of in our bg
|
||||||
# thread because the delay can throw off command line prompts or
|
# thread because the delay can throw off command line prompts or
|
||||||
@ -277,6 +280,9 @@ class LogHandler(logging.Handler):
|
|||||||
else:
|
else:
|
||||||
self._echofile.write(f'{msg}\n')
|
self._echofile.write(f'{msg}\n')
|
||||||
|
|
||||||
|
if __debug__:
|
||||||
|
echotime = time.monotonic()
|
||||||
|
|
||||||
self._event_loop.call_soon_threadsafe(
|
self._event_loop.call_soon_threadsafe(
|
||||||
tpartial(
|
tpartial(
|
||||||
self._emit_in_thread,
|
self._emit_in_thread,
|
||||||
@ -295,6 +301,10 @@ class LogHandler(logging.Handler):
|
|||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
# noinspection PyUnboundLocalVariable
|
# noinspection PyUnboundLocalVariable
|
||||||
duration = now - starttime
|
duration = now - starttime
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
format_duration = formattime - starttime
|
||||||
|
# noinspection PyUnboundLocalVariable
|
||||||
|
echo_duration = echotime - formattime
|
||||||
if duration > 0.05 and (
|
if duration > 0.05 and (
|
||||||
self._last_slow_emit_warning_time is None
|
self._last_slow_emit_warning_time is None
|
||||||
or now > self._last_slow_emit_warning_time + 10.0
|
or now > self._last_slow_emit_warning_time + 10.0
|
||||||
@ -307,8 +317,10 @@ class LogHandler(logging.Handler):
|
|||||||
tpartial(
|
tpartial(
|
||||||
logging.warning,
|
logging.warning,
|
||||||
'efro.log.LogHandler emit took too long'
|
'efro.log.LogHandler emit took too long'
|
||||||
' (%.2f seconds).',
|
' (%.2fs total; %.2fs format, %.2fs echo).',
|
||||||
duration,
|
duration,
|
||||||
|
format_duration,
|
||||||
|
echo_duration,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -282,10 +282,13 @@ class MessageProtocol:
|
|||||||
def _get_module_header(
|
def _get_module_header(
|
||||||
self,
|
self,
|
||||||
part: Literal['sender', 'receiver'],
|
part: Literal['sender', 'receiver'],
|
||||||
extra_import_code: str | None = None,
|
extra_import_code: str | None,
|
||||||
|
enable_async_sends: bool,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Return common parts of generated modules."""
|
"""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
|
import textwrap
|
||||||
|
|
||||||
tpimports: dict[str, list[str]] = {}
|
tpimports: dict[str, list[str]] = {}
|
||||||
@ -342,7 +345,7 @@ class MessageProtocol:
|
|||||||
|
|
||||||
if part == 'sender':
|
if part == 'sender':
|
||||||
import_lines += (
|
import_lines += (
|
||||||
'from efro.message import MessageSender,' ' BoundMessageSender'
|
'from efro.message import MessageSender, BoundMessageSender'
|
||||||
)
|
)
|
||||||
tpimport_typing_extras = ''
|
tpimport_typing_extras = ''
|
||||||
else:
|
else:
|
||||||
@ -362,11 +365,18 @@ class MessageProtocol:
|
|||||||
import_lines += f'\n{extra_import_code}\n'
|
import_lines += f'\n{extra_import_code}\n'
|
||||||
|
|
||||||
ovld = ', overload' if not single_message_type else ''
|
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, ' ')
|
tpimport_lines = textwrap.indent(tpimport_lines, ' ')
|
||||||
|
|
||||||
baseimps = ['Any']
|
baseimps = ['Any']
|
||||||
if part == 'receiver':
|
if part == 'receiver':
|
||||||
baseimps.append('Callable')
|
baseimps.append('Callable')
|
||||||
|
if part == 'sender' and enable_async_sends:
|
||||||
|
baseimps.append('Awaitable')
|
||||||
baseimps_s = ', '.join(baseimps)
|
baseimps_s = ', '.join(baseimps)
|
||||||
out = (
|
out = (
|
||||||
'# Released under the MIT License. See LICENSE for details.\n'
|
'# Released under the MIT License. See LICENSE for details.\n'
|
||||||
@ -375,7 +385,7 @@ class MessageProtocol:
|
|||||||
f'\n'
|
f'\n'
|
||||||
f'from __future__ import annotations\n'
|
f'from __future__ import annotations\n'
|
||||||
f'\n'
|
f'\n'
|
||||||
f'from typing import TYPE_CHECKING{ovld}\n'
|
f'from typing import TYPE_CHECKING{ovld}{ovld2}\n'
|
||||||
f'\n'
|
f'\n'
|
||||||
f'{import_lines}\n'
|
f'{import_lines}\n'
|
||||||
f'\n'
|
f'\n'
|
||||||
@ -399,13 +409,16 @@ class MessageProtocol:
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""Used by create_sender_module(); do not call directly."""
|
"""Used by create_sender_module(); do not call directly."""
|
||||||
# pylint: disable=too-many-locals
|
# pylint: disable=too-many-locals
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
msgtypes = list(self.message_ids_by_type.keys())
|
msgtypes = list(self.message_ids_by_type.keys())
|
||||||
|
|
||||||
ppre = '_' if private else ''
|
ppre = '_' if private else ''
|
||||||
out = self._get_module_header(
|
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, ' ')
|
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||||
out += (
|
out += (
|
||||||
@ -438,7 +451,8 @@ class MessageProtocol:
|
|||||||
continue
|
continue
|
||||||
pfx = 'async ' if async_pass else ''
|
pfx = 'async ' if async_pass else ''
|
||||||
sfx = '_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'
|
how = 'asynchronously' if async_pass else 'synchronously'
|
||||||
|
|
||||||
if len(msgtypes) == 1:
|
if len(msgtypes) == 1:
|
||||||
@ -451,22 +465,29 @@ class MessageProtocol:
|
|||||||
rtypevar = ' | '.join(_filt_tp_name(t) for t in rtypes)
|
rtypevar = ' | '.join(_filt_tp_name(t) for t in rtypes)
|
||||||
else:
|
else:
|
||||||
rtypevar = _filt_tp_name(rtypes[0])
|
rtypevar = _filt_tp_name(rtypes[0])
|
||||||
|
if async_pass:
|
||||||
|
rtypevar = f'Awaitable[{rtypevar}]'
|
||||||
out += (
|
out += (
|
||||||
f'\n'
|
f'\n'
|
||||||
f' {pfx}def send{sfx}(self,'
|
f' def send{sfx}(self,'
|
||||||
f' message: {msgtypevar})'
|
f' message: {msgtypevar})'
|
||||||
f' -> {rtypevar}:\n'
|
f' -> {rtypevar}:\n'
|
||||||
f' """Send a message {how}."""\n'
|
f' """Send a message {how}."""\n'
|
||||||
f' out = {awt}self._sender.'
|
f' out = {awt}self._sender.'
|
||||||
f'send{sfx}(self._obj, message)\n'
|
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:
|
else:
|
||||||
|
|
||||||
for msgtype in msgtypes:
|
for msgtype in msgtypes:
|
||||||
msgtypevar = msgtype.__name__
|
msgtypevar = msgtype.__name__
|
||||||
# rtypes = msgtype.get_response_types()
|
|
||||||
rtypes = msgtype.get_response_types()
|
rtypes = msgtype.get_response_types()
|
||||||
if len(rtypes) > 1:
|
if len(rtypes) > 1:
|
||||||
rtypevar = ' | '.join(
|
rtypevar = ' | '.join(
|
||||||
@ -482,10 +503,13 @@ class MessageProtocol:
|
|||||||
f' -> {rtypevar}:\n'
|
f' -> {rtypevar}:\n'
|
||||||
f' ...\n'
|
f' ...\n'
|
||||||
)
|
)
|
||||||
|
rtypevar = 'Response | None'
|
||||||
|
if async_pass:
|
||||||
|
rtypevar = f'Awaitable[{rtypevar}]'
|
||||||
out += (
|
out += (
|
||||||
f'\n'
|
f'\n'
|
||||||
f' {pfx}def send{sfx}(self, message: Message)'
|
f' def send{sfx}(self, message: Message)'
|
||||||
f' -> Response | None:\n'
|
f' -> {rtypevar}:\n'
|
||||||
f' """Send a message {how}."""\n'
|
f' """Send a message {how}."""\n'
|
||||||
f' return {awt}self._sender.'
|
f' return {awt}self._sender.'
|
||||||
f'send{sfx}(self._obj, message)\n'
|
f'send{sfx}(self._obj, message)\n'
|
||||||
@ -509,7 +533,9 @@ class MessageProtocol:
|
|||||||
ppre = '_' if private else ''
|
ppre = '_' if private else ''
|
||||||
msgtypes = list(self.message_ids_by_type.keys())
|
msgtypes = list(self.message_ids_by_type.keys())
|
||||||
out = self._get_module_header(
|
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, ' ')
|
ccind = textwrap.indent(protocol_create_code, ' ')
|
||||||
out += (
|
out += (
|
||||||
@ -602,11 +628,11 @@ class MessageProtocol:
|
|||||||
if is_async:
|
if is_async:
|
||||||
out += (
|
out += (
|
||||||
'\n'
|
'\n'
|
||||||
' async def handle_raw_message(\n'
|
' def handle_raw_message(\n'
|
||||||
' self, message: str, raise_unregistered: bool = False\n'
|
' self, message: str, raise_unregistered: bool = False\n'
|
||||||
' ) -> str:\n'
|
' ) -> Awaitable[str]:\n'
|
||||||
' """Asynchronously handle a raw incoming message."""\n'
|
' """Asynchronously handle a raw incoming message."""\n'
|
||||||
' return await self._receiver.'
|
' return self._receiver.'
|
||||||
'handle_raw_message_async(\n'
|
'handle_raw_message_async(\n'
|
||||||
' self._obj, message, raise_unregistered\n'
|
' self._obj, message, raise_unregistered\n'
|
||||||
' )\n'
|
' )\n'
|
||||||
|
|||||||
@ -62,12 +62,6 @@ class MessageReceiver:
|
|||||||
[Any, Message | None, Response | SysResponse, dict], None
|
[Any, Message | None, Response | SysResponse, dict], None
|
||||||
] | None = 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
|
# noinspection PyProtectedMember
|
||||||
def register_handler(
|
def register_handler(
|
||||||
self, call: Callable[[Any, Message], Response | None]
|
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
|
# Make sure we are only given async methods if we are an async handler
|
||||||
# and sync ones otherwise.
|
# and sync ones otherwise.
|
||||||
is_async = inspect.iscoroutinefunction(call)
|
# UPDATE - can't do this anymore since we now sometimes use
|
||||||
if self.is_async != is_async:
|
# regular functions which return awaitables instead of having
|
||||||
msg = (
|
# the entire function be async.
|
||||||
'Expected a sync method; found an async one.'
|
# is_async = inspect.iscoroutinefunction(call)
|
||||||
if is_async
|
# if self.is_async != is_async:
|
||||||
else 'Expected an async method; found a sync one.'
|
# msg = (
|
||||||
)
|
# 'Expected a sync method; found an async one.'
|
||||||
raise ValueError(msg)
|
# 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.
|
# Check annotation types to determine what message types we handle.
|
||||||
# Return-type annotation can be a Union, but we probably don't
|
# Return-type annotation can be a Union, but we probably don't
|
||||||
@ -189,19 +186,6 @@ class MessageReceiver:
|
|||||||
self._decode_filter_call = call
|
self._decode_filter_call = call
|
||||||
return 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(
|
def encode_filter_method(
|
||||||
self,
|
self,
|
||||||
call: Callable[
|
call: Callable[
|
||||||
@ -247,24 +231,6 @@ class MessageReceiver:
|
|||||||
bound_obj, _msg_dict, msg_decoded = self._decode_incoming_message_base(
|
bound_obj, _msg_dict, msg_decoded = self._decode_incoming_message_base(
|
||||||
bound_obj=bound_obj, msg=msg
|
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
|
return msg_decoded
|
||||||
|
|
||||||
def encode_user_response(
|
def encode_user_response(
|
||||||
@ -346,46 +312,83 @@ class MessageReceiver:
|
|||||||
logging.exception('Error in efro.message handling.')
|
logging.exception('Error in efro.message handling.')
|
||||||
return rstr
|
return rstr
|
||||||
|
|
||||||
async def handle_raw_message_async(
|
def handle_raw_message_async(
|
||||||
self, bound_obj: Any, msg: str, raise_unregistered: bool = False
|
self, bound_obj: Any, msg: str, raise_unregistered: bool = False
|
||||||
) -> str:
|
) -> Awaitable[str]:
|
||||||
"""Should be called when the receiver gets a message.
|
"""Should be called when the receiver gets a message.
|
||||||
|
|
||||||
The return value is the raw response to the 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"
|
assert self.is_async, "can't call async handler on sync receiver"
|
||||||
msg_decoded: Message | None = None
|
msg_decoded: Message | None = None
|
||||||
msgtype: type[Message] | None = None
|
msgtype: type[Message] | None = None
|
||||||
try:
|
try:
|
||||||
msg_decoded = await self._decode_incoming_message_async(
|
msg_decoded = self._decode_incoming_message(bound_obj, msg)
|
||||||
bound_obj, msg
|
|
||||||
)
|
|
||||||
msgtype = type(msg_decoded)
|
msgtype = type(msg_decoded)
|
||||||
handler = self._handlers.get(msgtype)
|
handler = self._handlers.get(msgtype)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
raise RuntimeError(f'Got unhandled message type: {msgtype}.')
|
||||||
response = await handler(bound_obj, msg_decoded)
|
handler_awaitable = handler(bound_obj, msg_decoded)
|
||||||
assert isinstance(response, Response | None)
|
|
||||||
return self.encode_user_response(bound_obj, msg_decoded, response)
|
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if raise_unregistered and isinstance(
|
if raise_unregistered and isinstance(
|
||||||
exc, UnregisteredMessageIDError
|
exc, UnregisteredMessageIDError
|
||||||
):
|
):
|
||||||
raise
|
raise
|
||||||
rstr, dolog = self.encode_error_response(
|
return self._handle_raw_message_async_error(
|
||||||
bound_obj, msg_decoded, exc
|
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:
|
class BoundMessageReceiver:
|
||||||
|
|||||||
@ -78,6 +78,14 @@ class MessageSender:
|
|||||||
CommunicationErrors raised here will be returned to the sender
|
CommunicationErrors raised here will be returned to the sender
|
||||||
as such; all other exceptions will result in a RuntimeError for
|
as such; all other exceptions will result in a RuntimeError for
|
||||||
the sender.
|
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
|
assert self._send_async_raw_message_call is None
|
||||||
self._send_async_raw_message_call = call
|
self._send_async_raw_message_call = call
|
||||||
@ -88,8 +96,9 @@ class MessageSender:
|
|||||||
) -> Callable[[Any, str, Message], Awaitable[str]]:
|
) -> Callable[[Any, str, Message], Awaitable[str]]:
|
||||||
"""Function decorator for extended send-async method.
|
"""Function decorator for extended send-async method.
|
||||||
|
|
||||||
This version of the method also is passed the original unencoded
|
Version of send_async_method which is also is passed the original
|
||||||
message.
|
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
|
assert self._send_async_raw_message_ex_call is None
|
||||||
self._send_async_raw_message_ex_call = call
|
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
|
self, bound_obj: Any, message: Message
|
||||||
) -> Response | None:
|
) -> Awaitable[Response | None]:
|
||||||
"""Send a message asynchronously."""
|
"""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(
|
return self.unpack_raw_response(
|
||||||
bound_obj=bound_obj,
|
bound_obj=bound_obj,
|
||||||
message=message,
|
message=message,
|
||||||
raw_response=await self.fetch_raw_response_async(
|
raw_response=await raw_response_awaitable,
|
||||||
bound_obj=bound_obj,
|
|
||||||
message=message,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def fetch_raw_response(
|
def fetch_raw_response(
|
||||||
@ -186,19 +212,23 @@ class MessageSender:
|
|||||||
return response
|
return response
|
||||||
return self._decode_raw_response(bound_obj, message, response_encoded)
|
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
|
self, bound_obj: Any, message: Message
|
||||||
) -> Response | SysResponse:
|
) -> Awaitable[Response | SysResponse]:
|
||||||
"""Fetch a raw message response.
|
"""Fetch a raw message response awaitable.
|
||||||
|
|
||||||
The result of this should be passed to unpack_raw_response() to
|
The result of this should be awaited and then passed to
|
||||||
produce the final message result.
|
unpack_raw_response() to produce the final message result.
|
||||||
|
|
||||||
Generally you can just call send(); calling fetch and unpack
|
Generally you can just call send(); calling fetch and unpack
|
||||||
manually is for when message sending and response handling need
|
manually is for when message sending and response handling need
|
||||||
to happen in different contexts/threads.
|
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 (
|
if (
|
||||||
self._send_async_raw_message_call is None
|
self._send_async_raw_message_call is None
|
||||||
and self._send_async_raw_message_ex_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)
|
msg_encoded = self._encode_message(bound_obj, message)
|
||||||
try:
|
try:
|
||||||
if self._send_async_raw_message_ex_call is not None:
|
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
|
bound_obj, msg_encoded, message
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
assert self._send_async_raw_message_call is not None
|
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
|
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:
|
except Exception as exc:
|
||||||
response = ErrorSysResponse(
|
response = ErrorSysResponse(
|
||||||
error_message='Error in MessageSender @send_async_method.',
|
error_message='Error in MessageSender @send_async_method.',
|
||||||
@ -378,23 +436,23 @@ class BoundMessageSender:
|
|||||||
assert self._obj is not None
|
assert self._obj is not None
|
||||||
return self._sender.send(bound_obj=self._obj, message=message)
|
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.
|
"""Send a message asynchronously.
|
||||||
|
|
||||||
Whenever possible, use the send_async() call provided by generated
|
Whenever possible, use the send_async() call provided by generated
|
||||||
subclasses instead of this; it will provide better type safety.
|
subclasses instead of this; it will provide better type safety.
|
||||||
"""
|
"""
|
||||||
assert self._obj is not None
|
assert self._obj is not None
|
||||||
return await self._sender.send_async(
|
return self._sender.send_async(bound_obj=self._obj, message=message)
|
||||||
bound_obj=self._obj, message=message
|
|
||||||
)
|
|
||||||
|
|
||||||
async def fetch_raw_response_async_untyped(
|
def fetch_raw_response_async_untyped(
|
||||||
self, message: Message
|
self, message: Message
|
||||||
) -> Response | SysResponse:
|
) -> Awaitable[Response | SysResponse]:
|
||||||
"""Split send (part 1 of 2)."""
|
"""Split send (part 1 of 2)."""
|
||||||
assert self._obj is not None
|
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
|
bound_obj=self._obj, message=message
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -323,12 +323,12 @@ class RPCEndpoint:
|
|||||||
if self.debug_print:
|
if self.debug_print:
|
||||||
self.debug_print_call(f'{self._label}: finished.')
|
self.debug_print_call(f'{self._label}: finished.')
|
||||||
|
|
||||||
async def send_message(
|
def send_message(
|
||||||
self,
|
self,
|
||||||
message: bytes,
|
message: bytes,
|
||||||
timeout: float | None = None,
|
timeout: float | None = None,
|
||||||
close_on_error: bool = True,
|
close_on_error: bool = True,
|
||||||
) -> bytes:
|
) -> Awaitable[bytes]:
|
||||||
"""Send a message to the peer and return a response.
|
"""Send a message to the peer and return a response.
|
||||||
|
|
||||||
If timeout is not provided, the default will be used.
|
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
|
respect to a given endpoint. Pass close_on_error=False to
|
||||||
override this for a particular message.
|
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:
|
if self.debug_print_io:
|
||||||
self.debug_print_call(
|
self.debug_print_call(
|
||||||
@ -358,16 +361,6 @@ class RPCEndpoint:
|
|||||||
f'{self._label}: have peerinfo? {self._peer_info is not None}.'
|
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 is a 16 bit looping value.
|
||||||
message_id = self._next_message_id
|
message_id = self._next_message_id
|
||||||
self._next_message_id = (self._next_message_id + 1) % 65536
|
self._next_message_id = (self._next_message_id + 1) % 65536
|
||||||
@ -420,8 +413,35 @@ class RPCEndpoint:
|
|||||||
if timeout is None:
|
if timeout is None:
|
||||||
timeout = self.DEFAULT_MESSAGE_TIMEOUT
|
timeout = self.DEFAULT_MESSAGE_TIMEOUT
|
||||||
assert timeout is not None
|
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:
|
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:
|
except asyncio.CancelledError as exc:
|
||||||
# Question: we assume this means the above wait_for() was
|
# Question: we assume this means the above wait_for() was
|
||||||
# cancelled; how do we distinguish between this and *us* being
|
# cancelled; how do we distinguish between this and *us* being
|
||||||
@ -449,7 +469,7 @@ class RPCEndpoint:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Stop waiting on the response.
|
# Stop waiting on the response.
|
||||||
msgobj.wait_task.cancel()
|
bytes_awaitable.cancel()
|
||||||
|
|
||||||
# Remove the record of this message.
|
# Remove the record of this message.
|
||||||
del self._in_flight_messages[message_id]
|
del self._in_flight_messages[message_id]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user