diff --git a/.efrocachemap b/.efrocachemap
index 4395799c..3cba208c 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -3995,50 +3995,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
- "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/55/b1/d1c692a3ddbcfec532e71a827f74",
- "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e4/02/9697c22bdc862cf4024da4291a67",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/92/52/03c71172f9ef4ebaf62e7b61fa3b",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1e/ea/529dc5f93a4597eb32dbc6adc5fc",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/65/0c/75a9348dab828ae8c35f4ea4a5af",
- "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8b/6c/21066172ee06ba37a9a65ad8201f",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/96/32/bc967f00d76dbef3df1a8580cfdd",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/63/73/09ea54fc26fa042970c298ced100",
- "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d6/11/859b3a49d2d76f83318708146f78",
- "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ec/92/b722ad7712ee2ecc15a595c0b152",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ee/43/67c17b0f8f5c6aad23c6eec0a4dc",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/29/c0/988af43d3cf5a22a8959d0c26aa0",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6e/ea/7ae615507621320412e5e3d71052",
- "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/90/40/4e57c929d175668f19ff52b14a80",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/67/43/7bb14e1ddb12ac6f321d02def119",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6e/f7/b892e1e69f7605e6ce3099c80ff6",
- "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d6/fe/037d6240e9b41f82e6d5b51b9e3a",
- "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cc/c7/63b1d1fa549417fb4f9a50704e09",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/06/f9/3f412708758f422557b56e3139a6",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/9f/c3/cc12905869eba2278e4021316233",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/70/9d/82e666398551b30fc854ed62682c",
- "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/82/ae4fb4b892054b1e09f2d0dd430b",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5b/c3/71ef621365cb3cd5d05c770ba342",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/c3/332d53ad4beb62b4b315d5f14b5c",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/f0/84557f7b3159a7f4600ee619689b",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/64/1ef3f2dab606a16586c5ace3b57f",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9a/4d/5249822bd9c9e9b39ae8f635bfbb",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cf/81/0187c04509a2e6eb1bbf82cf634d",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/dc/ac/782541d23b9419b1eeda8d17bd58",
- "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a7/ad/09b4bbe131cb94bf7e63dba25d84",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/c4/191772694cf5b8266228f2608f79",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d2/81/7ad01e29e031f2fa2f48ae4663ba",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/41/9e/e65888340a6a98d2c2e51bce0ea9",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b3/a4/8781964774ecfcb2d5c84029d9c9",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/08/a3/709ddff4335eff11913c75892ac2",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5f/46/da058d1d0c43e7193275f3970d46",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/87/60/f4b7c14aeac9e6e14ecb564608c6",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/72/b1/0fc35401b8475d86b20cc138ab40",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/aa/ce/22120a61f7b9dce0d822dc4e8794",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e5/1c/b52a0879d61e86a2d550b3882682",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/c1/36/db7c22ed5a386c6ea29c59ffc20e",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/77/26/93d4d7345649dd59beb21df0a0e6",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e2/bd/7cc56c36d6d45f6c29c37a3d2874",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/b7/47/662d87a97f3dc85e34b046b50e98",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b7/31/a293aea8039ee830d3577179aa81",
+ "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a2/11/7a48cde8029be043579abf94179e",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6b/77/198dd7c53b085c116c77a7877af5",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/14/9f/ca41b53c5fa84b063b5d089f061f",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/75/a2/f30da762e9451cea11bbf89b4c6e",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b8/de/fd123320eaac865c1484e7678397",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/81/90/b8f77fe7c99f6caf7305734a0962",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8d/8c/7a6b8f0c7a62b6fd7c67e89d9b3c",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9a/17/a6fdb6d82f342786e32ba4c64ded",
+ "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c1/14/f93f5ebba5ff82ca596931dbc30a",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/30/9c/9a8758cd98e1c2d566ef61d0c511",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5b/2f/3e28b0c669980be5f960515c125e",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/16/5c/de01bf8f26d3bea3cf30240dc609",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/85/8d848c0eee73737090a2e68dab13",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c6/9f/0b3cfdd1b580768b2cb0a3509de2",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/c5/688b96898a6cdd8d8c5a410d3dd9",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/44/a9/b16f6100e676469ee2c1fa48cde0",
+ "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/c8/d8/65aba46607929b1f697d9d082330",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/41/4c/0e5722286d59009754ac6d0b6867",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/cb/60/54dd87249f9e40b8075943bcab0f",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e2/88/53757bc9fd92d49bd35dc6d3be0e",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/69/078f7eef49a1127a1492db4703f6",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/82/f1/2b13fe77164f72d2bf57453bb8e5",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/b0/a853b61ab794706bbf395ecd2a80",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/76/3d/b0d2913a1650bdc35b2ca0d81154",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/b3/39a0642a376e1f131172f9500353",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/07/4804a222e0d92f0fab8b279ce4c1",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/55/09/bf8e7d6ce41962163411c6bbd884",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/be/5e0b2be7272c4e443cc974d5b182",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5e/ab/075e9137d21e6110d29b67210533",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/2a/c5e1d5ed4328c40821695db2cd84",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b7/dd/55210b3f3c075d9b237e0c6aa733",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a2/00/d01842e8b0777f7e6ea47c912b16",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/91/f9cb15d0876750e28abe3b0d221c",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b7/11/1ecfe322ae997772b71538664cad",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8d/d6/1e83dba73d581cfb2b2f6eb31f22",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/5c/e1/a38642b99936733a5a560f9300d3",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/24/31/78c54905becd2f236a620dda01e1",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7e/f5/48e9c6cd11de662188fef4484e6d",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e7/40/fce7451ad5e3802aeb058dc15712",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/a5/07/a9b7fdd826d2235e8ad2e398e13c",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/05/7e/c97aa4f1876c7a5029fb9478e1a5",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/e9/3e/120aaa006c5494394d862854fa4e",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/ac/c9/3d3fe16a6fe892945977350861c7",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 7861d0c9..4bbeebf2 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -603,6 +603,7 @@
depsval
dereferencing
descpos
+ desctype
dest
destdir
devel
@@ -1046,9 +1047,12 @@
getname
getnodes
getnodetype
+ getobj
+ getobjs
getopt
getplayer
getpt
+ getrefs
getremote
getres
getscanresults
@@ -1719,6 +1723,7 @@
nvidia
nyko
obj's
+ objid
objname
objs
objt
@@ -1794,6 +1799,8 @@
packagepathstr
packageversion
painttxtattr
+ pairsj
+ pairss
palmos
pandoc
pandroid
@@ -1947,6 +1954,7 @@
printnodes
printobjects
printpaths
+ printrefs
priv
privatetab
proactor
@@ -2630,6 +2638,8 @@
tplayer
tpos
tproxy
+ tpsj
+ tpss
tracebacks
tracemalloc
tradeoff
@@ -2644,6 +2654,7 @@
trynum
tscale
tscl
+ tsed
tself
tspc
tsrcpath
@@ -2843,6 +2854,7 @@
wsroot
wtcolor
wtflib
+ wtfslice
wttxt
wvmpth
xach
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 36d71f1a..20a4c476 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,9 @@
-### 1.7.10 (build 20882, api 7, 2022-09-24)
+### 1.7.10 (build 20884, api 7, 2022-09-27)
- Added eval support for cloud-console. This means you can type something like '1+1' in the console and see '2' printed. This is how Python behaves in the stdin console or in-game console or the standard Python interpreter.
- Exceptions in the cloud-console now print to stderr instead of logging.exception(). This means they aren't a pretty red color anymore, but this will keep cloud-console behaving well with things like servers where logging.exception() might trigger alarms or otherwise. This is also consistent with standard interactive Python behavior.
- Cloud console now shows the device name at the top instead of simply 'Console' while connected.
- Moved the function that actually runs cloud console code to `ba._cloud.cloud_console_exec()`.
+- Upgraded bundled Python (for Android and Apple builds) to 3.10.7.
### 1.7.9 (build 20880, api 7, 2022-09-24)
- Cleaned up the efro.message system to isolate response types that are used purely internally (via a new SysResponse type).
diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json
index daf534f9..460603ae 100644
--- a/assets/.asset_manifest_public.json
+++ b/assets/.asset_manifest_public.json
@@ -65,7 +65,6 @@
"ba_data/python/ba/__pycache__/_tournament.cpython-310.opt-1.pyc",
"ba_data/python/ba/__pycache__/_ui.cpython-310.opt-1.pyc",
"ba_data/python/ba/__pycache__/_workspace.cpython-310.opt-1.pyc",
- "ba_data/python/ba/__pycache__/deprecated.cpython-310.opt-1.pyc",
"ba_data/python/ba/__pycache__/internal.cpython-310.opt-1.pyc",
"ba_data/python/ba/__pycache__/macmusicapp.cpython-310.opt-1.pyc",
"ba_data/python/ba/__pycache__/modutils.cpython-310.opt-1.pyc",
@@ -136,7 +135,6 @@
"ba_data/python/ba/_tournament.py",
"ba_data/python/ba/_ui.py",
"ba_data/python/ba/_workspace.py",
- "ba_data/python/ba/deprecated.py",
"ba_data/python/ba/internal.py",
"ba_data/python/ba/macmusicapp.py",
"ba_data/python/ba/modutils.py",
@@ -516,6 +514,7 @@
"ba_data/python/efro/__init__.py",
"ba_data/python/efro/__pycache__/__init__.cpython-310.opt-1.pyc",
"ba_data/python/efro/__pycache__/call.cpython-310.opt-1.pyc",
+ "ba_data/python/efro/__pycache__/debug.cpython-310.opt-1.pyc",
"ba_data/python/efro/__pycache__/error.cpython-310.opt-1.pyc",
"ba_data/python/efro/__pycache__/log.cpython-310.opt-1.pyc",
"ba_data/python/efro/__pycache__/rpc.cpython-310.opt-1.pyc",
@@ -538,6 +537,7 @@
"ba_data/python/efro/dataclassio/_pathcapture.py",
"ba_data/python/efro/dataclassio/_prep.py",
"ba_data/python/efro/dataclassio/extras.py",
+ "ba_data/python/efro/debug.py",
"ba_data/python/efro/error.py",
"ba_data/python/efro/log.py",
"ba_data/python/efro/message/__init__.py",
diff --git a/assets/Makefile b/assets/Makefile
index d9c89f72..07334f66 100644
--- a/assets/Makefile
+++ b/assets/Makefile
@@ -199,7 +199,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/ba/_tournament.py \
build/ba_data/python/ba/_ui.py \
build/ba_data/python/ba/_workspace.py \
- build/ba_data/python/ba/deprecated.py \
build/ba_data/python/ba/internal.py \
build/ba_data/python/ba/macmusicapp.py \
build/ba_data/python/ba/modutils.py \
@@ -451,7 +450,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/ba/__pycache__/_tournament.cpython-310.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_ui.cpython-310.opt-1.pyc \
build/ba_data/python/ba/__pycache__/_workspace.cpython-310.opt-1.pyc \
- build/ba_data/python/ba/__pycache__/deprecated.cpython-310.opt-1.pyc \
build/ba_data/python/ba/__pycache__/internal.cpython-310.opt-1.pyc \
build/ba_data/python/ba/__pycache__/macmusicapp.cpython-310.opt-1.pyc \
build/ba_data/python/ba/__pycache__/modutils.cpython-310.opt-1.pyc \
@@ -669,6 +667,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
build/ba_data/python/efro/dataclassio/_pathcapture.py \
build/ba_data/python/efro/dataclassio/_prep.py \
build/ba_data/python/efro/dataclassio/extras.py \
+ build/ba_data/python/efro/debug.py \
build/ba_data/python/efro/error.py \
build/ba_data/python/efro/log.py \
build/ba_data/python/efro/message/__init__.py \
@@ -700,6 +699,7 @@ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
build/ba_data/python/efro/dataclassio/__pycache__/_pathcapture.cpython-310.opt-1.pyc \
build/ba_data/python/efro/dataclassio/__pycache__/_prep.cpython-310.opt-1.pyc \
build/ba_data/python/efro/dataclassio/__pycache__/extras.cpython-310.opt-1.pyc \
+ build/ba_data/python/efro/__pycache__/debug.cpython-310.opt-1.pyc \
build/ba_data/python/efro/__pycache__/error.cpython-310.opt-1.pyc \
build/ba_data/python/efro/__pycache__/log.cpython-310.opt-1.pyc \
build/ba_data/python/efro/message/__pycache__/__init__.cpython-310.opt-1.pyc \
diff --git a/assets/src/ba_data/python/._ba_sources_hash b/assets/src/ba_data/python/._ba_sources_hash
index ac896033..861b23df 100644
--- a/assets/src/ba_data/python/._ba_sources_hash
+++ b/assets/src/ba_data/python/._ba_sources_hash
@@ -1 +1 @@
-69724857583156237926512795146611373217
\ No newline at end of file
+194057364831757023796080999188881665880
\ No newline at end of file
diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py
index 88a4a4eb..29dda6e0 100644
--- a/assets/src/ba_data/python/ba/_bootstrap.py
+++ b/assets/src/ba_data/python/ba/_bootstrap.py
@@ -45,7 +45,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
- expected_build = 20882
+ expected_build = 20884
running_build: int = env['build_number']
if running_build != expected_build:
print(
diff --git a/assets/src/ba_data/python/ba/deprecated.py b/assets/src/ba_data/python/ba/deprecated.py
deleted file mode 100644
index de3ff2aa..00000000
--- a/assets/src/ba_data/python/ba/deprecated.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""Deprecated functionality.
-
-Classes or functions can be relocated here when they are deprecated.
-Any code using them should migrate to alternative methods, as
-deprecated items will eventually be fully removed.
-"""
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 361b5efa..9519c9e8 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -328,6 +328,7 @@
demangling
denom
dernit
+ desctype
destdir
dets
dfba
@@ -545,6 +546,8 @@
getname
getnodes
getnodetype
+ getobj
+ getobjs
getpackagecollidemodel
getpackagedata
getpackagemodel
@@ -553,6 +556,7 @@
getpublicpartyenabled
getpublicpartymaxsize
getqrcodetexture
+ getrefs
getres
getsession
getsound
@@ -955,6 +959,8 @@
outvalue
ouya
ovld
+ pairsj
+ pairss
parameteriv
passcode
pathcapture
@@ -1011,6 +1017,7 @@
printf
printnodes
printobjects
+ printrefs
priv
privatetab
processinfoplistfile
@@ -1347,6 +1354,8 @@
tpimport
tpimportex
tpimports
+ tpsj
+ tpss
tracebacks
tracestr
trackpad
@@ -1361,6 +1370,7 @@
trimesh
trimeshes
trynum
+ tsed
tself
tsrcpath
tunmd
@@ -1469,6 +1479,7 @@
writeauxiliaryfile
wspath
wsroot
+ wtfslice
wunused
wvmpth
xcframework
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index 22d83546..e0d073dc 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -32,7 +32,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kAppBuildNumber = 20882;
+const int kAppBuildNumber = 20884;
const char* kAppVersion = "1.7.10";
// Our standalone globals.
diff --git a/src/ballistica/python/python_context_call.cc b/src/ballistica/python/python_context_call.cc
index 938e2dd1..3a3980cc 100644
--- a/src/ballistica/python/python_context_call.cc
+++ b/src/ballistica/python/python_context_call.cc
@@ -10,7 +10,7 @@
namespace ballistica {
// FIXME - should be static member var
-PythonContextCall* PythonContextCall::current_call_ = nullptr;
+PythonContextCall* PythonContextCall::current_call_{};
PythonContextCall::PythonContextCall(PyObject* obj_in) {
assert(InLogicThread());
diff --git a/tools/efro/debug.py b/tools/efro/debug.py
new file mode 100644
index 00000000..b474afd7
--- /dev/null
+++ b/tools/efro/debug.py
@@ -0,0 +1,170 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Utilities for debugging memory leaks or other issues."""
+from __future__ import annotations
+
+import gc
+import sys
+import types
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from typing import Any
+
+ABS_MAX_LEVEL = 10
+
+# NOTE: In general we want this toolset to allow us to explore
+# which objects are holding references to others so we can diagnose
+# leaks/etc. It is a bit tricky to do that, however, without
+# affecting the objects we are looking at by adding temporary references
+# from module dicts, function scopes, etc. So we need to try to be
+# careful about cleaning up after ourselves and explicitly avoiding
+# returning these temporary references wherever possible.
+
+# A good test is running printrefs() repeatedly on some object that is
+# known to be static. If the list of references changes or the id or any
+# of the references, we're probably letting a temporary object sneak into
+# the results and should fix it.
+
+
+def getobjs(cls: type, contains: str | None = None) -> list[Any]:
+ """Return all garbage-collected objects matching criteria.
+
+ 'type' can be an actual type or a string in which case objects
+ whose types contain that string will be returned.
+
+ If 'contains' is provided, objects will be filtered to those
+ containing that in their str() representations.
+ """
+
+ # Don't wanna return stuff waiting to be garbage-collected.
+ gc.collect()
+
+ if not isinstance(cls, type | str):
+ raise TypeError('Expected a type or string for cls')
+ if not isinstance(contains, str | None):
+ raise TypeError('Expected a string or None for contains')
+
+ if isinstance(cls, str):
+ objs = [o for o in gc.get_objects() if cls in str(type(o))]
+ else:
+ objs = [o for o in gc.get_objects() if isinstance(o, cls)]
+ if contains is not None:
+ objs = [o for o in objs if contains in str(o)]
+
+ return objs
+
+
+def getobj(objid: int) -> Any:
+ """Return a garbage-collected object by its id.
+
+ Remember that this is VERY inefficient and should only ever be used
+ for debugging.
+ """
+ if not isinstance(objid, int):
+ raise TypeError(f'Expected an int for objid; got a {type(objid)}.')
+
+ # Don't wanna return stuff waiting to be garbage-collected.
+ for obj in gc.get_objects():
+ if id(obj) == objid:
+ return obj
+ raise RuntimeError(f'Object with id {objid} not found.')
+
+
+def getrefs(obj: Any) -> list[Any]:
+ """Given an object, return things referencing it."""
+ v = vars() # Ignore ref in locals.
+ return [o for o in gc.get_referrers(obj) if o is not v]
+
+
+def _desctype(obj: Any) -> str:
+ cls = type(obj)
+ if cls is types.ModuleType:
+ return f'{type(obj).__name__} {obj.__name__}'
+ if cls is types.MethodType:
+ bnd = 'bound' if hasattr(obj, '__self__') else 'unbound'
+ return f'{bnd} {type(obj).__name__} {obj.__name__}'
+ return f'{type(obj).__name__}'
+
+
+def _desc(obj: Any) -> str:
+ extra: str | None = None
+ if isinstance(obj, list | tuple):
+ # Print length and the first few types.
+ tps = [_desctype(i) for i in obj[:3]]
+ tpsj = ', '.join(tps)
+ tpss = (f', contains [{tpsj}, ...]'
+ if len(obj) > 3 else f', contains [{tpsj}]' if tps else '')
+ extra = f' (len {len(obj)}{tpss})'
+ elif isinstance(obj, dict):
+ # If it seems to be the vars() for a type or module,
+ # try to identify what.
+ for ref in getrefs(obj):
+ if hasattr(ref, '__dict__') and vars(ref) is obj:
+ extra = f' (vars for {_desctype(ref)} @ {id(ref)})'
+
+ # Generic dict: print length and the first few key:type pairs.
+ if extra is None:
+ pairs = [
+ f'{repr(n)}: {_desctype(v)}' for n, v in list(obj.items())[:3]
+ ]
+ pairsj = ', '.join(pairs)
+ pairss = (f', contains {{{pairsj}, ...}}' if len(obj) > 3 else
+ f', contains {{{pairsj}}}' if pairs else '')
+ extra = f' (len {len(obj)}{pairss})'
+ if extra is None:
+ extra = ''
+ return f'{_desctype(obj)} @ {id(obj)}{extra}'
+
+
+def _printrefs(obj: Any, level: int, max_level: int, exclude_objs: list,
+ expand_ids: list[int]) -> None:
+ ind = ' ' * level
+ print(ind + _desc(obj), file=sys.stderr)
+ v = vars()
+ if level < max_level or (id(obj) in expand_ids and level < ABS_MAX_LEVEL):
+ refs = getrefs(obj)
+ for ref in refs:
+
+ # It seems we tend to get a transient cell object with contents
+ # set to obj. Would be nice to understand why that happens
+ # but just ignoring it for now.
+ if isinstance(ref, types.CellType) and ref.cell_contents is obj:
+ continue
+
+ # Ignore anything we were asked to ignore.
+ if exclude_objs is not None:
+ if any(ref is eobj for eobj in exclude_objs):
+ continue
+
+ # Ignore references from our locals.
+ if ref is v:
+ continue
+
+ # The 'refs' list we just made will be listed as a referrer
+ # of this obj, so explicitly exclude it from the obj's listing.
+ _printrefs(ref,
+ level=level + 1,
+ max_level=max_level,
+ exclude_objs=exclude_objs + [refs],
+ expand_ids=expand_ids)
+
+
+def printrefs(obj: Any,
+ max_level: int = 2,
+ exclude_objs: list[Any] | None = None,
+ expand_ids: list[int] | None = None) -> None:
+ """Print human readable list of objects referring to an object.
+
+ 'max_level' specifies how many levels of recursion are printed.
+ 'exclude_objs' can be a list of exact objects to skip if found in the
+ referrers list. This can be useful to avoid printing the local context
+ where the object was passed in from (locals(), etc).
+ 'expand_ids' can be a list of object ids; if that particular object is
+ found, it will always be expanded even if max_level has been reached.
+ """
+ _printrefs(obj,
+ level=0,
+ max_level=max_level,
+ exclude_objs=[] if exclude_objs is None else exclude_objs,
+ expand_ids=[] if expand_ids is None else expand_ids)
diff --git a/tools/efro/rpc.py b/tools/efro/rpc.py
index 93c78ab5..793f434c 100644
--- a/tools/efro/rpc.py
+++ b/tools/efro/rpc.py
@@ -61,6 +61,21 @@ class _PeerInfo:
OUR_PROTOCOL = 2
+def ssl_stream_writer_underlying_transport_info(
+ writer: asyncio.StreamWriter) -> str:
+ """For debugging SSL Stream connections; returns raw transport info."""
+ # Note: accessing internals here so just returning info and not
+ # actual objs to reduce potential for breakage.
+ transport = getattr(writer, '_transport', None)
+ if transport is not None:
+ sslproto = getattr(transport, '_ssl_protocol', None)
+ if sslproto is not None:
+ raw_transport = getattr(sslproto, '_transport', None)
+ if raw_transport is not None:
+ return str(raw_transport)
+ return '(not found)'
+
+
class _InFlightMessage:
"""Represents a message that is out on the wire."""
@@ -138,6 +153,8 @@ class RPCEndpoint:
self._peer_info: _PeerInfo | None = None
self._keepalive_interval = keepalive_interval
self._keepalive_timeout = keepalive_timeout
+ self._did_close_writer = False
+ self._did_wait_closed_writer = False
# Need to hold weak-refs to these otherwise it creates dep-loops
# which keeps us alive.
@@ -156,6 +173,19 @@ class RPCEndpoint:
self._debug_print_call(
f'{self._label}: connected to {peername} at {self._tm()}.')
+ def __del__(self) -> None:
+ if self._run_called:
+ if not self._did_close_writer:
+ logging.warning(
+ 'RPCEndpoint %d dying with run'
+ ' called but writer not closed (transport=%s).', id(self),
+ ssl_stream_writer_underlying_transport_info(self._writer))
+ elif not self._did_wait_closed_writer:
+ logging.warning(
+ 'RPCEndpoint %d dying with run called'
+ ' but writer not wait-closed (transport=%s).', id(self),
+ ssl_stream_writer_underlying_transport_info(self._writer))
+
async def run(self) -> None:
"""Run the endpoint until the connection is lost or closed.
@@ -261,6 +291,9 @@ class RPCEndpoint:
try:
return await asyncio.wait_for(msgobj.wait_task, timeout=timeout)
except asyncio.CancelledError as exc:
+ # Question: we assume this means the above wait_for() was
+ # cancelled; what happens if a task running *us* is cancelled
+ # though?
if self._debug_print:
self._debug_print_call(
f'{self._label}: message {message_id} was cancelled.')
@@ -297,9 +330,12 @@ class RPCEndpoint:
for task in self._get_live_tasks():
task.cancel()
+ # Close our writer.
+ assert not self._did_close_writer
if self._debug_print:
self._debug_print_call(f'{self._label}: closing writer...')
self._writer.close()
+ self._did_close_writer = True
# We don't need this anymore and it is likely to be creating a
# dependency loop.
@@ -311,6 +347,7 @@ class RPCEndpoint:
async def wait_closed(self) -> None:
"""I said seagulls; mmmm; stop it now."""
+ # pylint: disable=too-many-branches
self._check_env()
# Make sure we only *enter* this call once.
@@ -321,6 +358,10 @@ class RPCEndpoint:
if not self._closing:
raise RuntimeError('Must be called after close()')
+ if not self._did_close_writer:
+ logging.warning('RPCEndpoint wait_closed() called but never'
+ ' explicitly closed writer.')
+
live_tasks = self._get_live_tasks()
if self._debug_print:
self._debug_print_call(
@@ -333,10 +374,8 @@ class RPCEndpoint:
# We want to know if any errors happened aside from CancelledError
# (which are BaseExceptions, not Exception).
if isinstance(result, Exception):
- if self._debug_print:
- logging.error(
- 'Got unexpected error cleaning up %s task: %s',
- self._label, result)
+ logging.warning('Got unexpected error cleaning up %s task: %s',
+ self._label, result)
if self._debug_print:
self._debug_print_call(
@@ -354,10 +393,14 @@ class RPCEndpoint:
# 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.
+ assert not self._did_wait_closed_writer
+ self._did_wait_closed_writer = True
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)
+ logging.info(
+ 'Timeout on _writer.wait_closed() for %s rpc (transport=%s).',
+ self._label,
+ ssl_stream_writer_underlying_transport_info(self._writer))
if self._debug_print:
self._debug_print_call(
f'{self._label}: got timeout in _writer.wait_closed();'
@@ -370,6 +413,10 @@ class RPCEndpoint:
self._debug_print_call(
f'{self._label}: silently ignoring error in'
f' _writer.wait_closed(): {exc}.')
+ except asyncio.CancelledError:
+ logging.warning('RPCEndpoint.wait_closed()'
+ ' got asyncio.CancelledError; not expected.')
+ raise
def _tm(self) -> str:
"""Simple readable time value for debugging."""
diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py
index 2e36910c..72832238 100644
--- a/tools/efrotools/pybuild.py
+++ b/tools/efrotools/pybuild.py
@@ -15,8 +15,8 @@ if TYPE_CHECKING:
# Python version we build here (not necessarily same as we use in repo).
PY_VER = '3.10'
-PY_VER_EXACT_ANDROID = '3.10.5'
-PY_VER_EXACT_APPLE = '3.10.4'
+PY_VER_EXACT_ANDROID = '3.10.7'
+PY_VER_EXACT_APPLE = '3.10.7'
ANDROID_PYTHON_REPO = 'https://github.com/GRRedWings/python3-android'
@@ -80,18 +80,18 @@ def build_apple(arch: str, debug: bool = False) -> None:
# Customize our minimum version requirements
txt = replace_exact(
txt,
- 'CFLAGS-macOS=-mmacosx-version-min=10.15\n',
- 'CFLAGS-macOS=-mmacosx-version-min=10.15\n',
+ 'VERSION_MIN-macOS=10.15\n',
+ 'VERSION_MIN-macOS=10.15\n',
)
txt = replace_exact(
txt,
- 'CFLAGS-iOS=-mios-version-min=12.0 ',
- 'CFLAGS-iOS=-mios-version-min=12.0 ',
+ 'VERSION_MIN-iOS=12.0\n',
+ 'VERSION_MIN-iOS=12.0\n',
)
txt = replace_exact(
txt,
- 'CFLAGS-tvOS=-mtvos-version-min=9.0 ',
- 'CFLAGS-tvOS=-mtvos-version-min=9.0 ',
+ 'VERSION_MIN-tvOS=9.0\n',
+ 'VERSION_MIN-tvOS=9.0\n',
)
assert '--with-pydebug' not in txt
@@ -119,23 +119,30 @@ def build_apple(arch: str, debug: bool = False) -> None:
# Inject our custom modifications to fire right after their normal
# Setup.local filtering and right before building (and pass the same
# 'slice' value they use so we can use it too).
- txt = replace_exact(
- txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
- '\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n',
- '\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
- '\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n'
- '\tcd $$(PYTHON_DIR-$(target)) && '
- f'../../../../../tools/pcommand python_apple_patch {arch} '
- '"$$(SLICE-$$(SDK-$(target)))"\n')
- txt = replace_exact(
- txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
- '\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n',
- '\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
- '\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n'
- '\tcd $$(PYTHON_DIR-$(os)) && '
- f'../../../../../tools/pcommand python_apple_patch {arch} '
- '"$$(SLICE-macosx)"\n')
-
+ # txt = replace_exact(
+ # txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
+ # '\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n',
+ # '\t\t\tsed -e "s/{{slice}}/$$(SLICE-$$(SDK-$(target)))/g" \\\n'
+ # '\t\t\t> $$(PYTHON_DIR-$(target))/Modules/Setup.local\n'
+ # '\tcd $$(PYTHON_DIR-$(target)) && '
+ # f'../../../../../tools/pcommand python_apple_patch {arch} '
+ # '"$$(SLICE-$$(SDK-$(target)))"\n')
+ # txt = replace_exact(
+ # txt, '\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
+ # '\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n',
+ # '\t\t\tsed -e "s/{{slice}}/$$(SLICE-macosx)/g" \\\n'
+ # '\t\t\t> $$(PYTHON_DIR-$(os))/Modules/Setup.local\n'
+ # '\tcd $$(PYTHON_DIR-$(os)) && '
+ # f'../../../../../tools/pcommand python_apple_patch {arch} '
+ # '"$$(SLICE-macosx)"\n')
+ # txt = replace_exact(
+ # txt,
+ # ' # Configure target Python\n',
+ # ' # Configure target Python\n'
+ # f'\t../../../../../tools/pcommand python_apple_patch'
+ # f'{arch} wtfslice\n',
+ # count=2,
+ # )
writefile('Makefile', txt)
# Ok; let 'er rip.
@@ -228,8 +235,8 @@ def apple_patch(arch: str, slc: str) -> None:
# blow away all the tweaks that this setup does to Setup.local and
# instead apply our very similar ones directly to Setup, just as we
# do for android.
- with open('Modules/Setup.local', 'w', encoding='utf-8') as outfile:
- outfile.write('# cleared by efrotools build\n')
+ # with open('Modules/Setup.local', 'w', encoding='utf-8') as outfile:
+ # outfile.write('# cleared by efrotools build\n')
_patch_setup_file('apple', arch, slc)
_patch_py_ssl()
@@ -292,6 +299,10 @@ def android_patch_ssl() -> None:
def _patch_py_ssl() -> None:
+ # UPDATE: this is now included in Python as of 3.10.6; woohoo!
+ if bool(True):
+ return
+
# I've tracked down an issue where Python's SSL module
# can spend lots of time in SSL_CTX_set_default_verify_paths()
# while holding the GIL, which hitches the game like crazy.