diff --git a/.efrocachemap b/.efrocachemap index 13d382b0..828baec0 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -3932,24 +3932,24 @@ "assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450", "assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e", "assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f", - "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/13/a0/ef123d2da6dadcf719db687a7f2b", - "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/35/5b8bb7d54fc172c269940562b6ad", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8c/51/a79b59f13bb601147766411ede9b", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0f/75/944ebd95803210c4fd76ae8fb987", - "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/05/87/7f0670d132417bb3ab90697afcf1", - "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3d/66/e28afa2246fef0f079b2e6e25d45", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/42/4a/3ab2b7025e66a942c63974ea74d7", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/01/28/1690f5728849db47a4db0badba4a", - "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/f9/8a/72522f7d2d6a9884fd19f7ab266b", - "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/84/a5/7c0050e6fdf0c9059a7dbff94551", - "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/01/a8/f25da7a3e3e6db52e8504672952c", - "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/82/21/261b3ba6d012a02386e425fc1257", - "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/22/0a/572910a668aeb607f6e6d9544266", - "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e1/1a/ff5b7abc008154a71152a1635d33", - "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e6/93/af621822f3d5d6021efecac32235", - "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3e/62/cc942c068c685832f6b7664bbb0f", - "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/68/1a/8e04067c191229b1dcfb6a470ec1", - "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/dd/1f/646dd2c8f69f3d123aa02de7ec5c", - "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/28/45/922dc5d0c0b61d252355cf09997d", - "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/41/25/842bf633e2f05dd3c037460064ab" + "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b8/eb/280d15f31dcbf3830e6730f9c4be", + "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b9/b6/7a4d577d71bbce1d0a42d0fa8f0e", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/64/46/406adb6907d84ab1fbce0238b2f5", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e2/4a/c3ab9dfa2eb438e2270c867a0a32", + "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/75/7d/64dee3a33a9b9df3213789d031ca", + "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/24/4e/0de9a93c5abf5f91efa893c0f657", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ba/e3/7c54b7b121a73945a334acf2a778", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8b/80/59caf9ded1ac2d852f57489049c2", + "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/72/b8/b24e6069245d0c3e7c1f049e1386", + "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/79/62/ad715d74684b940ce846e7e58d8c", + "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/23/41/552a8ffeef5bc10bd0af04c10565", + "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/bf/aa/1dcfd60aeaec8d42f06adac2156b", + "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a5/db/bcc616e930ef02de5f086cc819a1", + "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7f/3b/af2f0efe5996de7cfc31a112c514", + "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/22/e3bb8333289707bba87db62cff1b", + "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/88/21434630ae75dfd78e8f1225beaf", + "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3c/2c/317eabff84c4b1819aeb78dc984a", + "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/12/b2/e0851ca040064fb5c4d953003a10", + "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/15/88/cfef2301d0ff44a26c411ed11c39", + "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b2/7a/5c80fa986d5da7908d7c3c31c61f" } \ No newline at end of file diff --git a/ballisticacore-cmake/CMakeLists.txt b/ballisticacore-cmake/CMakeLists.txt index 6e675d47..833edf39 100644 --- a/ballisticacore-cmake/CMakeLists.txt +++ b/ballisticacore-cmake/CMakeLists.txt @@ -174,10 +174,15 @@ add_executable(ballisticacore # AUTOGENERATED_PUBLIC_BEGIN (this section is managed by the "update_project" tool) ${BA_SRC_ROOT}/ballistica/app/app.cc ${BA_SRC_ROOT}/ballistica/app/app.h + ${BA_SRC_ROOT}/ballistica/app/app_config.cc ${BA_SRC_ROOT}/ballistica/app/app_config.h + ${BA_SRC_ROOT}/ballistica/app/app_globals.cc ${BA_SRC_ROOT}/ballistica/app/app_globals.h + ${BA_SRC_ROOT}/ballistica/app/headless_app.cc ${BA_SRC_ROOT}/ballistica/app/headless_app.h + ${BA_SRC_ROOT}/ballistica/app/stress_test.cc ${BA_SRC_ROOT}/ballistica/app/stress_test.h + ${BA_SRC_ROOT}/ballistica/app/vr_app.cc ${BA_SRC_ROOT}/ballistica/app/vr_app.h ${BA_SRC_ROOT}/ballistica/audio/al_sys.cc ${BA_SRC_ROOT}/ballistica/audio/al_sys.h @@ -277,7 +282,9 @@ add_executable(ballisticacore ${BA_SRC_ROOT}/ballistica/game/game.h ${BA_SRC_ROOT}/ballistica/game/game_stream.h ${BA_SRC_ROOT}/ballistica/game/host_activity.h + ${BA_SRC_ROOT}/ballistica/game/player.cc ${BA_SRC_ROOT}/ballistica/game/player.h + ${BA_SRC_ROOT}/ballistica/game/player_spec.cc ${BA_SRC_ROOT}/ballistica/game/player_spec.h ${BA_SRC_ROOT}/ballistica/game/score_to_beat.h ${BA_SRC_ROOT}/ballistica/game/session/client_session.h @@ -453,6 +460,7 @@ add_executable(ballisticacore ${BA_SRC_ROOT}/ballistica/networking/network_write_module.h ${BA_SRC_ROOT}/ballistica/networking/networking.h ${BA_SRC_ROOT}/ballistica/networking/networking_sys.h + ${BA_SRC_ROOT}/ballistica/networking/sockaddr.cc ${BA_SRC_ROOT}/ballistica/networking/sockaddr.h ${BA_SRC_ROOT}/ballistica/networking/telnet_server.cc ${BA_SRC_ROOT}/ballistica/networking/telnet_server.h diff --git a/src/ballistica/app/app_config.cc b/src/ballistica/app/app_config.cc new file mode 100644 index 00000000..c7063aa8 --- /dev/null +++ b/src/ballistica/app/app_config.cc @@ -0,0 +1,245 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/app/app_config.h" + +#include + +#include "ballistica/ballistica.h" +#include "ballistica/platform/platform.h" +#include "ballistica/python/python.h" + +namespace ballistica { + +void AppConfig::Init() { new AppConfig(); } + +auto AppConfig::Entry::FloatValue() const -> float { + throw Exception("not a float entry"); +} + +auto AppConfig::Entry::StringValue() const -> std::string { + throw Exception("not a string entry"); +} + +auto AppConfig::Entry::IntValue() const -> int { + throw Exception("not an int entry"); +} + +auto AppConfig::Entry::BoolValue() const -> bool { + throw Exception("not a bool entry"); +} + +auto AppConfig::Entry::DefaultFloatValue() const -> float { + throw Exception("not a float entry"); +} + +auto AppConfig::Entry::DefaultStringValue() const -> std::string { + throw Exception("not a string entry"); +} + +auto AppConfig::Entry::DefaultIntValue() const -> int { + throw Exception("not an int entry"); +} + +auto AppConfig::Entry::DefaultBoolValue() const -> bool { + throw Exception("not a bool entry"); +} + +class AppConfig::StringEntry : public AppConfig::Entry { + public: + StringEntry() = default; + StringEntry(const char* name, std::string default_value) + : Entry(name), default_value_(std::move(default_value)) {} + auto GetType() const -> Type override { return Type::kString; } + auto Resolve() const -> std::string { + return g_python->GetRawConfigValue(name().c_str(), default_value_.c_str()); + } + auto StringValue() const -> std::string override { return Resolve(); } + auto DefaultStringValue() const -> std::string override { + return default_value_; + } + + private: + std::string default_value_; +}; + +class AppConfig::FloatEntry : public AppConfig::Entry { + public: + FloatEntry() = default; + FloatEntry(const char* name, float default_value) + : Entry(name), default_value_(default_value) {} + auto GetType() const -> Type override { return Type::kFloat; } + auto Resolve() const -> float { + return g_python->GetRawConfigValue(name().c_str(), default_value_); + } + auto FloatValue() const -> float override { return Resolve(); } + auto DefaultFloatValue() const -> float override { return default_value_; } + + private: + float default_value_{}; +}; + +class AppConfig::IntEntry : public AppConfig::Entry { + public: + IntEntry() = default; + IntEntry(const char* name, int default_value) + : Entry(name), default_value_(default_value) {} + auto GetType() const -> Type override { return Type::kInt; } + auto Resolve() const -> int { + return g_python->GetRawConfigValue(name().c_str(), default_value_); + } + auto IntValue() const -> int override { return Resolve(); } + auto DefaultIntValue() const -> int override { return default_value_; } + + private: + int default_value_{}; +}; + +class AppConfig::BoolEntry : public AppConfig::Entry { + public: + BoolEntry() = default; + BoolEntry(const char* name, bool default_value) + : Entry(name), default_value_(default_value) {} + auto GetType() const -> Type override { return Type::kBool; } + auto Resolve() const -> bool { + return g_python->GetRawConfigValue(name().c_str(), default_value_); + } + auto BoolValue() const -> bool override { return Resolve(); } + auto DefaultBoolValue() const -> bool override { return default_value_; } + + private: + bool default_value_{}; +}; + +AppConfig::AppConfig() { + // (We're a singleton). + assert(g_app_config == nullptr); + g_app_config = this; + SetupEntries(); +} + +template +void AppConfig::CompleteMap(const T& entry_map) { + for (auto&& i : entry_map) { + assert(entries_by_name_.find(i.second.name()) == entries_by_name_.end()); + assert(i.first < decltype(i.first)::kLast); + entries_by_name_[i.second.name()] = &i.second; + } + + // Make sure all values have entries. +#if BA_DEBUG_BUILD + int last = static_cast(decltype(entry_map.begin()->first)::kLast); // ew + for (int j = 0; j < last; ++j) { + auto i2 = + entry_map.find(static_castfirst)>(j)); + if (i2 == entry_map.end()) { + throw Exception("Missing appconfig entry " + std::to_string(j)); + } + } +#endif +} + +void AppConfig::SetupEntries() { + // Register all our typed entries. + float_entries_[FloatID::kScreenGamma] = FloatEntry("Screen Gamma", 1.0F); + float_entries_[FloatID::kScreenPixelScale] = + FloatEntry("Screen Pixel Scale", 1.0F); + float_entries_[FloatID::kTouchControlsScale] = + FloatEntry("Touch Controls Scale", 1.0F); + float_entries_[FloatID::kTouchControlsScaleMovement] = + FloatEntry("Touch Controls Scale Movement", 1.0F); + float_entries_[FloatID::kTouchControlsScaleActions] = + FloatEntry("Touch Controls Scale Actions", 1.0F); + float_entries_[FloatID::kSoundVolume] = FloatEntry("Sound Volume", 1.0F); + float_entries_[FloatID::kMusicVolume] = FloatEntry("Music Volume", 1.0F); + + // Note: keep this synced with the defaults in MainActivity.java. + float gvrrts_default = g_platform->IsRunningOnDaydream() ? 1.0F : 0.5F; + float_entries_[FloatID::kGoogleVRRenderTargetScale] = + FloatEntry("GVR Render Target Scale", gvrrts_default); + + string_entries_[StringID::kResolutionAndroid] = + StringEntry("Resolution (Android)", "Auto"); + string_entries_[StringID::kTouchActionControlType] = + StringEntry("Touch Action Control Type", "buttons"); + string_entries_[StringID::kTouchMovementControlType] = + StringEntry("Touch Movement Control Type", "swipe"); + string_entries_[StringID::kGraphicsQuality] = + StringEntry("Graphics Quality", "Auto"); + string_entries_[StringID::kTextureQuality] = + StringEntry("Texture Quality", "Auto"); + string_entries_[StringID::kVerticalSync] = + StringEntry("Vertical Sync", "Auto"); + string_entries_[StringID::kVRHeadRelativeAudio] = + StringEntry("VR Head Relative Audio", "Auto"); + string_entries_[StringID::kMacControllerSubsystem] = + StringEntry("Mac Controller Subsystem", "Classic"); + string_entries_[StringID::kTelnetPassword] = + StringEntry("Telnet Password", "changeme"); + + int_entries_[IntID::kPort] = IntEntry("Port", kDefaultPort); + int_entries_[IntID::kTelnetPort] = + IntEntry("Telnet Port", kDefaultTelnetPort); + + bool_entries_[BoolID::kTouchControlsSwipeHidden] = + BoolEntry("Touch Controls Swipe Hidden", false); + bool_entries_[BoolID::kFullscreen] = BoolEntry("Fullscreen", false); + bool_entries_[BoolID::kKickIdlePlayers] = + BoolEntry("Kick Idle Players", false); + bool_entries_[BoolID::kAlwaysUseInternalKeyboard] = + BoolEntry("Always Use Internal Keyboard", false); + bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false); + bool_entries_[BoolID::kTVBorder] = + BoolEntry("TV Border", g_platform->IsRunningOnTV()); + bool_entries_[BoolID::kKeyboardP2Enabled] = + BoolEntry("Keyboard P2 Enabled", false); + bool_entries_[BoolID::kEnablePackageMods] = + BoolEntry("Enable Package Mods", false); + bool_entries_[BoolID::kChatMuted] = BoolEntry("Chat Muted", false); + bool_entries_[BoolID::kEnableRemoteApp] = + BoolEntry("Enable Remote App", true); + bool_entries_[BoolID::kEnableTelnet] = BoolEntry("Enable Telnet", true); + bool_entries_[BoolID::kDisableCameraShake] = + BoolEntry("Disable Camera Shake", false); + bool_entries_[BoolID::kDisableCameraGyro] = + BoolEntry("Disable Camera Gyro", false); + + // Now add everything to our name map and make sure all is kosher. + CompleteMap(float_entries_); + CompleteMap(int_entries_); + CompleteMap(string_entries_); + CompleteMap(bool_entries_); +} + +auto AppConfig::Resolve(FloatID id) -> float { + auto i = float_entries_.find(id); + if (i == float_entries_.end()) { + throw Exception("Invalid config entry"); + } + return i->second.Resolve(); +} + +auto AppConfig::Resolve(StringID id) -> std::string { + auto i = string_entries_.find(id); + if (i == string_entries_.end()) { + throw Exception("Invalid config entry"); + } + return i->second.Resolve(); +} + +auto AppConfig::Resolve(BoolID id) -> bool { + auto i = bool_entries_.find(id); + if (i == bool_entries_.end()) { + throw Exception("Invalid config entry"); + } + return i->second.Resolve(); +} + +auto AppConfig::Resolve(IntID id) -> int { + auto i = int_entries_.find(id); + if (i == int_entries_.end()) { + throw Exception("Invalid config entry"); + } + return i->second.Resolve(); +} + +} // namespace ballistica diff --git a/src/ballistica/app/app_globals.cc b/src/ballistica/app/app_globals.cc new file mode 100644 index 00000000..bb246033 --- /dev/null +++ b/src/ballistica/app/app_globals.cc @@ -0,0 +1,12 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/app/app_globals.h" + +namespace ballistica { + +AppGlobals::AppGlobals(int argc_in, char** argv_in) + : argc{argc_in}, + argv{argv_in}, + main_thread_id{std::this_thread::get_id()} {} + +} // namespace ballistica diff --git a/src/ballistica/app/headless_app.cc b/src/ballistica/app/headless_app.cc new file mode 100644 index 00000000..97f3e01a --- /dev/null +++ b/src/ballistica/app/headless_app.cc @@ -0,0 +1,21 @@ +// Released under the MIT License. See LICENSE for details. +#if BA_HEADLESS_BUILD + +#include "ballistica/app/headless_app.h" + +#include "ballistica/ballistica.h" + +namespace ballistica { + +// We could technically use the vanilla App class here since we're not +// changing anything. +HeadlessApp::HeadlessApp(Thread* thread) : App(thread) { + // NewThreadTimer(10, true, NewLambdaRunnable([this] { + // assert(g_app); + // g_app->RunEvents(); + // })); +} + +} // namespace ballistica + +#endif // BA_HEADLESS_BUILD diff --git a/src/ballistica/app/stress_test.cc b/src/ballistica/app/stress_test.cc new file mode 100644 index 00000000..44357a84 --- /dev/null +++ b/src/ballistica/app/stress_test.cc @@ -0,0 +1,101 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/app/stress_test.h" + +#include "ballistica/ballistica.h" +#include "ballistica/graphics/graphics_server.h" +#include "ballistica/graphics/renderer.h" +#include "ballistica/input/input.h" +#include "ballistica/platform/platform.h" + +namespace ballistica { + +void StressTest::Update() { + assert(InMainThread()); + + // Handle a little misc stuff here. + // If we're currently running stress-tests, update that stuff. + if (stress_testing_ && g_input) { + // Update our fake inputs to make our dudes run around. + g_input->ProcessStressTesting(stress_test_player_count_); + + // Every 10 seconds update our stress-test stats. + millisecs_t t = GetRealTime(); + if (t - last_stress_test_update_time_ >= 10000) { + if (stress_test_stats_file_ == nullptr) { + assert(g_platform); + std::string f_name = + g_platform->GetUserPythonDirectory() + "/stress_test_stats.csv"; + stress_test_stats_file_ = g_platform->FOpen(f_name.c_str(), "wb"); + if (stress_test_stats_file_ != nullptr) { + fprintf(stress_test_stats_file_, + "time,averageFps,nodes,models,collide_models,textures,sounds," + "pssMem,sharedDirtyMem,privateDirtyMem\n"); + fflush(stress_test_stats_file_); + if (g_buildconfig.ostype_android()) { + // On android, let the OS know we've added or removed a file + // (limit to android or we'll get an unimplemented warning). + g_platform->AndroidRefreshFile(f_name); + } + } + } + if (stress_test_stats_file_ != nullptr) { + // See how many frames we've rendered this past interval. + int total_frames_rendered; + if (g_graphics_server && g_graphics_server->renderer()) { + total_frames_rendered = + g_graphics_server->renderer()->total_frames_rendered(); + } else { + total_frames_rendered = last_total_frames_rendered_; + } + float avg = + static_cast(total_frames_rendered + - last_total_frames_rendered_) + / (static_cast(t - last_stress_test_update_time_) / 1000.0f); + last_total_frames_rendered_ = total_frames_rendered; + uint32_t model_count = 0; + uint32_t collide_model_count = 0; + uint32_t texture_count = 0; + uint32_t sound_count = 0; + uint32_t node_count = 0; + if (g_media) { + model_count = g_media->total_model_count(); + collide_model_count = g_media->total_collide_model_count(); + texture_count = g_media->total_texture_count(); + sound_count = g_media->total_sound_count(); + } + assert(g_game); + std::string mem_usage = g_platform->GetMemUsageInfo(); + fprintf(stress_test_stats_file_, "%d,%.1f,%d,%d,%d,%d,%d,%s\n", + static_cast_check_fit(GetRealTime()), avg, node_count, + model_count, collide_model_count, texture_count, sound_count, + mem_usage.c_str()); + fflush(stress_test_stats_file_); + } + last_stress_test_update_time_ = t; + } + } +} + +void StressTest::Set(bool enable, int player_count) { + assert(InMainThread()); + bool was_stress_testing = stress_testing_; + stress_testing_ = enable; + stress_test_player_count_ = player_count; + + // If we're turning on, reset our intervals and things. + if (!was_stress_testing && stress_testing_) { + // So our first sample is 1 interval from now. + last_stress_test_update_time_ = GetRealTime(); + + // Reset our frames-rendered tally. + if (g_graphics_server && g_graphics_server->renderer()) { + last_total_frames_rendered_ = + g_graphics_server->renderer()->total_frames_rendered(); + } else { + // Assume zero if there's no graphics yet. + last_total_frames_rendered_ = 0; + } + } +} +} // namespace ballistica diff --git a/src/ballistica/app/vr_app.cc b/src/ballistica/app/vr_app.cc new file mode 100644 index 00000000..d305f143 --- /dev/null +++ b/src/ballistica/app/vr_app.cc @@ -0,0 +1,110 @@ +// Released under the MIT License. See LICENSE for details. +#if BA_VR_BUILD + +#include "ballistica/app/vr_app.h" + +#include "ballistica/game/game.h" +#include "ballistica/graphics/graphics_server.h" +#include "ballistica/graphics/renderer.h" + +namespace ballistica { + +VRApp::VRApp(Thread* thread) : App(thread) {} + +void VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) { + PushCall([this, state] { + // Convert this to a full hands state, adding in some simple elbow + // positioning of our own and left/right. + VRHandsState s; + s.l.tx = -0.2f; + s.l.ty = -0.2f; + s.l.tz = -0.3f; + + // Hmm; for now lets always assign this as right hand even when its in + // left-handed mode to keep things simple on the back-end. Can change later + // if there's a downside to that. + s.r.type = VRHandType::kDaydreamRemote; + s.r.tx = 0.2f; + s.r.ty = -0.2f; + s.r.tz = -0.3f; + s.r.yaw = state.r0; + s.r.pitch = state.r1; + s.r.roll = state.r2; + VRSetHands(s); + }); +} + +void VRApp::VRSetDrawDimensions(int w, int h) { + g_graphics_server->VideoResize(w, h); +} + +void VRApp::VRPreDraw() { + if (!g_graphics_server || !g_graphics_server->renderer()) { + return; + } + assert(InMainThread()); + if (FrameDef* frame_def = g_graphics_server->GetRenderFrameDef()) { + // Note: this could be part of PreprocessRenderFrameDef but + // the non-vr path needs it to be separate since preprocess doesn't + // happen sometimes. Should probably clean that up. + g_graphics_server->RunFrameDefMeshUpdates(frame_def); + + // store this for the duration of this frame + vr_render_frame_def_ = frame_def; + g_graphics_server->PreprocessRenderFrameDef(frame_def); + } +} + +void VRApp::VRPostDraw() { + assert(InMainThread()); + if (!g_graphics_server || !g_graphics_server->renderer()) { + return; + } + if (vr_render_frame_def_) { + g_graphics_server->FinishRenderFrameDef(vr_render_frame_def_); + vr_render_frame_def_ = nullptr; + } + RunRenderUpkeepCycle(); +} + +void VRApp::VRSetHead(float tx, float ty, float tz, float yaw, float pitch, + float roll) { + assert(InMainThread()); + Renderer* renderer = g_graphics_server->renderer(); + if (renderer == nullptr) return; + renderer->VRSetHead(tx, ty, tz, yaw, pitch, roll); +} + +void VRApp::VRSetHands(const VRHandsState& state) { + assert(InMainThread()); + + // Pass this along to the renderer (in this same thread) for drawing + // (so hands can be drawn at their absolute most up-to-date positions, etc). + Renderer* renderer = g_graphics_server->renderer(); + if (renderer == nullptr) return; + renderer->VRSetHands(state); + + // ALSO ship it off to the game/ui thread to actually handle input from it. + g_game->PushVRHandsState(state); +} + +void VRApp::VRDrawEye(int eye, float yaw, float pitch, float roll, float tan_l, + float tan_r, float tan_b, float tan_t, float eye_x, + float eye_y, float eye_z, int viewport_x, + int viewport_y) { + if (!g_graphics_server || !g_graphics_server->renderer()) { + return; + } + assert(InMainThread()); + if (vr_render_frame_def_) { + // set up VR eye stuff... + Renderer* renderer = g_graphics_server->renderer(); + renderer->VRSetEye(eye, yaw, pitch, roll, tan_l, tan_r, tan_b, tan_t, eye_x, + eye_y, eye_z, viewport_x, viewport_y); + g_graphics_server->DrawRenderFrameDef(vr_render_frame_def_); + } +} + +} // namespace ballistica + +#endif // BA_VR_BUILD diff --git a/src/ballistica/game/player.cc b/src/ballistica/game/player.cc new file mode 100644 index 00000000..1817b4d6 --- /dev/null +++ b/src/ballistica/game/player.cc @@ -0,0 +1,418 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/game/player.h" + +#include + +#include "ballistica/game/host_activity.h" +#include "ballistica/game/session/host_session.h" +#include "ballistica/generic/utils.h" +#include "ballistica/input/device/joystick.h" +#include "ballistica/python/class/python_class_session_player.h" +#include "ballistica/python/python.h" +#include "ballistica/python/python_context_call.h" +#include "ballistica/scene/node/node_attribute.h" +#include "ballistica/scene/node/node_type.h" + +namespace ballistica { + +Player::Player(int id_in, HostSession* host_session) + : id_(id_in), creation_time_(GetRealTime()), host_session_(host_session) { + assert(host_session); + assert(InGameThread()); +} + +Player::~Player() { + assert(InGameThread()); + + // If we have an input-device attached to us, detach it. + InputDevice* input_device = input_device_.get(); + if (input_device) { + input_device->DetachFromPlayer(); + } + + // Release our ref to ourself if we have one. + if (py_ref_) { + Py_DECREF(py_ref_); + } +} + +auto Player::GetName(bool full, bool icon) const -> std::string { + std::string n = full ? full_name_ : name_; + + // Quasi-hacky: if they ask for no icon, strip the first char off our string + // if its in the custom-use-range. + if (!icon) { + std::vector uni = Utils::UnicodeFromUTF8(n, "3f94f4f"); + if (!uni.empty() && uni[0] >= 0xE000 && uni[0] <= 0xF8FF) { + uni.erase(uni.begin()); + } + return Utils::UTF8FromUnicode(uni); + } else { + return n; + } +} + +auto Player::GetHostActivity() const -> HostActivity* { + return host_activity_.get(); +} + +void Player::SetHostActivity(HostActivity* a) { + assert(InGameThread()); + + // Make sure we get pulled out of one activity before being added to another. + if (a && in_activity_) { + std::string old_name = + host_activity_.exists() + ? PythonRef(host_activity_->GetPyActivity(), PythonRef::kAcquire) + .Str() + : ""; + std::string new_name = + PythonRef(a->GetPyActivity(), PythonRef::kAcquire).Str(); + BA_LOG_PYTHON_TRACE_ONCE( + "Player::SetHostActivity() called when already in an activity (old=" + + old_name + ", new=" + new_name + ")"); + } else if (!a && !in_activity_) { + BA_LOG_PYTHON_TRACE_ONCE( + "Player::SetHostActivity() called with nullptr when not in an " + "activity"); + } + host_activity_ = a; + in_activity_ = (a != nullptr); +} + +void Player::SetPosition(const Vector3f& position) { + position_ = position; + have_position_ = true; +} + +void Player::ResetInput() { + // Hold a ref to ourself while clearing this to make sure + // we don't die midway as a result of freeing something. + Object::Ref ref(this); + calls_.clear(); + left_held_ = right_held_ = up_held_ = down_held_ = have_position_ = false; +} + +void Player::SetPyTeam(PyObject* team) { + if (team != nullptr && team != Py_None) { + // We store a weak-ref to this. + py_team_weak_ref_.Steal(PyWeakref_NewRef(team, nullptr)); + } else { + py_team_weak_ref_.Release(); + } +} + +auto Player::GetPyTeam() -> PyObject* { + PyObject* obj = py_team_weak_ref_.get(); + if (!obj) { + return Py_None; + } + return PyWeakref_GetObject(obj); +} + +void Player::SetPyCharacter(PyObject* character) { + if (character != nullptr && character != Py_None) { + py_character_.Acquire(character); + } else { + py_character_.Release(); + } +} + +auto Player::GetPyCharacter() -> PyObject* { + return py_character_.exists() ? py_character_.get() : Py_None; +} + +void Player::SetPyColor(PyObject* c) { py_color_.Acquire(c); } +auto Player::GetPyColor() -> PyObject* { + return py_color_.exists() ? py_color_.get() : Py_None; +} + +void Player::SetPyHighlight(PyObject* c) { py_highlight_.Acquire(c); } +auto Player::GetPyHighlight() -> PyObject* { + return py_highlight_.exists() ? py_highlight_.get() : Py_None; +} + +void Player::SetPyActivityPlayer(PyObject* c) { py_activityplayer_.Acquire(c); } +auto Player::GetPyActivityPlayer() -> PyObject* { + return py_activityplayer_.exists() ? py_activityplayer_.get() : Py_None; +} + +auto Player::GetPyRef(bool new_ref) -> PyObject* { + assert(InGameThread()); + if (py_ref_ == nullptr) { + py_ref_ = PythonClassSessionPlayer::Create(this); + } + if (new_ref) { + Py_INCREF(py_ref_); + } + return py_ref_; +} + +void Player::AssignInputCall(InputType type, PyObject* call_obj) { + assert(InGameThread()); + assert(static_cast(type) >= 0 + && static_cast(type) < static_cast(InputType::kLast)); + + // Special case: if they're assigning hold-position-press or + // hold-position-release, or any direction events, we add in a hold-position + // press/release event before we deliver any other events.. that way newly + // created stuff is informed of the hold state and doesn't wrongly think they + // should start moving. + switch (type) { + case InputType::kHoldPositionPress: + case InputType::kHoldPositionRelease: + case InputType::kLeftPress: + case InputType::kLeftRelease: + case InputType::kRightPress: + case InputType::kUpPress: + case InputType::kUpRelease: + case InputType::kDownPress: + case InputType::kDownRelease: + case InputType::kUpDown: + case InputType::kLeftRight: { + send_hold_state_ = true; + break; + } + default: + break; + } + if (call_obj) { + calls_[static_cast(type)] = Object::New(call_obj); + } else { + calls_[static_cast(type)].Clear(); + } + + // If they assigned l/r, immediately send an update for its current value. + if (type == InputType::kLeftRight) { + RunInput(type, lr_state_); + } + + // Same for up/down. + if (type == InputType::kUpDown) { + RunInput(type, ud_state_); + } + + // Same for run. + if (type == InputType::kRun) { + RunInput(type, run_state_); + } + + // Same for fly. + if (type == InputType::kFlyPress && fly_held_) { + RunInput(type); + } +} + +void Player::RunInput(InputType type, float value) { + assert(InGameThread()); + + const float threshold = kJoystickDiscreteThresholdFloat; + + // Most input commands cause us to reset the player's time-out + // there are a few exceptions though - very small analog values + // get ignored since they can come through without user intervention. + bool reset_time_out = true; + if (type == InputType::kLeftRight || type == InputType::kUpDown) { + if (std::abs(value) < 0.3f) { + reset_time_out = false; + } + } + if (type == InputType::kRun) { + if (value < 0.3f) { + reset_time_out = false; + } + } + + // Also ignore hold-position stuff since it can come through without user + // interaction. + if ((type == InputType::kHoldPositionPress) + || (type == InputType::kHoldPositionRelease)) + reset_time_out = false; + + if (reset_time_out) { + time_out_ = BA_PLAYER_TIME_OUT; + } + + // Keep track of the hold-position state that comes through here. + // any-time hold position buttons are re-assigned, we subsequently + // re-send the current hold-state so whatever its driving starts out correctly + // held if need be. + if (type == InputType::kHoldPositionPress) { + hold_position_ = true; + } else if (type == InputType::kHoldPositionRelease) { + hold_position_ = false; + } else if (type == InputType::kFlyPress) { + fly_held_ = true; + } else if (type == InputType::kFlyRelease) { + fly_held_ = false; + } + + // If we were supposed to deliver hold-state, go ahead and do that first. + if (send_hold_state_) { + send_hold_state_ = false; + if (hold_position_) { + RunInput(InputType::kHoldPositionPress); + } else { + RunInput(InputType::kHoldPositionRelease); + } + } + + // Let's make our life simpler by converting held-position-joystick-events.. + { + // We need to store these since we might look at them during a hold-position + // event when we don't have their originating events available. + if (type == InputType::kLeftRight) { + lr_state_ = value; + } + if (type == InputType::kUpDown) { + ud_state_ = value; + } + if (type == InputType::kRun) { + run_state_ = value; + } + + // Special input commands - keep track of left/right and up/down positions + // so we can deliver simple "leftUp", "leftDown", etc type of events + // in addition to the standard absolute leftRight positions, etc. + if (type == InputType::kLeftRight || type == InputType::kHoldPositionPress + || type == InputType::kHoldPositionRelease) { + float arg = lr_state_; + if (hold_position_) { + arg = 0.0f; // Throttle is off. + } + if (left_held_) { + if (arg > -threshold) { + left_held_ = false; + RunInput(InputType::kLeftRelease); + } + } else if (right_held_) { + if (arg < threshold) { + right_held_ = false; + RunInput(InputType::kRightRelease); + } + } else { + if (arg >= threshold) { + if (!left_held_ && !up_held_ && !down_held_) { + right_held_ = true; + RunInput(InputType::kRightPress); + } + } else if (arg <= -threshold) { + if (!right_held_ && !up_held_ && !down_held_) { + left_held_ = true; + RunInput(InputType::kLeftPress); + } + } + } + } + if (type == InputType::kUpDown || type == InputType::kHoldPositionPress + || type == InputType::kHoldPositionRelease) { + float arg = ud_state_; + if (hold_position_) arg = 0.0f; // throttle is off; + if (up_held_) { + if (arg < threshold) { + up_held_ = false; + RunInput(InputType::kUpRelease); + } + } else if (down_held_) { + if (arg > -threshold) { + down_held_ = false; + RunInput(InputType::kDownRelease); + } + } else { + if (arg <= -threshold) { + if (!left_held_ && !right_held_ && !up_held_) { + down_held_ = true; + RunInput(InputType::kDownPress); + } + } else if (arg >= threshold) { + if (!left_held_ && !up_held_ && !right_held_) { + up_held_ = true; + RunInput(InputType::kUpPress); + } + } + } + } + } + + auto j = calls_.find(static_cast(type)); + if (j != calls_.end() && j->second.exists()) { + if (type == InputType::kRun) { + PythonRef args( + Py_BuildValue("(f)", std::min(1.0f, std::max(0.0f, value))), + PythonRef::kSteal); + j->second->Run(args.get()); + } else if (type == InputType::kLeftRight || type == InputType::kUpDown) { + PythonRef args( + Py_BuildValue("(f)", std::min(1.0f, std::max(-1.0f, value))), + PythonRef::kSteal); + j->second->Run(args.get()); + } else { + j->second->Run(); + } + } +} + +auto Player::GetHostSession() const -> HostSession* { + return host_session_.get(); +} + +void Player::SetName(const std::string& name, const std::string& full_name, + bool is_real) { + assert(InGameThread()); + HostSession* host_session = GetHostSession(); + BA_PRECONDITION(host_session); + name_is_real_ = is_real; + name_ = host_session->GetUnusedPlayerName(this, name); + full_name_ = full_name; + + // If we're already in the game and our name is changing, we need to update + // the roster. + if (accepted_) { + g_game->UpdateGameRoster(); + } +} + +void Player::InputCommand(InputType type, float value) { + assert(InGameThread()); + switch (type) { + case InputType::kUpDown: + case InputType::kLeftRight: + case InputType::kRun: + RunInput(type, value); + break; + // case InputType::kReset: + // Log("Error: FIXME: player-input-reset command unimplemented"); + // break; + default: + RunInput(type); + break; + } +} + +void Player::SetInputDevice(InputDevice* input_device) { + input_device_ = input_device; +} + +auto Player::GetPublicAccountID() const -> std::string { + assert(InGameThread()); + if (input_device_.exists()) { + return input_device_->GetPublicAccountID(); + } + return ""; +} + +void Player::SetIcon(const std::string& tex_name, + const std::string& tint_tex_name, + const std::vector& tint_color, + const std::vector& tint2_color) { + assert(tint_color.size() == 3); + assert(tint2_color.size() == 3); + icon_tex_name_ = tex_name; + icon_tint_tex_name_ = tint_tex_name; + icon_tint_color_ = tint_color; + icon_tint2_color_ = tint2_color; + icon_set_ = true; +} + +} // namespace ballistica diff --git a/src/ballistica/game/player_spec.cc b/src/ballistica/game/player_spec.cc new file mode 100644 index 00000000..1df777d9 --- /dev/null +++ b/src/ballistica/game/player_spec.cc @@ -0,0 +1,109 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/game/player_spec.h" + +#include + +#include "ballistica/app/app_globals.h" +#include "ballistica/game/account.h" +#include "ballistica/generic/json.h" +#include "ballistica/generic/utils.h" +#include "ballistica/platform/platform.h" + +namespace ballistica { + +PlayerSpec::PlayerSpec() : account_type_(AccountType::kInvalid) {} + +PlayerSpec::PlayerSpec(const std::string& s) { + cJSON* root_obj = cJSON_Parse(s.c_str()); + bool success = false; + if (root_obj) { + cJSON* name_obj = cJSON_GetObjectItem(root_obj, "n"); + cJSON* short_name_obj = cJSON_GetObjectItem(root_obj, "sn"); + cJSON* account_obj = cJSON_GetObjectItem(root_obj, "a"); + if (name_obj && short_name_obj && account_obj) { + name_ = Utils::GetValidUTF8(name_obj->valuestring, "psps"); + short_name_ = Utils::GetValidUTF8(short_name_obj->valuestring, "psps2"); + + // Account type may technically be something we don't recognize, + // but that's ok.. it'll just be 'invalid' to us in that case + account_type_ = Account::AccountTypeFromString(account_obj->valuestring); + success = true; + } + cJSON_Delete(root_obj); + } + if (!success) { + Log("Error creating PlayerSpec from string: '" + s + "'"); + name_ = ""; + short_name_ = ""; + account_type_ = AccountType::kInvalid; + } +} + +auto PlayerSpec::GetDisplayString() const -> std::string { + return Account::AccountTypeToIconString(account_type_) + name_; +} + +auto PlayerSpec::GetShortName() const -> std::string { + if (short_name_.empty()) { + return name_; + } + return short_name_; +} + +auto PlayerSpec::operator==(const PlayerSpec& spec) const -> bool { + // NOTE: need to add account ID in here once that's available + return (spec.name_ == name_ && spec.short_name_ == short_name_ + && spec.account_type_ == account_type_); +} + +auto PlayerSpec::GetSpecString() const -> std::string { + cJSON* root; + root = cJSON_CreateObject(); + cJSON_AddStringToObject(root, "n", name_.c_str()); + cJSON_AddStringToObject(root, "a", + Account::AccountTypeToString(account_type_).c_str()); + cJSON_AddStringToObject(root, "sn", short_name_.c_str()); + char* out = cJSON_PrintUnformatted(root); + std::string out_s = out; + free(out); + cJSON_Delete(root); + + // We should never allow ourself to have all this add up to more than 256. + assert(out_s.size() < 256); + + return out_s; +} + +auto PlayerSpec::GetAccountPlayerSpec() -> PlayerSpec { + PlayerSpec spec; + if (g_account->GetAccountState() == AccountState::kSignedIn) { + spec.account_type_ = g_app_globals->account_type; + spec.name_ = + Utils::GetValidUTF8(g_account->GetAccountName().c_str(), "bsgaps"); + } else { + spec.name_ = + Utils::GetValidUTF8(g_platform->GetDeviceName().c_str(), "bsgaps2"); + } + if (spec.name_.size() > 100) { + // FIXME should perhaps clamp this in unicode space + Log("account name size too long: '" + spec.name_ + "'"); + spec.name_.resize(100); + spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgaps3"); + } + return spec; +} + +auto PlayerSpec::GetDummyPlayerSpec(const std::string& name) -> PlayerSpec { + PlayerSpec spec; + 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_ + "'"); + spec.name_.resize(100); + spec.name_ = Utils::GetValidUTF8(spec.name_.c_str(), "bsgdps2"); + } + return spec; +} + +} // namespace ballistica diff --git a/src/ballistica/networking/sockaddr.cc b/src/ballistica/networking/sockaddr.cc new file mode 100644 index 00000000..8630d1bd --- /dev/null +++ b/src/ballistica/networking/sockaddr.cc @@ -0,0 +1,56 @@ +// Released under the MIT License. See LICENSE for details. + +#include "ballistica/networking/sockaddr.h" + +namespace ballistica { + +SockAddr::SockAddr(const std::string& addr, int port) { + memset(&addr_, 0, sizeof(addr_)); + + // try ipv4... + { + // inet_pton is not available on XP :-/ + // hmmm at this point we probably don't care; should test inet_pton. + // #if BA_OSTYPE_WINDOWS + // int addr_size = sizeof(addr_); + // std::wstring addr2; + // addr2.assign(addr.begin(), addr.end()); + // struct sockaddr_in* a4 = reinterpret_cast(&addr_); + // struct sockaddr_in6* a6 = reinterpret_cast(&addr_); + // int result = + // WSAStringToAddress(const_cast(addr2.c_str()), AF_INET, + // nullptr, (LPSOCKADDR)a4, &addr_size); + // if (result == 0) { + // if (a4->sin_family == AF_INET) { + // a4->sin_port = htons(port); + // return; + // } else if (a6->sin6_family == AF_INET6) { + // a6->sin6_port = htons(port); + // } + // } + // #else + struct in_addr addr_out {}; + int result = inet_pton(AF_INET, addr.c_str(), &addr_out); + if (result == 1) { + auto* a = reinterpret_cast(&addr_); + a->sin_family = AF_INET; + a->sin_port = htons(port); // NOLINT + a->sin_addr = addr_out; + return; + } else { + struct in6_addr addr6Out {}; + result = inet_pton(AF_INET6, addr.c_str(), &addr6Out); + if (result == 1) { + auto* a = reinterpret_cast(&addr_); + a->sin6_family = AF_INET6; + a->sin6_port = htons(port); // NOLINT + a->sin6_addr = addr6Out; + return; + } + } + // #endif + } + throw Exception("Invalid address: '" + addr + "'"); +} + +} // namespace ballistica