server lag fix and timer cleanup

This commit is contained in:
Eric 2023-06-14 18:34:44 -07:00
parent 7b21a7fccc
commit 782a8773a4
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
23 changed files with 279 additions and 93 deletions

88
.efrocachemap generated
View File

@ -4072,50 +4072,50 @@
"build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/18/4b/787a9267e17be3c49966072581a5", "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/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/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/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/71/df/89649ebeee912d3671994700ca98", "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/3a/55/2e767201ff0210baf2aab336b962", "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/96/b7/14b9ff3221e0643c5808408889a0", "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/96/a6/67826a50b92ef1b97c9350357bde", "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/1a/00/5f26c8e62d546190ef1a0e5d86c1", "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/e9/d9/c37edc30a6d87dc6a3fff69cd69d", "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/f0/96/eafa605033a621f0024ce23e8506", "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/e7/0c/e5d1f7f76463dab3a593c7e39d28", "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/23/98/40563ec129aeae3ae29fce9a4098", "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/19/db/025a2e4224285384ca5138b528b9", "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/3b/04/f7f76fbbb71c51d398999b91d962", "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/ce/65/019b91c6590778d7e2f32a6dc86d", "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/70/5d/74932199660c0faa295fc8b331e5", "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/e1/37/98c5e9424f0c180db6aa16a0a2e9", "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/c9/3f/3dc59e05f156376638a59e606954", "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/6b/2b/6a98ef5411ea98d0190aae33ca8a", "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/65/82/defe87b3e240bbabd8194beb71a2", "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/39/10/6e8db73db72f65b094401dc66972", "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/45/5d/a65990370464ca038cd0a1be680a", "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/3c/a8/5487ece199be8717cead6de157e3", "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/9d/71/77a0ae15f3216c51da44ccefeb87", "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/8c/de/a04c75afaee10276fedbd6dc12dd", "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/c1/45/f2143d53646891111b648a6cc014", "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/4c/0c/2afa4f64c6030ba37cacdb62fb4d", "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/d2/0a/0d4b99f583a4e7a55761c5493c06", "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/5e/ff/b8cd37fe6d5fa5df6a7bc6103ecc", "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/84/e3/815f8ba4a1c0ff4b01e44bd95682", "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/10/98/48262f1e81f07154c28cfec87ee9", "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/6e/97/e8029482a29578a9f6cc15813265", "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/5d/ac/fbe75dcd9f4a4461552cdaf8a3be", "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/94/ec/cce65d8f6f76e240597b073f17e7", "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/a9/15/89ec9af80cf169f837ea6c3c365d", "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/e1/d8/0949e33c04570cae7dafaf906770", "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/c5/f1c4dcdd2233d6ffc280db77ec99", "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/20/e5/3b83e186b63a95d704c69a6b5b76", "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/bf/fa/f79dc4a7ca33a8cca9df0547eef4", "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/9e/3c/09855cda12586dbba55884a259b0", "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/47/f1/3eadde3dcabd1b0b9be32a7f247f", "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/f3/f6/51991b000385d553f35563df4190", "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/a3/7d/cae2bd2e4daf11f90e73937170fc", "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/1e/cc/ab11419f2d792b8738605a1f3a24", "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/11/eb/55affc8d77ccac52c0b8fd128ad7", "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/5a/57/2df7d185467abcca2287a4410ee8", "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/__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/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", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/ea/6a/6a4721b144e5e297b542d2a0eea2",

View File

@ -135,6 +135,7 @@
<w>apptime</w> <w>apptime</w>
<w>apptimer</w> <w>apptimer</w>
<w>apptimers</w> <w>apptimers</w>
<w>apptimesecs</w>
<w>apputils</w> <w>apputils</w>
<w>archbase</w> <w>archbase</w>
<w>archivepath</w> <w>archivepath</w>
@ -2595,6 +2596,7 @@
<w>sirplus</w> <w>sirplus</w>
<w>sitebuiltins</w> <w>sitebuiltins</w>
<w>skey</w> <w>skey</w>
<w>sleepsecs</w>
<w>sline</w> <w>sline</w>
<w>slist</w> <w>slist</w>
<w>slists</w> <w>slists</w>

View File

@ -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 - 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 changes coming in 1.8. Basically this means that a lot of things will be
@ -321,6 +321,12 @@
would draw incorrectly. would draw incorrectly.
- (build 21106) Fixed an issue where in-game ping would always display green no - (build 21106) Fixed an issue where in-game ping would always display green no
matter how bad the ping was. 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) ### 1.7.19 (build 20997, api 7, 2023-01-19)

View File

@ -92,6 +92,7 @@
<w>apptime</w> <w>apptime</w>
<w>apptimer</w> <w>apptimer</w>
<w>apptimers</w> <w>apptimers</w>
<w>apptimesecs</w>
<w>archbase</w> <w>archbase</w>
<w>archos</w> <w>archos</w>
<w>argindex</w> <w>argindex</w>
@ -1499,6 +1500,7 @@
<w>sincestr</w> <w>sincestr</w>
<w>sisssssssss</w> <w>sisssssssss</w>
<w>sixteenbits</w> <w>sixteenbits</w>
<w>sleepsecs</w>
<w>slist</w> <w>slist</w>
<w>slists</w> <w>slists</w>
<w>smod</w> <w>smod</w>

View File

@ -28,7 +28,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21107 TARGET_BALLISTICA_BUILD = 21108
TARGET_BALLISTICA_VERSION = '1.7.20' TARGET_BALLISTICA_VERSION = '1.7.20'
_g_env_config: EnvConfig | None = None _g_env_config: EnvConfig | None = None

View File

@ -386,6 +386,8 @@ class PluginWindow(bui.Window):
sel_name = 'Settings' sel_name = 'Settings'
elif sel == self._back_button: elif sel == self._back_button:
sel_name = 'Back' sel_name = 'Back'
elif sel == self._scrollwidget:
sel_name = 'Scroll'
else: else:
raise ValueError(f'unrecognized selection \'{sel}\'') raise ValueError(f'unrecognized selection \'{sel}\'')
assert bui.app.classic is not None assert bui.app.classic is not None

View File

@ -42,6 +42,10 @@ void AppMode::ChangeGameSpeed(int offs) {}
void AppMode::StepDisplayTime() {} void AppMode::StepDisplayTime() {}
auto AppMode::GetHeadlessDisplayStep() -> microsecs_t {
return kAppModeMaxHeadlessDisplayStep;
}
auto AppMode::GetPartySize() const -> int { return 0; } auto AppMode::GetPartySize() const -> int { return 0; }
auto AppMode::GetNetworkDebugString() -> std::string { return ""; } auto AppMode::GetNetworkDebugString() -> std::string { return ""; }

View File

@ -9,6 +9,15 @@
namespace ballistica::base { 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 /// 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 /// 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 /// which operate in the main thread, most functionality here is based in the
@ -32,10 +41,20 @@ class AppMode {
/// Apply the app config. /// Apply the app config.
virtual void ApplyAppConfig(); virtual void ApplyAppConfig();
/// Update the logic thread. Can be called at any frequency; generally /// Update the logic thread for a new display-time. Can be called at any
/// corresponds to frame draws or a fixed timer. /// 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(); 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. /// Create a delegate for an input-device.
/// Return a raw pointer allocated using Object::NewDeferred. /// Return a raw pointer allocated using Object::NewDeferred.
virtual auto CreateInputDeviceDelegate(InputDevice* device) virtual auto CreateInputDeviceDelegate(InputDevice* device)

View File

@ -238,6 +238,9 @@ void BaseFeatureSet::set_app_mode(AppMode* mode) {
input->RebuildInputDeviceDelegates(); input->RebuildInputDeviceDelegates();
app_mode_->OnActivate(); app_mode_->OnActivate();
// Let some stuff know.
logic->OnAppModeChanged();
} catch (const Exception& exc) { } catch (const Exception& exc) {
// Anything going wrong while switching app-modes leaves us in an // Anything going wrong while switching app-modes leaves us in an
// undefined state; don't try to continue. // undefined state; don't try to continue.

View File

@ -220,8 +220,10 @@ void Logic::CompleteAppBootstrapping() {
// rate (10hz) to keep things efficient. Anyone dealing in display-time // rate (10hz) to keep things efficient. Anyone dealing in display-time
// should be able to handle a wide variety of rates anyway. // should be able to handle a wide variety of rates anyway.
if (g_core->HeadlessMode()) { if (g_core->HeadlessMode()) {
// NOTE: This length is currently milliseconds.
headless_display_time_step_timer_ = event_loop()->NewTimer( 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. // Let our initial app-mode know it has become active.
g_base->app_mode()->OnActivate(); g_base->app_mode()->OnActivate();
@ -267,7 +269,17 @@ void Logic::OnScreenSizeChange(float virtual_width, float virtual_height,
void Logic::StepDisplayTime() { void Logic::StepDisplayTime() {
assert(g_base->InLogicThread()); 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. // Give all our subsystems some update love.
// Note: keep these in the same order as OnAppStart. // 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 // Let's run display-timers *after* we step everything else so most things
// they interact with will be in an up-to-date state. // 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<double>(display_time_microsecs_) / 1000000.0;
display_time_increment_ =
static_cast<double>(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<double>(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<double>(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 // Here we update our smoothed display-time-increment based on how fast
// we are currently rendering frames. We want display-time to basically // we are currently rendering frames. We want display-time to basically
// be progressing at the same rate as app-time but in as constant // be progressing at the same rate as app-time but in as constant
@ -371,13 +459,14 @@ void Logic::UpdateDisplayTime() {
avg /= count; avg /= count;
double range = max - min; double range = max - min;
// If our range of recent increment values is somewhat large relative to an // If our range of recent increment values is somewhat large relative to
// average value, things are probably chaotic so just use the current value // an average value, things are probably chaotic, so just use the
// to respond quickly to changes. If things are more calm, use our nice // current value to respond quickly to changes. If things are more calm,
// smoothed value. // use our nice smoothed value.
// So in a case where we're seeing an average increment of 16ms, we snap // 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 // out of avg mode if there's more than 8ms between the longest and
// increments. // shortest increments.
double chaos = range / avg; double chaos = range / avg;
bool use_avg = chaos < 0.5; bool use_avg = chaos < 0.5;
auto used = use_avg ? avg : this_increment; auto used = use_avg ? avg : this_increment;
@ -388,14 +477,14 @@ void Logic::UpdateDisplayTime() {
// range. // range.
// How far the smoothed increment value needs to get away from the final // How far the smoothed increment value needs to get away from the final
// value to actually start moving it. // value to actually start moving it. Example: If our avg increment is
// Example: If our avg increment is 16.6ms (60fps), don't change our // 16.6ms (60fps), don't change our increment until the 'used' value is
// increment until the 'used' value is more than 0.5ms (16.6 * 0.03) from it // more than 0.5ms (16.6 * 0.03) from it in either direction.
// in either direction.
// Note: In practice I'm seeing that higher framerates // Note: In practice I'm seeing that higher framerates like 120 need
// like 120 need buffers that are larger relative to avg to remain stable. // buffers that are larger relative to avg to remain stable. Though
// Though perhaps a bit of jitter is not noticeable at high frame rates; // perhaps a bit of jitter is not noticeable at high frame rates; just
// just something to keep an eye on. // something to keep an eye on.
auto trail_buffer{avg * 0.03}; auto trail_buffer{avg * 0.03};
auto trailing_diff = used - display_time_increment_; auto trailing_diff = used - display_time_increment_;
@ -424,6 +513,11 @@ void Logic::UpdateDisplayTime() {
} }
// Lastly, apply our updated increment value to our time. // Lastly, apply our updated increment value to our time.
display_time_ += display_time_increment_; 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<microsecs_t>(display_time_ * 1000000.0);
display_time_increment_microsecs_ = display_time_microsecs_ - prev_microsecs;
} }
// Set up our sleeping based on what we're doing. // 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>& runnable) -> int { const Object::Ref<Runnable>& runnable) -> int {
// Display-Timers go into a timer-list that we exec explicitly when we // Display-Timers go into a timer-list that we exec explicitly when we
// step display-time. // step display-time.
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
int offset = 0; 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); offset, repeat ? -1 : 0, runnable);
return t->id(); return t->id();
} }
@ -537,7 +631,7 @@ void Logic::DeleteDisplayTimer(int timer_id) {
display_timers_->DeleteTimer(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()); assert(g_base->InLogicThread());
Timer* t = display_timers_->GetTimer(timer_id); Timer* t = display_timers_->GetTimer(timer_id);
if (t) { if (t) {

View File

@ -31,6 +31,8 @@ class Logic {
void OnAppResume(); void OnAppResume();
void OnAppShutdown(); void OnAppShutdown();
void OnAppModeChanged();
void ApplyAppConfig(); void ApplyAppConfig();
void OnScreenSizeChange(float virtual_width, float virtual_height, void OnScreenSizeChange(float virtual_width, float virtual_height,
float pixel_width, float pixel_height); float pixel_width, float pixel_height);
@ -58,35 +60,48 @@ class Logic {
void DeleteAppTimer(int timer_id); void DeleteAppTimer(int timer_id);
void SetAppTimerLength(int timer_id, millisecs_t length); 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>& runnable) -> int; const Object::Ref<Runnable>& runnable) -> int;
void DeleteDisplayTimer(int timer_id); 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). /// Get current display-time for the app in seconds.
/// Display-time is a seconds value that increments smoothly with
/// frame draws.
auto display_time() { return display_time_; } auto display_time() { return display_time_; }
/// Return current display-time increment (in seconds). This can shift with /// Get current display-time for the app in microseconds.
/// framerate changes but should remain mostly constant. auto display_time_microsecs() { return display_time_microsecs_; }
/// Return current display-time increment in seconds.
auto display_time_increment() -> double { return display_time_increment_; } 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_; } auto applied_app_config() const { return applied_app_config_; }
private: private:
void UpdateDisplayTime(); void UpdateDisplayTimeForFrameDraw();
void UpdateDisplayTimeForHeadlessMode();
void PostUpdateDisplayTimeForHeadlessMode();
void CompleteAppBootstrapping(); void CompleteAppBootstrapping();
void ProcessPendingWork(); void ProcessPendingWork();
void UpdatePendingWorkTimer(); void UpdatePendingWorkTimer();
void StepDisplayTime(); void StepDisplayTime();
double display_time_{}; double display_time_{};
microsecs_t display_time_microsecs_{};
double display_time_increment_{1.0 / 60.0}; 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 last_display_time_update_app_time_{-1.0};
double recent_display_time_increments_[kDisplayTimeSampleCount]{}; double recent_display_time_increments_[kDisplayTimeSampleCount]{};
int recent_display_time_increments_index_{-1}; int recent_display_time_increments_index_{-1};
// Headless scheduling.
std::unique_ptr<TimerList> display_timers_; std::unique_ptr<TimerList> display_timers_;
EventLoop* event_loop_{}; EventLoop* event_loop_{};
Timer* process_pending_work_timer_{}; Timer* process_pending_work_timer_{};

View File

@ -194,8 +194,8 @@ auto BasePlatform::GetPublicDeviceUUID() -> std::string {
// We used to plug version in directly here, but that caused uuids to // We used to plug version in directly here, but that caused uuids to
// shuffle too rapidly during periods of rapid development. This // shuffle too rapidly during periods of rapid development. This
// keeps it more constant. // keeps it more constant.
// __last_rand_uuid_component_shuffle_date__ 2022 12 17 // __last_rand_uuid_component_shuffle_date__ 2023 6 15
auto rand_uuid_component{"BMCJPHH0SC22KB0WVJ1RAYD68TPEXL58"}; auto rand_uuid_component{"JVRWZ82D4WMBO110OA0IFJV7JKMQV8W3"};
inputs.emplace_back(rand_uuid_component); inputs.emplace_back(rand_uuid_component);
auto gil{Python::ScopedInterpreterLock()}; auto gil{Python::ScopedInterpreterLock()};

View File

@ -103,7 +103,7 @@ auto PythonClassDisplayTimer::tp_new(PyTypeObject* type, PyObject* args,
return nullptr; return nullptr;
} }
self->timer_id_ = g_base->logic->NewDisplayTimer( self->timer_id_ = g_base->logic->NewDisplayTimer(
static_cast<millisecs_t>(length * 1000.0), repeat, static_cast<microsecs_t>(length * 1000000.0), repeat,
Object::New<Runnable, PythonContextCallRunnable>(call_obj)); Object::New<Runnable, PythonContextCallRunnable>(call_obj));
self->have_timer_ = true; self->have_timer_ = true;

View File

@ -341,7 +341,7 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds)
if (length < 0) { if (length < 0) {
throw Exception("Timer length cannot be < 0.", PyExcType::kValue); throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
} }
g_base->logic->NewDisplayTimer( g_base->logic->NewAppTimer(
static_cast<millisecs_t>(length * 1000.0), false, static_cast<millisecs_t>(length * 1000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj)); Object::New<Runnable, PythonContextCallRunnable>(call_obj));
Py_RETURN_NONE; Py_RETURN_NONE;
@ -376,9 +376,9 @@ static PyMethodDef PyAppTimerDef = {
"##### Examples\n" "##### Examples\n"
"Print some stuff through time:\n" "Print some stuff through time:\n"
">>> babase.screenmessage('hello from now!')\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" " '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", "... 'hello from the future 2!'))\n",
}; };
@ -434,8 +434,8 @@ static auto PyDisplayTimer(PyObject* self, PyObject* args, PyObject* keywds)
if (length < 0) { if (length < 0) {
throw Exception("Timer length cannot be < 0.", PyExcType::kValue); throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
} }
g_base->logic->NewAppTimer( g_base->logic->NewDisplayTimer(
static_cast<millisecs_t>(length * 1000.0), false, static_cast<microsecs_t>(length * 1000000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj)); Object::New<Runnable, PythonContextCallRunnable>(call_obj));
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
@ -474,9 +474,9 @@ static PyMethodDef PyDisplayTimerDef = {
"##### Examples\n" "##### Examples\n"
"Print some stuff through time:\n" "Print some stuff through time:\n"
">>> babase.screenmessage('hello from now!')\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" "... '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", "... 'hello from the future 2!'))\n",
}; };

View File

@ -19,7 +19,7 @@ class AppTimer : public Object {
timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable); timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable);
} }
void SetLength(uint32_t length) { void SetLength(millisecs_t length) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
base::g_base->logic->SetAppTimerLength(timer_id_, length); base::g_base->logic->SetAppTimerLength(timer_id_, length);
} }

View File

@ -401,7 +401,7 @@ void FlagNode::Step() {
dBodyID b = body_->body(); dBodyID b = body_->body();
assert(b); assert(b);
if (g_core->HeadlessMode()) { if (!g_core->HeadlessMode()) {
dVector3 p; dVector3 p;
FullShadowSet* full_shadows = full_shadow_set_.Get(); FullShadowSet* full_shadows = full_shadow_set_.Get();
if (full_shadows) { if (full_shadows) {

View File

@ -578,6 +578,14 @@ void HostSession::Update(int time_advance_millisecs, double time_advance) {
assert(test_ref.Exists()); assert(test_ref.Exists());
} }
auto HostSession::TimeToNextEvent() -> std::optional<microsecs_t> {
if (base_timers_.Empty()) {
return {};
}
auto to_next_ms = base_timers_.TimeToNextExpire(base_time_millisecs_);
return to_next_ms * 1000; // to microsecs.
}
HostSession::~HostSession() { HostSession::~HostSession() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
try { try {

View File

@ -103,6 +103,7 @@ class HostSession : public Session {
auto GetUnusedPlayerName(Player* p, const std::string& base_name) auto GetUnusedPlayerName(Player* p, const std::string& base_name)
-> std::string; -> std::string;
auto ContextAllowsDefaultTimerTypes() -> bool override; auto ContextAllowsDefaultTimerTypes() -> bool override;
auto TimeToNextEvent() -> std::optional<microsecs_t> override;
private: private:
void StepScene(); void StepScene();

View File

@ -408,6 +408,25 @@ auto SceneV1AppMode::GetPartySize() const -> int {
return cJSON_GetArraySize(game_roster_); return cJSON_GetArraySize(game_roster_);
} }
auto SceneV1AppMode::GetHeadlessDisplayStep() -> microsecs_t {
std::optional<microsecs_t> 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() { void SceneV1AppMode::StepDisplayTime() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
@ -426,7 +445,8 @@ void SceneV1AppMode::StepDisplayTime() {
// each time). // each time).
millisecs_t legacy_display_time_millisecs_inc; millisecs_t legacy_display_time_millisecs_inc;
if (legacy_display_time_millisecs_prev_ < 0) { 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<millisecs_t>( legacy_display_time_millisecs_inc = static_cast<millisecs_t>(
g_base->logic->display_time_increment() * 1000.0); g_base->logic->display_time_increment() * 1000.0);
@ -677,8 +697,8 @@ void SceneV1AppMode::UpdateKickVote() {
1, 1, 0); 1, 1, 0);
kick_vote_in_progress_ = false; kick_vote_in_progress_ = false;
// Disallow kicking for a while for everyone.. but ESPECIALLY so for the guy // Disallow kicking for a while for everyone.. but ESPECIALLY so for the
// who launched the failed vote. // guy who launched the failed vote.
for (ConnectionToClient* client : for (ConnectionToClient* client :
connections()->GetConnectionsToClients()) { connections()->GetConnectionsToClients()) {
millisecs_t delay = kKickVoteFailRetryDelay; millisecs_t delay = kKickVoteFailRetryDelay;

View File

@ -182,6 +182,7 @@ class SceneV1AppMode : public base::AppMode {
auto buffer_time() const { return buffer_time_; } auto buffer_time() const { return buffer_time_; }
void set_buffer_time(int val) { buffer_time_ = val; } void set_buffer_time(int val) { buffer_time_ = val; }
void OnActivate() override; void OnActivate() override;
auto GetHeadlessDisplayStep() -> microsecs_t override;
private: private:
void PruneScanResults(); void PruneScanResults();

View File

@ -19,6 +19,12 @@ Session::~Session() { g_core->session_count--; }
void Session::Update(int time_advance_millisecs, double time_advance) {} void Session::Update(int time_advance_millisecs, double time_advance) {}
auto Session::TimeToNextEvent() -> std::optional<microsecs_t> {
BA_LOG_ONCE(LogLevel::kError,
"Session::TimeToNextEvent() being called; should not happen.");
return 5000000;
}
auto Session::GetForegroundContext() -> base::ContextRef { return {}; } auto Session::GetForegroundContext() -> base::ContextRef { return {}; }
void Session::Draw(base::FrameDef*) {} void Session::Draw(base::FrameDef*) {}

View File

@ -19,6 +19,9 @@ class Session : public SceneV1Context {
/// a modern seconds advance. /// a modern seconds advance.
virtual void Update(int time_advance_millisecs, double time_advance); virtual void Update(int time_advance_millisecs, double time_advance);
/// Note: this should be returned in microsecs.
virtual auto TimeToNextEvent() -> std::optional<microsecs_t>;
// If this returns false, the screen will be cleared as part of rendering. // If this returns false, the screen will be cleared as part of rendering.
virtual auto DoesFillScreen() const -> bool = 0; virtual auto DoesFillScreen() const -> bool = 0;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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"; const char* kEngineVersion = "1.7.20";
auto MonolithicMain(const core::CoreConfig& core_config) -> int { auto MonolithicMain(const core::CoreConfig& core_config) -> int {