ripped out vestigial thread-module system and other c++ cleanup

This commit is contained in:
Eric 2022-09-09 14:49:58 -07:00
parent 38b94df9a5
commit b77feb4d0e
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
77 changed files with 782 additions and 930 deletions

View File

@ -3995,50 +3995,50 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/cb/30/c0e7d89b368751cf26b1cef50664",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/67/14/230fd06e06cb9040a03f6e74c1d7",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b6/59/c8badf86a08fa213610edbcfedac",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/13/399ce592b72284029a6291b89a0e",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ae/4c/84fcfbec817118d204402be28de8",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/46/c0/9f10a2154b3ccb59a9710492c1c8",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/68/9b/b68e52f40fbab1c3a8cf3a26c596",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2e/f4/ab1229ec3bda80f8f47c3cf8bc62",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9d/3b/3bd29560197990be6a4da17604b8",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d1/4f/9fb5889baf79de40ced767a75f47",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4b/b0/7854b4f01fd1516fdb2aaabe74af",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5d/4d/70ee88bd453725a3af8469b832a9",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/bd/df/b2fdbba76d96d5d64ae222a9cb5f",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/4b/d5/01958f073d57e11b8a7af71c88b2",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/f0/9f544b99cd379536e7b776e726ab",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ff/7f/96e6a7b76cf6c5a2b2cd381e4700",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/10/58/8dea7e5b6987346f9116d3758a6b",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/7f/cd/93c54dc256e1b3c3575d44efb730",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/d0/d0/8d9ae932ed95175a9aae1310de96",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/d9/30/7b9bf572809afa6102afb5c396c4",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4f/62/10fba4712bc7c1649de95f6d435a",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7f/74/101f9c7cdbea3924a3eb753372b8",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3d/10/0213350b34d61574be888f434776",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/69/3195bc2ac8ab95a072deb6328383",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fb/b8/1d53882382e1bdffea589b8abd52",
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/66/7d/4cf7e08113052eae664b714bdc64",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0a/3e/0e759cd0a7a1e098e3c1daff0827",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8a/a0/eb48807010d1e7fa6a4956d0967e",
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/45/1b/cd3972f7bb9cb4a7d4bfeb0977fb",
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b2/c1/92b3546f4ea454e3813062e7c2a0",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d8/7f/694dfd1b8580077fd1d3ab585611",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/ea/38c4415b9582016f40ecb9c371f6",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/54/3f42d8cbbabb633fe47dc9e63f8b",
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e0/9c/78bc9f3658e495238a9ae7095ad0",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3e/60/48c02923c5c7e7b8a3a705cdbe49",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7b/cb/93116682d10e6f868ec9215c0ab2",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/98/9b/43700428b53a552af3f32aa2577c",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/37/e2/b6da8c9dd13e91ebb705da51c653",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/f5/93/774197e1f445c4dee88ed2910006",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/2e/7c/139654fe813f9254ed5de42c7ef7",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ef/ea/8ccb18f593d003984a1f56cf0c78",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/99/f6/7e56f9dd76d97493a53ebe755fba",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/cf/d5/2d58578076ced4f6e2b218b18d11",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/a2/07/d85112deb0bf08aa01d18f098b94",
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/30/30/76f78a5f222c39ea755b5098b51f",
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/34/fd/2d0510b7da8899c78378296e38d3",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5b/49/356d0f8f7fee022b778ba14d6612",
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bb/ee/7d33058363bb336fa9c52eb87207",
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b4/88/1574d641b1ea03e7f6e388f614f9",
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bb/09/a68b6c70115f316fc5c044286e53",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/44/47/02f87d28d4b3129f32e6233c34f5",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/09/4f/fea5a0cd8b43fd4bfa91238c584e",
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c8/4a/769908c66c6d1eb74c8103ff5c30",
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c8/33/2067e22417e6546ebc62623a9186",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d1/62/eb550f8507ff920da54219ff195b",
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/2d/91/3b78ced38a1060fc6fe85438792c",
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/84/eb/9de1b6c883311cbd8729341599b9",
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d3/3b/86294ecdabdce129672784085280",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/07/0037ac9ef4cea6bc3bfdb3682729",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/29/e2/f84c711e2c7cc601dd5847cc60c1",
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/99/17/17a739a9a1e2f19449a5756a5c19",
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/71/bc/e74bae5930032177abc44078e056",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/1e/6c/5b7173be93adfc9c0e1d007cf65e",
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/bc/07/b08c86874771ee922da097fb0b06",
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/63/9dc4b8c56eaf6ee5259e4b60bc46",
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e3/25/692a46a2437c11055c58d95f0a10",
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/06/8f/1f7fcebee0a16c8f843cabebba1c",
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ec/c2/51a05d1066c208b3a17e8828238e",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a8/e3/88791ad2fbd654a60e6652e64554",
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6b/06/86d995f8ea1b6e27ac3b9818e267",
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f3/7d/b632f966f8ae8c39dc9015fb8eee",
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e9/9e/39dd389564d0a14f01bdb54b82ce",
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/7e/e865aa9e850350deefa7a826b89e",
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/1f/20ea75eb2a38a5aa740aebf68893",
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/87/49cc3fb53218a2c19633551d0f38",
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ac/38/4fc5345b0bbc0cc6965d510b2074",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b3/b0/c4f8ff30c9b62d2d0dbc926095fa",
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ec/96/b22889b59ba3279627d8de232a23",
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0e/6b/3b6758dd4b6b63ab9f666af9396a",
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/8c/6fa15f798f6ca73d753571f8cd4b",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/2d/af/9cc4b56883bcd72f65b9a814c401",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/5a/18/c0ff4809d4550c794a516a320ed1",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/27/25/4b3bd86f0ad71b2ad29ea8a9837d",
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/2f/1a/29105b0e71173826d2ea404982b6",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/b7/8a/3f0476b3981a630cc6ef8f1ec364",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/4d/32/9ffcdc35d17ac6c9e3bb2bfd0e3c",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/be/f0/8901dcd613fec9e58d02b08dce4b",
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/e3/c1/f79676f11b4f4778e26933ba42d7",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7",
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/98/12/571b2160d69d42580e8f31fa6a8d"
}

View File

@ -1,4 +1,4 @@
### 1.7.7 (build 20778, api 7, 2022-09-06)
### 1.7.7 (build 20794, api 7, 2022-09-09)
- Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread.
- Improved logging of missing playlist game types.
- Some ba.Lstr functionality can now be used in background threads.

View File

@ -1 +1 @@
161826047581351482927423840323346436406
265405600297036989512577170988205019181

View File

@ -3,6 +3,9 @@
"""Bootstrapping."""
from __future__ import annotations
import os
import sys
import signal
import threading
from typing import TYPE_CHECKING
@ -11,6 +14,8 @@ import _ba
if TYPE_CHECKING:
from typing import Any, TextIO, Callable
_g_did_bootstrap = False # pylint: disable=invalid-name
def bootstrap() -> None:
"""Run bootstrapping logic.
@ -18,9 +23,10 @@ def bootstrap() -> None:
This is the very first userland code that runs.
It sets up low level environment bits and creates the app instance.
"""
import os
import sys
import signal
global _g_did_bootstrap # pylint: disable=global-statement, invalid-name
if _g_did_bootstrap:
raise RuntimeError('Bootstrap has already been called.')
_g_did_bootstrap = True
# The first thing we set up is capturing/redirecting Python
# stdout/stderr so we can at least debug problems on systems where
@ -32,7 +38,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
expected_build = 20778
expected_build = 20794
running_build: int = env['build_number']
if running_build != expected_build:
print(

View File

@ -241,8 +241,6 @@ add_executable(ballisticacore
${BA_SRC_ROOT}/ballistica/core/logging.h
${BA_SRC_ROOT}/ballistica/core/macros.cc
${BA_SRC_ROOT}/ballistica/core/macros.h
${BA_SRC_ROOT}/ballistica/core/module.cc
${BA_SRC_ROOT}/ballistica/core/module.h
${BA_SRC_ROOT}/ballistica/core/object.cc
${BA_SRC_ROOT}/ballistica/core/object.h
${BA_SRC_ROOT}/ballistica/core/thread.cc

View File

@ -232,8 +232,6 @@
<ClInclude Include="..\..\src\ballistica\core\logging.h" />
<ClCompile Include="..\..\src\ballistica\core\macros.cc" />
<ClInclude Include="..\..\src\ballistica\core\macros.h" />
<ClCompile Include="..\..\src\ballistica\core\module.cc" />
<ClInclude Include="..\..\src\ballistica\core\module.h" />
<ClCompile Include="..\..\src\ballistica\core\object.cc" />
<ClInclude Include="..\..\src\ballistica\core\object.h" />
<ClCompile Include="..\..\src\ballistica\core\thread.cc" />

View File

@ -130,12 +130,6 @@
<ClInclude Include="..\..\src\ballistica\core\macros.h">
<Filter>ballistica\core</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\core\module.cc">
<Filter>ballistica\core</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\core\module.h">
<Filter>ballistica\core</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\core\object.cc">
<Filter>ballistica\core</Filter>
</ClCompile>

View File

@ -227,8 +227,6 @@
<ClInclude Include="..\..\src\ballistica\core\logging.h" />
<ClCompile Include="..\..\src\ballistica\core\macros.cc" />
<ClInclude Include="..\..\src\ballistica\core\macros.h" />
<ClCompile Include="..\..\src\ballistica\core\module.cc" />
<ClInclude Include="..\..\src\ballistica\core\module.h" />
<ClCompile Include="..\..\src\ballistica\core\object.cc" />
<ClInclude Include="..\..\src\ballistica\core\object.h" />
<ClCompile Include="..\..\src\ballistica\core\thread.cc" />

View File

@ -130,12 +130,6 @@
<ClInclude Include="..\..\src\ballistica\core\macros.h">
<Filter>ballistica\core</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\core\module.cc">
<Filter>ballistica\core</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\core\module.h">
<Filter>ballistica\core</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\core\object.cc">
<Filter>ballistica\core</Filter>
</ClCompile>

View File

@ -17,7 +17,7 @@
namespace ballistica {
App::App(Thread* thread)
: Module("app", thread), stress_test_(std::make_unique<StressTest>()) {
: thread_(thread), stress_test_(std::make_unique<StressTest>()) {
assert(g_app == nullptr);
g_app = this;
@ -37,8 +37,6 @@ void App::PostInit() {
g_platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
}
App::~App() = default;
auto App::ManagesEventLoop() const -> bool {
// We have 2 redundant values for essentially the same thing;
// should get rid of IsEventPushMode() once we've created
@ -104,7 +102,7 @@ void App::SetScreenResolution(float width, float height) {
}
void App::PushShutdownCompleteCall() {
PushCall([this] { ShutdownComplete(); });
thread()->PushCall([this] { ShutdownComplete(); });
}
void App::ShutdownComplete() {
@ -154,8 +152,9 @@ void App::OnPause() {
g_graphics->SetGyroEnabled(false);
// IMPORTANT: Any on-pause related stuff that threads need to do must
// be done from their HandleThreadPause(). If we push runnables to them,
// they may or may not be called before the thread is actually paused.
// be done from registered pause-callbacks. If we instead push runnables
// to them from here they may or may not be called before the thread
// is actually paused.
Thread::SetThreadsPaused(true);
@ -210,7 +209,7 @@ void App::OnResume() {
}
auto App::GetProductPrice(const std::string& product) -> std::string {
std::lock_guard<std::mutex> lock(product_prices_mutex_);
std::scoped_lock lock(product_prices_mutex_);
auto i = product_prices_.find(product);
if (i == product_prices_.end()) {
return "";
@ -221,7 +220,7 @@ auto App::GetProductPrice(const std::string& product) -> std::string {
void App::SetProductPrice(const std::string& product,
const std::string& price) {
std::lock_guard<std::mutex> lock(product_prices_mutex_);
std::scoped_lock lock(product_prices_mutex_);
product_prices_[product] = price;
}
@ -261,7 +260,7 @@ void App::PrimeEventPump() {
void App::PushShowOnlineScoreUICall(const std::string& show,
const std::string& game,
const std::string& game_version) {
PushCall([show, game, game_version] {
thread()->PushCall([show, game, game_version] {
assert(InMainThread());
g_platform->ShowOnlineScoreUI(show, game, game_version);
});
@ -269,7 +268,7 @@ void App::PushShowOnlineScoreUICall(const std::string& show,
void App::PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
const std::string& telnet_password) {
PushCall([port, telnet_port, enable_telnet, telnet_password] {
thread()->PushCall([port, telnet_port, enable_telnet, telnet_password] {
assert(InMainThread());
// Kick these off if they don't exist.
// (do we want to support changing ports on existing ones?)
@ -290,58 +289,59 @@ void App::PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
void App::PushPurchaseAckCall(const std::string& purchase,
const std::string& order_id) {
PushCall(
thread()->PushCall(
[purchase, order_id] { g_platform->PurchaseAck(purchase, order_id); });
}
void App::PushGetScoresToBeatCall(const std::string& level,
const std::string& config,
void* py_callback) {
PushCall([level, config, py_callback] {
thread()->PushCall([level, config, py_callback] {
assert(InMainThread());
g_platform->GetScoresToBeat(level, config, py_callback);
});
}
void App::PushPurchaseCall(const std::string& item) {
PushCall([item] {
thread()->PushCall([item] {
assert(InMainThread());
g_platform->Purchase(item);
});
}
void App::PushRestorePurchasesCall() {
PushCall([] {
thread()->PushCall([] {
assert(InMainThread());
g_platform->RestorePurchases();
});
}
void App::PushOpenURLCall(const std::string& url) {
PushCall([url] { g_platform->OpenURL(url); });
thread()->PushCall([url] { g_platform->OpenURL(url); });
}
void App::PushGetFriendScoresCall(const std::string& game,
const std::string& game_version, void* data) {
PushCall([game, game_version, data] {
thread()->PushCall([game, game_version, data] {
g_platform->GetFriendScores(game, game_version, data);
});
}
void App::PushSubmitScoreCall(const std::string& game,
const std::string& game_version, int64_t score) {
PushCall([game, game_version, score] {
thread()->PushCall([game, game_version, score] {
g_platform->SubmitScore(game, game_version, score);
});
}
void App::PushAchievementReportCall(const std::string& achievement) {
PushCall([achievement] { g_platform->ReportAchievement(achievement); });
thread()->PushCall(
[achievement] { g_platform->ReportAchievement(achievement); });
}
void App::PushStringEditCall(const std::string& name, const std::string& value,
int max_chars) {
PushCall([name, value, max_chars] {
thread()->PushCall([name, value, max_chars] {
static millisecs_t last_edit_time = 0;
millisecs_t t = GetRealTime();
@ -357,13 +357,13 @@ void App::PushStringEditCall(const std::string& name, const std::string& value,
}
void App::PushSetStressTestingCall(bool enable, int player_count) {
PushCall([this, enable, player_count] {
thread()->PushCall([this, enable, player_count] {
stress_test_->SetStressTesting(enable, player_count);
});
}
void App::PushResetAchievementsCall() {
PushCall([] { g_platform->ResetAchievements(); });
thread()->PushCall([] { g_platform->ResetAchievements(); });
}
void App::OnBootstrapComplete() {
@ -386,7 +386,7 @@ void App::OnBootstrapComplete() {
}
void App::PushCursorUpdate(bool vis) {
PushCall([vis] {
thread()->PushCall([vis] {
assert(InMainThread());
g_platform->SetHardwareCursorVisible(vis);
});

View File

@ -8,17 +8,17 @@
#include <string>
#include <unordered_map>
#include "ballistica/core/module.h"
#include "ballistica/app/stress_test.h"
#include "ballistica/ballistica.h"
namespace ballistica {
/// Our high level app interface module.
/// It runs in the main thread and is what platform wrappers
/// should primarily interact with.
class App : public Module {
class App {
public:
explicit App(Thread* thread);
~App() override;
/// This gets run after the constructor completes.
/// Any setup that may trigger a virtual method/etc. should go here.
@ -124,12 +124,14 @@ class App : public Module {
auto PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
const std::string& telnet_password) -> void;
auto PushShutdownCompleteCall() -> void;
auto thread() const -> Thread* { return thread_; }
private:
auto UpdatePauseResume() -> void;
auto OnPause() -> void;
auto OnResume() -> void;
auto ShutdownComplete() -> void;
Thread* thread_{};
bool done_{};
bool server_wrapper_managed_{};
bool sys_paused_app_{};

View File

@ -31,6 +31,7 @@ class AppGlobals {
/// Program argument values (on applicable platforms).
char** argv{};
bool threads_paused{};
std::unordered_map<std::string, NodeType*> node_types;
std::unordered_map<int, NodeType*> node_types_by_id;
std::unordered_map<std::string, NodeMessageType> node_message_types;

View File

@ -13,10 +13,10 @@ HeadlessApp::HeadlessApp(Thread* thread) : App(thread) {
// Handle a few misc things like stress-test updates.
// (SDL builds set up a similar timer so we need to also).
// This can probably go away at some point.
NewThreadTimer(10, true, NewLambdaRunnable([this] {
assert(g_app);
g_app->RunEvents();
}));
this->thread()->NewTimer(10, true, NewLambdaRunnable([this] {
assert(g_app);
g_app->RunEvents();
}));
}
} // namespace ballistica

View File

@ -3,6 +3,7 @@
#include "ballistica/app/vr_app.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/graphics/renderer.h"
@ -13,7 +14,7 @@ VRApp::VRApp(Thread* thread) : App(thread) {}
auto VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state)
-> void {
PushCall([this, state] {
thread()->PushCall([this, state] {
// Convert this to a full hands state, adding in some simple elbow
// positioning of our own and left/right.
VRHandsState s;

View File

@ -4,6 +4,7 @@
#include "ballistica/audio/audio_server.h"
#include "ballistica/audio/audio_source.h"
#include "ballistica/core/thread.h"
#include "ballistica/media/data/sound_data.h"
namespace ballistica {
@ -40,11 +41,12 @@ void Audio::SetListenerOrientation(const Vector3f& forward,
// This stops a particular sound play ID only.
void Audio::PushSourceStopSoundCall(uint32_t play_id) {
g_audio_server->PushCall([play_id] { g_audio_server->StopSound(play_id); });
g_audio_server->thread()->PushCall(
[play_id] { g_audio_server->StopSound(play_id); });
}
void Audio::PushSourceFadeOutCall(uint32_t play_id, uint32_t time) {
g_audio_server->PushCall(
g_audio_server->thread()->PushCall(
[play_id, time] { g_audio_server->FadeSoundOut(play_id, time); });
}
@ -60,7 +62,7 @@ auto Audio::SourceBeginNew() -> AudioSource* {
// Got to make sure to hold this until we've locked the source.
// Otherwise, theoretically, the audio thread could make our source
// available again before we can use it.
std::lock_guard<std::mutex> lock(available_sources_mutex_);
std::lock_guard lock(available_sources_mutex_);
// If there's an available source, reserve and return it.
auto i = available_sources_.begin();

View File

@ -7,7 +7,6 @@
#include <mutex>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
namespace ballistica {

View File

@ -8,6 +8,7 @@
#include "ballistica/audio/audio_source.h"
#include "ballistica/audio/audio_streamer.h"
#include "ballistica/audio/ogg_stream.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/generic/timer.h"
#include "ballistica/math/vector3f.h"
@ -195,7 +196,7 @@ void AudioServer::SetPaused(bool pause) {
}
void AudioServer::PushSourceSetIsMusicCall(uint32_t play_id, bool val) {
PushCall([this, play_id, val] {
thread()->PushCall([this, play_id, val] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetIsMusic(val);
@ -204,7 +205,7 @@ void AudioServer::PushSourceSetIsMusicCall(uint32_t play_id, bool val) {
}
void AudioServer::PushSourceSetPositionalCall(uint32_t play_id, bool val) {
PushCall([this, play_id, val] {
thread()->PushCall([this, play_id, val] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetPositional(val);
@ -214,7 +215,7 @@ void AudioServer::PushSourceSetPositionalCall(uint32_t play_id, bool val) {
void AudioServer::PushSourceSetPositionCall(uint32_t play_id,
const Vector3f& p) {
PushCall([this, play_id, p] {
thread()->PushCall([this, play_id, p] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetPosition(p.x, p.y, p.z);
@ -223,7 +224,7 @@ void AudioServer::PushSourceSetPositionCall(uint32_t play_id,
}
void AudioServer::PushSourceSetGainCall(uint32_t play_id, float val) {
PushCall([this, play_id, val] {
thread()->PushCall([this, play_id, val] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetGain(val);
@ -232,7 +233,7 @@ void AudioServer::PushSourceSetGainCall(uint32_t play_id, float val) {
}
void AudioServer::PushSourceSetFadeCall(uint32_t play_id, float val) {
PushCall([this, play_id, val] {
thread()->PushCall([this, play_id, val] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetFade(val);
@ -241,7 +242,7 @@ void AudioServer::PushSourceSetFadeCall(uint32_t play_id, float val) {
}
void AudioServer::PushSourceSetLoopingCall(uint32_t play_id, bool val) {
PushCall([this, play_id, val] {
thread()->PushCall([this, play_id, val] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->SetLooping(val);
@ -251,7 +252,7 @@ void AudioServer::PushSourceSetLoopingCall(uint32_t play_id, bool val) {
void AudioServer::PushSourcePlayCall(uint32_t play_id,
Object::Ref<SoundData>* sound) {
PushCall([this, play_id, sound] {
thread()->PushCall([this, play_id, sound] {
ThreadSource* s = GetPlayingSound(play_id);
// If this play command is valid, pass it along.
@ -271,7 +272,7 @@ void AudioServer::PushSourcePlayCall(uint32_t play_id,
}
void AudioServer::PushSourceStopCall(uint32_t play_id) {
PushCall([this, play_id] {
thread()->PushCall([this, play_id] {
ThreadSource* s = GetPlayingSound(play_id);
if (s) {
s->Stop();
@ -280,7 +281,7 @@ void AudioServer::PushSourceStopCall(uint32_t play_id) {
}
void AudioServer::PushSourceEndCall(uint32_t play_id) {
PushCall([this, play_id] {
thread()->PushCall([this, play_id] {
ThreadSource* s = GetPlayingSound(play_id);
assert(s);
s->client_source()->Lock(5);
@ -292,11 +293,11 @@ void AudioServer::PushSourceEndCall(uint32_t play_id) {
}
void AudioServer::PushResetCall() {
PushCall([this] { Reset(); });
thread()->PushCall([this] { Reset(); });
}
void AudioServer::PushSetListenerPositionCall(const Vector3f& p) {
PushCall([this, p] {
thread()->PushCall([this, p] {
#if BA_ENABLE_AUDIO
if (!paused_) {
ALfloat lpos[3] = {p.x, p.y, p.z};
@ -309,7 +310,7 @@ void AudioServer::PushSetListenerPositionCall(const Vector3f& p) {
void AudioServer::PushSetListenerOrientationCall(const Vector3f& forward,
const Vector3f& up) {
PushCall([this, forward, up] {
thread()->PushCall([this, forward, up] {
#if BA_ENABLE_AUDIO
if (!paused_) {
ALfloat lorient[6] = {forward.x, forward.y, forward.z, up.x, up.y, up.z};
@ -321,17 +322,17 @@ void AudioServer::PushSetListenerOrientationCall(const Vector3f& forward,
}
AudioServer::AudioServer(Thread* thread)
: Module("audio", thread),
impl_{new AudioServer::Impl()}
// impl_{std::make_unique<AudioServer::Impl>()}
{
: thread_(thread), impl_{new AudioServer::Impl()} {
// we're a singleton...
assert(g_audio_server == nullptr);
g_audio_server = this;
thread->AddPauseCallback(NewLambdaRunnableRaw([this] { OnThreadPause(); }));
thread->AddResumeCallback(NewLambdaRunnableRaw([this] { OnThreadResume(); }));
// Get our thread to give us periodic processing time.
process_timer_ = NewThreadTimer(kAudioProcessIntervalNormal, true,
NewLambdaRunnable([this] { Process(); }));
process_timer_ = thread->NewTimer(kAudioProcessIntervalNormal, true,
NewLambdaRunnable([this] { Process(); }));
#if BA_ENABLE_AUDIO
@ -782,8 +783,7 @@ void AudioServer::ThreadSource::UpdateAvailability() {
if (!busy) {
if (g_audio->available_sources_mutex().try_lock()) {
std::lock_guard<std::mutex> lock(g_audio->available_sources_mutex(),
std::adopt_lock);
std::lock_guard lock(g_audio->available_sources_mutex(), std::adopt_lock);
Stop();
Reset();
#if BA_DEBUG_BUILD
@ -1074,18 +1074,18 @@ void AudioServer::ThreadSource::UpdatePitch() {
}
void AudioServer::PushSetVolumesCall(float music_volume, float sound_volume) {
PushCall([this, music_volume, sound_volume] {
thread()->PushCall([this, music_volume, sound_volume] {
SetSoundVolume(sound_volume);
SetMusicVolume(music_volume);
});
}
void AudioServer::PushSetSoundPitchCall(float val) {
PushCall([this, val] { SetSoundPitch(val); });
thread()->PushCall([this, val] { SetSoundPitch(val); });
}
void AudioServer::PushSetPausedCall(bool pause) {
PushCall([this, pause] {
thread()->PushCall([this, pause] {
if (g_buildconfig.ostype_android()) {
Log("Error: Shouldn't be getting SetPausedCall on android.");
}
@ -1095,7 +1095,7 @@ void AudioServer::PushSetPausedCall(bool pause) {
void AudioServer::PushComponentUnloadCall(
const std::vector<Object::Ref<MediaComponentData>*>& components) {
PushCall([this, components] {
thread()->PushCall([this, components] {
// Unload all components we were passed...
for (auto&& i : components) {
(**i).Unload();
@ -1107,7 +1107,7 @@ void AudioServer::PushComponentUnloadCall(
}
void AudioServer::PushHavePendingLoadsCall() {
PushCall([this] {
thread()->PushCall([this] {
have_pending_loads_ = true;
UpdateTimerInterval();
});
@ -1115,16 +1115,16 @@ void AudioServer::PushHavePendingLoadsCall() {
void AudioServer::AddSoundRefDelete(const Object::Ref<SoundData>* c) {
{
std::lock_guard<std::mutex> lock(sound_ref_delete_list_mutex_);
std::scoped_lock lock(sound_ref_delete_list_mutex_);
sound_ref_delete_list_.push_back(c);
}
// Now push a call to the game thread to do the deletes.
g_game->PushCall([] { g_audio_server->ClearSoundRefDeleteList(); });
g_game->thread()->PushCall([] { g_audio_server->ClearSoundRefDeleteList(); });
}
void AudioServer::ClearSoundRefDeleteList() {
assert(InLogicThread());
std::lock_guard<std::mutex> lock(sound_ref_delete_list_mutex_);
std::scoped_lock lock(sound_ref_delete_list_mutex_);
for (const Object::Ref<SoundData>* i : sound_ref_delete_list_) {
delete i;
}
@ -1149,9 +1149,9 @@ void AudioServer::BeginInterruption() {
}
}
void AudioServer::HandleThreadPause() { SetPaused(true); }
auto AudioServer::OnThreadPause() -> void { SetPaused(true); }
void AudioServer::HandleThreadResume() { SetPaused(false); }
auto AudioServer::OnThreadResume() -> void { SetPaused(false); }
void AudioServer::EndInterruption() {
assert(!InAudioThread());

View File

@ -4,14 +4,15 @@
#define BALLISTICA_AUDIO_AUDIO_SERVER_H_
#include <map>
#include <mutex>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
namespace ballistica {
/// A module that handles audio processing.
class AudioServer : public Module {
class AudioServer {
public:
static auto source_id_from_play_id(uint32_t play_id) -> uint32_t {
return play_id & 0xFFFFu;
@ -27,9 +28,6 @@ class AudioServer : public Module {
void PushSetSoundPitchCall(float val);
void PushSetPausedCall(bool pause);
void HandleThreadPause() override;
void HandleThreadResume() override;
static void BeginInterruption();
static void EndInterruption();
@ -64,11 +62,16 @@ class AudioServer : public Module {
// Stop a sound from playing if it exists.
void StopSound(uint32_t play_id);
auto thread() const -> Thread* { return thread_; }
private:
class ThreadSource;
struct Impl;
~AudioServer() override;
~AudioServer();
auto OnThreadPause() -> void;
auto OnThreadResume() -> void;
void SetPaused(bool paused);
@ -104,6 +107,7 @@ class AudioServer : public Module {
// std::unique_ptr<Impl> impl_{};
Impl* impl_{};
Thread* thread_{};
Timer* process_timer_{};
bool have_pending_loads_{};
bool paused_{};

View File

@ -22,7 +22,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20778;
const int kAppBuildNumber = 20794;
const char* kAppVersion = "1.7.7";
// Our standalone globals.
@ -87,7 +87,7 @@ auto BallisticaMain(int argc, char** argv) -> int {
// -------------------------------------------------------------------------
g_app_globals = new AppGlobals(argc, argv);
g_app_internal = CreateAppInternal();
g_app_internal = GetAppInternal();
g_platform = Platform::Create();
g_account = new Account();
g_utils = new Utils();
@ -109,15 +109,21 @@ auto BallisticaMain(int argc, char** argv) -> int {
auto* network_write_thread = new Thread(ThreadIdentifier::kNetworkWrite);
g_app_globals->pausable_threads.push_back(network_write_thread);
// And add our other standard modules to them.
logic_thread->AddModule<Game>();
network_write_thread->AddModule<NetworkWriteModule>();
media_thread->AddModule<MediaServer>();
g_main_thread->AddModule<GraphicsServer>();
audio_thread->AddModule<AudioServer>();
// Spin up our subsystems in those threads.
logic_thread->PushCallSynchronous(
[logic_thread] { new Game(logic_thread); });
network_write_thread->PushCallSynchronous([network_write_thread] {
new NetworkWriteModule(network_write_thread);
});
media_thread->PushCallSynchronous(
[media_thread] { new MediaServer(media_thread); });
new GraphicsServer(g_main_thread);
audio_thread->PushCallSynchronous(
[audio_thread] { new AudioServer(audio_thread); });
// Now let the platform spin up any other threads/modules it uses.
// (bg-dynamics in non-headless builds, stdin/stdout where applicable, etc.)
// (bg-dynamics in non-headless builds, stdin/stdout where applicable,
// etc.)
g_platform->CreateAuxiliaryModules();
// Ok at this point we can be considered up-and-running.
@ -148,8 +154,8 @@ auto BallisticaMain(int argc, char** argv) -> int {
// In this case we'll now simply return and let the OS feed us events
// until the app quits.
// However, we may need to 'prime the pump' first. For instance,
// if the main thread event loop is driven by frame draws, it may need to
// manually pump events until drawing begins (otherwise it will never
// if the main thread event loop is driven by frame draws, it may need
// to manually pump events until drawing begins (otherwise it will never
// process the 'create-screen' event and wind up deadlocked).
g_app->PrimeEventPump();
}
@ -184,7 +190,7 @@ auto GetRealTime() -> millisecs_t {
// If we're at a different time than our last query, do our funky math.
if (t != g_app_globals->last_real_time_ticks) {
std::lock_guard<std::mutex> lock(g_app_globals->real_time_mutex);
std::scoped_lock lock(g_app_globals->real_time_mutex);
millisecs_t passed = t - g_app_globals->last_real_time_ticks;
// GetTicks() is supposed to be monotonic, but I've seen 'passed'

View File

@ -4,6 +4,7 @@
#include "ballistica/app/app.h"
#include "ballistica/core/logging.h"
#include "ballistica/core/thread.h"
#include "ballistica/internal/app_internal.h"
#include "ballistica/platform/platform.h"
@ -109,7 +110,7 @@ auto FatalError::DoBlockingFatalErrorDialog(const std::string& message)
bool finished{};
bool* startedptr{&started};
bool* finishedptr{&finished};
g_app->PushCall([message, startedptr, finishedptr] {
g_app->thread()->PushCall([message, startedptr, finishedptr] {
*startedptr = true;
g_platform->BlockingFatalErrorDialog(message);
*finishedptr = true;

View File

@ -69,7 +69,7 @@ void Logging::Log(const std::string& msg, bool to_stdout, bool to_server) {
// Add to our complete log.
if (g_app_globals != nullptr) {
std::lock_guard<std::mutex> lock(g_app_globals->log_mutex);
std::scoped_lock lock(g_app_globals->log_mutex);
if (!g_app_globals->log_full) {
(g_app_globals->log) += (msg + "\n");
if ((g_app_globals->log).size() > 10000) {

View File

@ -1,58 +0,0 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/core/module.h"
#include "ballistica/core/thread.h"
namespace ballistica {
void Module::PushLocalRunnable(Runnable* runnable) {
assert(std::this_thread::get_id() == thread()->thread_id());
runnables_.push_back(runnable);
}
void Module::PushRunnable(Runnable* runnable) {
// If we're being called from the module's thread, just drop it in the list.
// otherwise send it as a message to the other thread.
if (std::this_thread::get_id() == thread()->thread_id()) {
PushLocalRunnable(runnable);
} else {
thread_->PushModuleRunnable(runnable, id_);
}
}
auto Module::CheckPushSafety() -> bool {
if (std::this_thread::get_id() == thread()->thread_id()) {
// behave the same as the thread-message safety check for
// module messages.
return (runnables_.size() < kThreadMessageSafetyThreshold);
} else {
return thread_->CheckPushModuleRunnableSafety();
}
}
Module::Module(std::string name_in, Thread* thread_in)
: thread_(thread_in), name_(std::move(name_in)) {
id_ = thread_->RegisterModule(name_, this);
}
Module::~Module() = default;
auto Module::NewThreadTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> Timer* {
return thread_->NewTimer(length, repeat, runnable);
}
void Module::RunPendingRunnables() {
// Pull all runnables off the list first (its possible for one of these
// runnables to add more) and then process them.
assert(std::this_thread::get_id() == thread()->thread_id());
std::list<Runnable*> runnables;
runnables_.swap(runnables);
for (Runnable* i : runnables) {
i->Run();
delete i;
}
}
} // namespace ballistica

View File

@ -1,78 +0,0 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_CORE_MODULE_H_
#define BALLISTICA_CORE_MODULE_H_
#include <list>
#include <mutex>
#include <string>
#include <vector>
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/runnable.h"
namespace ballistica {
/// A logical entity that can be added to a thread and make use of its
/// event loop.
class Module {
public:
/// Add a runnable to this module's queue.
/// Pass a Runnable that has been allocated with new().
/// There must be no existing strong refs to it.
/// It will be owned and disposed of by the module from this point.
auto PushRunnable(Runnable* runnable) -> void;
/// Convenience function to push a lambda as a runnable.
template <typename F>
auto PushCall(const F& lambda) -> void {
PushRunnable(NewLambdaRunnableRaw(lambda));
}
/// Returns true if there is plenty of buffer space available for
/// PushCall/PushRunnable; can be used to avoid buffer-full errors
/// by discarding non-essential calls. An example is calls scheduled
/// due to receiving unreliable network packets; without watching
/// buffer space it can be possible for an attacker to bring down
/// the app through a flood of packets.
auto CheckPushSafety() -> bool;
/// Return the thread this module is running on.
auto thread() const -> Thread* { return thread_; }
virtual ~Module();
/// Push a runnable from the same thread as the module.
auto PushLocalRunnable(Runnable* runnable) -> void;
/// Called for each module when its thread is about to be suspended
/// (on platforms such as mobile).
virtual auto HandleThreadPause() -> void {}
/// Called for each module when its thread is about to be resumed
/// (on platforms such as mobile).
virtual auto HandleThreadResume() -> void {}
/// Whether this module has pending runnables.
auto has_pending_runnables() const -> bool { return !runnables_.empty(); }
/// Used by the module's owner thread to let it do its thing.
auto RunPendingRunnables() -> void;
auto name() const -> const std::string& { return name_; }
protected:
Module(std::string name, Thread* thread);
auto NewThreadTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> Timer*;
private:
std::string name_;
int id_{};
std::list<Runnable*> runnables_;
Thread* thread_{};
};
} // namespace ballistica
#endif // BALLISTICA_CORE_MODULE_H_

View File

@ -15,7 +15,7 @@ void Object::PrintObjects() {
#if BA_DEBUG_BUILD
std::string s;
{
std::lock_guard<std::mutex> lock(g_app_globals->object_list_mutex);
std::scoped_lock lock(g_app_globals->object_list_mutex);
s = std::to_string(g_app_globals->object_count) + " Objects at time "
+ std::to_string(GetRealTime()) + ";";
@ -66,7 +66,7 @@ Object::Object() {
object_birth_time_ = GetRealTime();
// Add ourself to the global object list.
std::lock_guard<std::mutex> lock(g_app_globals->object_list_mutex);
std::scoped_lock lock(g_app_globals->object_list_mutex);
object_prev_ = nullptr;
object_next_ = g_app_globals->object_list_first;
g_app_globals->object_list_first = this;
@ -80,7 +80,7 @@ Object::Object() {
Object::~Object() {
#if BA_DEBUG_BUILD
// Pull ourself from the global obj list.
std::lock_guard<std::mutex> lock(g_app_globals->object_list_mutex);
std::scoped_lock lock(g_app_globals->object_list_mutex);
if (object_next_) {
object_next_->object_prev_ = object_prev_;
}
@ -96,20 +96,19 @@ Object::~Object() {
// Avoiding Log for these low level errors; can lead to deadlock.
printf(
"Warning: Object is dying with non-zero ref-count; this is bad. "
"(this "
"might mean the object raised an exception in its constructor after "
"being strong-referenced first).\n");
"(this might mean the object raised an exception in its constructor"
" after being strong-referenced first).\n");
}
#endif // BA_DEBUG_BUILD
// Invalidate all our weak refs.
// We could call Release() on each but we'd have to deactivate the
// thread-check since virtual functions won't work right in a destructor.
// Also we can take a few shortcuts here since we know we're deleting the
// entire list, not just one object.
// thread-check since virtual functions won't work as expected in a
// destructor. Also we can take a few shortcuts here since we know
// we're deleting the entire list, not just one object.
while (object_weak_refs_) {
auto tmp = object_weak_refs_;
auto tmp{object_weak_refs_};
object_weak_refs_ = tmp->next_;
tmp->prev_ = nullptr;
tmp->next_ = nullptr;
@ -127,19 +126,19 @@ auto Object::GetObjectDescription() const -> std::string {
+ ">";
}
auto Object::GetDefaultOwnerThread() const -> ThreadIdentifier {
return ThreadIdentifier::kLogic;
}
auto Object::GetThreadOwnership() const -> Object::ThreadOwnership {
#if BA_DEBUG_BUILD
return thread_ownership_;
#else
// Not used in release build so doesn't matter.
return ThreadOwnership::kAny;
FatalError("Should not be called in release builds.");
return ThreadOwnership::kClassDefault;
#endif
}
auto Object::GetDefaultOwnerThread() const -> ThreadIdentifier {
return ThreadIdentifier::kLogic;
}
#if BA_DEBUG_BUILD
static auto GetCurrentThreadIdentifier() -> ThreadIdentifier {
@ -161,22 +160,23 @@ static auto GetCurrentThreadIdentifier() -> ThreadIdentifier {
}
}
void Object::ObjectThreadCheck() {
auto Object::ObjectUpdateForAcquire() -> void {
ThreadOwnership thread_ownership = GetThreadOwnership();
// If we're set to use the next-referencing thread and haven't set one
// yet, do so.
if (thread_ownership == ThreadOwnership::kNextReferencing
&& owner_thread_ == ThreadIdentifier::kInvalid) {
owner_thread_ = GetCurrentThreadIdentifier();
}
}
auto Object::ObjectThreadCheck() -> void {
if (!thread_checks_enabled_) {
return;
}
ThreadOwnership thread_ownership = GetThreadOwnership();
if (thread_ownership == ThreadOwnership::kAny) {
return;
}
// If we're set to use the next-referencing thread
// and haven't set that yet, do so.
if (thread_ownership == ThreadOwnership::kNextReferencing
&& owner_thread_ == ThreadIdentifier::kInvalid) {
owner_thread_ = GetCurrentThreadIdentifier();
}
ThreadIdentifier t;
if (thread_ownership == ThreadOwnership::kClassDefault) {

View File

@ -34,23 +34,23 @@ class Object {
// type-name plus address.
virtual auto GetObjectDescription() const -> std::string;
// This is called when adding or removing a reference to an Object;
// it can perform sanity-tests to make sure references are not being
// added at incorrect times or from incorrect threads.
// The default implementation uses the per-object
// ThreadOwnership/ThreadIdentifier values accessible below. NOTE: this
// check runs only in the debug build so don't add any logical side-effects!
#if BA_DEBUG_BUILD
virtual void ObjectThreadCheck();
#endif
enum class ThreadOwnership {
kClassDefault, // Uses class' GetDefaultOwnerThread() call.
kNextReferencing, // Uses whichever thread next acquires/accesses a ref.
kCustom, // Always use a specific thread.
kAny // Any thread is fine.
kClassDefault, // Uses class' GetDefaultOwnerThread() call.
kNextReferencing // Uses whichever thread next acquires/accesses a ref.
};
#if BA_DEBUG_BUILD
/// This is called when adding or removing a reference to an Object;
/// it can perform sanity-tests to make sure references are not being
/// added at incorrect times or from incorrect threads.
/// The default implementation uses the per-object
/// ThreadOwnership/ThreadIdentifier values accessible below. NOTE: this
/// check runs only in the debug build so don't add any logical side-effects!
virtual void ObjectThreadCheck();
#endif
/// Called by the default ObjectThreadCheck() to determine ThreadOwnership
/// for an Object. The default uses the object's individual value
/// (which defaults to ThreadOwnership::kClassDefault and can be set via
@ -62,21 +62,12 @@ class Object {
/// Default returns ThreadIdentifier::kLogic
virtual auto GetDefaultOwnerThread() const -> ThreadIdentifier;
/// Set thread ownership values for an individual object.
/// Note that these values may be ignored if ObjectThreadCheck() is
/// overridden, and thread_identifier is only relevant when ownership is
/// ThreadOwnership::kCustom.
/// UPDATE: turning off per-object controls; gonna see if we can get by
/// with just set_thread_checks_enabled() for temp special cases...
void SetThreadOwnership(
ThreadOwnership ownership,
ThreadIdentifier thread_identifier = ThreadIdentifier::kLogic) {
/// Set thread ownership for an individual object.
void SetThreadOwnership(ThreadOwnership ownership) {
#if BA_DEBUG_BUILD
thread_ownership_ = ownership;
if (thread_ownership_ == ThreadOwnership::kNextReferencing) {
owner_thread_ = ThreadIdentifier::kInvalid;
} else {
owner_thread_ = thread_identifier;
}
#endif
}
@ -128,9 +119,9 @@ class Object {
}
private:
Object* obj_ = nullptr;
WeakRefBase* prev_ = nullptr;
WeakRefBase* next_ = nullptr;
Object* obj_{};
WeakRefBase* prev_{};
WeakRefBase* next_{};
friend class Object;
}; // WeakRefBase
@ -278,8 +269,8 @@ class Object {
if (obj == nullptr) {
throw Exception("Acquiring invalid ptr of " + static_type_name<T>());
}
#if BA_DEBUG_BUILD
#if BA_DEBUG_BUILD
// Seems like it'd be a good idea to prevent creation of weak-refs to
// objects in their destructors, but it turns out we're currently
// doing this (session points contexts at itself as it dies, etc.)
@ -287,13 +278,14 @@ class Object {
obj->ObjectThreadCheck();
assert(obj_ == nullptr && next_ == nullptr && prev_ == nullptr);
#endif
if (obj->object_weak_refs_) {
obj->object_weak_refs_->prev_ = this;
next_ = obj->object_weak_refs_;
}
obj->object_weak_refs_ = this;
// Sanity checking: We make the assumption that static-casting our pointer
// Sanity check: We make the assumption that static-casting our pointer
// to/from Object gives the same results as reinterpret-casting it; let's
// be certain that's the case. In some cases involving multiple
// inheritance this might not be true, but we avoid those cases in our
@ -431,6 +423,7 @@ class Object {
}
#if BA_DEBUG_BUILD
obj->ObjectUpdateForAcquire();
obj->ObjectThreadCheck();
// Obvs shouldn't be referencing dead stuff.
@ -444,10 +437,10 @@ class Object {
// Log only to system log for these low-level errors;
// console or server can cause deadlock due to recursive
// ref-list locks.
printf(
"Incorrectly creating initial strong-ref to %s; use "
"New() or MakeRefCounted()\n",
obj->GetObjectDescription().c_str());
FatalError(
"Incorrectly creating initial strong-ref; use "
"New() or MakeRefCounted(): "
+ obj->GetObjectDescription());
}
obj->object_has_strong_ref_ = true;
#endif // BA_DEBUG_BUILD
@ -474,7 +467,7 @@ class Object {
}
}
}
T* obj_ = nullptr;
T* obj_{};
};
/// Object::New<Type>(): The preferred way to create ref-counted Objects.
@ -490,17 +483,16 @@ class Object {
#if BA_DEBUG_BUILD
if (ptr->object_creating_strong_reffed_) {
// Avoiding Log for these low level errors; can lead to deadlock.
printf("Object already set up as reffed in New: %s\n",
ptr->GetObjectDescription().c_str());
FatalError("ballistica::Object already set up as reffed in New: "
+ ptr->GetObjectDescription());
}
if (ptr->object_strong_ref_count_ > 0) {
// TODO(ericf): make this an error once its cleared out
printf("Obj strong-ref in constructor: %s\n",
ptr->GetObjectDescription().c_str());
FatalError("ballistica::Object hs strong-ref in constructor: "
+ ptr->GetObjectDescription());
}
ptr->object_in_constructor_ = false;
ptr->object_creating_strong_reffed_ = true;
#endif // BA_DEBUG_BUILD
#endif
return Object::Ref<TRETURN>(ptr);
}
@ -517,8 +509,8 @@ class Object {
T* ptr = new T(std::forward<ARGS>(args)...);
#if BA_DEBUG_BUILD
if (ptr->object_strong_ref_count_ > 0) {
printf("Obj strong-ref in constructor: %s\n",
ptr->GetObjectDescription().c_str());
FatalError("ballistica::Object has strong-ref in constructor: "
+ ptr->GetObjectDescription());
}
ptr->object_in_constructor_ = false;
#endif
@ -531,9 +523,9 @@ class Object {
// Make sure we're operating on a fresh object.
assert(ptr->object_strong_ref_count_ == 0);
if (ptr->object_creating_strong_reffed_) {
// Avoiding Log for these low level errors; can lead to deadlock.
printf("Object already set up as reffed in MakeRefCounted: %s\n",
ptr->GetObjectDescription().c_str());
FatalError(
"ballistica::Object already set up as reffed in MakeRefCounted: "
+ ptr->GetObjectDescription());
}
ptr->object_creating_strong_reffed_ = true;
#endif
@ -554,15 +546,13 @@ class Object {
}
private:
#if BA_DEBUG_BUILD
// Making operator new private here to help ensure all of our dynamic
// allocation/deallocation goes through our special functions (New(),
// NewDeferred(), etc.). However, sticking with original new for release
// builds since it may handle corner cases this does not.
#if BA_DEBUG_BUILD
auto operator new(size_t size) -> void* { return new char[size]; }
#endif
#if BA_DEBUG_BUILD
auto ObjectUpdateForAcquire() -> void;
bool object_has_strong_ref_{};
bool object_creating_strong_reffed_{};
bool object_is_dead_{};

View File

@ -9,28 +9,14 @@
namespace ballistica {
bool Thread::threads_paused_ = false;
void Thread::AddCurrentThreadName(const std::string& name) {
std::lock_guard<std::mutex> lock(g_app_globals->thread_name_map_mutex);
void Thread::SetInternalThreadName(const std::string& name) {
std::scoped_lock lock(g_app_globals->thread_name_map_mutex);
std::thread::id thread_id = std::this_thread::get_id();
auto i = g_app_globals->thread_name_map.find(thread_id);
std::string s;
if (i != g_app_globals->thread_name_map.end()) {
s = i->second;
}
if (!strstr(s.c_str(), name.c_str())) {
if (s.empty()) {
s = name;
} else {
s = s + "+" + name;
}
}
g_app_globals->thread_name_map[std::this_thread::get_id()] = s;
g_app_globals->thread_name_map[std::this_thread::get_id()] = name;
}
void Thread::ClearCurrentThreadName() {
std::lock_guard<std::mutex> lock(g_app_globals->thread_name_map_mutex);
std::scoped_lock lock(g_app_globals->thread_name_map_mutex);
auto i = g_app_globals->thread_name_map.find(std::this_thread::get_id());
if (i != g_app_globals->thread_name_map.end()) {
g_app_globals->thread_name_map.erase(i);
@ -50,27 +36,10 @@ void Thread::UpdateMainThreadID() {
}
}
void Thread::KillModule(const Module& module) {
for (auto i = modules_.begin(); i != modules_.end(); i++) {
if (*i == &module) {
delete *i;
modules_.erase(i);
return;
}
}
throw Exception("Module not found on this thread");
}
// These are all exactly the same; its just a way to try and clarify
// in stack traces which thread is running in case it is not otherwise
// evident.
void Thread::KillModules() {
for (auto i : modules_) {
delete i;
}
modules_.clear();
}
// These are all exactly the same, but by running different ones for
// different thread groups makes its easy to see which thread is which
// in profilers, backtraces, etc.
auto Thread::RunLogicThread(void* data) -> int {
return static_cast<Thread*>(data)->ThreadMain();
}
@ -108,13 +77,11 @@ void Thread::WaitForNextEvent(bool single_cycle) {
return;
}
// We also never wait if any of our modules have pending runnables.
// We also never wait if we have pending runnables.
// (we run all existing runnables in each loop cycle, but one of those
// may have enqueued more).
for (auto&& i : modules_) {
if (i->has_pending_runnables()) {
return;
}
if (has_pending_runnables()) {
return;
}
// While we're waiting, allow other python threads to run.
@ -124,7 +91,7 @@ void Thread::WaitForNextEvent(bool single_cycle) {
// If we've got active timers, wait for messages with a timeout so we can
// run the next timer payload.
if ((!paused_) && timers_.active_timer_count() > 0) {
if (!paused_ && timers_.active_timer_count() > 0) {
millisecs_t real_time = GetRealTime();
millisecs_t wait_time = timers_.GetTimeToNextExpire(real_time);
if (wait_time > 0) {
@ -185,76 +152,44 @@ auto Thread::RunEventLoop(bool single_cycle) -> int {
GetThreadMessages(&thread_messages);
for (auto& thread_message : thread_messages) {
switch (thread_message.type) {
case ThreadMessage::Type::kNewModule: {
// Launch a new module and unlock.
ModuleLauncher* tl;
tl = static_cast<ModuleLauncher*>(thread_message.pval);
tl->Launch(this);
auto cmd =
static_cast<uint32_t>(ThreadMessage::Type::kNewModuleConfirm);
WriteToOwner(&cmd, sizeof(cmd));
break;
}
case ThreadMessage::Type::kRunnable: {
auto module_id = thread_message.ival;
Module* t = GetModule(module_id);
assert(t);
auto e = static_cast<Runnable*>(thread_message.pval);
// Add the event to our list.
t->PushLocalRunnable(e);
RunnablesWhilePausedSanityCheck(e);
PushLocalRunnable(thread_message.runnable,
thread_message.completion_flag);
break;
}
case ThreadMessage::Type::kShutdown: {
// Shutdown; die!
done_ = true;
break;
}
case ThreadMessage::Type::kResume: {
assert(paused_);
// Let all modules do pause-related stuff.
for (auto&& i : modules_) {
i->HandleThreadResume();
}
paused_ = false;
break;
}
case ThreadMessage::Type::kPause: {
assert(!paused_);
// Let all modules do pause-related stuff.
for (auto&& i : modules_) {
i->HandleThreadPause();
}
RunPauseCallbacks();
paused_ = true;
last_pause_time_ = GetRealTime();
messages_since_paused_ = 0;
break;
}
case ThreadMessage::Type::kResume: {
assert(paused_);
RunResumeCallbacks();
paused_ = false;
break;
}
default: {
throw Exception();
}
}
// If the thread is going down.
if (done_) {
break;
}
}
// Run timers && queued module runnables unless we're paused.
if (!paused_) {
// Run timers.
timers_.Run(GetRealTime());
// Run module-messages.
for (auto& module_entry : modules_) {
module_entry->RunPendingRunnables();
}
RunPendingRunnables();
}
if (done_ || single_cycle) {
break;
}
@ -262,17 +197,6 @@ auto Thread::RunEventLoop(bool single_cycle) -> int {
return 0;
}
void Thread::RunnablesWhilePausedSanityCheck(Runnable* e) {
// We generally shouldn't be getting messages while paused..
// (check both our pause-state and the global one; wanna ignore things
// that might slip through if some just-unlocked thread msgs us but we
// haven't been unlocked yet)
// UPDATE - we are migrating away from distinct message classes and towards
// LambdaRunnables for everything, which means that we can't easily
// see details of what is coming through. Disabling this check for now.
}
void Thread::GetThreadMessages(std::list<ThreadMessage>* messages) {
assert(messages);
assert(std::this_thread::get_id() == thread_id());
@ -323,13 +247,12 @@ Thread::Thread(ThreadIdentifier identifier_in, ThreadType type_in)
// Let 'er rip.
thread_ = new std::thread(func, this);
// The thread lets us know when its up and running.
std::unique_lock<std::mutex> lock(data_to_client_mutex_);
// Block until the thread is bootstrapped.
// (maybe not necessary, but let's be cautious in case we'd
// try to use things like thread_id before they're known).
std::unique_lock lock(client_listener_mutex_);
client_listener_cv_.wait(lock, [this] { return bootstrapped_; });
uint32_t cmd;
ReadFromThread(&lock, &cmd, sizeof(cmd));
assert(static_cast<ThreadMessage::Type>(cmd)
== ThreadMessage::Type::kNewThreadConfirm);
break;
}
case ThreadType::kMain: {
@ -338,8 +261,11 @@ Thread::Thread(ThreadIdentifier identifier_in, ThreadType type_in)
assert(std::this_thread::get_id() == g_app_globals->main_thread_id);
thread_id_ = std::this_thread::get_id();
// Hmmm we might want to set our thread name here,
// as we do for other threads?
// Set our own thread-id-to-name mapping.
SetInternalThreadName("main");
// Hmmm we might want to set our OS thread name here,
// as we do for other threads? (SetCurrentThreadName).
// However on linux that winds up being what we see in top/etc
// so maybe shouldn't.
break;
@ -351,46 +277,60 @@ auto Thread::ThreadMain() -> int {
try {
assert(type_ == ThreadType::kStandard);
thread_id_ = std::this_thread::get_id();
const char* name;
const char* id_string;
switch (identifier_) {
case ThreadIdentifier::kLogic:
name = "logic";
id_string = "ballistica logic";
break;
case ThreadIdentifier::kStdin:
name = "stdin";
id_string = "ballistica stdin";
break;
case ThreadIdentifier::kMedia:
name = "media";
id_string = "ballistica media";
break;
case ThreadIdentifier::kFileOut:
name = "fileout";
id_string = "ballistica file-out";
break;
case ThreadIdentifier::kMain:
name = "main";
id_string = "ballistica main";
break;
case ThreadIdentifier::kAudio:
name = "audio";
id_string = "ballistica audio";
break;
case ThreadIdentifier::kBGDynamics:
name = "bgdynamics";
id_string = "ballistica bg-dynamics";
break;
case ThreadIdentifier::kNetworkWrite:
name = "networkwrite";
id_string = "ballistica network writing";
break;
default:
throw Exception();
}
assert(name && id_string);
SetInternalThreadName(name);
g_platform->SetCurrentThreadName(id_string);
// Send our owner a confirmation that we're alive.
auto cmd = static_cast<uint32_t>(ThreadMessage::Type::kNewThreadConfirm);
WriteToOwner(&cmd, sizeof(cmd));
// Mark ourself as bootstrapped and signal listeners so
// anyone waiting for us to spin up can move along.
{
std::scoped_lock lock(client_listener_mutex_);
bootstrapped_ = true;
}
client_listener_cv_.notify_all();
// Now just run our loop until we die.
int result = RunEventLoop();
KillModules();
ClearCurrentThreadName();
return result;
} catch (const std::exception& e) {
@ -453,15 +393,6 @@ void Thread::LogThreadMessageTally() {
case ThreadMessage::Type::kRunnable:
s += "kRunnable";
break;
case ThreadMessage::Type::kNewModule:
s += "kNewModule";
break;
case ThreadMessage::Type::kNewModuleConfirm:
s += "kNewModuleConfirm";
break;
case ThreadMessage::Type::kNewThreadConfirm:
s += "kNewThreadConfirm";
break;
case ThreadMessage::Type::kPause:
s += "kPause";
break;
@ -473,8 +404,8 @@ void Thread::LogThreadMessageTally() {
break;
}
if (m.type == ThreadMessage::Type::kRunnable) {
std::string m_name = g_platform->DemangleCXXSymbol(
typeid(*(static_cast<Runnable*>(m.pval))).name());
std::string m_name =
g_platform->DemangleCXXSymbol(typeid(*(m.runnable)).name());
s += std::string(": ") + m_name;
}
auto j = tally.find(s);
@ -550,55 +481,15 @@ void Thread::PushThreadMessage(const ThreadMessage& t) {
thread_message_cv_.notify_all();
}
#pragma clang diagnostic push
#pragma ide diagnostic ignored "ConstantParameter"
void Thread::WriteToOwner(const void* data, uint32_t size) {
assert(std::this_thread::get_id() == thread_id());
{
std::unique_lock<std::mutex> lock(data_to_client_mutex_);
data_to_client_.emplace_back(size);
memcpy(&(data_to_client_.back()[0]), data, size);
}
data_to_client_cv_.notify_all();
}
void Thread::ReadFromThread(std::unique_lock<std::mutex>* lock, void* buffer,
uint32_t size) {
// Threads cant read from themselves.. could load to lock-deadlock.
assert(std::this_thread::get_id() != thread_id());
data_to_client_cv_.wait(*lock, [this] {
// Go back to sleep on spurious wakeups
// (if we didn't wind up with any new messages)
return (!data_to_client_.empty());
});
// Read the oldest thing on our in-data list.
assert(!data_to_client_.empty());
assert(data_to_client_.front().size() == size);
memcpy(buffer, &(data_to_client_.front()[0]), size);
data_to_client_.pop_front();
}
#pragma clang diagnostic pop
void Thread::SetThreadsPaused(bool paused) {
threads_paused_ = paused;
g_app_globals->threads_paused = paused;
for (auto&& i : g_app_globals->pausable_threads) {
i->SetPaused(paused);
}
}
auto Thread::AreThreadsPaused() -> bool { return threads_paused_; }
auto Thread::RegisterModule(const std::string& name, Module* module) -> int {
AddCurrentThreadName(name);
// This should assure we were properly launched.
// (the ModuleLauncher will set the index to what ours will be)
int index = static_cast<int>(modules_.size());
// module_entries_.emplace_back(module, name, index);
modules_.push_back(module);
return index;
auto Thread::AreThreadsPaused() -> bool {
return g_app_globals->threads_paused;
}
auto Thread::NewTimer(millisecs_t length, bool repeat,
@ -613,12 +504,14 @@ auto Thread::GetCurrentThreadName() -> std::string {
return "unknown(not-yet-inited)";
}
{
std::lock_guard<std::mutex> lock(g_app_globals->thread_name_map_mutex);
std::scoped_lock lock(g_app_globals->thread_name_map_mutex);
auto i = g_app_globals->thread_name_map.find(std::this_thread::get_id());
if (i != g_app_globals->thread_name_map.end()) {
return i->second;
}
}
// FIXME - move this to platform.
#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS || BA_OSTYPE_LINUX
std::string name = "unknown (sys-name=";
char buffer[256];
@ -634,4 +527,99 @@ auto Thread::GetCurrentThreadName() -> std::string {
#endif
}
void Thread::RunPendingRunnables() {
// Pull all runnables off the list first (its possible for one of these
// runnables to add more) and then process them.
assert(std::this_thread::get_id() == thread_id());
std::list<std::pair<Runnable*, bool*>> runnables;
runnables_.swap(runnables);
bool do_notify_listeners{};
for (auto&& i : runnables) {
i.first->Run();
delete i.first;
// If this runnable wanted to be flagged when done, set its flag
// and make a note to wake all client listeners.
if (i.second != nullptr) {
*(i.second) = true;
do_notify_listeners = true;
}
}
if (do_notify_listeners) {
client_listener_cv_.notify_all();
}
}
void Thread::RunPauseCallbacks() {
for (Runnable* i : pause_callbacks_) {
i->Run();
}
}
void Thread::RunResumeCallbacks() {
for (Runnable* i : resume_callbacks_) {
i->Run();
}
}
void Thread::PushLocalRunnable(Runnable* runnable, bool* completion_flag) {
assert(std::this_thread::get_id() == thread_id());
runnables_.push_back(std::make_pair(runnable, completion_flag));
}
void Thread::PushCrossThreadRunnable(Runnable* runnable,
bool* completion_flag) {
PushThreadMessage(Thread::ThreadMessage(
Thread::ThreadMessage::Type::kRunnable, runnable, completion_flag));
}
void Thread::AddPauseCallback(Runnable* runnable) {
assert(std::this_thread::get_id() == thread_id());
pause_callbacks_.push_back(runnable);
}
void Thread::AddResumeCallback(Runnable* runnable) {
assert(std::this_thread::get_id() == thread_id());
resume_callbacks_.push_back(runnable);
}
void Thread::PushRunnable(Runnable* runnable) {
// If we're being called from withing our thread, just drop it in the list.
// otherwise send it as a message to the other thread.
if (std::this_thread::get_id() == thread_id()) {
PushLocalRunnable(runnable, nullptr);
} else {
PushCrossThreadRunnable(runnable, nullptr);
}
}
void Thread::PushRunnableSynchronous(Runnable* runnable) {
bool complete{};
bool* complete_ptr{&complete};
if (std::this_thread::get_id() == thread_id()) {
FatalError(
"PushRunnableSynchronous called from target thread;"
" would deadlock.");
} else {
PushCrossThreadRunnable(runnable, &complete);
}
// Now listen until our completion flag gets set.
std::unique_lock lock(client_listener_mutex_);
client_listener_cv_.wait(lock, [complete_ptr] {
// Go back to sleep on spurious wakeups
// (if we're not actually complete yet).
return *complete_ptr;
});
}
auto Thread::CheckPushSafety() -> bool {
if (std::this_thread::get_id() == thread_id()) {
// behave the same as the thread-message safety check.
return (runnables_.size() < kThreadMessageSafetyThreshold);
} else {
return CheckPushRunnableSafety();
}
}
} // namespace ballistica

View File

@ -12,6 +12,7 @@
#include "ballistica/app/app_globals.h"
#include "ballistica/ballistica.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/timer_list.h"
#include "ballistica/platform/min_sdl.h"
@ -26,18 +27,14 @@ class Thread {
ThreadType type_in = ThreadType::kStandard);
virtual ~Thread();
/// Register a name for the current thread (should generally describe its
/// purpose). If called multiple times, names will be combined with a '+'. ie:
/// "graphics+animation+audio".
void AddCurrentThreadName(const std::string& name);
void ClearCurrentThreadName();
auto ClearCurrentThreadName() -> void;
static auto GetCurrentThreadName() -> std::string;
/// Call this if the main thread changes.
static void UpdateMainThreadID();
static auto UpdateMainThreadID() -> void;
static void SetThreadsPaused(bool enable);
static auto SetThreadsPaused(bool enable) -> void;
static auto AreThreadsPaused() -> bool;
auto IsCurrent() const -> bool {
@ -47,105 +44,8 @@ class Thread {
// Used to quit the main thread.
void Quit();
struct ModuleLauncher {
virtual void Launch(Thread* g) = 0;
virtual ~ModuleLauncher() = default;
};
template <class MODULETYPE>
struct ModuleLauncherTemplate : public ModuleLauncher {
void Launch(Thread* g) override { new MODULETYPE(g); }
};
template <class MODULETYPE, class ARGTYPE>
struct ModuleLauncherArgTemplate : public ModuleLauncher {
explicit ModuleLauncherArgTemplate(ARGTYPE arg_in) : arg(arg_in) {}
ARGTYPE arg;
void Launch(Thread* g) override { new MODULETYPE(g, arg); }
};
void SetOwnsPython();
// Add a new module to a thread. This doesn't return anything. If you need
// a pointer to the module, have it store itself somewhere in its constructor
// or whatnot. Returning a pointer made it too easy to introduce race
// conditions with the thread trying to access itself via this pointer
// before it was set up.
template <class THREADTYPE>
void AddModule() {
switch (type_) {
case ThreadType::kStandard: {
// Launching a module in the current thread: do it immediately.
if (IsCurrent()) {
ModuleLauncherTemplate<THREADTYPE> launcher;
launcher.Launch(this);
} else {
// Launching a module in another thread;
// send a module-launcher and wait for the confirmation.
ModuleLauncherTemplate<THREADTYPE> launcher;
ModuleLauncher* tl = &launcher;
PushThreadMessage(
ThreadMessage(ThreadMessage::Type::kNewModule, 0, tl));
std::unique_lock<std::mutex> lock(data_to_client_mutex_);
uint32_t cmd;
ReadFromThread(&lock, &cmd, sizeof(cmd));
assert(static_cast<ThreadMessage::Type>(cmd)
== ThreadMessage::Type::kNewModuleConfirm);
}
break;
}
case ThreadType::kMain: {
assert(std::this_thread::get_id() == g_app_globals->main_thread_id);
new THREADTYPE(this);
break;
}
default: {
throw Exception();
}
}
}
// An alternate version of AddModule that passes an argument along
// to the thread's constructor.
template <class THREADTYPE, class ARGTYPE>
void AddModule(ARGTYPE arg) {
switch (type_) {
case ThreadType::kStandard: {
// Launching a module in the current thread: do it immediately.
if (IsCurrent()) {
ModuleLauncherArgTemplate<THREADTYPE, ARGTYPE> launcher(arg);
launcher.Launch(this);
} else {
// Launching a module in another thread;
// send a module-launcher and wait for the confirmation.
ModuleLauncherArgTemplate<THREADTYPE, ARGTYPE> launcher(arg);
ModuleLauncher* tl = &launcher;
PushThreadMessage(
ThreadMessage(ThreadMessage::Type::kNewModule, 0, tl));
std::unique_lock<std::mutex> lock(data_to_client_mutex_);
uint32_t cmd;
ReadFromThread(&lock, &cmd, sizeof(cmd));
assert(static_cast<ThreadMessage::Type>(cmd)
== ThreadMessage::Type::kNewModuleConfirm);
}
break;
}
case ThreadType::kMain: {
assert(std::this_thread::get_id() == g_app_globals->main_thread_id);
new THREADTYPE(this, arg);
break;
}
default: {
throw Exception();
}
}
}
void KillModule(const Module& module);
void SetPaused(bool paused);
auto thread_id() const -> std::thread::id { return thread_id_; }
@ -157,14 +57,7 @@ class Thread {
auto RunEventLoop(bool single_cycle = false) -> int;
auto identifier() const -> ThreadIdentifier { return identifier_; }
// For use by modules.
auto RegisterModule(const std::string& name, Module* module) -> int;
void PushModuleRunnable(Runnable* runnable, int module_index) {
PushThreadMessage(Thread::ThreadMessage(
Thread::ThreadMessage::Type::kRunnable, module_index, runnable));
}
auto CheckPushModuleRunnableSafety() -> bool {
auto CheckPushRunnableSafety() -> bool {
// We first complain when we get to 1000 queued messages so
// let's consider things unsafe when we're halfway there.
return (thread_message_count_ < kThreadMessageSafetyThreshold);
@ -174,52 +67,83 @@ class Thread {
auto NewTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> Timer*;
/// Add a runnable to this thread's event-loop.
/// Pass a Runnable that has been allocated with new().
/// There must be no existing strong refs to it.
/// It will be owned by the thread
auto PushRunnable(Runnable* runnable) -> void;
/// Convenience function to push a lambda as a runnable.
template <typename F>
auto PushCall(const F& lambda) -> void {
PushRunnable(NewLambdaRunnableRaw(lambda));
}
/// Add a runnable to this thread's event-loop and wait until it completes.
auto PushRunnableSynchronous(Runnable* runnable) -> void;
/// Convenience function to push a lambda as a runnable.
template <typename F>
auto PushCallSynchronous(const F& lambda) -> void {
PushRunnableSynchronous(NewLambdaRunnableRaw(lambda));
}
/// Add a callback to be run on event-loop pauses.
auto AddPauseCallback(Runnable* runnable) -> void;
/// Add a callback to be run on event-loop resumes.
auto AddResumeCallback(Runnable* runnable) -> void;
auto has_pending_runnables() const -> bool { return !runnables_.empty(); }
/// Returns true if there is plenty of buffer space available for
/// PushCall/PushRunnable; can be used to avoid buffer-full errors
/// by discarding non-essential calls. An example is calls scheduled
/// due to receiving unreliable network packets; without watching
/// buffer space it can be possible for an attacker to bring down
/// the app through a flood of packets.
auto CheckPushSafety() -> bool;
private:
struct ThreadMessage {
enum class Type {
kShutdown = 999,
kRunnable,
kNewModule,
kNewModuleConfirm,
kNewThreadConfirm,
kPause,
kResume
};
enum class Type { kShutdown = 999, kRunnable, kPause, kResume };
Type type;
void* pval;
int ival;
explicit ThreadMessage(Type type_in, int ival_in = 0,
void* pval_in = nullptr)
: type(type_in), ival(ival_in), pval(pval_in) {}
union {
Runnable* runnable;
};
bool* completion_flag{};
explicit ThreadMessage(Type type_in) : type(type_in) {}
explicit ThreadMessage(Type type, Runnable* runnable, bool* completion_flag)
: type(type), runnable(runnable), completion_flag{completion_flag} {}
};
static void RunnablesWhilePausedSanityCheck(Runnable* r);
void WaitForNextEvent(bool single_cycle);
void LoopUpkeep(bool once);
void LogThreadMessageTally();
void ReadFromThread(std::unique_lock<std::mutex>* lock, void* buffer,
uint32_t size);
void WriteToOwner(const void* data, uint32_t size);
bool writing_tally_ = false;
bool paused_ = false;
millisecs_t last_pause_time_ = 0;
int messages_since_paused_ = 0;
millisecs_t last_paused_message_report_time_ = 0;
bool done_ = false;
auto SetInternalThreadName(const std::string& name) -> void;
auto WaitForNextEvent(bool single_cycle) -> void;
auto LoopUpkeep(bool once) -> void;
auto LogThreadMessageTally() -> void;
auto PushLocalRunnable(Runnable* runnable, bool* completion_flag) -> void;
auto PushCrossThreadRunnable(Runnable* runnable, bool* completion_flag)
-> void;
auto NotifyClientListeners() -> void;
bool writing_tally_{};
bool paused_{};
millisecs_t last_pause_time_{};
int messages_since_paused_{};
millisecs_t last_paused_message_report_time_{};
bool done_{};
ThreadType type_;
int listen_sd_ = 0;
int listen_sd_{};
std::thread::id thread_id_{};
ThreadIdentifier identifier_ = ThreadIdentifier::kInvalid;
millisecs_t last_complaint_time_ = 0;
bool owns_python_ = false;
ThreadIdentifier identifier_{ThreadIdentifier::kInvalid};
millisecs_t last_complaint_time_{};
bool owns_python_{};
// FIXME: Should generalize this to some sort of PlatformThreadData class.
#if BA_XCODE_BUILD
void* auto_release_pool_ = nullptr;
#endif
void KillModules();
// These are all exactly the same, but by running different ones for
// different thread groups makes its easy to see which thread is which
// in profilers, backtraces, etc.
@ -231,25 +155,26 @@ class Thread {
static auto RunMediaThread(void* data) -> int;
auto ThreadMain() -> int;
std::thread* thread_;
void GetThreadMessages(std::list<ThreadMessage>* messages);
void PushThreadMessage(const ThreadMessage& t);
auto GetThreadMessages(std::list<ThreadMessage>* messages) -> void;
auto PushThreadMessage(const ThreadMessage& t) -> void;
auto RunPendingRunnables() -> void;
auto RunPauseCallbacks() -> void;
auto RunResumeCallbacks() -> void;
int thread_message_count_{};
bool bootstrapped_{};
std::list<std::pair<Runnable*, bool*>> runnables_;
std::list<Runnable*> pause_callbacks_;
std::list<Runnable*> resume_callbacks_;
std::thread* thread_{};
std::condition_variable thread_message_cv_;
std::mutex thread_message_mutex_;
std::list<ThreadMessage> thread_messages_;
int thread_message_count_ = 0;
std::condition_variable data_to_client_cv_;
std::mutex data_to_client_mutex_;
std::list<std::vector<char> > data_to_client_;
std::vector<Module*> modules_;
auto GetModule(int id) -> Module* {
assert(id >= 0 && id < static_cast<int>(modules_.size()));
return modules_[id];
}
// Complete list of all timers created by this group's modules.
std::condition_variable client_listener_cv_;
std::mutex client_listener_mutex_;
std::list<std::vector<char>> data_to_client_;
TimerList timers_;
static bool threads_paused_;
};
} // namespace ballistica

View File

@ -70,8 +70,7 @@ void BGDynamics::Step(const Vector3f& cam_pos) {
{ // Shadows.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_shadow_list_lock);
{
std::lock_guard<std::mutex> lock(
g_bg_dynamics_server->shadow_list_mutex_);
std::scoped_lock lock(g_bg_dynamics_server->shadow_list_mutex_);
auto size = g_bg_dynamics_server->shadows_.size();
d->shadow_step_data_.resize(size);
if (size > 0) {
@ -90,8 +89,7 @@ void BGDynamics::Step(const Vector3f& cam_pos) {
{ // Volume lights.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_volumelights_list_lock);
{
std::lock_guard<std::mutex> lock(
g_bg_dynamics_server->volume_light_list_mutex_);
std::scoped_lock lock(g_bg_dynamics_server->volume_light_list_mutex_);
auto size = g_bg_dynamics_server->volume_lights_.size();
d->volume_light_step_data_.resize(size);
if (size > 0) {
@ -116,7 +114,7 @@ void BGDynamics::Step(const Vector3f& cam_pos) {
{ // Fuses.
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_fuse_list_lock);
{
std::lock_guard<std::mutex> lock(g_bg_dynamics_server->fuse_list_mutex_);
std::scoped_lock lock(g_bg_dynamics_server->fuse_list_mutex_);
auto size = g_bg_dynamics_server->fuses_.size();
d->fuse_step_data_.resize(size);
if (size > 0) {
@ -137,7 +135,7 @@ void BGDynamics::Step(const Vector3f& cam_pos) {
// Increase our step count and ship it.
{
std::lock_guard<std::mutex> lock(g_bg_dynamics_server->step_count_mutex_);
std::scoped_lock lock(g_bg_dynamics_server->step_count_mutex_);
g_bg_dynamics_server->step_count_++;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h"
#include "ballistica/dynamics/bg/bg_dynamics_fuse_data.h"
#include "ballistica/dynamics/bg/bg_dynamics_height_cache.h"
@ -86,7 +87,7 @@ class BGDynamicsServer::Terrain {
// back to the main thread to get freed.
if (collide_model_) {
Object::Ref<CollideModelData>* ref = collide_model_;
g_game->PushCall([ref] {
g_game->thread()->PushCall([ref] {
(**ref).set_last_used_time(GetRealTime());
delete ref;
});
@ -660,7 +661,7 @@ void BGDynamicsServer::ParticleSet::UpdateAndCreateSnapshot(
}
BGDynamicsServer::BGDynamicsServer(Thread* thread)
: Module("bgDynamics", thread),
: thread_(thread),
height_cache_(new BGDynamicsHeightCache()),
collision_cache_(new CollisionCache) {
BA_PRECONDITION(g_bg_dynamics_server == nullptr);
@ -682,8 +683,6 @@ BGDynamicsServer::BGDynamicsServer(Thread* thread)
assert(ode_contact_group_);
}
BGDynamicsServer::~BGDynamicsServer() = default;
BGDynamicsServer::Tendril::~Tendril() {
// If we have a controller, tell them not to call us anymore.
if (controller_) {
@ -992,7 +991,7 @@ void BGDynamicsServer::Clear() {
}
void BGDynamicsServer::PushEmitCall(const BGDynamicsEmission& def) {
PushCall([this, def] { Emit(def); });
thread()->PushCall([this, def] { Emit(def); });
}
void BGDynamicsServer::Emit(const BGDynamicsEmission& def) {
@ -1364,7 +1363,7 @@ void BGDynamicsServer::Emit(const BGDynamicsEmission& def) {
}
void BGDynamicsServer::PushRemoveTerrainCall(CollideModelData* collide_model) {
PushCall([this, collide_model] {
thread()->PushCall([this, collide_model] {
assert(collide_model != nullptr);
bool found = false;
for (auto i = terrains_.begin(); i != terrains_.end(); ++i) {
@ -1394,19 +1393,19 @@ void BGDynamicsServer::PushRemoveTerrainCall(CollideModelData* collide_model) {
}
void BGDynamicsServer::PushAddShadowCall(BGDynamicsShadowData* shadow_data) {
PushCall([this, shadow_data] {
thread()->PushCall([this, shadow_data] {
assert(InBGDynamicsThread());
std::lock_guard<std::mutex> lock(shadow_list_mutex_);
std::scoped_lock lock(shadow_list_mutex_);
shadows_.push_back(shadow_data);
});
}
void BGDynamicsServer::PushRemoveShadowCall(BGDynamicsShadowData* shadow_data) {
PushCall([this, shadow_data] {
thread()->PushCall([this, shadow_data] {
assert(InBGDynamicsThread());
bool found = false;
{
std::lock_guard<std::mutex> lock(shadow_list_mutex_);
std::scoped_lock lock(shadow_list_mutex_);
for (auto i = shadows_.begin(); i != shadows_.end(); ++i) {
if ((*i) == shadow_data) {
found = true;
@ -1422,20 +1421,20 @@ void BGDynamicsServer::PushRemoveShadowCall(BGDynamicsShadowData* shadow_data) {
void BGDynamicsServer::PushAddVolumeLightCall(
BGDynamicsVolumeLightData* volume_light_data) {
PushCall([this, volume_light_data] {
thread()->PushCall([this, volume_light_data] {
// Add to our internal list.
std::lock_guard<std::mutex> lock(volume_light_list_mutex_);
std::scoped_lock lock(volume_light_list_mutex_);
volume_lights_.push_back(volume_light_data);
});
}
void BGDynamicsServer::PushRemoveVolumeLightCall(
BGDynamicsVolumeLightData* volume_light_data) {
PushCall([this, volume_light_data] {
thread()->PushCall([this, volume_light_data] {
// Remove from our list and kill.
bool found = false;
{
std::lock_guard<std::mutex> lock(volume_light_list_mutex_);
std::scoped_lock lock(volume_light_list_mutex_);
for (auto i = volume_lights_.begin(); i != volume_lights_.end(); ++i) {
if ((*i) == volume_light_data) {
found = true;
@ -1450,17 +1449,17 @@ void BGDynamicsServer::PushRemoveVolumeLightCall(
}
void BGDynamicsServer::PushAddFuseCall(BGDynamicsFuseData* fuse_data) {
PushCall([this, fuse_data] {
std::lock_guard<std::mutex> lock(fuse_list_mutex_);
thread()->PushCall([this, fuse_data] {
std::scoped_lock lock(fuse_list_mutex_);
fuses_.push_back(fuse_data);
});
}
void BGDynamicsServer::PushRemoveFuseCall(BGDynamicsFuseData* fuse_data) {
PushCall([this, fuse_data] {
thread()->PushCall([this, fuse_data] {
bool found = false;
{
std::lock_guard<std::mutex> lock(fuse_list_mutex_);
std::scoped_lock lock(fuse_list_mutex_);
for (auto i = fuses_.begin(); i != fuses_.end(); i++) {
if ((*i) == fuse_data) {
found = true;
@ -1475,11 +1474,11 @@ void BGDynamicsServer::PushRemoveFuseCall(BGDynamicsFuseData* fuse_data) {
}
void BGDynamicsServer::PushSetDebrisFrictionCall(float friction) {
PushCall([this, friction] { debris_friction_ = friction; });
thread()->PushCall([this, friction] { debris_friction_ = friction; });
}
void BGDynamicsServer::PushSetDebrisKillHeightCall(float height) {
PushCall([this, height] { debris_kill_height_ = height; });
thread()->PushCall([this, height] { debris_kill_height_ = height; });
}
auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* {
@ -2215,7 +2214,7 @@ auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* {
} // NOLINT (yes this should be shorter)
void BGDynamicsServer::PushTooSlowCall() {
PushCall([this] {
thread()->PushCall([this] {
if (chunk_count_ > 0 || tendril_count_thick_ > 0
|| tendril_count_thin_ > 0) {
// Ok lets kill a small percentage of our oldest chunks.
@ -2313,7 +2312,7 @@ void BGDynamicsServer::Step(StepData* step_data) {
// Now generate a snapshot of our state and send it to the game thread,
// so they can draw us.
BGDynamicsDrawSnapshot* snapshot = CreateDrawSnapshot();
g_game->PushCall([snapshot] {
g_game->thread()->PushCall([snapshot] {
snapshot->SetLogicThreadOwnership();
g_bg_dynamics->SetDrawSnapshot(snapshot);
});
@ -2326,19 +2325,19 @@ void BGDynamicsServer::Step(StepData* step_data) {
// Job's done!
{
std::lock_guard<std::mutex> lock(step_count_mutex_);
std::scoped_lock lock(step_count_mutex_);
step_count_--;
}
assert(step_count_ >= 0);
}
void BGDynamicsServer::PushStepCall(StepData* data) {
PushCall([this, data] { Step(data); });
thread()->PushCall([this, data] { Step(data); });
}
void BGDynamicsServer::PushAddTerrainCall(
Object::Ref<CollideModelData>* collide_model) {
PushCall([this, collide_model] {
thread()->PushCall([this, collide_model] {
assert(InBGDynamicsThread());
assert(collide_model != nullptr);
@ -2639,7 +2638,7 @@ void BGDynamicsServer::UpdateShadows() {
{
BA_DEBUG_TIME_CHECK_BEGIN(bg_dynamic_shadow_list_lock);
{
std::lock_guard<std::mutex> lock(shadow_list_mutex_);
std::scoped_lock lock(shadow_list_mutex_);
for (auto&& s : shadows_) {
s->UpdateClientData();
}

View File

@ -5,10 +5,10 @@
#include <list>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include "ballistica/math/matrix44f.h"
#include "ballistica/math/vector3f.h"
@ -16,7 +16,7 @@
namespace ballistica {
class BGDynamicsServer : public Module {
class BGDynamicsServer {
public:
struct Particle {
float x;
@ -81,7 +81,7 @@ class BGDynamicsServer : public Module {
};
explicit BGDynamicsServer(Thread* thread);
~BGDynamicsServer() override;
auto time() const -> uint32_t { return time_; }
auto graphics_quality() const -> GraphicsQuality { return graphics_quality_; }
@ -98,6 +98,7 @@ class BGDynamicsServer : public Module {
return spark_particles_.get();
}
auto step_count() const -> int { return step_count_; }
auto thread() const -> Thread* { return thread_; }
private:
class Terrain;
@ -121,6 +122,8 @@ class BGDynamicsServer : public Module {
void UpdateFuses();
void UpdateShadows();
auto CreateDrawSnapshot() -> BGDynamicsDrawSnapshot*;
Thread* thread_{};
BGDynamicsChunkType cb_type_ = BGDynamicsChunkType::kRock;
dBodyID cb_body_{};
float cb_cfm_{0.0f};

View File

@ -92,32 +92,32 @@ auto Account::AccountTypeToIconString(V1AccountType type) -> std::string {
Account::Account() = default;
auto Account::GetLoginName() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
return login_name_;
}
auto Account::GetLoginID() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
return login_id_;
}
auto Account::GetToken() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
return token_;
}
auto Account::GetExtra() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
return extra_;
}
auto Account::GetExtra2() -> std::string {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
return extra_2_;
}
auto Account::GetLoginState(int* state_num) -> V1LoginState {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
if (state_num) {
*state_num = login_state_num_;
}
@ -125,18 +125,18 @@ auto Account::GetLoginState(int* state_num) -> V1LoginState {
}
void Account::SetExtra(const std::string& extra) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
extra_ = extra;
}
void Account::SetExtra2(const std::string& extra) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
extra_2_ = extra;
}
void Account::SetToken(const std::string& account_id,
const std::string& token) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
// Hmm, does this compare logic belong in here?
if (login_id_ == account_id) {
token_ = token;
@ -148,7 +148,7 @@ void Account::SetLogin(V1AccountType account_type, V1LoginState login_state,
const std::string& login_id) {
bool call_login_did_change = false;
{
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
// We call out to Python so need to be in game thread.
assert(InLogicThread());
@ -183,7 +183,7 @@ void Account::SetLogin(V1AccountType account_type, V1LoginState login_state,
}
void Account::SetProductsPurchased(const std::vector<std::string>& products) {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
std::unordered_map<std::string, bool> purchases_old = product_purchases_;
product_purchases_.clear();
for (auto&& i : products) {
@ -195,7 +195,7 @@ void Account::SetProductsPurchased(const std::vector<std::string>& products) {
}
auto Account::GetProductPurchased(const std::string& product) -> bool {
std::lock_guard<std::mutex> lock(mutex_);
std::scoped_lock lock(mutex_);
auto i = product_purchases_.find(product);
if (i == product_purchases_.end()) {
return false;

View File

@ -2,6 +2,7 @@
#include "ballistica/game/connection/connection_set.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/connection/connection_to_client_udp.h"
#include "ballistica/game/connection/connection_to_host_udp.h"
#include "ballistica/game/game.h"
@ -215,14 +216,15 @@ void ConnectionSet::PushUDPConnectionPacketCall(
const std::vector<uint8_t>& data, const SockAddr& addr) {
// Avoid buffer-full errors if something is causing us to write too often;
// these are unreliable messages so its ok to just drop them.
if (!g_game->CheckPushSafety()) {
if (!g_game->thread()->CheckPushSafety()) {
BA_LOG_ONCE(
"Ignoring excessive udp-connection input packets; (could this be a "
"flood attack?).");
return;
}
g_game->PushCall([this, data, addr] { UDPConnectionPacket(data, addr); });
g_game->thread()->PushCall(
[this, data, addr] { UDPConnectionPacket(data, addr); });
}
auto ConnectionSet::Shutdown() -> void {
@ -352,11 +354,11 @@ auto ConnectionSet::DisconnectClient(int client_id, int ban_seconds) -> bool {
}
void ConnectionSet::PushClientDisconnectedCall(int id) {
g_game->PushCall([this, id] { HandleClientDisconnected(id); });
g_game->thread()->PushCall([this, id] { HandleClientDisconnected(id); });
}
void ConnectionSet::PushDisconnectedFromHostCall() {
g_game->PushCall([this] {
g_game->thread()->PushCall([this] {
if (connection_to_host_.exists()) {
bool was_connected = connection_to_host_->can_communicate();
connection_to_host_.Clear();
@ -377,7 +379,7 @@ void ConnectionSet::PushDisconnectedFromHostCall() {
void ConnectionSet::PushHostConnectedUDPCall(const SockAddr& addr,
bool print_connect_progress) {
g_game->PushCall([this, addr, print_connect_progress] {
g_game->thread()->PushCall([this, addr, print_connect_progress] {
// Attempt to disconnect any clients we have, turn off public-party
// advertising, etc.
g_game->CleanUpBeforeConnectingToHost();
@ -389,7 +391,7 @@ void ConnectionSet::PushHostConnectedUDPCall(const SockAddr& addr,
}
void ConnectionSet::PushDisconnectFromHostCall() {
g_game->PushCall([this] {
g_game->thread()->PushCall([this] {
if (connection_to_host_.exists()) {
connection_to_host_->RequestDisconnect();
}

View File

@ -66,7 +66,7 @@ const int kMaxChatMessages = 40;
const int kKickBanSeconds = 5 * 60;
Game::Game(Thread* thread)
: Module("game", thread),
: thread_(thread),
game_roster_(cJSON_CreateArray()),
realtimers_(new TimerList()),
connections_(std::make_unique<ConnectionSet>()) {
@ -89,6 +89,9 @@ Game::Game(Thread* thread)
Context::Init();
// We want to be informed when our thread is pausing.
thread->AddPauseCallback(NewLambdaRunnableRaw([this] { OnThreadPause(); }));
// Waaah does UI need to be a bs::Object?
// Update: yes it does in order to be a context target.
// (need to be able to create weak-refs to it).
@ -116,7 +119,7 @@ Game::Game(Thread* thread)
// This way it is more likely we can show a fatal error dialog
// since the main thread won't be blocking waiting for us to init.
std::string what = e.what();
PushCall([what] {
this->thread()->PushCall([what] {
// Just throw a standard exception since our what already
// contains a stack trace; if we throw an Exception we wind
// up with a useless second one.
@ -125,8 +128,16 @@ Game::Game(Thread* thread)
}
}
auto Game::OnThreadPause() -> void {
ScopedSetContext cp(GetUIContextTarget());
// Let Python and internal layers do their thing.
g_python->obj(Python::ObjID::kOnAppPauseCall).Call();
g_app_internal->OnLogicThreadPause();
}
void Game::InitSpecialChars() {
std::lock_guard<std::mutex> lock(special_char_mutex_);
std::scoped_lock lock(special_char_mutex_);
special_char_strings_[SpecialChar::kDownArrow] = "\xee\x80\x84";
special_char_strings_[SpecialChar::kUpArrow] = "\xee\x80\x83";
@ -244,13 +255,13 @@ void Game::ResetActivityTracking() {
#if BA_VR_BUILD
void Game::PushVRHandsState(const VRHandsState& state) {
PushCall([this, state] { vr_hands_state_ = state; });
thread()->PushCall([this, state] { vr_hands_state_ = state; });
}
#endif // BA_VR_BUILD
void Game::PushMediaPruneCall(int level) {
PushCall([level] {
thread()->PushCall([level] {
assert(InLogicThread());
g_media->Prune(level);
});
@ -260,13 +271,14 @@ void Game::PushSetV1LoginCall(V1AccountType account_type,
V1LoginState account_state,
const std::string& account_name,
const std::string& account_id) {
PushCall([this, account_type, account_state, account_name, account_id] {
thread()->PushCall([this, account_type, account_state, account_name,
account_id] {
g_account->SetLogin(account_type, account_state, account_name, account_id);
});
}
void Game::PushInitialScreenCreatedCall() {
PushCall([this] { InitialScreenCreated(); });
thread()->PushCall([this] { InitialScreenCreated(); });
}
void Game::InitialScreenCreated() {
@ -288,22 +300,22 @@ void Game::InitialScreenCreated() {
// Set up our timers.
process_timer_ =
NewThreadTimer(0, true, NewLambdaRunnable([this] { Process(); }));
media_prune_timer_ =
NewThreadTimer(2345, true, NewLambdaRunnable([this] { Prune(); }));
thread()->NewTimer(0, true, NewLambdaRunnable([this] { Process(); }));
media_prune_timer_ = thread()->NewTimer(
2345, true, NewLambdaRunnable([this] { PruneMedia(); }));
// Normally we schedule updates when we're asked to draw a frame.
// In headless mode, however, we're not drawing, so we need a dedicated
// timer to take its place.
if (HeadlessMode()) {
headless_update_timer_ =
NewThreadTimer(8, true, NewLambdaRunnable([this] { Update(); }));
thread()->NewTimer(8, true, NewLambdaRunnable([this] { Update(); }));
}
RunAppLaunchCommands();
}
void Game::Prune() { g_media->Prune(); }
void Game::PruneMedia() { g_media->Prune(); }
// Launch into main menu or whatever else.
void Game::RunAppLaunchCommands() {
@ -334,8 +346,6 @@ void Game::RunAppLaunchCommands() {
UpdateProcessTimer();
}
Game::~Game() = default;
// Set up our sleeping based on what we're doing.
void Game::UpdateProcessTimer() {
assert(InLogicThread());
@ -497,7 +507,7 @@ void Game::HandleQuitOnIdle() {
if (!idle_exiting_ && idle_seconds > (idle_exit_minutes_.value() * 60.0f)) {
idle_exiting_ = true;
PushCall([this, idle_seconds] {
thread()->PushCall([this, idle_seconds] {
assert(InLogicThread());
// Just go through _ba.quit()
@ -708,7 +718,7 @@ auto Game::IsInUIContext() const -> bool {
}
void Game::PushShowURLCall(const std::string& url) {
PushCall([url] {
thread()->PushCall([url] {
assert(InLogicThread());
assert(g_python);
g_python->ShowURL(url);
@ -725,7 +735,7 @@ auto Game::GetForegroundContext() -> Context {
}
void Game::PushBackButtonCall(InputDevice* input_device) {
PushCall([this, input_device] {
thread()->PushCall([this, input_device] {
assert(InLogicThread());
// Ignore if UI isn't up yet.
@ -747,7 +757,7 @@ void Game::PushBackButtonCall(InputDevice* input_device) {
}
void Game::PushStringEditSetCall(const std::string& value) {
PushCall([value] {
thread()->PushCall([value] {
if (!g_ui) {
Log("Error: No ui on StringEditSetEvent.");
return;
@ -764,7 +774,7 @@ void Game::PushStringEditSetCall(const std::string& value) {
}
void Game::PushStringEditCancelCall() {
PushCall([] {
thread()->PushCall([] {
if (!g_ui) {
Log("Error: No ui in PushStringEditCancelCall.");
return;
@ -903,7 +913,7 @@ void Game::RunMainMenu() {
// in the current visible context.
void Game::PushInGameConsoleScriptCommand(const std::string& command) {
PushCall([this, command] {
thread()->PushCall([this, command] {
// These are always run in whichever context is 'visible'.
ScopedSetContext cp(GetForegroundContext());
PythonCommand cmd(command, "<in-game-console>");
@ -932,7 +942,7 @@ void Game::PushInGameConsoleScriptCommand(const std::string& command) {
// Commands run via stdin.
void Game::PushStdinScriptCommand(const std::string& command) {
PushCall([this, command] {
thread()->PushCall([this, command] {
// These are always run in whichever context is 'visible'.
ScopedSetContext cp(GetForegroundContext());
PythonCommand cmd(command, "<stdin>");
@ -966,7 +976,7 @@ void Game::PushStdinScriptCommand(const std::string& command) {
}
void Game::PushInterruptSignalCall() {
PushCall([this] {
thread()->PushCall([this] {
assert(InLogicThread());
// Special case; when running under the server-wrapper, we completely
@ -981,26 +991,18 @@ void Game::PushInterruptSignalCall() {
}
void Game::PushAskUserForTelnetAccessCall() {
PushCall([this] {
thread()->PushCall([this] {
assert(InLogicThread());
ScopedSetContext cp(GetUIContext());
g_python->obj(Python::ObjID::kTelnetAccessRequestCall).Call();
});
}
void Game::HandleThreadPause() {
ScopedSetContext cp(GetUIContextTarget());
// Let Python and internal layers do their thing.
g_python->obj(Python::ObjID::kOnAppPauseCall).Call();
g_app_internal->OnLogicThreadPause();
}
void Game::PushPythonCall(const Object::Ref<PythonContextCall>& call) {
// Since we're mucking with refs, need to limit to game thread.
BA_PRECONDITION(InLogicThread());
BA_PRECONDITION(call->object_strong_ref_count() > 0);
PushCall([call] {
thread()->PushCall([call] {
assert(call.exists());
call->Run();
});
@ -1011,7 +1013,7 @@ void Game::PushPythonCallArgs(const Object::Ref<PythonContextCall>& call,
// Since we're mucking with refs, need to limit to game thread.
BA_PRECONDITION(InLogicThread());
BA_PRECONDITION(call->object_strong_ref_count() > 0);
PushCall([call, args] {
thread()->PushCall([call, args] {
assert(call.exists());
call->Run(args.get());
});
@ -1025,7 +1027,7 @@ void Game::PushPythonWeakCall(const Object::WeakRef<PythonContextCall>& call) {
// object to be passed in.
assert(call.exists() && call->object_strong_ref_count() > 0);
PushCall([call] {
thread()->PushCall([call] {
if (call.exists()) {
Python::ScopedCallLabel label("PythonWeakCallMessage");
call->Run();
@ -1042,13 +1044,13 @@ void Game::PushPythonWeakCallArgs(
// object to be passed in.
assert(call.exists() && call->object_strong_ref_count() > 0);
PushCall([call, args] {
thread()->PushCall([call, args] {
if (call.exists()) call->Run(args.get());
});
}
void Game::PushPythonRawCallable(PyObject* callable) {
PushCall([this, callable] {
thread()->PushCall([this, callable] {
assert(InLogicThread());
// Lets run this in the UI context.
@ -1065,7 +1067,8 @@ void Game::PushPythonRawCallable(PyObject* callable) {
void Game::PushScreenMessage(const std::string& message,
const Vector3f& color) {
PushCall([message, color] { g_graphics->AddScreenMessage(message, color); });
thread()->PushCall(
[message, color] { g_graphics->AddScreenMessage(message, color); });
}
void Game::SetReplaySpeedExponent(int val) {
@ -1113,19 +1116,19 @@ auto Game::GetUIContext() const -> Context {
}
void Game::PushToggleManualCameraCall() {
PushCall([] { g_graphics->ToggleManualCamera(); });
thread()->PushCall([] { g_graphics->ToggleManualCamera(); });
}
void Game::PushToggleDebugInfoDisplayCall() {
PushCall([] { g_graphics->ToggleNetworkDebugDisplay(); });
thread()->PushCall([] { g_graphics->ToggleNetworkDebugDisplay(); });
}
void Game::PushToggleCollisionGeometryDisplayCall() {
PushCall([] { g_graphics->ToggleDebugDraw(); });
thread()->PushCall([] { g_graphics->ToggleDebugDraw(); });
}
void Game::PushMainMenuPressCall(InputDevice* device) {
PushCall([this, device] { MainMenuPress(device); });
thread()->PushCall([this, device] { MainMenuPress(device); });
}
void Game::MainMenuPress(InputDevice* device) {
@ -1135,7 +1138,7 @@ void Game::MainMenuPress(InputDevice* device) {
void Game::PushScreenResizeCall(float virtual_width, float virtual_height,
float pixel_width, float pixel_height) {
PushCall([=] {
thread()->PushCall([=] {
ScreenResize(virtual_width, virtual_height, pixel_width, pixel_height);
});
}
@ -1158,7 +1161,8 @@ void Game::ScreenResize(float virtual_width, float virtual_height,
void Game::PushGameServiceAchievementListCall(
const std::set<std::string>& achievements) {
PushCall([this, achievements] { GameServiceAchievementList(achievements); });
thread()->PushCall(
[this, achievements] { GameServiceAchievementList(achievements); });
}
void Game::GameServiceAchievementList(
@ -1171,7 +1175,7 @@ void Game::GameServiceAchievementList(
void Game::PushScoresToBeatResponseCall(bool success,
const std::list<ScoreToBeat>& scores,
void* py_callback) {
PushCall([this, success, scores, py_callback] {
thread()->PushCall([this, success, scores, py_callback] {
ScoresToBeatResponse(success, scores, py_callback);
});
}
@ -1185,15 +1189,16 @@ void Game::ScoresToBeatResponse(bool success,
}
void Game::PushPlaySoundCall(SystemSoundID sound) {
PushCall([sound] { g_audio->PlaySound(g_media->GetSound(sound)); });
thread()->PushCall([sound] { g_audio->PlaySound(g_media->GetSound(sound)); });
}
void Game::PushFriendScoreSetCall(const FriendScoreSet& score_set) {
PushCall([score_set] { g_python->HandleFriendScoresCB(score_set); });
thread()->PushCall(
[score_set] { g_python->HandleFriendScoresCB(score_set); });
}
void Game::PushConfirmQuitCall() {
PushCall([this] {
thread()->PushCall([this] {
assert(InLogicThread());
if (HeadlessMode()) {
Log("PushConfirmQuitCall() unhandled on headless.");
@ -1256,11 +1261,11 @@ void Game::Draw() {
}
void Game::PushFrameDefRequest() {
PushCall([this] { Draw(); });
thread()->PushCall([this] { Draw(); });
}
void Game::PushOnAppResumeCall() {
PushCall([] {
thread()->PushCall([] {
// Wipe out whatever input device was in control of the UI.
assert(g_ui);
g_ui->SetUIInputDevice(nullptr);
@ -1348,7 +1353,7 @@ void Game::ApplyConfig() {
// FIXME: this should exist either on the client or the server; not both.
// (and should be communicated via frameldefs/etc.)
bool tv_border = g_app_config->Resolve(AppConfig::BoolID::kTVBorder);
g_graphics_server->PushCall(
g_graphics_server->thread()->PushCall(
[tv_border] { g_graphics_server->set_tv_border(tv_border); });
g_graphics->set_tv_border(tv_border);
@ -1427,11 +1432,11 @@ void Game::ApplyConfig() {
}
void Game::PushApplyConfigCall() {
PushCall([this] { ApplyConfig(); });
thread()->PushCall([this] { ApplyConfig(); });
}
void Game::PushRemoveGraphicsServerRenderHoldCall() {
PushCall([] {
thread()->PushCall([] {
// This call acts as a flush of sorts; when it goes through,
// we push a call to the graphics server saying its ok for it
// to start rendering again. Thus any already-queued-up
@ -1442,7 +1447,7 @@ void Game::PushRemoveGraphicsServerRenderHoldCall() {
void Game::PushFreeMediaComponentRefsCall(
const std::vector<Object::Ref<MediaComponentData>*>& components) {
PushCall([components] {
thread()->PushCall([components] {
for (auto&& i : components) {
delete i;
}
@ -1450,7 +1455,7 @@ void Game::PushFreeMediaComponentRefsCall(
}
void Game::PushHavePendingLoadsDoneCall() {
PushCall([] { g_media->ClearPendingLoadsDoneList(); });
thread()->PushCall([] { g_media->ClearPendingLoadsDoneList(); });
}
void Game::ToggleConsole() {
@ -1461,7 +1466,7 @@ void Game::ToggleConsole() {
}
void Game::PushConsolePrintCall(const std::string& msg) {
PushCall([msg] {
thread()->PushCall([msg] {
// Send them to the console if its been created or store them
// for when it is (unless we're headless in which case it never will).
if (auto console = g_app_globals->console) {
@ -1473,14 +1478,14 @@ void Game::PushConsolePrintCall(const std::string& msg) {
}
void Game::PushHavePendingLoadsCall() {
PushCall([this] {
thread()->PushCall([this] {
have_pending_loads_ = true;
UpdateProcessTimer();
});
}
void Game::PushShutdownCall(bool soft) {
PushCall([this, soft] { Shutdown(soft); });
thread()->PushCall([this, soft] { Shutdown(soft); });
}
void Game::Shutdown(bool soft) {
@ -1499,7 +1504,7 @@ void Game::Shutdown(bool soft) {
// Let's do the same stuff we do when our thread is pausing. (committing
// account-client to disk, etc).
HandleThreadPause();
OnThreadPause();
// Attempt to report/store outstanding log stuff.
g_app_internal->PutLog(false);
@ -1553,7 +1558,7 @@ void Game::SetLanguageKeys(
const std::unordered_map<std::string, std::string>& language) {
assert(InLogicThread());
{
std::lock_guard<std::mutex> lock(language_mutex_);
std::scoped_lock lock(language_mutex_);
language_ = language;
}
@ -1797,7 +1802,7 @@ auto Game::CompileResourceString(const std::string& s, const std::string& loc,
auto Game::GetResourceString(const std::string& key) -> std::string {
std::string val;
{
std::lock_guard<std::mutex> lock(language_mutex_);
std::scoped_lock lock(language_mutex_);
auto i = language_.find(key);
if (i != language_.end()) {
val = i->second;
@ -1807,7 +1812,7 @@ auto Game::GetResourceString(const std::string& key) -> std::string {
}
auto Game::CharStr(SpecialChar id) -> std::string {
std::lock_guard<std::mutex> lock(special_char_mutex_);
std::scoped_lock lock(special_char_mutex_);
std::string val;
auto i = special_char_strings_.find(id);
if (i != special_char_strings_.end()) {

View File

@ -13,7 +13,7 @@
#include <utility>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
namespace ballistica {
@ -22,10 +22,10 @@ const int kMaxPartyNameCombinedSize = 25;
/// The Game Module generally runs on a dedicated thread; it manages
/// all game logic, builds frame_defs to send to the graphics-server for
/// rendering, etc.
class Game : public Module {
class Game {
public:
explicit Game(Thread* thread);
~Game() override;
auto LaunchHostSession(PyObject* session_type_obj,
BenchmarkType benchmark_type = BenchmarkType::kNone)
-> void;
@ -102,7 +102,7 @@ class Game : public Module {
auto ChangeGameSpeed(int offs) -> void;
auto ResetInput() -> void;
auto RunMainMenu() -> void;
auto HandleThreadPause() -> void override;
auto OnThreadPause() -> void;
#if BA_VR_BUILD
auto PushVRHandsState(const VRHandsState& state) -> void;
@ -244,6 +244,7 @@ class Game : public Module {
return connections_.get();
}
auto mark_game_roster_dirty() -> void { game_roster_dirty_ = true; }
auto thread() const -> Thread* { return thread_; }
private:
auto HandleQuitOnIdle() -> void;
@ -258,7 +259,7 @@ class Game : public Module {
auto ScoresToBeatResponse(bool success, const std::list<ScoreToBeat>& scores,
void* py_callback) -> void;
auto Prune() -> void; // Periodic pruning of dead stuff.
auto PruneMedia() -> void;
auto Update() -> void;
auto Process() -> void;
auto UpdateKickVote() -> void;
@ -277,6 +278,7 @@ class Game : public Module {
int rift_step_index_{};
#endif
Thread* thread_{};
std::unique_ptr<ConnectionSet> connections_;
std::list<std::pair<millisecs_t, PlayerSpec> > banned_players_;
std::list<std::string> chat_messages_;

View File

@ -6,6 +6,7 @@
#include "ballistica/game/game_stream.h"
#include "ballistica/game/player.h"
#include "ballistica/game/session/host_session.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/timer.h"
#include "ballistica/input/device/input_device.h"
#include "ballistica/media/component/collide_model.h"

View File

@ -5,6 +5,7 @@
#include "ballistica/game/game_stream.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/player.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/generic/timer.h"
#include "ballistica/graphics/graphics.h"
#include "ballistica/input/device/input_device.h"

View File

@ -13,8 +13,8 @@ class Runnable : public Object {
public:
virtual void Run() = 0;
// these are used on lots of threads; lets
// lock to wherever we're first referenced
// these are used on lots of threads; we lock to whichever
// thread first creates a reference to us.
auto GetThreadOwnership() const -> ThreadOwnership override;
};

View File

@ -858,7 +858,7 @@ auto Graphics::GetEmptyFrameDef() -> FrameDef* {
void Graphics::ClearFrameDefDeleteList() {
assert(InLogicThread());
std::lock_guard<std::mutex> lock(frame_def_delete_list_mutex_);
std::scoped_lock lock(frame_def_delete_list_mutex_);
for (auto& i : frame_def_delete_list_) {
// We recycle our frame_defs so we don't have to reallocate all those
@ -1422,7 +1422,7 @@ void Graphics::ClearScreenMessageTranslations() {
}
void Graphics::ReturnCompletedFrameDef(FrameDef* frame_def) {
std::lock_guard<std::mutex> lock(frame_def_delete_list_mutex_);
std::scoped_lock lock(frame_def_delete_list_mutex_);
g_graphics->frame_def_delete_list_.push_back(frame_def);
}

View File

@ -30,15 +30,15 @@ void GraphicsServer::FullscreenCheck() {
}
#endif
GraphicsServer::GraphicsServer(Thread* thread) : Module("graphics", thread) {
GraphicsServer::GraphicsServer(Thread* thread) : thread_(thread) {
// We're a singleton.
assert(g_graphics_server == nullptr);
g_graphics_server = this;
// For janky old non-event-push mode, just fall back on a timer for rendering.
if (!g_platform->IsEventPushMode()) {
render_timer_ = NewThreadTimer(1000 / 60, true,
NewLambdaRunnable([this] { TryRender(); }));
render_timer_ = this->thread()->NewTimer(
1000 / 60, true, NewLambdaRunnable([this] { TryRender(); }));
}
}
@ -198,7 +198,7 @@ void GraphicsServer::ReloadMedia() {
// Now tell the game thread to kick off loads for everything, flip on
// progress bar drawing, and then tell the graphics thread to stop ignoring
// frame-defs.
g_game->PushCall([this] {
g_game->thread()->PushCall([this] {
g_media->MarkAllMediaForLoad();
g_graphics->EnableProgressBar(false);
PushRemoveRenderHoldCall();
@ -249,7 +249,7 @@ void GraphicsServer::RebuildLostContext() {
// Now tell the game thread to kick off loads for everything, flip on progress
// bar drawing, and then tell the graphics thread to stop ignoring frame-defs.
g_game->PushCall([this] {
g_game->thread()->PushCall([this] {
g_media->MarkAllMediaForLoad();
g_graphics->EnableProgressBar(false);
PushRemoveRenderHoldCall();
@ -343,8 +343,8 @@ void GraphicsServer::SetScreen(bool fullscreen, int width, int height,
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && BA_SDL_BUILD
if (create_fullscreen_check_timer) {
NewThreadTimer(1000, false,
NewLambdaRunnable([this] { FullscreenCheck(); }));
thread()->NewTimer(1000, false,
NewLambdaRunnable([this] { FullscreenCheck(); }));
}
#endif // BA_OSTYPE_MACOS
@ -463,7 +463,7 @@ void GraphicsServer::HandleFullContextScreenRebuild(
// Now tell the game thread to kick off loads for everything, flip on
// progress bar drawing, and then tell the graphics thread to stop ignoring
// frame-defs.
g_game->PushCall([this] {
g_game->thread()->PushCall([this] {
g_media->MarkAllMediaForLoad();
g_graphics->set_internal_components_inited(false);
g_graphics->EnableProgressBar(false);
@ -708,18 +708,18 @@ void GraphicsServer::PushSetScreenCall(bool fullscreen, int width, int height,
TextureQuality texture_quality,
GraphicsQuality graphics_quality,
const std::string& android_res) {
PushCall([=] {
thread()->PushCall([=] {
SetScreen(fullscreen, width, height, texture_quality, graphics_quality,
android_res);
});
}
void GraphicsServer::PushReloadMediaCall() {
PushCall([this] { ReloadMedia(); });
thread()->PushCall([this] { ReloadMedia(); });
}
void GraphicsServer::PushSetScreenGammaCall(float gamma) {
PushCall([this, gamma] {
thread()->PushCall([this, gamma] {
assert(InGraphicsThread());
if (!renderer_) {
return;
@ -729,7 +729,7 @@ void GraphicsServer::PushSetScreenGammaCall(float gamma) {
}
void GraphicsServer::PushSetScreenPixelScaleCall(float pixel_scale) {
PushCall([this, pixel_scale] {
thread()->PushCall([this, pixel_scale] {
assert(InGraphicsThread());
if (!renderer_) {
return;
@ -739,7 +739,7 @@ void GraphicsServer::PushSetScreenPixelScaleCall(float pixel_scale) {
}
void GraphicsServer::PushSetVSyncCall(bool sync, bool auto_sync) {
PushCall([this, sync, auto_sync] {
thread()->PushCall([this, sync, auto_sync] {
assert(InGraphicsThread());
#if BA_SDL_BUILD
@ -769,7 +769,7 @@ void GraphicsServer::PushSetVSyncCall(bool sync, bool auto_sync) {
void GraphicsServer::PushComponentUnloadCall(
const std::vector<Object::Ref<MediaComponentData>*>& components) {
PushCall([this, components] {
thread()->PushCall([this, components] {
// Unload all components we were passed.
for (auto&& i : components) {
(**i).Unload();
@ -781,7 +781,7 @@ void GraphicsServer::PushComponentUnloadCall(
}
void GraphicsServer::PushRemoveRenderHoldCall() {
PushCall([this] {
thread()->PushCall([this] {
assert(render_hold_);
render_hold_--;
if (render_hold_ < 0) {

View File

@ -9,7 +9,6 @@
#include <vector>
#include "ballistica/ballistica.h"
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
#include "ballistica/math/matrix44f.h"
@ -17,7 +16,7 @@ namespace ballistica {
// Runs in the main thread and renders frame_defs shipped to it by the
// Graphics
class GraphicsServer : public Module {
class GraphicsServer {
public:
explicit GraphicsServer(Thread* thread);
auto PushSetScreenGammaCall(float gamma) -> void;
@ -170,7 +169,7 @@ class GraphicsServer : public Module {
}
auto RebuildLostContext() -> void;
~GraphicsServer() override;
~GraphicsServer();
auto renderer() { return renderer_; }
auto quality() const -> GraphicsQuality {
@ -240,6 +239,7 @@ class GraphicsServer : public Module {
auto texture_quality_requested() const { return texture_quality_requested_; }
auto renderer() const { return renderer_; }
auto initial_screen_created() const { return initial_screen_created_; }
auto thread() const -> Thread* { return thread_; }
private:
auto HandleFullscreenToggling(bool do_set_existing_fs, bool do_toggle_fs,
@ -273,6 +273,7 @@ class GraphicsServer : public Module {
#if BA_ENABLE_OPENGL
std::unique_ptr<GLContext> gl_context_;
#endif
Thread* thread_{};
float res_x_{};
float res_y_{};
float res_x_virtual_{0.0f};

View File

@ -826,7 +826,7 @@ auto TextGraphics::GetBigCharIndex(int c) -> int {
}
void TextGraphics::LoadGlyphPage(uint32_t index) {
std::lock_guard<std::mutex> lock(glyph_load_mutex_);
std::scoped_lock lock(glyph_load_mutex_);
// Its possible someone else coulda loaded it since we last checked.
if (g_glyph_pages[index] == nullptr) {

View File

@ -5,6 +5,7 @@
#include "ballistica/app/app.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/audio/audio.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/connection/connection_set.h"
#include "ballistica/game/player.h"
#include "ballistica/graphics/renderer.h"
@ -305,7 +306,7 @@ Joystick::~Joystick() {
#if BA_ENABLE_SDL_JOYSTICKS
assert(g_app);
auto joystick = sdl_joystick_;
g_app->PushCall([joystick] { SDL_JoystickClose(joystick); });
g_app->thread()->PushCall([joystick] { SDL_JoystickClose(joystick); });
sdl_joystick_ = nullptr;
#else
Log("sdl_joystick_ set in non-sdl-joystick build destructor.");

View File

@ -5,6 +5,7 @@
#include "ballistica/app/app_config.h"
#include "ballistica/app/app_globals.h"
#include "ballistica/audio/audio.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/player.h"
#include "ballistica/graphics/camera.h"
#include "ballistica/input/device/joystick.h"
@ -330,7 +331,7 @@ Input::Input() {
}
void Input::PushCreateKeyboardInputDevices() {
g_game->PushCall([this] { CreateKeyboardInputDevices(); });
g_game->thread()->PushCall([this] { CreateKeyboardInputDevices(); });
}
void Input::CreateKeyboardInputDevices() {
@ -346,7 +347,7 @@ void Input::CreateKeyboardInputDevices() {
}
void Input::PushDestroyKeyboardInputDevices() {
g_game->PushCall([this] { DestroyKeyboardInputDevices(); });
g_game->thread()->PushCall([this] { DestroyKeyboardInputDevices(); });
}
void Input::DestroyKeyboardInputDevices() {
@ -554,7 +555,7 @@ void Input::ShowStandardInputDeviceDisconnectedMessage(InputDevice* j) {
void Input::PushAddInputDeviceCall(InputDevice* input_device,
bool standard_message) {
g_game->PushCall([this, input_device, standard_message] {
g_game->thread()->PushCall([this, input_device, standard_message] {
AddInputDevice(input_device, standard_message);
});
}
@ -616,7 +617,7 @@ void Input::AddInputDevice(InputDevice* input, bool standard_message) {
void Input::PushRemoveInputDeviceCall(InputDevice* input_device,
bool standard_message) {
g_game->PushCall([this, input_device, standard_message] {
g_game->thread()->PushCall([this, input_device, standard_message] {
RemoveInputDevice(input_device, standard_message);
});
}
@ -1087,7 +1088,7 @@ void Input::HandleBackPress(bool from_toolbar) {
}
void Input::PushTextInputEvent(const std::string& text) {
g_game->PushCall([this, text] {
g_game->thread()->PushCall([this, text] {
mark_input_active();
// Ignore if input is locked.
@ -1105,7 +1106,7 @@ void Input::PushTextInputEvent(const std::string& text) {
auto Input::PushJoystickEvent(const SDL_Event& event, InputDevice* input_device)
-> void {
g_game->PushCall([this, event, input_device] {
g_game->thread()->PushCall([this, event, input_device] {
HandleJoystickEvent(event, input_device);
});
}
@ -1137,11 +1138,11 @@ void Input::HandleJoystickEvent(const SDL_Event& event,
}
void Input::PushKeyPressEvent(const SDL_Keysym& keysym) {
g_game->PushCall([this, keysym] { HandleKeyPress(&keysym); });
g_game->thread()->PushCall([this, keysym] { HandleKeyPress(&keysym); });
}
void Input::PushKeyReleaseEvent(const SDL_Keysym& keysym) {
g_game->PushCall([this, keysym] { HandleKeyRelease(&keysym); });
g_game->thread()->PushCall([this, keysym] { HandleKeyRelease(&keysym); });
}
void Input::HandleKeyPress(const SDL_Keysym* keysym) {
@ -1392,7 +1393,7 @@ auto Input::UpdateModKeyStates(const SDL_Keysym* keysym, bool press) -> void {
}
auto Input::PushMouseScrollEvent(const Vector2f& amount) -> void {
g_game->PushCall([this, amount] { HandleMouseScroll(amount); });
g_game->thread()->PushCall([this, amount] { HandleMouseScroll(amount); });
}
auto Input::HandleMouseScroll(const Vector2f& amount) -> void {
@ -1425,7 +1426,7 @@ auto Input::HandleMouseScroll(const Vector2f& amount) -> void {
auto Input::PushSmoothMouseScrollEvent(const Vector2f& velocity, bool momentum)
-> void {
g_game->PushCall([this, velocity, momentum] {
g_game->thread()->PushCall([this, velocity, momentum] {
HandleSmoothMouseScroll(velocity, momentum);
});
}
@ -1460,7 +1461,7 @@ auto Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum)
}
auto Input::PushMouseMotionEvent(const Vector2f& position) -> void {
g_game->PushCall([this, position] { HandleMouseMotion(position); });
g_game->thread()->PushCall([this, position] { HandleMouseMotion(position); });
}
auto Input::HandleMouseMotion(const Vector2f& position) -> void {
@ -1511,7 +1512,7 @@ auto Input::HandleMouseMotion(const Vector2f& position) -> void {
}
auto Input::PushMouseDownEvent(int button, const Vector2f& position) -> void {
g_game->PushCall(
g_game->thread()->PushCall(
[this, button, position] { HandleMouseDown(button, position); });
}
@ -1588,7 +1589,7 @@ auto Input::HandleMouseDown(int button, const Vector2f& position) -> void {
}
auto Input::PushMouseUpEvent(int button, const Vector2f& position) -> void {
g_game->PushCall(
g_game->thread()->PushCall(
[this, button, position] { HandleMouseUp(button, position); });
}
@ -1637,7 +1638,7 @@ auto Input::HandleMouseUp(int button, const Vector2f& position) -> void {
}
void Input::PushTouchEvent(const TouchEvent& e) {
g_game->PushCall([e, this] { HandleTouchEvent(e); });
g_game->thread()->PushCall([e, this] { HandleTouchEvent(e); });
}
void Input::HandleTouchEvent(const TouchEvent& e) {

View File

@ -62,7 +62,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
size_t msg_len = 1 + strlen(msg + 1);
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, msg, static_cast_check_fit<socket_send_length_t>(msg_len),
0, addr, static_cast<socklen_t>(addr_len));
@ -91,7 +91,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
static_cast_check_fit<uint8_t>(RemoteError::kVersionMismatch)};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), sizeof(data), 0, addr,
static_cast<socklen_t>(addr_len));
break;
@ -135,7 +135,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
static_cast<uint8_t>(protocol_response)};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), sizeof(data), 0, addr,
static_cast<socklen_t>(addr_len));
} else {
@ -145,7 +145,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
RemoteError::kNotAcceptingConnections)};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), sizeof(data), 0, addr,
static_cast<socklen_t>(addr_len));
}
@ -179,7 +179,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
uint8_t data[1] = {BA_PACKET_REMOTE_DISCONNECT_ACK};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), 1, 0, addr,
static_cast<socklen_t>(addr_len));
}
@ -204,7 +204,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
static_cast_check_fit<uint8_t>(RemoteError::kNotConnected)};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), sizeof(data), 0, addr,
static_cast<socklen_t>(addr_len));
break;
@ -313,7 +313,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
uint8_t data[2] = {BA_PACKET_REMOTE_STATE_ACK, client->next_state_id};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), 2, 0, addr,
static_cast<socklen_t>(addr_len));
@ -331,7 +331,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
static_cast_check_fit<uint8_t>(RemoteError::kVersionMismatch)};
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
sendto(socket, reinterpret_cast<char*>(data), sizeof(data), 0, addr,
static_cast<socklen_t>(addr_len));
break;

View File

@ -7,20 +7,19 @@
#endif
#include "ballistica/app/app_globals.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/platform/platform.h"
namespace ballistica {
StdInputModule::StdInputModule(Thread* thread) : Module("stdin", thread) {
StdInputModule::StdInputModule(Thread* thread) : thread_(thread) {
assert(g_std_input_module == nullptr);
g_std_input_module = this;
}
StdInputModule::~StdInputModule() = default;
void StdInputModule::PushBeginReadCall() {
PushCall([this] {
thread()->PushCall([this] {
bool stdin_is_terminal = g_platform->is_stdin_a_terminal();
while (true) {
@ -28,7 +27,7 @@ void StdInputModule::PushBeginReadCall() {
// We send this to the game thread so it happens AFTER the
// results of the last script-command message we may have just sent.
if (stdin_is_terminal) {
g_game->PushCall([] {
g_game->thread()->PushCall([] {
if (!g_app_globals->shutting_down) {
printf(">>> ");
fflush(stdout);

View File

@ -3,17 +3,18 @@
#ifndef BALLISTICA_INPUT_STD_INPUT_MODULE_H_
#define BALLISTICA_INPUT_STD_INPUT_MODULE_H_
#include "ballistica/core/module.h"
#include "ballistica/ballistica.h"
namespace ballistica {
class StdInputModule : public Module {
class StdInputModule {
public:
explicit StdInputModule(Thread* thread);
~StdInputModule() override;
void PushBeginReadCall();
auto thread() const -> Thread* { return thread_; }
private:
Thread* thread_{};
std::string pending_input_;
};

View File

@ -10,7 +10,7 @@
namespace ballistica {
auto CreateAppInternal() -> AppInternal*;
auto GetAppInternal() -> AppInternal*;
class AppInternal {
public:

View File

@ -7,6 +7,7 @@
#endif
#include "ballistica/audio/audio_server.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/generic/timer.h"
#include "ballistica/graphics/graphics_server.h"
@ -614,7 +615,8 @@ void Media::MarkComponentForLoad(MediaComponentData* c) {
// ClearPendingLoadsDoneList)
auto media_ptr = new Object::Ref<MediaComponentData>(c);
g_media_server->PushRunnable(Object::NewDeferred<PreloadRunnable>(media_ptr));
g_media_server->thread()->PushRunnable(
Object::NewDeferred<PreloadRunnable>(media_ptr));
}
#pragma clang diagnostic push
@ -756,7 +758,7 @@ auto Media::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
std::vector<Object::Ref<T>*> l_unfinished;
std::vector<Object::Ref<T>*> l_finished;
{
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
// If we're already out of time.
if (!flush && GetRealTime() - starttime > PENDING_LOAD_PROCESS_TIME) {
@ -807,7 +809,7 @@ auto Media::RunPendingLoadList(std::vector<Object::Ref<T>*>* c_list) -> bool {
// Now add unfinished ones back onto the original list and finished ones into
// the done list.
{
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
for (auto&& i : l) {
c_list->push_back(i);
}
@ -1173,14 +1175,14 @@ void Media::AddPendingLoad(Object::Ref<MediaComponentData>* c) {
case MediaType::kTexture:
case MediaType::kModel: {
// Tell the graphics thread there's pending loads...
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
pending_loads_graphics_.push_back(c);
break;
}
case MediaType::kSound: {
// Tell the audio thread there's pending loads.
{
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
pending_loads_sounds_.push_back(c);
}
g_audio_server->PushHavePendingLoadsCall();
@ -1189,7 +1191,7 @@ void Media::AddPendingLoad(Object::Ref<MediaComponentData>* c) {
default: {
// Tell the game thread there's pending loads.
{
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
pending_loads_other_.push_back(c);
}
g_game->PushHavePendingLoadsCall();
@ -1201,7 +1203,7 @@ void Media::AddPendingLoad(Object::Ref<MediaComponentData>* c) {
void Media::ClearPendingLoadsDoneList() {
assert(InLogicThread());
std::lock_guard<std::mutex> lock(pending_load_list_mutex_);
std::scoped_lock lock(pending_load_list_mutex_);
// Our explicitly-allocated reference pointer has made it back to us here in
// the game thread.

View File

@ -3,13 +3,14 @@
#ifndef BALLISTICA_MEDIA_MEDIA_H_
#define BALLISTICA_MEDIA_MEDIA_H_
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "ballistica/core/context.h"
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
#include "ballistica/generic/runnable.h"
namespace ballistica {

View File

@ -2,6 +2,7 @@
#include "ballistica/media/media_server.h"
#include "ballistica/core/thread.h"
#include "ballistica/generic/huffman.h"
#include "ballistica/generic/timer.h"
#include "ballistica/generic/utils.h"
@ -12,7 +13,7 @@
namespace ballistica {
MediaServer::MediaServer(Thread* thread)
: Module("media", thread),
: thread_(thread),
writing_replay_(false),
replay_message_bytes_(0),
replays_broken_(false),
@ -21,14 +22,14 @@ MediaServer::MediaServer(Thread* thread)
g_media_server = this;
// get our thread to give us periodic processing time...
process_timer_ =
NewThreadTimer(1000, true, NewLambdaRunnable([this] { Process(); }));
process_timer_ = this->thread()->NewTimer(
1000, true, NewLambdaRunnable([this] { Process(); }));
}
MediaServer::~MediaServer() = default;
void MediaServer::PushBeginWriteReplayCall() {
PushCall([this] {
thread()->PushCall([this] {
if (replays_broken_) {
return;
}
@ -79,7 +80,7 @@ void MediaServer::PushBeginWriteReplayCall() {
}
void MediaServer::PushAddMessageToReplayCall(const std::vector<uint8_t>& data) {
PushCall([this, data] {
thread()->PushCall([this, data] {
if (replays_broken_) {
return;
}
@ -110,7 +111,7 @@ void MediaServer::PushAddMessageToReplayCall(const std::vector<uint8_t>& data) {
}
void MediaServer::PushEndWriteReplayCall() {
PushCall([this] {
thread()->PushCall([this] {
if (replays_broken_) {
return;
}

View File

@ -6,21 +6,23 @@
#include <list>
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/core/object.h"
namespace ballistica {
class MediaServer : public Module {
class MediaServer {
public:
explicit MediaServer(Thread* thread);
~MediaServer() override;
~MediaServer();
void PushBeginWriteReplayCall();
void PushEndWriteReplayCall();
void PushAddMessageToReplayCall(const std::vector<uint8_t>& data);
auto thread() const -> Thread* { return thread_; }
private:
void Process();
void WriteReplayMessages();
Thread* thread_{};
FILE* replay_out_file_{};
size_t replay_bytes_written_{};
bool writing_replay_{};

View File

@ -239,7 +239,7 @@ auto NetworkReader::RunThread() -> int {
"happen");
} else if (rresult == -1) {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
std::scoped_lock lock(sd_mutex_);
// If either of our sockets goes down lets close *both* of
// them.
@ -258,7 +258,7 @@ auto NetworkReader::RunThread() -> int {
// sockets (we ping ourself for this purpose).
if (paused_) {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
std::scoped_lock lock(sd_mutex_);
if (sd4_ != -1) {
g_platform->CloseSocket(sd4_);
sd4_ = -1;
@ -274,7 +274,7 @@ auto NetworkReader::RunThread() -> int {
break;
case BA_PACKET_SIMPLE_PING: {
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
std::scoped_lock lock(sd_mutex_);
char msg[1] = {BA_PACKET_SIMPLE_PONG};
sendto(sd, msg, 1, 0, reinterpret_cast<sockaddr*>(&from),
from_size);
@ -290,7 +290,7 @@ auto NetworkReader::RunThread() -> int {
std::vector<char> msg(1 + response.size());
msg[0] = BA_PACKET_JSON_PONG;
memcpy(msg.data() + 1, response.c_str(), response.size());
std::lock_guard<std::mutex> lock(sd_mutex_);
std::scoped_lock lock(sd_mutex_);
sendto(
sd, msg.data(),
static_cast_check_fit<socket_send_length_t>(msg.size()),
@ -378,7 +378,7 @@ auto NetworkReader::RunThread() -> int {
auto NetworkReader::OpenSockets() -> void {
// This needs to be locked during any socket-descriptor changes/writes.
std::lock_guard<std::mutex> lock(sd_mutex_);
std::scoped_lock lock(sd_mutex_);
int result;
int print_port_unavailable = false;

View File

@ -2,13 +2,13 @@
#include "ballistica/networking/network_write_module.h"
#include "ballistica/core/thread.h"
#include "ballistica/networking/networking.h"
#include "ballistica/networking/sockaddr.h"
namespace ballistica {
NetworkWriteModule::NetworkWriteModule(Thread* thread)
: Module("networkWrite", thread) {
NetworkWriteModule::NetworkWriteModule(Thread* thread) : thread_(thread) {
// we're a singleton
assert(g_network_write_module == nullptr);
g_network_write_module = this;
@ -18,11 +18,11 @@ void NetworkWriteModule::PushSendToCall(const std::vector<uint8_t>& msg,
const SockAddr& addr) {
// Avoid buffer-full errors if something is causing us to write too often;
// these are unreliable messages so its ok to just drop them.
if (!CheckPushSafety()) {
if (!thread()->CheckPushSafety()) {
BA_LOG_ONCE("Excessive send-to calls in net-write-module.");
return;
}
PushCall([this, msg, addr] {
thread()->PushCall([this, msg, addr] {
assert(g_network_reader);
Networking::SendTo(msg, addr);
});

View File

@ -5,15 +5,19 @@
#include <vector>
#include "ballistica/core/module.h"
#include "ballistica/ballistica.h"
namespace ballistica {
// this thread handles network output and whatnot
class NetworkWriteModule : public Module {
class NetworkWriteModule {
public:
void PushSendToCall(const std::vector<uint8_t>& msg, const SockAddr& addr);
explicit NetworkWriteModule(Thread* thread);
auto thread() const -> Thread* { return thread_; }
private:
Thread* thread_{};
};
} // namespace ballistica

View File

@ -159,7 +159,7 @@ void Networking::HostScanCycle() {
// Add or modify an entry for this.
{
std::lock_guard<std::mutex> lock(scan_results_mutex_);
std::scoped_lock lock(scan_results_mutex_);
// Ignore if it looks like its us.
if (id != GetAppInstanceUUID()) {
@ -197,7 +197,7 @@ auto Networking::GetScanResults() -> std::vector<Networking::ScanResultsEntry> {
std::vector<ScanResultsEntry> results;
results.resize(scan_results_.size());
{
std::lock_guard<std::mutex> lock(scan_results_mutex_);
std::scoped_lock lock(scan_results_mutex_);
int out_num = 0;
for (auto&& i : scan_results_) {
ScanResultsEntryPriv& in(i.second);
@ -252,7 +252,7 @@ void Networking::SendTo(const std::vector<uint8_t>& buffer,
assert(!buffer.empty());
// This needs to be locked during any sd changes/writes.
std::lock_guard<std::mutex> lock(g_network_reader->sd_mutex());
std::scoped_lock lock(g_network_reader->sd_mutex());
// Only send if the relevant socket is currently up.. silently ignore
// otherwise.

View File

@ -4,6 +4,7 @@
#include "ballistica/app/app_globals.h"
#include "ballistica/core/context.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/networking/networking.h"
#include "ballistica/networking/networking_sys.h"
@ -188,7 +189,7 @@ void TelnetServer::PushTelnetScriptCommand(const std::string& command) {
if (g_game == nullptr) {
return;
}
g_game->PushCall([this, command] {
g_game->thread()->PushCall([this, command] {
// These are always run in whichever context is 'visible'.
ScopedSetContext cp(g_game->GetForegroundContext());
if (!g_app_globals->user_ran_commands) {
@ -216,7 +217,7 @@ void TelnetServer::PushTelnetScriptCommand(const std::string& command) {
void TelnetServer::PushPrint(const std::string& s) {
assert(g_game);
g_game->PushCall([this, s] { Print(s); });
g_game->thread()->PushCall([this, s] { Print(s); });
}
void TelnetServer::Print(const std::string& s) {

View File

@ -659,20 +659,20 @@ void Platform::CreateApp() {
#endif
#if BA_HEADLESS_BUILD
g_main_thread->AddModule<HeadlessApp>();
new HeadlessApp(g_main_thread);
#elif BA_RIFT_BUILD
// Rift build can spin up in either VR or regular mode.
if (g_app_globals->vr_mode) {
g_main_thread->AddModule<VRApp>();
new VRApp(g_main_thread);
} else {
g_main_thread->AddModule<SDLApp>();
new SDLApp(g_main_thread);
}
#elif BA_CARDBOARD_BUILD
g_main_thread->AddModule<VRApp>();
new VRApp(g_main_thread);
#elif BA_SDL_BUILD
g_main_thread->AddModule<SDLApp>();
new SDLApp(g_main_thread);
#else
g_main_thread->AddModule<App>();
new App(g_main_thread);
#endif
// Let app do any init it needs to after it is fully constructed.
@ -705,7 +705,8 @@ void Platform::CreateAuxiliaryModules() {
g_app_globals->pausable_threads.push_back(bg_dynamics_thread);
#endif
#if !BA_HEADLESS_BUILD
bg_dynamics_thread->AddModule<BGDynamicsServer>();
bg_dynamics_thread->PushCallSynchronous(
[bg_dynamics_thread] { new BGDynamicsServer(bg_dynamics_thread); });
#endif
if (g_buildconfig.use_stdin_thread()) {
@ -713,7 +714,8 @@ void Platform::CreateAuxiliaryModules() {
// Note: this thread blocks indefinitely for input so we don't add it to the
// pausable list.
auto* std_input_thread = new Thread(ThreadIdentifier::kStdin);
std_input_thread->AddModule<StdInputModule>();
std_input_thread->PushCallSynchronous(
[std_input_thread] { new StdInputModule(std_input_thread); });
g_std_input_module->PushBeginReadCall();
}
}

View File

@ -4,6 +4,7 @@
#include "ballistica/platform/sdl/sdl_app.h"
#include "ballistica/app/stress_test.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/bg/bg_dynamics.h"
#include "ballistica/game/game.h"
@ -340,10 +341,10 @@ SDLApp::SDLApp(Thread* thread) : App(thread) {
// something is returned; In spirit, we're pretty much doing that same
// thing, except that we're free to handle other matters concurrently
// instead of being locked in a delay call.
NewThreadTimer(10, true, NewLambdaRunnable([this] {
assert(g_app);
g_app->RunEvents();
}));
this->thread()->NewTimer(10, true, NewLambdaRunnable([this] {
assert(g_app);
g_app->RunEvents();
}));
}
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_activity_data.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/session/host_session.h"
@ -84,7 +85,7 @@ void PythonClassActivityData::tp_dealloc(PythonClassActivityData* self) {
// it if need be; otherwise do it immediately.
if (!InLogicThread()) {
Object::WeakRef<HostActivity>* h = self->host_activity_;
g_game->PushCall([h] { delete h; });
g_game->thread()->PushCall([h] { delete h; });
} else {
delete self->host_activity_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_collide_model.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/media/component/collide_model.h"
#include "ballistica/python/python.h"
@ -104,7 +105,7 @@ void PythonClassCollideModel::tp_dealloc(PythonClassCollideModel* self) {
// be; otherwise do it immediately
if (!InLogicThread()) {
Object::Ref<CollideModel>* c = self->collide_model_;
g_game->PushCall([c] { Delete(c); });
g_game->thread()->PushCall([c] { Delete(c); });
} else {
Delete(self->collide_model_);
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_context.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/session/host_session.h"
@ -187,7 +188,7 @@ void PythonClassContext::tp_dealloc(PythonClassContext* self) {
if (!InLogicThread()) {
Context* c = self->context_;
Context* c2 = self->context_prev_;
g_game->PushCall([c, c2] {
g_game->thread()->PushCall([c, c2] {
delete c;
delete c2;
});

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_context_call.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/python/python.h"
#include "ballistica/python/python_context_call.h"
@ -118,7 +119,7 @@ void PythonClassContextCall::tp_dealloc(PythonClassContextCall* self) {
// be; otherwise do it immediately
if (!InLogicThread()) {
Object::Ref<PythonContextCall>* c = self->context_call_;
g_game->PushCall([c] { delete c; });
g_game->thread()->PushCall([c] { delete c; });
} else {
delete self->context_call_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_data.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/media/component/data.h"
#include "ballistica/python/python.h"
@ -101,7 +102,7 @@ void PythonClassData::tp_dealloc(PythonClassData* self) {
// be; otherwise do it immediately
if (!InLogicThread()) {
Object::Ref<Data>* s = self->data_;
g_game->PushCall([s] { Delete(s); });
g_game->thread()->PushCall([s] { Delete(s); });
} else {
Delete(self->data_);
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_input_device.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/player.h"
#include "ballistica/input/device/input_device.h"
#include "ballistica/python/python.h"
@ -142,7 +143,7 @@ void PythonClassInputDevice::tp_dealloc(PythonClassInputDevice* self) {
// until the delete goes through; could that ever be a problem?
if (!InLogicThread()) {
Object::WeakRef<InputDevice>* d = self->input_device_;
g_game->PushCall([d] { delete d; });
g_game->thread()->PushCall([d] { delete d; });
} else {
delete self->input_device_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_material.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/material/impact_sound_material_action.h"
#include "ballistica/dynamics/material/material.h"
#include "ballistica/dynamics/material/material_component.h"
@ -147,7 +148,7 @@ void PythonClassMaterial::tp_dealloc(PythonClassMaterial* self) {
// need be.. otherwise do it immediately.
if (!InLogicThread()) {
Object::Ref<Material>* ptr = self->material_;
g_game->PushCall([ptr] { Delete(ptr); });
g_game->thread()->PushCall([ptr] { Delete(ptr); });
} else {
Delete(self->material_);
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_model.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/media/component/model.h"
#include "ballistica/python/python.h"
@ -102,7 +103,7 @@ void PythonClassModel::tp_dealloc(PythonClassModel* self) {
// be; otherwise do it immediately
if (!InLogicThread()) {
Object::Ref<Model>* m = self->model_;
g_game->PushCall([m] { Delete(m); });
g_game->thread()->PushCall([m] { Delete(m); });
} else {
Delete(self->model_);
}

View File

@ -4,6 +4,7 @@
#include <list>
#include "ballistica/core/thread.h"
#include "ballistica/game/game_stream.h"
#include "ballistica/python/python.h"
#include "ballistica/scene/scene.h"
@ -113,7 +114,7 @@ void PythonClassNode::tp_dealloc(PythonClassNode* self) {
// be; otherwise do it immediately.
if (!InLogicThread()) {
Object::WeakRef<Node>* n = self->node_;
g_game->PushCall([n] { delete n; });
g_game->thread()->PushCall([n] { delete n; });
} else {
delete self->node_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_session_data.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/game/session/session.h"
#include "ballistica/generic/utils.h"
@ -82,7 +83,7 @@ void PythonClassSessionData::tp_dealloc(PythonClassSessionData* self) {
// until the delete goes through; could that ever be a problem?
if (!InLogicThread()) {
Object::WeakRef<Session>* s = self->session_;
g_game->PushCall([s] { delete s; });
g_game->thread()->PushCall([s] { delete s; });
} else {
delete self->session_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_session_player.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/host_activity.h"
#include "ballistica/game/player.h"
#include "ballistica/game/session/host_session.h"
@ -187,7 +188,7 @@ void PythonClassSessionPlayer::tp_dealloc(PythonClassSessionPlayer* self) {
// be; otherwise do it immediately.
if (!InLogicThread()) {
Object::WeakRef<Player>* p = self->player_;
g_game->PushCall([p] { delete p; });
g_game->thread()->PushCall([p] { delete p; });
} else {
delete self->player_;
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_sound.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/media/component/sound.h"
#include "ballistica/python/python.h"
@ -101,7 +102,7 @@ void PythonClassSound::tp_dealloc(PythonClassSound* self) {
// be; otherwise do it immediately
if (!InLogicThread()) {
Object::Ref<Sound>* s = self->sound_;
g_game->PushCall([s] { Delete(s); });
g_game->thread()->PushCall([s] { Delete(s); });
} else {
Delete(self->sound_);
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_texture.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/media/component/texture.h"
#include "ballistica/python/python.h"
@ -94,7 +95,7 @@ void PythonClassTexture::tp_dealloc(PythonClassTexture* self) {
// be; otherwise do it immediately.
if (!InLogicThread()) {
Object::Ref<Texture>* t = self->texture_;
g_game->PushCall([t] { Delete(t); });
g_game->thread()->PushCall([t] { Delete(t); });
} else {
Delete(self->texture_);
}

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_timer.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/python/python_context_call_runnable.h"
@ -166,7 +167,7 @@ void PythonClassTimer::tp_dealloc(PythonClassTimer* self) {
auto a1 = self->time_type_;
auto a2 = self->timer_id_;
auto a3 = self->context_;
g_game->PushCall(
g_game->thread()->PushCall(
[a0, a1, a2, a3] { PythonClassTimer::DoDelete(a0, a1, a2, a3); });
} else {
DoDelete(self->have_timer_, self->time_type_, self->timer_id_,

View File

@ -2,6 +2,7 @@
#include "ballistica/python/class/python_class_widget.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/generic/utils.h"
#include "ballistica/graphics/graphics.h"
@ -92,7 +93,7 @@ void PythonClassWidget::tp_dealloc(PythonClassWidget* self) {
// need be
if (!InLogicThread()) {
Object::WeakRef<Widget>* w = self->widget_;
g_game->PushCall([w] { delete w; });
g_game->thread()->PushCall([w] { delete w; });
} else {
delete self->widget_;
}

View File

@ -535,7 +535,7 @@ auto PyGetLog(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
std::string log_fin;
{
std::lock_guard<std::mutex> lock(g_app_globals->log_mutex);
std::scoped_lock lock(g_app_globals->log_mutex);
log_fin = g_app_globals->log;
}
// we want to use something with error handling here since the last

View File

@ -4,6 +4,7 @@
#include "ballistica/app/app_globals.h"
#include "ballistica/audio/audio.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/material/material.h"
#include "ballistica/game/account.h"
#include "ballistica/game/friend_score_set.h"
@ -853,6 +854,38 @@ auto Python::GetPyVector3f(PyObject* o) -> Vector3f {
Python::Python() = default;
static struct PyModuleDef ba_module_def = {PyModuleDef_HEAD_INIT};
static auto ba_exec(PyObject* module) -> int {
Python::InitModuleClasses(module);
return 0;
}
static PyModuleDef_Slot ba_slots[] = {
{Py_mod_exec, reinterpret_cast<void*>(ba_exec)}, {0, NULL}};
// Called when our _ba module is getting spun up.
static auto PyInit__ba() -> PyObject* {
assert(Python::HaveGIL());
// We should be able to assign these in the initializer above,
// but older g++ chokes on it at the moment...
// (and this is still more readable than setting ALL values positionally)
assert(ba_module_def.m_size == 0); // should all be zeroed though...
// Gather our methods into a static null-terminated list.
auto* all_methods = new std::vector<PyMethodDef>{Python::GetModuleMethods()};
all_methods->push_back(PyMethodDef{nullptr, nullptr, 0, nullptr});
ba_module_def.m_methods = all_methods->data();
ba_module_def.m_slots = ba_slots;
PyObject* module = PyModuleDef_Init(&ba_module_def);
BA_PRECONDITION(module);
return module;
}
void Python::Reset(bool do_init) {
assert(InLogicThread());
assert(g_python);
@ -939,6 +972,9 @@ void Python::Reset(bool do_init) {
config.module_search_paths_set = 1;
}
// Let Python know how to spin up our _ba module.
PyImport_AppendInittab("_ba", &PyInit__ba);
// Inits our _ba module and runs Py_Initialize().
g_app_internal->PyInitialize(&config);
@ -1063,14 +1099,14 @@ auto Python::InitModuleClasses(PyObject* module) -> void {
}
void Python::PushObjCall(ObjID obj_id) {
g_game->PushCall([obj_id] {
g_game->thread()->PushCall([obj_id] {
ScopedSetContext cp(g_game->GetUIContext());
g_python->obj(obj_id).Call();
});
}
void Python::PushObjCall(ObjID obj_id, const std::string& arg) {
g_game->PushCall([this, obj_id, arg] {
g_game->thread()->PushCall([this, obj_id, arg] {
ScopedSetContext cp(g_game->GetUIContext());
PythonRef args(Py_BuildValue("(s)", arg.c_str()),
ballistica::PythonRef::kSteal);

View File

@ -4,6 +4,7 @@
#include "ballistica/app/app_globals.h"
#include "ballistica/audio/audio.h"
#include "ballistica/generic/lambda_runnable.h"
#include "ballistica/graphics/component/empty_component.h"
#include "ballistica/input/device/input_device.h"
#include "ballistica/input/input.h"

View File

@ -3,6 +3,7 @@
#include "ballistica/ui/widget/container_widget.h"
#include "ballistica/audio/audio.h"
#include "ballistica/core/thread.h"
#include "ballistica/game/game.h"
#include "ballistica/graphics/component/empty_component.h"
#include "ballistica/graphics/component/simple_component.h"
@ -808,7 +809,7 @@ void ContainerWidget::Draw(RenderPass* pass, bool draw_transparent) {
// Probably not safe to delete ourself here since we're in
// the draw loop, but we can push a call to do it.
Object::WeakRef<Widget> weakref(this);
g_game->PushCall([weakref] {
g_game->thread()->PushCall([weakref] {
Widget* w = weakref.get();
if (w) g_ui->DeleteWidget(w);
});
@ -863,7 +864,7 @@ void ContainerWidget::Draw(RenderPass* pass, bool draw_transparent) {
// Probably not safe to delete ourself here since we're in the
// draw loop, but we can set up an event to do it.
Object::WeakRef<Widget> weakref(this);
g_game->PushCall([weakref] {
g_game->thread()->PushCall([weakref] {
Widget* w = weakref.get();
if (w) g_ui->DeleteWidget(w);
});