diff --git a/.efrocachemap b/.efrocachemap
index cd556947..e6d351ca 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -3971,50 +3971,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
- "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/fd/f8/10dab633b840e11d48028ece0e94",
- "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/95/5c/25082f7cccc42c15e5b83c75c475",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5e/97/9e0e1c1dc834ad181859b0ee0b78",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/b2/1c0acb568e9c59ac9fd5874cae5f",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/0b/18/2bc39720f1316779415cc94cb113",
- "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ac/a7/6a840428e14ef51bcd0df363a434",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c2/41/0c32e18e1e70a972a1115e92e186",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/47/64/7ccc6f00b6956c1a7432432549bd",
- "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c9/4f/5440667c1b6fea19ae0de266bfd8",
- "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/32/f0/239dbf6e7dd3cd3db7486fde7407",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/98/72/6c901ee766aebf76fb80eae90589",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/db/1e/6774a41b43542afc038908d8e89b",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/df/d2/1fd51009e9fb22045c2a583cd64a",
- "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fd/81/3fc11ac92e6ebcec071c7f93ffe7",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/7b/20b1def5897ee9f60817b75cb51f",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/13/b66b6692b578e3c1b7a253ac764e",
- "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/06/e4/45df563117d8cc07f606d3df504f",
- "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cf/d4/885ba111bb2b13084cf31f64cede",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/20/2d/3c0bc587aea1bd0e04702bfe2291",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/19/25/9ae33702dec28a19114b023f9dd5",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c2/86/37c7b19623c5f72eb4223f1fa6a2",
- "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f2/ee/e42d3fe7a0d2ad6a4395c6b5fd1f",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/27/4a/439e5c93cf7fdf31893dcc1cfaa3",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cf/4e/a1085b9060bb4c7ee260bcd3291c",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/42/59f61b5645662d30c1ad2c6410b9",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/f8/9298792aa9378a520596bba6a388",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/27/bb/cae4aa7229c869262db19e9dcdd8",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/de/1e35235ed2b1279fd95b845ed04d",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/53/05/e570e9edd509552a3d2028ab099d",
- "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/20/e9/9a956250851d65a3602c20ae990a",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/09/1c/d3536f33192842c6b30b06771611",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0c/4a/77171c3984442e73727bbf47ddec",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a1/a7/25adabdaa21491cd25ef28a4f846",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ba/0f/eaee0823fe7a1f299e3a19578451",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/66/fe/17320024436fd49b4d8886075fc9",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c4/77/17075093fd218bd46e6dad294f3f",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/f3/f3/a31d843c10f30086f84368376b35",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/c9/e9/221a9f0d18117353851624446b32",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/c3/54/0de51809c3a7edf28c38dd144a10",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/03/4d/76acfd2bcd4c0c94a85e450bc3d3",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/a4/94/76a46fa659db22ca2155e1b423a9",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/60/a3/5e7fe045108577d6c17076838c99",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/9d/5d/7d24ce4b8cccfca69c593ff59bfe",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/c9/4c/751c6d0ea202603d20385aaa1d94",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/d3/38075453348d9d2abd102554a937",
+ "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/1c/1cd7a078b292c0d01d634f97f222",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fa/20/4f69c5c8a3e9bf9afabff8a7118f",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3a/3a/d23f5fabe309b6e8694aff471b9b",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4d/6f/12942dd120a3ec0963249b596989",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4d/90/3b11a947cd2befe576c12dc58a9c",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/77/77/2c01655f3144cd40c9ee252c7226",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f2/62/a0ba340093f8c143ab39149244e9",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/67/a9/4b48db661bc7b1f6338ee0389e87",
+ "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8e/44/c087c45ca09e5a8e4284076d4663",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d8/ee/452c5c9164807e82d97214f7fc5b",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7a/f0/2a4003966c6e7ec531bcfb19f013",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ec/be/637f05ea13de731126047d88a6c2",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/53/c6/2503d6a33f1a86d3504732091540",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/14/ba5b7d47bec90c93af50fe344a1e",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/79/e08d502d6b980db9c9506b6614ca",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/62/a7/c88cf55d6242a7b28467b193cb7f",
+ "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/36/7e/6f55132fea94bbca6b41cc764a28",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/f4/4a/a7aa2270ca1bd8cc15cb5204d089",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/86/df/dd3bef3462b735d85243e24540f9",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f9/81/e339e45d6c650df8217acbfb5f29",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/de/d8/ce0ae67d8fa1bc4afb931c2e8a0f",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7b/0a/4907ea64f8492164a84ab9503aaf",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/48/b4/8ed406ae6ff13da64c7ec97b6f56",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/60/190e42f26c0f1224c2ffa69e3992",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/61/a4936fb9455dcd014a1eb5e570f1",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/29/75/b61dae6070661f68ddb54cf14ee4",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/31/1373c730b4e06c515a439b2e15d9",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7e/f2/567e3019d15c23424bd9c8562679",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b2/43/4c8e335e4771c9bbc01737ca8ca8",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0b/4c/6d68b4f4b2e10597ca74dff0b0cb",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/c1/f566397ffc7d5c943b32c6508479",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4d/07/d0ed02b5363672e0e1039567ce60",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/fc/e9c87d067c8511b8183f09dcd05d",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/e0/94da6e5b577853226c1ca81978de",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1b/06/983ab3446f2bf0b31a0d8c93322b",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/1c/dd/cf46a040f49e72b0693f10f72ca6",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/1b/f9/c5b6eeb4493d4a8acba006bc4293",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/cc/d1/e7965899f642259e45adc059b629",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/09/67/573507643fe3790b9f19ec707be0",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/1a/d0/a33a7402cdf38076ac726f155fcf",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/81/fb/68aaa7cb8fad0a49c6a9b47590b5",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7f/83/62496b38a9828d6ed21d41a636ea",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/89/1a/9d4b19c0c6fa33db78e75376c7e8",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/b3/15/7c6d580b3482870b5b058858624c",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 2f1c9891..08e9b907 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -81,6 +81,7 @@
anyofallofanyvalaosp
+ aparsedapichangesapisapks
@@ -217,6 +218,7 @@
bdirbelarussianbenboncan
+ benningbfiledirbfilesbgmodel
@@ -847,6 +849,7 @@
flavfldtypesflines
+ floinkdingleflooffloofclsfloop
@@ -950,6 +953,7 @@
gametypesgameutilsgbytecount
+ gcwegearvrgenchangeloggencmd
@@ -1383,6 +1387,7 @@
ltypeslubyteluuid
+ lvallzmalzmamodulemacappstore
@@ -1648,6 +1653,7 @@
ogvaloivalokbtn
+ oldbookoldladyonlnonscreencountdown
@@ -1818,6 +1824,7 @@
prchprecprecommand
+ precompilingpreconfigpreexecprefablib
@@ -1877,6 +1884,7 @@
ptransptypeptypename
+ pubdeviceidpublictabpubsyncpubtargets
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05929c7a..1efefb07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.6.12 (20556, 2022-04-19)
+### 1.6.12 (20561, 2022-04-21)
- More internal work on V2 master-server communication
### 1.6.11 (20539, 2022-03-23)
diff --git a/Makefile b/Makefile
index b4b0cd4c..957b84b6 100644
--- a/Makefile
+++ b/Makefile
@@ -695,6 +695,11 @@ test-message:
@tools/pcommand pytest -o log_cli=true -o log_cli_level=debug -s -vv \
tests/test_efro/test_message.py
+# Individual test with extra output enabled.
+test-dataclassio:
+ @tools/pcommand pytest -o log_cli=true -o log_cli_level=debug -s -vv \
+ tests/test_efro/test_dataclassio.py
+
# Individual test with extra output enabled.
test-rpc:
@tools/pcommand pytest -o log_cli=true -o log_cli_level=debug -s -vv \
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 55d198ef..b2418ae6 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -53,6 +53,7 @@
anyofallofanyvalaosp
+ aparsedapientryapostappconfig
@@ -104,6 +105,7 @@
bbbbbbbbcfnbdea
+ benningbezansonbgrabigendian
@@ -418,6 +420,7 @@
fjcofjcoiwefflipbit
+ floinkdinglefloopflopsyfname
@@ -462,6 +465,7 @@
gasmsggbusgcc's
+ gcwegearvrgenchangeloggencmd
@@ -668,6 +672,7 @@
ltypeslubyteluuid
+ lvallveclvoidmacbuild
@@ -821,6 +826,7 @@
offsyoiffsssokbtn
+ oldbookoldnameooooooooooo
@@ -896,6 +902,7 @@
pprepptabcomprecalc
+ precompilingpreconfigpredeclareprefs
@@ -926,6 +933,7 @@
ptrsptypeptypename
+ pubdeviceidpublictabpubtargetspulseaudio
diff --git a/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml b/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
index 4d495b1e..ffa57a65 100644
--- a/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
+++ b/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
@@ -10,8 +10,11 @@
+
+
+
+
+
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index 1862cc92..ea5169d1 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -21,7 +21,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kAppBuildNumber = 20556;
+const int kAppBuildNumber = 20561;
const char* kAppVersion = "1.6.12";
// Our standalone globals.
diff --git a/src/ballistica/dynamics/material/roll_sound_material_action.h b/src/ballistica/dynamics/material/roll_sound_material_action.h
index 93a373c2..37716c92 100644
--- a/src/ballistica/dynamics/material/roll_sound_material_action.h
+++ b/src/ballistica/dynamics/material/roll_sound_material_action.h
@@ -5,6 +5,7 @@
#include "ballistica/ballistica.h"
#include "ballistica/dynamics/material/material_action.h"
+#include "ballistica/media/component/sound.h"
namespace ballistica {
diff --git a/src/ballistica/input/device/test_input.cc b/src/ballistica/input/device/test_input.cc
index 0f5bc977..d3ad9fc2 100644
--- a/src/ballistica/input/device/test_input.cc
+++ b/src/ballistica/input/device/test_input.cc
@@ -89,7 +89,9 @@ void TestInput::Process(millisecs_t time) {
HandleAlreadyPressedTwice();
} else {
jump_pressed_ = !jump_pressed_;
- if (jump_pressed_) join_press_count_++;
+ if (jump_pressed_) {
+ join_press_count_++;
+ }
e.type = jump_pressed_ ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
e.jbutton.button = 0;
g_input->PushJoystickEvent(e, joystick_);
@@ -121,7 +123,9 @@ void TestInput::Process(millisecs_t time) {
HandleAlreadyPressedTwice();
} else {
pickup_pressed_ = !pickup_pressed_;
- if (pickup_pressed_) join_press_count_++;
+ if (pickup_pressed_) {
+ join_press_count_++;
+ }
e.type = pickup_pressed_ ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
e.jbutton.button = 3;
g_input->PushJoystickEvent(e, joystick_);
@@ -133,7 +137,9 @@ void TestInput::Process(millisecs_t time) {
HandleAlreadyPressedTwice();
} else {
punch_pressed_ = !punch_pressed_;
- if (punch_pressed_) join_press_count_++;
+ if (punch_pressed_) {
+ join_press_count_++;
+ }
e.type = punch_pressed_ ? SDL_JOYBUTTONDOWN : SDL_JOYBUTTONUP;
e.jbutton.button = 1;
g_input->PushJoystickEvent(e, joystick_);
diff --git a/src/ballistica/platform/apple/platform_apple.h b/src/ballistica/platform/apple/platform_apple.h
index 5cd9ff0b..52f1c85a 100644
--- a/src/ballistica/platform/apple/platform_apple.h
+++ b/src/ballistica/platform/apple/platform_apple.h
@@ -77,7 +77,7 @@ class PlatformApple : public Platform {
auto DoClipboardHasText() -> bool override;
auto DoClipboardSetText(const std::string& text) -> void override;
auto DoClipboardGetText() -> std::string override;
- auto GetPublicDeviceUUIDInputs() -> std::list override;
+ auto GetDeviceUUIDInputs() -> std::list override;
private:
};
diff --git a/src/ballistica/platform/linux/platform_linux.cc b/src/ballistica/platform/linux/platform_linux.cc
index 18bfb5a8..de9f1ed9 100644
--- a/src/ballistica/platform/linux/platform_linux.cc
+++ b/src/ballistica/platform/linux/platform_linux.cc
@@ -29,7 +29,7 @@ std::string PlatformLinux::GenerateUUID() {
return val;
}
-auto PlatformLinux::GetPublicDeviceUUIDInputs() -> std::list {
+auto PlatformLinux::GetDeviceUUIDInputs() -> std::list {
std::list out;
// For now let's just go with machine-id.
diff --git a/src/ballistica/platform/linux/platform_linux.h b/src/ballistica/platform/linux/platform_linux.h
index 07f09b5b..87c96304 100644
--- a/src/ballistica/platform/linux/platform_linux.h
+++ b/src/ballistica/platform/linux/platform_linux.h
@@ -21,7 +21,7 @@ class PlatformLinux : public Platform {
auto OpenDirExternally(const std::string& path) -> void override;
auto GetPlatformName() -> std::string override;
auto GetSubplatformName() -> std::string override;
- auto GetPublicDeviceUUIDInputs() -> std::list override;
+ auto GetDeviceUUIDInputs() -> std::list override;
};
} // namespace ballistica
diff --git a/src/ballistica/platform/platform.cc b/src/ballistica/platform/platform.cc
index 5032a513..960f3514 100644
--- a/src/ballistica/platform/platform.cc
+++ b/src/ballistica/platform/platform.cc
@@ -184,17 +184,13 @@ auto Platform::GenerateUUID() -> std::string {
auto Platform::GetPublicDeviceUUID() -> std::string {
assert(g_python);
if (public_device_uuid_.empty()) {
- std::list inputs{GetPublicDeviceUUIDInputs()};
+ std::list inputs{GetDeviceUUIDInputs()};
// This UUID is supposed to change periodically, so let's plug in
// some stuff to enforce that.
inputs.emplace_back(GetOSVersionString());
inputs.emplace_back(kAppVersion);
inputs.emplace_back("kerploople");
- // int i{};
- // for (auto&& input : inputs) {
- // printf("INPUT %d IS %s\n", i + 1, input.c_str());
- // }
auto gil{Python::ScopedInterpreterLock()};
auto pylist{g_python->StringList(inputs)};
auto args{g_python->SingleMemberTuple(pylist)};
@@ -205,8 +201,8 @@ auto Platform::GetPublicDeviceUUID() -> std::string {
return public_device_uuid_;
}
-auto Platform::GetPublicDeviceUUIDInputs() -> std::list {
- throw Exception("GetPublicDeviceUUIDInputs unimplemented");
+auto Platform::GetDeviceUUIDInputs() -> std::list {
+ throw Exception("GetDeviceUUIDInputs unimplemented");
}
auto Platform::GetDefaultConfigDir() -> std::string {
@@ -860,7 +856,7 @@ auto Platform::GetUserAgentString() -> std::string {
+ "; " + GetLocale() + ")"};
// This gets shipped to various places which might choke on fancy unicode
- // characers, so let's limit to simple ascii.
+ // characters, so let's limit to simple ascii.
out = Utils::StripNonAsciiFromUTF8(out);
return out;
diff --git a/src/ballistica/platform/platform.h b/src/ballistica/platform/platform.h
index e8a130fe..f6795e07 100644
--- a/src/ballistica/platform/platform.h
+++ b/src/ballistica/platform/platform.h
@@ -186,13 +186,12 @@ class Platform {
/// server bans or spam prevention.
auto GetPublicDeviceUUID() -> std::string;
- /// Return values which will be hashed to create a public device uuid.
- /// These values may include things that may change periodically such
- /// as minor os version numbers, but they should not include things
- /// that change constantly or that can be changed easily by the user.
- /// Only hashed versions of these values should ever be shared beyond
- /// the local device.
- virtual auto GetPublicDeviceUUIDInputs() -> std::list;
+ /// Return values which can be hashed to create a public device uuid.
+ /// Ideally these values should come from an OS-provided guid. They
+ /// should not include anything that is easily user-changeable.
+ /// IMPORTANT: Only hashed/transformed versions of these values should
+ /// ever be shared beyond the local device.
+ virtual auto GetDeviceUUIDInputs() -> std::list;
/// Return whether there is an actual legacy-device-uuid value for
/// this platform, and also return it if so.
diff --git a/src/ballistica/platform/windows/platform_windows.cc b/src/ballistica/platform/windows/platform_windows.cc
index 26710c19..35663fee 100644
--- a/src/ballistica/platform/windows/platform_windows.cc
+++ b/src/ballistica/platform/windows/platform_windows.cc
@@ -127,7 +127,7 @@ void PlatformWindows::SetupInterruptHandling() {
}
}
-auto PlatformWindows::GetPublicDeviceUUIDInputs() -> std::list {
+auto PlatformWindows::GetDeviceUUIDInputs() -> std::list {
std::list out;
std::string ret;
diff --git a/src/ballistica/platform/windows/platform_windows.h b/src/ballistica/platform/windows/platform_windows.h
index 844f8801..c41add71 100644
--- a/src/ballistica/platform/windows/platform_windows.h
+++ b/src/ballistica/platform/windows/platform_windows.h
@@ -16,7 +16,7 @@ class PlatformWindows : public Platform {
PlatformWindows();
void SetupInterruptHandling() override;
auto GetDeviceAccountUUIDPrefix() -> std::string override { return "w"; }
- auto GetPublicDeviceUUIDInputs() -> std::list override;
+ auto GetDeviceUUIDInputs() -> std::list override;
auto GenerateUUID() -> std::string override;
auto GetDefaultConfigDir() -> std::string override;
auto Remove(const char* path) -> int;
diff --git a/tests/test_efro/test_dataclassio.py b/tests/test_efro/test_dataclassio.py
index 28b7496f..83384620 100644
--- a/tests/test_efro/test_dataclassio.py
+++ b/tests/test_efro/test_dataclassio.py
@@ -873,3 +873,84 @@ def test_extended_data() -> None:
# Uses our output filter.
assert dataclass_to_dict(_TestClass2(vals=(1, 2))) == {'vals': [0, 0]}
+
+
+def test_soft_default() -> None:
+ """Test soft_default IOAttr value."""
+
+ # Try both of these with and without storage_name to make sure
+ # soft_default interacts correctly with both cases.
+
+ @ioprepped
+ @dataclass
+ class _TestClassA:
+ ival: int
+
+ @ioprepped
+ @dataclass
+ class _TestClassA2:
+ ival: Annotated[int, IOAttrs('i')]
+
+ @ioprepped
+ @dataclass
+ class _TestClassB:
+ ival: Annotated[int, IOAttrs(soft_default=0)]
+
+ @ioprepped
+ @dataclass
+ class _TestClassB2:
+ ival: Annotated[int, IOAttrs('i', soft_default=0)]
+
+ @ioprepped
+ @dataclass
+ class _TestClassB3:
+ ival: Annotated[int, IOAttrs('i', soft_default_factory=lambda: 0)]
+
+ # These should fail because there's no value for ival.
+ with pytest.raises(ValueError):
+ dataclass_from_dict(_TestClassA, {})
+
+ with pytest.raises(ValueError):
+ dataclass_from_dict(_TestClassA2, {})
+
+ # These should succeed because it has a soft-default value to
+ # fall back on.
+ dataclass_from_dict(_TestClassB, {})
+ dataclass_from_dict(_TestClassB2, {})
+ dataclass_from_dict(_TestClassB3, {})
+
+ # soft_default should also allow using store_default=False without
+ # requiring the dataclass to contain a default or default_factory
+
+ @ioprepped
+ @dataclass
+ class _TestClassC:
+ ival: Annotated[int, IOAttrs(store_default=False)] = 0
+
+ assert dataclass_to_dict(_TestClassC()) == {}
+
+ # This should fail since store_default would be meaningless without
+ # any source for the default value.
+ with pytest.raises(TypeError):
+
+ @ioprepped
+ @dataclass
+ class _TestClassC2:
+ ival: Annotated[int, IOAttrs(store_default=False)]
+
+ # However with our shiny soft_default it should work.
+ @ioprepped
+ @dataclass
+ class _TestClassC3:
+ ival: Annotated[int, IOAttrs(store_default=False, soft_default=0)]
+
+ assert dataclass_to_dict(_TestClassC3(0)) == {}
+
+ # we should disallow passing a few mutable types as soft_defaults
+ # just as dataclass does with regular defaults.
+ with pytest.raises(TypeError):
+
+ @ioprepped
+ @dataclass
+ class _TestClassD:
+ lval: Annotated[list, IOAttrs(soft_default=[])]
diff --git a/tools/efro/dataclassio/_base.py b/tools/efro/dataclassio/_base.py
index 1353c36b..d43b0fcf 100644
--- a/tools/efro/dataclassio/_base.py
+++ b/tools/efro/dataclassio/_base.py
@@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, get_args
from typing import _AnnotatedAlias # type: ignore
if TYPE_CHECKING:
- from typing import Any, Optional
+ from typing import Any, Optional, Callable, Union
# Types which we can pass through as-is.
SIMPLE_TYPES = {int, bool, str, float, type(None)}
@@ -96,18 +96,50 @@ def _is_valid_for_codec(obj: Any, codec: Codec) -> bool:
class IOAttrs:
- """For specifying io behavior in annotations."""
+ """For specifying io behavior in annotations.
+
+ 'storagename', if passed, is the name used when storing to json/etc.
+ 'store_default' can be set to False to avoid writing values when equal
+ to the default value. Note that this requires the dataclass field
+ to define a default or default_factory or for its IOAttrs to
+ define a soft_default value.
+ 'whole_days', if True, requires datetime values to be exactly on day
+ boundaries (see efro.util.utc_today()).
+ 'whole_hours', if True, requires datetime values to lie exactly on hour
+ boundaries (see efro.util.utc_this_hour()).
+ 'soft_default', if passed, is used as a default for loading and storing
+ purposes, but leaves the dataclass itself expecting a value to
+ be passed in all constructor calls/etc. This is useful when fields
+ are added that should not be considered optional in new code but for
+ which there may exist old data that does not contain those values.
+ 'soft_default_factory' should be given instead of 'soft_default' for
+ mutable types such as lists (to prevent the default from changing
+ over time).
+ """
+
+ # A sentinel object to detect if a parameter is supplied or not. Use
+ # a class to give it a better repr.
+ class _MissingType:
+ pass
+
+ MISSING = _MissingType()
storagename: Optional[str] = None
store_default: bool = True
whole_days: bool = False
whole_hours: bool = False
+ soft_default: Any = MISSING
+ soft_default_factory: Union[Callable[[], Any], _MissingType] = MISSING
- def __init__(self,
- storagename: Optional[str] = storagename,
- store_default: bool = store_default,
- whole_days: bool = whole_days,
- whole_hours: bool = whole_hours):
+ def __init__(
+ self,
+ storagename: Optional[str] = storagename,
+ store_default: bool = store_default,
+ whole_days: bool = whole_days,
+ whole_hours: bool = whole_hours,
+ soft_default: Any = MISSING,
+ soft_default_factory: Union[Callable[[], Any], _MissingType] = MISSING,
+ ):
# Only store values that differ from class defaults to keep
# our instances nice and lean.
@@ -120,18 +152,34 @@ class IOAttrs:
self.whole_days = whole_days
if whole_hours != cls.whole_hours:
self.whole_hours = whole_hours
+ if soft_default is not cls.soft_default:
+
+ # Do what dataclasses does with its default types and
+ # tell the user to use factory for mutable ones.
+ if isinstance(soft_default, (list, dict, set)):
+ raise ValueError(
+ f'mutable {type(soft_default)} is not allowed'
+ f' for soft_default; use soft_default_factory.')
+ self.soft_default = soft_default
+ if soft_default_factory is not cls.soft_default_factory:
+ self.soft_default_factory = soft_default_factory
def validate_for_field(self, cls: type, field: dataclasses.Field) -> None:
"""Ensure the IOAttrs instance is ok to use with the provided field."""
# Turning off store_default requires the field to have either
- # a default_factory or a default
+ # a default or a a default_factory or for us to have soft equivalents.
+
if not self.store_default:
- default_factory: Any = field.default_factory
- if (default_factory is dataclasses.MISSING
- and field.default is dataclasses.MISSING):
+ field_default_factory: Any = field.default_factory
+ if (field_default_factory is dataclasses.MISSING
+ and field.default is dataclasses.MISSING
+ and self.soft_default is self.MISSING
+ and self.soft_default_factory is self.MISSING):
raise TypeError(f'Field {field.name} of {cls} has'
- f' neither a default nor a default_factory;'
+ f' neither a default nor a default_factory'
+ f' and IOAttrs contains neither a soft_default'
+ f' nor a soft_default_factory;'
f' store_default=False cannot be set for it.')
def validate_datetime(self, value: datetime.datetime,
diff --git a/tools/efro/dataclassio/_inputter.py b/tools/efro/dataclassio/_inputter.py
index 274e1681..92e2c502 100644
--- a/tools/efro/dataclassio/_inputter.py
+++ b/tools/efro/dataclassio/_inputter.py
@@ -158,6 +158,7 @@ class _Inputter(Generic[T]):
associated values, and nested dataclasses should be passed as dicts.
"""
# pylint: disable=too-many-locals
+ # pylint: disable=too-many-branches
if not isinstance(values, dict):
raise TypeError(
f'Expected a dict for {fieldpath} on {cls.__name__};'
@@ -172,6 +173,16 @@ class _Inputter(Generic[T]):
# noinspection PyDataclass
fields = dataclasses.fields(cls)
fields_by_name = {f.name: f for f in fields}
+
+ # Preprocess all fields to convert Annotated[] to contained types
+ # and IOAttrs.
+ parsed_field_annotations = {
+ f.name: _parse_annotated(prep.annotations[f.name])
+ for f in fields
+ }
+
+ # Go through all data in the input, converting it to either dataclass
+ # args or extra data.
args: dict[str, Any] = {}
for rawkey, value in values.items():
key = prep.storage_names_to_attr_names.get(rawkey, rawkey)
@@ -197,18 +208,32 @@ class _Inputter(Generic[T]):
f"'{cls.__name__}' has no '{key}' field.")
else:
fieldname = field.name
- anntype = prep.annotations[fieldname]
- anntype, ioattrs = _parse_annotated(anntype)
-
+ anntype, ioattrs = parsed_field_annotations[fieldname]
subfieldpath = (f'{fieldpath}.{fieldname}'
if fieldpath else fieldname)
args[key] = self._value_from_input(cls, subfieldpath, anntype,
value, ioattrs)
+
+ # Go through all fields looking for any not yet present in our data.
+ # If we find any such fields with a soft-default value or factory
+ # defined, inject that value into our args.
+ for key, aparsed in parsed_field_annotations.items():
+ if key not in args:
+ ioattrs = aparsed[1]
+ if (ioattrs is not None and
+ (ioattrs.soft_default is not ioattrs.MISSING
+ or ioattrs.soft_default_factory is not ioattrs.MISSING)):
+ if ioattrs.soft_default is not ioattrs.MISSING:
+ args[key] = ioattrs.soft_default
+ else:
+ assert callable(ioattrs.soft_default_factory)
+ args[key] = ioattrs.soft_default_factory()
+
try:
out = cls(**args)
except Exception as exc:
- raise RuntimeError(f'Error instantiating class {cls.__name__}'
- f' at {fieldpath}: {exc}') from exc
+ raise ValueError(f'Error instantiating class {cls.__name__}'
+ f' at {fieldpath}: {exc}') from exc
if extra_attrs:
setattr(out, EXTRA_ATTRS_ATTR, extra_attrs)
return out
diff --git a/tools/efro/dataclassio/_outputter.py b/tools/efro/dataclassio/_outputter.py
index 746223db..03bb74be 100644
--- a/tools/efro/dataclassio/_outputter.py
+++ b/tools/efro/dataclassio/_outputter.py
@@ -68,18 +68,25 @@ class _Outputter:
# we can skip all output processing if we've got a default value.
if ioattrs is not None and not ioattrs.store_default:
default_factory: Any = field.default_factory
- if default_factory is not dataclasses.MISSING:
+ if field.default is not dataclasses.MISSING:
+ if field.default == value:
+ continue
+ elif default_factory is not dataclasses.MISSING:
if default_factory() == value:
continue
- elif field.default is not dataclasses.MISSING:
- if field.default == value:
+ elif ioattrs.soft_default is not ioattrs.MISSING:
+ if ioattrs.soft_default == value:
+ continue
+ elif ioattrs.soft_default_factory is not ioattrs.MISSING:
+ assert callable(ioattrs.soft_default_factory)
+ if ioattrs.soft_default_factory() == value:
continue
else:
raise RuntimeError(
f'Field {fieldname} of {cls.__name__} has'
- f' neither a default nor a default_factory;'
- f' store_default=False cannot be set for it.'
- f' (AND THIS SHOULD HAVE BEEN CAUGHT IN PREP!)')
+ f' no source of default values; store_default=False'
+ f' cannot be set for it. (AND THIS SHOULD HAVE BEEN'
+ f' CAUGHT IN PREP!)')
outvalue = self._process_value(cls, subfieldpath, anntype, value,
ioattrs)
diff --git a/tools/efro/rpc.py b/tools/efro/rpc.py
index 9ff1d3a3..3484efff 100644
--- a/tools/efro/rpc.py
+++ b/tools/efro/rpc.py
@@ -316,10 +316,6 @@ class RPCEndpoint:
self._debug_print_call(
f'{self._label}: tasks finished; waiting for writer close...')
- # At this point we shouldn't touch our tasks anymore.
- # Clearing them out allows us to go down
- # del self._tasks
-
# Now wait for our writer to finish going down.
# When we close our writer it generally triggers errors
# in our current blocked read/writes. However that same
@@ -328,7 +324,18 @@ class RPCEndpoint:
# So let's silently ignore it when that happens.
assert self._writer.is_closing()
try:
- await self._writer.wait_closed()
+ # It seems that as of Python 3.9.x it is possible for this to hang
+ # indefinitely. See https://github.com/python/cpython/issues/83939
+ # It sounds like this should be fixed in 3.11 but for now just
+ # forcing the issue with a timeout here.
+ await asyncio.wait_for(self._writer.wait_closed(), timeout=10.0)
+ except asyncio.TimeoutError:
+ logging.info('Timeout on _writer.wait_closed() for %s.',
+ self._label)
+ if self._debug_print:
+ self._debug_print_call(
+ f'{self._label}: got timeout in _writer.wait_closed();'
+ ' This should be fixed in future Python versions.')
except Exception as exc:
if not self._is_expected_connection_error(exc):
logging.exception('Error closing _writer for %s.', self._label)