diff --git a/.efrocachemap b/.efrocachemap
index b2429eb8..15bf8e53 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -3995,50 +3995,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
- "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9c/ed/0ae26a2512696e003eb3f7dd1ee5",
- "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/8f/cc9774b5a4a4feaae3006ea0db82",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/17/70/eb50b7cc07116befdc0ad36960fc",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/44/48/3ecb935394935983e25e667d3819",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/82/10/7f94dfa5fd5fa3473394fce58977",
- "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/83/8e/68be1304b09685223a6e88202960",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4b/3f/3708b5d1839611d77a54e962059e",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/03/ce/4d5816d91764f05c90214427913b",
- "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/43/8b/2666b8e3fbd8d34544092ca45bf4",
- "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ab/2d/3673b348cbc00eb761fa5f23ffa2",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/29/ead51be015acb0b5803b07ed6f1e",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1d/8d/72f67643af92c32c82364827a430",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/54/02/1c83ca2ed283f71e2d73d59b684b",
- "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ea/1c/6a518c25ea6d4222dd65d44252a0",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/60/f77a0cd12b6c27a13126764a790c",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/69/59/6a345958e24e295547264baacf17",
- "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/19/0c/d44240c9cf46cccae04e65a7f434",
- "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/9c/7e/1ed6f76e5cdf290810e762d8cf43",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/19/4f/914677c28d694003cad9091f751f",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/ec/3f/c38f346059fcd19ef049ce3a253a",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/63/9dc4b8c56eaf6ee5259e4b60bc46",
- "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e3/25/692a46a2437c11055c58d95f0a10",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/8f/1f7fcebee0a16c8f843cabebba1c",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ec/c2/51a05d1066c208b3a17e8828238e",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a8/e3/88791ad2fbd654a60e6652e64554",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6b/06/86d995f8ea1b6e27ac3b9818e267",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f3/7d/b632f966f8ae8c39dc9015fb8eee",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e9/9e/39dd389564d0a14f01bdb54b82ce",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/7e/e865aa9e850350deefa7a826b89e",
- "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/1f/20ea75eb2a38a5aa740aebf68893",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/87/49cc3fb53218a2c19633551d0f38",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ac/38/4fc5345b0bbc0cc6965d510b2074",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b3/b0/c4f8ff30c9b62d2d0dbc926095fa",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ec/96/b22889b59ba3279627d8de232a23",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0e/6b/3b6758dd4b6b63ab9f666af9396a",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/8c/6fa15f798f6ca73d753571f8cd4b",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/2d/af/9cc4b56883bcd72f65b9a814c401",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/5a/18/c0ff4809d4550c794a516a320ed1",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/27/25/4b3bd86f0ad71b2ad29ea8a9837d",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/2f/1a/29105b0e71173826d2ea404982b6",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/b7/8a/3f0476b3981a630cc6ef8f1ec364",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/4d/32/9ffcdc35d17ac6c9e3bb2bfd0e3c",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/be/f0/8901dcd613fec9e58d02b08dce4b",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e3/c1/f79676f11b4f4778e26933ba42d7",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ad/15/3b9d4eb830dd3f65266b90544461",
+ "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3f/94/ce703438b67755c530ca90d7147e",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/57/49/aef9dbd58c9638d2f57c61fa9dee",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/53/73/7aee5551f9aa8c60bfa36ce86889",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8d/cd/61b3022a6a3620bb9a0d288a3a07",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/67/d3/7e7b3147c119bcf9edc62f271755",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/41/81/b4225f4df31a6bb482eee912d1e7",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8a/3e/9a284ee8d8cb75fef73f31c95a8f",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/a5/a2/69829de7375f981ac01464d1271c",
+ "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/8f/de/c40022e95c0d51e3ea7d68713ac8",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2a/e3/74cdea89e9606f525ae01f5fb860",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c2/b3/8f5c7076b293ce6a961b23321ebe",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/45/4b/43950780d121290a40cb444e86c4",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/74/6c/5c8a7cab586c813a07d5180e25c2",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ca/36/94c0d402ec38ffdd74cd165f51c3",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b7/a9/14ea5150ad61a43b5780975551ac",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/23/72/2bdc1106cdf2aa163a591e7c47b8",
+ "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1e/d9/5a7fd190b86683bef223c1cdb0b6",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/2e/f1/27b90831c9a25d2b9d06a266e931",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/17/ce/d3e33370c016fc8110e6dddd120f",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/32/b0/df61f7b69d796fbdf2aa0d11974b",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/42/31/a45e87e70e5d5232956586620f35",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/94/5e/6ac9534c08e600201b04d28a6069",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5f/5d/52e74182f459f5b7b55ae07b36c6",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7b/9d/367338622b31482a8ef87621f5aa",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a8/18/4dff9ab6b2529d258a2ee5607d0c",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/79/b9/e5e9d0f92f82f6cf35abf0c049cf",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4c/b4/2c65b84265e0b2d576317ca86e77",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d1/86/635b942e0837be18e6d9d859f2c4",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b0/f5/3da5acc926ca5e6c02307b8a16a6",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a3/51/d624ac150603304183ba23d369a2",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e2/51/5eb2216e4cdc531c500244316f1d",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b1/2b/c72179ed1d11ae7e11f429b90ae9",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/52/78/cca5ff0fcc837467cd23239537c7",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e7/6e/eb7102dd939e85172d729ba471b8",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e7/92/b7c90b5dbf614aa056cf467f73e0",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/cf/d9/52c1c145efa1a24ebf54dd71f691",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/31/15/ef849aeaf5a9cc2169e6ec4998c8",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/86/a7/def29184cd6e947469726ae382e1",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/5f/30/42705d0b580994d1345c34651170",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/2f/b1/6a6e8cf45fc5afa4085bf90a526f",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/7e/fb/e7eaee33c6196f9204a2997fc65c",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/5a/39/e00cddb91e8eca83b117235c1332",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/07/dd/6e331996e6e6907f05a21abc20a6",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/98/12/571b2160d69d42580e8f31fa6a8d"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 728f06e7..70701bdf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.7.7 (build 20796, api 7, 2022-09-09)
+### 1.7.7 (build 20798, api 7, 2022-09-09)
- Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread.
- Improved logging of missing playlist game types.
- Some ba.Lstr functionality can now be used in background threads.
diff --git a/assets/src/ba_data/python/._bainternal_sources_hash b/assets/src/ba_data/python/._bainternal_sources_hash
index 26863289..4f6f8751 100644
--- a/assets/src/ba_data/python/._bainternal_sources_hash
+++ b/assets/src/ba_data/python/._bainternal_sources_hash
@@ -1 +1 @@
-265405600297036989512577170988205019181
\ No newline at end of file
+64954916459680243860176377055051806996
\ No newline at end of file
diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py
index 870e9a6d..490f64f5 100644
--- a/assets/src/ba_data/python/ba/_bootstrap.py
+++ b/assets/src/ba_data/python/ba/_bootstrap.py
@@ -38,7 +38,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
- expected_build = 20796
+ expected_build = 20798
running_build: int = env['build_number']
if running_build != expected_build:
print(
diff --git a/ballisticacore-cmake/CMakeLists.txt b/ballisticacore-cmake/CMakeLists.txt
index 010064e4..0f58aa0a 100644
--- a/ballisticacore-cmake/CMakeLists.txt
+++ b/ballisticacore-cmake/CMakeLists.txt
@@ -202,14 +202,14 @@ add_executable(ballisticacore
${BA_SRC_ROOT}/ballistica/app/app.h
${BA_SRC_ROOT}/ballistica/app/app_config.cc
${BA_SRC_ROOT}/ballistica/app/app_config.h
- ${BA_SRC_ROOT}/ballistica/app/app_globals.cc
- ${BA_SRC_ROOT}/ballistica/app/app_globals.h
- ${BA_SRC_ROOT}/ballistica/app/headless_app.cc
- ${BA_SRC_ROOT}/ballistica/app/headless_app.h
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor.cc
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor.h
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor_headless.cc
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor_headless.h
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor_vr.cc
+ ${BA_SRC_ROOT}/ballistica/app/app_flavor_vr.h
${BA_SRC_ROOT}/ballistica/app/stress_test.cc
${BA_SRC_ROOT}/ballistica/app/stress_test.h
- ${BA_SRC_ROOT}/ballistica/app/vr_app.cc
- ${BA_SRC_ROOT}/ballistica/app/vr_app.h
${BA_SRC_ROOT}/ballistica/audio/al_sys.cc
${BA_SRC_ROOT}/ballistica/audio/al_sys.h
${BA_SRC_ROOT}/ballistica/audio/audio.cc
diff --git a/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj b/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj
index e262bb74..06cae3fd 100644
--- a/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj
+++ b/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj
@@ -193,14 +193,14 @@
-
-
-
-
+
+
+
+
+
+
-
-
diff --git a/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj.filters b/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj.filters
index 72b42275..0db00d48 100644
--- a/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj.filters
+++ b/ballisticacore-windows/Generic/BallisticaCoreGeneric.vcxproj.filters
@@ -13,16 +13,22 @@
ballistica\app
-
+
ballistica\app
-
+
ballistica\app
-
+
ballistica\app
-
+
+ ballistica\app
+
+
+ ballistica\app
+
+
ballistica\app
@@ -31,12 +37,6 @@
ballistica\app
-
- ballistica\app
-
-
- ballistica\app
-
ballistica\audio
diff --git a/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj b/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj
index e1c85303..ccbacee1 100644
--- a/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj
+++ b/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj
@@ -188,14 +188,14 @@
-
-
-
-
+
+
+
+
+
+
-
-
diff --git a/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj.filters b/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj.filters
index 72b42275..0db00d48 100644
--- a/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj.filters
+++ b/ballisticacore-windows/Headless/BallisticaCoreHeadless.vcxproj.filters
@@ -13,16 +13,22 @@
ballistica\app
-
+
ballistica\app
-
+
ballistica\app
-
+
ballistica\app
-
+
+ ballistica\app
+
+
+ ballistica\app
+
+
ballistica\app
@@ -31,12 +37,6 @@
ballistica\app
-
- ballistica\app
-
-
- ballistica\app
-
ballistica\audio
diff --git a/src/ballistica/app/app.cc b/src/ballistica/app/app.cc
index 7c2843e1..55ef7da0 100644
--- a/src/ballistica/app/app.cc
+++ b/src/ballistica/app/app.cc
@@ -2,394 +2,17 @@
#include "ballistica/app/app.h"
-#include "ballistica/app/stress_test.h"
-#include "ballistica/core/thread.h"
-#include "ballistica/game/game.h"
-#include "ballistica/graphics/graphics_server.h"
-#include "ballistica/graphics/renderer.h"
-#include "ballistica/input/device/touch_input.h"
-#include "ballistica/input/input.h"
-#include "ballistica/networking/network_reader.h"
-#include "ballistica/networking/networking.h"
-#include "ballistica/networking/telnet_server.h"
-#include "ballistica/python/python.h"
+#include
namespace ballistica {
-App::App(Thread* thread)
- : thread_(thread), stress_test_(std::make_unique()) {
- assert(g_app == nullptr);
- g_app = this;
-
- // So anyone who needs to use the 'main' thread id can get at it...
- Thread::UpdateMainThreadID();
-
- // We modify some app behavior when run under the server manager.
- auto* envval = getenv("BA_SERVER_WRAPPER_MANAGED");
- server_wrapper_managed_ = (envval && strcmp(envval, "1") == 0);
-}
-
-void App::PostInit() {
- // If we've got a nice themed hardware cursor, show it.
- // Otherwise, hide the hardware cursor; we'll draw it in software.
- // (need to run this in postinit because SDL/etc. may not be inited yet
- // as of App::App()).
- g_platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
-}
-
-auto App::ManagesEventLoop() const -> bool {
- // We have 2 redundant values for essentially the same thing;
- // should get rid of IsEventPushMode() once we've created
- // App subclasses for our various platforms.
- return !g_platform->IsEventPushMode();
-}
-
-void App::RunRenderUpkeepCycle() {
- // This should only be used in cases where the OS is handling the event loop.
- assert(!ManagesEventLoop());
- if (ManagesEventLoop()) {
- return;
+App::App(int argc_in, char** argv_in)
+ : argc{argc_in}, argv{argv_in}, main_thread_id{std::this_thread::get_id()} {
+ // Enable extra timing logs via env var.
+ const char* debug_timing_env = getenv("BA_DEBUG_TIMING");
+ if (debug_timing_env != nullptr && !strcmp(debug_timing_env, "1")) {
+ debug_timing = true;
}
-
- // Pump thread messages (we're being driven by frame-draw callbacks
- // so this is the only place that it gets done at).
- thread()->RunEventLoop(true); // Single pass only.
-
- // Now do the general app event cycle for whoever needs to process things.
- RunEvents();
-}
-
-void App::RebuildLostGLContext() {
- assert(InMainThread());
- assert(g_graphics_server);
- if (g_graphics_server) {
- g_graphics_server->RebuildLostContext();
- }
-}
-
-void App::DrawFrame(bool during_resize) {
- assert(InMainThread());
-
- // It's possible to receive frames before we're ready to draw.
- if (!g_graphics_server || !g_graphics_server->renderer()) {
- return;
- }
-
- millisecs_t starttime = GetRealTime();
-
- // A resize-draw event means that we're drawing due to a window resize.
- // In this case we ignore regular draw events for a short while
- // afterwards which makes resizing smoother.
- // FIXME: should figure out the *correct* way to handle this;
- // I believe the underlying cause here is some sort of context contention
- // across threads.
- if (during_resize) {
- last_resize_draw_event_time_ = starttime;
- } else {
- if (starttime - last_resize_draw_event_time_ < (1000 / 30)) {
- return;
- }
- }
- g_graphics_server->TryRender();
- RunRenderUpkeepCycle();
-}
-
-void App::SetScreenResolution(float width, float height) {
- assert(InMainThread());
- if (!HeadlessMode()) {
- g_graphics_server->VideoResize(width, height);
- }
-}
-
-void App::PushShutdownCompleteCall() {
- thread()->PushCall([this] { ShutdownComplete(); });
-}
-
-void App::ShutdownComplete() {
- assert(InMainThread());
- assert(g_platform);
-
- done_ = true;
-
- // Kill our own event loop (or tell the OS to kill its).
- if (ManagesEventLoop()) {
- thread()->Quit();
- } else {
- g_platform->QuitApp();
- }
-}
-
-void App::RunEvents() {
- // there's probably a better place for this...
- stress_test_->Update();
-
- // Give platforms a chance to pump/handle their own events.
- // FIXME: now that we have app class overrides, platform should really
- // not be doing event handling. (need to fix rift build).
- g_platform->RunEvents();
-}
-
-void App::UpdatePauseResume() {
- if (actually_paused_) {
- // Unpause if no one wants pause.
- if (!sys_paused_app_) {
- OnResume();
- actually_paused_ = false;
- }
- } else {
- // Pause if anyone wants.
- if (sys_paused_app_) {
- OnPause();
- actually_paused_ = true;
- }
- }
-}
-
-void App::OnPause() {
- assert(InMainThread());
-
- // Avoid reading gyro values for a short time to avoid hitches when restored.
- g_graphics->SetGyroEnabled(false);
-
- // IMPORTANT: Any on-pause related stuff that threads need to do must
- // be done from registered pause-callbacks. If we instead push runnables
- // to them from here they may or may not be called before the thread
- // is actually paused.
-
- Thread::SetThreadsPaused(true);
-
- assert(g_networking);
- g_networking->Pause();
-
- assert(g_network_reader);
- if (g_network_reader) {
- g_network_reader->Pause();
- }
-
- if (g_app_globals->telnet_server) {
- g_app_globals->telnet_server->Pause();
- }
-
- g_platform->OnAppPause();
-}
-
-void App::OnResume() {
- assert(InMainThread());
- last_app_resume_time_ = GetRealTime();
- Thread::SetThreadsPaused(false);
-
- g_platform->OnAppResume();
- g_networking->Resume();
- g_network_reader->Resume();
-
- if (g_app_globals->telnet_server) {
- g_app_globals->telnet_server->Resume();
- }
-
- // Also let the Python layer do what it needs to
- // (starting/stopping music, etc.).
- g_python->PushObjCall(Python::ObjID::kHandleAppResumeCall);
- g_game->PushOnAppResumeCall();
-
- g_graphics->SetGyroEnabled(true);
-
- // When resuming from a paused state, we may want to
- // pause whatever game was running when we last were active.
- // TODO(efro): we should make this smarter so it doesn't happen if
- // we're in a network game or something that we can't pause;
- // bringing up the menu doesn't really accomplish anything there.
- if (g_app_globals->should_pause) {
- g_app_globals->should_pause = false;
-
- // If we've been completely backgrounded,
- // send a menu-press command to the game; this will
- // bring up a pause menu if we're in the game/etc.
- g_game->PushMainMenuPressCall(nullptr);
- }
-}
-
-auto App::GetProductPrice(const std::string& product) -> std::string {
- std::scoped_lock lock(product_prices_mutex_);
- auto i = product_prices_.find(product);
- if (i == product_prices_.end()) {
- return "";
- } else {
- return i->second;
- }
-}
-
-void App::SetProductPrice(const std::string& product,
- const std::string& price) {
- std::scoped_lock lock(product_prices_mutex_);
- product_prices_[product] = price;
-}
-
-void App::PauseApp() {
- assert(InMainThread());
- Platform::DebugLog("PauseApp@"
- + std::to_string(Platform::GetCurrentMilliseconds()));
- assert(!sys_paused_app_);
- sys_paused_app_ = true;
- UpdatePauseResume();
-}
-
-void App::ResumeApp() {
- assert(InMainThread());
- Platform::DebugLog("ResumeApp@"
- + std::to_string(Platform::GetCurrentMilliseconds()));
- assert(sys_paused_app_);
- sys_paused_app_ = false;
- UpdatePauseResume();
-}
-
-void App::DidFinishRenderingFrame(FrameDef* frame) {}
-
-void App::PrimeEventPump() {
- assert(!ManagesEventLoop());
-
- // Pump events manually until a screen gets created.
- // At that point we use frame-draws to drive our event loop.
- while (!g_graphics_server->initial_screen_created()) {
- g_main_thread->RunEventLoop(true);
- Platform::SleepMS(1);
- }
-}
-
-#pragma mark Push-Calls
-
-void App::PushShowOnlineScoreUICall(const std::string& show,
- const std::string& game,
- const std::string& game_version) {
- thread()->PushCall([show, game, game_version] {
- assert(InMainThread());
- g_platform->ShowOnlineScoreUI(show, game, game_version);
- });
-}
-
-void App::PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
- const std::string& telnet_password) {
- thread()->PushCall([port, telnet_port, enable_telnet, telnet_password] {
- assert(InMainThread());
- // Kick these off if they don't exist.
- // (do we want to support changing ports on existing ones?)
- if (g_network_reader == nullptr) {
- new NetworkReader(port);
- }
- if (g_app_globals->telnet_server == nullptr && enable_telnet) {
- new TelnetServer(telnet_port);
- assert(g_app_globals->telnet_server);
- if (telnet_password.empty()) {
- g_app_globals->telnet_server->SetPassword(nullptr);
- } else {
- g_app_globals->telnet_server->SetPassword(telnet_password.c_str());
- }
- }
- });
-}
-
-void App::PushPurchaseAckCall(const std::string& purchase,
- const std::string& order_id) {
- thread()->PushCall(
- [purchase, order_id] { g_platform->PurchaseAck(purchase, order_id); });
-}
-
-void App::PushGetScoresToBeatCall(const std::string& level,
- const std::string& config,
- void* py_callback) {
- thread()->PushCall([level, config, py_callback] {
- assert(InMainThread());
- g_platform->GetScoresToBeat(level, config, py_callback);
- });
-}
-
-void App::PushPurchaseCall(const std::string& item) {
- thread()->PushCall([item] {
- assert(InMainThread());
- g_platform->Purchase(item);
- });
-}
-
-void App::PushRestorePurchasesCall() {
- thread()->PushCall([] {
- assert(InMainThread());
- g_platform->RestorePurchases();
- });
-}
-
-void App::PushOpenURLCall(const std::string& url) {
- thread()->PushCall([url] { g_platform->OpenURL(url); });
-}
-
-void App::PushGetFriendScoresCall(const std::string& game,
- const std::string& game_version, void* data) {
- thread()->PushCall([game, game_version, data] {
- g_platform->GetFriendScores(game, game_version, data);
- });
-}
-
-void App::PushSubmitScoreCall(const std::string& game,
- const std::string& game_version, int64_t score) {
- thread()->PushCall([game, game_version, score] {
- g_platform->SubmitScore(game, game_version, score);
- });
-}
-
-void App::PushAchievementReportCall(const std::string& achievement) {
- thread()->PushCall(
- [achievement] { g_platform->ReportAchievement(achievement); });
-}
-
-void App::PushStringEditCall(const std::string& name, const std::string& value,
- int max_chars) {
- thread()->PushCall([name, value, max_chars] {
- static millisecs_t last_edit_time = 0;
- millisecs_t t = GetRealTime();
-
- // Ignore if too close together.
- // (in case second request comes in before first takes effect).
- if (t - last_edit_time < 1000) {
- return;
- }
- last_edit_time = t;
- assert(InMainThread());
- g_platform->EditText(name, value, max_chars);
- });
-}
-
-void App::PushSetStressTestingCall(bool enable, int player_count) {
- thread()->PushCall([this, enable, player_count] {
- stress_test_->SetStressTesting(enable, player_count);
- });
-}
-
-void App::PushResetAchievementsCall() {
- thread()->PushCall([] { g_platform->ResetAchievements(); });
-}
-
-void App::OnBootstrapComplete() {
- assert(InMainThread());
- assert(g_input);
-
- if (!HeadlessMode()) {
- // On desktop systems we just assume keyboard input exists and add it
- // immediately.
- if (g_platform->IsRunningOnDesktop()) {
- g_input->PushCreateKeyboardInputDevices();
- }
-
- // On non-tv, non-desktop, non-vr systems, create a touchscreen input.
- if (!g_platform->IsRunningOnTV() && !IsVRMode()
- && !g_platform->IsRunningOnDesktop()) {
- g_input->CreateTouchInput();
- }
- }
-}
-
-void App::PushCursorUpdate(bool vis) {
- thread()->PushCall([vis] {
- assert(InMainThread());
- g_platform->SetHardwareCursorVisible(vis);
- });
}
} // namespace ballistica
diff --git a/src/ballistica/app/app.h b/src/ballistica/app/app.h
index 2f6052af..c3d3546c 100644
--- a/src/ballistica/app/app.h
+++ b/src/ballistica/app/app.h
@@ -3,144 +3,90 @@
#ifndef BALLISTICA_APP_APP_H_
#define BALLISTICA_APP_APP_H_
-#include
#include
#include
+#include
#include
+#include
-#include "ballistica/app/stress_test.h"
#include "ballistica/ballistica.h"
namespace ballistica {
-/// Our high level app interface module.
-/// It runs in the main thread and is what platform wrappers
-/// should primarily interact with.
+// The first thing the engine does is allocate an instance of this as g_globals.
+// As much as possible, previously static/global values should be moved to here,
+// ideally as a temporary measure until they can be placed as non-static members
+// in the proper classes.
+// Any use of non-trivial global/static values such as class instances should be
+// avoided since it can introduce ambiguities during init and teardown.
+// For more explanation, see the 'Static and Global Variables' section in the
+// Google C++ Style Guide.
class App {
public:
- explicit App(Thread* thread);
+ App(int argc, char** argv);
- /// This gets run after the constructor completes.
- /// Any setup that may trigger a virtual method/etc. should go here.
- auto PostInit() -> void;
+ /// Program argument count (on applicable platforms).
+ int argc{};
- /// Return whether this class runs its own event loop.
- /// If true, BallisticaMain() will continuously ask the app for events
- /// until the app is quit, at which point BallisticaMain() returns.
- /// If false, BallisticaMain returns immediately and it is assumed
- /// that the OS handles the app lifecycle and pushes events to the app
- /// via callbacks/etc.
- auto ManagesEventLoop() const -> bool;
+ /// Program argument values (on applicable platforms).
+ char** argv{};
- /// Called for non-event-loop apps to give them an opportunity to
- /// ensure they are self-sustaining. For instance, an app relying on
- /// frame-draws for its main thread event processing may need to
- /// manually pump events until frame rendering begins.
- virtual auto PrimeEventPump() -> void;
+ bool threads_paused{};
+ std::unordered_map node_types;
+ std::unordered_map node_types_by_id;
+ std::unordered_map node_message_types;
+ std::vector node_message_formats;
+ bool workspaces_in_use{};
+ bool replay_open{};
+ std::vector pausable_threads;
+ TouchInput* touch_input{};
+ std::string console_startup_messages;
+ std::mutex log_mutex;
+ std::string log;
+ bool put_log{};
+ bool log_full{};
+ int master_server_source{0};
+ int session_count{};
+ bool shutting_down{};
+ bool have_incentivized_ad{true};
+ bool should_pause{};
+ TelnetServer* telnet_server{};
+ Console* console{};
+ bool reset_vr_orientation{};
+ bool user_ran_commands{};
+ V1AccountType account_type{V1AccountType::kInvalid};
+ bool remote_server_accepting_connections{true};
+ std::string exec_command;
+ std::string user_agent_string{"BA_USER_AGENT_UNSET (" BA_PLATFORM_STRING ")"};
+ int return_value{};
+ bool debug_timing{};
+ std::thread::id main_thread_id{};
+ bool is_bootstrapped{};
+ bool args_handled{};
+ std::string user_config_dir;
+ bool started_suicide{};
- /// Handle any pending OS events.
- /// On normal graphical builds this is triggered by RunRenderUpkeepCycle();
- /// timer intervals for headless builds, etc.
- /// Should process any pending OS events, etc.
- virtual auto RunEvents() -> void;
+ // Maximum time in milliseconds to buffer game input/output before sending
+ // it over the network.
+ int buffer_time{0};
- // These should be called by the window, view-controller, sdl,
- // or whatever is driving the app. They must be called from the main thread.
+ // How often we send dynamics resync messages.
+ int dynamics_sync_time{500};
- /// Should be called on mobile when the app is backgrounded.
- /// Pauses threads, closes network sockets, etc.
- auto PauseApp() -> void;
+ // How many steps we sample for each bucket.
+ int delay_bucket_samples{60};
- auto paused() const -> bool { return actually_paused_; }
-
- /// Should be called on mobile when the app is foregrounded.
- /// Spins threads back up, re-opens network sockets, etc.
- auto ResumeApp() -> void;
-
- /// The last time the app was resumed (uses GetRealTime() value).
- auto last_app_resume_time() const -> millisecs_t {
- return last_app_resume_time_;
- }
-
- /// Should be called when the window/screen resolution changes.
- auto SetScreenResolution(float width, float height) -> void;
-
- /// Should be called if the platform detects the GL context was lost.
- auto RebuildLostGLContext() -> void;
-
- /// Attempt to draw a frame.
- auto DrawFrame(bool during_resize = false) -> void;
-
- /// Used on platforms where our main thread event processing is driven by
- /// frame-draw commands given to us. This should be called after drawing
- /// a frame in order to bring game state up to date and process OS events.
- auto RunRenderUpkeepCycle() -> void;
-
- /// Called by the graphics-server when drawing completes for a frame.
- virtual auto DidFinishRenderingFrame(FrameDef* frame) -> void;
-
- /// Return the price of an IAP product as a human-readable string,
- /// or an empty string if not found.
- /// FIXME: move this to platform.
- auto GetProductPrice(const std::string& product) -> std::string;
- auto SetProductPrice(const std::string& product, const std::string& price)
- -> void;
-
- auto done() const -> bool { return done_; }
-
- /// Whether we're running under ballisticacore_server.py
- /// (affects some app behavior).
- auto server_wrapper_managed() const -> bool {
- return server_wrapper_managed_;
- }
-
- virtual auto OnBootstrapComplete() -> void;
-
- // Deferred calls that can be made from other threads.
-
- auto PushCursorUpdate(bool vis) -> void;
- auto PushShowOnlineScoreUICall(const std::string& show,
- const std::string& game,
- const std::string& game_version) -> void;
- auto PushGetFriendScoresCall(const std::string& game,
- const std::string& game_version, void* data)
- -> void;
- auto PushSubmitScoreCall(const std::string& game,
- const std::string& game_version, int64_t score)
- -> void;
- auto PushAchievementReportCall(const std::string& achievement) -> void;
- auto PushGetScoresToBeatCall(const std::string& level,
- const std::string& config, void* py_callback)
- -> void;
- auto PushOpenURLCall(const std::string& url) -> void;
- auto PushStringEditCall(const std::string& name, const std::string& value,
- int max_chars) -> void;
- auto PushSetStressTestingCall(bool enable, int player_count) -> void;
- auto PushPurchaseCall(const std::string& item) -> void;
- auto PushRestorePurchasesCall() -> void;
- auto PushResetAchievementsCall() -> void;
- auto PushPurchaseAckCall(const std::string& purchase,
- const std::string& order_id) -> void;
- auto PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
- const std::string& telnet_password) -> void;
- auto PushShutdownCompleteCall() -> void;
- auto thread() const -> Thread* { return thread_; }
-
- private:
- auto UpdatePauseResume() -> void;
- auto OnPause() -> void;
- auto OnResume() -> void;
- auto ShutdownComplete() -> void;
- Thread* thread_{};
- bool done_{};
- bool server_wrapper_managed_{};
- bool sys_paused_app_{};
- bool actually_paused_{};
- std::unique_ptr stress_test_;
- millisecs_t last_resize_draw_event_time_{};
- millisecs_t last_app_resume_time_{};
- std::unordered_map product_prices_;
- std::mutex product_prices_mutex_;
+ bool vr_mode{g_buildconfig.vr_build()};
+ millisecs_t real_time{};
+ millisecs_t last_real_time_ticks{};
+ std::mutex real_time_mutex;
+ std::mutex thread_name_map_mutex;
+ std::unordered_map thread_name_map;
+#if BA_DEBUG_BUILD
+ std::mutex object_list_mutex;
+ Object* object_list_first{};
+ int object_count{0};
+#endif
};
} // namespace ballistica
diff --git a/src/ballistica/app/app_flavor.cc b/src/ballistica/app/app_flavor.cc
new file mode 100644
index 00000000..37d59fc7
--- /dev/null
+++ b/src/ballistica/app/app_flavor.cc
@@ -0,0 +1,395 @@
+// Released under the MIT License. See LICENSE for details.
+
+#include "ballistica/app/app_flavor.h"
+
+#include "ballistica/app/stress_test.h"
+#include "ballistica/core/thread.h"
+#include "ballistica/game/game.h"
+#include "ballistica/graphics/graphics_server.h"
+#include "ballistica/graphics/renderer.h"
+#include "ballistica/input/device/touch_input.h"
+#include "ballistica/input/input.h"
+#include "ballistica/networking/network_reader.h"
+#include "ballistica/networking/networking.h"
+#include "ballistica/networking/telnet_server.h"
+#include "ballistica/python/python.h"
+
+namespace ballistica {
+
+AppFlavor::AppFlavor(Thread* thread)
+ : thread_(thread), stress_test_(std::make_unique()) {
+ // assert(g_app_flavor == nullptr);
+ // g_app_flavor = this;
+
+ // We modify some app behavior when run under the server manager.
+ auto* envval = getenv("BA_SERVER_WRAPPER_MANAGED");
+ server_wrapper_managed_ = (envval && strcmp(envval, "1") == 0);
+}
+
+void AppFlavor::PostInit() {
+ // If we've got a nice themed hardware cursor, show it.
+ // Otherwise, hide the hardware cursor; we'll draw it in software.
+ // (need to run this in postinit because SDL/etc. may not be inited yet
+ // as of AppFlavor::AppFlavor()).
+ g_platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
+}
+
+auto AppFlavor::ManagesEventLoop() const -> bool {
+ // We have 2 redundant values for essentially the same thing;
+ // should get rid of IsEventPushMode() once we've created
+ // AppFlavor subclasses for our various platforms.
+ return !g_platform->IsEventPushMode();
+}
+
+void AppFlavor::RunRenderUpkeepCycle() {
+ // This should only be used in cases where the OS is handling the event loop.
+ assert(!ManagesEventLoop());
+ if (ManagesEventLoop()) {
+ return;
+ }
+
+ // Pump thread messages (we're being driven by frame-draw callbacks
+ // so this is the only place that it gets done at).
+ thread()->RunEventLoop(true); // Single pass only.
+
+ // Now do the general app event cycle for whoever needs to process things.
+ RunEvents();
+}
+
+void AppFlavor::RebuildLostGLContext() {
+ assert(InMainThread());
+ assert(g_graphics_server);
+ if (g_graphics_server) {
+ g_graphics_server->RebuildLostContext();
+ }
+}
+
+void AppFlavor::DrawFrame(bool during_resize) {
+ assert(InMainThread());
+
+ // It's possible to receive frames before we're ready to draw.
+ if (!g_graphics_server || !g_graphics_server->renderer()) {
+ return;
+ }
+
+ millisecs_t starttime = GetRealTime();
+
+ // A resize-draw event means that we're drawing due to a window resize.
+ // In this case we ignore regular draw events for a short while
+ // afterwards which makes resizing smoother.
+ // FIXME: should figure out the *correct* way to handle this;
+ // I believe the underlying cause here is some sort of context contention
+ // across threads.
+ if (during_resize) {
+ last_resize_draw_event_time_ = starttime;
+ } else {
+ if (starttime - last_resize_draw_event_time_ < (1000 / 30)) {
+ return;
+ }
+ }
+ g_graphics_server->TryRender();
+ RunRenderUpkeepCycle();
+}
+
+void AppFlavor::SetScreenResolution(float width, float height) {
+ assert(InMainThread());
+ if (!HeadlessMode()) {
+ g_graphics_server->VideoResize(width, height);
+ }
+}
+
+void AppFlavor::PushShutdownCompleteCall() {
+ thread()->PushCall([this] { ShutdownComplete(); });
+}
+
+void AppFlavor::ShutdownComplete() {
+ assert(InMainThread());
+ assert(g_platform);
+
+ done_ = true;
+
+ // Kill our own event loop (or tell the OS to kill its).
+ if (ManagesEventLoop()) {
+ thread()->Quit();
+ } else {
+ g_platform->QuitApp();
+ }
+}
+
+void AppFlavor::RunEvents() {
+ // there's probably a better place for this...
+ stress_test_->Update();
+
+ // Give platforms a chance to pump/handle their own events.
+ // FIXME: now that we have app class overrides, platform should really
+ // not be doing event handling. (need to fix rift build).
+ g_platform->RunEvents();
+}
+
+void AppFlavor::UpdatePauseResume() {
+ if (actually_paused_) {
+ // Unpause if no one wants pause.
+ if (!sys_paused_app_) {
+ OnResume();
+ actually_paused_ = false;
+ }
+ } else {
+ // Pause if anyone wants.
+ if (sys_paused_app_) {
+ OnPause();
+ actually_paused_ = true;
+ }
+ }
+}
+
+void AppFlavor::OnPause() {
+ assert(InMainThread());
+
+ // Avoid reading gyro values for a short time to avoid hitches when restored.
+ g_graphics->SetGyroEnabled(false);
+
+ // IMPORTANT: Any on-pause related stuff that threads need to do must
+ // be done from registered pause-callbacks. If we instead push runnables
+ // to them from here they may or may not be called before the thread
+ // is actually paused.
+
+ Thread::SetThreadsPaused(true);
+
+ assert(g_networking);
+ g_networking->Pause();
+
+ assert(g_network_reader);
+ if (g_network_reader) {
+ g_network_reader->Pause();
+ }
+
+ if (g_app->telnet_server) {
+ g_app->telnet_server->Pause();
+ }
+
+ g_platform->OnAppPause();
+}
+
+void AppFlavor::OnResume() {
+ assert(InMainThread());
+ last_app_resume_time_ = GetRealTime();
+ Thread::SetThreadsPaused(false);
+
+ g_platform->OnAppResume();
+ g_networking->Resume();
+ g_network_reader->Resume();
+
+ if (g_app->telnet_server) {
+ g_app->telnet_server->Resume();
+ }
+
+ // Also let the Python layer do what it needs to
+ // (starting/stopping music, etc.).
+ g_python->PushObjCall(Python::ObjID::kHandleAppResumeCall);
+ g_game->PushOnAppResumeCall();
+
+ g_graphics->SetGyroEnabled(true);
+
+ // When resuming from a paused state, we may want to
+ // pause whatever game was running when we last were active.
+ // TODO(efro): we should make this smarter so it doesn't happen if
+ // we're in a network game or something that we can't pause;
+ // bringing up the menu doesn't really accomplish anything there.
+ if (g_app->should_pause) {
+ g_app->should_pause = false;
+
+ // If we've been completely backgrounded,
+ // send a menu-press command to the game; this will
+ // bring up a pause menu if we're in the game/etc.
+ g_game->PushMainMenuPressCall(nullptr);
+ }
+}
+
+auto AppFlavor::GetProductPrice(const std::string& product) -> std::string {
+ std::scoped_lock lock(product_prices_mutex_);
+ auto i = product_prices_.find(product);
+ if (i == product_prices_.end()) {
+ return "";
+ } else {
+ return i->second;
+ }
+}
+
+void AppFlavor::SetProductPrice(const std::string& product,
+ const std::string& price) {
+ std::scoped_lock lock(product_prices_mutex_);
+ product_prices_[product] = price;
+}
+
+void AppFlavor::PauseApp() {
+ assert(InMainThread());
+ Platform::DebugLog("PauseApp@"
+ + std::to_string(Platform::GetCurrentMilliseconds()));
+ assert(!sys_paused_app_);
+ sys_paused_app_ = true;
+ UpdatePauseResume();
+}
+
+void AppFlavor::ResumeApp() {
+ assert(InMainThread());
+ Platform::DebugLog("ResumeApp@"
+ + std::to_string(Platform::GetCurrentMilliseconds()));
+ assert(sys_paused_app_);
+ sys_paused_app_ = false;
+ UpdatePauseResume();
+}
+
+void AppFlavor::DidFinishRenderingFrame(FrameDef* frame) {}
+
+void AppFlavor::PrimeEventPump() {
+ assert(!ManagesEventLoop());
+
+ // Pump events manually until a screen gets created.
+ // At that point we use frame-draws to drive our event loop.
+ while (!g_graphics_server->initial_screen_created()) {
+ thread()->RunEventLoop(true);
+ Platform::SleepMS(1);
+ }
+}
+
+#pragma mark Push-Calls
+
+void AppFlavor::PushShowOnlineScoreUICall(const std::string& show,
+ const std::string& game,
+ const std::string& game_version) {
+ thread()->PushCall([show, game, game_version] {
+ assert(InMainThread());
+ g_platform->ShowOnlineScoreUI(show, game, game_version);
+ });
+}
+
+void AppFlavor::PushNetworkSetupCall(int port, int telnet_port,
+ bool enable_telnet,
+ const std::string& telnet_password) {
+ thread()->PushCall([port, telnet_port, enable_telnet, telnet_password] {
+ assert(InMainThread());
+ // Kick these off if they don't exist.
+ // (do we want to support changing ports on existing ones?)
+ if (g_network_reader == nullptr) {
+ new NetworkReader(port);
+ }
+ if (g_app->telnet_server == nullptr && enable_telnet) {
+ new TelnetServer(telnet_port);
+ assert(g_app->telnet_server);
+ if (telnet_password.empty()) {
+ g_app->telnet_server->SetPassword(nullptr);
+ } else {
+ g_app->telnet_server->SetPassword(telnet_password.c_str());
+ }
+ }
+ });
+}
+
+void AppFlavor::PushPurchaseAckCall(const std::string& purchase,
+ const std::string& order_id) {
+ thread()->PushCall(
+ [purchase, order_id] { g_platform->PurchaseAck(purchase, order_id); });
+}
+
+void AppFlavor::PushGetScoresToBeatCall(const std::string& level,
+ const std::string& config,
+ void* py_callback) {
+ thread()->PushCall([level, config, py_callback] {
+ assert(InMainThread());
+ g_platform->GetScoresToBeat(level, config, py_callback);
+ });
+}
+
+void AppFlavor::PushPurchaseCall(const std::string& item) {
+ thread()->PushCall([item] {
+ assert(InMainThread());
+ g_platform->Purchase(item);
+ });
+}
+
+void AppFlavor::PushRestorePurchasesCall() {
+ thread()->PushCall([] {
+ assert(InMainThread());
+ g_platform->RestorePurchases();
+ });
+}
+
+void AppFlavor::PushOpenURLCall(const std::string& url) {
+ thread()->PushCall([url] { g_platform->OpenURL(url); });
+}
+
+void AppFlavor::PushGetFriendScoresCall(const std::string& game,
+ const std::string& game_version,
+ void* data) {
+ thread()->PushCall([game, game_version, data] {
+ g_platform->GetFriendScores(game, game_version, data);
+ });
+}
+
+void AppFlavor::PushSubmitScoreCall(const std::string& game,
+ const std::string& game_version,
+ int64_t score) {
+ thread()->PushCall([game, game_version, score] {
+ g_platform->SubmitScore(game, game_version, score);
+ });
+}
+
+void AppFlavor::PushAchievementReportCall(const std::string& achievement) {
+ thread()->PushCall(
+ [achievement] { g_platform->ReportAchievement(achievement); });
+}
+
+void AppFlavor::PushStringEditCall(const std::string& name,
+ const std::string& value, int max_chars) {
+ thread()->PushCall([name, value, max_chars] {
+ static millisecs_t last_edit_time = 0;
+ millisecs_t t = GetRealTime();
+
+ // Ignore if too close together.
+ // (in case second request comes in before first takes effect).
+ if (t - last_edit_time < 1000) {
+ return;
+ }
+ last_edit_time = t;
+ assert(InMainThread());
+ g_platform->EditText(name, value, max_chars);
+ });
+}
+
+void AppFlavor::PushSetStressTestingCall(bool enable, int player_count) {
+ thread()->PushCall([this, enable, player_count] {
+ stress_test_->SetStressTesting(enable, player_count);
+ });
+}
+
+void AppFlavor::PushResetAchievementsCall() {
+ thread()->PushCall([] { g_platform->ResetAchievements(); });
+}
+
+void AppFlavor::OnBootstrapComplete() {
+ assert(InMainThread());
+ assert(g_input);
+
+ if (!HeadlessMode()) {
+ // On desktop systems we just assume keyboard input exists and add it
+ // immediately.
+ if (g_platform->IsRunningOnDesktop()) {
+ g_input->PushCreateKeyboardInputDevices();
+ }
+
+ // On non-tv, non-desktop, non-vr systems, create a touchscreen input.
+ if (!g_platform->IsRunningOnTV() && !IsVRMode()
+ && !g_platform->IsRunningOnDesktop()) {
+ g_input->CreateTouchInput();
+ }
+ }
+}
+
+void AppFlavor::PushCursorUpdate(bool vis) {
+ thread()->PushCall([vis] {
+ assert(InMainThread());
+ g_platform->SetHardwareCursorVisible(vis);
+ });
+}
+
+} // namespace ballistica
diff --git a/src/ballistica/app/app_flavor.h b/src/ballistica/app/app_flavor.h
new file mode 100644
index 00000000..14688fb4
--- /dev/null
+++ b/src/ballistica/app/app_flavor.h
@@ -0,0 +1,147 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_APP_APP_FLAVOR_H_
+#define BALLISTICA_APP_APP_FLAVOR_H_
+
+#include
+#include
+#include
+#include
+
+#include "ballistica/app/stress_test.h"
+#include "ballistica/ballistica.h"
+
+namespace ballistica {
+
+/// Defines our high level app behavior.
+class AppFlavor {
+ public:
+ explicit AppFlavor(Thread* thread);
+
+ /// Should be run after the instance is created and assigned.
+ /// Any setup that may trigger virtual methods or lookups via global
+ /// should go here.
+ auto PostInit() -> void;
+
+ /// Return whether this class runs its own event loop.
+ /// If true, BallisticaMain() will continuously ask the app for events
+ /// until the app is quit, at which point BallisticaMain() returns.
+ /// If false, BallisticaMain returns immediately and it is assumed
+ /// that the OS handles the app lifecycle and pushes events to the app
+ /// via callbacks/etc.
+ auto ManagesEventLoop() const -> bool;
+
+ /// Called for non-event-loop apps to give them an opportunity to
+ /// ensure they are self-sustaining. For instance, an app relying on
+ /// frame-draws for its main thread event processing may need to
+ /// manually pump events until frame rendering begins.
+ virtual auto PrimeEventPump() -> void;
+
+ /// Handle any pending OS events.
+ /// On normal graphical builds this is triggered by RunRenderUpkeepCycle();
+ /// timer intervals for headless builds, etc.
+ /// Should process any pending OS events, etc.
+ virtual auto RunEvents() -> void;
+
+ // These should be called by the window, view-controller, sdl,
+ // or whatever is driving the app. They must be called from the main thread.
+
+ /// Should be called on mobile when the app is backgrounded.
+ /// Pauses threads, closes network sockets, etc.
+ auto PauseApp() -> void;
+
+ auto paused() const -> bool { return actually_paused_; }
+
+ /// Should be called on mobile when the app is foregrounded.
+ /// Spins threads back up, re-opens network sockets, etc.
+ auto ResumeApp() -> void;
+
+ /// The last time the app was resumed (uses GetRealTime() value).
+ auto last_app_resume_time() const -> millisecs_t {
+ return last_app_resume_time_;
+ }
+
+ /// Should be called when the window/screen resolution changes.
+ auto SetScreenResolution(float width, float height) -> void;
+
+ /// Should be called if the platform detects the GL context was lost.
+ auto RebuildLostGLContext() -> void;
+
+ /// Attempt to draw a frame.
+ auto DrawFrame(bool during_resize = false) -> void;
+
+ /// Used on platforms where our main thread event processing is driven by
+ /// frame-draw commands given to us. This should be called after drawing
+ /// a frame in order to bring game state up to date and process OS events.
+ auto RunRenderUpkeepCycle() -> void;
+
+ /// Called by the graphics-server when drawing completes for a frame.
+ virtual auto DidFinishRenderingFrame(FrameDef* frame) -> void;
+
+ /// Return the price of an IAP product as a human-readable string,
+ /// or an empty string if not found.
+ /// FIXME: move this to platform.
+ auto GetProductPrice(const std::string& product) -> std::string;
+ auto SetProductPrice(const std::string& product, const std::string& price)
+ -> void;
+
+ auto done() const -> bool { return done_; }
+
+ /// Whether we're running under ballisticacore_server.py
+ /// (affects some app behavior).
+ auto server_wrapper_managed() const -> bool {
+ return server_wrapper_managed_;
+ }
+
+ virtual auto OnBootstrapComplete() -> void;
+
+ // Deferred calls that can be made from other threads.
+
+ auto PushCursorUpdate(bool vis) -> void;
+ auto PushShowOnlineScoreUICall(const std::string& show,
+ const std::string& game,
+ const std::string& game_version) -> void;
+ auto PushGetFriendScoresCall(const std::string& game,
+ const std::string& game_version, void* data)
+ -> void;
+ auto PushSubmitScoreCall(const std::string& game,
+ const std::string& game_version, int64_t score)
+ -> void;
+ auto PushAchievementReportCall(const std::string& achievement) -> void;
+ auto PushGetScoresToBeatCall(const std::string& level,
+ const std::string& config, void* py_callback)
+ -> void;
+ auto PushOpenURLCall(const std::string& url) -> void;
+ auto PushStringEditCall(const std::string& name, const std::string& value,
+ int max_chars) -> void;
+ auto PushSetStressTestingCall(bool enable, int player_count) -> void;
+ auto PushPurchaseCall(const std::string& item) -> void;
+ auto PushRestorePurchasesCall() -> void;
+ auto PushResetAchievementsCall() -> void;
+ auto PushPurchaseAckCall(const std::string& purchase,
+ const std::string& order_id) -> void;
+ auto PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
+ const std::string& telnet_password) -> void;
+ auto PushShutdownCompleteCall() -> void;
+ auto thread() const -> Thread* { return thread_; }
+
+ private:
+ auto UpdatePauseResume() -> void;
+ auto OnPause() -> void;
+ auto OnResume() -> void;
+ auto ShutdownComplete() -> void;
+ Thread* thread_{};
+ bool done_{};
+ bool server_wrapper_managed_{};
+ bool sys_paused_app_{};
+ bool actually_paused_{};
+ std::unique_ptr stress_test_;
+ millisecs_t last_resize_draw_event_time_{};
+ millisecs_t last_app_resume_time_{};
+ std::unordered_map product_prices_;
+ std::mutex product_prices_mutex_;
+};
+
+} // namespace ballistica
+
+#endif // BALLISTICA_APP_APP_FLAVOR_H_
diff --git a/src/ballistica/app/headless_app.cc b/src/ballistica/app/app_flavor_headless.cc
similarity index 70%
rename from src/ballistica/app/headless_app.cc
rename to src/ballistica/app/app_flavor_headless.cc
index 4da24f9e..2e83bc05 100644
--- a/src/ballistica/app/headless_app.cc
+++ b/src/ballistica/app/app_flavor_headless.cc
@@ -1,7 +1,7 @@
// Released under the MIT License. See LICENSE for details.
#if BA_HEADLESS_BUILD
-#include "ballistica/app/headless_app.h"
+#include "ballistica/app/app_flavor_headless.h"
#include "ballistica/ballistica.h"
@@ -9,13 +9,13 @@ namespace ballistica {
// We could technically use the vanilla App class here since we're not
// changing anything.
-HeadlessApp::HeadlessApp(Thread* thread) : App(thread) {
+AppFlavorHeadless::AppFlavorHeadless(Thread* thread) : AppFlavor(thread) {
// Handle a few misc things like stress-test updates.
// (SDL builds set up a similar timer so we need to also).
// This can probably go away at some point.
this->thread()->NewTimer(10, true, NewLambdaRunnable([this] {
- assert(g_app);
- g_app->RunEvents();
+ assert(g_app_flavor);
+ g_app_flavor->RunEvents();
}));
}
diff --git a/src/ballistica/app/app_flavor_headless.h b/src/ballistica/app/app_flavor_headless.h
new file mode 100644
index 00000000..2dead6c3
--- /dev/null
+++ b/src/ballistica/app/app_flavor_headless.h
@@ -0,0 +1,20 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_APP_APP_FLAVOR_HEADLESS_H_
+#define BALLISTICA_APP_APP_FLAVOR_HEADLESS_H_
+#if BA_HEADLESS_BUILD
+
+#include "ballistica/app/app_flavor.h"
+#include "ballistica/core/thread.h"
+
+namespace ballistica {
+
+class AppFlavorHeadless : public AppFlavor {
+ public:
+ explicit AppFlavorHeadless(Thread* thread);
+};
+
+} // namespace ballistica
+
+#endif // BA_HEADLESS_BUILD
+#endif // BALLISTICA_APP_APP_FLAVOR_HEADLESS_H_
diff --git a/src/ballistica/app/vr_app.cc b/src/ballistica/app/app_flavor_vr.cc
similarity index 78%
rename from src/ballistica/app/vr_app.cc
rename to src/ballistica/app/app_flavor_vr.cc
index 2fa3569f..129d8b51 100644
--- a/src/ballistica/app/vr_app.cc
+++ b/src/ballistica/app/app_flavor_vr.cc
@@ -1,7 +1,7 @@
// Released under the MIT License. See LICENSE for details.
#if BA_VR_BUILD
-#include "ballistica/app/vr_app.h"
+#include "ballistica/app/app_flavor_vr.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
@@ -10,9 +10,9 @@
namespace ballistica {
-VRApp::VRApp(Thread* thread) : App(thread) {}
+AppFlavorVR::AppFlavorVR(Thread* thread) : AppFlavor(thread) {}
-auto VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state)
+auto AppFlavorVR::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state)
-> void {
thread()->PushCall([this, state] {
// Convert this to a full hands state, adding in some simple elbow
@@ -36,11 +36,11 @@ auto VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state)
});
}
-auto VRApp::VRSetDrawDimensions(int w, int h) -> void {
+auto AppFlavorVR::VRSetDrawDimensions(int w, int h) -> void {
g_graphics_server->VideoResize(w, h);
}
-void VRApp::VRPreDraw() {
+void AppFlavorVR::VRPreDraw() {
if (!g_graphics_server || !g_graphics_server->renderer()) {
return;
}
@@ -57,7 +57,7 @@ void VRApp::VRPreDraw() {
}
}
-auto VRApp::VRPostDraw() -> void {
+auto AppFlavorVR::VRPostDraw() -> void {
assert(InMainThread());
if (!g_graphics_server || !g_graphics_server->renderer()) {
return;
@@ -69,15 +69,15 @@ auto VRApp::VRPostDraw() -> void {
RunRenderUpkeepCycle();
}
-auto VRApp::VRSetHead(float tx, float ty, float tz, float yaw, float pitch,
- float roll) -> void {
+auto AppFlavorVR::VRSetHead(float tx, float ty, float tz, float yaw,
+ float pitch, float roll) -> void {
assert(InMainThread());
Renderer* renderer = g_graphics_server->renderer();
if (renderer == nullptr) return;
renderer->VRSetHead(tx, ty, tz, yaw, pitch, roll);
}
-auto VRApp::VRSetHands(const VRHandsState& state) -> void {
+auto AppFlavorVR::VRSetHands(const VRHandsState& state) -> void {
assert(InMainThread());
// Pass this along to the renderer (in this same thread) for drawing
@@ -90,10 +90,10 @@ auto VRApp::VRSetHands(const VRHandsState& state) -> void {
g_game->PushVRHandsState(state);
}
-auto VRApp::VRDrawEye(int eye, float yaw, float pitch, float roll, float tan_l,
- float tan_r, float tan_b, float tan_t, float eye_x,
- float eye_y, float eye_z, int viewport_x, int viewport_y)
- -> void {
+auto AppFlavorVR::VRDrawEye(int eye, float yaw, float pitch, float roll,
+ float tan_l, float tan_r, float tan_b, float tan_t,
+ float eye_x, float eye_y, float eye_z,
+ int viewport_x, int viewport_y) -> void {
if (!g_graphics_server || !g_graphics_server->renderer()) {
return;
}
diff --git a/src/ballistica/app/vr_app.h b/src/ballistica/app/app_flavor_vr.h
similarity index 64%
rename from src/ballistica/app/vr_app.h
rename to src/ballistica/app/app_flavor_vr.h
index 2b1a7111..e0620275 100644
--- a/src/ballistica/app/vr_app.h
+++ b/src/ballistica/app/app_flavor_vr.h
@@ -1,15 +1,15 @@
// Released under the MIT License. See LICENSE for details.
-#ifndef BALLISTICA_APP_VR_APP_H_
-#define BALLISTICA_APP_VR_APP_H_
+#ifndef BALLISTICA_APP_APP_FLAVOR_VR_H_
+#define BALLISTICA_APP_APP_FLAVOR_VR_H_
#if BA_VR_BUILD
-#include "ballistica/app/app.h"
+#include "ballistica/app/app_flavor.h"
namespace ballistica {
-class VRApp : public App {
+class AppFlavorVR : public AppFlavor {
public:
/// For passing in state of Daydream remote (and maybe gear vr?..).
struct VRSimpleRemoteState {
@@ -19,14 +19,15 @@ class VRApp : public App {
float r2 = 0.0f;
};
- /// Return g_app as a VRApp. (assumes it actually is one).
- static auto get() -> VRApp* {
- assert(g_app != nullptr);
- assert(dynamic_cast(g_app) == static_cast(g_app));
- return static_cast(g_app);
+ /// Return g_app_flavor as a AppFlavorVR. (assumes it actually is one).
+ static auto get() -> AppFlavorVR* {
+ assert(g_app_flavor != nullptr);
+ assert(dynamic_cast(g_app_flavor)
+ == static_cast(g_app_flavor));
+ return static_cast(g_app_flavor);
}
- explicit VRApp(Thread* thread);
+ explicit AppFlavorVR(Thread* thread);
auto PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) -> void;
auto VRSetDrawDimensions(int w, int h) -> void;
auto VRPreDraw() -> void;
@@ -46,4 +47,4 @@ class VRApp : public App {
} // namespace ballistica
#endif // BA_VR_BUILD
-#endif // BALLISTICA_APP_VR_APP_H_
+#endif // BALLISTICA_APP_APP_FLAVOR_VR_H_
diff --git a/src/ballistica/app/app_globals.cc b/src/ballistica/app/app_globals.cc
deleted file mode 100644
index 96d019b7..00000000
--- a/src/ballistica/app/app_globals.cc
+++ /dev/null
@@ -1,18 +0,0 @@
-// Released under the MIT License. See LICENSE for details.
-
-#include "ballistica/app/app_globals.h"
-
-#include
-
-namespace ballistica {
-
-AppGlobals::AppGlobals(int argc_in, char** argv_in)
- : argc{argc_in}, argv{argv_in}, main_thread_id{std::this_thread::get_id()} {
- // Enable extra timing logs via env var.
- const char* debug_timing_env = getenv("BA_DEBUG_TIMING");
- if (debug_timing_env != nullptr && !strcmp(debug_timing_env, "1")) {
- debug_timing = true;
- }
-}
-
-} // namespace ballistica
diff --git a/src/ballistica/app/app_globals.h b/src/ballistica/app/app_globals.h
deleted file mode 100644
index e65d510a..00000000
--- a/src/ballistica/app/app_globals.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Released under the MIT License. See LICENSE for details.
-
-#ifndef BALLISTICA_APP_APP_GLOBALS_H_
-#define BALLISTICA_APP_APP_GLOBALS_H_
-
-#include
-#include
-#include
-#include
-#include
-
-#include "ballistica/ballistica.h"
-
-namespace ballistica {
-
-// The first thing the engine does is allocate an instance of this as g_globals.
-// As much as possible, previously static/global values should be moved to here,
-// ideally as a temporary measure until they can be placed as non-static members
-// in the proper classes.
-// Any use of non-trivial global/static values such as class instances should be
-// avoided since it can introduce ambiguities during init and teardown.
-// For more explanation, see the 'Static and Global Variables' section in the
-// Google C++ Style Guide.
-class AppGlobals {
- public:
- AppGlobals(int argc, char** argv);
-
- /// Program argument count (on applicable platforms).
- int argc{};
-
- /// Program argument values (on applicable platforms).
- char** argv{};
-
- bool threads_paused{};
- std::unordered_map node_types;
- std::unordered_map node_types_by_id;
- std::unordered_map node_message_types;
- std::vector node_message_formats;
- bool workspaces_in_use{};
- bool replay_open{};
- std::vector pausable_threads;
- TouchInput* touch_input{};
- std::string console_startup_messages;
- std::mutex log_mutex;
- std::string log;
- bool put_log{};
- bool log_full{};
- int master_server_source{0};
- int session_count{};
- bool shutting_down{};
- bool have_incentivized_ad{true};
- bool should_pause{};
- TelnetServer* telnet_server{};
- Console* console{};
- bool reset_vr_orientation{};
- bool user_ran_commands{};
- V1AccountType account_type{V1AccountType::kInvalid};
- bool remote_server_accepting_connections{true};
- std::string exec_command;
- std::string user_agent_string{"BA_USER_AGENT_UNSET (" BA_PLATFORM_STRING ")"};
- int return_value{};
- bool debug_timing{};
- std::thread::id main_thread_id{};
- bool is_bootstrapped{};
- bool args_handled{};
- std::string user_config_dir;
- bool started_suicide{};
-
- // Maximum time in milliseconds to buffer game input/output before sending
- // it over the network.
- int buffer_time{0};
-
- // How often we send dynamics resync messages.
- int dynamics_sync_time{500};
-
- // How many steps we sample for each bucket.
- int delay_bucket_samples{60};
-
- bool vr_mode{g_buildconfig.vr_build()};
- millisecs_t real_time{};
- millisecs_t last_real_time_ticks{};
- std::mutex real_time_mutex;
- std::mutex thread_name_map_mutex;
- std::unordered_map thread_name_map;
-#if BA_DEBUG_BUILD
- std::mutex object_list_mutex;
- Object* object_list_first{};
- int object_count{0};
-#endif
-};
-
-} // namespace ballistica
-
-#endif // BALLISTICA_APP_APP_GLOBALS_H_
diff --git a/src/ballistica/app/headless_app.h b/src/ballistica/app/headless_app.h
deleted file mode 100644
index 660b7213..00000000
--- a/src/ballistica/app/headless_app.h
+++ /dev/null
@@ -1,20 +0,0 @@
-// Released under the MIT License. See LICENSE for details.
-
-#ifndef BALLISTICA_APP_HEADLESS_APP_H_
-#define BALLISTICA_APP_HEADLESS_APP_H_
-#if BA_HEADLESS_BUILD
-
-#include "ballistica/app/app.h"
-#include "ballistica/core/thread.h"
-
-namespace ballistica {
-
-class HeadlessApp : public App {
- public:
- explicit HeadlessApp(Thread* thread);
-};
-
-} // namespace ballistica
-
-#endif // BA_HEADLESS_BUILD
-#endif // BALLISTICA_APP_HEADLESS_APP_H_
diff --git a/src/ballistica/audio/audio_server.cc b/src/ballistica/audio/audio_server.cc
index 598bbc2e..5e3f4727 100644
--- a/src/ballistica/audio/audio_server.cc
+++ b/src/ballistica/audio/audio_server.cc
@@ -2,7 +2,7 @@
#include "ballistica/audio/audio_server.h"
-#include "ballistica/app/app_globals.h"
+#include "ballistica/app/app.h"
#include "ballistica/audio/al_sys.h"
#include "ballistica/audio/audio.h"
#include "ballistica/audio/audio_source.h"
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index adad6424..9e8a4227 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -4,7 +4,7 @@
#include