diff --git a/.efrocachemap b/.efrocachemap
index 2c79de44..8be752eb 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -4072,50 +4072,50 @@
"build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/18/4b/787a9267e17be3c49966072581a5",
"build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/20/f6/4ce9bc3c1f3732f6adf8237fbe9b",
"build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/a5/30/9058181df0b1255bf6950cbc7813",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/8c/ed/211ad9931645ec21344cc09943cf",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/71/df/89649ebeee912d3671994700ca98",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3a/55/2e767201ff0210baf2aab336b962",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/96/b7/14b9ff3221e0643c5808408889a0",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/96/a6/67826a50b92ef1b97c9350357bde",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/1a/00/5f26c8e62d546190ef1a0e5d86c1",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e9/d9/c37edc30a6d87dc6a3fff69cd69d",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f0/96/eafa605033a621f0024ce23e8506",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/e7/0c/e5d1f7f76463dab3a593c7e39d28",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/23/98/40563ec129aeae3ae29fce9a4098",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/19/db/025a2e4224285384ca5138b528b9",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3b/04/f7f76fbbb71c51d398999b91d962",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/ce/65/019b91c6590778d7e2f32a6dc86d",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/70/5d/74932199660c0faa295fc8b331e5",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e1/37/98c5e9424f0c180db6aa16a0a2e9",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/c9/3f/3dc59e05f156376638a59e606954",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/6b/2b/6a98ef5411ea98d0190aae33ca8a",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/65/82/defe87b3e240bbabd8194beb71a2",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/39/10/6e8db73db72f65b094401dc66972",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/45/5d/a65990370464ca038cd0a1be680a",
- "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3c/a8/5487ece199be8717cead6de157e3",
- "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/9d/71/77a0ae15f3216c51da44ccefeb87",
- "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/8c/de/a04c75afaee10276fedbd6dc12dd",
- "build/prefab/lib/linux_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/c1/45/f2143d53646891111b648a6cc014",
- "build/prefab/lib/linux_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4c/0c/2afa4f64c6030ba37cacdb62fb4d",
- "build/prefab/lib/linux_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/d2/0a/0d4b99f583a4e7a55761c5493c06",
- "build/prefab/lib/linux_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/5e/ff/b8cd37fe6d5fa5df6a7bc6103ecc",
- "build/prefab/lib/linux_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/84/e3/815f8ba4a1c0ff4b01e44bd95682",
- "build/prefab/lib/mac_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/10/98/48262f1e81f07154c28cfec87ee9",
- "build/prefab/lib/mac_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/6e/97/e8029482a29578a9f6cc15813265",
- "build/prefab/lib/mac_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/5d/ac/fbe75dcd9f4a4461552cdaf8a3be",
- "build/prefab/lib/mac_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/94/ec/cce65d8f6f76e240597b073f17e7",
- "build/prefab/lib/mac_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/a9/15/89ec9af80cf169f837ea6c3c365d",
- "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/e1/d8/0949e33c04570cae7dafaf906770",
- "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/91/c5/f1c4dcdd2233d6ffc280db77ec99",
- "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/20/e5/3b83e186b63a95d704c69a6b5b76",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/bf/fa/f79dc4a7ca33a8cca9df0547eef4",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/9e/3c/09855cda12586dbba55884a259b0",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/47/f1/3eadde3dcabd1b0b9be32a7f247f",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/f3/f6/51991b000385d553f35563df4190",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/a3/7d/cae2bd2e4daf11f90e73937170fc",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/1e/cc/ab11419f2d792b8738605a1f3a24",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/11/eb/55affc8d77ccac52c0b8fd128ad7",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/5a/57/2df7d185467abcca2287a4410ee8",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/35/07/d6fdff6fe4bd1e69a223c3a620aa",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/3f/67/cdaf89a3ae87c92e7a69ead533d1",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/16/a7/857b4c971a3129c69ec7daa1d0bc",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0e/65/21ae4a44d94cbd82e7f3a3a122b5",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/62/a4/5f233d592052188accbe49ab0ec2",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2c/dd/417f3aefe03828172469ed81630b",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2e/97/df7ecde6a7e82fd4a579cce8bae7",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/83/f7/dbf7a4c6a63ae84f0e0eff4974c3",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/57/1e/8208bc0c7de0def0e7cc399b412b",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/54/1f/f718b7159d58a8af4ab14caa9d06",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/78/f2/59cd0bcd802c0a977a3352ec8be4",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/d7/09/709476d32c9d060225c8717366bc",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/e9/9a/4a1c14fd9ef8d96aa1052242671c",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/04/87/e2d9488fa7d0e44fcd2ae386ab5d",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/24/6f/c8a72d8010e6e3e721d64e1a80c7",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/9d/6a/806d3b173c7739a04cad0df3cb32",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/e0/70/aaf9248064e99fef9b934b52f486",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/91/0c/6e58dac0732e20de8536fa187a16",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/8a/95/23c7dadc9d94aabef250b352a5cd",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/c9/b8/afeaa6080be3c45430f73900bbcf",
+ "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/d9/56/b0a16db98621500b473844ec39d6",
+ "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/57/f9/f304c67d9958574b6b35de2ad13d",
+ "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/04/55/a41ad832c081ff421039827d16c1",
+ "build/prefab/lib/linux_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/65/a7/2008753f3dbe890a730ca081ee8f",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/a1/1f/cb01b3a6773cd8fc1a3ed1c320c5",
+ "build/prefab/lib/linux_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/49/a7/b65d9e50fc94a82e88e5802020b4",
+ "build/prefab/lib/linux_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/25/28/a1657fe9f97172fee036f8f31173",
+ "build/prefab/lib/linux_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/5c/5b/2edf7105dcd7a653fa449af25bab",
+ "build/prefab/lib/mac_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/c9/02/670a183eed30429ebd3c61a26229",
+ "build/prefab/lib/mac_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/7d/50/17bb34179dd958f1e3563634dab5",
+ "build/prefab/lib/mac_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/3f/9d/30372ce093efd265ac89955ef774",
+ "build/prefab/lib/mac_arm64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/ca/89/4d9fb94ed0ec2e5cc36f40908f34",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/cd/87/606884c0a1d45fbfc57db6c635cb",
+ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/88/1a/1ea680450c3c9bc97147df4bf99d",
+ "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/91/b3/31b980ca2a000871408cbc7923e3",
+ "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/67/d1/8b98ad3d0d67bf6bc23c2c9167cf",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/6d/bf/7b03106aec7089c1ff956aee1569",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/06/df/2cfcaec212dcb94432a247a631e7",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/e8/60/503824952252ee34eedfb779aa0e",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/28/1a/dfdeaffd5d7f997679ba534117db",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/47/6c/d9fa447b2fae36bfaa6ec1344a66",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b0/c3/0f4e4ab477affd0451e44476ad0a",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/c9/a3/3fd02b8024684d98a361e3c0ec70",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/1c/20/a80e4f7eea1631ed3da2a1279340",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa",
"src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/ea/6a/6a4721b144e5e297b542d2a0eea2",
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 4f60d122..cf48990b 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -135,6 +135,7 @@
apptime
apptimer
apptimers
+ apptimesecs
apputils
archbase
archivepath
@@ -2595,6 +2596,7 @@
sirplus
sitebuiltins
skey
+ sleepsecs
sline
slist
slists
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aeda0363..f0275112 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.7.20 (build 21107, api 8, 2023-06-14)
+### 1.7.20 (build 21108, api 8, 2023-06-14)
- This seems like a good time for a `refactoring` release in anticipation of
changes coming in 1.8. Basically this means that a lot of things will be
@@ -321,6 +321,12 @@
would draw incorrectly.
- (build 21106) Fixed an issue where in-game ping would always display green no
matter how bad the ping was.
+- (build 21107) Upped internal display-timer resolution from milliseconds to
+ microseconds.
+- (build 21107) Finished implementing new scheduling system for headless mode.
+ This should fix the issue where 1.7.20 servers would have 100ms of lag by
+ default. Server performance should now be equal to or better than 1.7.19.
+ Please holler if not.
### 1.7.19 (build 20997, api 7, 2023-01-19)
diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
index 3df10246..884c233c 100644
--- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
@@ -92,6 +92,7 @@
apptime
apptimer
apptimers
+ apptimesecs
archbase
archos
argindex
@@ -1499,6 +1500,7 @@
sincestr
sisssssssss
sixteenbits
+ sleepsecs
slist
slists
smod
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index c6dadc35..c10da081 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21107
+TARGET_BALLISTICA_BUILD = 21108
TARGET_BALLISTICA_VERSION = '1.7.20'
_g_env_config: EnvConfig | None = None
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
index d658867c..cccb2f2e 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
@@ -386,6 +386,8 @@ class PluginWindow(bui.Window):
sel_name = 'Settings'
elif sel == self._back_button:
sel_name = 'Back'
+ elif sel == self._scrollwidget:
+ sel_name = 'Scroll'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
assert bui.app.classic is not None
diff --git a/src/ballistica/base/app/app_mode.cc b/src/ballistica/base/app/app_mode.cc
index 3ee71a1a..a93105ba 100644
--- a/src/ballistica/base/app/app_mode.cc
+++ b/src/ballistica/base/app/app_mode.cc
@@ -42,6 +42,10 @@ void AppMode::ChangeGameSpeed(int offs) {}
void AppMode::StepDisplayTime() {}
+auto AppMode::GetHeadlessDisplayStep() -> microsecs_t {
+ return kAppModeMaxHeadlessDisplayStep;
+}
+
auto AppMode::GetPartySize() const -> int { return 0; }
auto AppMode::GetNetworkDebugString() -> std::string { return ""; }
diff --git a/src/ballistica/base/app/app_mode.h b/src/ballistica/base/app/app_mode.h
index 7df5bb96..f00f854b 100644
--- a/src/ballistica/base/app/app_mode.h
+++ b/src/ballistica/base/app/app_mode.h
@@ -9,6 +9,15 @@
namespace ballistica::base {
+/// The max amount of time a headless app can sleep if no events are pending.
+/// This should not be *too* high or it might cause delays when going from
+/// no events present to events present.
+const microsecs_t kAppModeMaxHeadlessDisplayStep{500000};
+
+/// The min amount of time a headless app can sleep. This provides an upper
+/// limit on stepping overhead in cases where events are densely packed.
+const microsecs_t kAppModeMinHeadlessDisplayStep{1000};
+
/// Represents 'what the app is doing'. The global app-mode can be switched
/// as the app is running. Be aware that, unlike the App/App classes
/// which operate in the main thread, most functionality here is based in the
@@ -32,10 +41,20 @@ class AppMode {
/// Apply the app config.
virtual void ApplyAppConfig();
- /// Update the logic thread. Can be called at any frequency; generally
- /// corresponds to frame draws or a fixed timer.
+ /// Update the logic thread for a new display-time. Can be called at any
+ /// frequency. In gui builds, generally corresponds with frame drawing. In
+ /// headless builds, generally corresponds with scene stepping or other
+ /// scheduled events. Check values on g_base->logic to see current
+ /// display-time and most recent step size applied.
virtual void StepDisplayTime();
+ /// Called right after stepping; should return the exact microseconds
+ /// between the current display time and the next event the app-mode has
+ /// scheduled. If no events are pending, should return
+ /// kAppModeMaxHeadlessDisplayStep. This will only be called on headless
+ /// builds.
+ virtual auto GetHeadlessDisplayStep() -> microsecs_t;
+
/// Create a delegate for an input-device.
/// Return a raw pointer allocated using Object::NewDeferred.
virtual auto CreateInputDeviceDelegate(InputDevice* device)
diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc
index 1e6a5ec8..3eeb7778 100644
--- a/src/ballistica/base/base.cc
+++ b/src/ballistica/base/base.cc
@@ -238,6 +238,9 @@ void BaseFeatureSet::set_app_mode(AppMode* mode) {
input->RebuildInputDeviceDelegates();
app_mode_->OnActivate();
+
+ // Let some stuff know.
+ logic->OnAppModeChanged();
} catch (const Exception& exc) {
// Anything going wrong while switching app-modes leaves us in an
// undefined state; don't try to continue.
diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc
index 18b0878f..233f4523 100644
--- a/src/ballistica/base/logic/logic.cc
+++ b/src/ballistica/base/logic/logic.cc
@@ -220,8 +220,10 @@ void Logic::CompleteAppBootstrapping() {
// rate (10hz) to keep things efficient. Anyone dealing in display-time
// should be able to handle a wide variety of rates anyway.
if (g_core->HeadlessMode()) {
+ // NOTE: This length is currently milliseconds.
headless_display_time_step_timer_ = event_loop()->NewTimer(
- 1000 / 10, true, NewLambdaRunnable([this] { StepDisplayTime(); }));
+ kAppModeMinHeadlessDisplayStep / 1000, true,
+ NewLambdaRunnable([this] { StepDisplayTime(); }));
}
// Let our initial app-mode know it has become active.
g_base->app_mode()->OnActivate();
@@ -267,7 +269,17 @@ void Logic::OnScreenSizeChange(float virtual_width, float virtual_height,
void Logic::StepDisplayTime() {
assert(g_base->InLogicThread());
- UpdateDisplayTime();
+ // We have two different modes of operation here. When running in headless
+ // mode, display time is driven by upcoming events such as sim steps; we
+ // basically want to sleep as long as we can and run steps exactly when
+ // events occur. When running with a gui, our display-time is driven by
+ // real draw times and is intended to keep frame intervals as visually
+ // consistent and smooth looking as possible.
+ if (g_core->HeadlessMode()) {
+ UpdateDisplayTimeForHeadlessMode();
+ } else {
+ UpdateDisplayTimeForFrameDraw();
+ }
// Give all our subsystems some update love.
// Note: keep these in the same order as OnAppStart.
@@ -285,10 +297,86 @@ void Logic::StepDisplayTime() {
// Let's run display-timers *after* we step everything else so most things
// they interact with will be in an up-to-date state.
- display_timers_->Run(g_core->GetAppTimeMillisecs());
+ display_timers_->Run(display_time_microsecs_);
+
+ if (g_core->HeadlessMode()) {
+ PostUpdateDisplayTimeForHeadlessMode();
+ }
}
-void Logic::UpdateDisplayTime() {
+void Logic::OnAppModeChanged() {
+ assert(g_base->InLogicThread());
+
+ // Kick our headless stepping into high gear; this will snap us out of
+ // any long sleep we're currently in the middle of.
+ if (g_core->HeadlessMode()) {
+ if (debug_log_display_time_) {
+ Log(LogLevel::kDebug,
+ "Resetting headless display step timer due to app-mode change.");
+ }
+ assert(headless_display_time_step_timer_);
+ // NOTE: This is currently milliseconds.
+ headless_display_time_step_timer_->SetLength(kAppModeMinHeadlessDisplayStep
+ / 1000);
+ }
+}
+
+void Logic::UpdateDisplayTimeForHeadlessMode() {
+ assert(g_base->InLogicThread());
+ // In this case we just keep display time synced up with app time; we don't
+ // care about keeping the increments smooth or consistent.
+
+ // The one thing we *do* try to do, however, is keep our timer length
+ // updated so that we fire exactly when the app mode has events scheduled
+ // (or at least close enough so we can fudge it and tell them its that exact
+ // time).
+
+ auto app_time_microsecs = g_core->GetAppTimeMicrosecs();
+
+ auto old_display_time_microsecs = display_time_microsecs_;
+ display_time_microsecs_ = app_time_microsecs;
+ display_time_increment_microsecs_ =
+ display_time_microsecs_ - old_display_time_microsecs;
+
+ // In this path our float values are driven by our int ones.
+ display_time_ = static_cast(display_time_microsecs_) / 1000000.0;
+ display_time_increment_ =
+ static_cast(display_time_increment_microsecs_) / 1000000.0;
+
+ if (debug_log_display_time_) {
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer), "stepping display-time at app-time %.4f",
+ static_cast(app_time_microsecs) / 1000000.0);
+ Log(LogLevel::kDebug, buffer);
+ }
+}
+
+void Logic::PostUpdateDisplayTimeForHeadlessMode() {
+ assert(g_base->InLogicThread());
+ // At this point we've stepped our app-mode, so let's ask it how
+ // long we've got until the next event. We'll plug this into our
+ // display-update timer so we can try to sleep until that point.
+ auto headless_display_step_microsecs =
+ std::max(std::min(g_base->app_mode()->GetHeadlessDisplayStep(),
+ kAppModeMaxHeadlessDisplayStep),
+ kAppModeMinHeadlessDisplayStep);
+
+ if (debug_log_display_time_) {
+ auto sleepsecs =
+ static_cast(headless_display_step_microsecs) / 1000000.0;
+ auto apptimesecs = g_core->GetAppTimeSeconds();
+ char buffer[256];
+ snprintf(buffer, sizeof(buffer),
+ "will try to sleep for %.4f at app-time %.4f (until %.4f)",
+ sleepsecs, apptimesecs, apptimesecs + sleepsecs);
+ Log(LogLevel::kDebug, buffer);
+ }
+
+ auto sleep_millisecs = headless_display_step_microsecs / 1000;
+ headless_display_time_step_timer_->SetLength(sleep_millisecs);
+}
+
+void Logic::UpdateDisplayTimeForFrameDraw() {
// Here we update our smoothed display-time-increment based on how fast
// we are currently rendering frames. We want display-time to basically
// be progressing at the same rate as app-time but in as constant
@@ -371,13 +459,14 @@ void Logic::UpdateDisplayTime() {
avg /= count;
double range = max - min;
- // If our range of recent increment values is somewhat large relative to an
- // average value, things are probably chaotic so just use the current value
- // to respond quickly to changes. If things are more calm, use our nice
- // smoothed value.
+ // If our range of recent increment values is somewhat large relative to
+ // an average value, things are probably chaotic, so just use the
+ // current value to respond quickly to changes. If things are more calm,
+ // use our nice smoothed value.
+
// So in a case where we're seeing an average increment of 16ms, we snap
- // out of avg mode if there's more than 8ms between the longest and shortest
- // increments.
+ // out of avg mode if there's more than 8ms between the longest and
+ // shortest increments.
double chaos = range / avg;
bool use_avg = chaos < 0.5;
auto used = use_avg ? avg : this_increment;
@@ -388,14 +477,14 @@ void Logic::UpdateDisplayTime() {
// range.
// How far the smoothed increment value needs to get away from the final
- // value to actually start moving it.
- // Example: If our avg increment is 16.6ms (60fps), don't change our
- // increment until the 'used' value is more than 0.5ms (16.6 * 0.03) from it
- // in either direction.
- // Note: In practice I'm seeing that higher framerates
- // like 120 need buffers that are larger relative to avg to remain stable.
- // Though perhaps a bit of jitter is not noticeable at high frame rates;
- // just something to keep an eye on.
+ // value to actually start moving it. Example: If our avg increment is
+ // 16.6ms (60fps), don't change our increment until the 'used' value is
+ // more than 0.5ms (16.6 * 0.03) from it in either direction.
+
+ // Note: In practice I'm seeing that higher framerates like 120 need
+ // buffers that are larger relative to avg to remain stable. Though
+ // perhaps a bit of jitter is not noticeable at high frame rates; just
+ // something to keep an eye on.
auto trail_buffer{avg * 0.03};
auto trailing_diff = used - display_time_increment_;
@@ -424,6 +513,11 @@ void Logic::UpdateDisplayTime() {
}
// Lastly, apply our updated increment value to our time.
display_time_ += display_time_increment_;
+
+ // In this path, our integer values just follow our float ones.
+ auto prev_microsecs = display_time_microsecs_;
+ display_time_microsecs_ = static_cast(display_time_ * 1000000.0);
+ display_time_increment_microsecs_ = display_time_microsecs_ - prev_microsecs;
}
// Set up our sleeping based on what we're doing.
@@ -521,13 +615,13 @@ void Logic::SetAppTimerLength(int timer_id, millisecs_t length) {
}
}
-auto Logic::NewDisplayTimer(millisecs_t length, bool repeat,
+auto Logic::NewDisplayTimer(microsecs_t length, bool repeat,
const Object::Ref& runnable) -> int {
// Display-Timers go into a timer-list that we exec explicitly when we
// step display-time.
assert(g_base->InLogicThread());
int offset = 0;
- Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMillisecs(), length,
+ Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length,
offset, repeat ? -1 : 0, runnable);
return t->id();
}
@@ -537,7 +631,7 @@ void Logic::DeleteDisplayTimer(int timer_id) {
display_timers_->DeleteTimer(timer_id);
}
-void Logic::SetDisplayTimerLength(int timer_id, millisecs_t length) {
+void Logic::SetDisplayTimerLength(int timer_id, microsecs_t length) {
assert(g_base->InLogicThread());
Timer* t = display_timers_->GetTimer(timer_id);
if (t) {
diff --git a/src/ballistica/base/logic/logic.h b/src/ballistica/base/logic/logic.h
index a3339ad5..3fa62f75 100644
--- a/src/ballistica/base/logic/logic.h
+++ b/src/ballistica/base/logic/logic.h
@@ -31,6 +31,8 @@ class Logic {
void OnAppResume();
void OnAppShutdown();
+ void OnAppModeChanged();
+
void ApplyAppConfig();
void OnScreenSizeChange(float virtual_width, float virtual_height,
float pixel_width, float pixel_height);
@@ -58,35 +60,48 @@ class Logic {
void DeleteAppTimer(int timer_id);
void SetAppTimerLength(int timer_id, millisecs_t length);
- auto NewDisplayTimer(millisecs_t length, bool repeat,
+ auto NewDisplayTimer(microsecs_t length, bool repeat,
const Object::Ref& runnable) -> int;
void DeleteDisplayTimer(int timer_id);
- void SetDisplayTimerLength(int timer_id, millisecs_t length);
+ void SetDisplayTimerLength(int timer_id, microsecs_t length);
- /// Get current display-time for the app (in seconds).
- /// Display-time is a seconds value that increments smoothly with
- /// frame draws.
+ /// Get current display-time for the app in seconds.
auto display_time() { return display_time_; }
- /// Return current display-time increment (in seconds). This can shift with
- /// framerate changes but should remain mostly constant.
+ /// Get current display-time for the app in microseconds.
+ auto display_time_microsecs() { return display_time_microsecs_; }
+
+ /// Return current display-time increment in seconds.
auto display_time_increment() -> double { return display_time_increment_; }
+ /// Return current display-time increment in microseconds.
+ auto display_time_increment_microsecs() -> double {
+ return display_time_increment_microsecs_;
+ }
+
auto applied_app_config() const { return applied_app_config_; }
private:
- void UpdateDisplayTime();
+ void UpdateDisplayTimeForFrameDraw();
+ void UpdateDisplayTimeForHeadlessMode();
+ void PostUpdateDisplayTimeForHeadlessMode();
void CompleteAppBootstrapping();
void ProcessPendingWork();
void UpdatePendingWorkTimer();
void StepDisplayTime();
double display_time_{};
+ microsecs_t display_time_microsecs_{};
double display_time_increment_{1.0 / 60.0};
+ microsecs_t display_time_increment_microsecs_{1000000 / 60};
+
+ // GUI scheduling.
double last_display_time_update_app_time_{-1.0};
double recent_display_time_increments_[kDisplayTimeSampleCount]{};
int recent_display_time_increments_index_{-1};
+ // Headless scheduling.
+
std::unique_ptr display_timers_;
EventLoop* event_loop_{};
Timer* process_pending_work_timer_{};
diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc
index 48b484ea..4ce933c5 100644
--- a/src/ballistica/base/platform/base_platform.cc
+++ b/src/ballistica/base/platform/base_platform.cc
@@ -194,8 +194,8 @@ auto BasePlatform::GetPublicDeviceUUID() -> std::string {
// We used to plug version in directly here, but that caused uuids to
// shuffle too rapidly during periods of rapid development. This
// keeps it more constant.
- // __last_rand_uuid_component_shuffle_date__ 2022 12 17
- auto rand_uuid_component{"BMCJPHH0SC22KB0WVJ1RAYD68TPEXL58"};
+ // __last_rand_uuid_component_shuffle_date__ 2023 6 15
+ auto rand_uuid_component{"JVRWZ82D4WMBO110OA0IFJV7JKMQV8W3"};
inputs.emplace_back(rand_uuid_component);
auto gil{Python::ScopedInterpreterLock()};
diff --git a/src/ballistica/base/python/class/python_class_display_timer.cc b/src/ballistica/base/python/class/python_class_display_timer.cc
index 11c7654c..eb9330bf 100644
--- a/src/ballistica/base/python/class/python_class_display_timer.cc
+++ b/src/ballistica/base/python/class/python_class_display_timer.cc
@@ -103,7 +103,7 @@ auto PythonClassDisplayTimer::tp_new(PyTypeObject* type, PyObject* args,
return nullptr;
}
self->timer_id_ = g_base->logic->NewDisplayTimer(
- static_cast(length * 1000.0), repeat,
+ static_cast(length * 1000000.0), repeat,
Object::New(call_obj));
self->have_timer_ = true;
diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc
index fb4f16dd..3d7b58d3 100644
--- a/src/ballistica/base/python/methods/python_methods_app.cc
+++ b/src/ballistica/base/python/methods/python_methods_app.cc
@@ -341,7 +341,7 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds)
if (length < 0) {
throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
}
- g_base->logic->NewDisplayTimer(
+ g_base->logic->NewAppTimer(
static_cast(length * 1000.0), false,
Object::New(call_obj));
Py_RETURN_NONE;
@@ -376,9 +376,9 @@ static PyMethodDef PyAppTimerDef = {
"##### Examples\n"
"Print some stuff through time:\n"
">>> babase.screenmessage('hello from now!')\n"
- ">>> babase.apptimer(1.0, ba.Call(ba.screenmessage,\n"
+ ">>> babase.apptimer(1.0, babase.Call(babase.screenmessage,\n"
" 'hello from the future!'))\n"
- ">>> babase.apptimer(2.0, ba.Call(ba.screenmessage,\n"
+ ">>> babase.apptimer(2.0, babase.Call(babase.screenmessage,\n"
"... 'hello from the future 2!'))\n",
};
@@ -434,8 +434,8 @@ static auto PyDisplayTimer(PyObject* self, PyObject* args, PyObject* keywds)
if (length < 0) {
throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
}
- g_base->logic->NewAppTimer(
- static_cast(length * 1000.0), false,
+ g_base->logic->NewDisplayTimer(
+ static_cast(length * 1000000.0), false,
Object::New(call_obj));
Py_RETURN_NONE;
BA_PYTHON_CATCH;
@@ -474,9 +474,9 @@ static PyMethodDef PyDisplayTimerDef = {
"##### Examples\n"
"Print some stuff through time:\n"
">>> babase.screenmessage('hello from now!')\n"
- ">>> babase.displaytimer(1.0, ba.Call(ba.screenmessage,\n"
+ ">>> babase.displaytimer(1.0, babase.Call(babase.screenmessage,\n"
"... 'hello from the future!'))\n"
- ">>> babase.displaytimer(2.0, ba.Call(ba.screenmessage,\n"
+ ">>> babase.displaytimer(2.0, babase.Call(babase.screenmessage,\n"
"... 'hello from the future 2!'))\n",
};
diff --git a/src/ballistica/base/support/app_timer.h b/src/ballistica/base/support/app_timer.h
index f6f8ba0e..e116c26a 100644
--- a/src/ballistica/base/support/app_timer.h
+++ b/src/ballistica/base/support/app_timer.h
@@ -19,7 +19,7 @@ class AppTimer : public Object {
timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable);
}
- void SetLength(uint32_t length) {
+ void SetLength(millisecs_t length) {
assert(g_base->InLogicThread());
base::g_base->logic->SetAppTimerLength(timer_id_, length);
}
diff --git a/src/ballistica/scene_v1/node/flag_node.cc b/src/ballistica/scene_v1/node/flag_node.cc
index 902003df..7605508b 100644
--- a/src/ballistica/scene_v1/node/flag_node.cc
+++ b/src/ballistica/scene_v1/node/flag_node.cc
@@ -401,7 +401,7 @@ void FlagNode::Step() {
dBodyID b = body_->body();
assert(b);
- if (g_core->HeadlessMode()) {
+ if (!g_core->HeadlessMode()) {
dVector3 p;
FullShadowSet* full_shadows = full_shadow_set_.Get();
if (full_shadows) {
diff --git a/src/ballistica/scene_v1/support/host_session.cc b/src/ballistica/scene_v1/support/host_session.cc
index 60e602a1..4e572a30 100644
--- a/src/ballistica/scene_v1/support/host_session.cc
+++ b/src/ballistica/scene_v1/support/host_session.cc
@@ -578,6 +578,14 @@ void HostSession::Update(int time_advance_millisecs, double time_advance) {
assert(test_ref.Exists());
}
+auto HostSession::TimeToNextEvent() -> std::optional {
+ if (base_timers_.Empty()) {
+ return {};
+ }
+ auto to_next_ms = base_timers_.TimeToNextExpire(base_time_millisecs_);
+ return to_next_ms * 1000; // to microsecs.
+}
+
HostSession::~HostSession() {
assert(g_base->InLogicThread());
try {
diff --git a/src/ballistica/scene_v1/support/host_session.h b/src/ballistica/scene_v1/support/host_session.h
index 7ba2460f..d1d97eca 100644
--- a/src/ballistica/scene_v1/support/host_session.h
+++ b/src/ballistica/scene_v1/support/host_session.h
@@ -103,6 +103,7 @@ class HostSession : public Session {
auto GetUnusedPlayerName(Player* p, const std::string& base_name)
-> std::string;
auto ContextAllowsDefaultTimerTypes() -> bool override;
+ auto TimeToNextEvent() -> std::optional override;
private:
void StepScene();
diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc
index 2ef2814f..2ac0915b 100644
--- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc
+++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc
@@ -408,6 +408,25 @@ auto SceneV1AppMode::GetPartySize() const -> int {
return cJSON_GetArraySize(game_roster_);
}
+auto SceneV1AppMode::GetHeadlessDisplayStep() -> microsecs_t {
+ std::optional min_time_to_next;
+ for (auto&& i : sessions_) {
+ if (!i.Exists()) {
+ continue;
+ }
+ auto this_time_to_next = i->TimeToNextEvent();
+ if (this_time_to_next.has_value()) {
+ if (!min_time_to_next.has_value()) {
+ min_time_to_next = *this_time_to_next;
+ } else {
+ min_time_to_next = std::min(*min_time_to_next, *this_time_to_next);
+ }
+ }
+ }
+ return min_time_to_next.has_value() ? *min_time_to_next
+ : base::kAppModeMaxHeadlessDisplayStep;
+}
+
void SceneV1AppMode::StepDisplayTime() {
assert(g_base->InLogicThread());
@@ -426,7 +445,8 @@ void SceneV1AppMode::StepDisplayTime() {
// each time).
millisecs_t legacy_display_time_millisecs_inc;
if (legacy_display_time_millisecs_prev_ < 0) {
- // Convert directly *only* the first time when we don't have prev available.
+ // Convert directly *only* the first time when we don't have prev
+ // available.
legacy_display_time_millisecs_inc = static_cast(
g_base->logic->display_time_increment() * 1000.0);
@@ -677,8 +697,8 @@ void SceneV1AppMode::UpdateKickVote() {
1, 1, 0);
kick_vote_in_progress_ = false;
- // Disallow kicking for a while for everyone.. but ESPECIALLY so for the guy
- // who launched the failed vote.
+ // Disallow kicking for a while for everyone.. but ESPECIALLY so for the
+ // guy who launched the failed vote.
for (ConnectionToClient* client :
connections()->GetConnectionsToClients()) {
millisecs_t delay = kKickVoteFailRetryDelay;
diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.h b/src/ballistica/scene_v1/support/scene_v1_app_mode.h
index 50900802..b1ab1b4d 100644
--- a/src/ballistica/scene_v1/support/scene_v1_app_mode.h
+++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.h
@@ -182,6 +182,7 @@ class SceneV1AppMode : public base::AppMode {
auto buffer_time() const { return buffer_time_; }
void set_buffer_time(int val) { buffer_time_ = val; }
void OnActivate() override;
+ auto GetHeadlessDisplayStep() -> microsecs_t override;
private:
void PruneScanResults();
diff --git a/src/ballistica/scene_v1/support/session.cc b/src/ballistica/scene_v1/support/session.cc
index 4115b44d..474278da 100644
--- a/src/ballistica/scene_v1/support/session.cc
+++ b/src/ballistica/scene_v1/support/session.cc
@@ -19,6 +19,12 @@ Session::~Session() { g_core->session_count--; }
void Session::Update(int time_advance_millisecs, double time_advance) {}
+auto Session::TimeToNextEvent() -> std::optional {
+ BA_LOG_ONCE(LogLevel::kError,
+ "Session::TimeToNextEvent() being called; should not happen.");
+ return 5000000;
+}
+
auto Session::GetForegroundContext() -> base::ContextRef { return {}; }
void Session::Draw(base::FrameDef*) {}
diff --git a/src/ballistica/scene_v1/support/session.h b/src/ballistica/scene_v1/support/session.h
index c15735ca..92b1b4da 100644
--- a/src/ballistica/scene_v1/support/session.h
+++ b/src/ballistica/scene_v1/support/session.h
@@ -19,6 +19,9 @@ class Session : public SceneV1Context {
/// a modern seconds advance.
virtual void Update(int time_advance_millisecs, double time_advance);
+ /// Note: this should be returned in microsecs.
+ virtual auto TimeToNextEvent() -> std::optional;
+
// If this returns false, the screen will be cleared as part of rendering.
virtual auto DoesFillScreen() const -> bool = 0;
diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc
index 74136458..6ff49b5d 100644
--- a/src/ballistica/shared/ballistica.cc
+++ b/src/ballistica/shared/ballistica.cc
@@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kEngineBuildNumber = 21107;
+const int kEngineBuildNumber = 21108;
const char* kEngineVersion = "1.7.20";
auto MonolithicMain(const core::CoreConfig& core_config) -> int {