From cfe42b39e6f06c0674f84aedb58ae0c481d04291 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 14 Sep 2022 19:11:34 -0700 Subject: [PATCH] major logging system revamp --- .efrocachemap | 90 ++--- .idea/dictionaries/ericf.xml | 4 + CHANGELOG.md | 11 +- assets/src/ba_data/python/._ba_sources_hash | 2 +- .../ba_data/python/._bainternal_sources_hash | 2 +- assets/src/ba_data/python/_ba.py | 269 ++------------- assets/src/ba_data/python/ba/__init__.py | 13 +- assets/src/ba_data/python/ba/_app.py | 4 +- assets/src/ba_data/python/ba/_appconfig.py | 6 - assets/src/ba_data/python/ba/_apputils.py | 19 +- assets/src/ba_data/python/ba/_asyncio.py | 2 +- assets/src/ba_data/python/ba/_bootstrap.py | 120 ++----- assets/src/ba_data/python/ba/_error.py | 5 + assets/src/ba_data/python/ba/_hooks.py | 2 +- assets/src/ba_data/python/ba/_meta.py | 12 +- assets/src/ba_data/python/ba/_playlist.py | 6 +- assets/src/ba_data/python/ba/_plugin.py | 10 +- assets/src/ba_data/python/ba/_servermode.py | 7 +- .../.idea/dictionaries/ericf.xml | 4 + src/ballistica/app/app.h | 8 +- src/ballistica/app/app_config.h | 2 +- src/ballistica/assets/assets.cc | 32 +- src/ballistica/assets/assets.h | 2 +- src/ballistica/assets/assets_server.cc | 30 +- src/ballistica/assets/data/data_data.cc | 4 +- src/ballistica/assets/data/sound_data.cc | 12 +- src/ballistica/audio/al_sys.cc | 9 +- src/ballistica/audio/audio_server.cc | 35 +- src/ballistica/audio/audio_server.h | 2 +- src/ballistica/audio/audio_source.cc | 2 +- src/ballistica/audio/audio_streamer.cc | 5 +- src/ballistica/audio/ogg_stream.cc | 3 +- src/ballistica/ballistica.cc | 34 +- src/ballistica/ballistica.h | 5 +- src/ballistica/core/context.cc | 2 +- src/ballistica/core/context.h | 4 +- src/ballistica/core/fatal_error.cc | 18 +- src/ballistica/core/logging.cc | 137 ++++---- src/ballistica/core/logging.h | 32 +- src/ballistica/core/macros.cc | 34 +- src/ballistica/core/macros.h | 16 +- src/ballistica/core/object.cc | 4 +- src/ballistica/core/thread.cc | 72 +++- src/ballistica/core/thread.h | 8 +- src/ballistica/core/types.h | 2 + src/ballistica/dynamics/bg/bg_dynamics.h | 2 +- .../dynamics/bg/bg_dynamics_draw_snapshot.h | 2 +- .../dynamics/bg/bg_dynamics_server.cc | 23 +- src/ballistica/dynamics/dynamics.cc | 3 +- .../dynamics/material/material_component.cc | 5 +- src/ballistica/dynamics/part.cc | 6 +- src/ballistica/dynamics/rigid_body.cc | 2 +- src/ballistica/generic/timer_list.cc | 6 +- src/ballistica/generic/utils.cc | 12 +- .../graphics/component/render_component.h | 3 +- src/ballistica/graphics/frame_def.h | 4 +- src/ballistica/graphics/gl/gl_sys.cc | 2 +- src/ballistica/graphics/gl/renderer_gl.cc | 120 ++++--- src/ballistica/graphics/graphics.cc | 7 +- src/ballistica/graphics/graphics.h | 2 +- src/ballistica/graphics/graphics_server.cc | 35 +- src/ballistica/graphics/graphics_server.h | 2 +- .../graphics/mesh/mesh_buffer_base.h | 4 +- src/ballistica/graphics/mesh/mesh_data.h | 2 +- .../graphics/mesh/mesh_indexed_base.h | 7 +- .../mesh/mesh_indexed_static_dynamic.h | 3 +- src/ballistica/graphics/text/text_graphics.cc | 3 +- src/ballistica/graphics/text/text_graphics.h | 2 +- .../input/device/client_input_device.cc | 3 +- src/ballistica/input/device/input_device.cc | 21 +- src/ballistica/input/device/input_device.h | 4 +- src/ballistica/input/device/joystick.cc | 12 +- src/ballistica/input/device/touch_input.cc | 5 +- src/ballistica/input/input.cc | 32 +- src/ballistica/input/input.h | 6 +- src/ballistica/input/remote_app.cc | 8 +- src/ballistica/internal/app_internal.h | 6 +- src/ballistica/logic/connection/connection.cc | 30 +- .../logic/connection/connection_set.cc | 29 +- .../logic/connection/connection_to_client.cc | 45 +-- .../connection/connection_to_client_udp.cc | 2 +- .../logic/connection/connection_to_host.cc | 26 +- .../connection/connection_to_host_udp.cc | 5 +- src/ballistica/logic/host_activity.cc | 21 +- src/ballistica/logic/logic.cc | 74 +++-- src/ballistica/logic/logic.h | 7 +- src/ballistica/logic/player_spec.cc | 7 +- .../logic/session/client_session.cc | 12 +- src/ballistica/logic/session/host_session.cc | 24 +- .../logic/session/net_client_session.cc | 6 +- .../logic/session/replay_client_session.cc | 9 +- src/ballistica/logic/session/session.cc | 3 +- src/ballistica/logic/v1_account.cc | 2 +- src/ballistica/networking/network_reader.cc | 54 +-- src/ballistica/networking/network_writer.cc | 3 +- src/ballistica/networking/networking.cc | 32 +- src/ballistica/networking/networking.h | 2 +- src/ballistica/networking/telnet_server.cc | 7 +- .../platform/apple/platform_apple.h | 3 +- .../platform/linux/platform_linux.cc | 8 +- src/ballistica/platform/platform.cc | 106 +++--- src/ballistica/platform/platform.h | 17 +- src/ballistica/platform/sdl/sdl_app.cc | 23 +- src/ballistica/platform/stdio_console.cc | 4 +- .../platform/windows/platform_windows.cc | 30 +- .../platform/windows/platform_windows.h | 3 +- .../class/python_class_activity_data.cc | 4 +- .../class/python_class_collide_model.cc | 4 +- .../python/class/python_class_context.cc | 7 +- .../python/class/python_class_context_call.cc | 4 +- .../python/class/python_class_data.cc | 4 +- .../python/class/python_class_input_device.cc | 7 +- .../python/class/python_class_material.cc | 4 +- .../python/class/python_class_model.cc | 4 +- .../python/class/python_class_node.cc | 4 +- .../python/class/python_class_session_data.cc | 4 +- .../class/python_class_session_player.cc | 24 +- .../python/class/python_class_sound.cc | 4 +- .../python/class/python_class_texture.cc | 4 +- .../python/class/python_class_timer.cc | 4 +- .../python/class/python_class_widget.cc | 6 +- .../python/methods/python_methods_app.cc | 126 ++++--- .../python/methods/python_methods_gameplay.cc | 4 +- .../methods/python_methods_networking.cc | 4 +- .../python/methods/python_methods_system.cc | 28 +- src/ballistica/python/python.cc | 151 ++++++--- src/ballistica/python/python.h | 31 +- src/ballistica/python/python_command.cc | 20 +- src/ballistica/python/python_command.h | 2 +- src/ballistica/python/python_context_call.cc | 13 +- src/ballistica/python/python_context_call.h | 2 +- src/ballistica/python/python_ref.cc | 4 +- src/ballistica/python/python_sys.h | 12 +- src/ballistica/scene/node/combine_node.cc | 2 +- src/ballistica/scene/node/globals_node.cc | 3 +- src/ballistica/scene/node/node.cc | 23 +- src/ballistica/scene/node/node_attribute.cc | 6 +- .../scene/node/node_attribute_connection.cc | 13 +- src/ballistica/scene/node/prop_node.cc | 7 +- src/ballistica/scene/node/region_node.cc | 3 +- src/ballistica/scene/node/sound_node.cc | 6 +- src/ballistica/scene/node/spaz_node.cc | 36 +- src/ballistica/scene/node/text_node.cc | 7 +- src/ballistica/scene/scene.cc | 9 +- src/ballistica/scene/scene_stream.cc | 58 ++-- src/ballistica/ui/ui.cc | 8 +- src/ballistica/ui/widget/button_widget.cc | 3 +- src/ballistica/ui/widget/container_widget.cc | 26 +- src/ballistica/ui/widget/text_widget.cc | 6 +- src/ballistica/ui/widget/widget.cc | 10 +- src/meta/bameta/python_embedded/binding.py | 8 +- tools/batools/dummymodule.py | 2 +- tools/efro/log.py | 311 +++++++++++------- 153 files changed, 1622 insertions(+), 1460 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 81c70975..3817aff0 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3995,51 +3995,51 @@ "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/84/d0/74f2b20adc517beeb0efafb4b4de", - "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/85/8e8cbeee10bf0c4f5b38792cd972", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/84/9e/70d571aa9d9c869ae544e81ceb9c", - "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/61/4b/9cb4a70e2c5fd5046a2a0aa1d011", - "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c4/9b/24e583b9bf7bcb406a544f5d9d90", - "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d8/4b/b73ae2c2e711699daca03bb46fbc", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/41/d1/b665246e68c64f90819807d6d528", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a4/81/daa5716db4fe65ae8ef4700af5a1", - "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/47/e5/eabd47ace227af9fb05bdaeea7aa", - "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/69/8b/c91d3562ac890e40aafb9f35cf5e", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/16/b1/45dcdcd101c5b293f78913f62a0e", - "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/81/bb/3fba8cad9c1ea3d01199b32385e7", - "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f3/0f/7c6074e1c2165b976e4a030d3876", - "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/02/9a/c31ef4286904f9970519f42f0644", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/84/62/f0fba929db90c96deac3d73a135b", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/95/72/64f86aba6b2219f49bc5f89f2189", - "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a8/a4/237ffde4b291ffc0cfb795863c1b", - "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/3f/58/877ef1ec2c1e7a4c405574ebc544", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/37/7e/bdc6a0ba2c95bfecc16911a80983", - "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/f5/5c/e70d827e538c18b914f9d1cc52af", - "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/02/e1d6afd343ce32d53c134c5fc4e7", - "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/18/45e61446f5f7e02624fb87fb8c97", - "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cf/46/393131611fa1395fd9c173d8426f", - "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/2e/d4a8ba6f7c46da387394f4542d02", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/df/6c/cafe5794323f445392e63114f14b", - "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/00/ba/5c85c98957ac9f0832a01ea83168", - "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/0c/c85a8d0a8b43dac24a94417297fb", - "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f1/ca/c78a414c9d6ec0ffb647585c3e33", - "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/6a/83518a680d4917e3356d21d3650d", - "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/39/2e1794bf48cbc4c67c63b9527a68", - "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3b/64/48be465941f4f1aa3332a1d32f6a", - "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/03/d9a332d36a337086b639c4240674", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/ca/533d54af50f2b5750f7c407dcbd5", - "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/d6/e9af602218f3dd14a5bedb6c7eb2", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/cc/2ee87bb2ead38a3bf7d8b2588f39", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e5/ef/91bb1dfff04d62899cbe363b0d23", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/a2/7a/faf4c2744e2762e58d0119dafa72", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/7d/66/d3aafe52f5d44a23aa426bc8e602", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/63/5b/a3d3b19e7583a72cbdeefbd00f7e", - "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/60/14/3a28abc1b1b87b14f05d86a2e900", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/6c/a3/ded8df37d31c54c4ab66e224015a", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/2b/c6/a8701e03471178983957c33026ff", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7e/16/307bb68c0561181ba701d0bcd350", - "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/09/b3/2fd1934cb6f0d2454e222c60ef3b", - "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7", + "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9a/b0/687077bb5518ba6297514a21cf79", + "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9a/07/a28a396bd3aff9c7d8ea4425c972", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/98/86/2b057bf3acb6707ee2345ae48726", + "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/13/96/49d0b9a8a5a808a619318e4ed3bd", + "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ed/87/b71085642185d3150a3dd59ed593", + "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/62/9b/2b1d5dbfa88a3b71c1476940ded0", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b7/12/f1f6253a38af6278dbcc4ee13345", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c7/79/dc992d142c303287a321d22a51c9", + "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/8a/90/9e476b436c7f9211f2e49308a063", + "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a8/9c/090dbf15aeb803532b96093ffb2e", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e3/2c/0eb229b654494a9bb99a663bee34", + "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ff/94/996c258446bcd1587ce55814c10f", + "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ac/d9/8baaae8352bcd21034d5daea284c", + "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7f/f3/2d78f72daebb218658f4dc010e8e", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/75/5e/a887e7a232caed9a4a0a9a0909ae", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/30/0d/f0de21829b62bf1256781df266ed", + "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/8d/10/a54cfe97f26717ab57477860cb43", + "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/14/30/350f4c8f8b17326c161a05353afa", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/ab/43/79fd942d5e2f99f49726d15fbebd", + "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/50/fc/8ac0aa34cc5e792dd126a96deb7f", + "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d5/f6/d62e6e6d5e7fe1945f08ccbb9a8f", + "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/02/c5/44d0082442b06153a7d7dce4c8ce", + "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/00/61378fb26ddcbf023fd3c40e4ffb", + "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0a/e2/a01a2de0835fee06fd7b357c700f", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/dd/8a8f84d8ed28fec12137f632e9f7", + "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6d/b1/34d06bce1b93bcefc4896bd2700a", + "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/61/f48cc804658071c759398f9c00dd", + "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a2/b7/a28b8ed7c1f13b53dea1c878c5fc", + "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a5/b2/f29c2fe15f33441de94331743353", + "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c2/63/bf93d39adb82748decfdb980b562", + "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/00/6d/ac55bffd774cf75c5944b679194a", + "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7b/dd/af154cabc6779257a38ea6c9e833", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/54/88/2de2dfa2c0c0a1766fa0d5f26ed6", + "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ff/26/da4a58abecf5d9275477eaec0c17", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/62/5d/8658a206b8c9b741be2422162784", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/57/d59920e098d23a2d150c899cde29", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/85/19/3c25e72ea3976b7d854c21696ec4", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/50/21/6c275dad22cc2cc1f95e21652c29", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/a0/1a/f894ee82b89dcb4a43c36ab882ee", + "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/c3/6a/0e89ae233f6ae76351d33ed2b0e6", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/ce/ba/c5a0c9b8224dc8df02c5189e22ac", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/1d/3a/560a77a6c97f6f39b765211b1ed8", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/5b/fb/b8c70b2a72452da1af92844335b8", + "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/a9/6c/12e1188dc02708b0b0fee30835b9", + "src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523", "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02", "src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd" } \ No newline at end of file diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 6ae45394..8c4ff566 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -64,9 +64,11 @@ aiomain alarmsound alibaba + allerrors allpaths allsettings allteams + allwarnings aman amazonaws aname @@ -2384,6 +2386,7 @@ startscan startsplits starttime + startupmsg statestr statictest statictestfiles @@ -2412,6 +2415,7 @@ stot strftime stringified + stringifying stringprep stringptr strippable diff --git a/CHANGELOG.md b/CHANGELOG.md index 07af68eb..d719a855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.7 (build 20842, api 7, 2022-09-13) +### 1.7.7 (build 20849, api 7, 2022-09-14) - 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. @@ -26,7 +26,16 @@ - Simplified C++ bootstrapping to allocate all globals in one place. - Renamed C++ Game classes to Logic. - The app now bootstraps Python in the main thread instead of the logic thread. This will keep things more consistent later when we are able to run under an already-existing Python interpreter. +- As a side-effect of initing Python in the main thread, it seems that Python now catches segfaults in our debug builds and prints Python stack traces. (see https://docs.python.org/3/library/faulthandler.html). We'll have to experiment and see if this is a net positive or something we want to disable or make optional. - Python and _ba are now completely initialized in public source code. Now we just need to enable the app to survive without _bainternal and it'll be possible to build a 100% open source app. +- `Logging::Log()` in the C++ layer now takes a LogLevel arg (kDebug, kWarning, kError, etc.) and simply calls the equivalent Python logging.XXX call. This unifies our C++ and Python logging to go through the same place. +- `ba.log()` is no more. Instead just use standard Python logging functions (logging.info(), logging.error(), etc.). +- `_ba.getlog()` is now `_ba.get_v1_cloud_log()`. Note that this functionality will go away eventually so you should use ba.app.log_handler and/or standard Python logging functions to get at app logs. +- Along the same lines, `_ba.get_log_file_path()` is now `_ba.get_v1_cloud_log_file_path()`. +- Added `_ba.display_log()` function which ships a log message to the in-game terminal and platform-specific places like the Android log. The engine wires up standard Python logging output to go through this. +- Added `_ba.v1_cloud_log()` which ships a message to the old v1-cloud-log (the log which is gathered and sent to the v1 master server to help me identify problems people are seeing). This is presently wired up to a subset of Python logging output to approximate how it used to work. +- Note: Previously in the C++ layer some code would mix Python print calls (such as PyErr_PrintEx()) with ballistica::Log() calls. Previously these all wound up going to the same place (Python's sys.stderr) so it worked, but now they no longer do and so this sort of mixing should be avoided. So if you see a weird combination of colored log output lines with non-colored lines that seem to go together, please holler as it means something needs to be fixed. + ### 1.7.6 (build 20687, api 7, 2022-08-11) - Cleaned up da MetaSubsystem code. diff --git a/assets/src/ba_data/python/._ba_sources_hash b/assets/src/ba_data/python/._ba_sources_hash index b56206f3..d78d9181 100644 --- a/assets/src/ba_data/python/._ba_sources_hash +++ b/assets/src/ba_data/python/._ba_sources_hash @@ -1 +1 @@ -126683827977798484003262787310231621875 \ No newline at end of file +76251027805752156826413428926087661089 \ No newline at end of file diff --git a/assets/src/ba_data/python/._bainternal_sources_hash b/assets/src/ba_data/python/._bainternal_sources_hash index 6e9f924c..3307093f 100644 --- a/assets/src/ba_data/python/._bainternal_sources_hash +++ b/assets/src/ba_data/python/._bainternal_sources_hash @@ -1 +1 @@ -222094078620857897443553282652634355523 \ No newline at end of file +139020022013133168311319486434408589898 \ No newline at end of file diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 0705868d..34e91a0a 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -1108,12 +1108,6 @@ def add_clean_frame_callback(call: Callable) -> None: return None -def add_transaction(transaction: dict, - callback: Callable | None = None) -> None: - """(internal)""" - return None - - def android_get_external_files_dir() -> str: """(internal) @@ -1472,6 +1466,16 @@ def disconnect_from_host() -> None: return None +def display_log(name: str, level: str, message: str) -> None: + """(internal) + + Sends a log message to the in-game console and any per-platform + log destinations (Android log, etc.). This generally is not called + directly and should instead be fed Python logging output. + """ + return None + + def do_once() -> bool: """Return whether this is the first time running a line of code. @@ -1561,15 +1565,6 @@ def focus_window() -> None: return None -def game_service_has_leaderboard(game: str, config: str) -> bool: - """(internal) - - Given a game and config string, returns whether there is a leaderboard - for it on the game service. - """ - return bool() - - def get_appconfig_builtin_keys() -> list[str]: """(internal)""" return ['blah', 'blah2'] @@ -1700,29 +1695,11 @@ def get_local_active_input_devices_count() -> int: return int() -def get_log_file_path() -> str: - """(internal) - - Return the path to the app log file. - """ - return str() - - def get_low_level_config_value(key: str, default_value: int) -> int: """(internal)""" return int() -def get_master_server_address(source: int = -1, - version: int = 1, - internal: bool = False) -> str: - """(internal) - - Return the address of the master server. - """ - return str() - - def get_max_graphics_quality() -> str: """(internal) @@ -1731,11 +1708,6 @@ def get_max_graphics_quality() -> str: return str() -def get_news_show() -> str: - """(internal)""" - return str() - - def get_package_collide_model(package: ba.AssetPackage, name: str) -> ba.CollideModel: """(internal)""" @@ -1767,16 +1739,6 @@ def get_package_texture(package: ba.AssetPackage, name: str) -> ba.Texture: return ba.Texture() -def get_price(item: str) -> str | None: - """(internal)""" - return '' - - -def get_public_login_id() -> str | None: - """(internal)""" - return '' - - def get_public_party_enabled() -> bool: """(internal)""" return bool() @@ -1787,16 +1749,6 @@ def get_public_party_max_size() -> int: return int() -def get_purchased(item: str) -> bool: - """(internal)""" - return bool() - - -def get_purchases_state() -> int: - """(internal)""" - return int() - - def get_qrcode_texture(url: str) -> ba.Texture: """(internal)""" import ba # pylint: disable=cyclic-import @@ -1824,11 +1776,6 @@ def get_replays_dir() -> str: return str() -def get_scores_to_beat(level: str, config: str, callback: Callable) -> None: - """(internal)""" - return None - - def get_special_widget(name: str) -> Widget: """(internal)""" return Widget() @@ -1872,56 +1819,16 @@ def get_ui_input_device() -> ba.InputDevice: return ba.InputDevice() -def get_v1_account_display_string(full: bool = True) -> str: +def get_v1_cloud_log() -> str: """(internal)""" return str() -def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any: - """(internal)""" - return _uninferrable() - - -def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any: - """(internal)""" - return _uninferrable() - - -def get_v1_account_misc_val(name: str, default_value: Any) -> Any: - """(internal)""" - return _uninferrable() - - -def get_v1_account_name() -> str: - """(internal)""" - return str() - - -def get_v1_account_state() -> str: - """(internal)""" - return str() - - -def get_v1_account_state_num() -> int: - """(internal)""" - return int() - - -def get_v1_account_ticket_count() -> int: +def get_v1_cloud_log_file_path() -> str: """(internal) - Returns the number of tickets for the current account. + Return the path to the app log file. """ - return int() - - -def get_v1_account_type() -> str: - """(internal)""" - return str() - - -def get_v2_fleet() -> str: - """(internal)""" return str() @@ -2015,11 +1922,6 @@ def getinputdevice(name: str, unique_id: str, doraise: bool = True) -> Any: return None -def getlog() -> str: - """(internal)""" - return str() - - def getmodel(name: str) -> ba.Model: """Return a model, loading it if necessary. @@ -2129,11 +2031,6 @@ def have_incentivized_ad() -> bool: return bool() -def have_outstanding_transactions() -> bool: - """(internal)""" - return bool() - - def have_permission(permission: ba.Permission) -> bool: """(internal)""" return bool() @@ -2210,15 +2107,10 @@ def imagewidget(edit: ba.Widget | None = None, return ba.Widget() -def in_game_purchase(item: str, price: int) -> None: - """(internal)""" - return None - - def in_logic_thread() -> bool: """(internal) - Returns whether or not the current thread is the game thread. + Returns whether or not the current thread is the logic thread. """ return bool() @@ -2240,11 +2132,6 @@ def increment_analytics_counts_raw(name: str, increment: int = 1) -> None: return None -def is_blessed() -> bool: - """(internal)""" - return bool() - - def is_in_replay() -> bool: """(internal)""" return bool() @@ -2301,22 +2188,6 @@ def lock_all_input() -> None: return None -def log(message: str, to_stdout: bool = True, to_server: bool = True) -> None: - """Category: **General Utility Functions** - - Log a message. This goes to the default logging mechanism depending - on the platform (stdout on mac, android log on android, etc). - - Log messages also go to the in-game console unless 'to_console' - is False. They are also sent to the master-server for use in analyzing - issues unless to_server is False. - - Python's standard print() is wired to call this (with default values) - so in most cases you can just use that. - """ - return None - - def mac_music_app_get_library_source() -> None: """(internal)""" return None @@ -2352,14 +2223,6 @@ def mac_music_app_stop() -> None: return None -def mark_config_dirty() -> None: - """(internal) - - Category: General Utility Functions - """ - return None - - def mark_log_sent() -> None: """(internal)""" return None @@ -2488,11 +2351,6 @@ def playsound(sound: Sound, return None -def power_ranking_query(callback: Callable, season: Any = None) -> None: - """(internal)""" - return None - - def print_context() -> None: """(internal) @@ -2509,23 +2367,6 @@ def print_load_info() -> None: return None -def print_stderr(message: str) -> None: - """(internal) - - Print to system stderr. - Also forwards to the internal console, etc. - """ - return None - - -def print_stdout(message: str) -> None: - """(internal) - Print to system stdout. - Also forwards to the internal console, etc. - """ - return None - - def printnodes() -> None: """Print various info about existing nodes; useful for debugging. @@ -2545,11 +2386,6 @@ def printobjects() -> None: return None -def purchase(item: str) -> None: - """(internal)""" - return None - - def pushcall(call: Callable, from_other_thread: bool = False, suppress_other_thread_warning: bool = False) -> None: @@ -2560,12 +2396,12 @@ def pushcall(call: Callable, This can be handy for calls that are disallowed from within other callbacks, etc. - This call expects to be used in the game thread, and will automatically + This call expects to be used in the logic thread, and will automatically save and restore the ba.Context to behave seamlessly. - If you want to push a call from outside of the game thread, + If you want to push a call from outside of the logic thread, however, you can pass 'from_other_thread' as True. In this case - the call will always run in the UI context on the game thread. + the call will always run in the UI context on the logic thread. """ return None @@ -2616,21 +2452,11 @@ def reload_media() -> None: return None -def report_achievement(achievement: str, pass_to_account: bool = True) -> None: - """(internal)""" - return None - - def request_permission(permission: ba.Permission) -> None: """(internal)""" return None -def reset_achievements() -> None: - """(internal)""" - return None - - def reset_game_activity_tracking() -> None: """(internal)""" return None @@ -2646,11 +2472,6 @@ def resolve_appconfig_value(key: str) -> Any: return _uninferrable() -def restore_purchases() -> None: - """(internal)""" - return None - - def rowwidget(edit: ba.Widget | None = None, parent: ba.Widget | None = None, size: Sequence[float] | None = None, @@ -2673,11 +2494,6 @@ def rowwidget(edit: ba.Widget | None = None, return ba.Widget() -def run_transactions() -> None: - """(internal)""" - return None - - def safecolor(color: Sequence[float], target_intensity: float = 0.6) -> tuple[float, ...]: """Given a color tuple, return a color safe to display as text. @@ -2953,48 +2769,11 @@ def show_progress_bar() -> None: return None -def sign_in_v1(account_type: str) -> None: - """(internal) - - Category: General Utility Functions - """ - return None - - -def sign_out_v1(v2_embedded: bool = False) -> None: - """(internal) - - Category: General Utility Functions - """ - return None - - def submit_analytics_counts() -> None: """(internal)""" return None -def submit_score(game: str, - config: str, - name: Any, - score: int | None, - callback: Callable, - friend_callback: Callable | None, - order: str = 'increasing', - tournament_id: str | None = None, - score_type: str = 'points', - campaign: str | None = None, - level: str | None = None) -> None: - """(internal) - - Submit a score to the server; callback will be called with the results. - As a courtesy, please don't send fake scores to the server. I'd prefer - to devote my time to improving the game instead of trying to make the - score server more mischief-proof. - """ - return None - - def textwidget(edit: ba.Widget | None = None, parent: ba.Widget | None = None, size: Sequence[float] | None = None, @@ -3173,12 +2952,6 @@ def timer(time: float, return None -def tournament_query(callback: Callable[[dict | None], None], - args: dict) -> None: - """(internal)""" - return None - - def uibounds() -> tuple[float, float, float, float]: """(internal) @@ -3198,6 +2971,14 @@ def unlock_all_input() -> None: return None +def v1_cloud_log(message: str) -> None: + """(internal) + + Push messages to the old v1 cloud log. + """ + return None + + def value_test(arg: str, change: float | None = None, absolute: float | None = None) -> float: diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 4b06c0ed..43d98979 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -13,12 +13,11 @@ from _ba import ( Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget, camerashake, checkboxwidget, columnwidget, containerwidget, do_once, emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession, - getsound, gettexture, hscrollwidget, imagewidget, log, newactivity, - newnode, playsound, printnodes, printobjects, pushcall, quit, rowwidget, - safecolor, screenmessage, scrollwidget, set_analytics_screen, charstr, - textwidget, time, timer, open_url, widget, clipboard_is_supported, - clipboard_has_text, clipboard_get_text, clipboard_set_text, getdata, - in_logic_thread) + getsound, gettexture, hscrollwidget, imagewidget, newactivity, newnode, + playsound, printnodes, printobjects, pushcall, quit, rowwidget, safecolor, + screenmessage, scrollwidget, set_analytics_screen, charstr, textwidget, + time, timer, open_url, widget, clipboard_is_supported, clipboard_has_text, + clipboard_get_text, clipboard_set_text, getdata, in_logic_thread) from ba._activity import Activity from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem from ba._actor import Actor @@ -103,7 +102,7 @@ __all__ = [ 'ImpactDamageMessage', 'in_logic_thread', 'InputDevice', 'InputDeviceNotFoundError', 'InputType', 'IntChoiceSetting', 'IntSetting', 'is_browser_likely_available', 'is_point_in_box', 'Keyboard', - 'LanguageSubsystem', 'Level', 'Lobby', 'log', 'Lstr', 'Map', 'Material', + 'LanguageSubsystem', 'Level', 'Lobby', 'Lstr', 'Map', 'Material', 'MetadataSubsystem', 'Model', 'MultiTeamSession', 'MusicPlayer', 'MusicPlayMode', 'MusicSubsystem', 'MusicType', 'newactivity', 'newnode', 'Node', 'NodeActor', 'NodeNotFoundError', 'normalized_color', diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py index b1c012c9..b976517b 100644 --- a/assets/src/ba_data/python/ba/_app.py +++ b/assets/src/ba_data/python/ba/_app.py @@ -26,6 +26,7 @@ if TYPE_CHECKING: import asyncio from typing import Any, Callable + import efro.log import ba from ba._cloud import CloudSubsystem from bastd.actor import spazappearance @@ -49,6 +50,7 @@ class App: # Implementations for these will be filled in by internal libs. accounts_v2: AccountV2Subsystem cloud: CloudSubsystem + log_handler: efro.log.LogHandler class State(Enum): """High level state the app can be in.""" @@ -384,7 +386,7 @@ class App: # If there's a leftover log file, attempt to upload it to the # master-server and/or get rid of it. - _apputils.handle_leftover_log_file() + _apputils.handle_leftover_v1_cloud_log_file() # Only do this stuff if our config file is healthy so we don't # overwrite a broken one or whatnot and wipe out data. diff --git a/assets/src/ba_data/python/ba/_appconfig.py b/assets/src/ba_data/python/ba/_appconfig.py index 13270319..4fd28deb 100644 --- a/assets/src/ba_data/python/ba/_appconfig.py +++ b/assets/src/ba_data/python/ba/_appconfig.py @@ -128,12 +128,6 @@ def read_config() -> tuple[AppConfig, bool]: shutil.copyfile(config_file_path, config_file_path + '.broken') except Exception as exc2: print('EXC copying broken config:', exc2) - try: - _ba.log('broken config contents:\n' + - config_contents.replace('\000', ''), - to_stdout=False) - except Exception as exc2: - print('EXC logging broken config contents:', exc2) config = AppConfig() # Now attempt to read one of our 'prev' backup copies. diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py index e1d5d626..d7c2cfe9 100644 --- a/assets/src/ba_data/python/ba/_apputils.py +++ b/assets/src/ba_data/python/ba/_apputils.py @@ -50,7 +50,7 @@ def should_submit_debug_info() -> bool: return _ba.app.config.get('Submit Debug Info', True) -def handle_log() -> None: +def handle_v1_cloud_log() -> None: """Called on debug log prints. When this happens, we can upload our log to the server @@ -74,7 +74,7 @@ def handle_log() -> None: activityname = 'unavailable' info = { - 'log': _ba.getlog(), + 'log': _ba.get_v1_cloud_log(), 'version': app.version, 'build': app.build_number, 'userAgentString': app.user_agent_string, @@ -108,7 +108,7 @@ def handle_log() -> None: def _reset() -> None: app.log_upload_timer_started = False if app.log_have_new: - handle_log() + handle_v1_cloud_log() if not _ba.is_log_full(): with _ba.Context('ui'): @@ -118,14 +118,15 @@ def handle_log() -> None: suppress_format_warning=True) -def handle_leftover_log_file() -> None: - """Handle an un-uploaded log from a previous run.""" +def handle_leftover_v1_cloud_log_file() -> None: + """Handle an un-uploaded v1-cloud-log from a previous run.""" try: import json from ba._net import master_server_post - if os.path.exists(_ba.get_log_file_path()): - with open(_ba.get_log_file_path(), encoding='utf-8') as infile: + if os.path.exists(_ba.get_v1_cloud_log_file_path()): + with open(_ba.get_v1_cloud_log_file_path(), + encoding='utf-8') as infile: info = json.loads(infile.read()) infile.close() do_send = should_submit_debug_info() @@ -136,7 +137,7 @@ def handle_leftover_log_file() -> None: # lets kill it. if data is not None: try: - os.remove(_ba.get_log_file_path()) + os.remove(_ba.get_v1_cloud_log_file_path()) except FileNotFoundError: # Saw this in the wild. The file just existed # a moment ago but I suppose something could have @@ -146,7 +147,7 @@ def handle_leftover_log_file() -> None: master_server_post('bsLog', info, response) else: # If they don't want logs uploaded just kill it. - os.remove(_ba.get_log_file_path()) + os.remove(_ba.get_v1_cloud_log_file_path()) except Exception: from ba import _error _error.print_exception('Error handling leftover log file.') diff --git a/assets/src/ba_data/python/ba/_asyncio.py b/assets/src/ba_data/python/ba/_asyncio.py index afb3adc4..3639d2af 100644 --- a/assets/src/ba_data/python/ba/_asyncio.py +++ b/assets/src/ba_data/python/ba/_asyncio.py @@ -18,7 +18,7 @@ import os if TYPE_CHECKING: import ba -# Our timer and event loop for the ballistica game thread. +# Our timer and event loop for the ballistica logic thread. _asyncio_timer: ba.Timer | None = None _asyncio_event_loop: asyncio.AbstractEventLoop | None = None diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py index 5dafc7f5..52b9bde7 100644 --- a/assets/src/ba_data/python/ba/_bootstrap.py +++ b/assets/src/ba_data/python/ba/_bootstrap.py @@ -5,13 +5,13 @@ from __future__ import annotations import os import sys -import threading from typing import TYPE_CHECKING +from efro.log import setup_logging, LogLevel import _ba if TYPE_CHECKING: - from typing import Any, TextIO, Callable + from efro.log import LogEntry _g_did_bootstrap = False # pylint: disable=invalid-name @@ -19,25 +19,31 @@ _g_did_bootstrap = False # pylint: disable=invalid-name def bootstrap() -> None: """Run bootstrapping logic. - This is the very first userland code that runs. + This is the very first ballistica code that runs (aside from imports). It sets up low level environment bits and creates the app instance. """ + 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 - # native stdout/stderr is not easily accessible (looking at you, Android). - sys.stdout = _Redirect(sys.stdout, _ba.print_stdout) # type: ignore - sys.stderr = _Redirect(sys.stderr, _ba.print_stderr) # type: ignore + # The first thing we do is set up our logging system and feed + # Python's stdout/stderr into it. Then we can at least debug problems + # on systems where native stdout/stderr is not easily accessible + # such as Android. + log_handler = setup_logging(log_path=None, + level=LogLevel.DEBUG, + suppress_non_root_debug=True, + log_stdout_stderr=True) + + log_handler.add_callback(_on_log) env = _ba.env() # Give a soft warning if we're being used with a different binary # version than we expect. - expected_build = 20842 + expected_build = 20849 running_build: int = env['build_number'] if running_build != expected_build: print( @@ -85,42 +91,6 @@ def bootstrap() -> None: os.environ['SSL_CERT_FILE'] = os.environ['REQUESTS_CA_BUNDLE'] = ( certifi.where()) - # FIXME: I think we should init Python in the main thread, which should - # also avoid these issues. (and also might help us play better with - # Python debuggers?) - - # Gloriously hacky workaround here: - # Our 'main' Python thread is the game thread (not the app's main - # thread) which means it has a small stack compared to the main - # thread (at least on apple). Sadly it turns out this causes the - # debug build of Python to blow its stack immediately when doing - # some big imports. - # Normally we'd just give the game thread the same stack size as - # the main thread and that'd be the end of it. However - # we're using std::threads which it turns out have no way to set - # the stack size (as of fall '19). Grumble. - # - # However python threads *can* take custom stack sizes. - # (and it appears they might use the main thread's by default?..) - # ...so as a workaround in the debug version, we can run problematic - # heavy imports here in another thread and all is well. - # If we ever see stack overflows in our release build we'll have - # to take more drastic measures like switching from std::threads - # to pthreads. - - if debug_build: - - # noinspection PyUnresolvedReferences - def _thread_func() -> None: - # pylint: disable=unused-import - import json - import urllib.request - - testthread = threading.Thread(target=_thread_func) - testthread.start() - testthread.join() - del testthread - # Clear out the standard quit/exit messages since they don't work for us. # pylint: disable=c-extension-no-member if not TYPE_CHECKING: @@ -132,54 +102,20 @@ def bootstrap() -> None: from ba._app import App import ba _ba.app = ba.app = App() + _ba.app.log_handler = log_handler -class _Redirect: +def _on_log(entry: LogEntry) -> None: - def __init__(self, original: TextIO, call: Callable[[str], None]) -> None: - self._lock = threading.Lock() - self._linebits: list[str] = [] - self._original = original - self._call = call - self._pending_ship = False + # Just forward this along to the engine to display in the in-game console, + # in the Android log, etc. + _ba.display_log(name=entry.name, + level=entry.level.name, + message=entry.message) - def write(self, sval: Any) -> None: - """Override standard write call.""" - - self._call(sval) - - # Now do logging: - # Add it to our accumulated line. - # If the message ends in a newline, we can ship it - # immediately as a log entry. Otherwise, schedule a ship - # next cycle (if it hasn't yet at that point) so that we - # can accumulate subsequent prints. - # (so stuff like print('foo', 123, 'bar') will ship as one entry) - with self._lock: - self._linebits.append(sval) - if sval.endswith('\n'): - self._shiplog() - else: - _ba.pushcall(self._shiplog, - from_other_thread=True, - suppress_other_thread_warning=True) - - def _shiplog(self) -> None: - with self._lock: - line = ''.join(self._linebits) - if not line: - return - self._linebits = [] - - # Log messages aren't expected to have trailing newlines. - if line.endswith('\n'): - line = line[:-1] - _ba.log(line, to_stdout=False) - - def flush(self) -> None: - """Flush the file.""" - self._original.flush() - - def isatty(self) -> bool: - """Are we a terminal?""" - return self._original.isatty() + # We also want to feed some logs to the old V1-cloud-log system. + # Let's go with anything warning or higher as well as the stdout/stderr + # log messages that ba.app.log_handler creates for us. + if entry.level.value >= LogLevel.WARNING.value or entry.name in ('stdout', + 'stderr'): + _ba.v1_cloud_log(entry.message) diff --git a/assets/src/ba_data/python/ba/_error.py b/assets/src/ba_data/python/ba/_error.py index bb82a78a..5e6aba42 100644 --- a/assets/src/ba_data/python/ba/_error.py +++ b/assets/src/ba_data/python/ba/_error.py @@ -125,6 +125,11 @@ class WidgetNotFoundError(NotFoundError): """ +# TODO: Should integrate some sort of context printing into our +# log handling so we can just use logging.exception() and kill these +# two functions. + + def print_exception(*args: Any, **keywds: Any) -> None: """Print info about an exception along with pertinent context state. diff --git a/assets/src/ba_data/python/ba/_hooks.py b/assets/src/ba_data/python/ba/_hooks.py index 9d103449..ab7e36cd 100644 --- a/assets/src/ba_data/python/ba/_hooks.py +++ b/assets/src/ba_data/python/ba/_hooks.py @@ -28,7 +28,7 @@ def finish_bootstrapping() -> None: assert _ba.in_logic_thread() # Kick off our asyncio event handling, allowing us to use coroutines - # in our game thread alongside our internal event handling. + # in our logic thread alongside our internal event handling. # setup_asyncio() # Ok, bootstrapping is done; time to get the show started. diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py index 40f75619..1faca382 100644 --- a/assets/src/ba_data/python/ba/_meta.py +++ b/assets/src/ba_data/python/ba/_meta.py @@ -192,13 +192,13 @@ class MetadataSubsystem: color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) if results.warnings: - _ba.log(textwrap.indent('\n'.join(results.warnings), - 'Warning (meta-scan): '), - to_server=False) + allwarnings = textwrap.indent('\n'.join(results.warnings), + 'Warning (meta-scan): ') + logging.warning(allwarnings) if results.errors: - _ba.log( - textwrap.indent('\n'.join(results.errors), - 'Error (meta-scan): ')) + allerrors = textwrap.indent('\n'.join(results.errors), + 'Error (meta-scan): ') + logging.error(allerrors) # Let the game know we're done. assert self._scan_complete_cb is not None diff --git a/assets/src/ba_data/python/ba/_playlist.py b/assets/src/ba_data/python/ba/_playlist.py index 1550934c..5518601a 100644 --- a/assets/src/ba_data/python/ba/_playlist.py +++ b/assets/src/ba_data/python/ba/_playlist.py @@ -5,6 +5,7 @@ from __future__ import annotations import copy +import logging from typing import Any, TYPE_CHECKING if TYPE_CHECKING: @@ -29,7 +30,6 @@ def filter_playlist(playlist: PlaylistType, # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements - import _ba from ba._map import get_filtered_map_name from ba._store import get_unowned_maps, get_unowned_game_types from ba._general import getclass @@ -140,8 +140,8 @@ def filter_playlist(playlist: PlaylistType, entry['settings'][setting.name] = setting.default goodlist.append(entry) except ImportError as exc: - _ba.log(f'Import failed while scanning playlist \'{name}\': {exc}', - to_server=False) + logging.warning('Import failed while scanning playlist \'%s\': %s', + name, exc) except Exception: from ba import _error _error.print_exception() diff --git a/assets/src/ba_data/python/ba/_plugin.py b/assets/src/ba_data/python/ba/_plugin.py index c4702045..d05cdbe9 100644 --- a/assets/src/ba_data/python/ba/_plugin.py +++ b/assets/src/ba_data/python/ba/_plugin.py @@ -4,6 +4,7 @@ from __future__ import annotations +import logging from typing import TYPE_CHECKING from dataclasses import dataclass @@ -127,8 +128,7 @@ class PluginSubsystem: subs=[('${PLUGIN}', plugkey), ('${ERROR}', str(exc))]), color=(1, 0, 0)) - _ba.log(f"Error loading plugin class '{plugkey}': {exc}", - to_server=False) + logging.exception("Error loading plugin class '%s'", plugkey) continue try: plugin = cls() @@ -155,10 +155,8 @@ class PluginSubsystem: color=(1, 1, 0), ) plugnames = ', '.join(disappeared_plugs) - _ba.log( - f'{len(disappeared_plugs)} plugin(s) no longer found:' - f' {plugnames}.', - to_server=False) + logging.warning('%d plugin(s) no longer found: %s.', + len(disappeared_plugs), plugnames) for goneplug in disappeared_plugs: del _ba.app.config['Plugins'][goneplug] _ba.app.config.commit() diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py index ebc7f20d..8c8ed981 100644 --- a/assets/src/ba_data/python/ba/_servermode.py +++ b/assets/src/ba_data/python/ba/_servermode.py @@ -5,6 +5,7 @@ from __future__ import annotations import sys import time +import logging from typing import TYPE_CHECKING from efro.terminal import Clr @@ -334,11 +335,11 @@ class ServerController: if self._first_run: curtimestr = time.strftime('%c') - _ba.log( + startupmsg = ( f'{Clr.BLD}{Clr.BLU}{_ba.appnameupper()} {app.version}' f' ({app.build_number})' - f' entering server-mode {curtimestr}{Clr.RST}', - to_server=False) + f' entering server-mode {curtimestr}{Clr.RST}') + logging.info(startupmsg) if sessiontype is FreeForAllSession: appcfg['Free-for-All Playlist Selection'] = self._playlist_name diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml index 16d6b197..487dfc87 100644 --- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml @@ -41,7 +41,9 @@ airborn alext alibaba + allerrors allocs + allwarnings alot alphaimg alphapixels @@ -1243,6 +1245,7 @@ startpos startsplits starttime + startupmsg startx starty statestr @@ -1262,6 +1265,7 @@ strdup stringi stringified + stringifying strlen strs strtof diff --git a/src/ballistica/app/app.h b/src/ballistica/app/app.h index babeecd3..754d42e1 100644 --- a/src/ballistica/app/app.h +++ b/src/ballistica/app/app.h @@ -32,10 +32,10 @@ class App { std::vector pausable_threads; TouchInput* touch_input{}; std::string console_startup_messages; - std::mutex log_mutex; - std::string log; - bool put_log{}; - bool log_full{}; + std::mutex v1_cloud_log_mutex; + std::string v1_cloud_log; + bool did_put_v1_cloud_log{}; + bool v1_cloud_log_full{}; int master_server_source{0}; int session_count{}; bool shutting_down{}; diff --git a/src/ballistica/app/app_config.h b/src/ballistica/app/app_config.h index ed45fdc9..9a970b4a 100644 --- a/src/ballistica/app/app_config.h +++ b/src/ballistica/app/app_config.h @@ -13,7 +13,7 @@ namespace ballistica { // This class wrangles user config values for the app. // The underlying config data currently lives in the Python layer, -// so at the moment these calls are only usable from the game thread, +// so at the moment these calls are only usable from the logic thread, // but that may change in the future. class AppConfig { public: diff --git a/src/ballistica/assets/assets.cc b/src/ballistica/assets/assets.cc index 9393e7a8..8bcc5ee1 100644 --- a/src/ballistica/assets/assets.cc +++ b/src/ballistica/assets/assets.cc @@ -294,7 +294,7 @@ void Assets::PrintLoadInfo() { snprintf(buffer, sizeof(buffer), " %-50s %10s %10s", "FILE", "PRELOAD_TIME", "LOAD_TIME"); s += buffer; - Log(s, true, false); + Log(LogLevel::kInfo, s); millisecs_t total_preload_time = 0; millisecs_t total_load_time = 0; assert(asset_lists_locked_); @@ -307,7 +307,7 @@ void Assets::PrintLoadInfo() { i.second->GetName().c_str(), static_cast_check_fit(preload_time), static_cast_check_fit(load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); num++; } assert(asset_lists_locked_); @@ -320,7 +320,7 @@ void Assets::PrintLoadInfo() { i.second->GetName().c_str(), static_cast_check_fit(preload_time), static_cast_check_fit(load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); num++; } assert(asset_lists_locked_); @@ -333,7 +333,7 @@ void Assets::PrintLoadInfo() { i.second->GetName().c_str(), static_cast_check_fit(preload_time), static_cast_check_fit(load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); num++; } assert(asset_lists_locked_); @@ -346,7 +346,7 @@ void Assets::PrintLoadInfo() { i.second->GetName().c_str(), static_cast_check_fit(preload_time), static_cast_check_fit(load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); num++; } assert(asset_lists_locked_); @@ -359,7 +359,7 @@ void Assets::PrintLoadInfo() { i.second->file_name_full().c_str(), static_cast_check_fit(preload_time), static_cast_check_fit(load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); num++; } snprintf(buffer, sizeof(buffer), @@ -367,7 +367,7 @@ void Assets::PrintLoadInfo() { "(feeding data to OpenGL, etc): %i", static_cast(total_preload_time), static_cast(total_load_time)); - Log(buffer, true, false); + Log(LogLevel::kInfo, buffer); } void Assets::MarkAllAssetsForLoad() { @@ -810,8 +810,8 @@ auto Assets::RunPendingLoadList(std::vector*>* c_list) -> bool { } } - // if we dumped anything on the pending loads done list, shake the game thread - // to tell it to kill the reference.. + // if we dumped anything on the pending loads done list, shake the logic + // thread to tell it to kill the reference.. if (!l_finished.empty()) { assert(g_logic); g_logic->PushHavePendingLoadsDoneCall(); @@ -977,7 +977,7 @@ void Assets::Prune(int level) { if (current_time - collide_model_data->last_used_time() > standard_asset_prune_time && (collide_model_data->object_strong_ref_count() <= 1)) { - // we can unload it immediately since that happens in the game thread... + // we can unload it immediately since that happens in the logic thread... collide_model_data->Unload(); auto i_next = i; ++i_next; @@ -1151,10 +1151,12 @@ auto Assets::FindAssetFile(FileType type, const std::string& name) // We wanna fail gracefully for some types. if (type == FileType::kSound && name != "blank") { - Log("Unable to load audio: '" + name + "'; trying fallback..."); + Log(LogLevel::kError, + "Unable to load audio: '" + name + "'; trying fallback..."); return FindAssetFile(type, "blank"); } else if (type == FileType::kTexture && name != "white") { - Log("Unable to load texture: '" + name + "'; trying fallback..."); + Log(LogLevel::kError, + "Unable to load texture: '" + name + "'; trying fallback..."); return FindAssetFile(type, "white"); } @@ -1181,7 +1183,7 @@ void Assets::AddPendingLoad(Object::Ref* c) { break; } default: { - // Tell the game thread there's pending loads. + // Tell the logic thread there's pending loads. { std::scoped_lock lock(pending_load_list_mutex_); pending_loads_other_.push_back(c); @@ -1198,7 +1200,7 @@ void Assets::ClearPendingLoadsDoneList() { std::scoped_lock lock(pending_load_list_mutex_); // Our explicitly-allocated reference pointer has made it back to us here in - // the game thread. + // the logic thread. // We can now kill the reference knowing that it's safe for this component // to die at any time (anyone needing it to be alive now should be holding a // reference themselves). @@ -1213,7 +1215,7 @@ void Assets::AddPackage(const std::string& name, const std::string& path) { assert(InLogicThread()); #if BA_DEBUG_BUILD if (packages_.find(name) != packages_.end()) { - Log("WARNING: adding duplicate package: '" + name + "'"); + Log(LogLevel::kWarning, "adding duplicate package: '" + name + "'"); } #endif // BA_DEBUG_BUILD packages_[name] = path; diff --git a/src/ballistica/assets/assets.h b/src/ballistica/assets/assets.h index 529d4856..a23bcefe 100644 --- a/src/ballistica/assets/assets.h +++ b/src/ballistica/assets/assets.h @@ -67,7 +67,7 @@ class Assets { /// recreating/adjusting the renderer. auto UnloadRendererBits(bool textures, bool models) -> void; - /// Should be called from the game thread after UnloadRendererBits(); + /// Should be called from the logic thread after UnloadRendererBits(); /// kicks off bg loads for all existing unloaded assets. auto MarkAllAssetsForLoad() -> void; auto PrintLoadInfo() -> void; diff --git a/src/ballistica/assets/assets_server.cc b/src/ballistica/assets/assets_server.cc index 733e9b35..81168ff9 100644 --- a/src/ballistica/assets/assets_server.cc +++ b/src/ballistica/assets/assets_server.cc @@ -57,7 +57,8 @@ void AssetsServer::PushBeginWriteReplayCall() { // we only allow writing one replay at once; make sure that's actually the // case if (writing_replay_) { - Log("AssetsServer got BeginWriteReplayCall while already writing"); + Log(LogLevel::kError, + "AssetsServer got BeginWriteReplayCall while already writing"); WriteReplayMessages(); if (replay_out_file_) { fclose(replay_out_file_); @@ -76,7 +77,8 @@ void AssetsServer::PushBeginWriteReplayCall() { replay_bytes_written_ = 0; if (!replay_out_file_) { - Log("ERROR: unable to open output-stream file: '" + file_path + "'"); + Log(LogLevel::kError, + "unable to open output-stream file: '" + file_path + "'"); } else { // write file id and protocol-version // NOTE - we always write replays in our host protocol version @@ -87,8 +89,8 @@ void AssetsServer::PushBeginWriteReplayCall() { || (fwrite(&version, sizeof(version), 1, replay_out_file_) != 1)) { fclose(replay_out_file_); replay_out_file_ = nullptr; - Log("error writing replay file header: " - + g_platform->GetErrnoString()); + Log(LogLevel::kError, "error writing replay file header: " + + g_platform->GetErrnoString()); } replay_bytes_written_ = 5; } @@ -108,7 +110,8 @@ void AssetsServer::PushAddMessageToReplayCall( // sanity check.. if (!writing_replay_) { - Log("AssetsServer got AddMessageToReplayCall while not writing replay"); + Log(LogLevel::kError, + "AssetsServer got AddMessageToReplayCall while not writing replay"); replays_broken_ = true; return; } @@ -118,7 +121,8 @@ void AssetsServer::PushAddMessageToReplayCall( // if we've got too much data built up (lets go with 10 megs for now), // abort if (replay_message_bytes_ > 10000000) { - Log("replay output buffer exceeded 10 megs; aborting replay"); + Log(LogLevel::kError, + "replay output buffer exceeded 10 megs; aborting replay"); fclose(replay_out_file_); replay_out_file_ = nullptr; replay_message_bytes_ = 0; @@ -139,7 +143,7 @@ void AssetsServer::PushEndWriteReplayCall() { // sanity check.. if (!writing_replay_) { - Log("_finishWritingReplay called while not writing"); + Log(LogLevel::kError, "_finishWritingReplay called while not writing"); replays_broken_ = true; return; } @@ -176,7 +180,8 @@ void AssetsServer::WriteReplayMessages() { if (fwrite(&len8, 1, 1, replay_out_file_) != 1) { fclose(replay_out_file_); replay_out_file_ = nullptr; - Log("error writing replay file: " + g_platform->GetErrnoString()); + Log(LogLevel::kError, + "error writing replay file: " + g_platform->GetErrnoString()); return; } } @@ -187,14 +192,16 @@ void AssetsServer::WriteReplayMessages() { if (fwrite(&len16, 2, 1, replay_out_file_) != 1) { fclose(replay_out_file_); replay_out_file_ = nullptr; - Log("error writing replay file: " + g_platform->GetErrnoString()); + Log(LogLevel::kError, + "error writing replay file: " + g_platform->GetErrnoString()); return; } } else { if (fwrite(&len32, 4, 1, replay_out_file_) != 1) { fclose(replay_out_file_); replay_out_file_ = nullptr; - Log("error writing replay file: " + g_platform->GetErrnoString()); + Log(LogLevel::kError, + "error writing replay file: " + g_platform->GetErrnoString()); return; } } @@ -205,7 +212,8 @@ void AssetsServer::WriteReplayMessages() { if (result != 1) { fclose(replay_out_file_); replay_out_file_ = nullptr; - Log("error writing replay file: " + g_platform->GetErrnoString()); + Log(LogLevel::kError, + "error writing replay file: " + g_platform->GetErrnoString()); return; } replay_bytes_written_ += data_compressed.size() + 2; diff --git a/src/ballistica/assets/data/data_data.cc b/src/ballistica/assets/data/data_data.cc index 770899e6..0a509fc4 100644 --- a/src/ballistica/assets/data/data_data.cc +++ b/src/ballistica/assets/data/data_data.cc @@ -20,8 +20,8 @@ void DataData::DoPreload() { // in the following case: // - asset thread grabs payload lock for Preload() // - asset thread tries to grab GIL in Preload(); spins. - // - meanwhile, something in game thread has called Load() - // - game thread holds GIL by default and now spins waiting on payload lock. + // - meanwhile, something in logic thread has called Load() + // - logic thread holds GIL by default and now spins waiting on payload lock. // - deadlock :-( // ...so the new plan is to simply load the file into a string in Preload() diff --git a/src/ballistica/assets/data/sound_data.cc b/src/ballistica/assets/data/sound_data.cc index 804b7fb9..5b257c0d 100644 --- a/src/ballistica/assets/data/sound_data.cc +++ b/src/ballistica/assets/data/sound_data.cc @@ -56,8 +56,8 @@ static auto LoadOgg(const char* file_name, std::vector* buffer, f = g_platform->FOpen(file_name, "rb"); if (f == nullptr) { fallback = true; - Log(std::string("Error: Can't open sound file '") + file_name - + "' for reading..."); + Log(LogLevel::kError, std::string("Can't open sound file '") + file_name + + "' for reading..."); // Attempt a fallback standin; if that doesn't work, throw in the towel. file_name = "data/global/audio/blank.ogg"; @@ -77,7 +77,8 @@ static auto LoadOgg(const char* file_name, std::vector* buffer, // Try opening the given file if (ov_open_callbacks(f, &ogg_file, nullptr, 0, callbacks) != 0) { - Log(std::string("Error decoding sound file '") + file_name + "'"); + Log(LogLevel::kError, + std::string("Error decoding sound file '") + file_name + "'"); fclose(f); @@ -199,8 +200,9 @@ static void LoadCachedOgg(const char* file_name, std::vector* buffer, // with invalid formats of 0 once. Report and ignore if we see // something like that. if (*format != AL_FORMAT_MONO16 && *format != AL_FORMAT_STEREO16) { - Log(std::string("Ignoring invalid audio cache of ") + file_name - + " with format " + std::to_string(*format)); + Log(LogLevel::kError, std::string("Ignoring invalid audio cache of ") + + file_name + " with format " + + std::to_string(*format)); } else { return; // SUCCESS!!!! } diff --git a/src/ballistica/audio/al_sys.cc b/src/ballistica/audio/al_sys.cc index 11518df6..c39a14d2 100644 --- a/src/ballistica/audio/al_sys.cc +++ b/src/ballistica/audio/al_sys.cc @@ -16,13 +16,14 @@ namespace ballistica { void _check_al_error(const char* file, int line) { if (g_audio_server->paused()) { - Log(Utils::BaseName(file) + ":" + std::to_string(line) - + ": Checking OpenAL error while paused."); + Log(LogLevel::kError, Utils::BaseName(file) + ":" + std::to_string(line) + + ": Checking OpenAL error while paused."); } ALenum al_err = alGetError(); if (al_err != AL_NO_ERROR) { - Log(Utils::BaseName(file) + ":" + std::to_string(line) - + ": OpenAL Error: " + GetALErrorString(al_err) + ";"); + Log(LogLevel::kError, Utils::BaseName(file) + ":" + std::to_string(line) + + ": OpenAL Error: " + GetALErrorString(al_err) + + ";"); } } diff --git a/src/ballistica/audio/audio_server.cc b/src/ballistica/audio/audio_server.cc index a63451d7..295bb803 100644 --- a/src/ballistica/audio/audio_server.cc +++ b/src/ballistica/audio/audio_server.cc @@ -144,7 +144,7 @@ struct AudioServer::SoundFadeNode { void AudioServer::SetPaused(bool pause) { if (!paused_) { if (!pause) { - Log("Error: got audio unpause request when already unpaused."); + Log(LogLevel::kError, "Got audio unpause request when already unpaused."); } else { #if BA_OSTYPE_IOS_TVOS // apple recommends this during audio-interruptions.. @@ -163,7 +163,7 @@ void AudioServer::SetPaused(bool pause) { } else { // unpause if requested.. if (pause) { - Log("Error: Got audio pause request when already paused."); + Log(LogLevel::kError, "Got audio pause request when already paused."); } else { #if BA_OSTYPE_IOS_TVOS // apple recommends this during audio-interruptions.. @@ -357,7 +357,7 @@ auto AudioServer::OnAppStartInThread() -> void { ALboolean enumeration = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT"); if (enumeration == AL_FALSE) { - Log("OpenAL enumeration extensions missing."); + Log(LogLevel::kError, "OpenAL enumeration extensions missing."); } else { const ALCchar* devices = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER); @@ -414,8 +414,8 @@ auto AudioServer::OnAppStartInThread() -> void { sound_source_refs_.push_back(s); sources_.push_back(&(*s)); } else { - Log("Error: Made " + std::to_string(i) + " sources; (wanted " - + std::to_string(target_source_count) + ")."); + Log(LogLevel::kError, "Made " + std::to_string(i) + " sources; (wanted " + + std::to_string(target_source_count) + ")."); break; } } @@ -473,9 +473,10 @@ void AudioServer::UpdateAvailableSources() { // that probably means somebody's grabbing a source but never // resubmitting it. if (t - i->client_source()->last_lock_time() > 10000) { - Log("Error: Client audio source has been locked for too long; " + Log(LogLevel::kError, + "Client audio source has been locked for too long; " "probably leaked. (debug id " - + std::to_string(i->client_source()->lock_debug_id()) + ")"); + + std::to_string(i->client_source()->lock_debug_id()) + ")"); } continue; } @@ -694,8 +695,8 @@ AudioServer::ThreadSource::ThreadSource(AudioServer* audio_thread_in, int id_in, ALenum err = alGetError(); valid_ = (err == AL_NO_ERROR); if (!valid_) { - Log(std::string("Error: AL Error ") + GetALErrorString(err) - + " on source creation."); + Log(LogLevel::kError, std::string("AL Error ") + GetALErrorString(err) + + " on source creation."); } else { // In vr mode we keep the microphone a bit closer to the camera // for realism purposes, so we need stuff louder in general. @@ -878,9 +879,9 @@ void AudioServer::ThreadSource::SetPosition(float x, float y, float z) { z = 500; } if (oob) { - BA_LOG_ONCE( - "Error: AudioServer::ThreadSource::SetPosition" - " got out-of-bounds value."); + BA_LOG_ONCE(LogLevel::kError, + "AudioServer::ThreadSource::SetPosition" + " got out-of-bounds value."); } ALfloat source_pos[] = {x, y, z}; alSourcefv(source_, AL_POSITION, source_pos); @@ -1098,7 +1099,7 @@ void AudioServer::PushSetSoundPitchCall(float val) { void AudioServer::PushSetPausedCall(bool pause) { thread()->PushCall([this, pause] { if (g_buildconfig.ostype_android()) { - Log("Error: Shouldn't be getting SetPausedCall on android."); + Log(LogLevel::kError, "Shouldn't be getting SetPausedCall on android."); } SetPaused(pause); }); @@ -1111,7 +1112,7 @@ void AudioServer::PushComponentUnloadCall( for (auto&& i : components) { (**i).Unload(); } - // ...and then ship these pointers back to the game thread, so it can free + // ...and then ship these pointers back to the logic thread, so it can free // the references. g_logic->PushFreeAssetComponentRefsCall(components); }); @@ -1129,7 +1130,7 @@ void AudioServer::AddSoundRefDelete(const Object::Ref* c) { 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. + // Now push a call to the logic thread to do the deletes. g_logic->thread()->PushCall( [] { g_audio_server->ClearSoundRefDeleteList(); }); } @@ -1154,7 +1155,7 @@ void AudioServer::BeginInterruption() { break; } if (GetRealTime() - t > 1000) { - Log("Error: Timed out waiting for audio pause."); + Log(LogLevel::kError, "Timed out waiting for audio pause."); break; } Platform::SleepMS(2); @@ -1176,7 +1177,7 @@ void AudioServer::EndInterruption() { break; } if (GetRealTime() - t > 1000) { - Log("Error: Timed out waiting for audio unpause."); + Log(LogLevel::kError, "Timed out waiting for audio unpause."); break; } Platform::SleepMS(2); diff --git a/src/ballistica/audio/audio_server.h b/src/ballistica/audio/audio_server.h index 9efb480a..e8d6720e 100644 --- a/src/ballistica/audio/audio_server.h +++ b/src/ballistica/audio/audio_server.h @@ -101,7 +101,7 @@ class AudioServer { // Some threads such as audio hold onto allocated Media-Component-Refs to keep // media components alive that they need. Media-Component-Refs, however, must - // be disposed of in the game thread, so they are passed back to it through + // be disposed of in the logic thread, so they are passed back to it through // this function. auto AddSoundRefDelete(const Object::Ref* c) -> void; diff --git a/src/ballistica/audio/audio_source.cc b/src/ballistica/audio/audio_source.cc index 492fd0ec..4e391f34 100644 --- a/src/ballistica/audio/audio_source.cc +++ b/src/ballistica/audio/audio_source.cc @@ -41,7 +41,7 @@ void AudioSource::SetPosition(float x, float y, float z) { assert(client_queue_size_ > 0); #if BA_DEBUG_BUILD if (std::isnan(x) || std::isnan(y) || std::isnan(z)) { - Log("Error: Got nan value in AudioSource::SetPosition."); + Log(LogLevel::kError, "Got nan value in AudioSource::SetPosition."); } #endif g_audio_server->PushSourceSetPositionCall(play_id_, Vector3f(x, y, z)); diff --git a/src/ballistica/audio/audio_streamer.cc b/src/ballistica/audio/audio_streamer.cc index d44a9982..9cdee4a1 100644 --- a/src/ballistica/audio/audio_streamer.cc +++ b/src/ballistica/audio/audio_streamer.cc @@ -86,8 +86,9 @@ void AudioStreamer::Update() { // A fun anomaly in the linux version; we sometimes get more // "processed" buffers than we have queued. if (queued < processed) { - Log("Error: streamer oddness: queued(" + std::to_string(queued) - + "); processed(" + std::to_string(processed) + ")"); + Log(LogLevel::kError, "Streamer oddness: queued(" + std::to_string(queued) + + "); processed(" + std::to_string(processed) + + ")"); processed = queued; } diff --git a/src/ballistica/audio/ogg_stream.cc b/src/ballistica/audio/ogg_stream.cc index 93286491..68b40ef6 100644 --- a/src/ballistica/audio/ogg_stream.cc +++ b/src/ballistica/audio/ogg_stream.cc @@ -88,7 +88,8 @@ void OggStream::DoStream(char* pcm, int* size, unsigned int* rate) { static bool reported_error = false; if (!reported_error) { reported_error = true; - Log("Error streaming ogg file: '" + file_name() + "'."); + Log(LogLevel::kError, + "Error streaming ogg file: '" + file_name() + "'."); } if (loops()) { ov_pcm_seek(&ogg_file_, 0); diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc index 355a3e30..4c2ea9b2 100644 --- a/src/ballistica/ballistica.cc +++ b/src/ballistica/ballistica.cc @@ -32,13 +32,13 @@ namespace ballistica { // These are set automatically via script; don't modify them here. -const int kAppBuildNumber = 20842; +const int kAppBuildNumber = 20849; const char* kAppVersion = "1.7.7"; // Our standalone globals. // These are separated out for easy access. // Everything else should go into App (or more ideally into a class). -int g_early_log_writes{10}; +int g_early_v1_cloud_log_writes{10}; App* g_app{}; AppConfig* g_app_config{}; @@ -86,8 +86,8 @@ auto BallisticaMain(int argc, char** argv) -> int { // avoid any logic that accesses other globals since they may // not yet exist. - // Minimal globals we must assign immediately as they are needed - // during construction of the others. + // Minimal globals we must assign immediately as they ARE needed + // for construction of the others (would be great to eliminate this need). g_platform = Platform::Create(); g_app = new App(argc, argv); g_app_internal = CreateAppInternal(); @@ -267,49 +267,56 @@ auto GetAppInstanceUUID() -> const std::string& { } if (!have_session_id) { // As an emergency fallback simply use a single random number. - Log("WARNING: GetSessionUUID() using rand fallback."); + Log(LogLevel::kWarning, "GetSessionUUID() using rand fallback."); srand(static_cast( Platform::GetCurrentMilliseconds())); // NOLINT session_id = std::to_string(static_cast(rand())); // NOLINT have_session_id = true; } if (session_id.size() >= 100) { - Log("WARNING: session id longer than it should be."); + Log(LogLevel::kWarning, "session id longer than it should be."); } } return session_id; } +auto InMainThread() -> bool { + assert(g_main_thread); // Root out early use of this. + return (g_main_thread->IsCurrent()); +} + auto InLogicThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_logic && g_logic->thread()->IsCurrent()); } -auto InMainThread() -> bool { - return (g_app && std::this_thread::get_id() == g_app->main_thread_id); -} - auto InGraphicsThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_graphics_server && g_graphics_server->thread()->IsCurrent()); } auto InAudioThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_audio_server && g_audio_server->thread()->IsCurrent()); } auto InBGDynamicsThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_bg_dynamics_server && g_bg_dynamics_server->thread()->IsCurrent()); } auto InAssetsThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_assets_server && g_assets_server->thread()->IsCurrent()); } auto InNetworkWriteThread() -> bool { + assert(g_app && g_app->is_bootstrapped); // Root out early use of this. return (g_network_writer && g_network_writer->thread()->IsCurrent()); } -auto Log(const std::string& msg, bool to_stdout, bool to_server) -> void { - Logging::Log(msg, to_stdout, to_server); +auto Log(LogLevel level, const std::string& msg) -> void { + Logging::Log(level, msg); } auto IsVRMode() -> bool { return g_app->vr_mode; } @@ -318,7 +325,8 @@ void ScreenMessage(const std::string& s, const Vector3f& color) { if (g_logic) { g_logic->PushScreenMessage(s, color); } else { - Log("ScreenMessage before g_logic init (will be lost): '" + s + "'"); + Log(LogLevel::kError, + "ScreenMessage before g_logic init (will be lost): '" + s + "'"); } } diff --git a/src/ballistica/ballistica.h b/src/ballistica/ballistica.h index eaab6d8b..33cf255a 100644 --- a/src/ballistica/ballistica.h +++ b/src/ballistica/ballistica.h @@ -102,7 +102,7 @@ const float kGameStepSeconds = (static_cast(kGameStepMilliseconds) / 1000.0f); // Globals. -extern int g_early_log_writes; +extern int g_early_v1_cloud_log_writes; extern V1Account* g_v1_account; extern AppFlavor* g_app_flavor; extern AppConfig* g_app_config; @@ -168,8 +168,7 @@ auto GetCurrentThreadName() -> std::string; /// Write a string to the log. /// This will go to stdout, windows debug log, android log, etc. /// A trailing newline will be added. -auto Log(const std::string& msg, bool to_stdout = true, bool to_server = true) - -> void; +auto Log(LogLevel level, const std::string& msg) -> void; /// Log a fatal error and kill the app. /// Can be called from any thread at any time. diff --git a/src/ballistica/core/context.cc b/src/ballistica/core/context.cc index 632f9f1b..c99d265e 100644 --- a/src/ballistica/core/context.cc +++ b/src/ballistica/core/context.cc @@ -100,7 +100,7 @@ auto ContextTarget::NewTimer(TimeType timetype, TimerMedium length, bool repeat, void ContextTarget::DeleteTimer(TimeType timetype, int timer_id) { // We throw on NewTimer; lets just ignore anything that comes // through here to avoid messing up destructors. - Log("ContextTarget::DeleteTimer() called; unexpected."); + Log(LogLevel::kError, "ContextTarget::DeleteTimer() called; unexpected."); } auto ContextTarget::GetTime(TimeType timetype) -> millisecs_t { diff --git a/src/ballistica/core/context.h b/src/ballistica/core/context.h index 68b0d101..bd96ec13 100644 --- a/src/ballistica/core/context.h +++ b/src/ballistica/core/context.h @@ -17,13 +17,13 @@ class Context { static auto current() -> const Context& { assert(g_context); - // Context can only be accessed from the game thread. + // Context can only be accessed from the logic thread. BA_PRECONDITION(InLogicThread()); return *g_context; } static void set_current(const Context& context) { - // Context can only be accessed from the game thread. + // Context can only be accessed from the logic thread. BA_PRECONDITION(InLogicThread()); *g_context = context; diff --git a/src/ballistica/core/fatal_error.cc b/src/ballistica/core/fatal_error.cc index 8459de92..7dc75d06 100644 --- a/src/ballistica/core/fatal_error.cc +++ b/src/ballistica/core/fatal_error.cc @@ -69,11 +69,15 @@ auto FatalError::ReportFatalError(const std::string& message, } } - // Prevent the early-log insta-send mechanism from firing since we do - // basically the same thing ourself here (avoid sending the same logs twice). - g_early_log_writes = 0; + // Prevent the early-v1-cloud-log insta-send mechanism from firing since + // we do basically the same thing ourself here (avoid sending the same + // logs twice). + g_early_v1_cloud_log_writes = 0; - Logging::Log(logmsg); + // Add this to our V1CloudLog which we'll be attempting to send momentarily, + // and also try to present it directly to the user. + Logging::V1CloudLog(logmsg); + Logging::DisplayLog("root", LogLevel::kCritical, logmsg); std::string prefix = "FATAL-ERROR-LOG:"; std::string suffix; @@ -83,7 +87,7 @@ auto FatalError::ReportFatalError(const std::string& message, if (g_app == nullptr) { suffix = logmsg; } - g_app_internal->DirectSendLogs(prefix, suffix, true, &result); + g_app_internal->DirectSendV1CloudLogs(prefix, suffix, true, &result); // If we're able to show a fatal-error dialog synchronously, do so. if (g_platform && g_platform->CanShowBlockingFatalErrorDialog()) { @@ -148,10 +152,10 @@ auto FatalError::HandleFatalError(bool exit_cleanly, // bring the app down ourself. if (!in_top_level_exception_handler) { if (exit_cleanly) { - Log("Calling exit(1)..."); + Logging::DisplayLog("root", LogLevel::kCritical, "Calling exit(1)..."); exit(1); } else { - Log("Calling abort()..."); + Logging::DisplayLog("root", LogLevel::kCritical, "Calling abort()..."); abort(); } } diff --git a/src/ballistica/core/logging.cc b/src/ballistica/core/logging.cc index 69a892b9..35805da9 100644 --- a/src/ballistica/core/logging.cc +++ b/src/ballistica/core/logging.cc @@ -13,98 +13,93 @@ namespace ballistica { -static void PrintCommon(const std::string& s) { +auto Logging::Log(LogLevel level, const std::string& msg) -> void { + // This is basically up to Python to handle, but its up to us + // if Python's not up yet. + if (!g_python) { + // Make an attempt to get it at least seen (and note the fact + // that its super-early). + const char* errmsg{"Logging::Log() called before g_python exists."}; + if (g_platform) { + g_platform->DisplayLog("root", LogLevel::kError, errmsg); + g_platform->DisplayLog("root", level, msg); + } + fprintf(stderr, "%s\n%s\n", errmsg, msg.c_str()); + return; + } + + // All we want to do is call Python logging.XXX. That's on Python. + g_python->LoggingCall(level, msg); +} + +auto Logging::DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) -> void { // Print to in-game console. { if (g_logic != nullptr) { - g_logic->PushConsolePrintCall(s); + g_logic->PushConsolePrintCall(msg); } else { if (g_platform != nullptr) { - g_platform->HandleLog( - "Warning: Log() called before game-thread setup; " - "will not appear on in-game console.\n"); + g_platform->DisplayLog("root", LogLevel::kWarning, + "DisplayLog() called before logic-thread setup; " + "will not appear on in-game console."); } } } + // Print to any telnet clients. if (g_app && g_app->telnet_server) { - g_app->telnet_server->PushPrint(s); + g_app->telnet_server->PushPrint(msg); } + + // Ship to platform-specific display mechanisms (android log, etc). + g_platform->DisplayLog(name, level, msg); } -void Logging::PrintStdout(const std::string& s, bool flush) { - fprintf(stdout, "%s", s.c_str()); - if (flush) { - fflush(stdout); - } - PrintCommon(s); -} - -void Logging::PrintStderr(const std::string& s, bool flush) { - fprintf(stderr, "%s", s.c_str()); - if (flush) { - fflush(stderr); - } - PrintCommon(s); -} - -void Logging::Log(const std::string& msg, bool to_stdout, bool to_server) { - if (to_stdout) { - PrintStdout(msg + "\n", true); +auto Logging::V1CloudLog(const std::string& msg) -> void { + // Route through platform-specific loggers if present. + // (things like Crashlytics crash-logging) + if (g_platform) { + Platform::DebugLog(msg); } - // Ship to the platform logging mechanism (android-log, stderr, etc.) - // if that's available yet. - if (g_platform != nullptr) { - g_platform->HandleLog(msg); - } - - // Ship to master-server/etc. - if (to_server) { - // Route through platform-specific loggers if present. - // (things like Crashlytics crash-logging) - if (g_platform) { - Platform::DebugLog(msg); - } - - // Add to our complete log. - if (g_app != nullptr) { - std::scoped_lock lock(g_app->log_mutex); - if (!g_app->log_full) { - (g_app->log) += (msg + "\n"); - if ((g_app->log).size() > 10000) { - // Allow some reasonable overflow for last statement. - if ((g_app->log).size() > 100000) { - // FIXME: This could potentially chop up utf-8 chars. - (g_app->log).resize(100000); - } - g_app->log += "\n\n"; - g_app->log_full = true; + // Add to our complete v1-cloud-log. + if (g_app != nullptr) { + std::scoped_lock lock(g_app->v1_cloud_log_mutex); + if (!g_app->v1_cloud_log_full) { + (g_app->v1_cloud_log) += (msg + "\n"); + if ((g_app->v1_cloud_log).size() > 10000) { + // Allow some reasonable overflow for last statement. + if ((g_app->v1_cloud_log).size() > 100000) { + // FIXME: This could potentially chop up utf-8 chars. + (g_app->v1_cloud_log).resize(100000); } + g_app->v1_cloud_log += "\n\n"; + g_app->v1_cloud_log_full = true; } } + } - // If the game is fully bootstrapped, let the Python layer handle logs. - // It will group log messages intelligently and ship them to the - // master server with various other context info included. - if (g_app && g_app->is_bootstrapped) { - assert(g_python != nullptr); - g_python->PushObjCall(Python::ObjID::kHandleLogCall); - } else { - // For log messages during bootstrapping we ship them immediately since - // we don't know if the Python layer is (or will be) able to. - if (g_early_log_writes > 0) { - g_early_log_writes -= 1; - std::string logprefix = "EARLY-LOG:"; - std::string logsuffix; + // If the game is fully bootstrapped, let the Python layer handle logs. + // It will group log messages intelligently and ship them to the + // master server with various other context info included. + if (g_app && g_app->is_bootstrapped) { + assert(g_python != nullptr); + g_python->PushObjCall(Python::ObjID::kHandleV1CloudLogCall); + } else { + // For log messages during bootstrapping we ship them immediately since + // we don't know if the Python layer is (or will be) able to. + if (g_early_v1_cloud_log_writes > 0) { + g_early_v1_cloud_log_writes -= 1; + std::string logprefix = "EARLY-LOG:"; + std::string logsuffix; - // If we're an early enough error, our global log isn't even available, - // so include this specific message as a suffix instead. - if (g_app == nullptr) { - logsuffix = msg; - } - g_app_internal->DirectSendLogs(logprefix, logsuffix, false); + // If we're an early enough error, our global log isn't even available, + // so include this specific message as a suffix instead. + if (g_app == nullptr) { + logsuffix = msg; } + g_app_internal->DirectSendV1CloudLogs(logprefix, logsuffix, false); } } } diff --git a/src/ballistica/core/logging.h b/src/ballistica/core/logging.h index 66d41ed9..6c7fb319 100644 --- a/src/ballistica/core/logging.h +++ b/src/ballistica/core/logging.h @@ -5,23 +5,33 @@ #include +#include "ballistica/ballistica.h" + namespace ballistica { class Logging { public: - /// Print a string directly to stdout as well as the in-game console - /// and any connected telnet consoles. - static auto PrintStdout(const std::string& s, bool flush = false) -> void; + /// Write a message to the log. Intended for logging use in C++ code. + /// This is safe to call by any thread at any time. In general it simply + /// passes through to the equivalent Python log calls: logging.info, + /// logging.warning, etc. + /// Note that log messages often must go through some background + /// processing before being seen by the user, meaning they may not + /// always work well for tight debugging purposes. In cases such as these, + /// printf() type calls or calling DisplayLog() directly may work better. + /// (though both of these should be avoided in permanent code). + static auto Log(LogLevel level, const std::string& msg) -> void; - /// Print a string directly to stderr as well as the in-game console - /// and any connected telnet consoles. - static auto PrintStderr(const std::string& s, bool flush = false) -> void; + /// Immediately display a log message in the in-game console, + /// platform-specific logs, etc. This generally should not be called + /// directly but instead wired up to display messages coming from the + /// Python logging system. + static auto DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) -> void; - /// Write a string to the debug log. - /// This will go to stdout, windows debug log, android log, etc. depending - /// on the platform. - static auto Log(const std::string& msg, bool to_stdout = true, - bool to_server = true) -> void; + /// Write a message to the v1 cloud log. This is considered legacy + /// and will be phased out eventually. + static auto V1CloudLog(const std::string& msg) -> void; }; } // namespace ballistica diff --git a/src/ballistica/core/macros.cc b/src/ballistica/core/macros.cc index ed4a25bb..27f436bf 100644 --- a/src/ballistica/core/macros.cc +++ b/src/ballistica/core/macros.cc @@ -18,8 +18,8 @@ void MacroFunctionTimerEnd(millisecs_t starttime, millisecs_t time, } millisecs_t endtime = g_platform->GetTicks(); if (endtime - starttime > time) { - Log("Warning: " + std::to_string(endtime - starttime) - + " milliseconds spent in " + funcname); + Log(LogLevel::kWarning, std::to_string(endtime - starttime) + + " milliseconds spent in " + funcname); } } @@ -32,9 +32,9 @@ void MacroFunctionTimerEndThread(millisecs_t starttime, millisecs_t time, } millisecs_t endtime = g_platform->GetTicks(); if (endtime - starttime > time) { - Log("Warning: " + std::to_string(endtime - starttime) - + " milliseconds spent by " + ballistica::GetCurrentThreadName() - + " thread in " + funcname); + Log(LogLevel::kWarning, + std::to_string(endtime - starttime) + " milliseconds spent by " + + ballistica::GetCurrentThreadName() + " thread in " + funcname); } } @@ -47,8 +47,9 @@ void MacroFunctionTimerEndEx(millisecs_t starttime, millisecs_t time, } millisecs_t endtime = g_platform->GetTicks(); if (endtime - starttime > time) { - Log("Warning: " + std::to_string(endtime - starttime) - + " milliseconds spent in " + funcname + " for " + what); + Log(LogLevel::kWarning, std::to_string(endtime - starttime) + + " milliseconds spent in " + funcname + " for " + + what); } } @@ -62,9 +63,10 @@ void MacroFunctionTimerEndThreadEx(millisecs_t starttime, millisecs_t time, } millisecs_t endtime = g_platform->GetTicks(); if (endtime - starttime > time) { - Log("Warning: " + std::to_string(endtime - starttime) - + " milliseconds spent by " + ballistica::GetCurrentThreadName() - + " thread in " + funcname + " for " + what); + Log(LogLevel::kWarning, std::to_string(endtime - starttime) + + " milliseconds spent by " + + ballistica::GetCurrentThreadName() + + " thread in " + funcname + " for " + what); } } @@ -77,9 +79,9 @@ void MacroTimeCheckEnd(millisecs_t starttime, millisecs_t time, } millisecs_t e = g_platform->GetTicks(); if (e - starttime > time) { - Log(std::string("Warning: ") + name + " took " - + std::to_string(e - starttime) + " milliseconds; " + file + " line " - + std::to_string(line)); + Log(LogLevel::kWarning, + std::string(name) + " took " + std::to_string(e - starttime) + + " milliseconds; " + file + " line " + std::to_string(line)); } } @@ -88,19 +90,19 @@ void MacroLogErrorTrace(const std::string& msg, const char* fname, int line) { snprintf(buffer, sizeof(buffer), "%s:%d:", fname, line); buffer[sizeof(buffer) - 1] = 0; Python::PrintStackTrace(); - Log(std::string(buffer) + " error: " + msg); + Log(LogLevel::kError, std::string(buffer) + " error: " + msg); } void MacroLogError(const std::string& msg, const char* fname, int line) { char e_buffer[2048]; snprintf(e_buffer, sizeof(e_buffer), "%s:%d:", fname, line); e_buffer[sizeof(e_buffer) - 1] = 0; - ballistica::Log(std::string(e_buffer) + " error: " + msg); + Log(LogLevel::kError, std::string(e_buffer) + " error: " + msg); } void MacroLogPythonTrace(const std::string& msg) { Python::PrintStackTrace(); - Log(msg); + Log(LogLevel::kError, msg); } } // namespace ballistica diff --git a/src/ballistica/core/macros.h b/src/ballistica/core/macros.h index 65b81043..85c70105 100644 --- a/src/ballistica/core/macros.h +++ b/src/ballistica/core/macros.h @@ -86,11 +86,11 @@ } \ ((void)0) // (see 'Trailing-semicolon note' at top) -#define BA_LOG_ONCE(msg) \ +#define BA_LOG_ONCE(lvl, msg) \ { \ static bool did_log_here = false; \ if (!did_log_here) { \ - ballistica::Log(msg); \ + ballistica::Log(lvl, msg); \ did_log_here = true; \ } \ } \ @@ -120,12 +120,12 @@ /// Test a condition and simply print a log message if it fails (on both debug /// and release builds) -#define BA_PRECONDITION_LOG(b) \ - { \ - if (!(b)) { \ - Log("Precondition failed: " #b); \ - } \ - } \ +#define BA_PRECONDITION_LOG(b) \ + { \ + if (!(b)) { \ + Log(LogLevel::kError, "Precondition failed: " #b); \ + } \ + } \ ((void)0) // (see 'Trailing-semicolon note' at top) /// Test a condition and abort the program if it fails (on both debug diff --git a/src/ballistica/core/object.cc b/src/ballistica/core/object.cc index dc194534..ee3280ee 100644 --- a/src/ballistica/core/object.cc +++ b/src/ballistica/core/object.cc @@ -54,9 +54,9 @@ void Object::PrintObjects() { assert(count == g_app->object_count); } } - Log(s); + Log(LogLevel::kInfo, s); #else - Log("PrintObjects() only functions in debug builds."); + Log(LogLevel::kInfo, "PrintObjects() only functions in debug builds."); #endif // BA_DEBUG_BUILD } diff --git a/src/ballistica/core/thread.cc b/src/ballistica/core/thread.cc index 805faef9..de2b5985 100644 --- a/src/ballistica/core/thread.cc +++ b/src/ballistica/core/thread.cc @@ -43,26 +43,54 @@ auto Thread::RunLogicThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunLogicThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} + auto Thread::RunAudioThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunAudioThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} auto Thread::RunBGDynamicThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunBGDynamicThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} + auto Thread::RunNetworkWriteThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunNetworkWriteThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} + auto Thread::RunStdInputThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunStdInputThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} auto Thread::RunAssetsThread(void* data) -> int { return static_cast(data)->ThreadMain(); } +auto Thread::RunAssetsThreadP(void* data) -> void* { + static_cast(data)->ThreadMain(); + return nullptr; +} + void Thread::SetPaused(bool paused) { // Can be toggled from the main thread only. assert(std::this_thread::get_id() == g_app->main_thread_id); @@ -213,34 +241,58 @@ Thread::Thread(ThreadTag identifier_in, ThreadSource source) switch (source_) { case ThreadSource::kCreate: { int (*func)(void*); + void* (*funcp)(void*); switch (identifier_) { case ThreadTag::kLogic: func = RunLogicThread; + funcp = RunLogicThreadP; break; case ThreadTag::kAssets: func = RunAssetsThread; + funcp = RunAssetsThreadP; break; case ThreadTag::kMain: // Shouldn't happen; this thread gets wrapped; not launched. throw Exception(); case ThreadTag::kAudio: func = RunAudioThread; + funcp = RunAudioThreadP; break; case ThreadTag::kBGDynamics: func = RunBGDynamicThread; + funcp = RunBGDynamicThreadP; break; case ThreadTag::kNetworkWrite: func = RunNetworkWriteThread; + funcp = RunNetworkWriteThreadP; break; case ThreadTag::kStdin: func = RunStdInputThread; + funcp = RunStdInputThreadP; break; default: throw Exception(); } - // Let 'er rip. - thread_ = new std::thread(func, this); + // Let 'er rip. + + // NOTE: Apple platforms have a default secondary thread stack size + // of 512k which I've found to be insufficient in cases of heavy + // Python recursion or large simulations. It sounds like Windows + // and Android might have 1mb as default; let's try to standardize + // on that across the board. Unfortunately we have to use pthreads + // to get custom stack sizes; std::thread stupidly doesn't support it. + + // FIXME - move this to platform. +#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS || BA_OSTYPE_LINUX + pthread_attr_t attr; + BA_PRECONDITION(pthread_attr_init(&attr) == 0); + BA_PRECONDITION(pthread_attr_setstacksize(&attr, 1024 * 1024) == 0); + pthread_t thread; + pthread_create(&thread, &attr, funcp, this); +#else + new std::thread(func, this); +#endif // Block until the thread is bootstrapped. // (maybe not necessary, but let's be cautious in case we'd @@ -376,8 +428,9 @@ void Thread::LogThreadMessageTally() { writing_tally_ = true; std::unordered_map tally; - Log("Thread message tally (" + std::to_string(thread_messages_.size()) - + " in list):"); + Log(LogLevel::kError, "Thread message tally (" + + std::to_string(thread_messages_.size()) + + " in list):"); for (auto&& m : thread_messages_) { std::string s; switch (m.type) { @@ -411,8 +464,8 @@ void Thread::LogThreadMessageTally() { } int entry = 1; for (auto&& i : tally) { - Log(" #" + std::to_string(entry++) + " (" + std::to_string(i.second) - + "x): " + i.first); + Log(LogLevel::kError, " #" + std::to_string(entry++) + " (" + + std::to_string(i.second) + "x): " + i.first); } writing_tally_ = false; } @@ -445,7 +498,8 @@ void Thread::PushThreadMessage(const ThreadMessage& t) { // Show count periodically. if ((std::this_thread::get_id() == g_app->main_thread_id) && foo > 100) { foo = 0; - Log("MSG COUNT " + std::to_string(thread_messages_.size())); + Log(LogLevel::kInfo, + "MSG COUNT " + std::to_string(thread_messages_.size())); } } @@ -453,8 +507,8 @@ void Thread::PushThreadMessage(const ThreadMessage& t) { static bool sent_error = false; if (!sent_error) { sent_error = true; - Log("Error: ThreadMessage list > 1000 in thread: " - + GetCurrentThreadName()); + Log(LogLevel::kError, + "ThreadMessage list > 1000 in thread: " + GetCurrentThreadName()); LogThreadMessageTally(); } } diff --git a/src/ballistica/core/thread.h b/src/ballistica/core/thread.h index 1a007e1e..d5a63d01 100644 --- a/src/ballistica/core/thread.h +++ b/src/ballistica/core/thread.h @@ -141,11 +141,17 @@ class Thread { // different thread groups makes its easy to see which thread is which // in profilers, backtraces, etc. static auto RunLogicThread(void* data) -> int; + static auto RunLogicThreadP(void* data) -> void*; static auto RunAudioThread(void* data) -> int; + static auto RunAudioThreadP(void* data) -> void*; static auto RunBGDynamicThread(void* data) -> int; + static auto RunBGDynamicThreadP(void* data) -> void*; static auto RunNetworkWriteThread(void* data) -> int; + static auto RunNetworkWriteThreadP(void* data) -> void*; static auto RunStdInputThread(void* data) -> int; + static auto RunStdInputThreadP(void* data) -> void*; static auto RunAssetsThread(void* data) -> int; + static auto RunAssetsThreadP(void* data) -> void*; auto ThreadMain() -> int; auto GetThreadMessages(std::list* messages) -> void; @@ -159,7 +165,7 @@ class Thread { std::list> runnables_; std::list pause_callbacks_; std::list resume_callbacks_; - std::thread* thread_{}; + // std::thread* thread_{}; std::condition_variable thread_message_cv_; std::mutex thread_message_mutex_; std::list thread_messages_; diff --git a/src/ballistica/core/types.h b/src/ballistica/core/types.h index 8792b79f..663d0ad9 100644 --- a/src/ballistica/core/types.h +++ b/src/ballistica/core/types.h @@ -424,6 +424,8 @@ enum class PyExcType { kWidgetNotFound }; +enum class LogLevel { kDebug, kInfo, kWarning, kError, kCritical }; + enum class SystemTextureID { kUIAtlas, kButtonSquare, diff --git a/src/ballistica/dynamics/bg/bg_dynamics.h b/src/ballistica/dynamics/bg/bg_dynamics.h index 934811ad..264b0b26 100644 --- a/src/ballistica/dynamics/bg/bg_dynamics.h +++ b/src/ballistica/dynamics/bg/bg_dynamics.h @@ -45,7 +45,7 @@ class BGDynamicsEmission { BGDynamicsTendrilType tendril_type{BGDynamicsTendrilType::kSmoke}; }; -// client (game thread) functionality for bg dynamics +// client (logic thread) functionality for bg dynamics class BGDynamics { public: BGDynamics(); diff --git a/src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h b/src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h index 625b8f76..777843ae 100644 --- a/src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h +++ b/src/ballistica/dynamics/bg/bg_dynamics_draw_snapshot.h @@ -10,7 +10,7 @@ namespace ballistica { // Big chunk of data sent back from the bg-dynamics server thread -// to the game thread for drawing. +// to the logic thread for drawing. class BGDynamicsDrawSnapshot { public: struct TendrilShadow { diff --git a/src/ballistica/dynamics/bg/bg_dynamics_server.cc b/src/ballistica/dynamics/bg/bg_dynamics_server.cc index f43b4b8d..cbcd8821 100644 --- a/src/ballistica/dynamics/bg/bg_dynamics_server.cc +++ b/src/ballistica/dynamics/bg/bg_dynamics_server.cc @@ -539,12 +539,12 @@ void BGDynamicsServer::ParticleSet::UpdateAndCreateSnapshot( auto* ibuf = Object::NewDeferred(p_count * 6); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. ibuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); *index_buffer = Object::MakeRefCounted(ibuf); auto* vbuf = Object::NewDeferred(p_count * 4); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. vbuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); *buffer = Object::MakeRefCounted(vbuf); @@ -1359,7 +1359,8 @@ void BGDynamicsServer::Emit(const BGDynamicsEmission& def) { } default: { int t = static_cast(def.emit_type); - BA_LOG_ONCE("Invalid bg-dynamics emit type: " + std::to_string(t)); + BA_LOG_ONCE(LogLevel::kError, + "Invalid bg-dynamics emit type: " + std::to_string(t)); break; } } @@ -1596,14 +1597,14 @@ auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* { if (shadow_max_count > 0) { auto* ibuf = Object::NewDeferred(shadow_max_count * 6); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. ibuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->shadow_indices = Object::MakeRefCounted(ibuf); s_index = &ss->shadow_indices->elements[0]; auto* vbuf = Object::NewDeferred(shadow_max_count * 4); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. vbuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->shadow_vertices = Object::MakeRefCounted(vbuf); s_vertex = &ss->shadow_vertices->elements[0]; @@ -1612,14 +1613,14 @@ auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* { if (light_max_count > 0) { auto* ibuf = Object::NewDeferred(light_max_count * 6); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. ibuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->light_indices = Object::MakeRefCounted(ibuf); l_index = &ss->light_indices->elements[0]; auto* vbuf = Object::NewDeferred(light_max_count * 4); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. vbuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->light_vertices = Object::MakeRefCounted(vbuf); l_vertex = &ss->light_vertices->elements[0]; @@ -1968,14 +1969,14 @@ auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* { if (smoke_slice_count > 0) { auto* ibuf = Object::NewDeferred(smoke_index_count); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. ibuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->tendril_indices = Object::MakeRefCounted(ibuf); uint16_t* index = &ss->tendril_indices->elements[0]; auto* vbuf = Object::NewDeferred(smoke_slice_count * 2); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. vbuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->tendril_vertices = Object::MakeRefCounted(vbuf); VertexSmokeFull* v = &ss->tendril_vertices->elements[0]; @@ -2133,13 +2134,13 @@ auto BGDynamicsServer::CreateDrawSnapshot() -> BGDynamicsDrawSnapshot* { auto* ibuf = Object::NewDeferred(index_count); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. ibuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->fuse_indices = Object::MakeRefCounted(ibuf); auto* vbuf = Object::NewDeferred(vertex_count); - // Game thread is default owner; needs to be us until we hand it over. + // Logic thread is default owner; needs to be us until we hand it over. vbuf->SetThreadOwnership(Object::ThreadOwnership::kNextReferencing); ss->fuse_vertices = Object::MakeRefCounted(vbuf); diff --git a/src/ballistica/dynamics/dynamics.cc b/src/ballistica/dynamics/dynamics.cc index c5e69ed9..3b9aa0b1 100644 --- a/src/ballistica/dynamics/dynamics.cc +++ b/src/ballistica/dynamics/dynamics.cc @@ -141,7 +141,8 @@ Dynamics::Dynamics(Scene* scene_in) Dynamics::~Dynamics() { if (in_process_) { - Log("Error: Dynamics going down within Process() call;" + Log(LogLevel::kError, + "Dynamics going down within Process() call;" " should not happen."); } ShutdownODE(); diff --git a/src/ballistica/dynamics/material/material_component.cc b/src/ballistica/dynamics/material/material_component.cc index e211ff15..76f4ace9 100644 --- a/src/ballistica/dynamics/material/material_component.cc +++ b/src/ballistica/dynamics/material/material_component.cc @@ -213,8 +213,9 @@ void MaterialComponent::Restore(const char** buffer, ClientSession* cs) { action = Object::New(); break; default: - Log("Error: Invalid material action: '" - + std::to_string(static_cast(type)) + "'."); + Log(LogLevel::kError, "Invalid material action: '" + + std::to_string(static_cast(type)) + + "'."); throw Exception(); } action->Restore(buffer, cs); diff --git a/src/ballistica/dynamics/part.cc b/src/ballistica/dynamics/part.cc index 2b7436b3..17263e5e 100644 --- a/src/ballistica/dynamics/part.cc +++ b/src/ballistica/dynamics/part.cc @@ -97,7 +97,8 @@ void Part::SetCollidingWith(int64_t node_id, int part, bool colliding, for (auto&& i : collisions_) { if (i.node == node_id && i.part == part) { BA_PRECONDITION(node()); - Log("Error: Got SetCollidingWith for part already colliding with."); + Log(LogLevel::kError, + "Got SetCollidingWith for part already colliding with."); return; } } @@ -117,7 +118,8 @@ void Part::SetCollidingWith(int64_t node_id, int part, bool colliding, return; } } - Log("Error: Got SetCollidingWith (separated) call for part we're " + Log(LogLevel::kError, + "Got SetCollidingWith (separated) call for part we're " "not colliding with."); } } diff --git a/src/ballistica/dynamics/rigid_body.cc b/src/ballistica/dynamics/rigid_body.cc index 2bbf37f3..2e795adb 100644 --- a/src/ballistica/dynamics/rigid_body.cc +++ b/src/ballistica/dynamics/rigid_body.cc @@ -168,7 +168,7 @@ auto RigidBody::Check() -> void { if (std::isnan(q[3])) err = true; if (err) { - Log("Error: Got error in rbd values!"); + Log(LogLevel::kError, "Got error in rbd values!"); } #if BA_DEBUG_BUILD for (int i = 0; i < 3; i++) { diff --git a/src/ballistica/generic/timer_list.cc b/src/ballistica/generic/timer_list.cc index 533b6c0b..b92b8b5d 100644 --- a/src/ballistica/generic/timer_list.cc +++ b/src/ballistica/generic/timer_list.cc @@ -20,14 +20,14 @@ TimerList::~TimerList() { if (g_buildconfig.debug_build()) { if (timer_count_active_ != 0) { - Log("Error: Invalid timerlist state on teardown."); + Log(LogLevel::kError, "Invalid timerlist state on teardown."); } if (timer_count_inactive_ != 0) { - Log("Error: Invalid timerlist state on teardown."); + Log(LogLevel::kError, "Invalid timerlist state on teardown."); } if (!((timer_count_total_ == 0) || (client_timer_ != nullptr && timer_count_total_ == 1))) { - Log("Error: Invalid timerlist state on teardown."); + Log(LogLevel::kError, "Invalid timerlist state on teardown."); } } } diff --git a/src/ballistica/generic/utils.cc b/src/ballistica/generic/utils.cc index a7f341d1..2ac25a04 100644 --- a/src/ballistica/generic/utils.cc +++ b/src/ballistica/generic/utils.cc @@ -136,9 +136,10 @@ Utils::Utils() { // it was parsed from. Use this to adjust the filtering as necessary so // the resulting type name matches what is expected. if (explicit_bool(false)) { - Log("static_type_name check; name is '" - + static_type_name() + "' debug_full is '" - + static_type_name(true) + "'"); + Log(LogLevel::kError, + "static_type_name check; name is '" + + static_type_name() + "' debug_full is '" + + static_type_name(true) + "'"); } // We now bake these in so they match across platforms... @@ -261,8 +262,9 @@ auto Utils::GetValidUTF8(const char* str, const char* loc) -> std::string { } } logged_count++; - Log("GOT INVALID UTF8 SEQUENCE: (" + log_str + "); RETURNING '" + to - + "'; LOC '" + loc + "'"); + Log(LogLevel::kError, "GOT INVALID UTF8 SEQUENCE: (" + log_str + + "); RETURNING '" + to + "'; LOC '" + loc + + "'"); } } else { diff --git a/src/ballistica/graphics/component/render_component.h b/src/ballistica/graphics/component/render_component.h index 08ac4fea..a85f9bff 100644 --- a/src/ballistica/graphics/component/render_component.h +++ b/src/ballistica/graphics/component/render_component.h @@ -15,7 +15,8 @@ class RenderComponent { : state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {} ~RenderComponent() { if (state_ != State::kSubmitted) { - Log("Error: RenderComponent dying without submit() having been called."); + Log(LogLevel::kError, + "RenderComponent dying without submit() having been called."); } } void DrawModel(ModelData* model, uint32_t flags = 0) { diff --git a/src/ballistica/graphics/frame_def.h b/src/ballistica/graphics/frame_def.h index 8e794ea0..90db34ae 100644 --- a/src/ballistica/graphics/frame_def.h +++ b/src/ballistica/graphics/frame_def.h @@ -12,8 +12,8 @@ namespace ballistica { -/// A flattened representation of a frame; generated by the game thread and sent -/// to the graphics thread to render. +/// A flattened representation of a frame; generated by the logic thread and +/// sent to the graphics thread to render. class FrameDef { public: auto light_pass() -> RenderPass* { return light_pass_.get(); } diff --git a/src/ballistica/graphics/gl/gl_sys.cc b/src/ballistica/graphics/gl/gl_sys.cc index 7709f2af..221a5e35 100644 --- a/src/ballistica/graphics/gl/gl_sys.cc +++ b/src/ballistica/graphics/gl/gl_sys.cc @@ -315,7 +315,7 @@ void GLContext::SetVSync(bool enable) { GLContext::~GLContext() { if (!InMainThread()) { - Log("Error: GLContext dying in non-graphics thread"); + Log(LogLevel::kError, "GLContext dying in non-graphics thread"); } #if BA_SDL2_BUILD diff --git a/src/ballistica/graphics/gl/renderer_gl.cc b/src/ballistica/graphics/gl/renderer_gl.cc index 7df0554e..5b0ccef3 100644 --- a/src/ballistica/graphics/gl/renderer_gl.cc +++ b/src/ballistica/graphics/gl/renderer_gl.cc @@ -144,10 +144,10 @@ static void _check_gl_error(int line) { const char* version = (const char*)glGetString(GL_VERSION); const char* vendor = (const char*)glGetString(GL_VENDOR); const char* renderer = (const char*)glGetString(GL_RENDERER); - Log("Error: OpenGL Error at line " + std::to_string(line) + ": " - + GLErrorToString(err) + "\nrenderer: " + renderer - + "\nvendor: " + vendor + "\nversion: " + version - + "\ntime: " + std::to_string(GetRealTime())); + Log(LogLevel::kError, "OpenGL Error at line " + std::to_string(line) + ": " + + GLErrorToString(err) + "\nrenderer: " + renderer + + "\nvendor: " + vendor + "\nversion: " + version + + "\ntime: " + std::to_string(GetRealTime())); } } @@ -256,16 +256,16 @@ void RendererGL::CheckGLExtensions() { // if we require ES3 if (have_es3) { g_running_es3 = true; - Log(std::string("Using OpenGL ES 3 (vendor: ") + vendor - + ", renderer: " + renderer + ", version: " + version_str + ")", - false, false); + Log(LogLevel::kInfo, std::string("Using OpenGL ES 3 (vendor: ") + vendor + + ", renderer: " + renderer + + ", version: " + version_str + ")"); } else { #if !BA_USE_ES3_INCLUDES g_running_es3 = false; - Log(std::string("USING OPENGL ES2 (vendor: ") + vendor - + ", renderer: " + renderer + ", version: " + version_str + ")", - false, false); + Log(LogLevel::kInfo, std::string("USING OPENGL ES2 (vendor: ") + vendor + + ", renderer: " + renderer + + ", version: " + version_str + ")"); // Can still support some stuff like framebuffer-blit with es2 extensions. assert(glBlitFramebuffer == nullptr || !first_extension_check_); @@ -381,7 +381,7 @@ void RendererGL::CheckGLExtensions() { c_types.push_back(TextureCompressionType::kETC1); } else { #if BA_OSTYPE_ANDROID - Log("Android device missing ETC1 support"); + Log(LogLevel::kError, "Android device missing ETC1 support"); #endif } @@ -509,7 +509,7 @@ void RendererGL::CheckGLExtensions() { &samples[0]); g_msaa_max_samples_rgb565 = samples[0]; } else { - BA_LOG_ONCE("Got 0 samplecounts for RGB565"); + BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB565"); g_msaa_max_samples_rgb565 = 0; } } @@ -525,7 +525,7 @@ void RendererGL::CheckGLExtensions() { &samples[0]); g_msaa_max_samples_rgb8 = samples[0]; } else { - BA_LOG_ONCE("Got 0 samplecounts for RGB8"); + BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB8"); g_msaa_max_samples_rgb8 = 0; } } @@ -539,10 +539,10 @@ void RendererGL::CheckGLExtensions() { #if MSAA_ERROR_TEST if (enable_msaa_) { ScreenMessage("MSAA ENABLED"); - Log("Ballistica MSAA Test: MSAA ENABLED", false, false); + Log(LogLevel::kInfo, "Ballistica MSAA Test: MSAA ENABLED"); } else { ScreenMessage("MSAA DISABLED"); - Log("Ballistica MSAA Test: MSAA DISABLED", false, false); + Log(LogLevel::kInfo, "Ballistica MSAA Test: MSAA DISABLED"); } #endif // MSAA_ERROR_TEST @@ -1023,11 +1023,12 @@ class RendererGL::ShaderGL : public Object { const char* renderer = (const char*)glGetString(GL_RENDERER); // Let's not crash here. We have a better chance of calling home this way // and theres a chance the game will still be playable. - Log(std::string("Compile failed for ") + GetTypeName() - + " shader:\n------------SOURCE BEGIN-------------\n" + src_in - + "\n-----------SOURCE END-------------\n" + GetInfo() - + "\nrenderer: " + renderer + "\nvendor: " + vendor - + "\nversion:" + version); + Log(LogLevel::kError, + std::string("Compile failed for ") + GetTypeName() + + " shader:\n------------SOURCE BEGIN-------------\n" + src_in + + "\n-----------SOURCE END-------------\n" + GetInfo() + + "\nrenderer: " + renderer + "\nvendor: " + vendor + + "\nversion:" + version); } else { assert(compile_status == GL_TRUE); std::string info = GetInfo(); @@ -1038,10 +1039,12 @@ class RendererGL::ShaderGL : public Object { const char* version = (const char*)glGetString(GL_VERSION); const char* vendor = (const char*)glGetString(GL_VENDOR); const char* renderer = (const char*)glGetString(GL_RENDERER); - Log(std::string("WARNING: info returned for ") + GetTypeName() - + " shader:\n------------SOURCE BEGIN-------------\n" + src_in - + "\n-----------SOURCE END-------------\n" + info + "\nrenderer: " - + renderer + "\nvendor: " + vendor + "\nversion:" + version); + Log(LogLevel::kError, + std::string("WARNING: info returned for ") + GetTypeName() + + " shader:\n------------SOURCE BEGIN-------------\n" + src_in + + "\n-----------SOURCE END-------------\n" + info + + "\nrenderer: " + renderer + "\nvendor: " + vendor + + "\nversion:" + version); } } DEBUG_CHECK_GL_ERROR; @@ -1131,7 +1134,8 @@ class RendererGL::ProgramGL { GLint linkStatus; glGetProgramiv(program_, GL_LINK_STATUS, &linkStatus); if (linkStatus == GL_FALSE) { - Log("Link failed for program '" + name_ + "':\n" + GetInfo()); + Log(LogLevel::kError, + "Link failed for program '" + name_ + "':\n" + GetInfo()); } else { assert(linkStatus == GL_TRUE); @@ -1140,8 +1144,8 @@ class RendererGL::ProgramGL { && (strstr(info.c_str(), "error:") || strstr(info.c_str(), "warning:") || strstr(info.c_str(), "Error:") || strstr(info.c_str(), "Warning:"))) { - Log("WARNING: program using frag shader '" + name_ - + "' returned info:\n" + info); + Log(LogLevel::kError, "WARNING: program using frag shader '" + name_ + + "' returned info:\n" + info); } } @@ -1278,8 +1282,9 @@ class RendererGL::ProgramGL { int c = glGetUniformLocation(program_, tex_name); if (c == -1) { #if !MSAA_ERROR_TEST - Log("Error: ShaderGL: " + name_ + ": Can't set texture unit for texture '" - + tex_name + "'"); + Log(LogLevel::kError, "ShaderGL: " + name_ + + ": Can't set texture unit for texture '" + + tex_name + "'"); DEBUG_CHECK_GL_ERROR; #endif } else { @@ -1511,7 +1516,8 @@ class RendererGL::SimpleProgramGL : public RendererGL::ProgramGL { "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -1613,7 +1619,8 @@ class RendererGL::SimpleProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } float r_{}, g_{}, b_{}, a_{}; @@ -1846,7 +1853,8 @@ class RendererGL::ObjectProgramGL : public RendererGL::ProgramGL { } s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -1918,7 +1926,8 @@ class RendererGL::ObjectProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } float r_, g_, b_, a_; @@ -2035,7 +2044,8 @@ class RendererGL::SmokeProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -2077,7 +2087,8 @@ class RendererGL::SmokeProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } float r_, g_, b_, a_; @@ -2164,7 +2175,8 @@ class RendererGL::BlurProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -2198,7 +2210,8 @@ class RendererGL::BlurProgramGL : public RendererGL::ProgramGL { " + texture2D(colorTex,vUV8));\n" "}"; if (flags & SHD_DEBUG_PRINT) { - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); } return s; } @@ -2248,7 +2261,8 @@ class RendererGL::ShieldProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -2301,7 +2315,8 @@ class RendererGL::ShieldProgramGL : public RendererGL::ProgramGL { //" gl_FragColor = vec4(vec3(depth),1);\n" if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } @@ -2510,7 +2525,8 @@ class RendererGL::PostProcessProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -2636,7 +2652,8 @@ class RendererGL::PostProcessProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } #endif // msaa bug test @@ -2751,7 +2768,8 @@ class RendererGL::SpriteProgramGL : public RendererGL::ProgramGL { s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s); return s; } auto GetFragmentCode(int flags) -> std::string { @@ -2787,7 +2805,8 @@ class RendererGL::SpriteProgramGL : public RendererGL::ProgramGL { } s += "}"; if (flags & SHD_DEBUG_PRINT) - Log("\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); + Log(LogLevel::kInfo, + "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s); return s; } float r_, g_, b_, a_; @@ -2808,7 +2827,7 @@ class RendererGL::TextureDataGL : public TextureRendererData { ~TextureDataGL() override { if (!InGraphicsThread()) { - Log("Error: TextureDataGL dying outside of graphics thread."); + Log(LogLevel::kError, "TextureDataGL dying outside of graphics thread."); } else { // if we're currently bound as anything, clear that out // (otherwise a new texture with that same ID won't be bindable) @@ -3272,6 +3291,7 @@ class RendererGL::ModelDataGL : public ModelRendererData { } case 4: { BA_LOG_ONCE( + LogLevel::kWarning, "GL WARNING - USING 32 BIT INDICES WHICH WONT WORK IN ES2!!"); elem_count_ = static_cast(model.indices32().size()); index_type_ = GL_UNSIGNED_INT; @@ -3473,7 +3493,8 @@ class RendererGL::MeshDataGL : public MeshRendererData { dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); index_state_ = data->state; have_index_data_ = true; - BA_LOG_ONCE("GL WARNING - USING 32 BIT INDICES WHICH WONT WORK IN ES2!!"); + BA_LOG_ONCE(LogLevel::kWarning, + "GL WARNING - USING 32 BIT INDICES WHICH WONT WORK IN ES2!!"); index_type_ = GL_UNSIGNED_INT; } } @@ -3889,6 +3910,7 @@ class RendererGL::RenderTargetGL : public RenderTarget { // this needs to be on for glClear to work on depth. if (!renderer_->depth_writing_enabled_) { BA_LOG_ONCE( + LogLevel::kWarning, "RendererGL: depth-writing not enabled when clearing depth"); } clear_mask |= GL_DEPTH_BUFFER_BIT; @@ -5743,7 +5765,8 @@ void RendererGL::UpdateVignetteTex(bool force) { if (err != GL_NO_ERROR) { static bool reported = false; if (!reported) { - Log("Error: 32-bit vignette creation failed; falling back to 16."); + Log(LogLevel::kError, + "32-bit vignette creation failed; falling back to 16."); reported = true; } const int kVignetteTexWidth = 64; @@ -5800,14 +5823,15 @@ void RendererGL::UpdateVignetteTex(bool force) { auto RendererGL::GetFunkyDepthIssue() -> bool { if (!funky_depth_issue_set_) { - BA_LOG_ONCE("fetching funky depth issue but not set"); + BA_LOG_ONCE(LogLevel::kError, "fetching funky depth issue but not set"); } return funky_depth_issue_; } auto RendererGL::GetDrawsShieldsFunny() -> bool { if (!draws_shields_funny_set_) { - BA_LOG_ONCE("fetching draws-shields-funny value but not set"); + BA_LOG_ONCE(LogLevel::kError, + "fetching draws-shields-funny value but not set"); } return draws_shields_funny_; } diff --git a/src/ballistica/graphics/graphics.cc b/src/ballistica/graphics/graphics.cc index ac573e62..d2fd216d 100644 --- a/src/ballistica/graphics/graphics.cc +++ b/src/ballistica/graphics/graphics.cc @@ -877,7 +877,8 @@ void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) { // (otherwise, overlapping fades can cause things to get lost) if (fade_end_call_.exists()) { if (g_buildconfig.debug_build()) { - Log("WARNING: 2 fades overlapping; running first fade-end-call early"); + Log(LogLevel::kWarning, + "2 fades overlapping; running first fade-end-call early"); } g_logic->PushPythonCall(fade_end_call_); fade_end_call_.Clear(); @@ -1070,6 +1071,7 @@ void Graphics::BuildAndPushFrameDef() { if (frame_def->GetOverlayFlatPass()->HasDrawCommands()) { if (!g_ui->IsWindowPresent()) { BA_LOG_ONCE( + LogLevel::kError, "Drawing in overlay pass in VR mode without UI; shouldn't " "happen!"); } @@ -1213,7 +1215,7 @@ void Graphics::DrawFades(FrameDef* frame_def, millisecs_t real_time) { if (fade_ <= 0.0f && fade_out_) { millisecs_t faded_time = real_time - (fade_start_ + fade_time_); if (faded_time > 15000) { - Log("FORCE-ENDING STUCK FADE"); + Log(LogLevel::kError, "FORCE-ENDING STUCK FADE"); fade_out_ = false; fade_ = 1.0f; fade_time_ = 1000; @@ -1503,6 +1505,7 @@ void Graphics::WaitForRendererToExist() { while (g_graphics_server == nullptr || g_graphics_server->renderer() == nullptr) { BA_LOG_ONCE( + LogLevel::kWarning, "BuildAndPushFrameDef() called before renderer is up; spinning..."); Platform::SleepMS(100); sleep_count++; diff --git a/src/ballistica/graphics/graphics.h b/src/ballistica/graphics/graphics.h index 4c6b02cb..8d8c309f 100644 --- a/src/ballistica/graphics/graphics.h +++ b/src/ballistica/graphics/graphics.h @@ -43,7 +43,7 @@ const float kBackingDepth1 = 0.0f; const float kShadowNeutral = 0.5f; -// Client class for graphics operations (used from the game thread). +// Client class for graphics operations (used from the logic thread). class Graphics { public: Graphics(); diff --git a/src/ballistica/graphics/graphics_server.cc b/src/ballistica/graphics/graphics_server.cc index 9cabd091..f0abe83f 100644 --- a/src/ballistica/graphics/graphics_server.cc +++ b/src/ballistica/graphics/graphics_server.cc @@ -50,7 +50,7 @@ void GraphicsServer::SetRenderHold() { void GraphicsServer::SetFrameDef(FrameDef* framedef) { // Note: we're just setting the framedef directly here - // even though this gets called from the game thread. + // even though this gets called from the logic thread. // Ideally it would seem we should push these to our thread // event list, but currently we spin-lock waiting for new // frames to appear which would prevent that from working; @@ -77,15 +77,15 @@ auto GraphicsServer::GetRenderFrameDef() -> FrameDef* { g_assets->RunPendingGraphicsLoads(); // Spin and wait for a short bit for a frame_def to appear. If it does, we - // grab it, render it, and also message the game thread to start generating + // grab it, render it, and also message the logic thread to start generating // another one. while (true) { if (frame_def_) { FrameDef* frame_def = frame_def_; frame_def_ = nullptr; - // Tell the game thread we're ready for the next frame_def so it can start - // building it while we render this one. + // Tell the logic thread we're ready for the next frame_def so it can + // start building it while we render this one. g_logic->PushFrameDefRequest(); return frame_def; } @@ -173,7 +173,7 @@ void GraphicsServer::TryRender() { FinishRenderFrameDef(frame_def); } - // Send this frame_def back to the game thread for deletion. + // Send this frame_def back to the logic thread for deletion. g_graphics->ReturnCompletedFrameDef(frame_def); } } @@ -194,7 +194,7 @@ void GraphicsServer::ReloadMedia() { assert(g_graphics_server); SetRenderHold(); - // Now tell the game thread to kick off loads for everything, flip on + // Now tell the logic thread to kick off loads for everything, flip on // progress bar drawing, and then tell the graphics thread to stop ignoring // frame-defs. g_logic->thread()->PushCall([this] { @@ -209,7 +209,7 @@ void GraphicsServer::RebuildLostContext() { assert(InGraphicsThread()); if (!renderer_) { - Log("Error: No renderer on GraphicsServer::_rebuildContext."); + Log(LogLevel::kError, "No renderer on GraphicsServer::_rebuildContext."); return; } @@ -246,8 +246,9 @@ void GraphicsServer::RebuildLostContext() { // we won't hitch if we actually render them.) SetRenderHold(); - // 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. + // Now tell the logic thread to kick off loads for everything, flip on + // progress bar drawing, and then tell the graphics thread to stop ignoring + // frame-defs. g_logic->thread()->PushCall([this] { g_assets->MarkAllAssetsForLoad(); g_graphics->EnableProgressBar(false); @@ -351,7 +352,7 @@ void GraphicsServer::SetScreen(bool fullscreen, int width, int height, } // The first time we complete setting up our screen, we send a message - // back to the game thread to complete the init process.. (they can't start + // back to the logic thread to complete the init process.. (they can't start // loading graphics and things until we have our context set up so we know // what types of textures to load, etc) if (!initial_screen_created_) { @@ -402,7 +403,7 @@ void GraphicsServer::HandleFullContextScreenRebuild( UpdateVirtualScreenRes(); - // Inform the game thread of the latest values. + // Inform the logic thread of the latest values. g_logic->PushScreenResizeCall(res_x_virtual_, res_y_virtual_, res_x_, res_y_); } @@ -459,7 +460,7 @@ void GraphicsServer::HandleFullContextScreenRebuild( // so we won't hitch if we actually render them.) SetRenderHold(); - // Now tell the game thread to kick off loads for everything, flip on + // Now tell the logic thread to kick off loads for everything, flip on // progress bar drawing, and then tell the graphics thread to stop ignoring // frame-defs. g_logic->thread()->PushCall([this] { @@ -513,7 +514,7 @@ void GraphicsServer::VideoResize(float h, float v) { res_y_ = v; UpdateVirtualScreenRes(); - // Inform the game thread of the latest values. + // Inform the logic thread of the latest values. g_logic->PushScreenResizeCall(res_x_virtual_, res_y_virtual_, res_x_, res_y_); if (renderer_) { renderer_->ScreenSizeChanged(); @@ -758,7 +759,7 @@ void GraphicsServer::PushSetVSyncCall(bool sync, bool auto_sync) { gl_context_->SetVSync(v_sync_); } } else { - Log("Error: Got SetVSyncCall with no gl context."); + Log(LogLevel::kError, "Got SetVSyncCall with no gl context."); } } } @@ -773,8 +774,8 @@ void GraphicsServer::PushComponentUnloadCall( for (auto&& i : components) { (**i).Unload(); } - // ..and then ship these pointers back to the game thread so it can free the - // references. + // ..and then ship these pointers back to the logic thread so it can free + // the references. g_logic->PushFreeAssetComponentRefsCall(components); }); } @@ -784,7 +785,7 @@ void GraphicsServer::PushRemoveRenderHoldCall() { assert(render_hold_); render_hold_--; if (render_hold_ < 0) { - Log("Error: RenderHold < 0"); + Log(LogLevel::kError, "RenderHold < 0"); render_hold_ = 0; } }); diff --git a/src/ballistica/graphics/graphics_server.h b/src/ballistica/graphics/graphics_server.h index aaa590fa..061da29c 100644 --- a/src/ballistica/graphics/graphics_server.h +++ b/src/ballistica/graphics/graphics_server.h @@ -32,7 +32,7 @@ class GraphicsServer { const std::vector*>& components) -> void; auto SetRenderHold() -> void; - // Used by the game thread to pass frame-defs to the graphics server + // Used by the logic thread to pass frame-defs to the graphics server // for rendering. auto SetFrameDef(FrameDef* framedef) -> void; diff --git a/src/ballistica/graphics/mesh/mesh_buffer_base.h b/src/ballistica/graphics/mesh/mesh_buffer_base.h index 0a498f49..04f264a6 100644 --- a/src/ballistica/graphics/mesh/mesh_buffer_base.h +++ b/src/ballistica/graphics/mesh/mesh_buffer_base.h @@ -7,10 +7,10 @@ namespace ballistica { -// Buffers used by the game thread to pass indices/vertices/etc. to meshes in +// Buffers used by the logic thread to pass indices/vertices/etc. to meshes in // the graphics thread. Note that it is safe to create these in other threads; // you just need to turn off thread-checks until you pass ownership to the game -// thread. (or just avoid creating references outside of the game thread) +// thread. (or just avoid creating references outside of the logic thread) class MeshBufferBase : public Object { public: uint32_t state; // which dynamicState value on the mesh this corresponds to diff --git a/src/ballistica/graphics/mesh/mesh_data.h b/src/ballistica/graphics/mesh/mesh_data.h index 1dd956c9..016b98c1 100644 --- a/src/ballistica/graphics/mesh/mesh_data.h +++ b/src/ballistica/graphics/mesh/mesh_data.h @@ -17,7 +17,7 @@ class MeshData { : type_(type), draw_type_(draw_type) {} virtual ~MeshData() { if (renderer_data_) { - Log("Error: MeshData going down with rendererData intact!"); + Log(LogLevel::kError, "MeshData going down with rendererData intact!"); } } std::list::iterator iterator_; diff --git a/src/ballistica/graphics/mesh/mesh_indexed_base.h b/src/ballistica/graphics/mesh/mesh_indexed_base.h index e8daed0c..5b372252 100644 --- a/src/ballistica/graphics/mesh/mesh_indexed_base.h +++ b/src/ballistica/graphics/mesh/mesh_indexed_base.h @@ -76,9 +76,10 @@ class MeshIndexedBase : public Mesh { // For use by subclasses in their IsValid() overrides auto IndexSizeIsValid(size_t data_size) const -> bool { if (index_data_size() == 2 && data_size > 65535) { - BA_LOG_ONCE("ERROR: got mesh data with > 65535 elems and 16 bit indices: " - + GetObjectDescription() - + ". This case requires 32 bit indices."); + BA_LOG_ONCE(LogLevel::kError, + "Got mesh data with > 65535 elems and 16 bit indices: " + + GetObjectDescription() + + ". This case requires 32 bit indices."); return false; } return true; diff --git a/src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h b/src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h index a1aeff0a..eb792a64 100644 --- a/src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h +++ b/src/ballistica/graphics/mesh/mesh_indexed_static_dynamic.h @@ -32,7 +32,8 @@ class MeshIndexedStaticDynamic : public MeshIndexedBase { // Static and dynamic data sizes should always match, right? if (static_data_->elements.size() != dynamic_data_->elements.size()) { - BA_LOG_ONCE("ERROR: mesh static and dynamic data sizes do not match"); + BA_LOG_ONCE(LogLevel::kError, + "Mesh static and dynamic data sizes do not match"); return false; } diff --git a/src/ballistica/graphics/text/text_graphics.cc b/src/ballistica/graphics/text/text_graphics.cc index c0153431..5e045591 100644 --- a/src/ballistica/graphics/text/text_graphics.cc +++ b/src/ballistica/graphics/text/text_graphics.cc @@ -331,7 +331,7 @@ TextGraphics::TextGraphics() { if (g.tex_max_x > 1.0f || g.tex_max_x < 0.0f || g.tex_min_x > 1.0 || g.tex_min_x < 0.0f || g.tex_max_y > 1.0f || g.tex_max_y < 0.0 || g.tex_min_y > 1.0f || g.tex_min_y < 0.0f) { - BA_LOG_ONCE("Warning: glyph bounds error"); + BA_LOG_ONCE(LogLevel::kWarning, "glyph bounds error"); } } } @@ -1021,6 +1021,7 @@ void TextGraphics::GetOSTextSpanBoundsAndWidth(const std::string& s, Rect* r, g_platform->GetTextBoundsAndWidth(s, &entry->r, &entry->width); } else { BA_LOG_ONCE( + LogLevel::kError, "FIXME: GetOSTextSpanBoundsAndWidth unimplemented on this platform"); r->l = 0.0f; r->r = 1.0f; diff --git a/src/ballistica/graphics/text/text_graphics.h b/src/ballistica/graphics/text/text_graphics.h index e0ff8bda..c293da30 100644 --- a/src/ballistica/graphics/text/text_graphics.h +++ b/src/ballistica/graphics/text/text_graphics.h @@ -19,7 +19,7 @@ namespace ballistica { const int kTextMaxUnicodeVal = 999999; const float kTextRowHeight = 32.0f; -// Encapsulates text-display functionality used by the game thread. +// Encapsulates text-display functionality used by the logic thread. class TextGraphics { public: TextGraphics(); diff --git a/src/ballistica/input/device/client_input_device.cc b/src/ballistica/input/device/client_input_device.cc index 378292d0..d146c1a1 100644 --- a/src/ballistica/input/device/client_input_device.cc +++ b/src/ballistica/input/device/client_input_device.cc @@ -27,7 +27,8 @@ auto ClientInputDevice::GetClientID() const -> int { if (ConnectionToClient* c = connection_to_client_.get()) { return c->id(); } else { - Log("ClientInputDevice::get_client_id(): connection_to_client no longer " + Log(LogLevel::kError, + "ClientInputDevice::get_client_id(): connection_to_client no longer " "exists; returning -1.."); return -1; } diff --git a/src/ballistica/input/device/input_device.cc b/src/ballistica/input/device/input_device.cc index b1ff66bd..9c7f4859 100644 --- a/src/ballistica/input/device/input_device.cc +++ b/src/ballistica/input/device/input_device.cc @@ -139,13 +139,15 @@ InputDevice::~InputDevice() { // when the host-session tells us to attach to a player void InputDevice::AttachToLocalPlayer(Player* player) { if (player_.exists()) { - Log("Error: InputDevice::AttachToLocalPlayer() called with already " + Log(LogLevel::kError, + "InputDevice::AttachToLocalPlayer() called with already " "existing " "player"); return; } if (remote_player_.exists()) { - Log("Error: InputDevice::AttachToLocalPlayer() called with already " + Log(LogLevel::kError, + "InputDevice::AttachToLocalPlayer() called with already " "existing " "remote-player"); return; @@ -158,13 +160,15 @@ void InputDevice::AttachToRemotePlayer(ConnectionToHost* connection_to_host, int remote_player_id) { assert(connection_to_host); if (player_.exists()) { - Log("Error: InputDevice::AttachToRemotePlayer()" + Log(LogLevel::kError, + "InputDevice::AttachToRemotePlayer()" " called with already existing " "player"); return; } if (remote_player_.exists()) { - Log("Error: InputDevice::AttachToRemotePlayer()" + Log(LogLevel::kError, + "InputDevice::AttachToRemotePlayer()" " called with already existing " "remote-player"); return; @@ -180,7 +184,8 @@ void InputDevice::RemoveRemotePlayerFromGame() { data[1] = static_cast_check_fit(index()); connection_to_host->SendReliableMessage(data); } else { - Log("Error: RemoveRemotePlayerFromGame called without remote player"); + Log(LogLevel::kError, + "RemoveRemotePlayerFromGame called without remote player"); } } @@ -210,12 +215,14 @@ void InputDevice::RequestPlayer() { last_input_time_ = g_logic->master_time(); if (player_.exists()) { - Log("Error: InputDevice::RequestPlayer()" + Log(LogLevel::kError, + "InputDevice::RequestPlayer()" " called with already-existing player"); return; } if (remote_player_.exists()) { - Log("Error: InputDevice::RequestPlayer() called with already-existing " + Log(LogLevel::kError, + "InputDevice::RequestPlayer() called with already-existing " "remote-player"); return; } diff --git a/src/ballistica/input/device/input_device.h b/src/ballistica/input/device/input_device.h index b9559fd2..012efa0c 100644 --- a/src/ballistica/input/device/input_device.h +++ b/src/ballistica/input/device/input_device.h @@ -13,8 +13,8 @@ namespace ballistica { /// Base class for game input devices (keyboard, joystick, etc). /// InputDevices can be allocated in any thread (generally on the main /// thread in response to some system event). An AddInputDevice() call -/// should then be pushed to the game thread to inform it of the new device. -/// Deletion of the input-device is then handled by the game thread +/// should then be pushed to the logic thread to inform it of the new device. +/// Deletion of the input-device is then handled by the logic thread /// and can be triggered by pushing a RemoveInputDevice() call to it. class InputDevice : public Object { public: diff --git a/src/ballistica/input/device/joystick.cc b/src/ballistica/input/device/joystick.cc index 5ee52393..2af36549 100644 --- a/src/ballistica/input/device/joystick.cc +++ b/src/ballistica/input/device/joystick.cc @@ -288,7 +288,7 @@ auto Joystick::GetButtonName(int index) -> std::string { Joystick::~Joystick() { if (!InLogicThread()) { - Log("Error: Joystick dying in wrong thread."); + Log(LogLevel::kError, "Joystick dying in wrong thread."); } // Kill our child if need be. @@ -301,7 +301,7 @@ Joystick::~Joystick() { // Send a message back to the main thread to close this SDL Joystick. // HMMM - can we just have the main thread close the joystick immediately // before informing us its dead?.. i don't think we actually use it at all - // here in the game thread.. + // here in the logic thread.. if (sdl_joystick_) { #if BA_ENABLE_SDL_JOYSTICKS assert(g_app_flavor); @@ -310,7 +310,8 @@ Joystick::~Joystick() { [joystick] { SDL_JoystickClose(joystick); }); sdl_joystick_ = nullptr; #else - Log("sdl_joystick_ set in non-sdl-joystick build destructor."); + Log(LogLevel::kError, + "sdl_joystick_ set in non-sdl-joystick build destructor."); #endif // BA_ENABLE_SDL_JOYSTICKS } } @@ -692,8 +693,9 @@ void Joystick::HandleSDLEvent(const SDL_Event* e) { hat_held_ = true; break; default: - BA_LOG_ONCE("Error: Invalid hat value: " - + std::to_string(static_cast(e->jhat.value))); + BA_LOG_ONCE(LogLevel::kError, + "Invalid hat value: " + + std::to_string(static_cast(e->jhat.value))); break; } } diff --git a/src/ballistica/input/device/touch_input.cc b/src/ballistica/input/device/touch_input.cc index 6ecb4dc7..a76bfa0c 100644 --- a/src/ballistica/input/device/touch_input.cc +++ b/src/ballistica/input/device/touch_input.cc @@ -840,7 +840,8 @@ void TouchInput::UpdateMapping() { } else if (touch_movement_type == "joystick") { movement_control_type_ = TouchInput::MovementControlType::kJoystick; } else { - Log("Error: Invalid touch-movement-type: " + touch_movement_type); + Log(LogLevel::kError, + "Invalid touch-movement-type: " + touch_movement_type); movement_control_type_ = TouchInput::MovementControlType::kSwipe; } std::string touch_action_type = @@ -850,7 +851,7 @@ void TouchInput::UpdateMapping() { } else if (touch_action_type == "buttons") { action_control_type_ = TouchInput::ActionControlType::kButtons; } else { - Log("Error: Invalid touch-action-type: " + touch_action_type); + Log(LogLevel::kError, "Invalid touch-action-type: " + touch_action_type); action_control_type_ = TouchInput::ActionControlType::kSwipe; } diff --git a/src/ballistica/input/input.cc b/src/ballistica/input/input.cc index 4cbfd4ed..1137276b 100644 --- a/src/ballistica/input/input.cc +++ b/src/ballistica/input/input.cc @@ -20,7 +20,7 @@ namespace ballistica { -// Though it seems strange, input is actually owned by the game thread, not the +// Though it seems strange, input is actually owned by the logic thread, not the // app thread. This keeps things simple for game logic interacting with input // stuff (controller names, counts, etc) but means we need to be prudent about // properly passing stuff between the game and app thread as needed. @@ -327,7 +327,8 @@ void Input::PushCreateKeyboardInputDevices() { void Input::CreateKeyboardInputDevices() { assert(InLogicThread()); if (keyboard_input_ != nullptr || keyboard_input_2_ != nullptr) { - Log("Error: CreateKeyboardInputDevices called with existing kbs."); + Log(LogLevel::kError, + "CreateKeyboardInputDevices called with existing kbs."); return; } keyboard_input_ = Object::NewDeferred(nullptr); @@ -343,7 +344,8 @@ void Input::PushDestroyKeyboardInputDevices() { void Input::DestroyKeyboardInputDevices() { assert(InLogicThread()); if (keyboard_input_ == nullptr || keyboard_input_2_ == nullptr) { - Log("Error: DestroyKeyboardInputDevices called with null kb(s)."); + Log(LogLevel::kError, + "DestroyKeyboardInputDevices called with null kb(s)."); return; } RemoveInputDevice(keyboard_input_, false); @@ -812,7 +814,8 @@ void Input::UpdateEnabledControllerSubsystems() { ignore_mfi_controllers_ = false; ignore_sdl_controllers_ = false; } else { - BA_LOG_ONCE("Invalid mac-controller-subsystem value: '" + sys + "'"); + BA_LOG_ONCE(LogLevel::kError, + "Invalid mac-controller-subsystem value: '" + sys + "'"); } } } @@ -842,7 +845,8 @@ void Input::Update() { // If input has been locked an excessively long amount of time, unlock it. if (input_lock_count_temp_) { if (real_time - last_input_temp_lock_time_ > 10000) { - Log("Error: Input has been temp-locked for 10 seconds; unlocking."); + Log(LogLevel::kError, + "Input has been temp-locked for 10 seconds; unlocking."); input_lock_count_temp_ = 0; PrintLockLabels(); input_lock_temp_labels_.clear(); @@ -938,8 +942,9 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) { input_lock_count_temp_--; input_unlock_temp_labels_.push_back(label); if (input_lock_count_temp_ < 0) { - Log("WARNING: temp input unlock at time " + std::to_string(GetRealTime()) - + " with no active lock: '" + label + "'"); + Log(LogLevel::kWarning, "temp input unlock at time " + + std::to_string(GetRealTime()) + + " with no active lock: '" + label + "'"); // This is to be expected since we can reset this to 0. input_lock_count_temp_ = 0; } @@ -991,7 +996,7 @@ void Input::PrintLockLabels() { s += "\n " + std::to_string(num++) + ": " + recent_input_locks_unlock; } - Log(s); + Log(LogLevel::kError, s); } void Input::ProcessStressTesting(int player_count) { @@ -1653,7 +1658,8 @@ void Input::HandleTouchEvent(const TouchEvent& e) { // overall multitouch gesture, it should always be winding up as our // single_touch_. if (e.type == TouchEvent::Type::kDown && single_touch_ != nullptr) { - BA_LOG_ONCE("Got touch labeled first but will not be our single."); + BA_LOG_ONCE(LogLevel::kError, + "Got touch labeled first but will not be our single."); } // Also: if the OS tells us that this is the end of an overall multi-touch @@ -1661,7 +1667,8 @@ void Input::HandleTouchEvent(const TouchEvent& e) { if ((e.type == TouchEvent::Type::kUp || e.type == TouchEvent::Type::kCanceled) && single_touch_ != nullptr && single_touch_ != e.touch) { - BA_LOG_ONCE("Last touch coming up is not single touch!"); + BA_LOG_ONCE(LogLevel::kError, + "Last touch coming up is not single touch!"); } } @@ -1792,8 +1799,9 @@ const char* GetScancodeName(SDL_Scancode scancode) { const char* name; if (static_cast(scancode) < SDL_SCANCODE_UNKNOWN || scancode >= SDL_NUM_SCANCODES) { - BA_LOG_ONCE("GetScancodeName passed invalid scancode " - + std::to_string(static_cast(scancode))); + BA_LOG_ONCE(LogLevel::kError, + "GetScancodeName passed invalid scancode " + + std::to_string(static_cast(scancode))); return ""; } diff --git a/src/ballistica/input/input.h b/src/ballistica/input/input.h index 6c02956e..8f1d7838 100644 --- a/src/ballistica/input/input.h +++ b/src/ballistica/input/input.h @@ -14,17 +14,17 @@ namespace ballistica { /// Class for managing input. -/// Should only be used in the game thread unless otherwise specified. +/// Should only be used in the logic thread unless otherwise specified. class Input { public: Input(); - // Add an input device. Must be called from the game thread; otherwise use + // Add an input device. Must be called from the logic thread; otherwise use // PushAddInputDeviceCall. auto AddInputDevice(InputDevice* input, bool standard_message) -> void; // Removes a previously-added input-device. Must be called from the - // game thread; otherwise use PushRemoveInputDeviceCall. + // logic thread; otherwise use PushRemoveInputDeviceCall. auto RemoveInputDevice(InputDevice* input, bool standard_message) -> void; // Given a device name and persistent identifier for it, returns a device or diff --git a/src/ballistica/input/remote_app.cc b/src/ballistica/input/remote_app.cc index 2bac63ec..43c3549a 100644 --- a/src/ballistica/input/remote_app.cc +++ b/src/ballistica/input/remote_app.cc @@ -75,9 +75,9 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt, } case BA_PACKET_REMOTE_ID_REQUEST: { if (amt < 5 || amt > 127) { - BA_LOG_ONCE( - "Error: received invalid BA_PACKET_REMOTE_ID_REQUEST of length " - + std::to_string(amt)); + BA_LOG_ONCE(LogLevel::kError, + "Received invalid BA_PACKET_REMOTE_ID_REQUEST of length " + + std::to_string(amt)); break; } @@ -212,7 +212,7 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt, // Each state is 2 bytes. So make sure our length adds up. if (amt != 4 + state_count * 3) { - BA_LOG_ONCE("Error: Invalid state packet"); + BA_LOG_ONCE(LogLevel::kError, "Invalid state packet"); return; } RemoteAppClient* client = clients_ + joystick_id; diff --git a/src/ballistica/internal/app_internal.h b/src/ballistica/internal/app_internal.h index 076c246f..6421c66d 100644 --- a/src/ballistica/internal/app_internal.h +++ b/src/ballistica/internal/app_internal.h @@ -41,9 +41,9 @@ class AppInternal { bool user_initiated) -> void = 0; virtual auto GetPublicV1AccountID() -> std::string = 0; virtual auto OnLogicThreadPause() -> void = 0; - virtual auto DirectSendLogs(const std::string& prefix, - const std::string& suffix, bool instant, - int* result = nullptr) -> void = 0; + virtual auto DirectSendV1CloudLogs(const std::string& prefix, + const std::string& suffix, bool instant, + int* result = nullptr) -> void = 0; virtual auto ClientInfoQuery(const std::string& val1, const std::string& val2, const std::string& val3, int build_number) -> void = 0; diff --git a/src/ballistica/logic/connection/connection.cc b/src/ballistica/logic/connection/connection.cc index 654602ba..ec901c52 100644 --- a/src/ballistica/logic/connection/connection.cc +++ b/src/ballistica/logic/connection/connection.cc @@ -149,7 +149,8 @@ void Connection::HandleGamePacketCompressed(const std::vector& data) { try { data_decompressed = g_utils->huffman()->decompress(data); } catch (const std::exception& e) { - Log(std::string("EXC in huffman decompression for packet: ") + e.what()); + Log(LogLevel::kError, + std::string("Error in huffman decompression for packet: ") + e.what()); // Hmmm i guess lets just ignore this packet and keep on trucking?.. or // should we kill the connection? @@ -168,7 +169,8 @@ void Connection::HandleGamePacket(const std::vector& data) { switch (data[0]) { case BA_GAMEPACKET_KEEPALIVE: { if (data.size() != 4) { - BA_LOG_ONCE("Error: got invalid BA_GAMEPACKET_KEEPALIVE packet."); + BA_LOG_ONCE(LogLevel::kError, + "Error: got invalid BA_GAMEPACKET_KEEPALIVE packet."); return; } millisecs_t real_time = GetRealTime(); @@ -181,7 +183,7 @@ void Connection::HandleGamePacket(const std::vector& data) { // Expect 1 byte type, 2 byte num, 3 byte acks, at least 1 byte payload. if (data.size() < 7) { - Log("Error: Got invalid BA_PACKET_STATE packet."); + Log(LogLevel::kError, "Got invalid BA_PACKET_STATE packet."); return; } uint16_t num; @@ -212,7 +214,7 @@ void Connection::HandleGamePacket(const std::vector& data) { // Expect 1 byte type, 2 byte num, 2 byte unreliable-num, 3 byte acks, // at least 1 byte payload. if (data.size() < 9) { - Log("Error: Got invalid BA_PACKET_STATE_UNRELIABLE packet."); + Log(LogLevel::kError, "Got invalid BA_PACKET_STATE_UNRELIABLE packet."); return; } uint16_t num, num_unreliable; @@ -233,8 +235,8 @@ void Connection::HandleGamePacket(const std::vector& data) { } default: - Log("Connection got unknown packet type: " - + std::to_string(static_cast(data[0]))); + Log(LogLevel::kError, "Connection got unknown packet type: " + + std::to_string(static_cast(data[0]))); break; } } @@ -316,8 +318,9 @@ void Connection::SendReliableMessage(const std::vector& data) { void Connection::SendUnreliableMessage(const std::vector& data) { // For now we just silently drop anything bigger than our max packet size. if (data.size() + 8 > kMaxPacketSize) { - BA_LOG_ONCE("Error: Dropping outgoing unreliable packet of size " - + std::to_string(data.size()) + "."); + BA_LOG_ONCE(LogLevel::kError, + "Error: Dropping outgoing unreliable packet of size " + + std::to_string(data.size()) + "."); return; } @@ -428,7 +431,7 @@ void Connection::HandleMessagePacket(const std::vector& buffer) { multipart_buffer_.resize(old_size + (buffer.size() - 1)); memcpy(&(multipart_buffer_[old_size]), &(buffer[1]), buffer.size() - 1); } else { - Log("got invalid BA_MESSAGE_MULTIPART"); + Log(LogLevel::kError, "got invalid BA_MESSAGE_MULTIPART"); } if (buffer[0] == BA_MESSAGE_MULTIPART_END) { HandleMessagePacket(multipart_buffer_); @@ -468,10 +471,11 @@ void Connection::SendGamePacket(const std::vector& data) { if (!can_send && data[0] != BA_GAMEPACKET_HANDSHAKE && data[0] != BA_GAMEPACKET_HANDSHAKE_RESPONSE) { if (explicit_bool(false)) { - BA_LOG_ONCE("SendGamePacket() called before can_communicate set (" - + g_platform->DemangleCXXSymbol(typeid(*this).name()) - + " ptype " + std::to_string(static_cast(data[0])) - + ")"); + BA_LOG_ONCE(LogLevel::kError, + "SendGamePacket() called before can_communicate set (" + + g_platform->DemangleCXXSymbol(typeid(*this).name()) + + " ptype " + std::to_string(static_cast(data[0])) + + ")"); } return; } diff --git a/src/ballistica/logic/connection/connection_set.cc b/src/ballistica/logic/connection/connection_set.cc index 1748a3b9..23797bf6 100644 --- a/src/ballistica/logic/connection/connection_set.cc +++ b/src/ballistica/logic/connection/connection_set.cc @@ -26,7 +26,8 @@ void ConnectionSet::RegisterClientController(ClientControllerInterface* c) { // This shouldn't happen, but if there's already a controller registered, // detach all clients from it. if (client_controller_) { - Log("RegisterClientController() called " + Log(LogLevel::kError, + "RegisterClientController() called " "but already have a controller; bad."); for (auto&& i : connections_to_clients_) { assert(i.second.exists()); @@ -206,7 +207,8 @@ auto ConnectionSet::GetConnectionsToClients() if (connections_to_client.second.exists()) { connections.push_back(connections_to_client.second.get()); } else { - Log("HAVE NONEXISTENT CONNECTION_TO_CLIENT IN LIST; UNEXPECTED"); + Log(LogLevel::kError, + "HAVE NONEXISTENT CONNECTION_TO_CLIENT IN LIST; UNEXPECTED"); } } return connections; @@ -218,6 +220,7 @@ void ConnectionSet::PushUDPConnectionPacketCall( // these are unreliable messages so its ok to just drop them. if (!g_logic->thread()->CheckPushSafety()) { BA_LOG_ONCE( + LogLevel::kError, "Ignoring excessive udp-connection input packets; (could this be a " "flood attack?)."); return; @@ -280,7 +283,8 @@ void ConnectionSet::SendScreenMessageToAll(const std::string& s, float r, auto ConnectionSet::PrepareForLaunchHostSession() -> void { // If for some reason we're still attached to a host, kill the connection. if (connection_to_host_.exists()) { - Log("Had host-connection during LaunchHostSession(); shouldn't happen."); + Log(LogLevel::kError, + "Had host-connection during LaunchHostSession(); shouldn't happen."); connection_to_host_->RequestDisconnect(); connection_to_host_.Clear(); has_connection_to_host_ = false; @@ -322,8 +326,8 @@ auto ConnectionSet::DisconnectClient(int client_id, int ban_seconds) -> bool { return false; } if (client_id > 255) { - Log("DisconnectClient got client_id > 255 (" + std::to_string(client_id) - + ")"); + Log(LogLevel::kError, "DisconnectClient got client_id > 255 (" + + std::to_string(client_id) + ")"); } else { std::vector msg_out(2); msg_out[0] = BA_MESSAGE_KICK_VOTE; @@ -404,7 +408,8 @@ auto ConnectionSet::UnregisterClientController(ClientControllerInterface* c) // This shouldn't happen. if (client_controller_ != c) { - Log("UnregisterClientController() called with a non-registered " + Log(LogLevel::kError, + "UnregisterClientController() called with a non-registered " "controller"); return; } @@ -668,7 +673,7 @@ auto ConnectionSet::UDPConnectionPacket(const std::vector& data_in, msg_out[0] = BA_PACKET_CLIENT_DENY; msg_out[1] = request_id; g_network_writer->PushSendToCall(msg_out, addr); - Log("All client slots full; really?.."); + Log(LogLevel::kError, "All client slots full; really?.."); break; } connection_to_client = Object::New( @@ -710,8 +715,9 @@ auto ConnectionSet::VerifyClientAddr(uint8_t client_id, const SockAddr& addr) if (addr == connection_to_client_udp->addr()) { return true; } - BA_LOG_ONCE("VerifyClientAddr() found mismatch for client " - + std::to_string(client_id) + "."); + BA_LOG_ONCE(LogLevel::kError, + "VerifyClientAddr() found mismatch for client " + + std::to_string(client_id) + "."); return false; } @@ -722,8 +728,9 @@ void ConnectionSet::SetClientInfoFromMasterServer( const std::string& client_token, PyObject* info_obj) { // NOLINTNEXTLINE (python doing bitwise math on signed int) if (!PyDict_Check(info_obj)) { - Log("got non-dict for master-server client info for token " + client_token - + ": " + Python::ObjToString(info_obj)); + Log(LogLevel::kError, + "got non-dict for master-server client info for token " + client_token + + ": " + Python::ObjToString(info_obj)); return; } for (ConnectionToClient* client : GetConnectionsToClients()) { diff --git a/src/ballistica/logic/connection/connection_to_client.cc b/src/ballistica/logic/connection/connection_to_client.cc index 402a7593..10d52a4c 100644 --- a/src/ballistica/logic/connection/connection_to_client.cc +++ b/src/ballistica/logic/connection/connection_to_client.cc @@ -129,14 +129,14 @@ void ConnectionToClient::HandleGamePacket(const std::vector& data) { } if (data.empty()) { - Log("Error: ConnectionToClient got data size 0."); + Log(LogLevel::kError, "ConnectionToClient got data size 0."); return; } switch (data[0]) { case BA_GAMEPACKET_HANDSHAKE_RESPONSE: { // We sent the client a handshake and they're responding. if (data.size() < 3) { - Log("got invalid BA_GAMEPACKET_HANDSHAKE_RESPONSE"); + Log(LogLevel::kError, "got invalid BA_GAMEPACKET_HANDSHAKE_RESPONSE"); return; } @@ -322,7 +322,7 @@ void ConnectionToClient::SendScreenMessage(const std::string& s, float r, void ConnectionToClient::HandleMessagePacket( const std::vector& buffer) { if (buffer.empty()) { - Log("Error: Got invalid HandleMessagePacket."); + Log(LogLevel::kError, "Got invalid HandleMessagePacket."); return; } @@ -369,7 +369,7 @@ void ConnectionToClient::HandleMessagePacket( if (b) { build_number_ = b->valueint; } else { - Log("no buildnumber in clientinfo msg"); + Log(LogLevel::kError, "no buildnumber in clientinfo msg"); } // Grab their token (we use this to ask the @@ -378,7 +378,7 @@ void ConnectionToClient::HandleMessagePacket( if (t) { token_ = t->valuestring; } else { - Log("no token in clientinfo msg"); + Log(LogLevel::kError, "no token in clientinfo msg"); } // Newer clients also pass a peer-hash, which @@ -397,8 +397,10 @@ void ConnectionToClient::HandleMessagePacket( } cJSON_Delete(info); } else { - Log("got invalid json in clientinfo message: '" - + std::string(reinterpret_cast(&(buffer[1]))) + "'"); + Log(LogLevel::kError, + "got invalid json in clientinfo message: '" + + std::string(reinterpret_cast(&(buffer[1]))) + + "'"); } } got_client_info_ = true; @@ -435,7 +437,8 @@ void ConnectionToClient::HandleMessagePacket( // we support for game streams vs client-connections. We could disallow // connections to/from these older peers while still allowing old replays // to play back. - BA_LOG_ONCE("Received old pre-json player profiles msg; ignoring."); + BA_LOG_ONCE(LogLevel::kError, + "Received old pre-json player profiles msg; ignoring."); break; } @@ -462,7 +465,8 @@ void ConnectionToClient::HandleMessagePacket( // spamming before we can verify their identities) if (g_logic->require_client_authentication() && !got_info_from_master_server_) { - Log("Ignoring chat message from peer with no client info."); + Log(LogLevel::kError, + "Ignoring chat message from peer with no client info."); SendScreenMessage(R"({"r":"loadingTryAgainText"})", 1, 0, 0); } else if (last_chat_times_.size() >= 5) { chat_block_time_ = now + next_chat_block_seconds_ * 1000; @@ -556,7 +560,7 @@ void ConnectionToClient::HandleMessagePacket( GetClientInputDevice(buffer[1])) { int count = static_cast((buffer.size() - 2) / 5); if ((buffer.size() - 2) % 5 != 0) { - Log("Error: invalid player-input-commands packet"); + Log(LogLevel::kError, "Error: invalid player-input-commands packet"); break; } int index = 2; @@ -574,7 +578,7 @@ void ConnectionToClient::HandleMessagePacket( case BA_MESSAGE_REMOVE_REMOTE_PLAYER: { last_remove_player_time_ = GetRealTime(); if (buffer.size() != 2) { - Log("Error: invalid remove-remote-player packet"); + Log(LogLevel::kError, "Error: invalid remove-remote-player packet"); break; } if (ClientInputDevice* cid = GetClientInputDevice(buffer[1])) { @@ -589,7 +593,7 @@ void ConnectionToClient::HandleMessagePacket( case BA_MESSAGE_REQUEST_REMOTE_PLAYER: { if (buffer.size() != 2) { - Log("Error: invalid remote-player-request packet"); + Log(LogLevel::kError, "Error: invalid remote-player-request packet"); break; } @@ -628,16 +632,18 @@ void ConnectionToClient::HandleMessagePacket( } else { // Either timed out or have info; let the request go through. if (still_waiting) { - Log("Allowing player-request without client\'s master-server " + Log(LogLevel::kError, + "Allowing player-request without client\'s master-server " "info (build " - + std::to_string(build_number_) + ")"); + + std::to_string(build_number_) + ")"); } hs->RequestPlayer(cid); } } } } else { - Log("Error: ConnectionToClient got remote player" + Log(LogLevel::kError, + "ConnectionToClient got remote player" " request but have no host session"); } break; @@ -650,8 +656,9 @@ void ConnectionToClient::HandleMessagePacket( if (multipart_buffer_size() > 50000) { // Its not actually unknown but shhh don't tell the hackers... SendScreenMessage(R"({"r":"errorUnknownText"})", 1, 0, 0); - Log("Client data limit exceeded by '" + peer_spec().GetShortName() - + "'; kicking."); + Log(LogLevel::kError, "Client data limit exceeded by '" + + peer_spec().GetShortName() + + "'; kicking."); g_logic->BanPlayer(peer_spec(), 1000 * 60); Error(""); return; @@ -738,8 +745,8 @@ void ConnectionToClient::HandleMasterServerClientInfo(PyObject* info_obj) { "{\"t\":[\"serverResponses\"," "\"Your account was rejected. Are you signed in?\"]}", 1, 0, 0); - Log("Master server found no valid account for '" - + peer_spec().GetShortName() + "'; kicking."); + Log(LogLevel::kError, "Master server found no valid account for '" + + peer_spec().GetShortName() + "'; kicking."); // Not benning anymore. People were exploiting this by impersonating // other players using their public ids to get them banned from diff --git a/src/ballistica/logic/connection/connection_to_client_udp.cc b/src/ballistica/logic/connection/connection_to_client_udp.cc index 09a65a34..4dbbf839 100644 --- a/src/ballistica/logic/connection/connection_to_client_udp.cc +++ b/src/ballistica/logic/connection/connection_to_client_udp.cc @@ -66,7 +66,7 @@ void ConnectionToClientUDP::HandleGamePacket( void ConnectionToClientUDP::Die() { if (did_die_) { - Log("Error: Posting multiple die messages; probably not good."); + Log(LogLevel::kError, "Posting multiple die messages; probably not good."); return; } // this will actually clear the object.. diff --git a/src/ballistica/logic/connection/connection_to_host.cc b/src/ballistica/logic/connection/connection_to_host.cc index baede471..e1500402 100644 --- a/src/ballistica/logic/connection/connection_to_host.cc +++ b/src/ballistica/logic/connection/connection_to_host.cc @@ -219,7 +219,8 @@ void ConnectionToHost::HandleGamePacket(const std::vector& data) { PyObject* profiles = g_python->GetRawConfigValue("Player Profiles"); PythonRef empty_dict; if (!profiles) { - Log("No profiles found; sending empty list to host"); + Log(LogLevel::kError, + "No profiles found; sending empty list to host"); empty_dict.Steal(PyDict_New()); profiles = empty_dict.get(); } @@ -231,7 +232,8 @@ void ConnectionToHost::HandleGamePacket(const std::vector& data) { PythonRef results = g_python->obj(Python::ObjID::kJsonDumpsCall).Call(args, keywds); if (!results.exists()) { - Log("Error getting json dump of local profiles"); + Log(LogLevel::kError, + "Error getting json dump of local profiles"); } else { try { // Pull the string as utf8 and send. @@ -241,13 +243,15 @@ void ConnectionToHost::HandleGamePacket(const std::vector& data) { memcpy(&(msg[1]), &s[0], s.size()); SendReliableMessage(msg); } catch (const std::exception& e) { - Log(std::string("exc sending player profiles to host: ") - + e.what()); + Log(LogLevel::kError, + std::string("exc sending player profiles to host: ") + + e.what()); } } } } else { - Log("Connected to old protocol; can't send player profiles"); + Log(LogLevel::kError, + "Connected to old protocol; can't send player profiles"); } } break; @@ -274,7 +278,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { assert(InLogicThread()); if (buffer.empty()) { - Log("Error: got invalid HandleMessagePacket"); + Log(LogLevel::kError, "Got invalid HandleMessagePacket"); return; } @@ -298,7 +302,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { if (b) { build_number_ = b->valueint; } else { - Log("no buildnumber in hostinfo msg"); + Log(LogLevel::kError, "no buildnumber in hostinfo msg"); } // Party name. cJSON* n = cJSON_GetObjectItem(info, "n"); @@ -307,7 +311,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { } cJSON_Delete(info); } else { - Log("got invalid json in hostinfo message"); + Log(LogLevel::kError, "got invalid json in hostinfo message"); } } got_host_info_ = true; @@ -399,7 +403,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { case BA_MESSAGE_ATTACH_REMOTE_PLAYER_2: { // New-style packet which includes a 32-bit player_id. if (buffer.size() != 6) { - Log("Error: invalid attach-remote-player-2 msg"); + Log(LogLevel::kError, "Invalid attach-remote-player-2 msg"); break; } @@ -426,7 +430,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { // public servers. // TODO(ericf): can remove this once back-compat-protocol > 29. if (buffer.size() != 3) { - Log("Error: Invalid attach-remote-player msg."); + Log(LogLevel::kError, "Invalid attach-remote-player msg."); break; } @@ -447,7 +451,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector& buffer) { case BA_MESSAGE_DETACH_REMOTE_PLAYER: { if (buffer.size() != 2) { - Log("Error: Invalid detach-remote-player msg"); + Log(LogLevel::kError, "Invalid detach-remote-player msg"); break; } InputDevice* input_device = g_input->GetInputDevice(buffer[1]); diff --git a/src/ballistica/logic/connection/connection_to_host_udp.cc b/src/ballistica/logic/connection/connection_to_host_udp.cc index c77a8a8d..8e332355 100644 --- a/src/ballistica/logic/connection/connection_to_host_udp.cc +++ b/src/ballistica/logic/connection/connection_to_host_udp.cc @@ -110,14 +110,15 @@ void ConnectionToHostUDP::Update() { // departure before doing this when possible. void ConnectionToHostUDP::Die() { if (did_die_) { - Log("Error: posting multiple die messages; probably not good."); + Log(LogLevel::kError, "Posting multiple die messages; probably not good."); return; } if (g_logic->connections()->connection_to_host() == this) { g_logic->connections()->PushDisconnectedFromHostCall(); did_die_ = true; } else { - Log("Error: Running update for non-current host-connection; shouldn't " + Log(LogLevel::kError, + "Running update for non-current host-connection; shouldn't " "happen."); } } diff --git a/src/ballistica/logic/host_activity.cc b/src/ballistica/logic/host_activity.cc index 9685cadd..be77634f 100644 --- a/src/ballistica/logic/host_activity.cc +++ b/src/ballistica/logic/host_activity.cc @@ -98,14 +98,14 @@ HostActivity::~HostActivity() { if (g_buildconfig.debug_build()) { PruneDeadRefs(&python_calls_); if (python_calls_.size() > 1) { - std::string s = "WARNING: " + std::to_string(python_calls_.size()) + std::string s = std::to_string(python_calls_.size()) + " live PythonContextCalls at shutdown for " + "HostActivity" + " (1 call is expected):"; int count = 1; for (auto& python_call : python_calls_) s += "\n " + std::to_string(count++) + ": " + (*python_call).GetObjectDescription(); - Log(s); + Log(LogLevel::kWarning, s); } } } @@ -153,15 +153,16 @@ void HostActivity::RegisterCall(PythonContextCall* call) { // If we're shutting down, just kill the call immediately. // (we turn all of our calls to no-ops as we shut down) if (shutting_down_) { - Log("WARNING: adding call to expired activity; call will not function: " - + call->GetObjectDescription()); + Log(LogLevel::kWarning, + "Adding call to expired activity; call will not function: " + + call->GetObjectDescription()); call->MarkDead(); } } void HostActivity::start() { if (_started) { - Log("Error: Start called twice for activity."); + Log(LogLevel::kError, "Start called twice for activity."); } _started = true; } @@ -253,7 +254,8 @@ void HostActivity::HandleOutOfBoundsNodes() { // Make sure someone's handling our out-of-bounds messages. out_of_bounds_in_a_row_++; if (out_of_bounds_in_a_row_ > 100) { - Log("Warning: 100 consecutive out-of-bounds messages sent." + Log(LogLevel::kWarning, + "100 consecutive out-of-bounds messages sent." " They are probably not being handled properly"); int j = 0; for (auto&& i : scene()->out_of_bounds_nodes()) { @@ -265,9 +267,10 @@ void HostActivity::HandleOutOfBoundsNodes() { if (delegate) { dstr = PythonRef(delegate, PythonRef::kAcquire).Str(); } - Log(" node #" + std::to_string(j) + ": type='" + n->type()->name() - + "' addr=" + Utils::PtrToString(i.get()) + " name='" + n->label() - + "' delegate=" + dstr); + Log(LogLevel::kWarning, + " node #" + std::to_string(j) + ": type='" + n->type()->name() + + "' addr=" + Utils::PtrToString(i.get()) + " name='" + + n->label() + "' delegate=" + dstr); } } out_of_bounds_in_a_row_ = 0; diff --git a/src/ballistica/logic/logic.cc b/src/ballistica/logic/logic.cc index 795951ac..a791d3f0 100644 --- a/src/ballistica/logic/logic.cc +++ b/src/ballistica/logic/logic.cc @@ -373,7 +373,8 @@ void Logic::PruneSessions() { try { i.Clear(); } catch (const std::exception& e) { - Log("Exception killing Session: " + std::string(e.what())); + Log(LogLevel::kError, + "Exception killing Session: " + std::string(e.what())); } have_dead_session = true; } @@ -663,8 +664,8 @@ void Logic::Update() { // Complain when our full update takes longer than 1/60th second. if (duration > (1000 / 60)) { - Log("Game update took too long (" + std::to_string(duration) + " ms).", - true, false); + Log(LogLevel::kInfo, + "Logic update took too long (" + std::to_string(duration) + " ms)."); // Limit these if we want (not doing so for now). next_long_update_report_time_ = real_time; @@ -686,8 +687,9 @@ void Logic::Reset() { // If all is well our sessions should all be dead. if (g_app->session_count != 0) { - Log("Error: session-count is non-zero (" - + std::to_string(g_app->session_count) + ") on Logic::Reset."); + Log(LogLevel::kError, "Session-count is non-zero (" + + std::to_string(g_app->session_count) + + ") on Logic::Reset."); } // Note: we don't clear real-time timers anymore. Should we?.. @@ -754,7 +756,7 @@ void Logic::PushBackButtonCall(InputDevice* input_device) { void Logic::PushStringEditSetCall(const std::string& value) { thread()->PushCall([value] { if (!g_ui) { - Log("Error: No ui on StringEditSetEvent."); + Log(LogLevel::kError, "No ui on StringEditSetEvent."); return; } #if BA_OSTYPE_ANDROID @@ -771,7 +773,7 @@ void Logic::PushStringEditSetCall(const std::string& value) { void Logic::PushStringEditCancelCall() { thread()->PushCall([] { if (!g_ui) { - Log("Error: No ui in PushStringEditCancelCall."); + Log(LogLevel::kError, "No ui in PushStringEditCancelCall."); return; } }); @@ -994,7 +996,7 @@ void Logic::PushAskUserForTelnetAccessCall() { } void Logic::PushPythonCall(const Object::Ref& call) { - // Since we're mucking with refs, need to limit to game thread. + // Since we're mucking with refs, need to limit to logic thread. BA_PRECONDITION(InLogicThread()); BA_PRECONDITION(call->object_strong_ref_count() > 0); thread()->PushCall([call] { @@ -1005,7 +1007,7 @@ void Logic::PushPythonCall(const Object::Ref& call) { void Logic::PushPythonCallArgs(const Object::Ref& call, const PythonRef& args) { - // Since we're mucking with refs, need to limit to game thread. + // Since we're mucking with refs, need to limit to logic thread. BA_PRECONDITION(InLogicThread()); BA_PRECONDITION(call->object_strong_ref_count() > 0); thread()->PushCall([call, args] { @@ -1015,7 +1017,7 @@ void Logic::PushPythonCallArgs(const Object::Ref& call, } void Logic::PushPythonWeakCall(const Object::WeakRef& call) { - // Since we're mucking with refs, need to limit to game thread. + // Since we're mucking with refs, need to limit to logic thread. BA_PRECONDITION(InLogicThread()); // Even though we only hold a weak ref, we expect a valid strong-reffed @@ -1032,7 +1034,7 @@ void Logic::PushPythonWeakCall(const Object::WeakRef& call) { void Logic::PushPythonWeakCallArgs( const Object::WeakRef& call, const PythonRef& args) { - // Since we're mucking with refs, need to limit to game thread. + // Since we're mucking with refs, need to limit to logic thread. BA_PRECONDITION(InLogicThread()); // Even though we only hold a weak ref, we expect a valid strong-reffed @@ -1181,7 +1183,7 @@ void Logic::PushConfirmQuitCall() { thread()->PushCall([this] { assert(InLogicThread()); if (HeadlessMode()) { - Log("PushConfirmQuitCall() unhandled on headless."); + Log(LogLevel::kError, "PushConfirmQuitCall() unhandled on headless."); } else { // If input is locked, just quit immediately.. a confirm screen wouldn't // work anyway @@ -1233,7 +1235,7 @@ void Logic::Draw() { HostActivity* ha = GetForegroundContext().GetHostActivity(); if (ha) { int64_t step = ha->scene()->stepnum(); - Log(std::to_string(step - last_step)); + Log(LogLevel::kInfo, std::to_string(step - last_step)); last_step = step; } } @@ -1278,7 +1280,8 @@ void Logic::ApplyConfig() { } else if (texqualstr == "Low") { texture_quality_requested = TextureQuality::kLow; } else { - Log("Invalid texture quality: '" + texqualstr + "'; defaulting to low."); + Log(LogLevel::kError, + "Invalid texture quality: '" + texqualstr + "'; defaulting to low."); texture_quality_requested = TextureQuality::kLow; } @@ -1298,8 +1301,8 @@ void Logic::ApplyConfig() { } else if (gqualstr == "Low") { graphics_quality_requested = GraphicsQuality::kLow; } else { - Log("Error: Invalid graphics quality: '" + gqualstr - + "'; defaulting to auto."); + Log(LogLevel::kError, + "Invalid graphics quality: '" + gqualstr + "'; defaulting to auto."); graphics_quality_requested = GraphicsQuality::kAuto; } @@ -1365,7 +1368,7 @@ void Logic::ApplyConfig() { } else { do_v_sync = false; auto_v_sync = false; - Log("Error: Invalid 'Vertical Sync' value: '" + v_sync + "'"); + Log(LogLevel::kError, "Invalid 'Vertical Sync' value: '" + v_sync + "'"); } g_graphics_server->PushSetVSyncCall(do_v_sync, auto_v_sync); @@ -1505,7 +1508,7 @@ auto Logic::RemovePlayer(Player* player) -> void { if (HostSession* host_session = player->GetHostSession()) { host_session->RemovePlayer(player); } else { - Log("Got RemovePlayer call but have no host_session"); + Log(LogLevel::kError, "Got RemovePlayer call but have no host_session"); } } @@ -1526,7 +1529,8 @@ void Logic::SetRealTimerLength(int timer_id, millisecs_t length) { if (t) { t->SetLength(length); } else { - Log("Error: Logic::SetRealTimerLength() called on nonexistent timer."); + Log(LogLevel::kError, + "Logic::SetRealTimerLength() called on nonexistent timer."); } } @@ -1577,8 +1581,9 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { if (!printed) { printed = true; char* c = cJSON_Print(obj); - BA_LOG_ONCE("found long key 'resource' in raw lstr json: " - + std::string(c)); + BA_LOG_ONCE( + LogLevel::kError, + "found long key 'resource' in raw lstr json: " + std::string(c)); free(c); } } @@ -1596,8 +1601,9 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { if (!printed) { printed = true; char* c = cJSON_Print(obj); - BA_LOG_ONCE("found long key 'fallback' in raw lstr json: " - + std::string(c)); + BA_LOG_ONCE( + LogLevel::kError, + "found long key 'fallback' in raw lstr json: " + std::string(c)); free(c); } } @@ -1620,8 +1626,9 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { if (!printed) { printed = true; char* c = cJSON_Print(obj); - BA_LOG_ONCE("found long key 'translate' in raw lstr json: " - + std::string(c)); + BA_LOG_ONCE( + LogLevel::kError, + "found long key 'translate' in raw lstr json: " + std::string(c)); free(c); } } @@ -1658,8 +1665,9 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { if (!printed) { printed = true; char* c = cJSON_Print(obj); - BA_LOG_ONCE("found long key 'value' in raw lstr json: " - + std::string(c)); + BA_LOG_ONCE( + LogLevel::kError, + "found long key 'value' in raw lstr json: " + std::string(c)); free(c); } } @@ -1689,8 +1697,8 @@ auto DoCompileResourceString(cJSON* obj) -> std::string { if (!printed) { printed = true; char* c = cJSON_Print(obj); - BA_LOG_ONCE("found long key 'subs' in raw lstr json: " - + std::string(c)); + BA_LOG_ONCE(LogLevel::kError, "found long key 'subs' in raw lstr json: " + + std::string(c)); free(c); } } @@ -1761,8 +1769,8 @@ auto Logic::CompileResourceString(const std::string& s, const std::string& loc, cJSON* root = cJSON_Parse(s.c_str()); if (root == nullptr) { - Log("CompileResourceString failed (loc " + loc + "); invalid json: '" + s - + "'"); + Log(LogLevel::kError, "CompileResourceString failed (loc " + loc + + "); invalid json: '" + s + "'"); *valid = false; return ""; } @@ -1771,8 +1779,8 @@ auto Logic::CompileResourceString(const std::string& s, const std::string& loc, result = DoCompileResourceString(root); *valid = true; } catch (const std::exception& e) { - Log("CompileResourceString failed (loc " + loc - + "): " + std::string(e.what()) + "; str='" + s + "'"); + Log(LogLevel::kError, "CompileResourceString failed (loc " + loc + "): " + + std::string(e.what()) + "; str='" + s + "'"); result = ""; *valid = false; } diff --git a/src/ballistica/logic/logic.h b/src/ballistica/logic/logic.h index 7fcd4163..57799970 100644 --- a/src/ballistica/logic/logic.h +++ b/src/ballistica/logic/logic.h @@ -44,7 +44,7 @@ class Logic { /// Push a generic 'menu press' event, optionally associated with an /// input device (nullptr to specify none). Note: caller must ensure - /// a RemoveInputDevice() call does not arrive at the game thread + /// a RemoveInputDevice() call does not arrive at the logic thread /// before this one. auto PushMainMenuPressCall(InputDevice* device) -> void; @@ -71,12 +71,13 @@ class Logic { auto PushMediaPruneCall(int level) -> void; auto PushAskUserForTelnetAccessCall() -> void; - // Push Python call and keep it alive; must be called from game thread. + // Push Python call and keep it alive; must be called from logic thread. auto PushPythonCall(const Object::Ref& call) -> void; auto PushPythonCallArgs(const Object::Ref& call, const PythonRef& args) -> void; - // Push Python call without keeping it alive; must be called from game thread. + // Push Python call without keeping it alive; must be called from logic + // thread. auto PushPythonWeakCall(const Object::WeakRef& call) -> void; auto PushPythonWeakCallArgs(const Object::WeakRef& call, diff --git a/src/ballistica/logic/player_spec.cc b/src/ballistica/logic/player_spec.cc index 71b6f515..8f8f28c9 100644 --- a/src/ballistica/logic/player_spec.cc +++ b/src/ballistica/logic/player_spec.cc @@ -33,7 +33,7 @@ PlayerSpec::PlayerSpec(const std::string& s) { cJSON_Delete(root_obj); } if (!success) { - Log("Error creating PlayerSpec from string: '" + s + "'"); + Log(LogLevel::kError, "Error creating PlayerSpec from string: '" + s + "'"); name_ = ""; short_name_ = ""; account_type_ = V1AccountType::kInvalid; @@ -95,7 +95,7 @@ auto PlayerSpec::GetAccountPlayerSpec() -> PlayerSpec { } if (spec.name_.size() > 100) { // FIXME should perhaps clamp this in unicode space - Log("account name size too long: '" + spec.name_ + "'"); + Log(LogLevel::kError, "account name size too long: '" + spec.name_ + "'"); spec.name_.resize(100); spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgaps3"); } @@ -107,7 +107,8 @@ auto PlayerSpec::GetDummyPlayerSpec(const std::string& name) -> PlayerSpec { spec.name_ = Utils::GetValidUTF8(name.c_str(), "bsgdps1"); if (spec.name_.size() > 100) { // FIXME should perhaps clamp this in unicode space - Log("dummy player spec name too long: '" + spec.name_ + "'"); + Log(LogLevel::kError, + "dummy player spec name too long: '" + spec.name_ + "'"); spec.name_.resize(100); spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgdps2"); } diff --git a/src/ballistica/logic/session/client_session.cc b/src/ballistica/logic/session/client_session.cc index d9b72a57..73e5b67a 100644 --- a/src/ballistica/logic/session/client_session.cc +++ b/src/ballistica/logic/session/client_session.cc @@ -196,10 +196,12 @@ void ClientSession::Update(int time_advance) { if (g_buildconfig.debug_build()) { if (current_cmd_ptr_ != nullptr) { if (current_cmd_ptr_ != &(current_cmd_[0]) + current_cmd_.size()) { - Log("SIZE ERROR FOR CMD " - + std::to_string(static_cast(current_cmd_[0])) - + " expected " + std::to_string(current_cmd_.size()) + " got " - + std::to_string(current_cmd_ptr_ - &(current_cmd_[0]))); + Log(LogLevel::kError, + "SIZE ERROR FOR CMD " + + std::to_string(static_cast(current_cmd_[0])) + + " expected " + std::to_string(current_cmd_.size()) + + " got " + + std::to_string(current_cmd_ptr_ - &(current_cmd_[0]))); } } assert(current_cmd_ptr_ == current_cmd_.data() + current_cmd_.size()); @@ -953,7 +955,7 @@ auto ClientSession::GetCollideModel(int id) const -> CollideModel* { } void ClientSession::Error(const std::string& description) { - Log("ERROR: client session error: " + description); + Log(LogLevel::kError, "Client session error: " + description); End(); } diff --git a/src/ballistica/logic/session/host_session.cc b/src/ballistica/logic/session/host_session.cc index 6dbbe3a1..caa5b7dc 100644 --- a/src/ballistica/logic/session/host_session.cc +++ b/src/ballistica/logic/session/host_session.cc @@ -242,7 +242,8 @@ void HostSession::RequestPlayer(InputDevice* device) { // Ignore if we have no Python session obj. if (!GetSessionPyObj()) { - Log("Error: HostSession::RequestPlayer() called w/no session_py_obj_."); + Log(LogLevel::kError, + "HostSession::RequestPlayer() called w/no session_py_obj_."); return; } @@ -325,11 +326,13 @@ void HostSession::IssuePlayerLeft(Player* player) { BA_LOG_PYTHON_TRACE_ONCE("missing player on IssuePlayerLeft"); } } else { - Log("WARNING: HostSession: IssuePlayerLeft caled with no " + Log(LogLevel::kWarning, + "HostSession: IssuePlayerLeft caled with no " "session_py_obj_"); } } catch (const std::exception& e) { - Log(std::string("Error calling on_player_leave(): ") + e.what()); + Log(LogLevel::kError, + std::string("Error calling on_player_leave(): ") + e.what()); } } @@ -347,7 +350,8 @@ void HostSession::SetForegroundHostActivity(HostActivity* a) { assert(InLogicThread()); if (shutting_down_) { - Log("WARNING: SetForegroundHostActivity called during session shutdown; " + Log(LogLevel::kWarning, + "SetForegroundHostActivity called during session shutdown; " "ignoring."); return; } @@ -603,7 +607,7 @@ HostSession::~HostSession() { if (g_buildconfig.debug_build()) { PruneDeadRefs(&python_calls_); if (python_calls_.size() > 1) { - std::string s = "WARNING: " + std::to_string(python_calls_.size()) + std::string s = std::to_string(python_calls_.size()) + " live PythonContextCalls at shutdown for " + "HostSession" + " (1 call is expected):"; int count = 1; @@ -611,11 +615,12 @@ HostSession::~HostSession() { s += ("\n " + std::to_string(count++) + ": " + i->GetObjectDescription()); } - Log(s); + Log(LogLevel::kWarning, s); } } } catch (const std::exception& e) { - Log("Exception in HostSession destructor: " + std::string(e.what())); + Log(LogLevel::kError, + "Exception in HostSession destructor: " + std::string(e.what())); } } @@ -626,8 +631,9 @@ void HostSession::RegisterCall(PythonContextCall* call) { // If we're shutting down, just kill the call immediately. // (we turn all of our calls to no-ops as we shut down). if (shutting_down_) { - Log("WARNING: adding call to expired session; call will not function: " - + call->GetObjectDescription()); + Log(LogLevel::kWarning, + "Adding call to expired session; call will not function: " + + call->GetObjectDescription()); call->MarkDead(); } } diff --git a/src/ballistica/logic/session/net_client_session.cc b/src/ballistica/logic/session/net_client_session.cc index 795bd1ee..a1f6b7b0 100644 --- a/src/ballistica/logic/session/net_client_session.cc +++ b/src/ballistica/logic/session/net_client_session.cc @@ -13,7 +13,8 @@ namespace ballistica { NetClientSession::NetClientSession() { // Sanity check: we should only ever be writing one replay at once. if (g_app->replay_open) { - Log("ERROR: g_replay_open true at netclient start; shouldn't happen."); + Log(LogLevel::kError, + "g_replay_open true at netclient start; shouldn't happen."); } assert(g_assets_server); g_assets_server->PushBeginWriteReplayCall(); @@ -25,7 +26,8 @@ NetClientSession::~NetClientSession() { if (writing_replay_) { // Sanity check: we should only ever be writing one replay at once. if (!g_app->replay_open) { - Log("ERROR: g_replay_open false at net-client close; shouldn't happen."); + Log(LogLevel::kError, + "g_replay_open false at net-client close; shouldn't happen."); } g_app->replay_open = false; assert(g_assets_server); diff --git a/src/ballistica/logic/session/replay_client_session.cc b/src/ballistica/logic/session/replay_client_session.cc index c93fb45f..d422cb4d 100644 --- a/src/ballistica/logic/session/replay_client_session.cc +++ b/src/ballistica/logic/session/replay_client_session.cc @@ -45,14 +45,16 @@ void ReplayClientSession::OnClientConnected(ConnectionToClient* c) { // sanity check - abort if its on either of our lists already for (ConnectionToClient* i : connections_to_clients_) { if (i == c) { - Log("Error: ReplayClientSession::OnClientConnected()" + Log(LogLevel::kError, + "ReplayClientSession::OnClientConnected()" " got duplicate connection"); return; } } for (ConnectionToClient* i : connections_to_clients_ignored_) { if (i == c) { - Log("Error: ReplayClientSession::OnClientConnected()" + Log(LogLevel::kError, + "ReplayClientSession::OnClientConnected()" " got duplicate connection"); return; } @@ -113,7 +115,8 @@ void ReplayClientSession::OnClientDisconnected(ConnectionToClient* c) { return; } } - Log("Error: ReplayClientSession::OnClientDisconnected()" + Log(LogLevel::kError, + "ReplayClientSession::OnClientDisconnected()" " called for connection not on lists"); } diff --git a/src/ballistica/logic/session/session.cc b/src/ballistica/logic/session/session.cc index facb0460..a785e291 100644 --- a/src/ballistica/logic/session/session.cc +++ b/src/ballistica/logic/session/session.cc @@ -31,7 +31,8 @@ void Session::GraphicsQualityChanged(GraphicsQuality q) {} void Session::DebugSpeedMultChanged() {} void Session::DumpFullState(SceneStream* out) { - Log("Session::DumpFullState() being called; shouldn't happen."); + Log(LogLevel::kError, + "Session::DumpFullState() being called; shouldn't happen."); } } // namespace ballistica diff --git a/src/ballistica/logic/v1_account.cc b/src/ballistica/logic/v1_account.cc index 23ccd39b..399211cc 100644 --- a/src/ballistica/logic/v1_account.cc +++ b/src/ballistica/logic/v1_account.cc @@ -150,7 +150,7 @@ void V1Account::SetLogin(V1AccountType account_type, V1LoginState login_state, { std::scoped_lock lock(mutex_); - // We call out to Python so need to be in game thread. + // We call out to Python so need to be in logic thread. assert(InLogicThread()); if (login_state_ != login_state || g_app->account_type != account_type || login_id_ != login_id || login_name_ != login_name) { diff --git a/src/ballistica/networking/network_reader.cc b/src/ballistica/networking/network_reader.cc index 15b62d2a..837dc644 100644 --- a/src/ballistica/networking/network_reader.cc +++ b/src/ballistica/networking/network_reader.cc @@ -43,7 +43,7 @@ auto NetworkReader::Pause() -> void { if (port4_ != -1) { PokeSelf(); } else { - Log("Error: NetworkReader port is -1 on pause"); + Log(LogLevel::kError, "NetworkReader port is -1 on pause"); } } @@ -63,8 +63,8 @@ void NetworkReader::Resume() { void NetworkReader::PokeSelf() { int sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { - Log("ERROR: unable to create sleep ping socket; errno " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Unable to create sleep ping socket; errno " + + g_platform->GetSocketErrorString()); } else { struct sockaddr_in serv_addr {}; memset(&serv_addr, 0, sizeof(serv_addr)); @@ -73,8 +73,8 @@ void NetworkReader::PokeSelf() { serv_addr.sin_port = 0; // any int bresult = ::bind(sd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (bresult == 1) { - Log("ERROR: unable to bind sleep socket: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, + "Unable to bind sleep socket: " + g_platform->GetSocketErrorString()); } else { struct sockaddr_in t_addr {}; memset(&t_addr, 0, sizeof(t_addr)); @@ -85,8 +85,8 @@ void NetworkReader::PokeSelf() { ssize_t sresult = sendto(sd, b, 1, 0, (struct sockaddr*)(&t_addr), sizeof(t_addr)); if (sresult == -1) { - Log("Error on sleep self-sendto: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Error on sleep self-sendto: " + + g_platform->GetSocketErrorString()); } } g_platform->CloseSocket(sd); @@ -144,7 +144,7 @@ static auto HandleGameQuery(const char* buffer, size_t size, BA_PRECONDITION_FATAL(!usid.empty()); if (usid.size() > 100) { - Log("had to truncate session-id; shouldn't happen"); + Log(LogLevel::kError, "had to truncate session-id; shouldn't happen"); usid.resize(100); } if (usid.empty()) { @@ -170,8 +170,8 @@ static auto HandleGameQuery(const char* buffer, size_t size, g_network_writer->PushSendToCall(msg_buffer, SockAddr(*from)); } else { - Log("Error: Got invalid game-query packet of len " + std::to_string(size) - + "; expected 5."); + Log(LogLevel::kError, "Got invalid game-query packet of len " + + std::to_string(size) + "; expected 5."); } } @@ -232,7 +232,8 @@ auto NetworkReader::RunThread() -> int { // Aint no thang. } else { // Let's complain for anything else though. - Log("Error on select: " + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, + "Error on select: " + g_platform->GetSocketErrorString()); } } else { // Wait for any data on either of our sockets. @@ -244,7 +245,8 @@ auto NetworkReader::RunThread() -> int { recvfrom(sd, buffer, sizeof(buffer), 0, reinterpret_cast(&from), &from_size); if (rresult == 0) { - Log("ERROR: NetworkReader Recv got length 0; this shouldn't " + Log(LogLevel::kError, + "NetworkReader Recv got length 0; this shouldn't " "happen"); } else if (rresult == -1) { // This needs to be locked during any sd changes/writes. @@ -354,7 +356,7 @@ auto NetworkReader::RunThread() -> int { case BA_PACKET_CLIENT_GAMEPACKET_COMPRESSED: case BA_PACKET_HOST_GAMEPACKET_COMPRESSED: { // These messages are associated with udp host/client - // connections.. pass them to the game thread to wrangle. + // connections.. pass them to the logic thread to wrangle. std::vector msg_buffer(rresult2); memcpy(&(msg_buffer[0]), buffer, rresult2); g_logic->connections()->PushUDPConnectionPacketCall( @@ -395,8 +397,8 @@ auto NetworkReader::OpenSockets() -> void { sd4_ = socket(AF_INET, SOCK_DGRAM, 0); if (sd4_ < 0) { - Log("ERROR: Unable to open host socket; errno " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Unable to open host socket; errno " + + g_platform->GetSocketErrorString()); } else { g_platform->SetSocketNonBlocking(sd4_); @@ -413,9 +415,8 @@ auto NetworkReader::OpenSockets() -> void { // If we're headless then we abort here; we're useless if we don't get // the port we wanted. if (HeadlessMode()) { - Log("FATAL ERROR: unable to bind to requested udp port " - + std::to_string(port4_) + " (ipv4)"); - exit(1); + FatalError("Unable to bind to requested udp port " + + std::to_string(port4_) + " (ipv4)"); } // Primary ipv4 bind failed; try on any port as a backup. @@ -450,8 +451,8 @@ auto NetworkReader::OpenSockets() -> void { // available everywhere (win XP, etc) so let's do this for now. sd6_ = socket(AF_INET6, SOCK_DGRAM, 0); if (sd6_ < 0) { - Log("ERROR: Unable to open ipv6 socket: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, + "Unable to open ipv6 socket: " + g_platform->GetSocketErrorString()); } else { // Since we're explicitly creating both a v4 and v6 socket, tell the v6 // to *not* do both itself (not sure if this is necessary; on mac it @@ -460,7 +461,7 @@ auto NetworkReader::OpenSockets() -> void { if (setsockopt(sd6_, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast(&on), sizeof(on)) == -1) { - Log("error setting socket as ipv6-only"); + Log(LogLevel::kError, "Error setting socket as ipv6-only"); } g_platform->SetSocketNonBlocking(sd6_); @@ -473,9 +474,8 @@ auto NetworkReader::OpenSockets() -> void { if (result != 0) { if (HeadlessMode()) { - Log("FATAL ERROR: unable to bind to requested udp port " - + std::to_string(port6_) + " (ipv6)"); - exit(1); + FatalError("Unable to bind to requested udp port " + + std::to_string(port6_) + " (ipv6)"); } // Primary ipv6 bind failed; try backup. @@ -508,9 +508,9 @@ auto NetworkReader::OpenSockets() -> void { + std::to_string(initial_requested_port) + "; some network functionality may fail.", {1, 0.5f, 0}); - Log("Unable to bind udp port " + std::to_string(initial_requested_port) - + "; some network functionality may fail.", - true, false); + Log(LogLevel::kWarning, "Unable to bind udp port " + + std::to_string(initial_requested_port) + + "; some network functionality may fail."); } } diff --git a/src/ballistica/networking/network_writer.cc b/src/ballistica/networking/network_writer.cc index 82679ce5..3050e8b5 100644 --- a/src/ballistica/networking/network_writer.cc +++ b/src/ballistica/networking/network_writer.cc @@ -22,7 +22,8 @@ void NetworkWriter::PushSendToCall(const std::vector& msg, // 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 (!thread()->CheckPushSafety()) { - BA_LOG_ONCE("Excessive send-to calls in net-write-module."); + BA_LOG_ONCE(LogLevel::kError, + "Excessive send-to calls in net-write-module."); return; } thread()->PushCall([this, msg, addr] { diff --git a/src/ballistica/networking/networking.cc b/src/ballistica/networking/networking.cc index 3aaca894..7af90cf3 100644 --- a/src/ballistica/networking/networking.cc +++ b/src/ballistica/networking/networking.cc @@ -32,14 +32,14 @@ void Networking::HostScanCycle() { scan_socket_ = socket(AF_INET, SOCK_DGRAM, 0); if (scan_socket_ == -1) { - Log("Error opening scan socket: " + g_platform->GetSocketErrorString() - + "."); + Log(LogLevel::kError, "Error opening scan socket: " + + g_platform->GetSocketErrorString() + "."); return; } // Since this guy lives in the game-thread we need it to not block. if (!g_platform->SetSocketNonBlocking(scan_socket_)) { - Log("Error setting socket non-blocking."); + Log(LogLevel::kError, "Error setting socket non-blocking."); g_platform->CloseSocket(scan_socket_); scan_socket_ = -1; return; @@ -54,7 +54,8 @@ void Networking::HostScanCycle() { int result = ::bind(scan_socket_, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (result == 1) { - Log("Error binding socket: " + g_platform->GetSocketErrorString() + "."); + Log(LogLevel::kError, + "Error binding socket: " + g_platform->GetSocketErrorString() + "."); g_platform->CloseSocket(scan_socket_); scan_socket_ = -1; return; @@ -66,8 +67,8 @@ void Networking::HostScanCycle() { sizeof(op_val)); if (result != 0) { - Log("Error enabling broadcast for scan-socket: " - + g_platform->GetSocketErrorString() + "."); + Log(LogLevel::kError, "Error enabling broadcast for scan-socket: " + + g_platform->GetSocketErrorString() + "."); g_platform->CloseSocket(scan_socket_); scan_socket_ = -1; return; @@ -99,8 +100,8 @@ void Networking::HostScanCycle() { case ENETUNREACH: break; default: - Log("Error on scanSocket sendto: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Error on scanSocket sendto: " + + g_platform->GetSocketErrorString()); } } } @@ -122,7 +123,8 @@ void Networking::HostScanCycle() { case EWOULDBLOCK: break; default: - Log("Error: recvfrom error: " + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, + "Error: recvfrom error: " + g_platform->GetSocketErrorString()); break; } break; @@ -179,10 +181,12 @@ void Networking::HostScanCycle() { PruneScanResults(); } } else { - Log("Error: Got invalid BA_PACKET_GAME_QUERY_RESPONSE packet"); + Log(LogLevel::kError, + "Got invalid BA_PACKET_GAME_QUERY_RESPONSE packet"); } } else { - Log("Error: Got invalid BA_PACKET_GAME_QUERY_RESPONSE packet"); + Log(LogLevel::kError, + "Got invalid BA_PACKET_GAME_QUERY_RESPONSE packet"); } } } @@ -228,7 +232,8 @@ void Networking::EndHostScanning() { void Networking::Pause() { if (!running_) { - Log("Networking::pause() called with running_ already false"); + Log(LogLevel::kError, + "Networking::pause() called with running_ already false"); } running_ = false; @@ -238,7 +243,8 @@ void Networking::Pause() { void Networking::Resume() { if (running_) { - Log("Networking::resume() called with running_ already true"); + Log(LogLevel::kError, + "Networking::resume() called with running_ already true"); } running_ = true; } diff --git a/src/ballistica/networking/networking.h b/src/ballistica/networking/networking.h index 76f6766e..6554d5c5 100644 --- a/src/ballistica/networking/networking.h +++ b/src/ballistica/networking/networking.h @@ -112,7 +112,7 @@ namespace ballistica { #define HUFFMAN_TRAINING_MODE 0 #endif -// Bits used by the game thread for network communication. +// Bits used by the logic thread for network communication. class Networking { public: // Send a message to an address. This may block for a brief moment, so it can diff --git a/src/ballistica/networking/telnet_server.cc b/src/ballistica/networking/telnet_server.cc index 91c430c1..7a75d1f9 100644 --- a/src/ballistica/networking/telnet_server.cc +++ b/src/ballistica/networking/telnet_server.cc @@ -69,7 +69,8 @@ auto TelnetServer::RunThread() -> int { sd_ = socket(AF_INET, SOCK_STREAM, 0); if (sd_ < 0) { - Log("Error: Unable to open host socket; errno " + std::to_string(errno)); + Log(LogLevel::kError, + "Unable to open host socket; errno " + std::to_string(errno)); return 1; } @@ -78,7 +79,7 @@ auto TelnetServer::RunThread() -> int { int status = setsockopt(sd_, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)); if (-1 == status) { - Log("Error setting SO_REUSEADDR on telnet server"); + Log(LogLevel::kError, "Error setting SO_REUSEADDR on telnet server"); } // Bind to local server port. @@ -221,7 +222,7 @@ void TelnetServer::PushPrint(const std::string& s) { } void TelnetServer::Print(const std::string& s) { - // Currently we make the assumption that *only* the game thread writes to our + // Currently we make the assumption that *only* the logic thread writes to our // socket. assert(InLogicThread()); if (client_sd_ != -1) { diff --git a/src/ballistica/platform/apple/platform_apple.h b/src/ballistica/platform/apple/platform_apple.h index 90055735..f9a31860 100644 --- a/src/ballistica/platform/apple/platform_apple.h +++ b/src/ballistica/platform/apple/platform_apple.h @@ -25,7 +25,8 @@ class PlatformApple : public Platform { auto DoHasTouchScreen() -> bool override; auto GetUIScale() -> UIScale override; auto IsRunningOnDesktop() -> bool override; - auto HandleLog(const std::string& msg) -> void override; + auto DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) -> void override; auto SetupDataDirectory() -> void override; auto GetTextBoundsAndWidth(const std::string& text, Rect* r, float* width) -> void override; diff --git a/src/ballistica/platform/linux/platform_linux.cc b/src/ballistica/platform/linux/platform_linux.cc index de9f1ed9..58af4402 100644 --- a/src/ballistica/platform/linux/platform_linux.cc +++ b/src/ballistica/platform/linux/platform_linux.cc @@ -63,8 +63,8 @@ void PlatformLinux::OpenFileExternally(const std::string& path) { std::string cmd = std::string("xdg-open \"") + path + "\""; int result = system(cmd.c_str()); if (result != 0) { - Log("Error: Got return value " + std::to_string(result) - + " on xdg-open cmd '" + cmd + "'"); + Log(LogLevel::kError, "Got return value " + std::to_string(result) + + " on xdg-open cmd '" + cmd + "'"); } } @@ -72,8 +72,8 @@ void PlatformLinux::OpenDirExternally(const std::string& path) { std::string cmd = std::string("xdg-open \"") + path + "\""; int result = system(cmd.c_str()); if (result != 0) { - Log("Error: Got return value " + std::to_string(result) - + " on xdg-open cmd '" + cmd + "'"); + Log(LogLevel::kError, "Got return value " + std::to_string(result) + + " on xdg-open cmd '" + cmd + "'"); } } diff --git a/src/ballistica/platform/platform.cc b/src/ballistica/platform/platform.cc index 8ce6ada9..0fa607e7 100644 --- a/src/ballistica/platform/platform.cc +++ b/src/ballistica/platform/platform.cc @@ -169,10 +169,12 @@ auto Platform::GetLegacyDeviceUUID() -> const std::string& { legacy_device_uuid_ += val; if (FILE* f2 = FOpen(path.c_str(), "wb")) { size_t result = fwrite(val.c_str(), val.size(), 1, f2); - if (result != 1) Log("unable to write bsuuid file."); + if (result != 1) + Log(LogLevel::kError, "unable to write bsuuid file."); fclose(f2); } else { - Log("unable to open bsuuid file for writing: '" + path + "'"); + Log(LogLevel::kError, + "unable to open bsuuid file for writing: '" + path + "'"); } } } @@ -182,7 +184,7 @@ auto Platform::GetLegacyDeviceUUID() -> const std::string& { } auto Platform::GetDeviceV1AccountUUIDPrefix() -> std::string { - Log("GetDeviceV1AccountUUIDPrefix() unimplemented"); + Log(LogLevel::kError, "GetDeviceV1AccountUUIDPrefix() unimplemented"); return "u"; } @@ -261,10 +263,11 @@ void Platform::SetLowLevelConfigValue(const char* key, int value) { FILE* f = FOpen(path.c_str(), "w"); if (f) { size_t result = fwrite(out.c_str(), out.size(), 1, f); - if (result != 1) Log("unable to write low level config file."); + if (result != 1) + Log(LogLevel::kError, "unable to write low level config file."); fclose(f); } else { - Log("unable to open low level config file for writing."); + Log(LogLevel::kError, "unable to open low level config file for writing."); } } @@ -306,11 +309,10 @@ auto Platform::GetAppPythonDirectory() -> std::string { // Fall back to our default if that doesn't exist. if (FilePathExists(app_python_dir_)) { using_custom_app_python_dir_ = true; - Log("Using custom app Python path: '" - + (GetUserPythonDirectory() + BA_DIRSLASH + "sys" + BA_DIRSLASH - + kAppVersion) - + "'.", - true, false); + Log(LogLevel::kInfo, "Using custom app Python path: '" + + (GetUserPythonDirectory() + BA_DIRSLASH + "sys" + + BA_DIRSLASH + kAppVersion) + + "'."); } else { // Going with relative paths for cleaner tracebacks... @@ -484,7 +486,8 @@ auto Platform::GetLocale() -> std::string { return lang; } else { if (!g_buildconfig.headless_build()) { - BA_LOG_ONCE("No LANG value available; defaulting to en_US"); + BA_LOG_ONCE(LogLevel::kError, + "No LANG value available; defaulting to en_US"); } return "en_US"; } @@ -588,7 +591,7 @@ static void HandleArgs(int argc, char** argv) { exit(-1); } } else { - Log("ERROR: expected arg after -cfgdir"); + Log(LogLevel::kError, "Expected arg after -cfgdir."); exit(-1); } } @@ -672,7 +675,8 @@ auto Platform::GetUIScale() -> UIScale { return UIScale::kLarge; } -void Platform::HandleLog(const std::string& msg) { +void Platform::DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) { // Do nothing by default. } @@ -848,13 +852,13 @@ auto Platform::ConvertIncomingLeaderboardScore( void Platform::GetFriendScores(const std::string& game, const std::string& game_version, void* data) { // As a default, just fail gracefully. - Log("FIXME: GetFriendScores unimplemented"); + Log(LogLevel::kError, "FIXME: GetFriendScores unimplemented"); g_logic->PushFriendScoreSetCall(FriendScoreSet(false, data)); } void Platform::SubmitScore(const std::string& game, const std::string& version, int64_t score) { - Log("FIXME: SubmitScore() unimplemented"); + Log(LogLevel::kError, "FIXME: SubmitScore() unimplemented"); } void Platform::ReportAchievement(const std::string& achievement) {} @@ -866,13 +870,13 @@ auto Platform::HaveLeaderboard(const std::string& game, void Platform::EditText(const std::string& title, const std::string& value, int max_chars) { - Log("FIXME: EditText() unimplemented"); + Log(LogLevel::kError, "FIXME: EditText() unimplemented"); } void Platform::ShowOnlineScoreUI(const std::string& show, const std::string& game, const std::string& game_version) { - Log("FIXME: ShowOnlineScoreUI() unimplemented"); + Log(LogLevel::kError, "FIXME: ShowOnlineScoreUI() unimplemented"); } void Platform::Purchase(const std::string& item) { @@ -880,7 +884,9 @@ void Platform::Purchase(const std::string& item) { g_python->PushObjCall(Python::ObjID::kUnavailableMessageCall); } -void Platform::RestorePurchases() { Log("RestorePurchases() unimplemented"); } +void Platform::RestorePurchases() { + Log(LogLevel::kError, "RestorePurchases() unimplemented"); +} void Platform::AndroidSetResString(const std::string& res) { throw Exception(); @@ -889,11 +895,11 @@ void Platform::AndroidSetResString(const std::string& res) { void Platform::ApplyConfig() {} void Platform::AndroidSynthesizeBackPress() { - Log("AndroidSynthesizeBackPress() unimplemented"); + Log(LogLevel::kError, "AndroidSynthesizeBackPress() unimplemented"); } void Platform::AndroidQuitActivity() { - Log("AndroidQuitActivity() unimplemented"); + Log(LogLevel::kError, "AndroidQuitActivity() unimplemented"); } auto Platform::GetDeviceV1AccountID() -> std::string { @@ -920,7 +926,8 @@ auto Platform::DemangleCXXSymbol(const std::string& s) -> std::string { abi::__cxa_demangle(s.c_str(), nullptr, nullptr, &demangle_status); if (demangled_name != nullptr) { if (demangle_status != 0) { - BA_LOG_ONCE("__cxa_demangle got buffer but non-zero status; unexpected"); + BA_LOG_ONCE(LogLevel::kError, + "__cxa_demangle got buffer but non-zero status; unexpected"); } std::string retval = demangled_name; free(static_cast(demangled_name)); @@ -938,7 +945,7 @@ auto Platform::NewAutoReleasePool() -> void* { throw Exception(); } void Platform::DrainAutoReleasePool(void* pool) { throw Exception(); } void Platform::OpenURL(const std::string& url) { - // Can't open URLs in VR - just tell the game thread to show the url. + // Can't open URLs in VR - just tell the logic thread to show the url. if (IsVRMode()) { g_logic->PushShowURLCall(url); return; @@ -949,16 +956,18 @@ void Platform::OpenURL(const std::string& url) { } void Platform::DoOpenURL(const std::string& url) { - Log("DoOpenURL unimplemented on this platform."); + Log(LogLevel::kError, "DoOpenURL unimplemented on this platform."); } -void Platform::ResetAchievements() { Log("ResetAchievements() unimplemented"); } +void Platform::ResetAchievements() { + Log(LogLevel::kError, "ResetAchievements() unimplemented"); +} void Platform::GameCenterLogin() { throw Exception(); } void Platform::PurchaseAck(const std::string& purchase, const std::string& order_id) { - Log("PurchaseAck() unimplemented"); + Log(LogLevel::kError, "PurchaseAck() unimplemented"); } void Platform::RunEvents() {} @@ -969,17 +978,18 @@ void Platform::OnAppPause() {} void Platform::OnAppResume() {} void Platform::MusicPlayerPlay(PyObject* target) { - Log("MusicPlayerPlay() unimplemented on this platform"); + Log(LogLevel::kError, "MusicPlayerPlay() unimplemented on this platform"); } void Platform::MusicPlayerStop() { - Log("MusicPlayerStop() unimplemented on this platform"); + Log(LogLevel::kError, "MusicPlayerStop() unimplemented on this platform"); } void Platform::MusicPlayerShutdown() { - Log("MusicPlayerShutdown() unimplemented on this platform"); + Log(LogLevel::kError, "MusicPlayerShutdown() unimplemented on this platform"); } void Platform::MusicPlayerSetVolume(float volume) { - Log("MusicPlayerSetVolume() unimplemented on this platform"); + Log(LogLevel::kError, + "MusicPlayerSetVolume() unimplemented on this platform"); } auto Platform::IsOSPlayingMusic() -> bool { return false; } @@ -987,7 +997,7 @@ auto Platform::IsOSPlayingMusic() -> bool { return false; } void Platform::AndroidShowAppInvite(const std::string& title, const std::string& message, const std::string& code) { - Log("AndroidShowAppInvite() unimplemented"); + Log(LogLevel::kError, "AndroidShowAppInvite() unimplemented"); } void Platform::IncrementAnalyticsCount(const std::string& name, int increment) { @@ -1006,11 +1016,11 @@ void Platform::SubmitAnalyticsCounts() {} void Platform::SetPlatformMiscReadVals(const std::string& vals) {} void Platform::AndroidRefreshFile(const std::string& file) { - Log("AndroidRefreshFile() unimplemented"); + Log(LogLevel::kError, "AndroidRefreshFile() unimplemented"); } void Platform::ShowAd(const std::string& purpose) { - Log("ShowAd() unimplemented"); + Log(LogLevel::kError, "ShowAd() unimplemented"); } auto Platform::GetHasAds() -> bool { return false; } @@ -1021,17 +1031,19 @@ auto Platform::GetHasVideoAds() -> bool { } void Platform::SignInV1(const std::string& account_type) { - Log("SignInV1() unimplemented"); + Log(LogLevel::kError, "SignInV1() unimplemented"); } void Platform::V1LoginDidChange() { // Default is no-op. } -void Platform::SignOutV1() { Log("SignOutV1() unimplemented"); } +void Platform::SignOutV1() { + Log(LogLevel::kError, "SignOutV1() unimplemented"); +} void Platform::AndroidShowWifiSettings() { - Log("AndroidShowWifiSettings() unimplemented"); + Log(LogLevel::kError, "AndroidShowWifiSettings() unimplemented"); } void Platform::SetHardwareCursorVisible(bool visible) { @@ -1044,40 +1056,40 @@ void Platform::SetHardwareCursorVisible(bool visible) { auto Platform::QuitApp() -> void { exit(g_app->return_value); } auto Platform::OpenFileExternally(const std::string& path) -> void { - Log("OpenFileExternally() unimplemented"); + Log(LogLevel::kError, "OpenFileExternally() unimplemented"); } auto Platform::OpenDirExternally(const std::string& path) -> void { - Log("OpenDirExternally() unimplemented"); + Log(LogLevel::kError, "OpenDirExternally() unimplemented"); } auto Platform::MacMusicAppInit() -> void { - Log("MacMusicAppInit() unimplemented"); + Log(LogLevel::kError, "MacMusicAppInit() unimplemented"); } auto Platform::MacMusicAppGetVolume() -> int { - Log("MacMusicAppGetVolume() unimplemented"); + Log(LogLevel::kError, "MacMusicAppGetVolume() unimplemented"); return 0; } auto Platform::MacMusicAppSetVolume(int volume) -> void { - Log("MacMusicAppSetVolume() unimplemented"); + Log(LogLevel::kError, "MacMusicAppSetVolume() unimplemented"); } auto Platform::MacMusicAppGetLibrarySource() -> void { - Log("MacMusicAppGetLibrarySource() unimplemented"); + Log(LogLevel::kError, "MacMusicAppGetLibrarySource() unimplemented"); } auto Platform::MacMusicAppStop() -> void { - Log("MacMusicAppStop() unimplemented"); + Log(LogLevel::kError, "MacMusicAppStop() unimplemented"); } auto Platform::MacMusicAppPlayPlaylist(const std::string& playlist) -> bool { - Log("MacMusicAppPlayPlaylist() unimplemented"); + Log(LogLevel::kError, "MacMusicAppPlayPlaylist() unimplemented"); return false; } auto Platform::MacMusicAppGetPlaylists() -> std::list { - Log("MacMusicAppGetPlaylists() unimplemented"); + Log(LogLevel::kError, "MacMusicAppGetPlaylists() unimplemented"); return {}; } @@ -1181,8 +1193,8 @@ auto Platform::SetSocketNonBlocking(int sd) -> bool { #else int result = fcntl(sd, F_SETFL, O_NONBLOCK); if (result != 0) { - Log("Error setting non-blocking socket: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Error setting non-blocking socket: " + + g_platform->GetSocketErrorString()); return false; } return true; @@ -1280,7 +1292,7 @@ static void HandleSIGINT(int s) { if (g_logic) { g_logic->PushInterruptSignalCall(); } else { - Log("SigInt handler called before g_logic exists."); + Log(LogLevel::kError, "SigInt handler called before g_logic exists."); } } #endif diff --git a/src/ballistica/platform/platform.h b/src/ballistica/platform/platform.h index e1ced644..77932c2b 100644 --- a/src/ballistica/platform/platform.h +++ b/src/ballistica/platform/platform.h @@ -126,10 +126,10 @@ class Platform { #pragma mark PRINTING/LOGGING -------------------------------------------------- - // Send a message to the default platform handler. - // IMPORTANT: No Object::Refs should be created or destroyed within this call, - // or deadlock can occur. - virtual auto HandleLog(const std::string& msg) -> void; + /// Display a message to any default log for the platform (android log, etc.) + /// Note that this can be called from any thread. + virtual auto DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) -> void; #pragma mark ENVIRONMENT ------------------------------------------------------- @@ -427,11 +427,10 @@ class Platform { -> void; /// Print a log message to be included in crash logs or other debug - /// mechanisms. Standard log messages (at least with to_server=true) get - /// send here as well. It can be useful to call this directly to report - /// extra details that may help in debugging, as these calls are not - /// considered 'noteworthy' or presented to the user as standard Log() - /// calls are. + /// mechanisms (example: Crashlytics). V1-cloud-log messages get forwarded + /// to here as well. It can be useful to call this directly to report extra + /// details that may help in debugging, as these calls are not considered + /// 'noteworthy' or presented to the user as standard Log() calls are. virtual auto HandleDebugLog(const std::string& msg) -> void; static auto DebugLog(const std::string& msg) -> void { diff --git a/src/ballistica/platform/sdl/sdl_app.cc b/src/ballistica/platform/sdl/sdl_app.cc index 24801293..3f28f087 100644 --- a/src/ballistica/platform/sdl/sdl_app.cc +++ b/src/ballistica/platform/sdl/sdl_app.cc @@ -43,8 +43,8 @@ void SDLApp::HandleSDLEvent(const SDL_Event& event) { g_input->PushJoystickEvent(event, js); } } else { - Log("Error: Unable to get SDL Joystick for event type " - + std::to_string(event.type)); + Log(LogLevel::kError, "Unable to get SDL Joystick for event type " + + std::to_string(event.type)); } break; } @@ -245,8 +245,8 @@ auto FilterSDLEvent(const SDL_Event* event) -> int { return true; // sdl should keep this. } } catch (const std::exception& e) { - BA_LOG_ONCE(std::string("Exception in inline SDL-Event handling: ") - + e.what()); + BA_LOG_ONCE(LogLevel::kError, + std::string("Error in inline SDL-Event handling: ") + e.what()); throw; } } @@ -369,7 +369,7 @@ void SDLApp::DoSwap() { if (g_buildconfig.debug_build()) { millisecs_t diff = GetRealTime() - swap_start_time_; if (diff > 5) { - Log("WARNING: Swap handling delay of " + std::to_string(diff)); + Log(LogLevel::kWarning, "Swap handling delay of " + std::to_string(diff)); } } @@ -514,14 +514,15 @@ void SDLApp::SDLJoystickConnected(int device_index) { // We add all existing inputs when bootstrapping is complete; we should // never be getting these before that happens. if (g_input == nullptr || g_app_flavor == nullptr || !IsBootstrapped()) { - Log("Unexpected SDLJoystickConnected early in boot sequence."); + Log(LogLevel::kError, + "Unexpected SDLJoystickConnected early in boot sequence."); return; } // Create the joystick here in the main thread and then pass it over to the - // game thread to be added to the game. + // logic thread to be added to the game. if (g_buildconfig.ostype_ios_tvos()) { - BA_LOG_ONCE("WTF GOT SDL-JOY-CONNECTED ON IOS"); + BA_LOG_ONCE(LogLevel::kError, "WTF GOT SDL-JOY-CONNECTED ON IOS"); } else { auto* j = Object::NewDeferred(device_index); if (g_buildconfig.sdl2_build() && g_buildconfig.enable_sdl_joysticks()) { @@ -566,9 +567,9 @@ void SDLApp::RemoveSDLInputDevice(int index) { if (static_cast_check_fit(sdl_joysticks_.size()) > index) { sdl_joysticks_[index] = nullptr; } else { - Log("Error: Invalid index on RemoveSDLInputDevice: size is " - + std::to_string(sdl_joysticks_.size()) + "; index is " - + std::to_string(index)); + Log(LogLevel::kError, "Invalid index on RemoveSDLInputDevice: size is " + + std::to_string(sdl_joysticks_.size()) + + "; index is " + std::to_string(index)); } g_input->PushRemoveInputDeviceCall(j, true); } diff --git a/src/ballistica/platform/stdio_console.cc b/src/ballistica/platform/stdio_console.cc index 9e7036aa..0ef6f190 100644 --- a/src/ballistica/platform/stdio_console.cc +++ b/src/ballistica/platform/stdio_console.cc @@ -77,8 +77,8 @@ auto StdioConsole::OnAppStart() -> void { } } } else { - Log("StdioConsole got non-eof error reading stdin: " - + std::to_string(ferror(stdin))); + Log(LogLevel::kError, "StdioConsole got non-eof error reading stdin: " + + std::to_string(ferror(stdin))); } break; } diff --git a/src/ballistica/platform/windows/platform_windows.cc b/src/ballistica/platform/windows/platform_windows.cc index 9269f3ad..81376aa1 100644 --- a/src/ballistica/platform/windows/platform_windows.cc +++ b/src/ballistica/platform/windows/platform_windows.cc @@ -111,7 +111,7 @@ BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) { if (g_logic) { g_logic->PushInterruptSignalCall(); } else { - Log("SigInt handler called before g_logic exists."); + Log(LogLevel::kError, "SigInt handler called before g_logic exists."); } return TRUE; @@ -123,7 +123,7 @@ BOOL WINAPI CtrlHandler(DWORD fdwCtrlType) { void PlatformWindows::SetupInterruptHandling() { // Set up Ctrl-C handling. if (!SetConsoleCtrlHandler(CtrlHandler, TRUE)) { - Log("Error on SetConsoleCtrlHandler()"); + Log(LogLevel::kError, "Error on SetConsoleCtrlHandler()"); } } @@ -719,10 +719,11 @@ std::string PlatformWindows::DoGetDeviceName() { bool PlatformWindows::DoHasTouchScreen() { return false; } -void PlatformWindows::HandleLog(const std::string& msg) { +void PlatformWindows::DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) { // if (have_stdin_stdout_) { // // On headless builds we use default handler (simple stdout). - // return Platform::HandleLog(msg); + // return Platform::DisplayLog(msg); // } // Also spit this out as a debug-string for when running from msvc. @@ -822,7 +823,8 @@ void PlatformWindows::DoOpenURL(const std::string& url) { // This should return > 32 on success. if (r <= 32) { - Log("Error " + std::to_string(r) + " opening URL '" + url + "'"); + Log(LogLevel::kError, + "Error " + std::to_string(r) + " opening URL '" + url + "'"); } } @@ -831,8 +833,8 @@ void PlatformWindows::OpenFileExternally(const std::string& path) { ShellExecute(nullptr, _T("open"), _T("notepad.exe"), utf8_decode(path).c_str(), nullptr, SW_SHOWNORMAL)); if (r <= 32) { - Log("Error " + std::to_string(r) + " on open_file_externally for '" + path - + "'"); + Log(LogLevel::kError, "Error " + std::to_string(r) + + " on open_file_externally for '" + path + "'"); } } @@ -841,8 +843,8 @@ void PlatformWindows::OpenDirExternally(const std::string& path) { ShellExecute(nullptr, _T("open"), _T("explorer.exe"), utf8_decode(path).c_str(), nullptr, SW_SHOWNORMAL)); if (r <= 32) { - Log("Error " + std::to_string(r) + " on open_dir_externally for '" + path - + "'"); + Log(LogLevel::kError, "Error " + std::to_string(r) + + " on open_dir_externally for '" + path + "'"); } } @@ -873,15 +875,15 @@ std::vector PlatformWindows::GetBroadcastAddrs() { pIPAddrTable = static_cast(MALLOC(dwSize)); } if (pIPAddrTable == nullptr) { - Log("Error: Memory allocation failed for GetIpAddrTable\n"); + Log(LogLevel::kError, "Memory allocation failed for GetIpAddrTable\n"); err = true; } if (!err) { // Make a second call to GetIpAddrTable to get the actual data we want if ((dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) != NO_ERROR) { - Log("Error: GetIpAddrTable failed with error " - + std::to_string(dwRetVal)); + Log(LogLevel::kError, + "GetIpAddrTable failed with error " + std::to_string(dwRetVal)); err = true; } } @@ -916,8 +918,8 @@ bool PlatformWindows::SetSocketNonBlocking(int sd) { unsigned long dataval = 1; // NOLINT (func signature wants long) int result = ioctlsocket(sd, FIONBIO, &dataval); if (result != 0) { - Log("Error setting non-blocking socket: " - + g_platform->GetSocketErrorString()); + Log(LogLevel::kError, "Error setting non-blocking socket: " + + g_platform->GetSocketErrorString()); return false; } return true; diff --git a/src/ballistica/platform/windows/platform_windows.h b/src/ballistica/platform/windows/platform_windows.h index ba7566a5..7857aca8 100644 --- a/src/ballistica/platform/windows/platform_windows.h +++ b/src/ballistica/platform/windows/platform_windows.h @@ -32,7 +32,8 @@ class PlatformWindows : public Platform { auto GetLocale() -> std::string override; auto DoGetDeviceName() -> std::string override; auto DoHasTouchScreen() -> bool override; - void HandleLog(const std::string& msg) override; + void DisplayLog(const std::string& name, LogLevel level, + const std::string& msg) override; void SetupDataDirectory() override; void SetEnv(const std::string& name, const std::string& value) override; auto GetIsStdinATerminal() -> bool override; diff --git a/src/ballistica/python/class/python_class_activity_data.cc b/src/ballistica/python/class/python_class_activity_data.cc index b08927f9..14bd3ffc 100644 --- a/src/ballistica/python/class/python_class_activity_data.cc +++ b/src/ballistica/python/class/python_class_activity_data.cc @@ -69,7 +69,7 @@ auto PythonClassActivityData::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } self->host_activity_ = new Object::WeakRef(); @@ -81,7 +81,7 @@ auto PythonClassActivityData::tp_new(PyTypeObject* type, PyObject* args, void PythonClassActivityData::tp_dealloc(PythonClassActivityData* self) { BA_PYTHON_TRY; - // These have to be destructed in the game thread; send them along to + // These have to be destructed in the logic thread; send them along to // it if need be; otherwise do it immediately. if (!InLogicThread()) { Object::WeakRef* h = self->host_activity_; diff --git a/src/ballistica/python/class/python_class_collide_model.cc b/src/ballistica/python/class/python_class_collide_model.cc index 6616aadc..086c3b35 100644 --- a/src/ballistica/python/class/python_class_collide_model.cc +++ b/src/ballistica/python/class/python_class_collide_model.cc @@ -71,7 +71,7 @@ auto PythonClassCollideModel::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } @@ -101,7 +101,7 @@ void PythonClassCollideModel::Delete(Object::Ref* ref) { void PythonClassCollideModel::tp_dealloc(PythonClassCollideModel* self) { BA_PYTHON_TRY; - // these have to be deleted in the game thread - send the ptr along if need + // these have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately if (!InLogicThread()) { Object::Ref* c = self->collide_model_; diff --git a/src/ballistica/python/class/python_class_context.cc b/src/ballistica/python/class/python_class_context.cc index 2b4c52ab..21915540 100644 --- a/src/ballistica/python/class/python_class_context.cc +++ b/src/ballistica/python/class/python_class_context.cc @@ -136,7 +136,7 @@ auto PythonClassContext::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } @@ -147,7 +147,8 @@ auto PythonClassContext::tp_new(PyTypeObject* type, PyObject* args, if (source == "ui") { cs = Context(g_logic->GetUIContextTarget()); } else if (source == "UI") { - BA_LOG_ONCE("'UI' context-target option is deprecated; please use 'ui'"); + BA_LOG_ONCE(LogLevel::kError, + "'UI' context-target option is deprecated; please use 'ui'"); Python::PrintStackTrace(); cs = Context(g_logic->GetUIContextTarget()); } else if (source == "current") { @@ -183,7 +184,7 @@ auto PythonClassContext::tp_new(PyTypeObject* type, PyObject* args, void PythonClassContext::tp_dealloc(PythonClassContext* self) { BA_PYTHON_TRY; - // Contexts have to be deleted in the game thread; + // Contexts have to be deleted in the logic thread; // ship them to it for deletion if need be; otherwise do it immediately. if (!InLogicThread()) { Context* c = self->context_; diff --git a/src/ballistica/python/class/python_class_context_call.cc b/src/ballistica/python/class/python_class_context_call.cc index 2aa433dc..81cfd2cb 100644 --- a/src/ballistica/python/class/python_class_context_call.cc +++ b/src/ballistica/python/class/python_class_context_call.cc @@ -103,7 +103,7 @@ auto PythonClassContextCall::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } self->context_call_ = new Object::Ref( @@ -115,7 +115,7 @@ auto PythonClassContextCall::tp_new(PyTypeObject* type, PyObject* args, void PythonClassContextCall::tp_dealloc(PythonClassContextCall* self) { BA_PYTHON_TRY; - // these have to be deleted in the game thread - send the ptr along if need + // these have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately if (!InLogicThread()) { Object::Ref* c = self->context_call_; diff --git a/src/ballistica/python/class/python_class_data.cc b/src/ballistica/python/class/python_class_data.cc index 4f6584dc..eaebd5f9 100644 --- a/src/ballistica/python/class/python_class_data.cc +++ b/src/ballistica/python/class/python_class_data.cc @@ -68,7 +68,7 @@ auto PythonClassData::tp_new(PyTypeObject* type, PyObject* args, PyObject* kwds) if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } @@ -98,7 +98,7 @@ void PythonClassData::Delete(Object::Ref* ref) { void PythonClassData::tp_dealloc(PythonClassData* self) { BA_PYTHON_TRY; - // these have to be deleted in the game thread - send the ptr along if need + // these have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately if (!InLogicThread()) { Object::Ref* s = self->data_; diff --git a/src/ballistica/python/class/python_class_input_device.cc b/src/ballistica/python/class/python_class_input_device.cc index d9b6ecd6..8f262fe3 100644 --- a/src/ballistica/python/class/python_class_input_device.cc +++ b/src/ballistica/python/class/python_class_input_device.cc @@ -126,7 +126,7 @@ auto PythonClassInputDevice::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } self->input_device_ = new Object::WeakRef(); @@ -137,7 +137,7 @@ auto PythonClassInputDevice::tp_new(PyTypeObject* type, PyObject* args, void PythonClassInputDevice::tp_dealloc(PythonClassInputDevice* self) { BA_PYTHON_TRY; - // These have to be destructed in the game thread - send them along to it if + // These have to be destructed in the logic thread - send them along to it if // need be. // FIXME: Technically the main thread has a pointer to a dead PyObject // until the delete goes through; could that ever be a problem? @@ -391,7 +391,8 @@ auto PythonClassInputDevice::GetButtonName(PythonClassInputDevice* self, PythonRef results = g_python->obj(Python::ObjID::kLstrFromJsonCall).Call(args2); if (!results.exists()) { - Log("Error creating Lstr from raw button name: '" + bname + "'"); + Log(LogLevel::kError, + "Error creating Lstr from raw button name: '" + bname + "'"); PythonRef args3(Py_BuildValue("(s)", "?"), PythonRef::kSteal); results = g_python->obj(Python::ObjID::kLstrFromJsonCall).Call(args3); } diff --git a/src/ballistica/python/class/python_class_material.cc b/src/ballistica/python/class/python_class_material.cc index c1a049ad..a1c6cfe1 100644 --- a/src/ballistica/python/class/python_class_material.cc +++ b/src/ballistica/python/class/python_class_material.cc @@ -92,7 +92,7 @@ auto PythonClassMaterial::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } PyObject* name_obj = Py_None; @@ -144,7 +144,7 @@ void PythonClassMaterial::Delete(Object::Ref* m) { void PythonClassMaterial::tp_dealloc(PythonClassMaterial* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread - push a call if + // These have to be deleted in the logic thread - push a call if // need be.. otherwise do it immediately. if (!InLogicThread()) { Object::Ref* ptr = self->material_; diff --git a/src/ballistica/python/class/python_class_model.cc b/src/ballistica/python/class/python_class_model.cc index 7e0d637d..6da13d4e 100644 --- a/src/ballistica/python/class/python_class_model.cc +++ b/src/ballistica/python/class/python_class_model.cc @@ -69,7 +69,7 @@ auto PythonClassModel::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } if (!s_create_empty_) { @@ -99,7 +99,7 @@ void PythonClassModel::Delete(Object::Ref* ref) { void PythonClassModel::tp_dealloc(PythonClassModel* self) { BA_PYTHON_TRY; - // these have to be deleted in the game thread - send the ptr along if need + // these have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately if (!InLogicThread()) { Object::Ref* m = self->model_; diff --git a/src/ballistica/python/class/python_class_node.cc b/src/ballistica/python/class/python_class_node.cc index ef4b67ed..e8a31a32 100644 --- a/src/ballistica/python/class/python_class_node.cc +++ b/src/ballistica/python/class/python_class_node.cc @@ -87,7 +87,7 @@ auto PythonClassNode::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } // Clion incorrectly things s_create_empty will always be false. @@ -110,7 +110,7 @@ auto PythonClassNode::tp_new(PyTypeObject* type, PyObject* args, void PythonClassNode::tp_dealloc(PythonClassNode* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread; send the ptr along if need + // These have to be deleted in the logic thread; send the ptr along if need // be; otherwise do it immediately. if (!InLogicThread()) { Object::WeakRef* n = self->node_; diff --git a/src/ballistica/python/class/python_class_session_data.cc b/src/ballistica/python/class/python_class_session_data.cc index db534145..1dc4c396 100644 --- a/src/ballistica/python/class/python_class_session_data.cc +++ b/src/ballistica/python/class/python_class_session_data.cc @@ -66,7 +66,7 @@ auto PythonClassSessionData::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } self->session_ = new Object::WeakRef(); @@ -77,7 +77,7 @@ auto PythonClassSessionData::tp_new(PyTypeObject* type, PyObject* args, void PythonClassSessionData::tp_dealloc(PythonClassSessionData* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread; + // These have to be deleted in the logic thread; // ...send the ptr along if need be. // FIXME: technically the main thread has a pointer to a dead PyObject // until the delete goes through; could that ever be a problem? diff --git a/src/ballistica/python/class/python_class_session_player.cc b/src/ballistica/python/class/python_class_session_player.cc index 9e92af8f..66f6f36d 100644 --- a/src/ballistica/python/class/python_class_session_player.cc +++ b/src/ballistica/python/class/python_class_session_player.cc @@ -157,7 +157,7 @@ auto PythonClassSessionPlayer::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } @@ -184,7 +184,7 @@ auto PythonClassSessionPlayer::tp_new(PyTypeObject* type, PyObject* args, void PythonClassSessionPlayer::tp_dealloc(PythonClassSessionPlayer* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread - send the ptr along if need + // These have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately. if (!InLogicThread()) { Object::WeakRef* p = self->player_; @@ -258,8 +258,9 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, throw Exception(PyExcType::kSessionPlayerNotFound); } if (!p->has_py_data()) { - BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s) - + "' without data set."); + BA_LOG_ONCE(LogLevel::kError, "Calling getAttr for player attr '" + + std::string(s) + + "' without data set."); } PyObject* obj = p->GetPyCharacter(); Py_INCREF(obj); @@ -270,8 +271,9 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, throw Exception(PyExcType::kSessionPlayerNotFound); } if (!p->has_py_data()) { - BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s) - + "' without data set."); + BA_LOG_ONCE(LogLevel::kError, "Calling getAttr for player attr '" + + std::string(s) + + "' without data set."); } PyObject* obj = p->GetPyColor(); Py_INCREF(obj); @@ -282,8 +284,9 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, throw Exception(PyExcType::kSessionPlayerNotFound); } if (!p->has_py_data()) { - BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s) - + "' without data set."); + BA_LOG_ONCE(LogLevel::kError, "Calling getAttr for player attr '" + + std::string(s) + + "' without data set."); } PyObject* obj = p->GetPyHighlight(); Py_INCREF(obj); @@ -294,8 +297,9 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, throw Exception(PyExcType::kSessionPlayerNotFound); } if (!p->has_py_data()) { - BA_LOG_ONCE("Error: Calling getAttr for player attr '" + std::string(s) - + "' without data set."); + BA_LOG_ONCE(LogLevel::kError, "Calling getAttr for player attr '" + + std::string(s) + + "' without data set."); } PyObject* obj = p->GetPyActivityPlayer(); Py_INCREF(obj); diff --git a/src/ballistica/python/class/python_class_sound.cc b/src/ballistica/python/class/python_class_sound.cc index 59491749..6a76d646 100644 --- a/src/ballistica/python/class/python_class_sound.cc +++ b/src/ballistica/python/class/python_class_sound.cc @@ -68,7 +68,7 @@ auto PythonClassSound::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } if (!s_create_empty_) { @@ -98,7 +98,7 @@ void PythonClassSound::Delete(Object::Ref* ref) { void PythonClassSound::tp_dealloc(PythonClassSound* self) { BA_PYTHON_TRY; - // these have to be deleted in the game thread - send the ptr along if need + // these have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately if (!InLogicThread()) { Object::Ref* s = self->sound_; diff --git a/src/ballistica/python/class/python_class_texture.cc b/src/ballistica/python/class/python_class_texture.cc index 33d13cd4..adb4b4b5 100644 --- a/src/ballistica/python/class/python_class_texture.cc +++ b/src/ballistica/python/class/python_class_texture.cc @@ -63,7 +63,7 @@ auto PythonClassTexture::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } if (!s_create_empty_) { @@ -91,7 +91,7 @@ void PythonClassTexture::Delete(Object::Ref* ref) { void PythonClassTexture::tp_dealloc(PythonClassTexture* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread - send the ptr along if need + // These have to be deleted in the logic thread - send the ptr along if need // be; otherwise do it immediately. if (!InLogicThread()) { Object::Ref* t = self->texture_; diff --git a/src/ballistica/python/class/python_class_timer.cc b/src/ballistica/python/class/python_class_timer.cc index ac553819..1895f793 100644 --- a/src/ballistica/python/class/python_class_timer.cc +++ b/src/ballistica/python/class/python_class_timer.cc @@ -76,7 +76,7 @@ auto PythonClassTimer::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } @@ -161,7 +161,7 @@ void PythonClassTimer::DoDelete(bool have_timer, TimeType time_type, void PythonClassTimer::tp_dealloc(PythonClassTimer* self) { BA_PYTHON_TRY; - // These have to be deleted in the game thread. + // These have to be deleted in the logic thread. if (!InLogicThread()) { auto a0 = self->have_timer_; auto a1 = self->time_type_; diff --git a/src/ballistica/python/class/python_class_widget.cc b/src/ballistica/python/class/python_class_widget.cc index 8ebf2b39..684fa4c1 100644 --- a/src/ballistica/python/class/python_class_widget.cc +++ b/src/ballistica/python/class/python_class_widget.cc @@ -78,7 +78,7 @@ auto PythonClassWidget::tp_new(PyTypeObject* type, PyObject* args, if (!InLogicThread()) { throw Exception( "ERROR: " + std::string(type_obj.tp_name) - + " objects must only be created in the game thread (current is (" + + " objects must only be created in the logic thread (current is (" + GetCurrentThreadName() + ")."); } self->widget_ = new Object::WeakRef(); @@ -89,7 +89,7 @@ auto PythonClassWidget::tp_new(PyTypeObject* type, PyObject* args, void PythonClassWidget::tp_dealloc(PythonClassWidget* self) { BA_PYTHON_TRY; - // these have to be destructed in the game thread - send them along to it if + // these have to be destructed in the logic thread - send them along to it if // need be if (!InLogicThread()) { Object::WeakRef* w = self->widget_; @@ -222,7 +222,7 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args, if (p) { p->DeleteWidget(w); } else { - Log("Error: Can't delete widget: no parent."); + Log(LogLevel::kError, "Can't delete widget: no parent."); } } Py_RETURN_NONE; diff --git a/src/ballistica/python/methods/python_methods_app.cc b/src/ballistica/python/methods/python_methods_app.cc index d8a7fc6f..44f2e45b 100644 --- a/src/ballistica/python/methods/python_methods_app.cc +++ b/src/ballistica/python/methods/python_methods_app.cc @@ -197,7 +197,7 @@ auto PyGetForegroundHostSession(PyObject* self, PyObject* args, return nullptr; } - // Note: we return None if not in the game thread. + // Note: we return None if not in the logic thread. HostSession* s = InLogicThread() ? g_logic->GetForegroundContext().GetHostSession() : nullptr; @@ -259,7 +259,7 @@ auto PyGetActivity(PyObject* self, PyObject* args, PyObject* keywds) return nullptr; } - // Fail gracefully if called from outside the game thread. + // Fail gracefully if called from outside the logic thread. if (!InLogicThread()) { Py_RETURN_NONE; } @@ -292,7 +292,7 @@ auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { // The from-other-thread case is basically a different call. if (from_other_thread) { - // Warn the user not to use this from the game thread since it doesnt + // Warn the user not to use this from the logic thread since it doesnt // save/restore context. if (!suppress_warning && InLogicThread()) { g_python->IssueCallInLogicThreadWarning(call_obj); @@ -301,7 +301,7 @@ auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { // This gets called from other python threads so we can't construct // Objects and things here or we'll trip our thread-checks. Instead we // just increment the python object's refcount and pass it along raw; - // the game thread decrements it on the other end. + // the logic thread decrements it on the other end. Py_INCREF(call_obj); g_logic->PushPythonRawCallable(call_obj); } else { @@ -461,7 +461,7 @@ auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds) return nullptr; } if (log) { - Log(message); + Log(LogLevel::kInfo, message); } // Transient messages get sent to clients as high-level messages instead of @@ -552,7 +552,7 @@ auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds) tint_color.x, tint_color.y, tint_color.z, tint2_color.x, tint2_color.y, tint2_color.z); } else { - Log("Error: unhandled screenmessage output_stream case"); + Log(LogLevel::kError, "Unhandled screenmessage output_stream case."); } } @@ -581,7 +581,7 @@ auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { if (g_buildconfig.ostype_ios_tvos()) { // This should never be called on iOS - Log("Error: Quit called."); + Log(LogLevel::kError, "Quit called."); } bool handled = false; @@ -628,7 +628,7 @@ auto PyBless(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { auto PyApplyConfig(PyObject* self, PyObject* args) -> PyObject* { BA_PYTHON_TRY; - // Hmm; python runs in the game thread; technically we could just run + // Hmm; python runs in the logic thread; technically we could just run // ApplyConfig() immediately (though pushing is probably safer). g_logic->PushApplyConfigCall(); Py_RETURN_NONE; @@ -807,42 +807,53 @@ auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* { BA_PYTHON_CATCH; } -auto PyPrintStdout(PyObject* self, PyObject* args) -> PyObject* { +auto PyDisplayLog(PyObject* self, PyObject* args, PyObject* keywds) + -> PyObject* { BA_PYTHON_TRY; - const char* s; - if (!PyArg_ParseTuple(args, "s", &s)) { + static const char* kwlist[] = {"name", "level", "message", nullptr}; + const char* name; + const char* levelstr; + const char* message; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "sss", + const_cast(kwlist), &name, &levelstr, + &message)) { return nullptr; } - Logging::PrintStdout(s); + + // Calc LogLevel enum val from their string val. + LogLevel level; + if (levelstr == std::string("DEBUG")) { + level = LogLevel::kDebug; + } else if (levelstr == std::string("INFO")) { + level = LogLevel::kInfo; + } else if (levelstr == std::string("WARNING")) { + level = LogLevel::kWarning; + } else if (levelstr == std::string("ERROR")) { + level = LogLevel::kError; + } else if (levelstr == std::string("CRITICAL")) { + level = LogLevel::kCritical; + } else { + // Assume we should avoid Log() calls here since it could infinite loop. + fprintf(stderr, "Invalid log level to display_log(): %s\n", levelstr); + level = LogLevel::kInfo; + } + Logging::DisplayLog(name, level, message); + Py_RETURN_NONE; BA_PYTHON_CATCH; } -auto PyPrintStderr(PyObject* self, PyObject* args) -> PyObject* { +auto PyV1CloudLog(PyObject* self, PyObject* args, PyObject* keywds) + -> PyObject* { BA_PYTHON_TRY; - const char* s; - if (!PyArg_ParseTuple(args, "s", &s)) { + const char* message; + static const char* kwlist[] = {"message", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s", + const_cast(kwlist), &message)) { return nullptr; } - Logging::PrintStderr(s); - Py_RETURN_NONE; - BA_PYTHON_CATCH; -} + Logging::V1CloudLog(message); -auto PyLog(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { - BA_PYTHON_TRY; - static const char* kwlist[] = {"message", "to_stdout", "to_server", nullptr}; - int to_server = 1; - int to_stdout = 1; - std::string message; - PyObject* message_obj; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|pp", - const_cast(kwlist), &message_obj, - &to_stdout, &to_server)) { - return nullptr; - } - message = Python::GetPyString(message_obj); - Log(message, static_cast(to_stdout), static_cast(to_server)); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -900,39 +911,22 @@ auto PythonMethodsApp::GetMethods() -> std::vector { "(for helping with the transition from milliseconds-based time calls\n" "to seconds-based ones)"}, - {"log", (PyCFunction)PyLog, METH_VARARGS | METH_KEYWORDS, - "log(message: str, to_stdout: bool = True,\n" - " to_server: bool = True) -> None\n" - "\n" - "Category: **General Utility Functions**\n" - "\n" - "Log a message. This goes to the default logging mechanism depending\n" - "on the platform (stdout on mac, android log on android, etc).\n" - "\n" - "Log messages also go to the in-game console unless 'to_console'\n" - "is False. They are also sent to the master-server for use in " - "analyzing\n" - "issues unless to_server is False.\n" - "\n" - "Python's standard print() is wired to call this (with default " - "values)\n" - "so in most cases you can just use that."}, - - {"print_stdout", PyPrintStdout, METH_VARARGS, - "print_stdout(message: str) -> None\n" - "\n" - "(internal)" - "\n" - "Print to system stdout.\n" - "Also forwards to the internal console, etc."}, - - {"print_stderr", PyPrintStderr, METH_VARARGS, - "print_stderr(message: str) -> None\n" + {"display_log", (PyCFunction)PyDisplayLog, METH_VARARGS | METH_KEYWORDS, + "display_log(name: str, level: str, message: str) -> None\n" "\n" "(internal)\n" "\n" - "Print to system stderr.\n" - "Also forwards to the internal console, etc."}, + "Sends a log message to the in-game console and any per-platform\n" + "log destinations (Android log, etc.). This generally is not called\n" + "directly and should instead be fed Python logging output."}, + + {"v1_cloud_log", (PyCFunction)PyV1CloudLog, + METH_VARARGS | METH_KEYWORDS, + "v1_cloud_log(message: str) -> None\n" + "\n" + "(internal)\n" + "\n" + "Push messages to the old v1 cloud log."}, {"set_stress_testing", PySetStressTesting, METH_VARARGS, "set_stress_testing(testing: bool, player_count: int) -> None\n" @@ -1120,13 +1114,13 @@ auto PythonMethodsApp::GetMethods() -> std::vector { "This can be handy for calls that are disallowed from within other\n" "callbacks, etc.\n" "\n" - "This call expects to be used in the game thread, and will " + "This call expects to be used in the logic thread, and will " "automatically\n" "save and restore the ba.Context to behave seamlessly.\n" "\n" - "If you want to push a call from outside of the game thread,\n" + "If you want to push a call from outside of the logic thread,\n" "however, you can pass 'from_other_thread' as True. In this case\n" - "the call will always run in the UI context on the game thread."}, + "the call will always run in the UI context on the logic thread."}, {"getactivity", (PyCFunction)PyGetActivity, METH_VARARGS | METH_KEYWORDS, diff --git a/src/ballistica/python/methods/python_methods_gameplay.cc b/src/ballistica/python/methods/python_methods_gameplay.cc index 4ba23115..fdc0a550 100644 --- a/src/ballistica/python/methods/python_methods_gameplay.cc +++ b/src/ballistica/python/methods/python_methods_gameplay.cc @@ -57,7 +57,7 @@ auto PyPrintNodes(PyObject* self, PyObject* args) -> PyObject* { snprintf(buffer, sizeof(buffer), "#%d: type: %-14s desc: %s", count, i->type()->name().c_str(), i->label().c_str()); s += buffer; - Log(buffer); + Log(LogLevel::kInfo, buffer); count++; } Py_RETURN_NONE; @@ -381,7 +381,7 @@ auto PyGetForegroundHostActivity(PyObject* self, PyObject* args, return nullptr; } - // Note: we return None if not in the game thread. + // Note: we return None if not in the logic thread. HostActivity* h = InLogicThread() ? g_logic->GetForegroundContext().GetHostActivity() : nullptr; diff --git a/src/ballistica/python/methods/python_methods_networking.cc b/src/ballistica/python/methods/python_methods_networking.cc index 27eb5a2d..6c5a4d6e 100644 --- a/src/ballistica/python/methods/python_methods_networking.cc +++ b/src/ballistica/python/methods/python_methods_networking.cc @@ -324,8 +324,8 @@ auto PySetMasterServerSource(PyObject* self, PyObject* args) -> PyObject* { int source; if (!PyArg_ParseTuple(args, "i", &source)) return nullptr; if (source != 0 && source != 1) { - BA_LOG_ONCE("Error: Invalid server source: " + std::to_string(source) - + "."); + BA_LOG_ONCE(LogLevel::kError, + "Invalid server source: " + std::to_string(source) + "."); source = 1; } g_app->master_server_source = source; diff --git a/src/ballistica/python/methods/python_methods_system.cc b/src/ballistica/python/methods/python_methods_system.cc index 7b593c8f..1cb80409 100644 --- a/src/ballistica/python/methods/python_methods_system.cc +++ b/src/ballistica/python/methods/python_methods_system.cc @@ -79,7 +79,7 @@ auto PySetUpSigInt(PyObject* self) -> PyObject* { if (g_app_flavor) { g_platform->SetupInterruptHandling(); } else { - Log("SigInt handler called before g_app_flavor exists."); + Log(LogLevel::kError, "SigInt handler called before g_app_flavor exists."); } Py_RETURN_NONE; BA_PYTHON_CATCH; @@ -354,7 +354,7 @@ auto PyPrintContext(PyObject* self, PyObject* args, PyObject* keywds) const_cast(kwlist))) { return nullptr; } - Python::LogContextAuto(); + Python::PrintContextAuto(); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -521,19 +521,20 @@ auto PyGetVolatileDataDirectory(PyObject* self, PyObject* args) -> PyObject* { auto PyIsLogFull(PyObject* self, PyObject* args) -> PyObject* { BA_PYTHON_TRY; - if (g_app->log_full) { + if (g_app->v1_cloud_log_full) { Py_RETURN_TRUE; } Py_RETURN_FALSE; BA_PYTHON_CATCH; } -auto PyGetLog(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { +auto PyGetV1CloudLog(PyObject* self, PyObject* args, PyObject* keywds) + -> PyObject* { BA_PYTHON_TRY; std::string log_fin; { - std::scoped_lock lock(g_app->log_mutex); - log_fin = g_app->log; + std::scoped_lock lock(g_app->v1_cloud_log_mutex); + log_fin = g_app->v1_cloud_log; } // we want to use something with error handling here since the last // bit of this string could be truncated utf8 chars.. @@ -545,8 +546,8 @@ auto PyGetLog(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { auto PyMarkLogSent(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; - // this way we won't try to send it at shutdown time and whatnot - g_app->put_log = true; + // This way we won't try to send it at shutdown time and whatnot + g_app->did_put_v1_cloud_log = true; Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -902,8 +903,9 @@ auto PythonMethodsSystem::GetMethods() -> std::vector { "\n" "(internal)"}, - {"getlog", (PyCFunction)PyGetLog, METH_VARARGS | METH_KEYWORDS, - "getlog() -> str\n" + {"get_v1_cloud_log", (PyCFunction)PyGetV1CloudLog, + METH_VARARGS | METH_KEYWORDS, + "get_v1_cloud_log() -> str\n" "\n" "(internal)"}, @@ -912,8 +914,8 @@ auto PythonMethodsSystem::GetMethods() -> std::vector { "\n" "(internal)"}, - {"get_log_file_path", PyGetLogFilePath, METH_VARARGS, - "get_log_file_path() -> str\n" + {"get_v1_cloud_log_file_path", PyGetLogFilePath, METH_VARARGS, + "get_v1_cloud_log_file_path() -> str\n" "\n" "(internal)\n" "\n" @@ -1055,7 +1057,7 @@ auto PythonMethodsSystem::GetMethods() -> std::vector { "\n" "(internal)\n" "\n" - "Returns whether or not the current thread is the game thread."}, + "Returns whether or not the current thread is the logic thread."}, {"request_permission", (PyCFunction)PyRequestPermission, METH_VARARGS | METH_KEYWORDS, diff --git a/src/ballistica/python/python.cc b/src/ballistica/python/python.cc index ae8d285f..3be6a998 100644 --- a/src/ballistica/python/python.cc +++ b/src/ballistica/python/python.cc @@ -68,6 +68,46 @@ namespace ballistica { #pragma ide diagnostic ignored "hicpp-signed-bitwise" #pragma ide diagnostic ignored "RedundantCast" +auto Python::LoggingCall(LogLevel loglevel, const std::string& msg) -> void { + // If we've not yet captured our Python logging calls, stash this call away. + // We'll submit all accumulated entries after we bootstrap python. + if (!objexists(ObjID::kLoggingCriticalCall)) { + std::scoped_lock lock(early_log_lock_); + early_logs_.emplace_back(std::make_pair(loglevel, msg)); + return; + } + + // Ok; seems we've got Python calls. Run the right one for our log level. + ObjID logcallobj; + switch (loglevel) { + case LogLevel::kDebug: + logcallobj = Python::ObjID::kLoggingDebugCall; + break; + case LogLevel::kInfo: + logcallobj = Python::ObjID::kLoggingInfoCall; + break; + case LogLevel::kWarning: + logcallobj = Python::ObjID::kLoggingWarningCall; + break; + case LogLevel::kError: + logcallobj = Python::ObjID::kLoggingErrorCall; + break; + case LogLevel::kCritical: + logcallobj = Python::ObjID::kLoggingCriticalCall; + break; + default: + logcallobj = Python::ObjID::kLoggingInfoCall; + fprintf(stderr, "Unexpected LogLevel %d\n", static_cast(loglevel)); + break; + } + + // Make sure we're good to go from any thread. + ScopedInterpreterLock lock; + + PythonRef args(Py_BuildValue("(s)", msg.c_str()), PythonRef::kSteal); + obj(logcallobj).Call(args); +} + void Python::SetPythonException(const Exception& exc) { PyExcType exctype{exc.python_type()}; const char* description{GetShortExceptionDescription(exc)}; @@ -130,7 +170,8 @@ void Python::PrintStackTrace() { if (g_python->objexists(objid)) { g_python->obj(objid).Call(); } else { - Log("Warning: Python::PrintStackTrace() called before bootstrap complete; " + Log(LogLevel::kWarning, + "Python::PrintStackTrace() called before bootstrap complete; " "not printing."); } } @@ -1042,6 +1083,16 @@ auto Python::InitBallisticaPython() -> void { // Import and grab all the Python stuff we use from C++. #include "ballistica/generated/python_embedded/binding.inc" + // If we've got any early log calls, it is now safe to push them along + // to Python. + assert(objexists(ObjID::kLoggingCriticalCall)); + { + std::scoped_lock lock(early_log_lock_); + for (auto&& entry : early_logs_) { + LoggingCall(entry.first, "DEFERRED-EARLY-LOG: " + entry.second); + } + early_logs_.clear(); + } g_app_internal->PythonPostInit(); // Alright I guess let's pull ba in to main, since pretty @@ -1187,14 +1238,15 @@ auto Python::GetResource(const char* key, const char* fallback_resource, try { return GetPyString(results.get()); } catch (const std::exception&) { - Log("GetResource failed for '" + std::string(key) + "'"); + Log(LogLevel::kError, + "GetResource failed for '" + std::string(key) + "'"); // Hmm; I guess let's just return the key to help identify/fix the // issue?.. return std::string(""; } } else { - Log("GetResource failed for '" + std::string(key) + "'"); + Log(LogLevel::kError, "GetResource failed for '" + std::string(key) + "'"); } // Hmm; I guess let's just return the key to help identify/fix the issue?.. @@ -1212,11 +1264,13 @@ auto Python::GetTranslation(const char* category, const char* s) try { return GetPyString(results.get()); } catch (const std::exception&) { - Log("GetTranslation failed for '" + std::string(category) + "'"); + Log(LogLevel::kError, + "GetTranslation failed for '" + std::string(category) + "'"); return ""; } } else { - Log("GetTranslation failed for category '" + std::string(category) + "'"); + Log(LogLevel::kError, + "GetTranslation failed for category '" + std::string(category) + "'"); } return ""; } @@ -1228,7 +1282,7 @@ void Python::RunDeepLink(const std::string& url) { PythonRef args(Py_BuildValue("(s)", url.c_str()), PythonRef::kSteal); obj(ObjID::kDeepLinkCall).Call(args); } else { - Log("Error on deep-link call"); + Log(LogLevel::kError, "Error on deep-link call"); } } @@ -1253,7 +1307,7 @@ void Python::ShowURL(const std::string& url) { PythonRef args(Py_BuildValue("(s)", url.c_str()), PythonRef::kSteal); obj(ObjID::kShowURLWindowCall).Call(args); } else { - Log("Error: ShowURLWindowCall nonexistent."); + Log(LogLevel::kError, "ShowURLWindowCall nonexistent."); } } @@ -1296,7 +1350,8 @@ auto Python::FilterChatMessage(std::string* message, int client_id) -> bool { try { *message = Python::GetPyString(result.get()); } catch (const std::exception& e) { - Log("Error getting string from chat filter: " + std::string(e.what())); + Log(LogLevel::kError, + "Error getting string from chat filter: " + std::string(e.what())); } return true; } @@ -1781,9 +1836,9 @@ auto Python::DoNewNode(PyObject* args, PyObject* keywds) -> Node* { attr_vals.emplace_back( t->GetAttribute(std::string(PyUnicode_AsUTF8(key))), value); } catch (const std::exception&) { - Log("ERROR: Attr not found on initial attr set: '" - + std::string(PyUnicode_AsUTF8(key)) + "' on " + type + " node '" - + name + "'"); + Log(LogLevel::kError, "Attr not found on initial attr set: '" + + std::string(PyUnicode_AsUTF8(key)) + "' on " + + type + " node '" + name + "'"); } } @@ -1793,8 +1848,9 @@ auto Python::DoNewNode(PyObject* args, PyObject* keywds) -> Node* { try { SetNodeAttr(node, i.first->name().c_str(), i.second); } catch (const std::exception& e) { - Log("ERROR: exception in initial attr set for attr '" + i.first->name() - + "' on " + type + " node '" + name + "':" + e.what()); + Log(LogLevel::kError, "Exception in initial attr set for attr '" + + i.first->name() + "' on " + type + " node '" + + name + "':" + e.what()); } } } @@ -1807,10 +1863,12 @@ auto Python::DoNewNode(PyObject* args, PyObject* keywds) -> Node* { if (PythonClassNode::Check(owner_obj)) { Node* owner_node = GetPyNode(owner_obj, true); if (owner_node == nullptr) { - Log("ERROR: empty node-ref passed for 'owner'; pass None if you want " + Log(LogLevel::kError, + "Empty node-ref passed for 'owner'; pass None if you want " "no owner."); } else if (owner_node->scene() != node->scene()) { - Log("ERROR: owner node is from a different scene; ignoring."); + Log(LogLevel::kError, + "Owner node is from a different scene; ignoring."); } else { owner_node->AddDependentNode(node); } @@ -1830,8 +1888,9 @@ auto Python::DoNewNode(PyObject* args, PyObject* keywds) -> Node* { } node->OnCreate(); } catch (const std::exception& e) { - Log("ERROR: exception in OnCreate() for node " - + ballistica::ObjToString(node) + "':" + e.what()); + Log(LogLevel::kError, "Exception in OnCreate() for node " + + ballistica::ObjToString(node) + + "':" + e.what()); } return node; @@ -2039,10 +2098,11 @@ auto Python::GetNodeAttr(Node* node, const char* attr_name) -> PyObject* { } void Python::IssueCallInLogicThreadWarning(PyObject* call_obj) { - Log("WARNING: ba.pushcall() called from the game thread with " + Log(LogLevel::kWarning, + "ba.pushcall() called from the logic thread with " "from_other_thread set to true (call " - + ObjToString(call_obj) + " at " + GetPythonFileLocation() - + "). That arg should only be used from other threads."); + + ObjToString(call_obj) + " at " + GetPythonFileLocation() + + "). That arg should only be used from other threads."); } void Python::LaunchStringEdit(TextWidget* w) { @@ -2253,41 +2313,41 @@ auto Python::GetContextBaseString() -> std::string { return s; } -void Python::LogContextForCallableLabel(const char* label) { +void Python::PrintContextForCallableLabel(const char* label) { assert(InLogicThread()); assert(label); std::string s = std::string(" root call: ") + label; s += g_python->GetContextBaseString(); - Log(s); + PySys_WriteStderr("%s\n", s.c_str()); } -void Python::LogContextNonLogicThread() { +void Python::PrintContextNonLogicThread() { std::string s = - std::string(" root call: "); - Log(s); + std::string(" root call: "); + PySys_WriteStderr("%s\n", s.c_str()); } -void Python::LogContextEmpty() { +void Python::PrintContextEmpty() { assert(InLogicThread()); std::string s = std::string(" root call: "); s += g_python->GetContextBaseString(); - Log(s); + PySys_WriteStderr("%s\n", s.c_str()); } -void Python::LogContextAuto() { +void Python::PrintContextAuto() { // Lets print whatever context info is available. // FIXME: If we have recursive calls this may not print // the context we'd expect; we'd need a unified stack. if (!InLogicThread()) { - LogContextNonLogicThread(); + PrintContextNonLogicThread(); } else if (const char* label = ScopedCallLabel::current_label()) { - LogContextForCallableLabel(label); + PrintContextForCallableLabel(label); } else if (PythonCommand* cmd = PythonCommand::current_command()) { - cmd->LogContext(); + cmd->PrintContext(); } else if (PythonContextCall* call = PythonContextCall::current_call()) { - call->LogContext(); + call->PrintContext(); } else { - LogContextEmpty(); + PrintContextEmpty(); } } @@ -2304,8 +2364,8 @@ void Python::AcquireGIL() { if (debug_timing) { auto duration{Platform::GetCurrentMilliseconds() - startms}; if (duration > (1000 / 120)) { - Log("GIL acquire took too long (" + std::to_string(duration) + " ms).", - true, false); + Log(LogLevel::kInfo, + "GIL acquire took too long (" + std::to_string(duration) + " ms)."); } } } @@ -2491,7 +2551,8 @@ auto Python::GetRawConfigValue(const char* name, float default_value) -> float { try { return GetPyFloat(value); } catch (const std::exception&) { - Log("expected a float for config value '" + std::string(name) + "'"); + Log(LogLevel::kError, + "expected a float for config value '" + std::string(name) + "'"); return default_value; } } @@ -2511,7 +2572,8 @@ auto Python::GetRawConfigValue(const char* name, } return GetPyFloat(value); } catch (const std::exception&) { - Log("expected a float for config value '" + std::string(name) + "'"); + Log(LogLevel::kError, + "expected a float for config value '" + std::string(name) + "'"); return default_value; } } @@ -2526,7 +2588,8 @@ auto Python::GetRawConfigValue(const char* name, int default_value) -> int { try { return static_cast_check_fit(GetPyInt64(value)); } catch (const std::exception&) { - Log("Expected an int value for config value '" + std::string(name) + "'."); + Log(LogLevel::kError, + "Expected an int value for config value '" + std::string(name) + "'."); return default_value; } } @@ -2541,7 +2604,8 @@ auto Python::GetRawConfigValue(const char* name, bool default_value) -> bool { try { return GetPyBool(value); } catch (const std::exception&) { - Log("Expected a bool value for config value '" + std::string(name) + "'."); + Log(LogLevel::kError, + "Expected a bool value for config value '" + std::string(name) + "'."); return default_value; } } @@ -2564,7 +2628,7 @@ void Python::TimeFormatCheck(TimeFormat time_format, PyObject* length_obj) { if (length >= 200.0) { static bool warned = false; if (!warned) { - Log("Warning: time value " + Log(LogLevel::kWarning, "Time value " +std::to_string(length)+" passed as seconds;" " did you mean milliseconds?" " (if so, pass suppress_format_warning=True to stop this warning)"); @@ -2578,7 +2642,7 @@ void Python::TimeFormatCheck(TimeFormat time_format, PyObject* length_obj) { if (length < 1.0 && length > 0.0000001) { static bool warned = false; if (!warned) { - Log("Warning: time value " + Log(LogLevel::kWarning, "Time value " + std::to_string(length) + " passed as milliseconds;" " did you mean seconds?" " (if so, pass suppress_format_warning=True to stop this warning)"); @@ -2589,8 +2653,9 @@ void Python::TimeFormatCheck(TimeFormat time_format, PyObject* length_obj) { } else { static bool warned = false; if (!warned) { - BA_LOG_ONCE("TimeFormatCheck got timeformat value: '" - + std::to_string(static_cast(time_format)) + "'"); + BA_LOG_ONCE(LogLevel::kError, + "TimeFormatCheck got timeformat value: '" + + std::to_string(static_cast(time_format)) + "'"); warned = true; } } diff --git a/src/ballistica/python/python.h b/src/ballistica/python/python.h index bf451032..a48e847b 100644 --- a/src/ballistica/python/python.h +++ b/src/ballistica/python/python.h @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -42,7 +43,7 @@ class Python { /// Use this to protect Python code that may be run in cases where we don't /// hold the Global Interpreter Lock (GIL) (basically anything outside of the - /// game thread). + /// logic thread). class ScopedInterpreterLock { public: ScopedInterpreterLock(); @@ -75,10 +76,17 @@ class Python { auto ValidatedPackageAssetName(PyObject* package, const char* name) -> std::string; - static auto LogContextForCallableLabel(const char* label) -> void; - static auto LogContextEmpty() -> void; - static auto LogContextAuto() -> void; - static auto LogContextNonLogicThread() -> void; + /// Calls Python logging function (logging.error, logging.warning, etc.) + /// Can be called from any thread at any time. If called before Python + /// logging is available, logs locally using Logging::DisplayLog() + /// (with an added warning). + auto LoggingCall(LogLevel loglevel, const std::string& msg) -> void; + + // Print various context debugging bits to Python's sys.stderr. + static auto PrintContextForCallableLabel(const char* label) -> void; + static auto PrintContextEmpty() -> void; + static auto PrintContextAuto() -> void; + static auto PrintContextNonLogicThread() -> void; Python(); static auto Create() -> Python*; @@ -295,7 +303,7 @@ class Python { kVROrientationResetCBMessageCall, kVROrientationResetMessageCall, kHandleAppResumeCall, - kHandleLogCall, + kHandleV1CloudLogCall, kLaunchMainMenuSessionCall, kLanguageTestToggleCall, kAwardInControlAchievementCall, @@ -352,6 +360,11 @@ class Python { kUUIDStrCall, kHashStringsCall, kHaveAccountV2CredentialsCall, + kLoggingDebugCall, + kLoggingInfoCall, + kLoggingWarningCall, + kLoggingErrorCall, + kLoggingCriticalCall, kLast // Sentinel; must be at end. }; @@ -379,7 +392,7 @@ class Python { /// Create a Python single-member tuple. auto SingleMemberTuple(const PythonRef& member) -> PythonRef; - /// Push a call to a preset obj to the game thread + /// Push a call to a preset obj to the logic thread /// (will be run in the UI context). auto PushObjCall(ObjID obj) -> void; @@ -420,7 +433,9 @@ class Python { std::set do_once_locations_; PythonRef objs_[static_cast(ObjID::kLast)]; bool inited_{}; - std::list > clean_frame_commands_; + std::list> clean_frame_commands_; + std::mutex early_log_lock_; + std::list> early_logs_; PythonRef game_pad_call_; PythonRef keyboard_call_; PyObject* empty_dict_object_{}; diff --git a/src/ballistica/python/python_command.cc b/src/ballistica/python/python_command.cc index 187bde06..54553315 100644 --- a/src/ballistica/python/python_command.cc +++ b/src/ballistica/python/python_command.cc @@ -85,7 +85,7 @@ auto PythonCommand::Run() -> bool { if (!g_python) { // This probably means the game is dying; let's not // throw an exception here so we don't mask the original error. - Log("PythonCommand: not running due to null g_python"); + Log(LogLevel::kError, "PythonCommand: not running due to null g_python."); return false; } assert(!dead_); @@ -99,14 +99,14 @@ auto PythonCommand::Run() -> bool { g_python->main_dict()); POP_PYCOMMAND(); - // Technically the python call could have killed us; + // Technically the Python call could have killed us; // make sure that didn't happen. assert(!dead_); if (v == nullptr) { // Save/restore error or it can mess with context print calls. BA_PYTHON_ERROR_SAVE; - Log("ERROR: exception in Python call:"); - LogContext(); + PySys_WriteStderr("Exception in Python call:\n"); + PrintContext(); BA_PYTHON_ERROR_RESTORE; // We pass zero here to avoid grabbing references to this exception @@ -158,8 +158,8 @@ auto PythonCommand::RunReturnObj(bool print_errors, PyObject* context) if (print_errors) { // Save/restore error or it can mess with context print calls. BA_PYTHON_ERROR_SAVE; - Log("ERROR: exception in Python call:"); - LogContext(); + PySys_WriteStderr("Exception in Python call:\n"); + PrintContext(); BA_PYTHON_ERROR_RESTORE; // We pass zero here to avoid grabbing references to this exception // which can cause objects to stick around and trip up our deletion checks @@ -181,8 +181,8 @@ auto PythonCommand::RunReturnObj(bool print_errors, PyObject* context) if (print_errors) { // save/restore error or it can mess with context print calls BA_PYTHON_ERROR_SAVE; - Log("ERROR: exception in Python call:"); - LogContext(); + PySys_WriteStderr("Exception in Python call:\n"); + PrintContext(); BA_PYTHON_ERROR_RESTORE; // we pass zero here to avoid grabbing references to this exception // which can cause objects to stick around and trip up our deletion checks @@ -199,11 +199,11 @@ auto PythonCommand::RunReturnObj(bool print_errors, PyObject* context) return v; } -void PythonCommand::LogContext() { +void PythonCommand::PrintContext() { assert(Python::HaveGIL()); std::string s = std::string(" call: ") + command(); s += g_python->GetContextBaseString(); - Log(s); + PySys_WriteStderr("%s\n", s.c_str()); } } // namespace ballistica diff --git a/src/ballistica/python/python_command.h b/src/ballistica/python/python_command.h index f91e729f..8926e2d5 100644 --- a/src/ballistica/python/python_command.h +++ b/src/ballistica/python/python_command.h @@ -51,7 +51,7 @@ class PythonCommand { /// Returns nullptr on errors, but Python error state will be cleared. auto RunReturnObj(bool print_errors, PyObject* context) -> PyObject*; - void LogContext(); + void PrintContext(); /// Return true if the command can be evaluated; otherwise it can only be /// executed diff --git a/src/ballistica/python/python_context_call.cc b/src/ballistica/python/python_context_call.cc index 279d58c0..3ce54a87 100644 --- a/src/ballistica/python/python_context_call.cc +++ b/src/ballistica/python/python_context_call.cc @@ -75,7 +75,7 @@ void PythonContextCall::Run(PyObject* args) { if (!g_python) { // This probably means the game is dying; let's not // throw an exception here so we don't mask the original error. - Log("PythonCommand: not running due to null g_python"); + Log(LogLevel::kError, "PythonCommand: not running due to null g_python"); return; } @@ -86,7 +86,8 @@ void PythonContextCall::Run(PyObject* args) { // Sanity test: make sure our context didn't go away. #if BA_DEBUG_BUILD if (context_.target.get() != context_target_sanity_test_) { - Log("WARNING: running Call after it's context has died: " + object_.Str()); + Log(LogLevel::kWarning, + "Running Call after it's context has died: " + object_.Str()); } #endif // BA_DEBUG_BUILD @@ -112,8 +113,8 @@ void PythonContextCall::Run(PyObject* args) { // Save/restore python error or it can mess with context print calls. BA_PYTHON_ERROR_SAVE; - Log("ERROR: exception in Python call:"); - LogContext(); + PySys_WriteStderr("Exception in Python call:\n"); + PrintContext(); BA_PYTHON_ERROR_RESTORE; // We pass zero here to avoid grabbing references to this exception @@ -124,12 +125,12 @@ void PythonContextCall::Run(PyObject* args) { } } -void PythonContextCall::LogContext() { +void PythonContextCall::PrintContext() { assert(InLogicThread()); std::string s = std::string(" root call: ") + object().Str(); s += ("\n root call origin: " + file_loc()); s += g_python->GetContextBaseString(); - Log(s); + Log(LogLevel::kError, s); } } // namespace ballistica diff --git a/src/ballistica/python/python_context_call.h b/src/ballistica/python/python_context_call.h index f91100cb..9b04ff1a 100644 --- a/src/ballistica/python/python_context_call.h +++ b/src/ballistica/python/python_context_call.h @@ -31,7 +31,7 @@ class PythonContextCall : public Object { void MarkDead(); auto object() const -> const PythonRef& { return object_; } auto file_loc() const -> const std::string& { return file_loc_; } - void LogContext(); + auto PrintContext() -> void; private: void GetTrace(); // we try to grab basic trace info diff --git a/src/ballistica/python/python_ref.cc b/src/ballistica/python/python_ref.cc index 008213d9..6121b029 100644 --- a/src/ballistica/python/python_ref.cc +++ b/src/ballistica/python/python_ref.cc @@ -155,8 +155,8 @@ auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const if (print_errors) { // Save/restore error or it can mess with context print calls. BA_PYTHON_ERROR_SAVE; - Log("ERROR: exception in Python call:"); - Python::LogContextAuto(); + PySys_WriteStderr("Exception in Python call:\n"); + Python::PrintContextAuto(); BA_PYTHON_ERROR_RESTORE; // We pass zero here to avoid grabbing references to this exception diff --git a/src/ballistica/python/python_sys.h b/src/ballistica/python/python_sys.h index 23508ae6..0e53b373 100644 --- a/src/ballistica/python/python_sys.h +++ b/src/ballistica/python/python_sys.h @@ -60,12 +60,12 @@ ((void)0) // For use in tp_dealloc; simply prints the error. -#define BA_PYTHON_DEALLOC_CATCH \ - } \ - catch (const std::exception& e) { \ - Log(std::string("Error: tp_dealloc exception: ") \ - + GetShortExceptionDescription(e)); \ - } \ +#define BA_PYTHON_DEALLOC_CATCH \ + } \ + catch (const std::exception& e) { \ + Log(LogLevel::kError, std::string("tp_dealloc exception: ") \ + + GetShortExceptionDescription(e)); \ + } \ ((void)0) // Sets Python error and returns -1. diff --git a/src/ballistica/scene/node/combine_node.cc b/src/ballistica/scene/node/combine_node.cc index e4948921..0d94572d 100644 --- a/src/ballistica/scene/node/combine_node.cc +++ b/src/ballistica/scene/node/combine_node.cc @@ -41,7 +41,7 @@ auto CombineNode::GetOutput() -> std::vector { if (dirty_) { if (do_size_unset_warning_) { do_size_unset_warning_ = false; - BA_LOG_ONCE("ERROR: CombineNode size unset for " + label()); + BA_LOG_ONCE(LogLevel::kError, "CombineNode size unset for " + label()); } int actual_size = std::min(4, std::max(0, size_)); output_.resize(static_cast(actual_size)); diff --git a/src/ballistica/scene/node/globals_node.cc b/src/ballistica/scene/node/globals_node.cc index 34772cb3..be43714b 100644 --- a/src/ballistica/scene/node/globals_node.cc +++ b/src/ballistica/scene/node/globals_node.cc @@ -106,7 +106,8 @@ GlobalsNode::GlobalsNode(Scene* scene) : Node(scene, node_type) { // FIXME: Need to update this for non-host activities at some point. if (HostActivity* ha = context().GetHostActivity()) { if (ha->globals_node()) { - Log("WARNING: more than one globals node created in HostActivity; this " + Log(LogLevel::kWarning, + "More than one globals node created in HostActivity; this " "shouldn't happen"); } ha->SetGlobalsNode(this); diff --git a/src/ballistica/scene/node/node.cc b/src/ballistica/scene/node/node.cc index e61d147c..9f369442 100644 --- a/src/ballistica/scene/node/node.cc +++ b/src/ballistica/scene/node/node.cc @@ -14,7 +14,8 @@ namespace ballistica { NodeType::~NodeType() { - Log("ERROR: SHOULD NOT BE DESTRUCTING A TYPE type=(" + name_ + ")"); + Log(LogLevel::kError, + "SHOULD NOT BE DESTRUCTING A TYPE type=(" + name_ + ")"); } Node::Node(Scene* scene_in, NodeType* node_type) @@ -260,7 +261,7 @@ void Node::DispatchOutOfBoundsMessage() { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node OutOfBoundsMessage dispatch"); } else { - Log("Error creating OutOfBoundsMessage"); + Log(LogLevel::kError, "Error creating OutOfBoundsMessage"); } } @@ -275,7 +276,7 @@ void Node::DispatchPickUpMessage(Node* node) { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node PickUpMessage dispatch"); } else { - Log("Error creating PickUpMessage"); + Log(LogLevel::kError, "Error creating PickUpMessage"); } } @@ -288,7 +289,7 @@ void Node::DispatchDropMessage() { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node DropMessage dispatch"); } else { - Log("Error creating DropMessage"); + Log(LogLevel::kError, "Error creating DropMessage"); } } @@ -304,7 +305,7 @@ void Node::DispatchPickedUpMessage(Node* by_node) { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node PickedUpMessage dispatch"); } else { - Log("Error creating PickedUpMessage"); + Log(LogLevel::kError, "Error creating PickedUpMessage"); } } @@ -320,7 +321,7 @@ void Node::DispatchDroppedMessage(Node* by_node) { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node DroppedMessage dispatch"); } else { - Log("Error creating DroppedMessage"); + Log(LogLevel::kError, "Error creating DroppedMessage"); } } @@ -333,7 +334,7 @@ void Node::DispatchShouldShatterMessage() { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node ShouldShatterMessage dispatch"); } else { - Log("Error creating ShouldShatterMessage"); + Log(LogLevel::kError, "Error creating ShouldShatterMessage"); } } @@ -348,7 +349,7 @@ void Node::DispatchImpactDamageMessage(float intensity) { if (instance.exists()) { DispatchUserMessage(instance.get(), "Node ImpactDamageMessage dispatch"); } else { - Log("Error creating ImpactDamageMessage"); + Log(LogLevel::kError, "Error creating ImpactDamageMessage"); } } @@ -376,8 +377,10 @@ void Node::DispatchUserMessage(PyObject* obj, const char* label) { c.Call(PythonRef(Py_BuildValue("(O)", obj), PythonRef::kSteal)); } } catch (const std::exception& e) { - Log(std::string("Error in handlemessage() with message ") - + PythonRef(obj, PythonRef::kAcquire).Str() + ": '" + e.what() + "'"); + Log(LogLevel::kError, + std::string("Error in handlemessage() with message ") + + PythonRef(obj, PythonRef::kAcquire).Str() + ": '" + e.what() + + "'"); } } } diff --git a/src/ballistica/scene/node/node_attribute.cc b/src/ballistica/scene/node/node_attribute.cc index d940857f..42c382b2 100644 --- a/src/ballistica/scene/node/node_attribute.cc +++ b/src/ballistica/scene/node/node_attribute.cc @@ -48,8 +48,8 @@ auto NodeAttributeUnbound::GetNodeAttributeTypeName(NodeAttributeType t) case NodeAttributeType::kCollideModelArray: return "collide-model-array"; default: - Log("Error: Unknown attr type name: " - + std::to_string(static_cast(t))); + Log(LogLevel::kError, + "Unknown attr type name: " + std::to_string(static_cast(t))); return "unknown"; } } @@ -98,7 +98,7 @@ void NodeAttributeUnbound::DisconnectIncoming(Node* node) { #if BA_DEBUG_BUILD if (test_ref.exists()) { - Log("Error: Attr connection still exists after ref releases!"); + Log(LogLevel::kError, "Attr connection still exists after ref releases!"); } #endif } diff --git a/src/ballistica/scene/node/node_attribute_connection.cc b/src/ballistica/scene/node/node_attribute_connection.cc index 4b27fb32..d3b308f9 100644 --- a/src/ballistica/scene/node/node_attribute_connection.cc +++ b/src/ballistica/scene/node/node_attribute_connection.cc @@ -94,12 +94,13 @@ void NodeAttributeConnection::Update() { src_node->type()->GetAttribute(src_attr_index); NodeAttributeUnbound* dst_attr = dst_node->type()->GetAttribute(dst_attr_index); - Log("ERROR: attribute connection update: " + std::string(e.what()) - + "; srcAttr='" + src_attr->name() + "', src_node='" - + src_node->type()->name() + "', srcNodeName='" + src_node->label() - + "', dstAttr='" + dst_attr->name() + "', dstNode='" - + dst_node->type()->name() + "', dstNodeName='" + dst_node->label() - + "'"); + Log(LogLevel::kError, + "Attribute connection update: " + std::string(e.what()) + + "; srcAttr='" + src_attr->name() + "', src_node='" + + src_node->type()->name() + "', srcNodeName='" + + src_node->label() + "', dstAttr='" + dst_attr->name() + + "', dstNode='" + dst_node->type()->name() + "', dstNodeName='" + + dst_node->label() + "'"); } } } diff --git a/src/ballistica/scene/node/prop_node.cc b/src/ballistica/scene/node/prop_node.cc index be16c83d..3e5b534c 100644 --- a/src/ballistica/scene/node/prop_node.cc +++ b/src/ballistica/scene/node/prop_node.cc @@ -265,7 +265,8 @@ void PropNode::SetBody(const std::string& val) { // we're ok with redundant sets, but complain/ignore if they try to switch.. if (body_.exists()) { if (body_type_ != body_type || shape_ != shape) { - Log("ERROR: body attr can not be changed from its initial value"); + Log(LogLevel::kError, + "body attr can not be changed from its initial value"); return; } } @@ -426,8 +427,8 @@ void PropNode::Step() { if (body_type_ == BodyType::UNSET) { if (!reported_unset_body_type_) { reported_unset_body_type_ = true; - Log("ERROR: prop-node " + GetObjectDescription() - + " did not have its 'body' attr set."); + Log(LogLevel::kError, "prop-node " + GetObjectDescription() + + " did not have its 'body' attr set."); return; } } diff --git a/src/ballistica/scene/node/region_node.cc b/src/ballistica/scene/node/region_node.cc index 1fc4f016..e509bdc8 100644 --- a/src/ballistica/scene/node/region_node.cc +++ b/src/ballistica/scene/node/region_node.cc @@ -90,7 +90,8 @@ void RegionNode::Step() { RigidBody::kCollideRegion, RigidBody::kCollideActive); } else { if (region_type_ != "box") { - BA_LOG_ONCE("got unexpected region type: " + region_type_); + BA_LOG_ONCE(LogLevel::kError, + "Got unexpected region type: " + region_type_); } body_ = Object::New( 0, &part_, RigidBody::Type::kGeomOnly, RigidBody::Shape::kBox, diff --git a/src/ballistica/scene/node/sound_node.cc b/src/ballistica/scene/node/sound_node.cc index a34e70b2..da6b7834 100644 --- a/src/ballistica/scene/node/sound_node.cc +++ b/src/ballistica/scene/node/sound_node.cc @@ -81,7 +81,8 @@ void SoundNode::SetLoop(bool val) { // We don't actually update looping on a playing sound. if (playing_) - BA_LOG_ONCE("Error: can't set 'loop' attr on already-playing sound."); + BA_LOG_ONCE(LogLevel::kError, + "Can't set 'loop' attr on already-playing sound."); } void SoundNode::SetSound(Sound* s) { @@ -97,7 +98,8 @@ void SoundNode::SetPositional(bool val) { if (val == positional_) return; positional_ = val; if (playing_) - BA_LOG_ONCE("Error: can't set 'positional' attr on already-playing sound"); + BA_LOG_ONCE(LogLevel::kError, + "Can't set 'positional' attr on already-playing sound"); } void SoundNode::SetMusic(bool val) { diff --git a/src/ballistica/scene/node/spaz_node.cc b/src/ballistica/scene/node/spaz_node.cc index 26ef2b15..b07cb679 100644 --- a/src/ballistica/scene/node/spaz_node.cc +++ b/src/ballistica/scene/node/spaz_node.cc @@ -5885,7 +5885,8 @@ auto SpazNode::GetRigidBody(int id) -> RigidBody* { return hair_ponytail_bottom_body_.get(); break; default: - Log("Error: Request for unknown spaz body: " + std::to_string(id)); + Log(LogLevel::kError, + "Request for unknown spaz body: " + std::to_string(id)); break; } @@ -6491,7 +6492,7 @@ void SpazNode::SetStyle(const std::string& val) { shoulder_offset_y_ = -0.05f; reflection_scale_ = 0.02f; } else { - BA_LOG_ONCE("Error: Unrecognized spaz style: '" + style_ + "'"); + BA_LOG_ONCE(LogLevel::kError, "Unrecognized spaz style: '" + style_ + "'"); } UpdateBodiesForStyle(); } @@ -6620,20 +6621,25 @@ void SpazNode::SetHoldNode(Node* val) { assert(dynamics); Collision* c = dynamics->active_collision(); if (c) { - Log("SRC NODE: " + ObjToString(dynamics->GetActiveCollideSrcNode())); - Log("OPP NODE: " + ObjToString(dynamics->GetActiveCollideDstNode())); - Log("SRC BODY " - + std::to_string(dynamics->GetCollideMessageReverseOrder() - ? c->body_id_1 - : c->body_id_2)); - Log("OPP BODY " - + std::to_string(dynamics->GetCollideMessageReverseOrder() - ? c->body_id_2 - : c->body_id_1)); - Log("REVERSE " - + std::to_string(dynamics->GetCollideMessageReverseOrder())); + Log(LogLevel::kError, + "SRC NODE: " + ObjToString(dynamics->GetActiveCollideSrcNode())); + Log(LogLevel::kError, + "OPP NODE: " + ObjToString(dynamics->GetActiveCollideDstNode())); + Log(LogLevel::kError, + "SRC BODY " + + std::to_string(dynamics->GetCollideMessageReverseOrder() + ? c->body_id_1 + : c->body_id_2)); + Log(LogLevel::kError, + "OPP BODY " + + std::to_string(dynamics->GetCollideMessageReverseOrder() + ? c->body_id_2 + : c->body_id_1)); + Log(LogLevel::kError, + "REVERSE " + + std::to_string(dynamics->GetCollideMessageReverseOrder())); } else { - Log(""); + Log(LogLevel::kError, ""); } } throw Exception("specified hold_body (" + std::to_string(hold_body_) diff --git a/src/ballistica/scene/node/text_node.cc b/src/ballistica/scene/node/text_node.cc index a6f27062..7be19570 100644 --- a/src/ballistica/scene/node/text_node.cc +++ b/src/ballistica/scene/node/text_node.cc @@ -115,11 +115,12 @@ void TextNode::SetText(const std::string& val) { bool valid; g_logic->CompileResourceString(val, "setText format check", &valid); if (!valid) { - BA_LOG_ONCE("Invalid resource string: '" + val + "' on node '" + label() - + "'"); + BA_LOG_ONCE(LogLevel::kError, "Invalid resource string: '" + val + + "' on node '" + label() + "'"); Python::PrintStackTrace(); } else if (print_false_positives) { - BA_LOG_ONCE("Got false positive for json check on '" + val + "'"); + BA_LOG_ONCE(LogLevel::kError, + "Got false positive for json check on '" + val + "'"); Python::PrintStackTrace(); } } diff --git a/src/ballistica/scene/scene.cc b/src/ballistica/scene/scene.cc index 2f1acc65..acf77a96 100644 --- a/src/ballistica/scene/scene.cc +++ b/src/ballistica/scene/scene.cc @@ -81,7 +81,7 @@ void Scene::PlaySound(Sound* sound, float volume, bool host_only) { auto Scene::IsOutOfBounds(float x, float y, float z) -> bool { if (std::isnan(x) || std::isnan(y) || std::isnan(z) || std::isinf(x) || std::isinf(y) || std::isinf(z)) - BA_LOG_ONCE("ERROR: got INF/NAN value on IsOutOfBounds() check"); + BA_LOG_ONCE(LogLevel::kError, "Got INF/NAN value on IsOutOfBounds() check"); return ((x < bounds_min_[0]) || (x > bounds_max_[0]) || (y < bounds_min_[1]) || (y > bounds_max_[1]) || (z < bounds_min_[2]) @@ -206,7 +206,7 @@ void Scene::DeleteNode(Node* node) { // Sanity test: at this point the node should be dead. #if BA_DEBUG_BUILD if (temp_weak_ref.exists()) { - Log("Error: node still exists after ref release!!"); + Log(LogLevel::kError, "Node still exists after ref release!!"); } #endif // BA_DEBUG_BUILD @@ -396,8 +396,9 @@ void Scene::DumpNodes(SceneStream* out) { break; } default: - Log("Invalid attr type for Scene::DumpNodes() attr set: " - + std::to_string(static_cast(attr.type()))); + Log(LogLevel::kError, + "Invalid attr type for Scene::DumpNodes() attr set: " + + std::to_string(static_cast(attr.type()))); break; } } diff --git a/src/ballistica/scene/scene_stream.cc b/src/ballistica/scene/scene_stream.cc index cdea88f5..ff1aff8b 100644 --- a/src/ballistica/scene/scene_stream.cc +++ b/src/ballistica/scene/scene_stream.cc @@ -35,7 +35,8 @@ SceneStream::SceneStream(HostSession* host_session, bool save_replay) if (save_replay) { // Sanity check - we should only ever be writing one replay at once. if (g_app->replay_open) { - Log("ERROR: g_replay_open true at replay start; shouldn't happen."); + Log(LogLevel::kError, + "g_replay_open true at replay start; shouldn't happen."); } assert(g_assets_server); g_assets_server->PushBeginWriteReplayCall(); @@ -57,7 +58,8 @@ SceneStream::~SceneStream() { if (writing_replay_) { // Sanity check: We should only ever be writing one replay at once. if (!g_app->replay_open) { - Log("ERROR: g_replay_open false at replay close; shouldn't happen."); + Log(LogLevel::kError, + "g_replay_open false at replay close; shouldn't happen."); } g_app->replay_open = false; assert(g_assets_server); @@ -77,38 +79,40 @@ SceneStream::~SceneStream() { size_t count; count = GetPointerCount(scenes_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " scene graphs in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + + " scene graphs in output stream at shutdown"); } count = GetPointerCount(nodes_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " nodes in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + " nodes in output stream at shutdown"); } count = GetPointerCount(materials_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " materials in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + " materials in output stream at shutdown"); } count = GetPointerCount(textures_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " textures in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + " textures in output stream at shutdown"); } count = GetPointerCount(models_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " models in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + " models in output stream at shutdown"); } count = GetPointerCount(sounds_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " sounds in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + " sounds in output stream at shutdown"); } count = GetPointerCount(collide_models_); if (count != 0) { - Log("ERROR: " + std::to_string(count) - + " collide_models in output stream at shutdown"); + Log(LogLevel::kError, + std::to_string(count) + + " collide_models in output stream at shutdown"); } } } @@ -121,7 +125,8 @@ auto SceneStream::GetOutMessage() const -> std::vector { assert(!host_session_); // this should only be getting used for // standalone temp ones.. if (!out_command_.empty()) { - Log("Error: SceneStream shutting down with non-empty outCommand"); + Log(LogLevel::kError, + "SceneStream shutting down with non-empty outCommand"); } return out_message_; } @@ -186,11 +191,12 @@ void SceneStream::Remove(T* val, std::vector* vec, } void SceneStream::Fail() { - Log("Error writing replay file"); + Log(LogLevel::kError, "Error writing replay file"); if (writing_replay_) { // Sanity check: We should only ever be writing one replay at once. if (!g_app->replay_open) { - Log("ERROR: g_replay_open false at replay close; shouldn't happen."); + Log(LogLevel::kError, + "g_replay_open false at replay close; shouldn't happen."); } assert(g_assets_server); g_assets_server->PushEndWriteReplayCall(); @@ -201,7 +207,8 @@ void SceneStream::Fail() { void SceneStream::Flush() { if (!out_command_.empty()) - Log("Error: SceneStream flushing down with non-empty outCommand"); + Log(LogLevel::kError, + "SceneStream flushing down with non-empty outCommand"); if (!out_message_.empty()) { ShipSessionCommandsMessage(); } @@ -534,7 +541,7 @@ void SceneStream::SetTime(millisecs_t t) { } millisecs_t diff = t - time_; if (diff > 255) { - Log("Error: SceneStream got time diff > 255; not expected."); + Log(LogLevel::kError, "SceneStream got time diff > 255; not expected."); diff = 255; } WriteCommandInt64(SessionCommand::kBaseTimeStep, diff); @@ -1183,13 +1190,15 @@ void SceneStream::OnClientConnected(ConnectionToClient* c) { // Sanity check - abort if its on either of our lists already. for (auto& connections_to_client : connections_to_clients_) { if (connections_to_client == c) { - Log("Error: SceneStream::OnClientConnected() got duplicate connection."); + Log(LogLevel::kError, + "SceneStream::OnClientConnected() got duplicate connection."); return; } } for (auto& i : connections_to_clients_ignored_) { if (i == c) { - Log("Error: SceneStream::OnClientConnected() got duplicate connection."); + Log(LogLevel::kError, + "SceneStream::OnClientConnected() got duplicate connection."); return; } } @@ -1242,7 +1251,8 @@ void SceneStream::OnClientDisconnected(ConnectionToClient* c) { return; } } - Log("Error: SceneStream::OnClientDisconnected() called for connection not on " + Log(LogLevel::kError, + "SceneStream::OnClientDisconnected() called for connection not on " "lists"); } diff --git a/src/ballistica/ui/ui.cc b/src/ballistica/ui/ui.cc index 7ddf8c4b..945d51fb 100644 --- a/src/ballistica/ui/ui.cc +++ b/src/ballistica/ui/ui.cc @@ -60,13 +60,13 @@ auto UI::OnAppStart() -> void { if (force_scale_) { if (scale_ == UIScale::kSmall) { ScreenMessage("FORCING SMALL UI FOR TESTING", Vector3f(1, 0, 0)); - Log("FORCING SMALL UI FOR TESTING"); + Log(LogLevel::kInfo, "FORCING SMALL UI FOR TESTING"); } else if (scale_ == UIScale::kMedium) { ScreenMessage("FORCING MEDIUM UI FOR TESTING", Vector3f(1, 0, 0)); - Log("FORCING MEDIUM UI FOR TESTING"); + Log(LogLevel::kInfo, "FORCING MEDIUM UI FOR TESTING"); } else if (scale_ == UIScale::kLarge) { ScreenMessage("FORCING LARGE UI FOR TESTING", Vector3f(1, 0, 0)); - Log("FORCING LARGE UI FOR TESTING"); + Log(LogLevel::kInfo, "FORCING LARGE UI FOR TESTING"); } else { FatalError("Unhandled scale."); } @@ -151,7 +151,7 @@ void UI::Update(millisecs_t time_advance) { if (node_warning_count_ > 3) { static bool complained = false; if (!complained) { - Log(">10 nodes in UI context!"); + Log(LogLevel::kError, ">10 nodes in UI context!"); complained = true; } } diff --git a/src/ballistica/ui/widget/button_widget.cc b/src/ballistica/ui/widget/button_widget.cc index 507d45d7..cfefdad9 100644 --- a/src/ballistica/ui/widget/button_widget.cc +++ b/src/ballistica/ui/widget/button_widget.cc @@ -554,7 +554,8 @@ void ButtonWidget::Activate() { DoActivate(); } void ButtonWidget::DoActivate(bool isRepeat) { if (!enabled_) { - Log("WARNING: ButtonWidget::DoActivate() called on disabled button"); + Log(LogLevel::kWarning, + "ButtonWidget::DoActivate() called on disabled button"); return; } diff --git a/src/ballistica/ui/widget/container_widget.cc b/src/ballistica/ui/widget/container_widget.cc index 9d1e0eeb..5d82083b 100644 --- a/src/ballistica/ui/widget/container_widget.cc +++ b/src/ballistica/ui/widget/container_widget.cc @@ -1346,7 +1346,8 @@ void ContainerWidget::SelectWidget(Widget* w, SelectionCause c) { } } else { if (root_selectable_) { - Log("Error: SelectWidget() called on a ContainerWidget which is itself " + Log(LogLevel::kError, + "SelectWidget() called on a ContainerWidget which is itself " "selectable. Ignoring."); return; } @@ -1372,8 +1373,9 @@ void ContainerWidget::SelectWidget(Widget* w, SelectionCause c) { } else { static bool printed = false; if (!printed) { - Log("Warning: SelectWidget called on unselectable widget: " - + w->GetWidgetTypeName()); + Log(LogLevel::kWarning, + "SelectWidget called on unselectable widget: " + + w->GetWidgetTypeName()); Python::PrintStackTrace(); printed = true; } @@ -1543,7 +1545,7 @@ void ContainerWidget::SelectDownWidget() { BA_DEBUG_UI_READ_LOCK; if (!g_ui || !g_ui->root_widget() || !g_ui->screen_root_widget()) { - BA_LOG_ONCE("SelectDownWidget called before UI init."); + BA_LOG_ONCE(LogLevel::kError, "SelectDownWidget called before UI init."); return; } @@ -1576,7 +1578,7 @@ void ContainerWidget::SelectDownWidget() { } if (w) { if (!w->IsSelectable()) { - Log("Error: Down_widget is not selectable."); + Log(LogLevel::kError, "Down_widget is not selectable."); } else { w->Show(); // Avoid tap sounds and whatnot if we're just re-selecting ourself. @@ -1607,7 +1609,7 @@ void ContainerWidget::SelectUpWidget() { BA_DEBUG_UI_READ_LOCK; if (!g_ui || !g_ui->root_widget() || !g_ui->screen_root_widget()) { - BA_LOG_ONCE("SelectUpWidget called before UI init."); + BA_LOG_ONCE(LogLevel::kError, "SelectUpWidget called before UI init."); return; } @@ -1640,7 +1642,7 @@ void ContainerWidget::SelectUpWidget() { } if (w) { if (!w->IsSelectable()) { - Log("Error: up_widget is not selectable."); + Log(LogLevel::kError, "up_widget is not selectable."); } else { w->Show(); // Avoid tap sounds and whatnot if we're just re-selecting ourself. @@ -1671,7 +1673,7 @@ void ContainerWidget::SelectLeftWidget() { BA_DEBUG_UI_READ_LOCK; if (!g_ui || !g_ui->root_widget() || !g_ui->screen_root_widget()) { - BA_LOG_ONCE("SelectLeftWidget called before UI init."); + BA_LOG_ONCE(LogLevel::kError, "SelectLeftWidget called before UI init."); return; } @@ -1691,7 +1693,7 @@ void ContainerWidget::SelectLeftWidget() { } if (w) { if (!w->IsSelectable()) { - Log("Error: left_widget is not selectable."); + Log(LogLevel::kError, "left_widget is not selectable."); } else { w->Show(); // Avoid tap sounds and whatnot if we're just re-selecting ourself. @@ -1721,7 +1723,7 @@ void ContainerWidget::SelectRightWidget() { BA_DEBUG_UI_READ_LOCK; if (!g_ui || !g_ui->root_widget() || !g_ui->screen_root_widget()) { - BA_LOG_ONCE("SelectRightWidget called before UI init."); + BA_LOG_ONCE(LogLevel::kError, "SelectRightWidget called before UI init."); return; } @@ -1742,7 +1744,7 @@ void ContainerWidget::SelectRightWidget() { } if (w) { if (!w->IsSelectable()) { - Log("Error: right_widget is not selectable."); + Log(LogLevel::kError, "right_widget is not selectable."); } else { w->Show(); // Avoid tap sounds and whatnot if we're just re-selecting ourself. @@ -1773,7 +1775,7 @@ void ContainerWidget::SelectNextWidget() { BA_DEBUG_UI_READ_LOCK; if (!g_ui || !g_ui->root_widget() || !g_ui->screen_root_widget()) { - BA_LOG_ONCE("SelectNextWidget called before UI init."); + BA_LOG_ONCE(LogLevel::kError, "SelectNextWidget called before UI init."); return; } diff --git a/src/ballistica/ui/widget/text_widget.cc b/src/ballistica/ui/widget/text_widget.cc index 060ea48a..c45241e9 100644 --- a/src/ballistica/ui/widget/text_widget.cc +++ b/src/ballistica/ui/widget/text_widget.cc @@ -464,10 +464,12 @@ void TextWidget::SetText(const std::string& text_in_raw) { g_logic->CompileResourceString(text_in_raw, "TextWidget::SetText format check", &valid); if (!valid) { - BA_LOG_ONCE("Invalid resource string: '" + text_in_raw + "'"); + BA_LOG_ONCE(LogLevel::kError, + "Invalid resource string: '" + text_in_raw + "'"); Python::PrintStackTrace(); } else if (explicit_bool(print_false_positives)) { - BA_LOG_ONCE("Got false positive for json check on '" + text_in_raw + "'"); + BA_LOG_ONCE(LogLevel::kError, + "Got false positive for json check on '" + text_in_raw + "'"); Python::PrintStackTrace(); } } diff --git a/src/ballistica/ui/widget/widget.cc b/src/ballistica/ui/widget/widget.cc index ea0f1bab..094c35a9 100644 --- a/src/ballistica/ui/widget/widget.cc +++ b/src/ballistica/ui/widget/widget.cc @@ -46,7 +46,8 @@ void Widget::SetDepthRange(float min_depth, float max_depth) { auto Widget::IsInMainStack() const -> bool { if (!g_ui) { - BA_LOG_ONCE("Widget::IsInMainStack() called before ui creation."); + BA_LOG_ONCE(LogLevel::kError, + "Widget::IsInMainStack() called before ui creation."); return false; } // Navigate up to the top of the hierarchy and see if the @@ -200,9 +201,10 @@ void Widget::ScreenPointToWidget(float* x, float* y) const { float y_test = *y; WidgetPointToScreen(&x_test, &y_test); if (std::abs(x_test - x_old) > 0.01f || std::abs(y_test - y_old) > 0.01f) { - Log("ScreenPointToWidget sanity check error: expected (" - + std::to_string(x_old) + "," + std::to_string(y_old) + ") got (" - + std::to_string(x_test) + "," + std::to_string(y_test) + ")"); + Log(LogLevel::kError, + "ScreenPointToWidget sanity check error: expected (" + + std::to_string(x_old) + "," + std::to_string(y_old) + ") got (" + + std::to_string(x_test) + "," + std::to_string(y_test) + ")"); } #endif // BA_DEBUG_BUILD || BA_TEST_BUILD } diff --git a/src/meta/bameta/python_embedded/binding.py b/src/meta/bameta/python_embedded/binding.py index b565fd20..9408b71d 100644 --- a/src/meta/bameta/python_embedded/binding.py +++ b/src/meta/bameta/python_embedded/binding.py @@ -8,6 +8,7 @@ from __future__ import annotations import json import copy +import logging from typing import TYPE_CHECKING import ba @@ -50,7 +51,7 @@ def get_binding_values() -> tuple[Any, ...]: _hooks.orientation_reset_cb_message, # kVROrientationResetCBMessageCall _hooks.orientation_reset_message, # kVROrientationResetMessageCall _hooks.on_app_resume, # kHandleAppResumeCall - _apputils.handle_log, # kHandleLogCall + _apputils.handle_v1_cloud_log, # kHandleV1CloudLogCall _hooks.launch_main_menu_session, # kLaunchMainMenuSessionCall _hooks.language_test_toggle, # kLanguageTestToggleCall _hooks.award_in_control_achievement, # kAwardInControlAchievementCall @@ -136,4 +137,9 @@ def get_binding_values() -> tuple[Any, ...]: _hooks.uuid_str, # kUUIDStrCall _hooks.hash_strings, # kHashStringsCall _hooks.have_account_v2_credentials, # kHaveAccountV2CredentialsCall + logging.debug, # kLoggingDebugCall + logging.info, # kLoggingInfoCall + logging.warning, # kLoggingWarningCall + logging.error, # kLoggingErrorCall + logging.critical, # kLoggingCriticalCall ) # yapf: disable diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py index 3f4954a7..dc5afbbb 100755 --- a/tools/batools/dummymodule.py +++ b/tools/batools/dummymodule.py @@ -770,7 +770,7 @@ def update(projroot: str, check: bool, force: bool) -> None: for mname in ('_ba', '_bainternal'): # Skip internal module in public since it might # not exist and is read-only anyway. - if mname == '_ba' and public: + if mname == '_bainternal' and public: continue outfilename = os.path.abspath( diff --git a/tools/efro/log.py b/tools/efro/log.py index af647f56..376a2056 100644 --- a/tools/efro/log.py +++ b/tools/efro/log.py @@ -5,20 +5,22 @@ from __future__ import annotations import sys import time +import asyncio import logging import datetime -import threading from enum import Enum from dataclasses import dataclass from typing import TYPE_CHECKING, Annotated +from threading import Thread, current_thread, Lock from efro.util import utc_now +from efro.call import tpartial from efro.terminal import TerminalColor from efro.dataclassio import ioprepped, IOAttrs, dataclass_to_json if TYPE_CHECKING: from pathlib import Path - from typing import Any, Callable + from typing import Any, Callable, TextIO class LogLevel(Enum): @@ -35,6 +37,15 @@ class LogLevel(Enum): CRITICAL = 4 +LOG_NAMES_TO_LEVELS = { + 'DEBUG': LogLevel.DEBUG, + 'INFO': LogLevel.INFO, + 'WARNING': LogLevel.WARNING, + 'ERROR': LogLevel.ERROR, + 'CRITICAL': LogLevel.CRITICAL +} + + @ioprepped @dataclass class LogEntry: @@ -53,19 +64,61 @@ class LogHandler(logging.Handler): to stdout/stderr with pretty colors. """ + _event_loop: asyncio.AbstractEventLoop + + # IMPORTANT: Any debug prints we do here should ONLY go to echofile. + # Otherwise we can get infinite loops as those prints come back to us + # as new log entries. + def __init__(self, path: str | Path | None, - echofile: Any, + echofile: TextIO | None, suppress_non_root_debug: bool = False): super().__init__() # pylint: disable=consider-using-with self._file = (None if path is None else open(path, 'w', encoding='utf-8')) self._echofile = echofile + self._callbacks_lock = Lock() self._callbacks: list[Callable[[LogEntry], None]] = [] self._suppress_non_root_debug = suppress_non_root_debug + self._file_chunks: dict[str, list[str]] = {'stdout': [], 'stderr': []} + self._file_chunk_ship_task: dict[str, asyncio.Task | None] = { + 'stdout': None, + 'stderr': None + } + self._printed_callback_error = False + self._thread_bootstrapped = False + self._thread = Thread(target=self._thread_main, daemon=True) + self._thread.start() + + # Spin until our thread has set up its basic stuff; + # otherwise we could wind up trying to push stuff to our + # event loop before the loop exists. + while not self._thread_bootstrapped: + time.sleep(0.001) + + def add_callback(self, call: Callable[[LogEntry], None]) -> None: + """Add a callback to be run for each LogEntry. + + Note that this callback will always run in a background thread. + """ + with self._callbacks_lock: + self._callbacks.append(call) + + def _thread_main(self) -> None: + self._event_loop = asyncio.new_event_loop() + # NOTE: if we ever use default threadpool at all we should allow + # setting it for our loop. + asyncio.set_event_loop(self._event_loop) + self._thread_bootstrapped = True + self._event_loop.run_forever() def emit(self, record: logging.LogRecord) -> None: + # Called by logging to send us records. + # We simply package them up and ship them to our thread. + + assert current_thread() is not self._thread # Special case - filter out this common extra-chatty category. # TODO - should use a standard logging.Filter for this. @@ -73,30 +126,109 @@ class LogHandler(logging.Handler): and record.levelname == 'DEBUG'): return - # Bake down all log formatting into a simple string. + # We want to forward as much as we can along without processing it + # (better to do so in a bg thread). + # However its probably best to flatten the message string here since + # it could cause problems stringifying things in threads where they + # didn't expect to be stringified. msg = self.format(record) - # Translate Python log levels to our own. - level = { - 'DEBUG': LogLevel.DEBUG, - 'INFO': LogLevel.INFO, - 'WARNING': LogLevel.WARNING, - 'ERROR': LogLevel.ERROR, - 'CRITICAL': LogLevel.CRITICAL - }[record.levelname] + self._event_loop.call_soon_threadsafe( + tpartial(self._emit_in_loop, record.name, record.levelname, + record.created, msg)) - entry = LogEntry(message=msg, - name=record.name, - level=level, + def _emit_in_loop(self, name: str, levelname: str, created: float, + message: str) -> None: + try: + self._emit_entry( + LogEntry(name=name, + message=message, + level=LOG_NAMES_TO_LEVELS[levelname], time=datetime.datetime.fromtimestamp( - record.created, datetime.timezone.utc)) + created, datetime.timezone.utc))) + except Exception: + import traceback + traceback.print_exc(file=self._echofile) - for call in self._callbacks: - call(entry) + def file_write(self, name: str, output: str) -> None: + """Send raw stdout/stderr output to the logger to be collated.""" - # Also route log entries to the echo file (generally stdout/stderr) - # with pretty colors. - if self._echofile is not None: + self._event_loop.call_soon_threadsafe( + tpartial(self._file_write_in_loop, name, output)) + + def _file_write_in_loop(self, name: str, output: str) -> None: + try: + assert name in ('stdout', 'stderr') + + # Here we try to be somewhat smart about breaking arbitrary + # print output into discrete log entries. + + # Individual parts of a print come across as separate writes, + # and the end of a print will be a standalone '\n' by default. + # So let's ship whatever we've got when one of those comes in. + if output == '\n': + self._ship_file_chunks(name, cancel_ship_task=True) + else: + # By default just keep adding chunks. + # However we keep a timer running anytime we've got + # unshipped chunks so that we can ship what we've got + # after a short bit if we never get a newline. + self._file_chunks[name].append(output) + + ship_task = self._file_chunk_ship_task[name] + if ship_task is None: + self._file_chunk_ship_task[name] = ( + self._event_loop.create_task( + self._ship_chunks_task(name))) + + except Exception: + import traceback + traceback.print_exc(file=self._echofile) + + async def _ship_chunks_task(self, name: str) -> None: + await asyncio.sleep(0.1) + self._ship_file_chunks(name, cancel_ship_task=False) + + def _ship_file_chunks(self, name: str, cancel_ship_task: bool) -> None: + self._emit_entry( + LogEntry(name=name, + message=''.join(self._file_chunks[name]), + level=LogLevel.INFO, + time=utc_now())) + self._file_chunks[name] = [] + ship_task = self._file_chunk_ship_task[name] + if cancel_ship_task and ship_task is not None: + ship_task.cancel() + self._file_chunk_ship_task[name] = None + + def _emit_entry(self, entry: LogEntry) -> None: + # This runs in our bg event loop thread and does most of the work. + assert current_thread() is self._thread + + with self._callbacks_lock: + for call in self._callbacks: + try: + call(entry) + except Exception: + # Only print one callback error to avoid insanity. + if not self._printed_callback_error: + import traceback + traceback.print_exc(file=self._echofile) + self._printed_callback_error = True + + # Dump to our structured log file. + # TODO: set a timer for flushing; don't flush every line. + if self._file is not None: + entry_s = dataclass_to_json(entry) + assert '\n' not in entry_s # Make sure its a single line. + print(entry_s, file=self._file, flush=True) + + # Also print pretty colored output to our echo file (stdout/stderr). + # Note that we don't do this for log entries generated from + # stdout/stderr since that would result in them being printed + # twice. + if (self._echofile is not None + and entry.name not in ('stdout', 'stderr')): cbegin: str cend: str cbegin, cend = { @@ -111,101 +243,40 @@ class LogHandler(logging.Handler): (TerminalColor.STRONG_MAGENTA.value + TerminalColor.BOLD.value + TerminalColor.BG_BLACK.value, TerminalColor.RESET.value), - }[level] + }[entry.level] - self._echofile.write(f'{cbegin}{msg}{cend}\n') - - # Note to self: it sounds like logging wraps calls to us - # in a lock so we shouldn't have to worry about garbled - # json output due to multiple threads writing at once, - # but may be good to find out for sure? - if self._file is not None: - entry_s = dataclass_to_json(entry) - assert '\n' not in entry_s # make sure its a single line - print(entry_s, file=self._file, flush=True) - - def emit_custom(self, name: str, message: str, level: LogLevel) -> None: - """Custom emit call for our stdout/stderr redirection.""" - entry = LogEntry(name=name, - message=message, - level=level, - time=utc_now()) - - for call in self._callbacks: - call(entry) - - if self._file is not None: - entry_s = dataclass_to_json(entry) - assert '\n' not in entry_s # Make sure its a single line. - print(entry_s, file=self._file, flush=True) - - def add_callback(self, call: Callable[[LogEntry], None]) -> None: - """Add a callback to be run for each added entry.""" - self._callbacks.append(call) + # Should we be flushing here? + self._echofile.write(f'{cbegin}{entry.message}{cend}\n') -class LogRedirect: - """A file-like object for redirecting stdout/stderr to our log.""" +class FileLogEcho: + """A file-like object for forwarding stdout/stderr to a LogHandler.""" - def __init__(self, name: str, orig_out: Any, log_handler: LogHandler, - log_level: LogLevel): + def __init__(self, original: TextIO, name: str, + handler: LogHandler) -> None: + assert name in ('stdout', 'stderr') + self._original = original self._name = name - self._orig_out = orig_out - self._log_handler = log_handler - self._log_level = log_level - self._chunk = '' - self._chunk_start_time = 0.0 - self._lock = threading.Lock() + self._handler = handler - def write(self, s: str) -> None: - """Write something to output.""" + def write(self, output: Any) -> None: + """Override standard write call.""" + self._original.write(output) + self._handler.file_write(self._name, output) - assert isinstance(s, str) + def flush(self) -> None: + """Flush the file.""" + self._original.flush() - # First, ship it off to the original destination. - self._orig_out.write(s) - - # Now add this to our chunk and ship completed chunks - # off to the logger. - # Let's consider a chunk completed when we're passed - # a single '\n' by itself. (print() statement will do - # this at the end by default). - # We may get some false positives/negatives this way - # but it should result in *most* big multi-line print - # statements being wrapped into a single log entry. - # Also, flush with only_old=True can be called periodically - # to dump any pending chunks that don't happen to fit - # this pattern. - with self._lock: - if s == '\n': - self._log_handler.emit_custom(name=self._name, - message=self._chunk, - level=self._log_level) - self._chunk = '' - else: - if self._chunk == '': - self._chunk_start_time = time.time() - self._chunk += s - - def flush(self, only_old: bool = False) -> None: - """Flushhhhh!""" - self._orig_out.flush() - if only_old and time.time() - self._chunk_start_time < 0.5: - return - with self._lock: - if self._chunk != '': - chunk = self._chunk - if chunk.endswith('\n'): - chunk = chunk[:-1] - self._log_handler.emit_custom(name=self._name, - message=chunk, - level=self._log_level) - self._chunk = '' + def isatty(self) -> bool: + """Are we a terminal?""" + return self._original.isatty() def setup_logging(log_path: str | Path | None, level: LogLevel, - suppress_non_root_debug: bool = False) -> LogHandler: + suppress_non_root_debug: bool = False, + log_stdout_stderr: bool = False) -> LogHandler: """Set up our logging environment. Returns the custom handler which can be used to fetch information @@ -222,26 +293,32 @@ def setup_logging(log_path: str | Path | None, # Wire logger output to go to a structured log file. # Also echo it to stderr IF we're running in a terminal. + # Note: by passing in the *original* stderr here before we + # (potentially) replace it, we ensure that our log echos + # won't themselves be intercepted and sent to the logger + # which would create an infinite loop. loghandler = LogHandler( log_path, echofile=sys.stderr if sys.stderr.isatty() else None, suppress_non_root_debug=suppress_non_root_debug) + # Note: going ahead with force=True here so that we replace any + # existing logger. Though we warn if it looks like we are doing + # that so we can try to avoid creating the first one. + had_previous_handlers = bool(logging.root.handlers) logging.basicConfig(level=lmap[level], format='%(message)s', - handlers=[loghandler]) + handlers=[loghandler], + force=True) + if had_previous_handlers: + logging.warning('setup_logging: force-replacing previous handlers.') - # DISABLING THIS BIT FOR NOW - want to keep things as pure as possible. - if bool(False): - # Now wire Python stdout/stderr output to generate log entries - # in addition to its regular routing. Make sure to do this *after* we - # tell the log-handler to write to stderr, otherwise we get an infinite - # loop. - # NOTE: remember that this won't capture subcommands or other - # non-python stdout/stderr output. - sys.stdout = LogRedirect( # type: ignore - 'stdout', sys.stdout, loghandler, LogLevel.INFO) - sys.stderr = LogRedirect( # type: ignore - 'stderr', sys.stderr, loghandler, LogLevel.INFO) + # Optionally intercept Python's stdout/stderr output and generate + # log entries from it. + if log_stdout_stderr: + sys.stdout = FileLogEcho( # type: ignore + sys.stdout, 'stdout', loghandler) + sys.stderr = FileLogEcho( # type: ignore + sys.stderr, 'stderr', loghandler) return loghandler