mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-26 00:47:10 +08:00
Finished wiring up clean_exit_minutes, unclean_exit_minutes, and idle_exit_minutes in server configs
This commit is contained in:
parent
5125b005c7
commit
5f909d0b91
@ -3932,24 +3932,24 @@
|
||||
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
|
||||
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
|
||||
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
|
||||
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/62/e1/70ff36467d1875af3e0e38da754c",
|
||||
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/5c/83/e1f9e8db08f24a1d0b08958b9e09",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/59/ae/bc2f695f28eb3405415ea11c8083",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cb/ab/076371cc80fb408dfe2cbd4da8b0",
|
||||
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/70/203ffd8485f6e3b5432c70b556b3",
|
||||
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f5/51/9b37b71adfaa2d5ca706bc168b2f",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5d/87/089a0508c2876e6090f337a6a1c6",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fe/49/c33812c8596e946c4b7ff4a70176",
|
||||
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/65/c0/cede9d63e3c3fd3148b1d25ba62f",
|
||||
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/4b/e0/26d2281f316b0f704a6a404b030b",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a7/b8/988ab1f0d337516c034301ce219a",
|
||||
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/69/80/9068d8f99a060c625abee7b49184",
|
||||
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4f/4c/8590730e5d1cdae456c1b734a2a1",
|
||||
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/44/e5/d1c3162e114e51a5b5b826c2ec7c",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ee/21/8fab3da6b974cf323024d076b609",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/20/5d/881676243c5f44bdca677497b4d4",
|
||||
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/90/a3/e265c556587921cdfd8c753b592f",
|
||||
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4c/5e/80ac1a7a4a75de8869755aa7cbc1",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b0/4a/2144949c2cb6c3ef7d99c4409bfb",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/ce/078603417240c7f8a7f067d55a6f"
|
||||
"build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/46/08/c5c18d49571ed38eebf9596704c5",
|
||||
"build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/2d/9858a8c8735debb23fe24e2efe4b",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3f/e9/7600ca36570a7dcf5af9647a2e21",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b9/e4/ec93fdb61177cda5ff992506ac17",
|
||||
"build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/df/fb/05e0398c5bbe64fad410ea375356",
|
||||
"build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/25/eb/ea4a1b0694ad2cf60352defc69af",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/50/422233084047a81e036c132bde39",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3f/4d/3447d7b2f7a8bc64ca7bdd6536f1",
|
||||
"build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/38/35/fa7173464f9527e374a0ce94b3bd",
|
||||
"build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/cc/b1/a94d7f123b22ed23e00f40e579d1",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/53/4a/02860b49bda65c53123447c69b90",
|
||||
"build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/fe/e2/1c603aed8baf632d28226b2031e6",
|
||||
"build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/a1/e5a2906b6bd18229f21f5a5d0876",
|
||||
"build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b9/e7/ac87044e0eac75551bcacdf2e41d",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/18/c2/3a457694a20c7dc22eb0d5cb67fc",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3a/0c/96264568710c8e6a84af77a4f0d9",
|
||||
"build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/51/96/56697bf4a444ebe66b28c616e279",
|
||||
"build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2c/5b/303bfdb73180faf55ba78fa2bcc8",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/09/83/ecaaf27743299eb80fd6ae704f23",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/22/9f/c6686359ba318f695a0a0900f2c2"
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
### 1.5.29 (20246)
|
||||
- Exposed ba method/class initing in public C++ layer.
|
||||
- The 'restart' and 'shutdown' commands in the server script now default to immediate=True
|
||||
- Wired up 'clean_exit_minutes', 'unclean_exit_minutes', and 'idle_exit_minutes' options in the server config
|
||||
|
||||
### 1.5.28 (20239)
|
||||
- Simplified ba.enum_by_value()
|
||||
|
||||
@ -2079,7 +2079,7 @@ def get_idle_time() -> int:
|
||||
|
||||
(internal)
|
||||
|
||||
Returns the amount of time since any game input has been processed
|
||||
Returns the amount of time since any game input has been received.
|
||||
"""
|
||||
return int()
|
||||
|
||||
|
||||
@ -124,7 +124,7 @@ class ServerManagerApp:
|
||||
|
||||
# Python will handle SIGINT for us (as KeyboardInterrupt) but we
|
||||
# need to register a SIGTERM handler so we have a chance to clean
|
||||
# up our child-process when someone tells us to die. (and avoid
|
||||
# up our subprocess when someone tells us to die. (and avoid
|
||||
# zombie processes)
|
||||
signal.signal(signal.SIGTERM, self._handle_term_signal)
|
||||
|
||||
@ -150,12 +150,17 @@ class ServerManagerApp:
|
||||
print(f'{Clr.SRED}Unexpected interpreter exception:'
|
||||
f' {exc} ({type(exc)}){Clr.RST}')
|
||||
|
||||
print(f'{Clr.CYN}Server manager shutting down...{Clr.RST}')
|
||||
|
||||
if self._subprocess_thread.is_alive():
|
||||
print(f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}')
|
||||
|
||||
# Mark ourselves as shutting down and wait for the process to wrap up.
|
||||
self._done = True
|
||||
self._subprocess_thread.join()
|
||||
|
||||
def cmd(self, statement: str) -> None:
|
||||
"""Exec a Python command on the current running server child-process.
|
||||
"""Exec a Python command on the current running server subprocess.
|
||||
|
||||
Note that commands are executed asynchronously and no status or
|
||||
return value is accessible from this manager app.
|
||||
@ -227,7 +232,7 @@ class ServerManagerApp:
|
||||
KickCommand(client_id=client_id, ban_time=ban_time))
|
||||
|
||||
def restart(self, immediate: bool = True) -> None:
|
||||
"""Restart the server child-process.
|
||||
"""Restart the server subprocess.
|
||||
|
||||
This can be necessary for some config changes to take effect.
|
||||
By default, the server will exit immediately. If 'immediate' is passed
|
||||
@ -246,7 +251,7 @@ class ServerManagerApp:
|
||||
IMMEDIATE_SHUTDOWN_TIME_LIMIT)
|
||||
|
||||
def shutdown(self, immediate: bool = True) -> None:
|
||||
"""Shut down the server child-process and exit the wrapper
|
||||
"""Shut down the server subprocess and exit the wrapper
|
||||
|
||||
By default, the server will exit immediately. If 'immediate' is passed
|
||||
as False, however, the server will instead exit at the next clean
|
||||
@ -256,7 +261,8 @@ class ServerManagerApp:
|
||||
self._enqueue_server_command(
|
||||
ShutdownCommand(reason=ShutdownReason.NONE, immediate=immediate))
|
||||
|
||||
# So we know to bail completely once this subprocess completes.
|
||||
# An explicit shutdown means we know to bail completely once this
|
||||
# subprocess completes.
|
||||
self._wrapper_shutdown_desired = True
|
||||
|
||||
# If we're asking for an immediate shutdown but don't get one within
|
||||
@ -304,7 +310,7 @@ class ServerManagerApp:
|
||||
raise SystemExit()
|
||||
|
||||
def _run_server_cycle(self) -> None:
|
||||
"""Spin up the server child-process and run it until exit."""
|
||||
"""Spin up the server subprocess and run it until exit."""
|
||||
|
||||
self._prep_subprocess_environment()
|
||||
|
||||
@ -317,7 +323,7 @@ class ServerManagerApp:
|
||||
# slight behavior tweaks. Hmm; should this be an argument instead?
|
||||
os.environ['BA_SERVER_WRAPPER_MANAGED'] = '1'
|
||||
|
||||
print(f'{Clr.CYN}Launching server child-process...{Clr.RST}')
|
||||
print(f'{Clr.CYN}Launching server subprocess...{Clr.RST}')
|
||||
binary_name = ('ballisticacore_headless.exe'
|
||||
if os.name == 'nt' else './ballisticacore_headless')
|
||||
self._subprocess = subprocess.Popen(
|
||||
@ -332,14 +338,17 @@ class ServerManagerApp:
|
||||
finally:
|
||||
self._kill_subprocess()
|
||||
|
||||
# If we want to die completely after this subprocess has ended,
|
||||
# tell the main thread to die.
|
||||
if self._wrapper_shutdown_desired:
|
||||
# Note: need to only do this if main thread is still in the
|
||||
# interpreter; otherwise it seems this can lead to deadlock.
|
||||
|
||||
# Only do this if the main thread is not already waiting for
|
||||
# us to die; otherwise it can lead to deadlock.
|
||||
if not self._done:
|
||||
self._done = True
|
||||
|
||||
# Our main thread is still be blocked in its prompt or
|
||||
# whatnot; let it know it should die.
|
||||
# This should break the main thread out of its blocking
|
||||
# interpreter call.
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
def _prep_subprocess_environment(self) -> None:
|
||||
@ -356,6 +365,7 @@ class ServerManagerApp:
|
||||
bincfg['Port'] = self._config.port
|
||||
bincfg['Auto Balance Teams'] = self._config.auto_balance_teams
|
||||
bincfg['Show Tutorial'] = False
|
||||
bincfg['Idle Exit Minutes'] = self._config.idle_exit_minutes
|
||||
with open('dist/ba_root/config.json', 'w') as outfile:
|
||||
outfile.write(json.dumps(bincfg))
|
||||
|
||||
@ -384,11 +394,9 @@ class ServerManagerApp:
|
||||
self._subprocess.stdin.flush()
|
||||
|
||||
def _run_subprocess_until_exit(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
assert current_thread() is self._subprocess_thread
|
||||
assert self._subprocess is not None
|
||||
assert self._subprocess.stdin is not None
|
||||
assert self._subprocess_launch_time is not None
|
||||
|
||||
# Send the initial server config which should kick things off.
|
||||
# (but make sure its values are still valid first)
|
||||
@ -414,35 +422,7 @@ class ServerManagerApp:
|
||||
self._subprocess_commands = []
|
||||
|
||||
# Request restarts/shut-downs for various reasons.
|
||||
sincelaunch = time.time() - self._subprocess_launch_time
|
||||
if (self._restart_minutes is not None and sincelaunch >
|
||||
(self._restart_minutes * 60.0)
|
||||
and not self._subprocess_sent_auto_restart):
|
||||
print(f'{Clr.CYN}restart_minutes ({self._restart_minutes})'
|
||||
f' elapsed; requesting child-process'
|
||||
f' soft restart...{Clr.RST}')
|
||||
self.restart()
|
||||
self._subprocess_sent_auto_restart = True
|
||||
if self._config.clean_exit_minutes is not None:
|
||||
elapsed = (time.time() - self._launch_time) / 60.0
|
||||
if (elapsed > self._config.clean_exit_minutes
|
||||
and not self._subprocess_sent_clean_exit):
|
||||
print(f'{Clr.CYN}clean_exit_minutes'
|
||||
f' ({self._config.clean_exit_minutes})'
|
||||
f' elapsed; requesting child-process'
|
||||
f' shutdown...{Clr.RST}')
|
||||
self.shutdown(immediate=False)
|
||||
self._subprocess_sent_clean_exit = True
|
||||
if self._config.unclean_exit_minutes is not None:
|
||||
elapsed = (time.time() - self._launch_time) / 60.0
|
||||
if (elapsed > self._config.unclean_exit_minutes
|
||||
and not self._subprocess_sent_unclean_exit):
|
||||
print(f'{Clr.CYN}unclean_exit_minutes'
|
||||
f' ({self._config.unclean_exit_minutes})'
|
||||
f' elapsed; requesting child-process'
|
||||
f' shutdown...{Clr.RST}')
|
||||
self.shutdown(immediate=True)
|
||||
self._subprocess_sent_unclean_exit = True
|
||||
self._request_shutdowns_or_restarts()
|
||||
|
||||
# If they want to force-kill our subprocess, simply exit this
|
||||
# loop; the cleanup code will kill the process.
|
||||
@ -457,17 +437,60 @@ class ServerManagerApp:
|
||||
if code == 0:
|
||||
clr = Clr.CYN
|
||||
slp = 0.0
|
||||
desc = ''
|
||||
elif code == 154:
|
||||
clr = Clr.CYN
|
||||
slp = 0.0
|
||||
desc = ' (idle_exit_minutes reached)'
|
||||
self._wrapper_shutdown_desired = True
|
||||
else:
|
||||
clr = Clr.SRED
|
||||
slp = 5.0 # Avoid super fast death loops.
|
||||
print(f'{clr}Server child-process exited'
|
||||
f' with code {code}.{Clr.RST}')
|
||||
desc = ''
|
||||
print(f'{clr}Server subprocess exited'
|
||||
f' with code {code}{desc}.{Clr.RST}')
|
||||
self._reset_subprocess_vars()
|
||||
time.sleep(slp)
|
||||
break
|
||||
|
||||
time.sleep(0.25)
|
||||
|
||||
def _request_shutdowns_or_restarts(self) -> None:
|
||||
assert current_thread() is self._subprocess_thread
|
||||
assert self._subprocess_launch_time is not None
|
||||
sincelaunch = time.time() - self._subprocess_launch_time
|
||||
|
||||
if (self._restart_minutes is not None and sincelaunch >
|
||||
(self._restart_minutes * 60.0)
|
||||
and not self._subprocess_sent_auto_restart):
|
||||
print(f'{Clr.CYN}restart_minutes ({self._restart_minutes})'
|
||||
f' elapsed; requesting subprocess'
|
||||
f' soft restart...{Clr.RST}')
|
||||
self.restart()
|
||||
self._subprocess_sent_auto_restart = True
|
||||
|
||||
if self._config.clean_exit_minutes is not None:
|
||||
elapsed = (time.time() - self._launch_time) / 60.0
|
||||
if (elapsed > self._config.clean_exit_minutes
|
||||
and not self._subprocess_sent_clean_exit):
|
||||
print(f'{Clr.CYN}clean_exit_minutes'
|
||||
f' ({self._config.clean_exit_minutes})'
|
||||
f' elapsed; requesting subprocess'
|
||||
f' shutdown...{Clr.RST}')
|
||||
self.shutdown(immediate=False)
|
||||
self._subprocess_sent_clean_exit = True
|
||||
|
||||
if self._config.unclean_exit_minutes is not None:
|
||||
elapsed = (time.time() - self._launch_time) / 60.0
|
||||
if (elapsed > self._config.unclean_exit_minutes
|
||||
and not self._subprocess_sent_unclean_exit):
|
||||
print(f'{Clr.CYN}unclean_exit_minutes'
|
||||
f' ({self._config.unclean_exit_minutes})'
|
||||
f' elapsed; requesting subprocess'
|
||||
f' shutdown...{Clr.RST}')
|
||||
self.shutdown(immediate=True)
|
||||
self._subprocess_sent_unclean_exit = True
|
||||
|
||||
def _reset_subprocess_vars(self) -> None:
|
||||
self._subprocess = None
|
||||
self._subprocess_launch_time = None
|
||||
@ -477,12 +500,12 @@ class ServerManagerApp:
|
||||
self._subprocess_force_kill_time = None
|
||||
|
||||
def _kill_subprocess(self) -> None:
|
||||
"""End the server process if it still exists."""
|
||||
"""End the server subprocess if it still exists."""
|
||||
assert current_thread() is self._subprocess_thread
|
||||
if self._subprocess is None:
|
||||
return
|
||||
|
||||
print(f'{Clr.CYN}Stopping server process...{Clr.RST}')
|
||||
print(f'{Clr.CYN}Stopping subprocess...{Clr.RST}')
|
||||
|
||||
# First, ask it nicely to die and give it a moment.
|
||||
# If that doesn't work, bring down the hammer.
|
||||
@ -492,7 +515,7 @@ class ServerManagerApp:
|
||||
except subprocess.TimeoutExpired:
|
||||
self._subprocess.kill()
|
||||
self._reset_subprocess_vars()
|
||||
print(f'{Clr.CYN}Server process stopped.{Clr.RST}')
|
||||
print(f'{Clr.CYN}Subprocess stopped.{Clr.RST}')
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<!-- THIS FILE IS AUTO GENERATED; DO NOT EDIT BY HAND -->
|
||||
<h4><em>last updated on 2020-11-12 for Ballistica version 1.5.29 build 20248</em></h4>
|
||||
<h4><em>last updated on 2020-11-15 for Ballistica version 1.5.29 build 20254</em></h4>
|
||||
<p>This page documents the Python classes and functions in the 'ba' module,
|
||||
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please <a href="mailto:support@froemling.net">let me know</a>. Happy modding!</p>
|
||||
<hr>
|
||||
|
||||
@ -16,6 +16,10 @@ auto AppConfig::Entry::FloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::OptionalFloatValue() const -> std::optional<float> {
|
||||
throw Exception("not an optional float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::StringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
@ -32,6 +36,11 @@ auto AppConfig::Entry::DefaultFloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultOptionalFloatValue() const
|
||||
-> std::optional<float> {
|
||||
throw Exception("not an optional float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultStringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
@ -78,6 +87,26 @@ class AppConfig::FloatEntry : public AppConfig::Entry {
|
||||
float default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::OptionalFloatEntry : public AppConfig::Entry {
|
||||
public:
|
||||
OptionalFloatEntry() = default;
|
||||
OptionalFloatEntry(const char* name, std::optional<float> default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kOptionalFloat; }
|
||||
auto Resolve() const -> std::optional<float> {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto OptionalFloatValue() const -> std::optional<float> override {
|
||||
return Resolve();
|
||||
}
|
||||
auto DefaultOptionalFloatValue() const -> std::optional<float> override {
|
||||
return default_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<float> default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::IntEntry : public AppConfig::Entry {
|
||||
public:
|
||||
IntEntry() = default;
|
||||
@ -157,6 +186,9 @@ void AppConfig::SetupEntries() {
|
||||
float_entries_[FloatID::kGoogleVRRenderTargetScale] =
|
||||
FloatEntry("GVR Render Target Scale", gvrrts_default);
|
||||
|
||||
optional_float_entries_[OptionalFloatID::kIdleExitMinutes] =
|
||||
OptionalFloatEntry("Idle Exit Minutes", std::optional<float>());
|
||||
|
||||
string_entries_[StringID::kResolutionAndroid] =
|
||||
StringEntry("Resolution (Android)", "Auto");
|
||||
string_entries_[StringID::kTouchActionControlType] =
|
||||
@ -220,6 +252,14 @@ auto AppConfig::Resolve(FloatID id) -> float {
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(OptionalFloatID id) -> std::optional<float> {
|
||||
auto i = optional_float_entries_.find(id);
|
||||
if (i == optional_float_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(StringID id) -> std::string {
|
||||
auto i = string_entries_.find(id);
|
||||
if (i == string_entries_.end()) {
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@ -30,6 +31,11 @@ class AppConfig {
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
enum class OptionalFloatID {
|
||||
kIdleExitMinutes,
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
enum class StringID {
|
||||
kResolutionAndroid,
|
||||
kTouchActionControlType,
|
||||
@ -68,16 +74,18 @@ class AppConfig {
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
enum class Type { kString, kInt, kFloat, kBool };
|
||||
enum class Type { kString, kInt, kFloat, kOptionalFloat, kBool };
|
||||
Entry() = default;
|
||||
explicit Entry(const char* name) : name_(name) {}
|
||||
virtual auto GetType() const -> Type = 0;
|
||||
auto name() const -> const std::string& { return name_; }
|
||||
virtual auto FloatValue() const -> float;
|
||||
virtual auto OptionalFloatValue() const -> std::optional<float>;
|
||||
virtual auto StringValue() const -> std::string;
|
||||
virtual auto IntValue() const -> int;
|
||||
virtual auto BoolValue() const -> bool;
|
||||
virtual auto DefaultFloatValue() const -> float;
|
||||
virtual auto DefaultOptionalFloatValue() const -> std::optional<float>;
|
||||
virtual auto DefaultStringValue() const -> std::string;
|
||||
virtual auto DefaultIntValue() const -> int;
|
||||
virtual auto DefaultBoolValue() const -> bool;
|
||||
@ -91,6 +99,7 @@ class AppConfig {
|
||||
|
||||
// Given specific ids, returns resolved values (fastest access).
|
||||
auto Resolve(FloatID id) -> float;
|
||||
auto Resolve(OptionalFloatID id) -> std::optional<float>;
|
||||
auto Resolve(StringID id) -> std::string;
|
||||
auto Resolve(IntID id) -> int;
|
||||
auto Resolve(BoolID id) -> bool;
|
||||
@ -113,6 +122,7 @@ class AppConfig {
|
||||
private:
|
||||
class StringEntry;
|
||||
class FloatEntry;
|
||||
class OptionalFloatEntry;
|
||||
class IntEntry;
|
||||
class BoolEntry;
|
||||
template <typename T>
|
||||
@ -120,6 +130,7 @@ class AppConfig {
|
||||
void SetupEntries();
|
||||
std::map<std::string, const Entry*> entries_by_name_;
|
||||
std::map<FloatID, FloatEntry> float_entries_;
|
||||
std::map<OptionalFloatID, OptionalFloatEntry> optional_float_entries_;
|
||||
std::map<IntID, IntEntry> int_entries_;
|
||||
std::map<StringID, StringEntry> string_entries_;
|
||||
std::map<BoolID, BoolEntry> bool_entries_;
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't change here.
|
||||
const int kAppBuildNumber = 20249;
|
||||
const int kAppBuildNumber = 20256;
|
||||
const char* kAppVersion = "1.5.29";
|
||||
|
||||
// Our standalone globals.
|
||||
|
||||
@ -507,6 +507,27 @@ void Game::UpdateKickVote() {
|
||||
}
|
||||
}
|
||||
|
||||
void Game::HandleQuitOnIdle() {
|
||||
if (idle_exit_minutes_) {
|
||||
float idle_seconds{g_input->input_idle_time() * 0.001f};
|
||||
if (!idle_exiting_ && idle_seconds > (idle_exit_minutes_.value() * 60.0f)) {
|
||||
idle_exiting_ = true;
|
||||
|
||||
PushCall([this, idle_seconds] {
|
||||
assert(InGameThread());
|
||||
|
||||
// Special exit value the wrapper script looks for to know we
|
||||
// idled out.
|
||||
g_app_globals->return_value = 154;
|
||||
|
||||
// Just go through _ba.quit()
|
||||
// FIXME: Shouldn't need to go out to the python layer here...
|
||||
g_python->obj(Python::ObjID::kQuitCall).Call();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bring our scenes, real-time timers, etc up to date.
|
||||
void Game::Update() {
|
||||
assert(InGameThread());
|
||||
@ -521,6 +542,8 @@ void Game::Update() {
|
||||
g_input->Update();
|
||||
UpdateKickVote();
|
||||
|
||||
HandleQuitOnIdle();
|
||||
|
||||
// Send the game roster to our clients if it's changed recently.
|
||||
if (game_roster_dirty_) {
|
||||
if (real_time > last_game_roster_send_time_ + 2500) {
|
||||
@ -548,9 +571,9 @@ void Game::Update() {
|
||||
|
||||
// TODO(ericf): On modern systems (VR and otherwise) we'll see 80hz, 90hz,
|
||||
// 120hz, 240hz, etc. It would be great to generalize this to gravitate
|
||||
// towards clean step patterns in all cases, not just the 60hz and 90hz cases
|
||||
// we handle now. In general we want stuff like 1,1,2,1,1,2,1,1,2, not
|
||||
// 1,1,1,2,1,2,2,1,1.
|
||||
// towards clean step patterns in all cases, not just the 60hz and 90hz
|
||||
// cases we handle now. In general we want stuff like 1,1,2,1,1,2,1,1,2,
|
||||
// not 1,1,1,2,1,2,2,1,1.
|
||||
|
||||
// Figure out where our net-time *should* be getting to to match real-time.
|
||||
millisecs_t target_master_time = real_time + master_time_offset_;
|
||||
@ -1212,7 +1235,7 @@ void Game::Draw() {
|
||||
g_graphics->BuildAndPushFrameDef();
|
||||
|
||||
// Now bring the game up to date.
|
||||
// By doing this *after* shipping a new frame_def we're reducing the
|
||||
// By doing this *after* shipping a new frame-def we're reducing the
|
||||
// chance of frame drops at the expense of adding a bit of visual latency.
|
||||
// Could maybe try to be smart about which to do first, but not sure
|
||||
// if its worth it.
|
||||
@ -1403,6 +1426,9 @@ void Game::ApplyConfig() {
|
||||
g_app_config->Resolve(AppConfig::BoolID::kDisableCameraGyro);
|
||||
g_graphics->set_camera_gyro_explicitly_disabled(disable_camera_gyro);
|
||||
|
||||
idle_exit_minutes_ =
|
||||
g_app_config->Resolve(AppConfig::OptionalFloatID::kIdleExitMinutes);
|
||||
|
||||
// Any platform-specific settings.
|
||||
g_platform->ApplyConfig();
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@ -251,6 +252,7 @@ class Game : public Module {
|
||||
auto mark_game_roster_dirty() -> void { game_roster_dirty_ = true; }
|
||||
|
||||
private:
|
||||
auto HandleQuitOnIdle() -> void;
|
||||
auto InitSpecialChars() -> void;
|
||||
auto Draw() -> void;
|
||||
auto PartyInvite(const std::string& name, const std::string& invite_id)
|
||||
@ -265,13 +267,6 @@ class Game : public Module {
|
||||
auto ScoresToBeatResponse(bool success, const std::list<ScoreToBeat>& scores,
|
||||
void* py_callback) -> void;
|
||||
|
||||
#if BA_VR_BUILD
|
||||
VRHandsState vr_hands_state_;
|
||||
#endif
|
||||
#if BA_RIFT_BUILD
|
||||
int rift_step_index_{};
|
||||
#endif
|
||||
|
||||
auto Prune() -> void; // Periodic pruning of dead stuff.
|
||||
auto Update() -> void;
|
||||
auto Process() -> void;
|
||||
@ -284,6 +279,13 @@ class Game : public Module {
|
||||
auto GetGameRosterMessage() -> std::vector<uint8_t>;
|
||||
auto Shutdown(bool soft) -> void;
|
||||
|
||||
#if BA_VR_BUILD
|
||||
VRHandsState vr_hands_state_;
|
||||
#endif
|
||||
#if BA_RIFT_BUILD
|
||||
int rift_step_index_{};
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ConnectionSet> connections_;
|
||||
std::list<std::pair<millisecs_t, PlayerSpec> > banned_players_;
|
||||
std::list<std::string> chat_messages_;
|
||||
@ -314,6 +316,8 @@ class Game : public Module {
|
||||
std::unordered_map<SpecialChar, std::string> special_char_strings_;
|
||||
bool ran_app_launch_commands_{};
|
||||
bool kick_idle_players_{};
|
||||
std::optional<float> idle_exit_minutes_{};
|
||||
bool idle_exiting_{};
|
||||
std::unique_ptr<TimerList> realtimers_;
|
||||
Timer* process_timer_{};
|
||||
Timer* headless_update_timer_{};
|
||||
|
||||
@ -268,7 +268,10 @@ void InputDevice::Update() {
|
||||
}
|
||||
|
||||
void InputDevice::UpdateLastInputTime() {
|
||||
// Keep our own individual time, and also let
|
||||
// the overall input system know something happened.
|
||||
last_input_time_ = g_game->master_time();
|
||||
g_input->mark_input_active();
|
||||
}
|
||||
|
||||
void InputDevice::InputCommand(InputType type, float value) {
|
||||
|
||||
@ -22,18 +22,18 @@ class InputDevice : public Object {
|
||||
~InputDevice() override;
|
||||
|
||||
/// Called when the device is attached/detached to a local player.
|
||||
virtual void AttachToLocalPlayer(Player* player);
|
||||
virtual void AttachToRemotePlayer(ConnectionToHost* connection_to_host,
|
||||
int remote_player_id);
|
||||
virtual void DetachFromPlayer();
|
||||
virtual auto AttachToLocalPlayer(Player* player) -> void;
|
||||
virtual auto AttachToRemotePlayer(ConnectionToHost* connection_to_host,
|
||||
int remote_player_id) -> void;
|
||||
virtual auto DetachFromPlayer() -> void;
|
||||
|
||||
/// Issues a command to the remote game to remove the player we're attached
|
||||
/// to.
|
||||
void RemoveRemotePlayerFromGame();
|
||||
auto RemoveRemotePlayerFromGame() -> void;
|
||||
|
||||
/// Return the (not necessarily unique) name of the input device.
|
||||
auto GetDeviceName() -> std::string;
|
||||
virtual void ResetHeldStates();
|
||||
virtual auto ResetHeldStates() -> void;
|
||||
|
||||
/// Return the default base player name for players using this input device.
|
||||
virtual auto GetDefaultPlayerName() -> std::string;
|
||||
@ -69,10 +69,10 @@ class InputDevice : public Object {
|
||||
auto index() const -> int { return index_; }
|
||||
|
||||
/// Read new control values from config.
|
||||
virtual void UpdateMapping() {}
|
||||
virtual auto UpdateMapping() -> void {}
|
||||
|
||||
/// Called during the game loop - for manual button repeats, etc.
|
||||
virtual void Update();
|
||||
virtual auto Update() -> void;
|
||||
|
||||
/// Return client id or -1 if local.
|
||||
virtual auto GetClientID() const -> int;
|
||||
@ -81,7 +81,7 @@ class InputDevice : public Object {
|
||||
virtual auto IsRemoteClient() const -> bool;
|
||||
|
||||
#if BA_SDL_BUILD || BA_MINSDL_BUILD
|
||||
virtual void HandleSDLEvent(const SDL_Event* e) {}
|
||||
virtual auto HandleSDLEvent(const SDL_Event* e) -> void {}
|
||||
#endif
|
||||
virtual auto GetAllowsConfiguring() -> bool { return true; }
|
||||
|
||||
@ -121,26 +121,14 @@ class InputDevice : public Object {
|
||||
auto has_py_ref() -> bool { return (py_ref_ != nullptr); }
|
||||
auto last_input_time() const -> millisecs_t { return last_input_time_; }
|
||||
virtual auto ShouldBeHiddenFromUser() -> bool;
|
||||
static void ResetRandomNames();
|
||||
|
||||
protected:
|
||||
void ShipBufferIfFull();
|
||||
|
||||
/// Pass some input command on to whatever we're connected to
|
||||
/// (player or remote-player).
|
||||
void InputCommand(InputType type, float value = 0.0f);
|
||||
|
||||
/// Called for all devices when they've successfully been added
|
||||
/// to the input-device list, have a valid ID, name, etc.
|
||||
virtual void ConnectionComplete() {}
|
||||
|
||||
/// Subclasses should call this to request a player in the local game.
|
||||
void RequestPlayer();
|
||||
static auto ResetRandomNames() -> void;
|
||||
|
||||
/// Return a human-readable name for the device's type.
|
||||
/// This is used for display and also for storing configs/etc.
|
||||
virtual auto GetRawDeviceName() -> std::string { return "Input Device"; }
|
||||
|
||||
auto number() const { return number_; }
|
||||
|
||||
/// Return any extra description for the device.
|
||||
/// This portion is only used for display and not for storing configs.
|
||||
/// An example is Mac PS3 controllers; they return "(bluetooth)" or "(usb)"
|
||||
@ -152,28 +140,45 @@ class InputDevice : public Object {
|
||||
/// a string.
|
||||
virtual auto GetDeviceIdentifier() -> std::string { return ""; }
|
||||
|
||||
/// Called for all devices when they've successfully been added
|
||||
/// to the input-device list, have a valid ID, name, etc.
|
||||
virtual auto ConnectionComplete() -> void {}
|
||||
|
||||
auto UpdateLastInputTime() -> void;
|
||||
|
||||
auto set_index(int index_in) -> void { index_ = index_in; }
|
||||
auto set_numbered_identifier(int n) -> void { number_ = n; }
|
||||
|
||||
protected:
|
||||
auto ShipBufferIfFull() -> void;
|
||||
|
||||
/// Pass some input command on to whatever we're connected to
|
||||
/// (player or remote-player).
|
||||
auto InputCommand(InputType type, float value = 0.0f) -> void;
|
||||
|
||||
/// Subclasses should call this to request a player in the local game.
|
||||
auto RequestPlayer() -> void;
|
||||
|
||||
auto remote_player_id() const -> int { return remote_player_id_; }
|
||||
void UpdateLastInputTime();
|
||||
|
||||
private:
|
||||
millisecs_t last_remote_input_commands_send_time_ = 0;
|
||||
auto GetPyInputDevice(bool new_ref) -> PyObject*;
|
||||
|
||||
millisecs_t last_remote_input_commands_send_time_{};
|
||||
std::vector<uint8_t> remote_input_commands_buffer_;
|
||||
|
||||
// note: this is in base-net-time
|
||||
millisecs_t last_input_time_ = 0;
|
||||
millisecs_t last_input_time_{};
|
||||
|
||||
// We're attached to *one* of these two.
|
||||
Object::WeakRef<Player> player_;
|
||||
Object::WeakRef<ConnectionToHost> remote_player_;
|
||||
|
||||
int remote_player_id_ = -1;
|
||||
PyObject* py_ref_ = nullptr;
|
||||
auto GetPyInputDevice(bool new_ref) -> PyObject*;
|
||||
void set_index(int index_in) { index_ = index_in; }
|
||||
void set_numbered_identifier(int n) { number_ = n; }
|
||||
int index_ = -1; // Our overall device index.
|
||||
int number_ = -1; // Our type-specific number.
|
||||
friend class Input;
|
||||
int remote_player_id_{-1};
|
||||
PyObject* py_ref_{};
|
||||
int index_{-1}; // Our overall device index.
|
||||
int number_{-1}; // Our type-specific number.
|
||||
|
||||
BA_DISALLOW_CLASS_COPIES(InputDevice);
|
||||
};
|
||||
|
||||
|
||||
@ -411,7 +411,7 @@ auto Input::GetNewNumberedIdentifier(const std::string& name,
|
||||
// suffix that's not taken.
|
||||
for (auto&& i : input_devices_) {
|
||||
if (i.exists()) {
|
||||
if ((i->GetRawDeviceName() == name) && i->number_ == num) {
|
||||
if ((i->GetRawDeviceName() == name) && i->number() == num) {
|
||||
in_use = true;
|
||||
break;
|
||||
}
|
||||
@ -717,18 +717,18 @@ auto Input::GetLocalActiveInputDeviceCount() -> int {
|
||||
|
||||
// This can get called alot so lets cache the value.
|
||||
millisecs_t current_time = g_game->master_time();
|
||||
if (current_time != last_have_many_local_active_input_devices_check_time_) {
|
||||
last_have_many_local_active_input_devices_check_time_ = current_time;
|
||||
if (current_time != last_get_local_active_input_device_count_check_time_) {
|
||||
last_get_local_active_input_device_count_check_time_ = current_time;
|
||||
|
||||
int count = 0;
|
||||
for (auto& input_device : input_devices_) {
|
||||
// Only count non-keyboard, non-touchscreen, local devices that have been
|
||||
// Tally up local non-keyboard, non-touchscreen devices that have been
|
||||
// used in the last minute.
|
||||
if (input_device.exists() && !(*input_device).IsKeyboard()
|
||||
&& !(*input_device).IsTouchScreen() && !(*input_device).IsUIOnly()
|
||||
&& (*input_device).IsLocal()
|
||||
&& ((*input_device).last_input_time() != 0
|
||||
&& g_game->master_time() - (*input_device).last_input_time()
|
||||
if (input_device.exists() && !input_device->IsKeyboard()
|
||||
&& !input_device->IsTouchScreen() && !input_device->IsUIOnly()
|
||||
&& input_device->IsLocal()
|
||||
&& (input_device->last_input_time() != 0
|
||||
&& g_game->master_time() - input_device->last_input_time()
|
||||
< 60000)) {
|
||||
count++;
|
||||
}
|
||||
@ -801,9 +801,9 @@ auto Input::ShouldCompletelyIgnoreInputDevice(InputDevice* input_device)
|
||||
return ignore_sdl_controllers_ && input_device->IsSDLController();
|
||||
}
|
||||
|
||||
auto Input::GetIdleTime() const -> millisecs_t {
|
||||
return GetRealTime() - last_input_time_;
|
||||
}
|
||||
// auto Input::GetIdleTime() const -> millisecs_t {
|
||||
// return GetRealTime() - last_input_time_;
|
||||
// }
|
||||
|
||||
void Input::UpdateEnabledControllerSubsystems() {
|
||||
assert(IsBootstrapped());
|
||||
@ -872,6 +872,14 @@ void Input::Update() {
|
||||
if (real_time - last_input_device_count_update_time_ > incr) {
|
||||
UpdateInputDeviceCounts();
|
||||
last_input_device_count_update_time_ = real_time;
|
||||
|
||||
// Keep our idle-time up to date.
|
||||
if (input_active_) {
|
||||
input_idle_time_ = 0;
|
||||
} else {
|
||||
input_idle_time_ += incr;
|
||||
}
|
||||
input_active_ = false;
|
||||
}
|
||||
|
||||
for (auto& input_device : input_devices_) {
|
||||
@ -1080,7 +1088,7 @@ void Input::HandleBackPress(bool from_toolbar) {
|
||||
|
||||
void Input::PushTextInputEvent(const std::string& text) {
|
||||
g_game->PushCall([this, text] {
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// Ignore if input is locked.
|
||||
if (IsInputLocked()) {
|
||||
@ -1115,7 +1123,7 @@ void Input::HandleJoystickEvent(const SDL_Event& event,
|
||||
}
|
||||
|
||||
// Make note that we're not idle.
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// And that this particular device isn't idle either.
|
||||
input_device->UpdateLastInputTime();
|
||||
@ -1139,7 +1147,7 @@ void Input::PushKeyReleaseEvent(const SDL_Keysym& keysym) {
|
||||
void Input::HandleKeyPress(const SDL_Keysym* keysym) {
|
||||
assert(InGameThread());
|
||||
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// Ignore all key presses if input is locked.
|
||||
if (IsInputLocked()) {
|
||||
@ -1304,7 +1312,7 @@ void Input::HandleKeyRelease(const SDL_Keysym* keysym) {
|
||||
|
||||
// Note: we want to let these through even if input is locked.
|
||||
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// Give Python a crack at it for captures, etc.
|
||||
if (g_python->HandleKeyReleaseEvent(*keysym)) {
|
||||
@ -1381,7 +1389,7 @@ auto Input::HandleMouseScroll(const Vector2f& amount) -> void {
|
||||
if (IsInputLocked()) {
|
||||
return;
|
||||
}
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
Widget* root_widget = g_ui->root_widget();
|
||||
if (std::abs(amount.y) > 0.0001f && root_widget) {
|
||||
@ -1417,7 +1425,7 @@ auto Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum)
|
||||
if (IsInputLocked()) {
|
||||
return;
|
||||
}
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
bool handled = false;
|
||||
Widget* root_widget = g_ui->root_widget();
|
||||
@ -1447,7 +1455,7 @@ auto Input::PushMouseMotionEvent(const Vector2f& position) -> void {
|
||||
auto Input::HandleMouseMotion(const Vector2f& position) -> void {
|
||||
assert(g_graphics);
|
||||
assert(InGameThread());
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
float old_cursor_pos_x = cursor_pos_x_;
|
||||
float old_cursor_pos_y = cursor_pos_y_;
|
||||
@ -1508,7 +1516,7 @@ auto Input::HandleMouseDown(int button, const Vector2f& position) -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
last_mouse_move_time_ = GetRealTime();
|
||||
mouse_move_count_++;
|
||||
@ -1575,7 +1583,7 @@ auto Input::PushMouseUpEvent(int button, const Vector2f& position) -> void {
|
||||
|
||||
auto Input::HandleMouseUp(int button, const Vector2f& position) -> void {
|
||||
assert(InGameThread());
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// Convert normalized view coords to our virtual ones.
|
||||
cursor_pos_x_ = g_graphics->PixelToVirtualX(
|
||||
@ -1629,7 +1637,7 @@ void Input::HandleTouchEvent(const TouchEvent& e) {
|
||||
return;
|
||||
}
|
||||
|
||||
ResetIdleTime();
|
||||
mark_input_active();
|
||||
|
||||
// float x = e.x;
|
||||
// float y = e.y;
|
||||
|
||||
@ -81,10 +81,11 @@ class Input {
|
||||
|
||||
// Get the total idle time for the system.
|
||||
// FIXME - should better coordinate this with InputDevice::getLastUsedTime().
|
||||
auto GetIdleTime() const -> millisecs_t;
|
||||
// auto GetIdleTime() const -> millisecs_t;
|
||||
|
||||
// Should be called whenever user-input of some form comes through.
|
||||
auto ResetIdleTime() -> void { last_input_time_ = GetRealTime(); }
|
||||
// auto ResetIdleTime() -> void { last_input_time_ = GetRealTime(); }
|
||||
auto mark_input_active() { input_active_ = true; }
|
||||
|
||||
// Should be called regularly to update button repeats, etc.
|
||||
auto Update() -> void;
|
||||
@ -128,6 +129,9 @@ class Input {
|
||||
auto PushDestroyKeyboardInputDevices() -> void;
|
||||
auto PushCreateKeyboardInputDevices() -> void;
|
||||
|
||||
/// Roughly how long in milliseconds have all input devices been idle.
|
||||
auto input_idle_time() const { return input_idle_time_; }
|
||||
|
||||
private:
|
||||
auto UpdateInputDeviceCounts() -> void;
|
||||
auto GetNewNumberedIdentifier(const std::string& name,
|
||||
@ -151,8 +155,11 @@ class Input {
|
||||
auto UpdateModKeyStates(const SDL_Keysym* keysym, bool press) -> void;
|
||||
auto CreateKeyboardInputDevices() -> void;
|
||||
auto DestroyKeyboardInputDevices() -> void;
|
||||
|
||||
bool input_active_{};
|
||||
millisecs_t input_idle_time_{};
|
||||
int local_active_input_device_count_{};
|
||||
millisecs_t last_have_many_local_active_input_devices_check_time_{};
|
||||
millisecs_t last_get_local_active_input_device_count_check_time_{};
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, int> >
|
||||
reserved_identifiers_;
|
||||
int max_controller_count_so_far_{};
|
||||
@ -165,7 +172,7 @@ class Input {
|
||||
bool have_non_touch_inputs_{};
|
||||
float cursor_pos_x_{};
|
||||
float cursor_pos_y_{};
|
||||
millisecs_t last_input_time_{};
|
||||
// millisecs_t last_input_time_{};
|
||||
millisecs_t last_click_time_{};
|
||||
millisecs_t double_click_time_{200};
|
||||
millisecs_t last_mouse_move_time_{};
|
||||
|
||||
@ -177,7 +177,7 @@ auto PyGetIdleTime(PyObject* self, PyObject* args) -> PyObject* {
|
||||
BA_PYTHON_TRY;
|
||||
Platform::SetLastPyCall("get_idle_time");
|
||||
return PyLong_FromLong(static_cast_check_fit<long>( // NOLINT
|
||||
g_input ? g_input->GetIdleTime() : 0));
|
||||
g_input ? g_input->input_idle_time() : 0));
|
||||
BA_PYTHON_CATCH;
|
||||
}
|
||||
|
||||
@ -964,7 +964,7 @@ auto PythonMethodsSystem::GetMethods() -> std::vector<PyMethodDef> {
|
||||
"\n"
|
||||
"(internal)\n"
|
||||
"\n"
|
||||
"Returns the amount of time since any game input has been processed"},
|
||||
"Returns the amount of time since any game input has been received."},
|
||||
|
||||
{"set_have_mods", PySetHaveMods, METH_VARARGS,
|
||||
"set_have_mods(have_mods: bool) -> None\n"
|
||||
|
||||
@ -2442,6 +2442,26 @@ auto Python::GetRawConfigValue(const char* name, float default_value) -> float {
|
||||
}
|
||||
}
|
||||
|
||||
auto Python::GetRawConfigValue(const char* name,
|
||||
std::optional<float> default_value)
|
||||
-> std::optional<float> {
|
||||
assert(InGameThread());
|
||||
assert(objexists(ObjID::kConfig));
|
||||
PyObject* value = PyDict_GetItemString(obj(ObjID::kConfig).get(), name);
|
||||
if (value == nullptr) {
|
||||
return default_value;
|
||||
}
|
||||
try {
|
||||
if (value == Py_None) {
|
||||
return std::optional<float>();
|
||||
}
|
||||
return GetPyFloat(value);
|
||||
} catch (const std::exception&) {
|
||||
Log("expected a float for config value '" + std::string(name) + "'");
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
auto Python::GetRawConfigValue(const char* name, int default_value) -> int {
|
||||
assert(InGameThread());
|
||||
assert(objexists(ObjID::kConfig));
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@ -160,6 +161,8 @@ class Python {
|
||||
auto GetRawConfigValue(const char* name, const char* default_value)
|
||||
-> std::string;
|
||||
auto GetRawConfigValue(const char* name, float default_value) -> float;
|
||||
auto GetRawConfigValue(const char* name, std::optional<float> default_value)
|
||||
-> std::optional<float>;
|
||||
auto GetRawConfigValue(const char* name, int default_value) -> int;
|
||||
auto GetRawConfigValue(const char* name, bool default_value) -> bool;
|
||||
void SetRawConfigValue(const char* name, float value);
|
||||
|
||||
@ -230,7 +230,6 @@ void UI::AddWidget(Widget* w, ContainerWidget* parent) {
|
||||
|
||||
auto UI::SendWidgetMessage(const WidgetMessage& m) -> int {
|
||||
if (!root_widget_.exists()) {
|
||||
// Log("SendWidgetMessage() called before root widget created");
|
||||
return false;
|
||||
}
|
||||
return root_widget_->HandleMessage(m);
|
||||
|
||||
@ -91,23 +91,26 @@ class ServerConfig:
|
||||
# http://bombsquadgame.com/accountquery?id=ACCOUNT_ID_HERE
|
||||
stats_url: Optional[str] = None
|
||||
|
||||
# If present, the server will attempt to gracefully exit after this
|
||||
# amount of time. A graceful exit can occur at the end of a series
|
||||
# If present, the server manager will attempt to gracefully exit after
|
||||
# this amount of time. A graceful exit can occur at the end of a series
|
||||
# or other opportune time.
|
||||
# Servers with no exit times set will run indefinitely (though the server
|
||||
# binary will be restarted periodically to clear any leaked memory).
|
||||
# Servers with no exit times set will run indefinitely, though the
|
||||
# server binary will be restarted periodically to clear any memory
|
||||
# leaks or other bad state.
|
||||
clean_exit_minutes: Optional[float] = None
|
||||
|
||||
# If present, the server will shut down immediately after the given
|
||||
# amount of time). This can be useful as a fallback for clean_exit_time.
|
||||
# Servers with no exit times set will run indefinitely (though the server
|
||||
# binary will be restarted periodically to clear any leaked memory).
|
||||
# If present, the server manager will shut down immediately after this
|
||||
# amount of time. This can be useful as a fallback for clean_exit_time.
|
||||
# Servers with no exit times set will run indefinitely, though the
|
||||
# server binary will be restarted periodically to clear any memory
|
||||
# leaks or other bad state.
|
||||
unclean_exit_minutes: Optional[float] = None
|
||||
|
||||
# If present, the server will shut down immediately if this amount of
|
||||
# time passes with no connected clients.
|
||||
# Servers with no exit times set will run indefinitely (though the server
|
||||
# binary will be restarted periodically to clear any leaked memory).
|
||||
# time passes with no activity from any players.
|
||||
# Servers with no exit times set will run indefinitely, though the
|
||||
# server binary will be restarted periodically to clear any memory
|
||||
# leaks or other bad state.
|
||||
idle_exit_minutes: Optional[float] = None
|
||||
|
||||
|
||||
|
||||
@ -642,11 +642,11 @@ def _get_server_config_template_yaml(projroot: str) -> str:
|
||||
if vname == 'playlist_code':
|
||||
# User wouldn't want to pass the default of None here.
|
||||
vval = 12345
|
||||
elif vname == 'clean_exit_time':
|
||||
elif vname == 'clean_exit_minutes':
|
||||
vval = 60
|
||||
elif vname == 'unclean_exit_time':
|
||||
elif vname == 'unclean_exit_minutes':
|
||||
vval = 90
|
||||
elif vname == 'idle_exit_time':
|
||||
elif vname == 'idle_exit_minutes':
|
||||
vval = 20
|
||||
elif vname == 'stats_url':
|
||||
vval = ('https://mystatssite.com/'
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user