diff --git a/.efrocachemap b/.efrocachemap index 8c71bc14..474ca50f 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,11 +421,11 @@ "build/assets/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/74/be/fe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/48/ab/8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/2b/46/8aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/a1/f9/645b8c7e1e99dd11446bc77005da", - "build/assets/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/83/87/06fc7255ebf8a895ad37d2dfa13d", + "build/assets/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/b8/5e/c8766634397fb77ae3a407c05d63", + "build/assets/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/6f/38/958616d8cb85916aa8b2bcd84f63", "build/assets/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/57/68/d03a19b9035cfae7cdc5377d889a", "build/assets/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/b6/00/924583b899165757f412eef0dd01", - "build/assets/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/50/4c/4fb39f065b1a2f0320026a2e1b92", + "build/assets/ba_data/data/languages/chinesetraditional.json": "https://files.ballistica.net/cache/ba1/3f/e9/60a8f0ca529aa57b4f9cb7385abc", "build/assets/ba_data/data/languages/croatian.json": "https://files.ballistica.net/cache/ba1/76/65/32c67af5bd0144c2d63cab0516fa", "build/assets/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/f3/ce/219840946cb8f9aa6d3e25927ab3", "build/assets/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/d6/9080783d5c9dcc0af737f02b6f1e", @@ -438,7 +438,7 @@ "build/assets/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/23/6f/8547ba09722b7c7f5b8333986984", "build/assets/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/a6/5d/78f912e9a89f98de004405167a6a", "build/assets/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/88/ee/0cda537bab9ac827def5e236fe1a", - "build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/00/ba/cf1b8bb9f7914f64647d4665b0a8", + "build/assets/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/a9/b5/10de2f3928d8c1f4887e0975743f", "build/assets/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/58/3b/ae1ecc04375cee089a82359110b7", "build/assets/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/67/44/40ada7b8e76adceb2129d7668df6", "build/assets/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/bd/c1/3f8632adda5517059323d928f192", @@ -450,13 +450,13 @@ "build/assets/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/aa/99/f9f597787fe4e09c8ab53fe2e081", "build/assets/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/d7/45/2dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/27/96/2d53dc3f7dd4e877cd40faafeeef", - "build/assets/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/bd/5e/80c74f96bb50d270396d437d6750", + "build/assets/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/45/dd/ce6d9dd446293f5e0ae541f36943", "build/assets/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/77/d6/71f10613291ebf9c71da66f18a18", "build/assets/ba_data/data/languages/tamil.json": "https://files.ballistica.net/cache/ba1/c7/fc/5ed7bd686839ec1a867763248cf9", "build/assets/ba_data/data/languages/thai.json": "https://files.ballistica.net/cache/ba1/33/f6/3753c9af9a5b238d229a0bf23fbc", "build/assets/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/0a/97/f1f948f6587ea7d40b639aba67ce", "build/assets/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/97/4a/399422e3061fdd82f66591283397", - "build/assets/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/e4/74/5c85bc56487bb715712c8b90a1eb", + "build/assets/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/b0/e3/d73ccf96c5fa490a54f090ee77a5", "build/assets/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/92/1c/d1e50f60fe3e101f246e172750ba", "build/assets/ba_data/data/maps/big_g.json": "https://files.ballistica.net/cache/ba1/1d/d3/01d490643088a435ce75df971054", "build/assets/ba_data/data/maps/bridgit.json": "https://files.ballistica.net/cache/ba1/6a/ea/74805f4880cc11237c5734a24422", @@ -951,7 +951,7 @@ "build/assets/ba_data/python-site-packages/certifi/cacert.pem": "https://files.ballistica.net/cache/ba1/6a/c2/9a6bccca11cd2ed7e16e27dfccec", "build/assets/ba_data/python-site-packages/certifi/core.py": "https://files.ballistica.net/cache/ba1/1b/50/5388f1475fabd1b60031f985271c", "build/assets/ba_data/python-site-packages/typing_extensions.py": "https://files.ballistica.net/cache/ba1/08/4d/93bb609d798a3930dfb5e25eba59", - "build/assets/ba_data/python-site-packages/yaml/__init__.py": "https://files.ballistica.net/cache/ba1/55/7c/37ea8dbd4fa4d6dac97f399b6fdd", + "build/assets/ba_data/python-site-packages/yaml/__init__.py": "https://files.ballistica.net/cache/ba1/2b/74/7e5772c203377222afc888ac6b71", "build/assets/ba_data/python-site-packages/yaml/composer.py": "https://files.ballistica.net/cache/ba1/ce/f8/71e1f5f99ba2a7c44941b70afb06", "build/assets/ba_data/python-site-packages/yaml/constructor.py": "https://files.ballistica.net/cache/ba1/8a/15/e361e34b79491c81553bb3534062", "build/assets/ba_data/python-site-packages/yaml/cyaml.py": "https://files.ballistica.net/cache/ba1/9b/11/cba12e6f1cf2efe1725a20d7e1e5", @@ -4068,50 +4068,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/a8/5e/e644cd4120304fba4d4dbd9762e2", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/92/73/0a1325df721b51d0c9f2e0f6f075", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/24/f8/b372c8e02345a5540bd976069de8", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/81/5d/8368c53554a9b997f0a1d38c0ced", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/cb/90/6f74ab7d83e5ff9db887f391664a", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/cd/51/6639bbf611e918b21c1676b25764", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d1/4b/5f2cb963955fd973bd7498c57519", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/9b/9f/1e1d79b604749c2033e7fad06394", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/97/f6/2f0e7dba9be2437f43dc4af5e449", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2e/22/297bedb0cbfb169fb9ba607bff1b", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c6/3e/6b09e07290a01483186b2ca44f8b", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/9e/c9/89f8f396ba8597453d81542f6ff6", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/0b/d8/4b6df9ec36d59a5560615c763bbf", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2d/5e/f6e1619a73c21c886425d878b96a", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b6/4b/c7e9a14a16d69b20313f7007662c", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2c/00/fe4d13eb53c0d825da2750b30cec", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/69/19/13f59849fe2a31eac8a350db2e12", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/01/ab/f243ffaa211fd9463cc7ede10f29", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/87/d3/88a28894dc15e511f3ba56f44267", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/b5/1e/bb7075104c420ca1c0663d24768c", - "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382", - "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/25/2f/3bd787d6debb2c4073fd6c2e8098", - "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/aa/534f35b6499762739646ea173382", - "build/prefab/lib/linux_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/25/2f/3bd787d6debb2c4073fd6c2e8098", - "build/prefab/lib/linux_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/b8/01/153180116f1ab302aa8e6fd9ca9c", - "build/prefab/lib/linux_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/93/1c/1aba110dcf69d8651b428f2927ed", - "build/prefab/lib/linux_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/b8/01/153180116f1ab302aa8e6fd9ca9c", - "build/prefab/lib/linux_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/93/1c/1aba110dcf69d8651b428f2927ed", - "build/prefab/lib/mac_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4b/1a/21983185f7bdd78842b572535dad", - "build/prefab/lib/mac_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3c/1d/d7864d7822c64ee06cee0dde659e", - "build/prefab/lib/mac_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4b/1a/21983185f7bdd78842b572535dad", - "build/prefab/lib/mac_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3c/1d/d7864d7822c64ee06cee0dde659e", - "build/prefab/lib/mac_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/65/ab/524fe2f6a339b6480173c2c1624a", - "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d", - "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/06/5c/90c3a49e16a004e2db71909af919", - "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/47/61/eca0961c54b2eae2cf65fac7848d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/d3/34/d2fa72d15a085424bad4157a6f2e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/56/5f/6cde7712eebd76bcd9081b1d063a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/8e/bb/cde5d48031a147358f49372348fc", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/82/e5/7d8d72481b84b81a3ec2b85cddf1", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/74/11/5059d262beb03fda192c967760ea", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/bf/57/94af76a5f7f51c10e9725730469e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/fc/56/19374bffec117190ae9c132cff68", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/c6/c9/b6828fe5295e6d5df08fad9ebf3f", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/31/48/05c88daed3cf3e90f5a051ec6a0c", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/72/f9/d85a330987d81ffc895fc8ec922c", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1a/92/6d465e12a3460498d978f31abf15", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0c/0e/1b219bc8d0b6d7ccf57bdd721860", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/4a/7f/1068c0311d88aa8145e487f0e30b", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/1d/0d/fd0d55be1b0e69de4c4667d34644", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3f/d0/cec6952032a5ddc8c1a63b1e1ba3", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/a7/6e/b40ca2ec7dde8c0e9edb0db376c1", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/fe/88/123692ae9ffbc97aaddbd51c0f30", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/16/31/b64154143f4e903388b9013b41c7", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c0/f9/aea06ce01cffb7931aca46285316", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d0/a0/a9a8c9fe3addcc0a9f7a014acef1", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/66/e3/d827e9edb1363d55a85636fc5f58", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/b4/05/e5a12646d900aeeaa7e974dabda2", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/94/16/24b8715c0f5c2eb6699ff20e594d", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/83/6d/d914432abe2d071890a6b3e2d6f1", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/10/b7/04b0835c36649b8bcd70abf2f3d7", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/0a/13/807d5e92e146b5e9bfc679dbd44b", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/3f/1e/2970e115041bc1828b3b2b8ef4db", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/9f/28/bf466437892861a399375c864a0d", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/4e/69/3d3715ffb88e61962dff80e52fa0", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/df/78/f138dbf92a93dcd647831fb8fde4", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/4e/69/3d3715ffb88e61962dff80e52fa0", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/df/78/f138dbf92a93dcd647831fb8fde4", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/4d/45/84cd8d36933f680c4c5ea6ed56e3", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/31/e8/ebc78517b4f6c3dba799d96b6770", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/4d/45/84cd8d36933f680c4c5ea6ed56e3", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/31/e8/ebc78517b4f6c3dba799d96b6770", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/16/31/fa50eca4cccba5819aba7598cdd2", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/80/f5/1e75ca051bcc9cf5622443368820", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/16/31/fa50eca4cccba5819aba7598cdd2", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/80/f5/1e75ca051bcc9cf5622443368820", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/34/26/fe4dacd23b76a39c024e220a6851", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/84/7d/952ba7e47c98635853b6b3e046fa", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/ad/de/141e3f5ea646f9d359a7edc40524", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "https://files.ballistica.net/cache/ba1/84/7d/952ba7e47c98635853b6b3e046fa", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/0d/e3/9a30e693bc57d27a093019988e2d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b3/df/933a84818c7a7f7059c5d2aef159", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/76/46/7e4792528fdc5ee7b432b8239567", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/03/54/742e4cae6ce81180e6977ccf3cd1", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/35/1b/cd6f88e5a7dda82848362d3ee41e", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/8f/b4/32805c7732aa03bc4417aed0f61b", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/7a/10/3c2abaa7fa0280fe5111209a9fd5", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/60/88/4d71bc815388e9d867d8ffa95c61", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/f8/85/fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/f8/cd/3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/3e/7a/203e2a5d2b5bb42cfe3fd2fe16c2", @@ -4119,7 +4119,7 @@ "src/ballistica/classic/mgen/pyembed/binding_classic.inc": "https://files.ballistica.net/cache/ba1/3c/eb/412513963f0818ab39c58bf292e3", "src/ballistica/core/mgen/pyembed/binding_core.inc": "https://files.ballistica.net/cache/ba1/9d/0a/3c9636138e35284923e0c8311c69", "src/ballistica/core/mgen/pyembed/env.inc": "https://files.ballistica.net/cache/ba1/8b/e4/6e5818f360d10b7b0224a9e91d07", - "src/ballistica/core/mgen/python_modules_monolithic.h": "https://files.ballistica.net/cache/ba1/c4/8c/4f5294e83eb1ff22edffebf2bf8b", + "src/ballistica/core/mgen/python_modules_monolithic.h": "https://files.ballistica.net/cache/ba1/fb/96/7ed1c7db0c77d8deb4f00a7103c5", "src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "https://files.ballistica.net/cache/ba1/d8/0f/970053099b3044204bfe29ddefce", "src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "https://files.ballistica.net/cache/ba1/44/a4/5492db057bf7f7158c3b0fa11f0f", "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "https://files.ballistica.net/cache/ba1/6e/98/2bd0dda68e8b821f5b5cc18ce1d5" diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index ddecd372..9c646237 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -233,6 +233,8 @@ ballistica's ballisticakit ballisticakitcb + ballisticakitso + ballisticaplus bamaster bamasteraddr bamasterlegacy @@ -318,6 +320,7 @@ bmas bmasl bmcjphh + bmodfeaturesets bname bndl boffs @@ -522,6 +525,7 @@ clrred cmakelist cmakelists + cmakemodular cmakeserver cmath cmathmodule @@ -729,6 +733,7 @@ dingsound dingsoundhigh dinl + dir's dirfilter dirmanifest dirname @@ -832,6 +837,7 @@ dusing dval dxml + dylibdir dynload eachother eaddrnotavail @@ -894,6 +900,7 @@ enumvalue enval envcfg + envglobals envhash envname envs @@ -930,6 +937,7 @@ excludetypes excstr exec'ed + exec'ing execcode execed execing @@ -982,6 +990,7 @@ fdata fdataraw fdcount + fdcwd fdesc fdict fdirs @@ -1103,6 +1112,8 @@ fsconfigpath fsdf fset + fsetmfilenames + fsetmfilenamevals fsetname fsets fsettings @@ -1130,6 +1141,7 @@ fullpath fullprice fullscreen + fullstr fulltest funcname funcnames @@ -1403,6 +1415,7 @@ initializers initialplayerinfos initing + initname inits inittab inmobi @@ -1419,6 +1432,7 @@ insta installdir instancer + instpath interfacetype internalmodule internalsrc @@ -1551,6 +1565,7 @@ lfval libballistica libballisticakit + libballisticaplus libbz libbzip libcrypto @@ -1721,6 +1736,7 @@ maxw maxwait maxwidth + mbstowcs mbytecount mdiv mdocs @@ -1741,6 +1757,7 @@ metaprogramming metascan meteorshower + mfilename mfpath mhash mhbegin @@ -1984,6 +2001,7 @@ olde oldlady oldpath + oldpaths oldtoken olduuid olduuids @@ -2003,7 +2021,9 @@ opposingbody opposingnode opstr + optnm optparse + optstuff orchestrahitsound origwrapper ortho @@ -2019,6 +2039,7 @@ ourhash ourname ourpackage + ourpaths ourself outdata outdelay @@ -2065,6 +2086,7 @@ pathbar pathcapture pathdst + pathlen pathlib pathlist pathnames @@ -2357,8 +2379,10 @@ pythondontwritebytecode pythonenumsmodule pythonhashseed + pythonoptimize pythonpath pythonpaths + pythonutf pythonw pytree pytz @@ -2390,6 +2414,8 @@ readexactly readline readlines + readlink + readlinkat realpath realsies recache @@ -2708,10 +2734,12 @@ sred sriyakaal sshd + ssize sslcontext sslproto ssval stackstr + stager standin starscale startercache @@ -2820,6 +2848,7 @@ syncitem syncitems synclist + syscall sysconfigdata sysctl syslogmodule @@ -3186,6 +3215,7 @@ winapi winbeast wincfg + wincfglc wincount winempty winnergroup diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9f29b6..28726bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,38 @@ -### 1.7.23 (build 21171, api 8, 2023-07-16) +### 1.7.24 (build 21183, api 8, 2023-07-21) + +- Due to the cleanup done in 1.7.20, it is now possible to build and run + Ballistica as a 'pure' Python app consisting of binary Python modules loaded + by a standard Python interpreter. This new build style is referred to as + 'modular'. The traditional form of the app, where we bootstrap Python + ourselves inside a standalone binary, is called 'monolithic'. To build and run + Ballistica in modular form, you can do `make cmake-modular`. This should make + it easier to use certain things like Python debuggers with Ballistica. While I + expect most builds of the game to remain monolithic, this may become the + default for certain situations such as server builds or possibly Linux builds + if it seems beneficial. We'll see. Modular mode should work on Linux and Mac + currently; other platforms remain monolithic-only for now. +- Changed path wrangling a bit in baenv.py. All ballistica Python paths + (including python-site-packages) are now placed before any other existing + Python paths. This should provide a more consistent environment and means + Ballistica will always use its own version of things like yaml or certifi or + typing_extensions instead of one the user has installed via pip. Holler if you + run into any problems because of this and we can make an option to use the old + behavior where Ballistica's app and site paths get placed at the end. +- It is now possible to manually run the app loop even on monolithic builds; + just do `PYTHONPATH=ba_data/python ./ballisticacore -c "import baenv; + baenv.configure(); import babase; babase.app.run()"`. This is basically the + same thing modular builds are doing except that they use a regular Python + interpreter instead of the ballisticakit binary. +- Cleaned up the `tools/pcommand stage_assets` command. It now always expects a + separate `-debug` or `-release` arg. So old commands such as `tools/pcommand + stage_assets -win-Win32-Debug .` now look like `tools/pcommand stage_assets + -win-Win32 -debug .`. Please holler if you run into any broken asset-staging + calls in the Makefile/etc. +- `FeatureSet.has_native_python_module` has been renamed to + `FeatureSet.has_python_binary_module` for better consistency with related + functionality. + +### 1.7.23 (build 21178, api 8, 2023-07-19) - Network security improvements. (Thanks Dliwk!) - You can now double click a chat message to copy it. (Thanks Vishal332008!) diff --git a/Makefile b/Makefile index 5427675b..4b78c784 100644 --- a/Makefile +++ b/Makefile @@ -243,16 +243,16 @@ prefab-mac-arm64-gui-debug: prefab-mac-arm64-gui-debug-build prefab-mac-x86-64-gui-debug-build: prereqs assets-cmake \ build/prefab/full/mac_x86_64_gui/debug/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/mac_x86_64_gui/debug + @$(STAGE_ASSETS) -cmake -debug build/prefab/full/mac_x86_64_gui/debug prefab-mac-arm64-gui-debug-build: prereqs assets-cmake \ build/prefab/full/mac_arm64_gui/debug/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/mac_arm64_gui/debug + @$(STAGE_ASSETS) -cmake -debug build/prefab/full/mac_arm64_gui/debug build/prefab/full/mac_%_gui/debug/ballisticakit: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/mac_%_gui/debug/libballistica_plus.a: .efrocachemap +build/prefab/lib/mac_%_gui/debug/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Mac gui release: @@ -273,16 +273,16 @@ prefab-mac-arm64-gui-release: prefab-mac-arm64-gui_release-build prefab-mac-x86-64-gui-release-build: prereqs assets-cmake \ build/prefab/full/mac_x86_64_gui/release/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/mac_x86_64_gui/release + @$(STAGE_ASSETS) -cmake -release build/prefab/full/mac_x86_64_gui/release prefab-mac-arm64-gui-release-build: prereqs assets-cmake \ build/prefab/full/mac_arm64_gui/release/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/mac_arm64_gui/release + @$(STAGE_ASSETS) -cmake -release build/prefab/full/mac_arm64_gui/release build/prefab/full/mac_%_gui/release/ballisticakit: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/mac_%_gui/release/libballistica_plus.a: .efrocachemap +build/prefab/lib/mac_%_gui/release/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Mac server debug: @@ -312,7 +312,7 @@ prefab-mac-arm64-server-debug-build: prereqs assets-server \ build/prefab/full/mac_%_server/debug/dist/ballisticakit_headless: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/mac_%_server/debug/libballistica_plus.a: .efrocachemap +build/prefab/lib/mac_%_server/debug/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Mac server release: @@ -344,7 +344,7 @@ prefab-mac-arm64-server-release-build: prereqs assets-server \ build/prefab/full/mac_%_server/release/dist/ballisticakit_headless: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/mac_%_server/release/libballistica_plus.a: .efrocachemap +build/prefab/lib/mac_%_server/release/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Linux gui debug: @@ -365,16 +365,16 @@ prefab-linux-arm64-gui-debug: prefab-linux-arm64-gui-debug-build prefab-linux-x86-64-gui-debug-build: prereqs assets-cmake \ build/prefab/full/linux_x86_64_gui/debug/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/linux_x86_64_gui/debug + @$(STAGE_ASSETS) -cmake -debug build/prefab/full/linux_x86_64_gui/debug prefab-linux-arm64-gui-debug-build: prereqs assets-cmake \ build/prefab/full/linux_arm64_gui/debug/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/linux_arm64_gui/debug + @$(STAGE_ASSETS) -cmake -debug build/prefab/full/linux_arm64_gui/debug build/prefab/full/linux_%_gui/debug/ballisticakit: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/linux_%_gui/debug/libballistica_plus.a: .efrocachemap +build/prefab/lib/linux_%_gui/debug/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Linux gui release: @@ -395,16 +395,16 @@ prefab-linux-arm64-gui-release: prefab-linux-arm64-gui-release-build prefab-linux-x86-64-gui-release-build: prereqs assets-cmake \ build/prefab/full/linux_x86_64_gui/release/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/linux_x86_64_gui/release + @$(STAGE_ASSETS) -cmake -release build/prefab/full/linux_x86_64_gui/release prefab-linux-arm64-gui-release-build: prereqs assets-cmake \ build/prefab/full/linux_arm64_gui/release/ballisticakit - @$(STAGE_ASSETS) -cmake build/prefab/full/linux_arm64_gui/release + @$(STAGE_ASSETS) -cmake -release build/prefab/full/linux_arm64_gui/release build/prefab/full/linux_%_gui/release/ballisticakit: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/linux_%_gui/release/libballistica_plus.a: .efrocachemap +build/prefab/lib/linux_%_gui/release/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Linux server debug: @@ -436,7 +436,7 @@ prefab-linux-arm64-server-debug-build: prereqs assets-server \ build/prefab/full/linux_%_server/debug/dist/ballisticakit_headless: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/linux_%_server/debug/libballistica_plus.a: .efrocachemap +build/prefab/lib/linux_%_server/debug/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Linux server release: @@ -468,7 +468,7 @@ prefab-linux-arm64-server-release-build: prereqs assets-server \ build/prefab/full/linux_%_server/release/dist/ballisticakit_headless: .efrocachemap @tools/pcommand efrocache_get $@ -build/prefab/lib/linux_%_server/release/libballistica_plus.a: .efrocachemap +build/prefab/lib/linux_%_server/release/libballisticaplus.a: .efrocachemap @tools/pcommand efrocache_get $@ # Windows gui debug: @@ -482,7 +482,7 @@ prefab-windows-x86-gui-debug: prefab-windows-x86-gui-debug-build prefab-windows-x86-gui-debug-build: prereqs assets-windows-$(WINPLAT_X86) \ build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe - @$(STAGE_ASSETS) -win-$(WINPLAT_X86)-Debug \ + @$(STAGE_ASSETS) -win-$(WINPLAT_X86) -debug \ build/prefab/full/windows_x86_gui/debug build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe: .efrocachemap @@ -506,7 +506,7 @@ prefab-windows-x86-gui-release: prefab-windows-x86-gui-release-build prefab-windows-x86-gui-release-build: prereqs \ assets-windows-$(WINPLAT_X86) \ build/prefab/full/windows_x86_gui/release/BallisticaKit.exe - @$(STAGE_ASSETS) -win-$(WINPLAT_X86)-Release \ + @$(STAGE_ASSETS) -win-$(WINPLAT_X86) -release \ build/prefab/full/windows_x86_gui/release build/prefab/full/windows_x86_gui/release/BallisticaKit.exe: .efrocachemap @@ -531,7 +531,7 @@ prefab-windows-x86-server-debug: prefab-windows-x86-server-debug-build prefab-windows-x86-server-debug-build: prereqs \ assets-windows-$(WINPLAT_X86) \ build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe - @$(STAGE_ASSETS) -winserver-$(WINPLAT_X86)-Debug \ + @$(STAGE_ASSETS) -winserver-$(WINPLAT_X86) -debug \ build/prefab/full/windows_x86_server/debug build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe: .efrocachemap @@ -556,7 +556,7 @@ prefab-windows-x86-server-release: prefab-windows-x86-server-release-build prefab-windows-x86-server-release-build: prereqs \ assets-windows-$(WINPLAT_X86) \ build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe - @$(STAGE_ASSETS) -winserver-$(WINPLAT_X86)-Release \ + @$(STAGE_ASSETS) -winserver-$(WINPLAT_X86) -release \ build/prefab/full/windows_x86_server/release build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe: .efrocachemap @@ -931,7 +931,7 @@ WINDOWS_CONFIGURATION ?= Debug # Stage assets and other files so a built binary will run. windows-staging: assets-windows resources meta - $(STAGE_ASSETS) -win-$(WINPLT)-$(WINCFG) \ + $(STAGE_ASSETS) -win-$(WINPLT) -$(WINCFGLC) \ build/windows/$(WINCFG)_$(WINPLT) # Build and run a debug windows build (from WSL). @@ -1026,7 +1026,7 @@ cmake-lldb: cmake-build # Build but don't run it. cmake-build: assets-cmake resources cmake-binary - @$(STAGE_ASSETS) -cmake build/cmake/$(CM_BT_LC) + @$(STAGE_ASSETS) -cmake -$(CM_BT_LC) build/cmake/$(CM_BT_LC) @tools/pcommand echo BLD Build complete: BLU build/cmake/$(CM_BT_LC) cmake-binary: meta @@ -1046,25 +1046,43 @@ cmake-server: cmake-server-build cmake-server-build: assets-server meta cmake-server-binary @$(STAGE_ASSETS) -cmakeserver -$(CM_BT_LC) build/cmake/server-$(CM_BT_LC) @tools/pcommand echo BLD \ - Server build complete: BLU build/cmake/server-$(CM_BT_LC) + Server build complete: BLU build/cmake/server-$(CM_BT_LC) -# Build just the headless binary. -# Note: We currently symlink FOO_headless. In packaged builds we rename it. cmake-server-binary: meta @tools/pcommand cmake_prep_dir build/cmake/server-$(CM_BT_LC)/dist @cd build/cmake/server-$(CM_BT_LC)/dist && test -f Makefile \ || cmake -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) -DHEADLESS=true \ $(shell pwd)/ballisticakit-cmake @tools/pcommand update_cmake_prefab_lib server $(CM_BT_LC) build/cmake/server-$(CM_BT_LC)/dist - @cd build/cmake/server-$(CM_BT_LC)/dist && $(MAKE) -j$(CPUS) + @cd build/cmake/server-$(CM_BT_LC)/dist && $(MAKE) -j$(CPUS) \ + ballisticakit cmake-server-clean: rm -rf build/cmake/server-$(CM_BT_LC) +cmake-modular-build: assets-cmake meta cmake-modular-binary + @$(STAGE_ASSETS) -cmakemodular -$(CM_BT_LC) \ + build/cmake/modular-$(CM_BT_LC)/staged + @tools/pcommand echo BLD \ + Modular build complete: BLU build/cmake/modular-$(CM_BT_LC)/staged + +cmake-modular: cmake-modular-build + @cd build/cmake/modular-$(CM_BT_LC)/staged && ./ballisticakit + +cmake-modular-binary: meta + @tools/pcommand cmake_prep_dir build/cmake/modular-$(CM_BT_LC) + @cd build/cmake/modular-$(CM_BT_LC) && test -f Makefile \ + || cmake -DCMAKE_BUILD_TYPE=$(CMAKE_BUILD_TYPE) \ + $(shell pwd)/ballisticakit-cmake + @cd build/cmake/modular-$(CM_BT_LC) && $(MAKE) -j$(CPUS) ballisticakitso + +cmake-modular-clean: + rm -rf build/cmake/modular-$(CM_BT_LC) + # Stage assets for building/running within CLion. clion-staging: assets-cmake resources meta - $(STAGE_ASSETS) -cmake build/clion_debug - $(STAGE_ASSETS) -cmake build/clion_release + $(STAGE_ASSETS) -cmake -debug build/clion_debug + $(STAGE_ASSETS) -cmake -release build/clion_release # Tell make which of these targets don't represent files. .PHONY: cmake cmake-build cmake-clean cmake-server cmake-server-build \ @@ -1162,6 +1180,7 @@ WIN_MSBUILD_EXE_B = "$(_WMSBE_1B)$(_WMSBE_2B)" WINPRJ = $(WINDOWS_PROJECT) WINPLT = $(WINDOWS_PLATFORM) WINCFG = $(WINDOWS_CONFIGURATION) +WINCFGLC = $(shell echo $(WINDOWS_CONFIGURATION) | tr A-Z a-z) # When using CLion, our cmake dir is root. Expose .clang-format there too. ballisticakit-cmake/.clang-format: .clang-format diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index 2ff36119..1d8e0e06 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -137,6 +137,8 @@ ballistica ballistica's ballisticakit + ballisticakitso + ballisticaplus bamasteraddr bamasterlegacy bameta @@ -206,6 +208,7 @@ bmas bmasl bmcjphh + bmodfeaturesets bname bodyid bodypart @@ -324,6 +327,7 @@ clipcount cloudtoba cmakelist + cmakemodular cmath cmds cmdspath @@ -454,6 +458,7 @@ diesound diffbit dinl + dir's dirfilter dirslash displaytime @@ -507,6 +512,7 @@ dusing dval dxgi + dylibdir dynamicdata echidna echofile @@ -539,6 +545,7 @@ enumvalue enval envcfg + envglobals envs envval ericf @@ -558,6 +565,7 @@ exargs exctype exec'ed + exec'ing execed execinfo execing @@ -592,6 +600,7 @@ fdata fdataraw fdcount + fdcwd fdirs fdirx fdiry @@ -660,6 +669,8 @@ fsarg fsconfigpath fset + fsetmfilenames + fsetmfilenamevals fsetname fsets fsmetapackagename @@ -673,6 +684,7 @@ ftos ftou fullpath + fullstr funcname funcp fval @@ -826,10 +838,12 @@ inides initguid initing + initname inittab inputdevice inputter insta + instpath intercollide internalformat internalmodule @@ -914,6 +928,7 @@ lgui lhalf libballistica + libballisticaplus libbz libbzip libfile @@ -998,6 +1013,7 @@ maxtries maxwait maxwidth + mbstowcs mdpath mediump memalign @@ -1014,6 +1030,7 @@ metallink metamakefile meth + mfilename mhbegin mhend microsecs @@ -1164,6 +1181,7 @@ olde oldname oldpath + oldpaths oldtoken olduuid olduuids @@ -1192,6 +1210,8 @@ opposingbody opposingnode optin + optnm + optstuff ortho osis ossaudiodev @@ -1201,6 +1221,7 @@ ourcode ourname ourpackage + ourpaths ourself ourstanding outdict @@ -1223,6 +1244,7 @@ pathbar pathcapture pathdst + pathlen pathlist pathparts pathsegs @@ -1362,6 +1384,8 @@ pysitedir pythondevmode pythonenumsmodule + pythonoptimize + pythonutf pytype qerr qrcode @@ -1386,6 +1410,8 @@ rdynamic reaaaly readexactly + readlink + readlinkat readset realloc reallocations @@ -1579,6 +1605,7 @@ sssssssi ssssssssssss ssval + stager standin startedptr startms @@ -1635,6 +1662,7 @@ swiftgeneratepch swiftmergegeneratedheaders symbolification + syscall syscalls sysresponse tabdefs @@ -1839,6 +1867,7 @@ wiimote wiimotes winapi + wincfglc windowshade winmm winsock diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt index f5763dfe..8d79abde 100644 --- a/ballisticakit-cmake/CMakeLists.txt +++ b/ballisticakit-cmake/CMakeLists.txt @@ -177,6 +177,12 @@ add_library(ode ) target_include_directories(ode PRIVATE ${ODE_SRC_ROOT}) +# Necessary on GCC to allow linking in to our .so version. +# (but disabling semantic-interposition should lessen the associated speed hit). +if (CMAKE_CXX_COMPILER_ID MATCHES GNU) + target_compile_options(ode PRIVATE -fPIC -fno-semantic-interposition) +endif () + # NOTE: There used to be an issue with optimized GCC builds where mesh # collisions would fail randomly, leading to characters falling through # floors somewhat regularly. For this reason I was limiting optimization to @@ -194,8 +200,7 @@ target_include_directories(ode PRIVATE ${ODE_SRC_ROOT}) # endif() # endif () -# BallisticaKit binary. -add_executable(ballisticakit +set(BALLISTICA_SOURCES ${BA_SRC_ROOT}/external/qr_code_generator/QrCode.cpp # AUTOGENERATED_PUBLIC_BEGIN (this section is managed by the "update_project" tool) ${BA_SRC_ROOT}/ballistica/base/app/app.cc @@ -742,28 +747,65 @@ add_executable(ballisticakit ${BA_SRC_ROOT}/ballistica/ui_v1/widget/widget.cc ${BA_SRC_ROOT}/ballistica/ui_v1/widget/widget.h # AUTOGENERATED_PUBLIC_END - ) +) + +# BallisticaKit monolithic binary. +add_executable(ballisticakit ${BALLISTICA_SOURCES}) # Gets -rdynamic added when linking gcc builds which exports all symbols # which gives us more meaningful stack traces using backtrace_symbols(). set_target_properties(ballisticakit PROPERTIES ENABLE_EXPORTS 1) if (HEADLESS) - set_target_properties(ballisticakit PROPERTIES OUTPUT_NAME "ballisticakit_headless") + set_target_properties(ballisticakit + PROPERTIES OUTPUT_NAME "ballisticakit_headless") endif () target_include_directories(ballisticakit PRIVATE ${Python_INCLUDE_DIRS} ${BA_SRC_ROOT}/external/open_dynamics_engine-ef - ${EXTRA_INCLUDE_DIRS} - ) + ${EXTRA_INCLUDE_DIRS}) target_link_libraries(ballisticakit PRIVATE - ${CMAKE_CURRENT_BINARY_DIR}/prefablib/libballistica_plus.a ode pthread ${Python_LIBRARIES} + ${CMAKE_CURRENT_BINARY_DIR}/prefablib/libballisticaplus.a ode pthread ${Python_LIBRARIES} ${SDL2_LIBRARIES} ${EXTRA_LIBRARIES} dl) -# Hack for building on rpi (might be due to my manually built Python 3.8) -# Hopefully can remove later... +# Hack for building on rpi; need to update my pi so I can remove this. if(EXISTS "/home/pi") target_link_libraries(ballisticakit PRIVATE dl util stdc++fs) endif() + + +# BallisticaKit modular shared library +# (for use with vanilla Python interpreters). +add_library(ballisticakitso SHARED ${BALLISTICA_SOURCES}) + +# This is a 'modular' build. +target_compile_definitions(ballisticakitso PRIVATE BA_MONOLITHIC_BUILD=0) + +# Gets -rdynamic added when linking gcc builds which exports all symbols +# which gives us more meaningful stack traces using backtrace_symbols(). +set_target_properties(ballisticakitso PROPERTIES ENABLE_EXPORTS 1) + +set_target_properties(ballisticakitso + PROPERTIES PREFIX "") +set_target_properties(ballisticakitso + PROPERTIES SUFFIX ".so") + +if (HEADLESS) + set_target_properties(ballisticakitso + PROPERTIES OUTPUT_NAME "ballisticakit_headless") +else () + set_target_properties(ballisticakitso + PROPERTIES OUTPUT_NAME "ballisticakit") +endif () + +target_include_directories(ballisticakitso PRIVATE + ${Python_INCLUDE_DIRS} + ${BA_SRC_ROOT}/external/open_dynamics_engine-ef + ${EXTRA_INCLUDE_DIRS}) + +target_link_libraries(ballisticakitso PRIVATE + ${CMAKE_CURRENT_BINARY_DIR}/prefablib/libballisticaplus.a ode pthread ${Python_LIBRARIES} + ${SDL2_LIBRARIES} ${EXTRA_LIBRARIES} dl) + diff --git a/config/featuresets/featureset_core.py b/config/featuresets/featureset_core.py index 79658f97..f83ad5c7 100644 --- a/config/featuresets/featureset_core.py +++ b/config/featuresets/featureset_core.py @@ -14,7 +14,7 @@ fset = FeatureSet.get_active() fset.requirements = set() -fset.has_native_python_module = False +fset.has_python_binary_module = False # Bits of code we're using that don't conform to our feature-set based # namespace scheme. diff --git a/config/featuresets/featureset_scene_v1_lib.py b/config/featuresets/featureset_scene_v1_lib.py index f74d682c..9d79d77b 100644 --- a/config/featuresets/featureset_scene_v1_lib.py +++ b/config/featuresets/featureset_scene_v1_lib.py @@ -12,6 +12,6 @@ from batools.featureset import FeatureSet # Grab the FeatureSet we should apply to. fset = FeatureSet.get_active() -fset.has_native_python_module = False +fset.has_python_binary_module = False fset.requirements = {'core', 'base', 'scene_v1'} diff --git a/config/featuresets/featureset_ui_v1_lib.py b/config/featuresets/featureset_ui_v1_lib.py index 3f4f4eb9..16112371 100644 --- a/config/featuresets/featureset_ui_v1_lib.py +++ b/config/featuresets/featureset_ui_v1_lib.py @@ -12,6 +12,6 @@ from batools.featureset import FeatureSet # Grab the FeatureSet we should apply to. fset = FeatureSet.get_active() -fset.has_native_python_module = False +fset.has_python_binary_module = False fset.requirements = {'core', 'base', 'ui_v1', 'classic'} diff --git a/src/assets/ba_data/python/babase/_env.py b/src/assets/ba_data/python/babase/_env.py index a651dc88..9fabca2c 100644 --- a/src/assets/ba_data/python/babase/_env.py +++ b/src/assets/ba_data/python/babase/_env.py @@ -54,8 +54,10 @@ def on_native_module_import() -> None: # make noise if that's not the case. if debug_build != sys.flags.dev_mode: logging.warning( - 'Mismatch in ballistica debug_build %s' - ' and sys.flags.dev_mode %s; this may cause problems.', + 'Ballistica was built with debug-mode %s' + ' but Python is running with dev-mode %s;' + ' this mismatch may cause problems.' + ' See https://docs.python.org/3/library/devmode.html', debug_build, sys.flags.dev_mode, ) @@ -75,11 +77,11 @@ def setup_env_for_app_run() -> None: assert baenv.config_exists() # If we were unable to set paths earlier, complain now. - if baenv.g_paths_set_failed: + if baenv.did_paths_set_fail(): logging.warning( 'Ballistica Python paths have not been set. This may cause' ' problems. To ensure paths are set, run baenv.configure()' - ' before importing any ballistica modules.' + ' BEFORE importing any Ballistica modules.' ) # Set up interrupt-signal handling. @@ -144,9 +146,10 @@ def on_app_launching() -> None: assert _babase.in_logic_thread() # Let the user know if the app python dir is a custom one. - if baenv.g_user_system_scripts_dir is not None: + user_sys_scripts_dir = baenv.get_user_system_scripts_dir() + if user_sys_scripts_dir is not None: _babase.screenmessage( - f"Using user system scripts: '{baenv.g_user_system_scripts_dir}'", + f"Using user system scripts: '{user_sys_scripts_dir}'", color=(0.6, 0.6, 1.0), ) diff --git a/src/assets/ba_data/python/baclassic/_store.py b/src/assets/ba_data/python/baclassic/_store.py index 8caf4950..b82f73dc 100644 --- a/src/assets/ba_data/python/baclassic/_store.py +++ b/src/assets/ba_data/python/baclassic/_store.py @@ -267,6 +267,9 @@ class StoreSubsystem: 'icons.mikirog': { 'icon': babase.charstr(babase.SpecialChar.MIKIROG) }, + 'icons.explodinary': { + 'icon': babase.charstr(babase.SpecialChar.EXPLODINARY_LOGO) + }, } return babase.app.classic.store_items diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 8cd7fc29..6f755b66 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -4,9 +4,11 @@ This module is used to set up and/or check the global Python environment before running a ballistica app. This includes things such as paths, -logging, debug-modes, garbage-collection settings, and signal handling. -Because these things are global in nature, this should be done before -any ballistica modules are imported. +logging, and app-dirs. Because these things are global in nature, this +should be done before any ballistica modules are imported. + +This module can also be exec'ed directly to set up a default environment +and then run the app. Ballistica can be used without explicitly configuring the environment in order to integrate it in arbitrary Python environments, but this may @@ -20,25 +22,42 @@ import logging from pathlib import Path from dataclasses import dataclass from typing import TYPE_CHECKING +import __main__ from efro.log import setup_logging, LogLevel if TYPE_CHECKING: from efro.log import LogHandler +# IMPORTANT - It is likely (and in some cases expected) that this +# module's code will be exec'ed multiple times. This is because it is +# the job of this module to set up paths for an engine run, and that may +# involve modifying sys.path in such a way that this module resolves to +# a different path afterwards (for example from +# /abs/path/to/ba_data/scripts/babase.py to ba_data/scripts/babase.py). +# This can result in the next import of baenv loading us from our 'new' +# location, which may or may not actually be the same file on disk as +# the old. Either way, however, multiple execs will happen in some form. +# +# So we need to do a few things to handle that situation gracefully. +# +# - First, we need to store any mutable global state in the __main__ +# module; not in ourself. This way, alternate versions of ourself will +# still know if we already ran configure/etc. +# +# - Second, we should avoid the use of isinstance and similar calls for +# our types. An EnvConfig we create would technically be a different +# type than that created by an alternate baenv. + # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21171 -TARGET_BALLISTICA_VERSION = '1.7.23' - -_g_env_config: EnvConfig | None = None -g_paths_set_failed = False # pylint: disable=invalid-name -g_user_system_scripts_dir: str | None = None +TARGET_BALLISTICA_BUILD = 21183 +TARGET_BALLISTICA_VERSION = '1.7.24' @dataclass class EnvConfig: - """Final settings put together by the configure call.""" + """Environment put together by the configure call.""" config_dir: str data_dir: str @@ -49,17 +68,56 @@ class EnvConfig: log_handler: LogHandler | None +@dataclass +class EnvGlobals: + """Our globals we store in the main module.""" + + config: EnvConfig | None = None + config_called: bool = False + paths_set_failed: bool = False + user_system_scripts_dir: str | None = None + + @classmethod + def get(cls) -> EnvGlobals: + """Create/return our singleton.""" + name = '_baenv_globals' + envglobals: EnvGlobals | None = getattr(__main__, name, None) + if envglobals is None: + envglobals = EnvGlobals() + setattr(__main__, name, envglobals) + return envglobals + + def config_exists() -> bool: """Has a config been created?""" - return _g_env_config is not None + + return EnvGlobals.get().config is not None + + +def did_paths_set_fail() -> bool: + """Did we try to set paths and failed?""" + return EnvGlobals.get().paths_set_failed + + +def get_user_system_scripts_dir() -> str | None: + """If there's a custom user system scripts dir in play, return it.""" + return EnvGlobals.get().user_system_scripts_dir def get_config() -> EnvConfig: - """Return the active env-config. Creates default if none exists.""" - if _g_env_config is None: + """Return the active config, creating a default if none exists.""" + envglobals = EnvGlobals.get() + + if not envglobals.config_called: configure() - assert _g_env_config is not None - return _g_env_config + + config = envglobals.config + if config is None: + raise RuntimeError( + 'baenv.configure() has been called but no config exists;' + ' perhaps it errored?' + ) + return config def configure( @@ -72,22 +130,91 @@ def configure( ) -> None: """Set up the Python environment for running a ballistica app. - This includes things such as Python paths and log redirection. For - that reason, this should be called before any other ballistica - modules are imported, since it may make changes to sys.path, - affecting where those modules get loaded from. + This includes things such as Python path wrangling and app directory + creation. This should be called before any other ballistica modules + are imported since it may make changes to sys.path which can affect + where those modules get loaded from. """ - # pylint: disable=too-many-branches - # pylint: disable=too-many-locals - global _g_env_config # pylint: disable=global-statement - if _g_env_config is not None: - raise RuntimeError('An EnvConfig has already been created.') + envglobals = EnvGlobals.get() - # The very first thing we do is set up our logging system and feed - # Python's stdout/stderr into it. Then we can at least debug problems - # on systems where native stdout/stderr is not easily accessible - # such as Android. + if envglobals.config_called: + raise RuntimeError( + 'baenv.configure() has already been called;' + ' it can only be called once.' + ) + envglobals.config_called = True + + # The very first thing we do is set up our logging system and pipe + # Python's stdout/stderr into it. Then we can at least debug + # problems on systems where native stdout/stderr is not easily + # accessible such as Android. + log_handler = _setup_logging() + + # We want to always be run in UTF-8 mode; complain if we're not. + if sys.flags.utf8_mode != 1: + logging.warning( + "Python's UTF-8 mode is not set. Running ballistica without" + ' it may lead to errors.' + ) + + # Attempt to set up Python paths and our own data paths so that + # engine modules, mods, etc. are pulled from predictable places. + ( + user_python_dir, + app_python_dir, + site_python_dir, + data_dir, + config_dir, + standard_app_python_dir, + ) = _setup_paths( + user_python_dir, + app_python_dir, + site_python_dir, + data_dir, + config_dir, + ) + + # Attempt to create dirs that we'll write stuff to. + _setup_dirs(config_dir, user_python_dir) + + # Get ssl working if needed so we can use https and all that. + _setup_certs(contains_python_dist) + + # This is now the active config. + envglobals.config = EnvConfig( + config_dir=config_dir, + data_dir=data_dir, + user_python_dir=user_python_dir, + app_python_dir=app_python_dir, + standard_app_python_dir=standard_app_python_dir, + site_python_dir=site_python_dir, + log_handler=log_handler, + ) + + +def _calc_data_dir(data_dir: str | None) -> str: + if data_dir is None: + # To calc default data_dir, we assume this module was imported + # from that dir's ba_data/python subdir. + assert Path(__file__).parts[-3:-1] == ('ba_data', 'python') + data_dir_path = Path(__file__).parents[2] + + # Prefer tidy relative paths like '.' if possible so that things + # like stack traces are easier to read. + + # NOTE: Perhaps we should have an option to disable this + # behavior for cases where the user might be doing chdir stuff. + cwd_path = Path.cwd() + data_dir = str( + data_dir_path.relative_to(cwd_path) + if data_dir_path.is_relative_to(cwd_path) + else data_dir_path + ) + return data_dir + + +def _setup_logging() -> LogHandler: log_handler = setup_logging( log_path=None, level=LogLevel.DEBUG, @@ -95,41 +222,46 @@ def configure( log_stdout_stderr=True, cache_size_limit=1024 * 1024, ) + return log_handler - # Sanity check: we should always be run in UTF-8 mode. - if sys.flags.utf8_mode != 1: - logging.warning( - "Python's UTF-8 mode is not set. Running ballistica without" - ' it may lead to errors.' - ) - # Now do paths. We want to set stuff up so that engine modules, - # mods, etc. are pulled from predictable places. - cwd_path = Path.cwd() +def _setup_certs(contains_python_dist: bool) -> None: + # In situations where we're bringing our own Python let's also + # provide our own root certs so ssl works. We can consider + # overriding this in particular embedded cases if we can verify that + # system certs are working. We also allow forcing this via an env + # var if the user desires. + if ( + contains_python_dist + or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1' + ): + import certifi - # A few paths we can ALWAYS calculate since they don't affect Python - # imports: + # Let both OpenSSL and requests (if present) know to use this. + os.environ['SSL_CERT_FILE'] = os.environ[ + 'REQUESTS_CA_BUNDLE' + ] = certifi.where() - # Default data_dir assumes this module was imported from its - # ba_data/python subdir. - if data_dir is None: - assert Path(__file__).parts[-3:-1] == ('ba_data', 'python') - data_dir_path = Path(__file__).parents[2] - # Prefer tidy relative paths like '.' if possible. - data_dir = str( - data_dir_path.relative_to(cwd_path) - if data_dir_path.is_relative_to(cwd_path) - else data_dir_path - ) + +def _setup_paths( + user_python_dir: str | None, + app_python_dir: str | None, + site_python_dir: str | None, + data_dir: str | None, + config_dir: str | None, +) -> tuple[str | None, str | None, str | None, str, str, str]: + # First a few paths we can ALWAYS calculate since they don't affect + # Python imports: + + envglobals = EnvGlobals.get() + + data_dir = _calc_data_dir(data_dir) # Default config-dir is simply ~/.ballisticakit if config_dir is None: config_dir = str(Path(Path.home(), '.ballisticakit')) - # Ok now Python paths. - - # By default, app-python-dir is simply ba_data/python under - # data-dir. + # Standard app-python-dir is simply ba_data/python under data-dir. standard_app_python_dir = str(Path(data_dir, 'ba_data', 'python')) # If _babase has already been imported, there's not much we can do @@ -137,12 +269,11 @@ def configure( if '_babase' in sys.modules: app_python_dir = user_python_dir = site_python_dir = None - # We don't actually complain yet here; we simply take note - # that we weren't able to set paths. Then we complain if/when - # the app is started. This way, non-app uses of babase won't be - # filled with unnecessary warnings. - global g_paths_set_failed # pylint: disable=global-statement - g_paths_set_failed = True + # We don't actually complain yet here; we simply take note that + # we weren't able to set paths. Then we complain if/when the app + # is started. This way, non-app uses of babase won't be filled + # with unnecessary warnings. + envglobals.paths_set_failed = True else: # Ok; _babase hasn't been imported yet so we can muck with @@ -163,35 +294,58 @@ def configure( # Wherever our user_python_dir is, if we find a sys/FOO dir # under it where FOO matches our version, use that as our - # app_python_dir. - check_dir = os.path.join( - user_python_dir, 'sys', TARGET_BALLISTICA_VERSION - ) - if os.path.isdir(check_dir): - global g_user_system_scripts_dir # pylint: disable=global-statement - g_user_system_scripts_dir = check_dir - app_python_dir = check_dir + # app_python_dir. This allows modding built-in stuff on + # platforms where there is no write access to said built-in + # stuff. + check_dir = Path(user_python_dir, 'sys', TARGET_BALLISTICA_VERSION) + if check_dir.is_dir(): + envglobals.user_system_scripts_dir = app_python_dir = str(check_dir) # Ok, now apply these to sys.path. # First off, strip out any instances of the path containing this - # module. We will probably be re-adding the same path in a - # moment but its technically possible that we won't be (if - # app_python_dir is overridden to somewhere else, etc.) + # module. We will *probably* be re-adding the same path in a + # moment so this keeps things cleaner. Though hmm should we + # leave it in there in cases where we *don't* re-add the same + # path?... our_parent_path = Path(__file__).parent.resolve() - paths: list[str] = [ + oldpaths: list[str] = [ p for p in sys.path if Path(p).resolve() != our_parent_path ] - # Let's lookup mods first (so users can do whatever they want). - # and then our bundled scripts last (don't want bundled - # site-package stuff overwriting system versions) - paths.insert(0, user_python_dir) - paths.append(app_python_dir) - paths.append(site_python_dir) - sys.path = paths - # Attempt to create the dirs that we'll write stuff to. Not the end - # of the world if we fail though. + # Let's place mods first (so users can override whatever they + # want) followed by our app scripts and lastly our bundled site + # stuff. + + # One could make the argument that at least our bundled app & + # site stuff should be placed at the end so actual local site + # stuff could override it. That could be a good thing or a bad + # thing. Maybe we could add an option for that, but for now I'm + # prioritizing our stuff to give as consistent an environment as + # possible. + ourpaths = [user_python_dir, app_python_dir, site_python_dir] + + # Special case: our modular builds will have a 'python-dylib' + # dir alongside the 'python' scripts dir which contains our + # binary Python modules. If we see that, add it to the path also. + # Not sure if we'd ever have a need to customize this path. + dylibdir = f'{app_python_dir}-dylib' + if os.path.exists(dylibdir): + ourpaths.append(dylibdir) + + sys.path = ourpaths + oldpaths + + return ( + user_python_dir, + app_python_dir, + site_python_dir, + data_dir, + config_dir, + standard_app_python_dir, + ) + + +def _setup_dirs(config_dir: str | None, user_python_dir: str | None) -> None: create_dirs: list[tuple[str, str | None]] = [ ('config', config_dir), ('user_python', user_python_dir), @@ -201,32 +355,22 @@ def configure( try: os.makedirs(cdir, exist_ok=True) except Exception: + # Not the end of the world if we can't make these dirs. logging.warning( "Unable to create %s dir at '%s'.", cdirname, cdir ) - _g_env_config = EnvConfig( - config_dir=config_dir, - data_dir=data_dir, - user_python_dir=user_python_dir, - app_python_dir=app_python_dir, - standard_app_python_dir=standard_app_python_dir, - site_python_dir=site_python_dir, - log_handler=log_handler, - ) - # In embedded situations (when we're providing our own Python) let's - # also provide our own root certs so ssl works. We can consider - # overriding this in particular embedded cases if we can verify that - # system certs are working. (We also allow forcing this via an env - # var if the user desires) - if ( - contains_python_dist - or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1' - ): - import certifi +def _main() -> None: + # Run a default configure BEFORE importing babase. + # (may affect where babase comes from). + configure() - # Let both OpenSSL and requests (if present) know to use this. - os.environ['SSL_CERT_FILE'] = os.environ[ - 'REQUESTS_CA_BUNDLE' - ] = certifi.where() + import babase + + babase.app.run() + + +# Allow exec'ing this module directly to do a standard app run. +if __name__ == '__main__': + _main() diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index a1f6ed03..fbb6773e 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -177,6 +177,11 @@ void BaseFeatureSet::StartApp() { LogVersionInfo(); + // The logic thread (or maybe other things) need to run Python as + // we're bringing them up, so let it go for the duration of this call. + // We'll explicitly grab it if/when we need it. + Python::ScopedInterpreterLockRelease gil_release; + // Read in ba.app.config for anyone who wants to start looking at it // (though we don't explicitly ask anyone to apply it until later). python->ReadConfig(); @@ -280,6 +285,10 @@ void BaseFeatureSet::RunAppToCompletion() { StartApp(); } + // Let go of the GIL while we're running. The logic thread or other things + // will grab it when needed. + Python::ScopedInterpreterLockRelease gil_release; + // On our event-loop-managing platforms we now simply sit in our event // loop until the app is quit. g_core->main_event_loop()->RunEventLoop(false); @@ -494,8 +503,13 @@ void BaseFeatureSet::DoV1CloudLog(const std::string& msg) { // We may attempt to import stuff and that should *never* happen before // base is fully imported. if (!IsBaseCompletelyImported()) { - printf( - "WARNING: V1CloudLog called before babase fully imported; ignoring.\n"); + static bool warned = false; + if (!warned) { + warned = true; + printf( + "WARNING: V1CloudLog called before babase fully imported; " + "ignoring.\n"); + } return; } @@ -523,7 +537,11 @@ void BaseFeatureSet::DoV1CloudLog(const std::string& msg) { // Need plus for direct sends. if (!HavePlus()) { - printf("WARNING: V1CloudLog direct-sends not available; ignoring.\n"); + static bool did_warn = false; + if (!did_warn) { + did_warn = true; + printf("WARNING: V1CloudLog direct-sends not available; ignoring.\n"); + } return; } diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index d4c121b7..62c9a69e 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -49,8 +49,6 @@ static PyMethodDef PyAppNameDef = { static auto PyRunApp(PyObject* self) -> PyObject* { BA_PYTHON_TRY; - FatalError("NOT WORKING YET; COME BACK SOON."); - assert(g_base); g_base->RunAppToCompletion(); diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index effbeb24..a515a8c2 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -97,7 +97,7 @@ void CoreFeatureSet::PostInit() { // FIXME: MOVE THIS TO A RUN_APP_TO_COMPLETION() SORT OF PLACE. // For now it does the right thing here since all we have is monolithic // builds but this will need to account for more situations later. - python->ReleaseMainThreadGIL(); + // python->ReleaseMainThreadGIL(); } auto CoreFeatureSet::CalcBuildSrcDir() -> std::string { diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc index 40a76b88..9ff02388 100644 --- a/src/ballistica/core/python/core_python.cc +++ b/src/ballistica/core/python/core_python.cc @@ -202,14 +202,6 @@ void CorePython::ImportPythonObjs() { } } -void CorePython::ReleaseMainThreadGIL() { - assert(g_core->InMainThread()); - // After we bootstrap Python here in the main thread we release the GIL. - // We'll explicitly reacquire it anytime we need it (mainly in the logic - // thread once that comes up later). - PyEval_SaveThread(); -} - void CorePython::SoftImportBase() { auto gil{Python::ScopedInterpreterLock()}; auto result = PythonRef::StolenSoft(PyImport_ImportModule("_babase")); @@ -345,29 +337,4 @@ void CorePython::LoggingCall(LogLevel loglevel, const std::string& msg) { objs().Get(logcallobj).Call(args); } -void CorePython::AcquireGIL() { - assert(g_base_soft && g_base_soft->InLogicThread()); - auto debug_timing{g_core->core_config().debug_timing}; - millisecs_t startms{debug_timing ? CorePlatform::GetCurrentMillisecs() : 0}; - - if (logic_thread_state_) { - PyEval_RestoreThread(logic_thread_state_); - logic_thread_state_ = nullptr; - } - - if (debug_timing) { - auto duration{CorePlatform::GetCurrentMillisecs() - startms}; - if (duration > (1000 / 120)) { - Log(LogLevel::kInfo, - "GIL acquire took too long (" + std::to_string(duration) + " ms)."); - } - } -} - -void CorePython::ReleaseGIL() { - assert(g_base_soft && g_base_soft->InLogicThread()); - assert(logic_thread_state_ == nullptr); - logic_thread_state_ = PyEval_SaveThread(); -} - } // namespace ballistica::core diff --git a/src/ballistica/core/python/core_python.h b/src/ballistica/core/python/core_python.h index 619f6eca..9b9ab74e 100644 --- a/src/ballistica/core/python/core_python.h +++ b/src/ballistica/core/python/core_python.h @@ -52,11 +52,8 @@ class CorePython { /// logging is available, logs locally using Logging::DisplayLog() /// (with an added warning). void LoggingCall(LogLevel loglevel, const std::string& msg); - void AcquireGIL(); - void ReleaseGIL(); void ImportPythonObjs(); void VerifyPythonEnvironment(); - void ReleaseMainThreadGIL(); void SoftImportBase(); const auto& objs() { return objs_; } @@ -64,8 +61,6 @@ class CorePython { private: PythonObjectSet objs_; - PyThreadState* logic_thread_state_{}; - // Log calls we make before we're set up to ship logs through Python // go here. They all get shipped at once as soon as it is possible. bool python_logging_calls_enabled_{}; diff --git a/src/ballistica/plus/README.md b/src/ballistica/plus/README.md index 0451f7e4..fcb16a64 100644 --- a/src/ballistica/plus/README.md +++ b/src/ballistica/plus/README.md @@ -2,5 +2,5 @@ Bits of the engine related to accounts and cloud functionality. In prefab builds the compiled code for this feature set is contained in the pre-compiled static -ballistica_plus library. The plus feature set can also be removed from +ballisticaplus library. The plus feature set can also be removed from spinoff projects if desired to remove the need for that library. diff --git a/src/ballistica/scene_v1/connection/connection_to_client.cc b/src/ballistica/scene_v1/connection/connection_to_client.cc index 2fcda718..8e3cdc9c 100644 --- a/src/ballistica/scene_v1/connection/connection_to_client.cc +++ b/src/ballistica/scene_v1/connection/connection_to_client.cc @@ -385,16 +385,18 @@ void ConnectionToClient::HandleMessagePacket( case BA_MESSAGE_CLIENT_INFO: { if (buffer.size() > 1) { + // Make a null-terminated copy of the string data. std::vector str_buffer(buffer.size()); - memcpy(&(str_buffer[0]), &(buffer[1]), buffer.size() - 1); + memcpy(str_buffer.data(), buffer.data() + 1, buffer.size() - 1); str_buffer[str_buffer.size() - 1] = 0; - cJSON* info = cJSON_Parse(reinterpret_cast(&(buffer[1]))); + + cJSON* info = cJSON_Parse(str_buffer.data()); if (info) { cJSON* b = cJSON_GetObjectItem(info, "b"); if (b) { build_number_ = b->valueint; } else { - Log(LogLevel::kError, "no buildnumber in clientinfo msg"); + Log(LogLevel::kError, "No buildnumber in clientinfo msg."); } // Grab their token (we use this to ask the @@ -403,7 +405,7 @@ void ConnectionToClient::HandleMessagePacket( if (t) { token_ = t->valuestring; } else { - Log(LogLevel::kError, "no token in clientinfo msg"); + Log(LogLevel::kError, "No token in clientinfo msg."); } // Newer clients also pass a peer-hash, which @@ -423,9 +425,9 @@ void ConnectionToClient::HandleMessagePacket( cJSON_Delete(info); } else { Log(LogLevel::kError, - "got invalid json in clientinfo message: '" + "Got invalid json in clientinfo message: '" + std::string(reinterpret_cast(&(buffer[1]))) - + "'"); + + "'."); } } got_client_info_ = true; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 1ddfbe7d..88f590e9 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -22,7 +22,7 @@ #endif // If desired, define main() in the global namespace. -#if BA_DEFINE_MAIN +#if BA_MONOLITHIC_BUILD && BA_DEFINE_MAIN auto main(int argc, char** argv) -> int { auto core_config = ballistica::core::CoreConfig::FromCommandLineAndEnv(argc, argv); @@ -39,8 +39,10 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21171; -const char* kEngineVersion = "1.7.23"; +const int kEngineBuildNumber = 21183; +const char* kEngineVersion = "1.7.24"; + +#if BA_MONOLITHIC_BUILD auto MonolithicMain(const core::CoreConfig& core_config) -> int { // This code is meant to be run standalone so won't inherit any @@ -171,6 +173,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { return -1; // Didn't even get core; something clearly wrong. } +#endif // BA_MONOLITHIC_BUILD + void FatalError(const std::string& message) { // Let the user and/or master-server know we're dying. FatalError::ReportFatalError(message, false); diff --git a/src/ballistica/shared/ballistica.h b/src/ballistica/shared/ballistica.h index 9fe8712c..0d7114ea 100644 --- a/src/ballistica/shared/ballistica.h +++ b/src/ballistica/shared/ballistica.h @@ -104,8 +104,10 @@ class CoreConfig; // enough that avoiding the extra class includes seems like an overall // compile-time/convenience win. +#if BA_MONOLITHIC_BUILD /// Entry point for standard monolithic builds. Handles all initing and running. auto MonolithicMain(const core::CoreConfig& config) -> int; +#endif // BA_MONOLITHIC_BUILD // Print a momentary message on the screen. void ScreenMessage(const std::string& msg); diff --git a/src/ballistica/shared/buildconfig/buildconfig_cmake.h b/src/ballistica/shared/buildconfig/buildconfig_cmake.h index 8b3a2e0b..5d57c40a 100644 --- a/src/ballistica/shared/buildconfig/buildconfig_cmake.h +++ b/src/ballistica/shared/buildconfig/buildconfig_cmake.h @@ -64,7 +64,9 @@ // Allow stdin commands too. #define BA_ENABLE_STDIO_CONSOLE 1 +#ifndef BA_DEFINE_MAIN #define BA_DEFINE_MAIN 1 +#endif #if !BA_DEBUG_BUILD diff --git a/src/ballistica/shared/buildconfig/buildconfig_common.h b/src/ballistica/shared/buildconfig/buildconfig_common.h index 35e175e0..c3691395 100644 --- a/src/ballistica/shared/buildconfig/buildconfig_common.h +++ b/src/ballistica/shared/buildconfig/buildconfig_common.h @@ -23,8 +23,10 @@ namespace ballistica { // can override any of these before this is included. // Monolithic builds consist of a single binary that inits and manages -// Python itself, as opposed to modular builds which are made up of -// Python binary modules which are run under a standard Python runtime. +// Python itself, as opposed to modular builds which are made up of Python +// binary modules which are run under a standard Python runtime. This will +// be 0 for both modular (.so) builds of the engine as well as for static +// libraries such as baplus intended to be linked to either version. #ifndef BA_MONOLITHIC_BUILD #define BA_MONOLITHIC_BUILD 1 #endif diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index 0d0e3c05..c5c6f9ef 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -7,6 +7,7 @@ #include "ballistica/core/support/base_soft.h" #include "ballistica/shared/foundation/fatal_error.h" #include "ballistica/shared/python/python.h" +#include "ballistica/shared/python/python_sys.h" namespace ballistica { @@ -224,7 +225,7 @@ void EventLoop::WaitForNextEvent(bool single_cycle) { // While we're waiting, allow other python threads to run. if (acquires_python_gil_) { - g_core->python->ReleaseGIL(); + ReleaseGIL(); } // If we've got active timers, wait for messages with a timeout so we can @@ -257,7 +258,7 @@ void EventLoop::WaitForNextEvent(bool single_cycle) { } if (acquires_python_gil_) { - g_core->python->AcquireGIL(); + AcquireGIL(); } } @@ -451,7 +452,7 @@ void EventLoop::SetAcquiresPythonGIL() { assert(!acquires_python_gil_); // This should be called exactly once. assert(ThreadIsCurrent()); acquires_python_gil_ = true; - g_core->python->AcquireGIL(); + AcquireGIL(); } // Explicitly kill the main thread. @@ -784,4 +785,30 @@ auto EventLoop::CheckPushRunnableSafety() -> bool { return thread_messages_.size() < kThreadMessageSafetyThreshold; } +void EventLoop::AcquireGIL() { + assert(g_base_soft && g_base_soft->InLogicThread()); + auto debug_timing{g_core->core_config().debug_timing}; + millisecs_t startms{debug_timing ? core::CorePlatform::GetCurrentMillisecs() + : 0}; + + if (py_thread_state_) { + PyEval_RestoreThread(py_thread_state_); + py_thread_state_ = nullptr; + } + + if (debug_timing) { + auto duration{core::CorePlatform::GetCurrentMillisecs() - startms}; + if (duration > (1000 / 120)) { + Log(LogLevel::kInfo, + "GIL acquire took too long (" + std::to_string(duration) + " ms)."); + } + } +} + +void EventLoop::ReleaseGIL() { + assert(g_base_soft && g_base_soft->InLogicThread()); + assert(py_thread_state_ == nullptr); + py_thread_state_ = PyEval_SaveThread(); +} + } // namespace ballistica diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index ff93384b..0a30544d 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -164,6 +164,9 @@ class EventLoop { void RunPauseCallbacks(); void RunResumeCallbacks(); + void AcquireGIL(); + void ReleaseGIL(); + bool bootstrapped_{}; std::list> runnables_; std::list pause_callbacks_; @@ -174,6 +177,7 @@ class EventLoop { std::condition_variable client_listener_cv_; std::mutex client_listener_mutex_; std::list> data_to_client_; + PyThreadState* py_thread_state_{}; TimerList timers_; }; diff --git a/src/ballistica/shared/python/python.cc b/src/ballistica/shared/python/python.cc index 414e2ccc..05226da1 100644 --- a/src/ballistica/shared/python/python.cc +++ b/src/ballistica/shared/python/python.cc @@ -377,21 +377,16 @@ void Python::MarkReachedEndOfModule(PyObject* module) { class Python::ScopedInterpreterLock::Impl { public: Impl() { - if (need_lock_) { - // Grab the python GIL. - gstate_ = PyGILState_Ensure(); - } + // Grab the python GIL. + gil_state_ = PyGILState_Ensure(); } ~Impl() { - if (need_lock_) { - // Release the python GIL. - PyGILState_Release(gstate_); - } + // Release the python GIL. + PyGILState_Release(gil_state_); } private: - bool need_lock_{true}; - PyGILState_STATE gstate_{PyGILState_UNLOCKED}; + PyGILState_STATE gil_state_{PyGILState_UNLOCKED}; }; Python::ScopedInterpreterLock::ScopedInterpreterLock() @@ -401,6 +396,31 @@ Python::ScopedInterpreterLock::ScopedInterpreterLock() Python::ScopedInterpreterLock::~ScopedInterpreterLock() { delete impl_; } +class Python::ScopedInterpreterLockRelease::Impl { + public: + Impl() { + assert(HaveGIL()); + // Release the GIL. + thread_state_ = PyEval_SaveThread(); + } + ~Impl() { + // Restore the GIL. + PyEval_RestoreThread(thread_state_); + } + + private: + PyThreadState* thread_state_{}; +}; + +Python::ScopedInterpreterLockRelease::ScopedInterpreterLockRelease() + : impl_{new Python::ScopedInterpreterLockRelease::Impl()} +// impl_{std::make_unique()} +{} + +Python::ScopedInterpreterLockRelease::~ScopedInterpreterLockRelease() { + delete impl_; +} + // (some stuff borrowed from python's source code - used in our overriding of // objects' dir() results) diff --git a/src/ballistica/shared/python/python.h b/src/ballistica/shared/python/python.h index 40344e80..7b43a1d8 100644 --- a/src/ballistica/shared/python/python.h +++ b/src/ballistica/shared/python/python.h @@ -39,9 +39,9 @@ class Python { BA_DISALLOW_CLASS_COPIES(ScopedCallLabel); }; - /// Use this to protect Python code that may be run in cases where we don't - /// hold the Global Interpreter Lock (GIL) (basically anything outside of the - /// logic thread). + /// Use this to protect Python code that may be run in cases where we + /// don't hold the Global Interpreter Lock (GIL). (Basically anything + /// outside of the logic thread). class ScopedInterpreterLock { public: ScopedInterpreterLock(); @@ -55,7 +55,20 @@ class Python { Impl* impl_{}; }; - // static auto Create() -> Python*; + /// Use this for cases where we *do* hold the GIL but want to release + /// it for some operation. + class ScopedInterpreterLockRelease { + public: + ScopedInterpreterLockRelease(); + ~ScopedInterpreterLockRelease(); + + private: + class Impl; + // Note: should use unique_ptr for this, but build fails on raspberry pi + // (gcc 8.3.0). Works on Ubuntu 9.3 so should try again later. + // std::unique_ptr impl_{}; + Impl* impl_{}; + }; /// Return whether the current thread holds the global-interpreter-lock. /// We must always hold the GIL while running python code. diff --git a/src/external/readlinktest.c b/src/external/readlinktest.c new file mode 100644 index 00000000..1f65d6b3 --- /dev/null +++ b/src/external/readlinktest.c @@ -0,0 +1,6 @@ +#include + +int main(int argc, char** argv) { + printf("HELLO WORLD!\n"); + return 0; +} diff --git a/tools/batools/assetstaging.py b/tools/batools/assetstaging.py index a33e1e4b..490c01a6 100755 --- a/tools/batools/assetstaging.py +++ b/tools/batools/assetstaging.py @@ -16,26 +16,38 @@ from efrotools import PYVER if TYPE_CHECKING: pass -# Suffix for the pyc files we include in stagings. -# We're using deterministic opt pyc files; see PEP 552. -# Note: this means anyone wanting to modify .py files in a build -# will need to wipe out the existing .pyc files first or the changes -# will be ignored. +# Suffix for the pyc files we include in stagings. We're using +# deterministic opt pyc files; see PEP 552. +# +# Note: this means anyone +# wanting to modify .py files in a build will need to wipe out the +# existing .pyc files first or the changes will be ignored. OPT_PYC_SUFFIX = 'cpython-' + PYVER.replace('.', '') + '.opt-1.pyc' -class Config: - """Encapsulates command options.""" +def stage_assets(projroot: str, args: list[str] | None = None) -> None: + """Stage assets for a build.""" + + if args is None: + args = sys.argv + + AssetStager(projroot).run(args) + + +class AssetStager: + """Context for a run of the tool.""" def __init__(self, projroot: str) -> None: self.projroot = projroot # We always calc src relative to this script. - self.src = self.projroot + '/build/assets' + self.src = f'{self.projroot}/build/assets' self.dst: str | None = None self.serverdst: str | None = None self.win_extras_src: str | None = None self.win_platform: str | None = None self.win_type: str | None = None + self.include_python_dylib = False + self.include_shell_launcher = False self.include_audio = True self.include_meshes = True self.include_collision_meshes = True @@ -51,11 +63,105 @@ class Config: self.is_payload_full = False self.debug: bool | None = None + def run(self, args: list[str]) -> None: + """Do the thing.""" + self._parse_args(args) + + # Ok, now for every top level dir in src, come up with a nice single + # command to sync the needed subset of it to dst. + + # We can now use simple speedy timestamp based updates since we no + # longer have to try to preserve timestamps to get .pyc files to + # behave (hooray!) + + # Do our stripped down pylib dir for platforms that use that. + if self.include_pylib: + self._sync_pylib() + else: + if self.dst is not None and os.path.isdir(f'{self.dst}/pylib'): + subprocess.run(['rm', '-rf', f'{self.dst}/pylib'], check=True) + + # Sync our server files if we're doing that. + if self.serverdst is not None: + self._sync_server_files() + + # On windows we need to pull in some dlls and this and that (we also + # include a non-stripped-down set of Python libs). + if self.win_extras_src is not None: + self._sync_windows_extras() + + # Standard stuff in ba_data. + self._sync_ba_data() + + # On Android we need to build a payload file so it knows what to + # pull out of the apk. + if self.include_payload_file: + assert self.dst is not None + _write_payload_file(self.dst, self.is_payload_full) + + def _parse_args(self, args: list[str]) -> None: + """Parse args and apply to ourself.""" + if len(args) < 1: + raise RuntimeError('Expected at least one argument.') + platform_arg = args[0] + + # Require either -debug or -release in args. + if '-debug' in args: + self.debug = True + assert '-release' not in args + elif '-release' in args: + self.debug = False + else: + raise RuntimeError( + "Expected either '-debug' or '-release' in args." + ) + + if platform_arg == '-android': + self._parse_android_args(args) + elif platform_arg.startswith('-win'): + self._parse_win_args(platform_arg, args) + elif platform_arg == '-cmake': + self.dst = args[-1] + self.tex_suffix = '.dds' + elif platform_arg == '-cmakemodular': + self.dst = args[-1] + self.tex_suffix = '.dds' + self.include_python_dylib = True + self.include_shell_launcher = True + elif platform_arg == '-cmakeserver': + self.dst = os.path.join(args[-1], 'dist') + self.serverdst = args[-1] + self.include_textures = False + self.include_audio = False + self.include_meshes = False + + elif platform_arg == '-xcode-mac': + self.src = os.environ['SOURCE_ROOT'] + '/build/assets' + self.dst = ( + os.environ['TARGET_BUILD_DIR'] + + '/' + + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'] + ) + self.include_pylib = True + self.pylib_src_name = 'pylib-apple' + self.tex_suffix = '.dds' + elif platform_arg == '-xcode-ios': + self.src = os.environ['SOURCE_ROOT'] + '/build/assets' + self.dst = ( + os.environ['TARGET_BUILD_DIR'] + + '/' + + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'] + ) + self.include_pylib = True + self.pylib_src_name = 'pylib-apple' + self.tex_suffix = '.pvr' + else: + raise RuntimeError('No valid platform arg provided.') + def _parse_android_args(self, args: list[str]) -> None: - # On Android we get nitpicky with what - # we want to copy in since we can speed up - # iterations by installing stripped down - # apks. + # On Android we get nitpicky with exactly what we want to copy + # in since we can speed up iterations by installing stripped + # down apks. self.dst = 'assets/ballistica_files' self.pylib_src_name = 'pylib-android' self.include_payload_file = True @@ -98,9 +204,9 @@ class Config: elif arg == '-audio': self.include_audio = True - def _parse_win_platform(self, platform: str, args: list[str]) -> None: + def _parse_win_args(self, platform: str, args: list[str]) -> None: """Parse sub-args in the windows platform string.""" - winempty, wintype, winplt, wincfg = platform.split('-') + winempty, wintype, winplt = platform.split('-') self.win_platform = winplt self.win_type = wintype assert winempty == '' @@ -115,78 +221,375 @@ class Config: self.include_audio = False self.include_meshes = False else: - raise RuntimeError(f'Invalid wintype: "{wintype}"') + raise RuntimeError(f"Invalid wintype: '{wintype}'.") if winplt == 'Win32': - self.win_extras_src = self.projroot + '/build/assets/windows/Win32' + self.win_extras_src = f'{self.projroot}/build/assets/windows/Win32' elif winplt == 'x64': - self.win_extras_src = self.projroot + '/build/assets/windows/x64' + self.win_extras_src = f'{self.projroot}/build/assets/windows/x64' else: - raise RuntimeError(f'Invalid winplt: "{winplt}"') + raise RuntimeError(f"Invalid winplt: '{winplt}'.") - if wincfg == 'Debug': - self.debug = True - elif wincfg == 'Release': - self.debug = False + def _sync_windows_extras(self) -> None: + # pylint: disable=too-many-branches + assert self.win_extras_src is not None + assert self.win_platform is not None + assert self.win_type is not None + if not os.path.isdir(self.win_extras_src): + raise RuntimeError( + f"Win extras src dir not found: '{self.win_extras_src}'." + ) + + # Ok, lets do full syncs on each subdir we find so we properly + # delete anything in dst that disappeared from src. Lastly we'll + # sync over the remaining top level files. Note: technically it'll + # be possible to leave orphaned top level files in dst, so when + # building packages/etc. we should always start from scratch. + assert self.dst is not None + assert self.debug is not None + pyd_rules: list[str] + if self.debug: + pyd_rules = ['--include', '*_d.pyd'] else: - raise RuntimeError(f'Invalid wincfg: "{wincfg}"') + pyd_rules = ['--exclude', '*_d.pyd', '--include', '*.pyd'] - def parse_args(self, args: list[str]) -> None: - """Parse args and apply to the cfg.""" - if len(args) < 1: - raise RuntimeError('Expected a platform argument.') - platform = args[0] - if platform == '-android': - self._parse_android_args(args) - elif platform.startswith('-win'): - self._parse_win_platform(platform, args) - elif platform == '-cmake': - self.dst = args[1] - self.tex_suffix = '.dds' - elif '-cmakeserver' in args: - self.dst = os.path.join(args[-1], 'dist') - self.serverdst = args[-1] - self.include_textures = False - self.include_audio = False - self.include_meshes = False + for dirname in ('DLLs', 'Lib'): + # EWW: seems Windows Python currently sets its path to ./lib but + # it comes with Lib. Windows is normally case-insensitive but + # this messes it up when running under WSL. Let's install it as + # lib for now. + dstdirname = 'lib' if dirname == 'Lib' else dirname + os.makedirs(f'{self.dst}/{dstdirname}', exist_ok=True) + cmd: list[str] = ( + [ + 'rsync', + '--recursive', + '--times', + '--delete', + '--delete-excluded', + '--prune-empty-dirs', + '--include', + '*.ico', + '--include', + '*.cat', + '--include', + '*.dll', + ] + + pyd_rules + + [ + '--include', + '*.py', + '--include', + f'*.{OPT_PYC_SUFFIX}', + '--include', + '*/', + '--exclude', + '*', + f'{os.path.join(self.win_extras_src, dirname)}/', + f'{self.dst}/{dstdirname}/', + ] + ) + subprocess.run(cmd, check=True) - # Require either -debug or -release in args. - # FIXME: should require this for all platforms for consistency. - if '-debug' in args: - self.debug = True - assert '-release' not in args - elif '-release' in args: - self.debug = False + # Now sync the top level individual files that we want. We could + # technically copy everything over but this keeps staging dirs a bit + # tidier. + dbgsfx = '_d' if self.debug else '' + + # Note: Needs updating when Python version changes (currently 3.11). + toplevelfiles: list[str] = [f'python311{dbgsfx}.dll'] + + if self.win_type == 'win': + toplevelfiles += [ + 'libvorbis.dll', + 'libvorbisfile.dll', + 'ogg.dll', + 'OpenAL32.dll', + 'SDL2.dll', + ] + elif self.win_type == 'winserver': + toplevelfiles += [f'python{dbgsfx}.exe'] + + # Include debug dlls so folks without msvc can run them. + if self.debug: + if self.win_platform == 'x64': + toplevelfiles += [ + 'msvcp140d.dll', + 'vcruntime140d.dll', + 'vcruntime140_1d.dll', + 'ucrtbased.dll', + ] else: - raise RuntimeError( - "Expected either '-debug' or '-release' in args." - ) - elif '-xcode-mac' in args: - self.src = os.environ['SOURCE_ROOT'] + '/build/assets' - self.dst = ( - os.environ['TARGET_BUILD_DIR'] - + '/' - + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'] - ) - self.include_pylib = True - self.pylib_src_name = 'pylib-apple' - self.tex_suffix = '.dds' - elif '-xcode-ios' in args: - self.src = os.environ['SOURCE_ROOT'] + '/build/assets' - self.dst = ( - os.environ['TARGET_BUILD_DIR'] - + '/' - + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'] - ) - self.include_pylib = True - self.pylib_src_name = 'pylib-apple' - self.tex_suffix = '.pvr' + toplevelfiles += [ + 'msvcp140d.dll', + 'vcruntime140d.dll', + 'ucrtbased.dll', + ] + + # Include the runtime redistributables in release builds. + if not self.debug: + if self.win_platform == 'x64': + toplevelfiles.append('vc_redist.x64.exe') + elif self.win_platform == 'Win32': + toplevelfiles.append('vc_redist.x86.exe') + else: + raise RuntimeError(f'Invalid win_platform {self.win_platform}') + + cmd2 = ( + ['rsync', '--times'] + + [os.path.join(self.win_extras_src, f) for f in toplevelfiles] + + [f'{self.dst}/'] + ) + subprocess.run(cmd2, check=True) + + # If we're running under WSL we won't be able to launch these .exe + # files unless they're marked executable, so do that here. Update: + # gonna try simply setting this flag on the source side. + # _run(f'chmod +x {self.dst}/*.exe') + + def _sync_pylib(self) -> None: + assert self.pylib_src_name is not None + assert self.dst is not None + os.makedirs(f'{self.dst}/pylib', exist_ok=True) + cmd: list[str] = [ + 'rsync', + '--recursive', + '--times', + '--delete', + '--delete-excluded', + '--prune-empty-dirs', + '--include', + '*.py', + '--include', + f'*.{OPT_PYC_SUFFIX}', + '--include', + '*/', + '--exclude', + '*', + f'{self.src}/{self.pylib_src_name}/', + f'{self.dst}/pylib/', + ] + subprocess.run(cmd, check=True) + + def _sync_ba_data(self) -> None: + assert self.dst is not None + os.makedirs(f'{self.dst}/ba_data', exist_ok=True) + cmd: list[str] = [ + 'rsync', + '--recursive', + '--times', + '--delete', + '--prune-empty-dirs', + ] + + # Normally we use --delete-excluded so that we can do sparse + # syncs for quick iteration on android apks/etc. However for our + # modular builds we need to avoid that flag because we do a + # second pass after to sync in our python-dylib stuff and with + # that flag it all gets blown on the first pass. + if not self.include_python_dylib: + cmd.append('--delete-excluded') else: - raise RuntimeError('No valid platform arg provided.') + # Shouldn't be trying to do sparse stuff. + assert ( + self.include_textures + and self.include_audio + and self.include_fonts + and self.include_json + and self.include_meshes + and self.include_collision_meshes + ) + # Keep rsync from trying to prune this as an 'empty' dir. + cmd += ['--exclude', '/python-dylib'] + + if self.include_scripts: + cmd += [ + '--include', + '*.py', + '--include', + '*.pem', + '--include', + f'*.{OPT_PYC_SUFFIX}', + ] + + if self.include_textures: + assert self.tex_suffix is not None + cmd += ['--include', f'*{self.tex_suffix}'] + + if self.include_audio: + cmd += ['--include', '*.ogg'] + + if self.include_fonts: + cmd += ['--include', '*.fdata'] + + if self.include_json: + cmd += ['--include', '*.json'] + + if self.include_meshes: + cmd += ['--include', '*.bob'] + + if self.include_collision_meshes: + cmd += ['--include', '*.cob'] + + # By default we want to include all dirs and exclude all files. + cmd += [ + '--include', + '*/', + '--exclude', + '*', + f'{self.src}/ba_data/', + f'{self.dst}/ba_data/', + ] + subprocess.run(cmd, check=True) + + if self.include_python_dylib: + self._sync_python_dylib() + + if self.include_shell_launcher: + self._sync_shell_launcher() + + def _sync_shell_launcher(self) -> None: + path = f'{self.dst}/ballisticakit' + + # For now this is so simple we just do an ad-hoc write each time; + # not worth setting up files and syncs. + if self.debug: + optstuff = 'export PYTHONDEVMODE=1\nexport PYTHONOPTIMIZE=0\n' + else: + optstuff = 'export PYTHONDEVMODE=0\nexport PYTHONOPTIMIZE=1\n' + + optnm = 'DEBUG' if self.debug else 'RELEASE' + with open(path, 'w', encoding='utf-8') as outfile: + outfile.write( + '#!/bin/sh\n' + '\n' + '# We should error if anything here errors.\n' + 'set -e\n' + '\n' + '# We want Python to use UTF-8 everywhere for consistency.\n' + '# (This will be the default in the future; see PEP 686).\n' + f'export PYTHONUTF8=1\n' + '\n' + f'# This is a Ballistica {optnm} build; set Python to match.\n' + f'{optstuff}' + '\n' + '# Run the app, forwarding along all arguments.\n' + '# Basically this does:\n' + '# import baenv; baenv.configure();' + ' import babase; babase.app.run().\n' + 'python3.11 ba_data/python/baenv.py $@\n' + ) + subprocess.run(['chmod', '+x', path], check=True) + + def _sync_python_dylib(self) -> None: + # pylint: disable=too-many-locals + from batools.featureset import FeatureSet + + # Note: we're technically not *syncing* quite so much as + # *constructing* here. + + dylib_staging_dir = f'{self.dst}/ba_data/python-dylib' + + # Name of our single shared library containing all our stuff. + soname = 'ballisticakit.so' + + # All featuresets in the project with binary modules. + bmodfeaturesets = { + f.name: f + for f in FeatureSet.get_all_for_project(self.projroot) + if f.has_python_binary_module + } + + # Map of featureset names (foo) to module filenames (_foo.so). + fsetmfilenames = { + f.name: f'{f.name_python_binary_module}.so' + for f in bmodfeaturesets.values() + } + + # Set of all module filenames (_foo.so, etc.) we should have. + fsetmfilenamevals = set(fsetmfilenames.values()) + + if not os.path.exists(dylib_staging_dir): + os.makedirs(dylib_staging_dir, exist_ok=True) + + # Create a symlink to our original built so. NOTE: Anyone + # building final app packages/etc. should replace this with the + # actual file. This is just for development. + + # FIXME - currently assuming our built .so lives one dir above + # our staging dir; should not be making that assumption. + built_so_path = f'{self.dst}/../{soname}' + staged_so_path = f'{dylib_staging_dir}/{soname}' + + if not os.path.islink(staged_so_path): + relpath = os.path.relpath(built_so_path, dylib_staging_dir) + subprocess.run(['ln', '-sf', relpath, staged_so_path], check=True) + + # Ok, now we want to create symlinks for each of our featureset + # Python modules. All of our stuff lives in the same .so and we + # can use symlinks to help Python find them all there. See the + # following: + # https://peps.python.org/pep-0489/#multiple-modules-in-one-library + for fsetname, featureset in bmodfeaturesets.items(): + if featureset.has_python_binary_module: + mfilename = fsetmfilenames[fsetname] + instpath = f'{dylib_staging_dir}/{mfilename}' + if not os.path.islink(instpath): + subprocess.run(['ln', '-sf', soname, instpath], check=True) + + # Lastly, blow away anything in that dir that's not something we + # just made (clears out featuresets that get renamed or + # disabled, etc). + fnames = os.listdir(dylib_staging_dir) + for fname in fnames: + if not fname in fsetmfilenamevals and fname != soname: + fpath = f'{dylib_staging_dir}/{fname}' + print(f"Pruning orphaned dylib path: '{fpath}'.") + subprocess.run(['rm', '-rf', fpath], check=True) + + def _sync_server_files(self) -> None: + assert self.serverdst is not None + assert self.debug is not None + modeval = 'debug' if self.debug else 'release' + + # NOTE: staging these directly from src; not build. + _stage_server_file( + projroot=self.projroot, + mode=modeval, + infilename=f'{self.projroot}/src/assets/server_package/' + 'ballisticakit_server.py', + outfilename=os.path.join( + self.serverdst, + 'ballisticakit_server.py' + if self.win_type is not None + else 'ballisticakit_server', + ), + ) + _stage_server_file( + projroot=self.projroot, + mode=modeval, + infilename=f'{self.projroot}/src/assets/server_package/README.txt', + outfilename=os.path.join(self.serverdst, 'README.txt'), + ) + _stage_server_file( + projroot=self.projroot, + mode=modeval, + infilename=f'{self.projroot}/src/assets/server_package/' + 'config_template.yaml', + outfilename=os.path.join(self.serverdst, 'config_template.yaml'), + ) + if self.win_type is not None: + fname = 'launch_ballisticakit_server.bat' + _stage_server_file( + projroot=self.projroot, + mode=modeval, + infilename=f'{self.projroot}/src/assets/server_package/{fname}', + outfilename=os.path.join(self.serverdst, fname), + ) -def md5sum(filename: str) -> str: - """Generate an md5sum given a filename.""" +def _filehash(filename: str) -> str: + """Generate a hash for a file.""" md5 = hashlib.md5() with open(filename, mode='rb') as infile: for buf in iter(partial(infile.read, 1024), b''): @@ -194,18 +597,9 @@ def md5sum(filename: str) -> str: return md5.hexdigest() -def _run(cmd: str, echo: bool = False) -> None: - """Run an os command; raise Exception on non-zero return value.""" - if echo: - print(cmd) - result = os.system(cmd) - if result != 0: - raise RuntimeError(f"Error running cmd: '{cmd}'.") - - def _write_payload_file(assets_root: str, full: bool) -> None: if not assets_root.endswith('/'): - assets_root = assets_root + '/' + assets_root = f'{assets_root}/' # Now construct a payload file if we have any files. file_list = [] @@ -222,226 +616,24 @@ def _write_payload_file(assets_root: str, full: bool) -> None: raise RuntimeError( f"Invalid filename (contains spaces): '{fpathshort}'" ) - payload_str += fpathshort + ' ' + md5sum(fpath) + '\n' + payload_str += f'{fpathshort} {_filehash(fpath)}\n' file_list.append(fpathshort) - payload_path = assets_root + '/payload_info' + payload_path = f'{assets_root}/payload_info' if file_list: - # Write the file count, whether this is a 'full' payload, and finally - # the file list. - payload_str = ( - str(len(file_list)) - + '\n' - + ('1' if full else '0') - + '\n' - + payload_str - ) + # Write the file count, whether this is a 'full' payload, and + # finally the file list. + fullstr = '1' if full else '0' + payload_str = f'{len(file_list)}\n{fullstr}\n{payload_str}' with open(payload_path, 'w', encoding='utf-8') as outfile: outfile.write(payload_str) else: - # Remove the payload file; this will cause the game to completely - # skip the payload processing step. + # Remove the payload file; this will cause the game to + # completely skip the payload processing step. if os.path.exists(payload_path): os.unlink(payload_path) -def _sync_windows_extras(cfg: Config) -> None: - # pylint: disable=too-many-branches - assert cfg.win_extras_src is not None - assert cfg.win_platform is not None - assert cfg.win_type is not None - if not os.path.isdir(cfg.win_extras_src): - raise RuntimeError( - "Win extras src dir not found: '{cfg.win_extras_src}'." - ) - - # Ok, lets do full syncs on each subdir we find so we - # properly delete anything in dst that disappeared from src. - # Lastly we'll sync over the remaining top level files. - # Note: technically it'll be possible to leave orphaned top level - # files in dst, so when building packages/etc. we should always start - # from scratch. - assert cfg.dst is not None - assert cfg.debug is not None - if cfg.debug: - pyd_rules = "--include '*_d.pyd'" - else: - pyd_rules = "--exclude '*_d.pyd' --include '*.pyd'" - - for dirname in ('DLLs', 'Lib'): - # EWW: seems windows python currently sets its path to ./lib but it - # comes with Lib. Windows is normally case-insensitive but this messes - # it up when running under WSL. Let's install it as lib for now. - dstdirname = 'lib' if dirname == 'Lib' else dirname - _run(f'mkdir -p "{cfg.dst}/{dstdirname}"') - cmd = ( - 'rsync --recursive --update --delete --delete-excluded ' - ' --prune-empty-dirs' - " --include '*.ico' --include '*.cat'" - f" --include '*.dll' {pyd_rules}" - " --include '*.py' --include '*." + OPT_PYC_SUFFIX + "'" - " --include '*/' --exclude '*' \"" - + os.path.join(cfg.win_extras_src, dirname) - + '/" ' - '"' + cfg.dst + '/' + dstdirname + '/"' - ) - _run(cmd) - - # Now sync the top level individual files that we want. - # We could technically copy everything over but this keeps staging - # dirs a bit tidier. - dbgsfx = '_d' if cfg.debug else '' - # Note: Below needs updating when Python version changes (currently 3.11) - toplevelfiles: list[str] = [f'python311{dbgsfx}.dll'] - - if cfg.win_type == 'win': - toplevelfiles += [ - 'libvorbis.dll', - 'libvorbisfile.dll', - 'ogg.dll', - 'OpenAL32.dll', - 'SDL2.dll', - ] - elif cfg.win_type == 'winserver': - toplevelfiles += [f'python{dbgsfx}.exe'] - - # Include debug dlls so folks without msvc can run them. - if cfg.debug: - if cfg.win_platform == 'x64': - toplevelfiles += [ - 'msvcp140d.dll', - 'vcruntime140d.dll', - 'vcruntime140_1d.dll', - 'ucrtbased.dll', - ] - else: - toplevelfiles += [ - 'msvcp140d.dll', - 'vcruntime140d.dll', - 'ucrtbased.dll', - ] - - # Include the runtime redistributables in release builds. - if not cfg.debug: - if cfg.win_platform == 'x64': - toplevelfiles.append('vc_redist.x64.exe') - elif cfg.win_platform == 'Win32': - toplevelfiles.append('vc_redist.x86.exe') - else: - raise RuntimeError(f'Invalid win_platform {cfg.win_platform}') - - cmd2 = ( - ['rsync', '--update'] - + [os.path.join(cfg.win_extras_src, f) for f in toplevelfiles] - + [cfg.dst + '/'] - ) - subprocess.run(cmd2, check=True) - - # If we're running under WSL we won't be able to launch these .exe files - # unless they're marked executable, so do that here. - # Update: gonna try simply setting this flag on the source side. - # _run(f'chmod +x {cfg.dst}/*.exe') - - -def _sync_pylib(cfg: Config) -> None: - assert cfg.pylib_src_name is not None - assert cfg.dst is not None - _run(f'mkdir -p "{cfg.dst}/pylib"') - cmd = ( - f'rsync --recursive --update --delete --delete-excluded ' - f' --prune-empty-dirs' - f" --include '*.py' --include '*.{OPT_PYC_SUFFIX}'" - f" --include '*/' --exclude '*'" - f' "{cfg.src}/{cfg.pylib_src_name}/" ' - f'"{cfg.dst}/pylib/"' - ) - _run(cmd) - - -def _sync_standard_game_data(cfg: Config) -> None: - assert cfg.dst is not None - _run('mkdir -p "' + cfg.dst + '/ba_data"') - cmd = ( - 'rsync --recursive --update --delete --delete-excluded' - ' --prune-empty-dirs' - ) - - if cfg.include_scripts: - cmd += ( - f" --include '*.py' --include '*.pem'" - f" --include '*.{OPT_PYC_SUFFIX}'" - ) - - if cfg.include_textures: - assert cfg.tex_suffix is not None - cmd += " --include '*" + cfg.tex_suffix + "'" - - if cfg.include_audio: - cmd += " --include '*.ogg'" - - if cfg.include_fonts: - cmd += " --include '*.fdata'" - - if cfg.include_json: - cmd += " --include '*.json'" - - if cfg.include_meshes: - cmd += " --include '*.bob'" - - if cfg.include_collision_meshes: - cmd += " --include '*.cob'" - - cmd += ( - " --include='*/' --exclude='*' \"" - + cfg.src - + '/ba_data/" "' - + cfg.dst - + '/ba_data/"' - ) - _run(cmd) - - -def _sync_server_files(cfg: Config) -> None: - assert cfg.serverdst is not None - assert cfg.debug is not None - modeval = 'debug' if cfg.debug else 'release' - - # NOTE: staging these directly from src; not build. - stage_server_file( - projroot=cfg.projroot, - mode=modeval, - infilename=f'{cfg.projroot}/src/assets/server_package/' - 'ballisticakit_server.py', - outfilename=os.path.join( - cfg.serverdst, - 'ballisticakit_server.py' - if cfg.win_type is not None - else 'ballisticakit_server', - ), - ) - stage_server_file( - projroot=cfg.projroot, - mode=modeval, - infilename=f'{cfg.projroot}/src/assets/server_package/README.txt', - outfilename=os.path.join(cfg.serverdst, 'README.txt'), - ) - stage_server_file( - projroot=cfg.projroot, - mode=modeval, - infilename=f'{cfg.projroot}/src/assets/server_package/' - 'config_template.yaml', - outfilename=os.path.join(cfg.serverdst, 'config_template.yaml'), - ) - if cfg.win_type is not None: - fname = 'launch_ballisticakit_server.bat' - stage_server_file( - projroot=cfg.projroot, - mode=modeval, - infilename=f'{cfg.projroot}/src/assets/server_package/{fname}', - outfilename=os.path.join(cfg.serverdst, fname), - ) - - def _write_if_changed( path: str, contents: str, make_executable: bool = False ) -> None: @@ -459,7 +651,7 @@ def _write_if_changed( subprocess.run(['chmod', '+x', path], check=True) -def stage_server_file( +def _stage_server_file( projroot: str, mode: str, infilename: str, outfilename: str ) -> None: """Stage files for the server environment with some filtering.""" @@ -528,45 +720,3 @@ def stage_server_file( _write_if_changed(outfilename, '\n'.join(lines) + '\n') else: raise RuntimeError(f"Unknown server file for staging: '{basename}'.") - - -def main(projroot: str, args: list[str] | None = None) -> None: - """Stage assets for a build.""" - - if args is None: - args = sys.argv - - cfg = Config(projroot) - cfg.parse_args(args) - - # Ok, now for every top level dir in src, come up with a nice single - # command to sync the needed subset of it to dst. - - # We can now use simple speedy timestamp based updates since - # we no longer have to try to preserve timestamps to get .pyc files - # to behave (hooray!) - - # Do our stripped down pylib dir for platforms that use that. - if cfg.include_pylib: - _sync_pylib(cfg) - else: - if cfg.dst is not None and os.path.isdir(cfg.dst + '/pylib'): - subprocess.run(['rm', '-rf', cfg.dst + '/pylib'], check=True) - - # Sync our server files if we're doing that. - if cfg.serverdst is not None: - _sync_server_files(cfg) - - # On windows we need to pull in some dlls and this and that - # (we also include a non-stripped-down set of python libs). - if cfg.win_extras_src is not None: - _sync_windows_extras(cfg) - - # Standard stuff in ba_data - _sync_standard_game_data(cfg) - - # On Android we need to build a payload file so it knows - # what to pull out of the apk. - if cfg.include_payload_file: - assert cfg.dst is not None - _write_payload_file(cfg.dst, cfg.is_payload_full) diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py index 87d24b69..52705cd5 100755 --- a/tools/batools/dummymodule.py +++ b/tools/batools/dummymodule.py @@ -937,7 +937,7 @@ def generate_dummy_modules(projroot: str) -> None: # Generate a dummy module for each featureset that has a binary module. featuresets = FeatureSet.get_all_for_project(project_root=projroot) - featuresets = [f for f in featuresets if f.has_native_python_module] + featuresets = [f for f in featuresets if f.has_python_binary_module] mnames: list[str] = [fs.name_python_binary_module for fs in featuresets] builddir = 'build/dummymodules' diff --git a/tools/batools/featureset.py b/tools/batools/featureset.py index 42351f5f..fd5dc394 100644 --- a/tools/batools/featureset.py +++ b/tools/batools/featureset.py @@ -54,7 +54,7 @@ class FeatureSet: # its C++ code. The build process will try to create dummy # modules for all native modules, so to avoid errors you must # tell it if you don't have one. - self.has_native_python_module = True + self.has_python_binary_module = True # If True, for feature-set 'foo_bar', the build system will # define a 'babase.app.foo_bar' attr which points to a lazy diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py index e84651d5..46475442 100644 --- a/tools/batools/pcommand.py +++ b/tools/batools/pcommand.py @@ -806,11 +806,13 @@ def efro_gradle() -> None: def stage_assets() -> None: """Stage assets for a build.""" - from batools.assetstaging import main + import batools.assetstaging from efro.error import CleanError try: - main(projroot=str(PROJROOT), args=sys.argv[2:]) + batools.assetstaging.stage_assets( + projroot=str(PROJROOT), args=sys.argv[2:] + ) except CleanError as exc: exc.pretty_print() sys.exit(1) diff --git a/tools/batools/pcommand2.py b/tools/batools/pcommand2.py index 4abbf51f..f90756e5 100644 --- a/tools/batools/pcommand2.py +++ b/tools/batools/pcommand2.py @@ -25,16 +25,48 @@ def gen_monolithic_register_modules() -> None: featuresets = FeatureSet.get_all_for_project(str(PROJROOT)) # Filter out ones without native modules. - featuresets = [f for f in featuresets if f.has_native_python_module] + featuresets = [f for f in featuresets if f.has_python_binary_module] pymodulenames = sorted(f.name_python_binary_module for f in featuresets) + def initname(mname: str) -> str: + # plus is a special case since we need to define that symbol + # ourself. + return f'DoPyInit_{mname}' if mname == '_baplus' else f'PyInit_{mname}' + extern_def_code = '\n'.join( - f'auto PyInit_{n}() -> PyObject*;' for n in pymodulenames + f'auto {initname(n)}() -> PyObject*;' for n in pymodulenames ) + py_register_code = '\n'.join( - f'PyImport_AppendInittab("{n}", &PyInit_{n});' for n in pymodulenames + f'PyImport_AppendInittab("{n}", &{initname(n)});' for n in pymodulenames ) + + if '_baplus' in pymodulenames: + init_plus_code = ( + '\n' + '// Slight hack: because we are currently building baplus as a' + ' static module\n' + '// and linking it in, symbols exported there (namely' + ' PyInit__baplus) do not\n' + '// seem to be available through us when we are compiled as' + ' a dynamic\n' + '// library. This leads to Python being unable to load baplus.' + ' While I\'m sure\n' + '// there is some way to get those symbols exported, I\'m worried' + ' it might be\n' + '// a messy platform-specific affair. So instead we\'re just' + ' defining that\n' + '// function here when baplus is present and forwarding it through' + ' to the\n' + '// static library version.\n' + 'extern "C" auto PyInit__baplus() -> PyObject* {\n' + ' return DoPyInit__baplus();\n' + '}\n' + ) + else: + init_plus_code = '' + base_code = """ // Released under the MIT License. See LICENSE for details. @@ -43,13 +75,12 @@ def gen_monolithic_register_modules() -> None: // THIS CODE IS AUTOGENERATED BY META BUILD; DO NOT EDIT BY HAND. + #include "ballistica/shared/ballistica.h" #include "ballistica/shared/python/python_sys.h" - #if BA_MONOLITHIC_BUILD extern "C" { ${EXTERN_DEF_CODE} } - #endif // BA_MONOLITHIC_BUILD namespace ballistica { @@ -58,15 +89,15 @@ def gen_monolithic_register_modules() -> None: /// binary modules get located as .so files on disk as per regular /// Python behavior. void MonolithicRegisterPythonModules() { - #if BA_MONOLITHIC_BUILD + if (g_buildconfig.monolithic_build()) { ${PY_REGISTER_CODE} - #else - FatalError( - "MonolithicRegisterPythonModules should not be called" - " in modular builds."); - #endif // BA_MONOLITHIC_BUILD + } else { + FatalError( + "MonolithicRegisterPythonModules should not be called" + " in modular builds."); + } } - + ${PY_INIT_PLUS} } // namespace ballistica #endif // BALLISTICA_CORE_MGEN_PYTHON_MODULES_MONOLITHIC_H_ @@ -74,7 +105,10 @@ def gen_monolithic_register_modules() -> None: out = ( textwrap.dedent(base_code) .replace('${EXTERN_DEF_CODE}', extern_def_code) - .replace('${PY_REGISTER_CODE}', textwrap.indent(py_register_code, ' ')) + .replace( + '${PY_REGISTER_CODE}', textwrap.indent(py_register_code, ' ') + ) + .replace('${PY_INIT_PLUS}', init_plus_code) .strip() + '\n' ) @@ -84,19 +118,6 @@ def gen_monolithic_register_modules() -> None: outfile.write(out) -def stage_server_file() -> None: - """Stage files for the server environment with some filtering.""" - from efro.error import CleanError - import batools.assetstaging - - if len(sys.argv) != 5: - raise CleanError('Expected 3 args (mode, infile, outfile).') - mode, infilename, outfilename = sys.argv[2], sys.argv[3], sys.argv[4] - batools.assetstaging.stage_server_file( - str(PROJROOT), mode, infilename, outfilename - ) - - def py_examine() -> None: """Run a python examination at a given point in a given file.""" import os @@ -244,7 +265,7 @@ def update_cmake_prefab_lib() -> None: ) suffix = '_server' if buildtype == 'server' else '_gui' target = ( - f'build/prefab/lib/{platform}{suffix}/{mode}/' f'libballistica_plus.a' + f'build/prefab/lib/{platform}{suffix}/{mode}/' f'libballisticaplus.a' ) # Build the target and then copy it to dst if it doesn't exist there yet @@ -252,7 +273,7 @@ def update_cmake_prefab_lib() -> None: subprocess.run(['make', target], check=True) libdir = os.path.join(builddir, 'prefablib') - libpath = os.path.join(libdir, 'libballistica_plus.a') + libpath = os.path.join(libdir, 'libballisticaplus.a') update = True time1 = os.path.getmtime(target) diff --git a/tools/batools/spinoff/_context.py b/tools/batools/spinoff/_context.py index 2a5eee3c..f32f31b1 100644 --- a/tools/batools/spinoff/_context.py +++ b/tools/batools/spinoff/_context.py @@ -788,7 +788,7 @@ class SpinoffContext: # Strip precompiled plus library out of the cmake file. text = replace_exact( text, - '${CMAKE_CURRENT_BINARY_DIR}/prefablib/libballistica_plus.a' + '${CMAKE_CURRENT_BINARY_DIR}/prefablib/libballisticaplus.a' ' ode ', 'ode ', label=src_path, diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py index db0082a3..35301f2a 100644 --- a/tools/efrotools/pybuild.py +++ b/tools/efrotools/pybuild.py @@ -714,42 +714,77 @@ def _patch_py_wreadlink_test() -> None: fname = 'Python/fileutils.c' txt = readfile(fname) + # Final fix for this problem. + # It seems that readlink() might be broken in android at the moment, + # returning an int while claiming it to be a ssize_t value. This makes + # the error case (-1) actually come out as 4294967295. When cast back + # to an int it is -1, so that's what we do. This should be fine to do + # even on a fixed version. txt = replace_exact( txt, ' res = readlink(cpath, cbuf, cbuf_len);\n', - ( - ' res = readlink(cpath, cbuf, cbuf_len);\n' - ' const wchar_t *path2 = path;\n' - ' int path2len = 0;\n' - ' while (*path2) {\n' - ' path2++;\n' - ' path2len++;\n' - ' }\n' - ' char dlog1[512];\n' - ' if (res >= 0) {\n' - ' snprintf(dlog1, sizeof(dlog1), "ValsA1 pathlen=%d slen=%d' - ' path=\'%s\'", path2len, strlen(cpath), cpath);\n' - ' } else {\n' - ' snprintf(dlog1, sizeof(dlog1), "ValsA2 pathlen=%d",' - ' path2len);\n' - ' }\n' - ' Py_BallisticaLowLevelDebugLog(dlog1);\n' - ), + ' res = (int)readlink(cpath, cbuf, cbuf_len);\n', ) - txt = replace_exact( - txt, - " cbuf[res] = '\\0'; /* buf will be null terminated */", - ( - ' char dlog[512];\n' - ' snprintf(dlog, sizeof(dlog), "ValsB res=%d resx=%X' - ' eq1=%d eq2=%d",' - ' (int)res, res, (int)(res == -1),' - ' (int)((size_t)res == cbuf_len));\n' - ' Py_BallisticaLowLevelDebugLog(dlog);\n' - " cbuf[res] = '\\0'; /* buf will be null terminated */" - ), - ) + # Verbose problem exploration: + # txt = replace_exact( + # txt, + # '#include // mbstowcs()\n', + # '#include // mbstowcs()\n' + # '#include \n', + # ) + + # txt = replace_exact(txt, ' Py_ssize_t res;\n', '') + + # txt = replace_exact( + # txt, + # ' res = readlink(cpath, cbuf, cbuf_len);\n', + # ( + # ' Py_ssize_t res = readlink(cpath, cbuf, cbuf_len);\n' + # ' Py_ssize_t res2 = readlink(cpath, cbuf, cbuf_len);\n' + # ' ssize_t res3 = readlink(cpath, cbuf, cbuf_len);\n' + # ' ssize_t res4 = readlinkat(AT_FDCWD, cpath, + # cbuf, cbuf_len);\n' + # ' int res5 = syscall(SYS_readlinkat, AT_FDCWD, cpath,' + # ' cbuf, cbuf_len);\n' + # ' ssize_t res6 = syscall(SYS_readlinkat, AT_FDCWD, cpath,' + # ' cbuf, cbuf_len);\n' + # ' char dlog[512];\n' + # ' snprintf(dlog, sizeof(dlog),' + # ' "res=%zd res2=%zd res3=%zd res4=%zd res5=%d res6=%zd"\n' + # ' " (res == -1)=%d (res2 == -1)=%d (res3 == -1)=%d' + # ' (res4 == -1)=%d (res5 == -1)=%d (res6 == -1)=%d",\n' + # ' res, res2, res3, res4, res5, res6,\n' + # ' (res == -1), (res2 == -1), (res3 == -1),' + # ' (res4 == -1), (res5 == -1), (res6 == -1));\n' + # ' Py_BallisticaLowLevelDebugLog(dlog);\n' + # '\n' + # ' char dlog1[512];\n' + # ' ssize_t st1;\n' + # ' Py_ssize_t st2;\n' + # ' snprintf(dlog1, sizeof(dlog1), + # "ValsA1 sz1=%zu sz2=%zu res=%zd' + # ' res_hex=%lX res_cmp=%d res_cmp_2=%d pathlen=%d slen=%d' + # ' path=\'%s\'", sizeof(st1), sizeof(st2), res,' + # ' res, (int)(res == -1), (int)((int)res == -1),' + # ' (int)wcslen(path), (int)strlen(cpath), cpath);\n' + # ' Py_BallisticaLowLevelDebugLog(dlog1);\n' + # ), + # ) + + # txt = replace_exact( + # txt, + # " cbuf[res] = '\\0'; /* buf will be null terminated */", + # ( + # ' char dlog[512];\n' + # ' snprintf(dlog, sizeof(dlog), "ValsB res=%d resx=%lX' + # ' eq1=%d eq2=%d",' + # ' (int)res, res, (int)(res == -1),' + # ' (int)((size_t)res == cbuf_len));\n' + # ' Py_BallisticaLowLevelDebugLog(dlog);\n' + # " cbuf[res] = '\\0'; /* buf will be null terminated */" + # ), + # ) writefile(fname, txt) diff --git a/tools/pcommand b/tools/pcommand index 094a4a5a..21c45233 100755 --- a/tools/pcommand +++ b/tools/pcommand @@ -114,7 +114,6 @@ from batools.pcommand import ( from batools.pcommand2 import ( gen_python_init_module, gen_monolithic_register_modules, - stage_server_file, py_examine, clean_orphaned_assets, win_ci_install_prereqs,