From f69bc6e1afed46c7082030a7a7b1ad0b5a17ede1 Mon Sep 17 00:00:00 2001 From: Death Killer <884052+deathkiller@users.noreply.github.com> Date: Tue, 24 Oct 2023 18:43:29 +0200 Subject: [PATCH] Added `IsPausable`, `InitialState` and game version to cache descriptor, improved console on Windows, refactoring --- Sources/Jazz2.vcxproj | 2 + Sources/Jazz2.vcxproj.filters | 6 + Sources/Jazz2/Actors/Player.cpp | 2 +- Sources/Jazz2/ILevelHandler.h | 1 + Sources/Jazz2/IRootController.h | 2 +- Sources/Jazz2/LevelHandler.cpp | 45 +++-- Sources/Jazz2/LevelHandler.h | 4 + Sources/Jazz2/Multiplayer/MultiLevelHandler.h | 4 + Sources/Jazz2/Multiplayer/NetworkManager.cpp | 6 +- Sources/Jazz2/Multiplayer/NetworkManager.h | 3 +- Sources/Jazz2/PreferencesCache.cpp | 8 + Sources/Jazz2/PreferencesCache.h | 5 +- Sources/Jazz2/UI/Menu/BeginSection.cpp | 2 +- Sources/Jazz2/UI/Menu/IMenuContainer.h | 2 +- Sources/Jazz2/UI/Menu/InGameMenu.cpp | 3 +- Sources/Jazz2/UI/Menu/InGameMenu.h | 2 +- Sources/Jazz2/UI/Menu/LoadingSection.cpp | 35 ++++ Sources/Jazz2/UI/Menu/LoadingSection.h | 20 ++ Sources/Jazz2/UI/Menu/MainMenu.cpp | 27 ++- Sources/Jazz2/UI/Menu/MainMenu.h | 4 +- Sources/Main.cpp | 170 ++++++++++++++--- Sources/Shared/Containers/ArrayView.h | 20 +- Sources/Shared/Containers/String.cpp | 180 +++++++++--------- Sources/Shared/Containers/String.h | 5 +- Sources/Shared/Containers/StringView.h | 27 +-- Sources/Shared/IO/Stream.h | 2 +- Sources/nCine/AppConfiguration.cpp | 16 +- Sources/nCine/AppConfiguration.h | 21 +- Sources/nCine/Application.cpp | 15 +- Sources/nCine/Base/Algorithms.cpp | 94 +++------ Sources/nCine/Base/Algorithms.h | 168 ++++++++++------ Sources/nCine/Graphics/BinaryShaderCache.cpp | 31 ++- Sources/nCine/Input/JoyMapping.cpp | 1 - Sources/nCine/MainApplication.cpp | 119 ++++++++---- Sources/nCine/MainApplication.h | 6 + cmake/ncine_headers.cmake | 1 + cmake/ncine_sources.cmake | 1 + 37 files changed, 691 insertions(+), 369 deletions(-) create mode 100644 Sources/Jazz2/UI/Menu/LoadingSection.cpp create mode 100644 Sources/Jazz2/UI/Menu/LoadingSection.h diff --git a/Sources/Jazz2.vcxproj b/Sources/Jazz2.vcxproj index d0977e71..ef0c88af 100644 --- a/Sources/Jazz2.vcxproj +++ b/Sources/Jazz2.vcxproj @@ -384,6 +384,7 @@ + @@ -730,6 +731,7 @@ + diff --git a/Sources/Jazz2.vcxproj.filters b/Sources/Jazz2.vcxproj.filters index c59d9ba4..0bbb2ba8 100644 --- a/Sources/Jazz2.vcxproj.filters +++ b/Sources/Jazz2.vcxproj.filters @@ -1353,6 +1353,9 @@ Header Files\Shared\Threading + + Header Files\Jazz2\UI\Menu + @@ -2252,6 +2255,9 @@ Source Files\Jazz2\Actors + + Source Files\Jazz2\UI\Menu + diff --git a/Sources/Jazz2/Actors/Player.cpp b/Sources/Jazz2/Actors/Player.cpp index 78104bb4..bc8311bf 100644 --- a/Sources/Jazz2/Actors/Player.cpp +++ b/Sources/Jazz2/Actors/Player.cpp @@ -161,7 +161,7 @@ namespace Jazz2::Actors void Player::OnUpdate(float timeMult) { #if defined(DEATH_DEBUG) - if (_levelHandler->PlayerActionPressed(_playerIndex, PlayerActions::ChangeWeapon)) { + if (PreferencesCache::AllowCheats && _levelHandler->PlayerActionPressed(_playerIndex, PlayerActions::ChangeWeapon)) { float moveDistance = (_levelHandler->PlayerActionPressed(_playerIndex, PlayerActions::Run) ? 400.0f : 100.0f); if (_levelHandler->PlayerActionHit(_playerIndex, PlayerActions::Left)) { MoveInstantly(Vector2f(-moveDistance, 0.0f), MoveType::Relative | MoveType::Force); diff --git a/Sources/Jazz2/ILevelHandler.h b/Sources/Jazz2/ILevelHandler.h index 2a28bcba..024b3c6b 100644 --- a/Sources/Jazz2/ILevelHandler.h +++ b/Sources/Jazz2/ILevelHandler.h @@ -39,6 +39,7 @@ namespace Jazz2 virtual Tiles::TileMap* TileMap() = 0; virtual GameDifficulty Difficulty() const = 0; + virtual bool IsPausable() const = 0; virtual bool IsReforged() const = 0; virtual Recti LevelBounds() const = 0; virtual float ElapsedFrames() const = 0; diff --git a/Sources/Jazz2/IRootController.h b/Sources/Jazz2/IRootController.h index 74792ceb..ee9cf710 100644 --- a/Sources/Jazz2/IRootController.h +++ b/Sources/Jazz2/IRootController.h @@ -33,7 +33,7 @@ namespace Jazz2 virtual void ChangeLevel(LevelInitialization&& levelInit) = 0; #if defined(WITH_MULTIPLAYER) - virtual bool ConnectToServer(const char* address, std::uint16_t port) = 0; + virtual bool ConnectToServer(const StringView& address, std::uint16_t port) = 0; virtual bool CreateServer(std::uint16_t port) = 0; #endif diff --git a/Sources/Jazz2/LevelHandler.cpp b/Sources/Jazz2/LevelHandler.cpp index 18b9c547..aaf88c77 100644 --- a/Sources/Jazz2/LevelHandler.cpp +++ b/Sources/Jazz2/LevelHandler.cpp @@ -216,17 +216,20 @@ namespace Jazz2 { float timeMult = theApplication().timeMult(); - UpdatePressedActions(); + if (_pauseMenu == nullptr) { + UpdatePressedActions(); - if (PlayerActionHit(0, PlayerActions::Menu) && _pauseMenu == nullptr && _nextLevelType == ExitType::None) { - PauseGame(); - } + if (PlayerActionHit(0, PlayerActions::Menu) && _nextLevelType == ExitType::None) { + PauseGame(); + } #if defined(DEATH_DEBUG) - if (PlayerActionPressed(0, PlayerActions::ChangeWeapon) && PlayerActionHit(0, PlayerActions::Jump)) { - _cheatsUsed = true; - BeginLevelChange(ExitType::Warp | ExitType::FastTransition, nullptr); - } + if (PreferencesCache::AllowCheats && PlayerActionPressed(0, PlayerActions::ChangeWeapon) && PlayerActionHit(0, PlayerActions::Jump)) { + _cheatsUsed = true; + BeginLevelChange(ExitType::Warp | ExitType::FastTransition, nullptr); + } #endif + } + #if defined(WITH_AUDIO) // Destroy stopped players and resume music after Sugar Rush if (_sugarRushMusic != nullptr && _sugarRushMusic->isStopped()) { @@ -245,7 +248,7 @@ namespace Jazz2 } #endif - if (_pauseMenu == nullptr) { + if (!IsPausable() || _pauseMenu == nullptr) { if (_nextLevelType != ExitType::None) { _nextLevelTime -= timeMult; @@ -291,7 +294,7 @@ namespace Jazz2 } } - if (_difficulty != GameDifficulty::Multiplayer) { + if (/*_difficulty != GameDifficulty::Multiplayer*/true) { if (!_players.empty()) { auto& pos = _players[0]->GetPos(); int32_t tx1 = (int32_t)pos.X / Tiles::TileSet::DefaultTileSize; @@ -1613,22 +1616,26 @@ namespace Jazz2 { // Show in-game pause menu _pauseMenu = std::make_shared(this); - // Prevent updating of all level objects - _rootNode->setUpdateEnabled(false); + if (IsPausable()) { + // Prevent updating of all level objects + _rootNode->setUpdateEnabled(false); + } #if defined(WITH_AUDIO) // Use low-pass filter on music and pause all SFX if (_music != nullptr) { _music->setLowPass(0.1f); } - for (auto& sound : _playingSounds) { - if (sound->isPlaying()) { - sound->pause(); + if (IsPausable()) { + for (auto& sound : _playingSounds) { + if (sound->isPlaying()) { + sound->pause(); + } + } + // If Sugar Rush music is playing, pause it and play normal music instead + if (_sugarRushMusic != nullptr && _music != nullptr) { + _music->play(); } - } - // If Sugar Rush music is playing, pause it and play normal music instead - if (_sugarRushMusic != nullptr && _music != nullptr) { - _music->play(); } #endif } diff --git a/Sources/Jazz2/LevelHandler.h b/Sources/Jazz2/LevelHandler.h index 2f74800e..1bfea8a8 100644 --- a/Sources/Jazz2/LevelHandler.h +++ b/Sources/Jazz2/LevelHandler.h @@ -80,6 +80,10 @@ namespace Jazz2 return _difficulty; } + bool IsPausable() const override { + return true; + } + bool IsReforged() const override { return _isReforged; } diff --git a/Sources/Jazz2/Multiplayer/MultiLevelHandler.h b/Sources/Jazz2/Multiplayer/MultiLevelHandler.h index 87723dc4..5a7c3073 100644 --- a/Sources/Jazz2/Multiplayer/MultiLevelHandler.h +++ b/Sources/Jazz2/Multiplayer/MultiLevelHandler.h @@ -35,6 +35,10 @@ namespace Jazz2::Multiplayer MultiLevelHandler(IRootController* root, NetworkManager* networkManager, const LevelInitialization& levelInit); ~MultiLevelHandler() override; + bool IsPausable() const override { + return false; + } + float GetAmbientLight() const override; void SetAmbientLight(float value) override; diff --git a/Sources/Jazz2/Multiplayer/NetworkManager.cpp b/Sources/Jazz2/Multiplayer/NetworkManager.cpp index 580d89f9..56977d9e 100644 --- a/Sources/Jazz2/Multiplayer/NetworkManager.cpp +++ b/Sources/Jazz2/Multiplayer/NetworkManager.cpp @@ -18,6 +18,8 @@ // Undefine it again after include #undef far +#include + #define MAX_CLIENTS 64 namespace Jazz2::Multiplayer @@ -39,7 +41,7 @@ namespace Jazz2::Multiplayer } } - bool NetworkManager::CreateClient(INetworkHandler* handler, const char* address, std::uint16_t port, std::uint32_t clientData) + bool NetworkManager::CreateClient(INetworkHandler* handler, const StringView& address, std::uint16_t port, std::uint32_t clientData) { if (!_initialized || _host != nullptr) { return false; @@ -53,7 +55,7 @@ namespace Jazz2::Multiplayer _state = NetworkState::Connecting; ENetAddress addr = { }; - enet_address_set_host(&addr, address); + enet_address_set_host(&addr, String::nullTerminatedView(address).data()); addr.port = port; ENetPeer* peer = enet_host_connect(_host, &addr, (std::size_t)NetworkChannel::Count, clientData); diff --git a/Sources/Jazz2/Multiplayer/NetworkManager.h b/Sources/Jazz2/Multiplayer/NetworkManager.h index 67356a19..d9931907 100644 --- a/Sources/Jazz2/Multiplayer/NetworkManager.h +++ b/Sources/Jazz2/Multiplayer/NetworkManager.h @@ -8,6 +8,7 @@ #include "../../nCine/Threading/ThreadSync.h" #include +#include struct _ENetHost; @@ -39,7 +40,7 @@ namespace Jazz2::Multiplayer NetworkManager(); ~NetworkManager(); - bool CreateClient(INetworkHandler* handler, const char* address, std::uint16_t port, std::uint32_t clientData); + bool CreateClient(INetworkHandler* handler, const StringView& address, std::uint16_t port, std::uint32_t clientData); bool CreateServer(INetworkHandler* handler, std::uint16_t port); void Dispose(); diff --git a/Sources/Jazz2/PreferencesCache.cpp b/Sources/Jazz2/PreferencesCache.cpp index 1196995e..d0c4252a 100644 --- a/Sources/Jazz2/PreferencesCache.cpp +++ b/Sources/Jazz2/PreferencesCache.cpp @@ -14,6 +14,9 @@ using namespace Death::IO; namespace Jazz2 { +#if defined(WITH_MULTIPLAYER) + String PreferencesCache::InitialState; +#endif UnlockableEpisodes PreferencesCache::UnlockedEpisodes = UnlockableEpisodes::None; RescaleMode PreferencesCache::ActiveRescaleMode = RescaleMode::None; bool PreferencesCache::EnableFullscreen = false; @@ -296,6 +299,11 @@ namespace Jazz2 } else if (arg == "/mute"_s) { MasterVolume = 0.0f; } +#if defined(WITH_MULTIPLAYER) + else if (InitialState.empty() && (arg == "/server"_s || arg.hasPrefix("/connect:"_s))) { + InitialState = arg; + } +#endif } } diff --git a/Sources/Jazz2/PreferencesCache.h b/Sources/Jazz2/PreferencesCache.h index 18d34a79..770bfeec 100644 --- a/Sources/Jazz2/PreferencesCache.h +++ b/Sources/Jazz2/PreferencesCache.h @@ -79,6 +79,9 @@ namespace Jazz2 static constexpr std::int32_t UnlimitedFps = 0; static constexpr std::int32_t UseVsync = -1; +#if defined(WITH_MULTIPLAYER) + static String InitialState; +#endif static UnlockableEpisodes UnlockedEpisodes; // Graphics @@ -148,9 +151,7 @@ namespace Jazz2 static constexpr float TouchPaddingMultiplier = 0.003f; - /// Deleted copy constructor PreferencesCache(const PreferencesCache&) = delete; - /// Deleted assignment operator PreferencesCache& operator=(const PreferencesCache&) = delete; static String _configPath; diff --git a/Sources/Jazz2/UI/Menu/BeginSection.cpp b/Sources/Jazz2/UI/Menu/BeginSection.cpp index c45bfe86..d967692d 100644 --- a/Sources/Jazz2/UI/Menu/BeginSection.cpp +++ b/Sources/Jazz2/UI/Menu/BeginSection.cpp @@ -333,7 +333,7 @@ namespace Jazz2::UI::Menu // TODO: Multiplayer case (int32_t)Item::TODO_ConnectTo: // TODO: Hardcoded address and port - _root->ConnectToServer("127.0.0.1", 10666); + _root->ConnectToServer("127.0.0.1"_s, 7438); break; case (int32_t)Item::TODO_CreateServer: // TODO: Hardcoded address and port diff --git a/Sources/Jazz2/UI/Menu/IMenuContainer.h b/Sources/Jazz2/UI/Menu/IMenuContainer.h index 4b1a361c..032c36cb 100644 --- a/Sources/Jazz2/UI/Menu/IMenuContainer.h +++ b/Sources/Jazz2/UI/Menu/IMenuContainer.h @@ -42,7 +42,7 @@ namespace Jazz2::UI::Menu virtual void LeaveSection() = 0; virtual void ChangeLevel(Jazz2::LevelInitialization&& levelInit) = 0; #if defined(WITH_MULTIPLAYER) - virtual bool ConnectToServer(const char* address, std::uint16_t port) = 0; + virtual bool ConnectToServer(const StringView& address, std::uint16_t port) = 0; virtual bool CreateServer(std::uint16_t port) = 0; #endif virtual void ApplyPreferencesChanges(ChangedPreferencesType type) = 0; diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.cpp b/Sources/Jazz2/UI/Menu/InGameMenu.cpp index ad6724a3..20751e6f 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.cpp +++ b/Sources/Jazz2/UI/Menu/InGameMenu.cpp @@ -235,7 +235,7 @@ namespace Jazz2::UI::Menu } #if defined(WITH_MULTIPLAYER) - bool InGameMenu::ConnectToServer(const char* address, std::uint16_t port) + bool InGameMenu::ConnectToServer(const StringView& address, std::uint16_t port) { return _root->_root->ConnectToServer(address, port); } @@ -264,6 +264,7 @@ namespace Jazz2::UI::Menu } if ((type & ChangedPreferencesType::Language) == ChangedPreferencesType::Language) { + // All sections have to be recreated to load new language _sections.clear(); SwitchToSection(); } diff --git a/Sources/Jazz2/UI/Menu/InGameMenu.h b/Sources/Jazz2/UI/Menu/InGameMenu.h index eac7b866..c16b783f 100644 --- a/Sources/Jazz2/UI/Menu/InGameMenu.h +++ b/Sources/Jazz2/UI/Menu/InGameMenu.h @@ -31,7 +31,7 @@ namespace Jazz2::UI::Menu void LeaveSection() override; void ChangeLevel(Jazz2::LevelInitialization&& levelInit) override; #if defined(WITH_MULTIPLAYER) - bool ConnectToServer(const char* address, std::uint16_t port) override; + bool ConnectToServer(const StringView& address, std::uint16_t port) override; bool CreateServer(std::uint16_t port) override; #endif void ApplyPreferencesChanges(ChangedPreferencesType type) override; diff --git a/Sources/Jazz2/UI/Menu/LoadingSection.cpp b/Sources/Jazz2/UI/Menu/LoadingSection.cpp new file mode 100644 index 00000000..2c1157f6 --- /dev/null +++ b/Sources/Jazz2/UI/Menu/LoadingSection.cpp @@ -0,0 +1,35 @@ +#include "LoadingSection.h" + +namespace Jazz2::UI::Menu +{ + LoadingSection::LoadingSection(const StringView& message) + : _message(message) + { + } + + LoadingSection::LoadingSection(String&& message) + : _message(std::move(message)) + { + + } + + void LoadingSection::OnUpdate(float timeMult) + { + } + + void LoadingSection::OnDraw(Canvas* canvas) + { + Vector2i viewSize = canvas->ViewSize; + Vector2f center = Vector2f(viewSize.X * 0.5f, viewSize.Y * 0.5f); + + constexpr float topLine = 131.0f; + std::int32_t charOffset = 0; + + _root->DrawStringShadow(_message, charOffset, center.X, topLine + 40.0f, IMenuContainer::FontLayer, + Alignment::Top, Font::DefaultColor, 1.2f, 0.4f, 0.6f, 0.6f, 0.6f, 0.9f, 1.2f); + } + + void LoadingSection::OnTouchEvent(const nCine::TouchEvent& event, const Vector2i& viewSize) + { + } +} \ No newline at end of file diff --git a/Sources/Jazz2/UI/Menu/LoadingSection.h b/Sources/Jazz2/UI/Menu/LoadingSection.h new file mode 100644 index 00000000..c67f0d4e --- /dev/null +++ b/Sources/Jazz2/UI/Menu/LoadingSection.h @@ -0,0 +1,20 @@ +#pragma once + +#include "MenuSection.h" + +namespace Jazz2::UI::Menu +{ + class LoadingSection : public MenuSection + { + public: + LoadingSection(const StringView& message); + LoadingSection(String&& message); + + void OnUpdate(float timeMult) override; + void OnDraw(Canvas* canvas) override; + void OnTouchEvent(const nCine::TouchEvent& event, const Vector2i& viewSize) override; + + private: + String _message; + }; +} \ No newline at end of file diff --git a/Sources/Jazz2/UI/Menu/MainMenu.cpp b/Sources/Jazz2/UI/Menu/MainMenu.cpp index ce4dcced..28b9b9c2 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.cpp +++ b/Sources/Jazz2/UI/Menu/MainMenu.cpp @@ -66,6 +66,30 @@ namespace Jazz2::UI::Menu _canvasOverlay->setParent(nullptr); } + void MainMenu::Reset() + { + bool shouldSwitch = false; + while (!_sections.empty()) { + if (_sections.size() == 1 && dynamic_cast(_sections.back().get())) { + if (shouldSwitch) { + auto& lastSection = _sections.back(); + lastSection->OnShow(this); + + if (_canvasBackground->ViewSize != Vector2i::Zero) { + Recti clipRectangle = lastSection->GetClipRectangle(_canvasBackground->ViewSize); + _upscalePass.SetClipRectangle(clipRectangle); + } + } + return; + } + + _sections.pop_back(); + shouldSwitch = true; + } + + SwitchToSection(); + } + void MainMenu::OnBeginFrame() { float timeMult = theApplication().timeMult(); @@ -327,7 +351,7 @@ namespace Jazz2::UI::Menu } #if defined(WITH_MULTIPLAYER) - bool MainMenu::ConnectToServer(const char* address, std::uint16_t port) + bool MainMenu::ConnectToServer(const StringView& address, std::uint16_t port) { return _root->ConnectToServer(address, port); } @@ -353,6 +377,7 @@ namespace Jazz2::UI::Menu } if ((type & ChangedPreferencesType::Language) == ChangedPreferencesType::Language) { + // All sections have to be recreated to load new language _sections.clear(); SwitchToSection(); } diff --git a/Sources/Jazz2/UI/Menu/MainMenu.h b/Sources/Jazz2/UI/Menu/MainMenu.h index aa694c95..f5b0edb9 100644 --- a/Sources/Jazz2/UI/Menu/MainMenu.h +++ b/Sources/Jazz2/UI/Menu/MainMenu.h @@ -35,6 +35,8 @@ namespace Jazz2::UI::Menu MainMenu(IRootController* root, bool afterIntro); ~MainMenu() override; + void Reset(); + void OnBeginFrame() override; void OnInitializeViewport(int32_t width, int32_t height) override; @@ -46,7 +48,7 @@ namespace Jazz2::UI::Menu void LeaveSection() override; void ChangeLevel(Jazz2::LevelInitialization&& levelInit) override; #if defined(WITH_MULTIPLAYER) - bool ConnectToServer(const char* address, std::uint16_t port) override; + bool ConnectToServer(const StringView& address, std::uint16_t port) override; bool CreateServer(std::uint16_t port) override; #endif void ApplyPreferencesChanges(ChangedPreferencesType type) override; diff --git a/Sources/Main.cpp b/Sources/Main.cpp index e7ed17d4..4787ed79 100644 --- a/Sources/Main.cpp +++ b/Sources/Main.cpp @@ -25,6 +25,7 @@ #include "Jazz2/UI/Cinematics.h" #include "Jazz2/UI/ControlScheme.h" #include "Jazz2/UI/Menu/MainMenu.h" +#include "Jazz2/UI/Menu/LoadingSection.h" #include "Jazz2/UI/Menu/SimpleMessageSection.h" #include "Jazz2/Compatibility/JJ2Anims.h" @@ -76,6 +77,7 @@ class GameEventHandler : public IAppEventHandler, public IInputEventHandler, pub static constexpr std::int32_t DefaultHeight = 405; #if defined(WITH_MULTIPLAYER) + static constexpr std::uint16_t MultiplayerDefaultPort = 7438; static constexpr std::uint32_t MultiplayerProtocolVersion = 1; #endif @@ -98,7 +100,7 @@ class GameEventHandler : public IAppEventHandler, public IInputEventHandler, pub void ChangeLevel(LevelInitialization&& levelInit) override; #if defined(WITH_MULTIPLAYER) - bool ConnectToServer(const char* address, std::uint16_t port) override; + bool ConnectToServer(const StringView& address, std::uint16_t port) override; bool CreateServer(std::uint16_t port) override; bool OnPeerConnected(const Peer& peer, std::uint32_t clientData) override; @@ -134,9 +136,11 @@ class GameEventHandler : public IAppEventHandler, public IInputEventHandler, pub void RefreshCache(); void CheckUpdates(); #endif + static void WriteCacheDescriptor(const StringView& path, std::uint64_t currentVersion, std::int64_t animsModified); static void SaveEpisodeEnd(const LevelInitialization& pendingLevelChange); static void SaveEpisodeContinue(const LevelInitialization& pendingLevelChange); static void UpdateRichPresence(const LevelInitialization& levelInit); + static bool TryParseAddressAndPort(const StringView& input, String& address, std::uint16_t& port); }; void GameEventHandler::OnPreInit(AppConfiguration& config) @@ -218,11 +222,40 @@ void GameEventHandler::OnInit() thread.SetName("Parallel initialization"); # endif +# if defined(WITH_MULTIPLAYER) + if (PreferencesCache::InitialState == "/server"_s) { + thread.Join(); + + auto mainMenu = std::make_unique(this, false); + mainMenu->SwitchToSection(_("Creating server...")); + SetStateHandler(std::move(mainMenu)); + + // TODO: Hardcoded port + CreateServer(MultiplayerDefaultPort); + } else if (PreferencesCache::InitialState.hasPrefix("/connect:"_s)) { + thread.Join(); + + String address; std::uint16_t port; + if (TryParseAddressAndPort(PreferencesCache::InitialState.exceptPrefix(9), address, port)) { + if (port == 0) { + port = MultiplayerDefaultPort; + } + + auto mainMenu = std::make_unique(this, false); + mainMenu->SwitchToSection(_f("Connecting to %s:%u...", address.data(), port)); + SetStateHandler(std::move(mainMenu)); + + ConnectToServer(address.data(), (std::uint16_t)port); + return; + } + } +# endif + SetStateHandler(std::make_unique(this, "intro"_s, [thread](IRootController* root, bool endOfStream) mutable { if ((root->GetFlags() & Jazz2::IRootController::Flags::IsVerified) != Jazz2::IRootController::Flags::IsVerified) { return false; } - + thread.Join(); root->GoToMainMenu(endOfStream); return true; @@ -251,6 +284,33 @@ void GameEventHandler::OnInit() CheckUpdates(); # endif +# if defined(WITH_MULTIPLAYER) + if (PreferencesCache::InitialState == "/server"_s) { + LOGI("Starting server on port %u...", MultiplayerDefaultPort); + + auto mainMenu = std::make_unique(this, false); + mainMenu->SwitchToSection(_("Creating server...")); + SetStateHandler(std::move(mainMenu)); + + // TODO: Hardcoded port + CreateServer(MultiplayerDefaultPort); + } else if (PreferencesCache::InitialState.hasPrefix("/connect:"_s)) { + String address; std::uint16_t port; + if (TryParseAddressAndPort(PreferencesCache::InitialState.exceptPrefix(9), address, port)) { + if (port == 0) { + port = MultiplayerDefaultPort; + } + + auto mainMenu = std::make_unique(this, false); + mainMenu->SwitchToSection(_f("Connecting to %s:%u...", address.data(), port)); + SetStateHandler(std::move(mainMenu)); + + ConnectToServer(address.data(), (std::uint16_t)port); + return; + } + } +# endif + SetStateHandler(std::make_unique(this, "intro"_s, [](IRootController* root, bool endOfStream) { root->GoToMainMenu(endOfStream); return true; @@ -373,9 +433,12 @@ void GameEventHandler::GoToMainMenu(bool afterIntro) #if defined(WITH_MULTIPLAYER) _networkManager = nullptr; #endif - - SetStateHandler(std::make_unique(this, afterIntro)); - UpdateRichPresence({}); + if (auto mainMenu = dynamic_cast(_currentHandler.get())) { + mainMenu->Reset(); + } else { + SetStateHandler(std::make_unique(this, afterIntro)); + UpdateRichPresence({}); + } }); } @@ -457,8 +520,10 @@ void GameEventHandler::ChangeLevel(LevelInitialization&& levelInit) } #if defined(WITH_MULTIPLAYER) -bool GameEventHandler::ConnectToServer(const char* address, std::uint16_t port) +bool GameEventHandler::ConnectToServer(const StringView& address, std::uint16_t port) { + LOGI("Connecting to %s:%u...", address, port); + if (_networkManager == nullptr) { _networkManager = std::make_unique(); } @@ -468,6 +533,8 @@ bool GameEventHandler::ConnectToServer(const char* address, std::uint16_t port) bool GameEventHandler::CreateServer(std::uint16_t port) { + LOGI("Creating server on port %u...", port); + if (_networkManager == nullptr) { _networkManager = std::make_unique(); } @@ -600,23 +667,26 @@ void GameEventHandler::RefreshCache() return; } + constexpr std::uint64_t currentVersion = parseVersion({ NCINE_VERSION, countof(NCINE_VERSION) - 1 }); + auto& resolver = ContentResolver::Get(); + auto cachePath = fs::CombinePath({ resolver.GetCachePath(), "Animations"_s, "cache.index"_s }); // Check cache state { - auto s = fs::Open(fs::CombinePath({ resolver.GetCachePath(), "Animations"_s, "cache.index"_s }), FileAccessMode::Read); + auto s = fs::Open(cachePath, FileAccessMode::Read); if (s->GetSize() < 16) { goto RecreateCache; } - uint64_t signature = s->ReadValue(); - uint8_t fileType = s->ReadValue(); - uint16_t version = s->ReadValue(); + std::uint64_t signature = s->ReadValue(); + std::uint8_t fileType = s->ReadValue(); + std::uint16_t version = s->ReadValue(); if (signature != 0x2095A59FF0BFBBEF || fileType != ContentResolver::CacheIndexFile || version != Compatibility::JJ2Anims::CacheVersion) { goto RecreateCache; } - uint8_t flags = s->ReadValue(); + std::uint8_t flags = s->ReadValue(); if ((flags & 0x01) == 0x01) { // Don't overwrite cache LOGI("Cache is protected"); @@ -628,20 +698,39 @@ void GameEventHandler::RefreshCache() if (!fs::IsReadableFile(animsPath)) { animsPath = fs::FindPathCaseInsensitive(fs::CombinePath(resolver.GetSourcePath(), "AnimsSw.j2a"_s)); } - int64_t animsCached = s->ReadValue(); - int64_t animsModified = fs::GetLastModificationTime(animsPath).GetValue(); + std::int64_t animsCached = s->ReadValue(); + std::int64_t animsModified = fs::GetLastModificationTime(animsPath).GetValue(); if (animsModified != 0 && animsCached != animsModified) { goto RecreateCache; } // If some events were added, recreate cache - uint16_t eventTypeCount = s->ReadValue(); - if (eventTypeCount != (uint16_t)EventType::Count) { + std::uint16_t eventTypeCount = s->ReadValue(); + if (eventTypeCount != (std::uint16_t)EventType::Count) { goto RecreateCache; } // Cache is up-to-date - LOGI("Cache is already up-to-date"); + std::uint64_t lastVersion = s->ReadValue(); + + // Close the file, so it can be writable for possible update + s = nullptr; + + if (currentVersion != lastVersion) { + if ((lastVersion & 0xFFFFFFFFULL) == 0x0FFFFFFFULL) { + LOGI("Cache is already up-to-date, but created in experimental build v%i.%i.0", (lastVersion >> 48) & 0xFFFFULL, (lastVersion >> 32) & 0xFFFFULL); + } else { + LOGI("Cache is already up-to-date, but created in different build v%i.%i.%i", (lastVersion >> 48) & 0xFFFFULL, (lastVersion >> 32) & 0xFFFFULL, lastVersion & 0xFFFFFFFFULL); + } + + WriteCacheDescriptor(cachePath, currentVersion, animsModified); + + LOGI("Pruning binary shader cache..."); + RenderResources::binaryShaderCache().prune(); + } else { + LOGI("Cache is already up-to-date"); + } + _flags |= Flags::IsVerified | Flags::IsPlayable; return; } @@ -668,18 +757,13 @@ void GameEventHandler::RefreshCache() RefreshCacheLevels(); - // Create cache index - auto so = fs::Open(fs::CombinePath({ resolver.GetCachePath(), "Animations"_s, "cache.index"_s }), FileAccessMode::Write); + LOGI("Cache was recreated"); + std::int64_t animsModified = fs::GetLastModificationTime(animsPath).GetValue(); + WriteCacheDescriptor(cachePath, currentVersion, animsModified); - so->WriteValue(0x2095A59FF0BFBBEF); // Signature - so->WriteValue(ContentResolver::CacheIndexFile); - so->WriteValue(Compatibility::JJ2Anims::CacheVersion); - so->WriteValue(0x00); // Flags - int64_t animsModified = fs::GetLastModificationTime(animsPath).GetValue(); - so->WriteValue(animsModified); - so->WriteValue((uint16_t)EventType::Count); + LOGI("Pruning binary shader cache..."); + RenderResources::binaryShaderCache().prune(); - LOGI("Cache was recreated"); _flags |= Flags::IsVerified | Flags::IsPlayable; } @@ -921,9 +1005,18 @@ void GameEventHandler::RefreshCacheLevels() } } } - - LOGI("Pruning binary shader cache..."); - RenderResources::binaryShaderCache().prune(); +} + +void GameEventHandler::WriteCacheDescriptor(const StringView& path, std::uint64_t currentVersion, std::int64_t animsModified) +{ + auto so = fs::Open(path, FileAccessMode::Write); + so->WriteValue(0x2095A59FF0BFBBEF); // Signature + so->WriteValue(ContentResolver::CacheIndexFile); + so->WriteValue(Compatibility::JJ2Anims::CacheVersion); + so->WriteValue(0x00); // Flags + so->WriteValue(animsModified); + so->WriteValue((std::uint16_t)EventType::Count); + so->WriteValue(currentVersion); } void GameEventHandler::CheckUpdates() @@ -1102,7 +1195,7 @@ void GameEventHandler::CheckUpdates() Http::Request req(url, Http::InternetProtocol::V4); Http::Response resp = req.Send("GET"_s, std::chrono::seconds(10)); if (resp.Status.Code == Http::HttpStatus::Ok && !resp.Body.empty() && resp.Body.size() < sizeof(_newestVersion) - 1) { - std::uint64_t currentVersion = parseVersion(NCINE_VERSION); + constexpr std::uint64_t currentVersion = parseVersion({ NCINE_VERSION, countof(NCINE_VERSION) - 1 }); std::uint64_t latestVersion = parseVersion(StringView(reinterpret_cast(resp.Body.data()), resp.Body.size())); if (currentVersion < latestVersion) { std::memcpy(_newestVersion, resp.Body.data(), resp.Body.size()); @@ -1272,6 +1365,23 @@ void GameEventHandler::UpdateRichPresence(const LevelInitialization& levelInit) #endif } +bool GameEventHandler::TryParseAddressAndPort(const StringView& input, String& address, std::uint16_t& port) +{ + auto portSep = input.findLast(':'); + if (portSep == nullptr) { + return false; + } + + address = String(input.prefix(portSep.begin())); + if (address.empty()) { + return false; + } + + auto portString = input.suffix(portSep.begin() + 1); + port = (std::uint16_t)stou32(portString.data(), portString.size()); + return true; +} + #if defined(DEATH_TARGET_ANDROID) std::unique_ptr CreateAppEventHandler() { diff --git a/Sources/Shared/Containers/ArrayView.h b/Sources/Shared/Containers/ArrayView.h index 3c879364..7579ea67 100644 --- a/Sources/Shared/Containers/ArrayView.h +++ b/Sources/Shared/Containers/ArrayView.h @@ -54,7 +54,10 @@ namespace Death::Containers public: typedef T Type; - constexpr /*implicit*/ ArrayView(std::nullptr_t = nullptr) noexcept : _data{}, _size {} {} + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} + + constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView(T* data, std::size_t size) noexcept : _data(data), _size(size) {} @@ -172,7 +175,10 @@ namespace Death::Containers public: typedef void Type; - constexpr /*implicit*/ ArrayView(std::nullptr_t = nullptr) noexcept : _data{}, _size{} {} + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} + + constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView(void* data, std::size_t size) noexcept : _data(data), _size(size) {} @@ -233,7 +239,10 @@ namespace Death::Containers public: typedef const void Type; - constexpr /*implicit*/ ArrayView(std::nullptr_t = nullptr) noexcept : _data{}, _size{} {} + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + template::value>::type> constexpr /*implicit*/ ArrayView(U) noexcept : _data{}, _size{} {} + + constexpr /*implicit*/ ArrayView() noexcept : _data{}, _size{} {} constexpr /*implicit*/ ArrayView(const void* data, std::size_t size) noexcept : _data(data), _size(size) {} @@ -356,7 +365,10 @@ namespace Death::Containers Size = size_ }; - constexpr /*implicit*/ StaticArrayView(std::nullptr_t = nullptr) noexcept : _data{} {} + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a ArrayView or std::size_t */ + template::value>::type> constexpr /*implicit*/ StaticArrayView(U) noexcept : _data{} {} + + constexpr /*implicit*/ StaticArrayView() noexcept : _data{} {} template::value && !std::is_same::value>::type> constexpr explicit StaticArrayView(U data) noexcept : _data(data) {} diff --git a/Sources/Shared/Containers/String.cpp b/Sources/Shared/Containers/String.cpp index 827df3ea..d2bb044d 100644 --- a/Sources/Shared/Containers/String.cpp +++ b/Sources/Shared/Containers/String.cpp @@ -27,7 +27,7 @@ namespace Death::Containers out._large.size |= std::size_t(view.flags() & StringViewFlags::Global); return out; } - return String { view }; + return String{view}; } String String::nullTerminatedView(AllocatedInitT, StringView view) { @@ -36,7 +36,7 @@ namespace Death::Containers out._large.size |= std::size_t(view.flags() & StringViewFlags::Global); return out; } - return String { AllocatedInit, view }; + return String{AllocatedInit, view}; } String String::nullTerminatedGlobalView(StringView view) { @@ -45,7 +45,7 @@ namespace Death::Containers out._large.size |= std::size_t(StringViewFlags::Global); return out; } - return String { view }; + return String{view}; } String String::nullTerminatedGlobalView(AllocatedInitT, StringView view) { @@ -54,7 +54,7 @@ namespace Death::Containers out._large.size |= std::size_t(StringViewFlags::Global); return out; } - return String { AllocatedInit, view }; + return String{AllocatedInit, view}; } inline void String::construct(NoInitT, const std::size_t size) { @@ -112,7 +112,7 @@ namespace Death::Containers String::String(const ArrayView view) : String{view.data(), view.size()} {} - String::String(const char* const data) : String{data, data ? std::strlen(data) : 0} {} + String::String(std::nullptr_t, std::nullptr_t, std::nullptr_t, const char* const data) : String{data, data ? std::strlen(data) : 0} {} String::String(const char* const data, const std::size_t size) : _large{} @@ -120,7 +120,7 @@ namespace Death::Containers #if defined(DEATH_TARGET_32BIT) // Compared to StringView construction which happens a lot this shouldn't, and the chance of strings > 1 GB on 32-bit // is rare but possible and thus worth checking even in release - DEATH_ASSERT(size < std::size_t { 1 } << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); + DEATH_ASSERT(size < std::size_t{1} << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); #endif DEATH_ASSERT(data || size == 0, , "Containers::String: Received a null string of size %zu", size); @@ -193,7 +193,7 @@ namespace Death::Containers // Compared to StringView construction which happens a lot this shouldn't, the chance of strings > 1 GB on 32-bit // is rare but possible and thus worth checking even in release; but most importantly checking for null // termination outweighs potential speed issues - DEATH_ASSERT(size < std::size_t { 1 } << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); + DEATH_ASSERT(size < std::size_t{1} << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); DEATH_ASSERT(data && !data[size], , "Containers::String: Can only take ownership of a non-null null-terminated array"); _large.data = data; @@ -213,7 +213,7 @@ namespace Death::Containers { // Compared to StringView construction which happens a lot this shouldn't, and the chance of strings > 1 GB on 32-bit // is rare but possible and thus worth checking even in release - DEATH_ASSERT(size < std::size_t { 1 } << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); + DEATH_ASSERT(size < std::size_t{1} << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); if (size < Implementation::SmallStringSize) { // Everything already zero-init'd in the constructor init list @@ -229,7 +229,7 @@ namespace Death::Containers { // Compared to StringView construction which happens a lot this shouldn't, and the chance of strings > 1 GB on 32-bit // is rare but possible and thus worth checking even in release - DEATH_ASSERT(size < std::size_t { 1 } << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); + DEATH_ASSERT(size < std::size_t{1} << (sizeof(std::size_t) * 8 - 2), , "Containers::String: String expected to be smaller than 2^%zu bytes, got %zu", sizeof(std::size_t) * 8 - 2, size); construct(NoInit, size); } @@ -299,11 +299,11 @@ namespace Death::Containers if (_small.size & Implementation::SmallStringBit) { const std::size_t size = _small.size & ~SmallSizeMask; // Allocate the output including a null terminator at the end, but don't include it in the size - out = Array { Array{NoInit, size + 1}.release(), size }; + out = Array{Array{NoInit, size + 1}.release(), size}; out[size] = '\0'; std::memcpy(out.data(), _small.data, size); } else { - out = Array { _large.data, _large.size & ~LargeSizeMask, deleter() }; + out = Array{_large.data, _large.size & ~LargeSizeMask, deleter()}; } // Same as in release(). Create a zero-size small string to fullfil the guarantee of data() being always non-null @@ -461,119 +461,119 @@ namespace Death::Containers } MutableStringView String::slice(char* const begin, char* const end) { - return MutableStringView { *this }.slice(begin, end); + return MutableStringView{*this}.slice(begin, end); } StringView String::slice(const char* const begin, const char* const end) const { - return StringView { *this }.slice(begin, end); + return StringView{*this}.slice(begin, end); } MutableStringView String::slice(const std::size_t begin, const std::size_t end) { - return MutableStringView { *this }.slice(begin, end); + return MutableStringView{*this}.slice(begin, end); } StringView String::slice(const std::size_t begin, const std::size_t end) const { - return StringView { *this }.slice(begin, end); + return StringView{*this}.slice(begin, end); } MutableStringView String::prefix(char* const end) { - return MutableStringView { *this }.prefix(end); + return MutableStringView{*this}.prefix(end); } StringView String::prefix(const char* const end) const { - return StringView { *this }.prefix(end); + return StringView{*this}.prefix(end); } MutableStringView String::suffix(char* const begin) { - return MutableStringView { *this }.suffix(begin); + return MutableStringView{*this}.suffix(begin); } StringView String::suffix(const char* const begin) const { - return StringView { *this }.suffix(begin); + return StringView{*this}.suffix(begin); } MutableStringView String::prefix(const std::size_t count) { - return MutableStringView { *this }.prefix(count); + return MutableStringView{*this}.prefix(count); } StringView String::prefix(const std::size_t count) const { - return StringView { *this }.prefix(count); + return StringView{*this}.prefix(count); } MutableStringView String::exceptPrefix(const std::size_t count) { - return MutableStringView { *this }.exceptPrefix(count); + return MutableStringView{*this}.exceptPrefix(count); } StringView String::exceptPrefix(const std::size_t count) const { - return StringView { *this }.exceptPrefix(count); + return StringView{*this}.exceptPrefix(count); } MutableStringView String::exceptSuffix(const std::size_t count) { - return MutableStringView { *this }.exceptSuffix(count); + return MutableStringView{*this}.exceptSuffix(count); } StringView String::exceptSuffix(const std::size_t count) const { - return StringView { *this }.exceptSuffix(count); + return StringView{*this}.exceptSuffix(count); } Array String::split(const char delimiter) { - return MutableStringView { *this }.split(delimiter); + return MutableStringView{*this}.split(delimiter); } Array String::split(const char delimiter) const { - return StringView { *this }.split(delimiter); + return StringView{*this}.split(delimiter); } Array String::split(const StringView delimiter) { - return MutableStringView { *this }.split(delimiter); + return MutableStringView{*this}.split(delimiter); } Array String::split(const StringView delimiter) const { - return StringView { *this }.split(delimiter); + return StringView{*this}.split(delimiter); } Array String::splitWithoutEmptyParts(const char delimiter) { - return MutableStringView { *this }.splitWithoutEmptyParts(delimiter); + return MutableStringView{*this}.splitWithoutEmptyParts(delimiter); } Array String::splitWithoutEmptyParts(const char delimiter) const { - return StringView { *this }.splitWithoutEmptyParts(delimiter); + return StringView{*this}.splitWithoutEmptyParts(delimiter); } Array String::splitOnAnyWithoutEmptyParts(const StringView delimiters) { - return MutableStringView { *this }.splitOnAnyWithoutEmptyParts(delimiters); + return MutableStringView{*this}.splitOnAnyWithoutEmptyParts(delimiters); } Array String::splitOnAnyWithoutEmptyParts(const StringView delimiters) const { - return StringView { *this }.splitOnAnyWithoutEmptyParts(delimiters); + return StringView{*this}.splitOnAnyWithoutEmptyParts(delimiters); } Array String::splitOnWhitespaceWithoutEmptyParts() { - return MutableStringView { *this }.splitOnWhitespaceWithoutEmptyParts(); + return MutableStringView{*this}.splitOnWhitespaceWithoutEmptyParts(); } Array String::splitOnWhitespaceWithoutEmptyParts() const { - return StringView { *this }.splitOnWhitespaceWithoutEmptyParts(); + return StringView{*this}.splitOnWhitespaceWithoutEmptyParts(); } StaticArray<3, MutableStringView> String::partition(const char separator) { - return MutableStringView { *this }.partition(separator); + return MutableStringView{*this}.partition(separator); } StaticArray<3, StringView> String::partition(const char separator) const { - return StringView { *this }.partition(separator); + return StringView{*this}.partition(separator); } StaticArray<3, MutableStringView> String::partition(const StringView separator) { - return MutableStringView { *this }.partition(separator); + return MutableStringView{*this}.partition(separator); } StaticArray<3, StringView> String::partition(const StringView separator) const { - return StringView { *this }.partition(separator); + return StringView{*this}.partition(separator); } String String::join(const ArrayView strings) const { - return StringView { *this }.join(strings); + return StringView{*this}.join(strings); } String String::join(const std::initializer_list strings) const { @@ -582,7 +582,7 @@ namespace Death::Containers } String String::joinWithoutEmptyParts(const ArrayView strings) const { - return StringView { *this }.joinWithoutEmptyParts(strings); + return StringView{*this}.joinWithoutEmptyParts(strings); } String String::joinWithoutEmptyParts(const std::initializer_list strings) const { @@ -591,199 +591,199 @@ namespace Death::Containers } bool String::hasPrefix(const StringView prefix) const { - return StringView { *this }.hasPrefix(prefix); + return StringView{*this}.hasPrefix(prefix); } bool String::hasPrefix(const char prefix) const { - return StringView { *this }.hasPrefix(prefix); + return StringView{*this}.hasPrefix(prefix); } bool String::hasSuffix(const StringView suffix) const { - return StringView { *this }.hasSuffix(suffix); + return StringView{*this}.hasSuffix(suffix); } bool String::hasSuffix(const char suffix) const { - return StringView { *this }.hasSuffix(suffix); + return StringView{*this}.hasSuffix(suffix); } MutableStringView String::exceptPrefix(const StringView prefix) { - return MutableStringView { *this }.exceptPrefix(prefix); + return MutableStringView{*this}.exceptPrefix(prefix); } StringView String::exceptPrefix(const StringView prefix) const { - return StringView { *this }.exceptPrefix(prefix); + return StringView{*this}.exceptPrefix(prefix); } MutableStringView String::exceptSuffix(const StringView suffix) { - return MutableStringView { *this }.exceptSuffix(suffix); + return MutableStringView{*this}.exceptSuffix(suffix); } StringView String::exceptSuffix(const StringView suffix) const { - return StringView { *this }.exceptSuffix(suffix); + return StringView{*this}.exceptSuffix(suffix); } MutableStringView String::trimmed(const StringView characters) { - return MutableStringView { *this }.trimmed(characters); + return MutableStringView{*this}.trimmed(characters); } StringView String::trimmed(const StringView characters) const { - return StringView { *this }.trimmed(characters); + return StringView{*this}.trimmed(characters); } MutableStringView String::trimmed() { - return MutableStringView { *this }.trimmed(); + return MutableStringView{*this}.trimmed(); } StringView String::trimmed() const { - return StringView { *this }.trimmed(); + return StringView{*this}.trimmed(); } MutableStringView String::trimmedPrefix(const StringView characters) { - return MutableStringView { *this }.trimmedPrefix(characters); + return MutableStringView{*this}.trimmedPrefix(characters); } StringView String::trimmedPrefix(const StringView characters) const { - return StringView { *this }.trimmedPrefix(characters); + return StringView{*this}.trimmedPrefix(characters); } MutableStringView String::trimmedPrefix() { - return MutableStringView { *this }.trimmedPrefix(); + return MutableStringView{*this}.trimmedPrefix(); } StringView String::trimmedPrefix() const { - return StringView { *this }.trimmedPrefix(); + return StringView{*this}.trimmedPrefix(); } MutableStringView String::trimmedSuffix(const StringView characters) { - return MutableStringView { *this }.trimmedSuffix(characters); + return MutableStringView{*this}.trimmedSuffix(characters); } StringView String::trimmedSuffix(const StringView characters) const { - return StringView { *this }.trimmedSuffix(characters); + return StringView{*this}.trimmedSuffix(characters); } MutableStringView String::trimmedSuffix() { - return MutableStringView { *this }.trimmedSuffix(); + return MutableStringView{*this}.trimmedSuffix(); } StringView String::trimmedSuffix() const { - return StringView { *this }.trimmedSuffix(); + return StringView{*this}.trimmedSuffix(); } MutableStringView String::find(const StringView substring) { // Calling straight into the concrete implementation to reduce call stack depth - return MutableStringView { *this }.findOr(substring, nullptr); + return MutableStringView{*this}.findOr(substring, nullptr); } StringView String::find(const StringView substring) const { // Calling straight into the concrete implementation to reduce call stack depth - return StringView { *this }.findOr(substring, nullptr); + return StringView{*this}.findOr(substring, nullptr); } MutableStringView String::find(const char character) { // Calling straight into the concrete implementation to reduce call stack depth - return MutableStringView { *this }.findOr(character, nullptr); + return MutableStringView{*this}.findOr(character, nullptr); } StringView String::find(const char character) const { // Calling straight into the concrete implementation to reduce call stack depth - return StringView { *this }.findOr(character, nullptr); + return StringView{*this}.findOr(character, nullptr); } MutableStringView String::findOr(const StringView substring, char* const fail) { - return MutableStringView { *this }.findOr(substring, fail); + return MutableStringView{*this}.findOr(substring, fail); } StringView String::findOr(const StringView substring, const char* const fail) const { - return StringView { *this }.findOr(substring, fail); + return StringView{*this}.findOr(substring, fail); } MutableStringView String::findOr(const char character, char* const fail) { - return MutableStringView { *this }.findOr(character, fail); + return MutableStringView{*this}.findOr(character, fail); } StringView String::findOr(const char character, const char* const fail) const { - return StringView { *this }.findOr(character, fail); + return StringView{*this}.findOr(character, fail); } MutableStringView String::findLast(const StringView substring) { // Calling straight into the concrete implementation to reduce call stack depth - return MutableStringView { *this }.findLastOr(substring, nullptr); + return MutableStringView{*this}.findLastOr(substring, nullptr); } StringView String::findLast(const StringView substring) const { // Calling straight into the concrete implementation to reduce call stack depth - return StringView { *this }.findLastOr(substring, nullptr); + return StringView{*this}.findLastOr(substring, nullptr); } MutableStringView String::findLast(const char character) { // Calling straight into the concrete implementation to reduce call stack depth - return MutableStringView { *this }.findLastOr(character, nullptr); + return MutableStringView{*this}.findLastOr(character, nullptr); } StringView String::findLast(const char character) const { /* Calling straight into the concrete implementation to reduce call stack depth */ - return StringView { *this }.findLastOr(character, nullptr); + return StringView{*this}.findLastOr(character, nullptr); } MutableStringView String::findLastOr(const StringView substring, char* const fail) { - return MutableStringView { *this }.findLastOr(substring, fail); + return MutableStringView{*this}.findLastOr(substring, fail); } StringView String::findLastOr(const StringView substring, const char* const fail) const { - return StringView { *this }.findLastOr(substring, fail); + return StringView{*this}.findLastOr(substring, fail); } MutableStringView String::findLastOr(const char character, char* const fail) { - return MutableStringView { *this }.findLastOr(character, fail); + return MutableStringView{*this}.findLastOr(character, fail); } StringView String::findLastOr(const char character, const char* const fail) const { - return StringView { *this }.findLastOr(character, fail); + return StringView{*this}.findLastOr(character, fail); } bool String::contains(const StringView substring) const { - return StringView { *this }.contains(substring); + return StringView{*this}.contains(substring); } bool String::contains(const char character) const { - return StringView { *this }.contains(character); + return StringView{*this}.contains(character); } MutableStringView String::findAny(const StringView characters) { - return MutableStringView { *this }.findAny(characters); + return MutableStringView{*this}.findAny(characters); } StringView String::findAny(const StringView characters) const { - return StringView { *this }.findAny(characters); + return StringView{*this}.findAny(characters); } MutableStringView String::findAnyOr(const StringView characters, char* fail) { - return MutableStringView { *this }.findAnyOr(characters, fail); + return MutableStringView{*this}.findAnyOr(characters, fail); } StringView String::findAnyOr(const StringView characters, const char* fail) const { - return StringView { *this }.findAnyOr(characters, fail); + return StringView{*this}.findAnyOr(characters, fail); } MutableStringView String::findLastAny(const StringView characters) { - return MutableStringView { *this }.findLastAny(characters); + return MutableStringView{*this}.findLastAny(characters); } StringView String::findLastAny(const StringView characters) const { - return StringView { *this }.findLastAny(characters); + return StringView{*this}.findLastAny(characters); } MutableStringView String::findLastAnyOr(const StringView characters, char* fail) { - return MutableStringView { *this }.findLastAnyOr(characters, fail); + return MutableStringView{*this}.findLastAnyOr(characters, fail); } StringView String::findLastAnyOr(const StringView characters, const char* fail) const { - return StringView { *this }.findLastAnyOr(characters, fail); + return StringView{*this}.findLastAnyOr(characters, fail); } bool String::containsAny(const StringView substring) const { - return StringView { *this }.containsAny(substring); + return StringView{*this}.containsAny(substring); } char* String::release() { @@ -802,11 +802,11 @@ namespace Death::Containers namespace Implementation { String StringConverter::from(const std::string& other) { - return String { other.data(), other.size() }; + return String{other.data(), other.size()}; } std::string StringConverter::to(const String& other) { - return std::string { other.data(), other.size() }; + return std::string{other.data(), other.size()}; } } } \ No newline at end of file diff --git a/Sources/Shared/Containers/String.h b/Sources/Shared/Containers/String.h index 5174e0a0..d3bf6b1e 100644 --- a/Sources/Shared/Containers/String.h +++ b/Sources/Shared/Containers/String.h @@ -165,7 +165,8 @@ namespace Death::Containers * @cpp nullptr @ce --- in that case an empty string is constructed. * Depending on the size, it's either stored allocated or in a SSO. */ - /*implicit*/ String(const char* data); + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a String or std::size_t */ + template::value && !std::is_convertible::value>::type> /*implicit*/ String(T data) : String{nullptr, nullptr, nullptr, data} {} /** * @brief Construct from a sized C string @@ -860,6 +861,8 @@ namespace Death::Containers char* release(); private: + // Delegated to from the (templated) String(const char*). THREE extra nullptr arguments to avoid accidental ambiguous overloads. + explicit String(std::nullptr_t, std::nullptr_t, std::nullptr_t, const char* data); // Delegated to from the (templated) String(char*, Deleter). Argument order shuffled together with a null parameter to avoid accidental ambiguous overloads. explicit String(Deleter deleter, std::nullptr_t, char* data) noexcept; diff --git a/Sources/Shared/Containers/StringView.h b/Sources/Shared/Containers/StringView.h index b9791c87..76b92ebd 100644 --- a/Sources/Shared/Containers/StringView.h +++ b/Sources/Shared/Containers/StringView.h @@ -47,14 +47,14 @@ namespace Death::Containers * string view with this flag set doesn't need to have a copy allocated in * order to ensure it stays in scope. */ - Global = std::size_t { 1 } << (sizeof(std::size_t) * 8 - 1), + Global = std::size_t{1} << (sizeof(std::size_t) * 8 - 1), /** * The referenced string is null-terminated. A string view with this flag * set doesn't need to have a null-terminated copy allocated in order to * pass to an API that expects only null-terminated strings. */ - NullTerminated = std::size_t { 1 } << (sizeof(std::size_t) * 8 - 2) + NullTerminated = std::size_t{1} << (sizeof(std::size_t) * 8 - 2) }; DEFINE_ENUM_OPERATORS(StringViewFlags); @@ -75,12 +75,15 @@ namespace Death::Containers template class BasicStringView { public: + /* To avoid ambiguity in certain cases of passing 0 to overloads that take either a StringView or std::size_t */ + template::value>::type> constexpr /*implicit*/ BasicStringView(U) noexcept : _data{}, _sizePlusFlags{std::size_t(StringViewFlags::Global)} {} + /** * @brief Default constructor * * A default-constructed instance has @ref StringViewFlags::Global set. */ - constexpr /*implicit*/ BasicStringView(std::nullptr_t = nullptr) noexcept : _data{}, _sizePlusFlags{std::size_t(StringViewFlags::Global)} {} + constexpr /*implicit*/ BasicStringView() noexcept : _data{}, _sizePlusFlags{std::size_t(StringViewFlags::Global)} {} /** * @brief Construct from a C string of known size @@ -104,7 +107,7 @@ namespace Death::Containers constexpr /*implicit*/ BasicStringView(T* data, std::size_t size, StringViewFlags flags = {}) noexcept : _data{data}, _sizePlusFlags{ // This ends up being called from BasicStringView(T*, Flags), so basically on every implicit conversion // from a C string, thus the release build perf aspect wins over safety - (size | (std::size_t(flags) & Implementation::StringViewSizeMask))} { } + (size | (std::size_t(flags) & Implementation::StringViewSizeMask))} {} /** * @brief Construct from a @ref String @@ -141,7 +144,7 @@ namespace Death::Containers // It's also explicitly disallowing T[] arguments (which are implicitly convertible to an ArrayView), because those should be picking the T* // overload and rely on strlen(), consistently with how C string literals work; and disallowing construction from a StringView // because it'd get preferred over the implicit copy constructor. - template::type>::value && !std::is_same::type, BasicStringView>::value, decltype(ArrayView{std::declval()})>::type> constexpr /*implicit*/ BasicStringView(U&& data, StringViewFlags flags = { }) noexcept : BasicStringView{flags, ArrayView(data)} {} + template::type>::value && !std::is_same::type, BasicStringView>::value && !std::is_same::type, std::nullptr_t>::value, decltype(ArrayView{std::declval()})>::type> constexpr /*implicit*/ BasicStringView(U&& data, StringViewFlags flags = {}) noexcept: BasicStringView{flags, ArrayView(data)} {} /** @brief Construct a @ref StringView from a @ref MutableStringView */ template::value>::type> constexpr /*implicit*/ BasicStringView(BasicStringView mutable_) noexcept : _data{mutable_._data}, _sizePlusFlags{mutable_._sizePlusFlags} {} @@ -162,7 +165,7 @@ namespace Death::Containers * The @ref BasicStringView(std::nullptr_t) overload (which is a * default constructor) is additionally @cpp constexpr @ce. */ - DEATH_CONSTEXPR14 /*implicit*/ BasicStringView(T* data, StringViewFlags extraFlags = { }) noexcept : BasicStringView {data, extraFlags, nullptr} {} + template::value && std::is_convertible::value>::type> /*implicit*/ BasicStringView(U data, StringViewFlags extraFlags = {}) noexcept : BasicStringView{data, extraFlags, nullptr} {} /** * @brief Construct a view on an external type / from an external representation @@ -283,7 +286,7 @@ namespace Death::Containers * is @cpp nullptr @ce, returns zero-sized @cpp nullptr @ce view. */ constexpr BasicStringView prefix(T* end) const { - return end ? slice(_data, end) : BasicStringView {}; + return end ? slice(_data, end) : BasicStringView{}; } /** @@ -761,13 +764,13 @@ namespace Death::Containers // Called from BasicStringView(U&&, StringViewFlags), see its comment for details; arguments in a flipped order to avoid accidental // ambiguity. The ArrayView type is a template to avoid having to include ArrayView.h. - template::value>::type> constexpr explicit BasicStringView(StringViewFlags flags, ArrayView data) noexcept : BasicStringView { data.data(), data.size(), flags } {} + template::value>::type> constexpr explicit BasicStringView(StringViewFlags flags, ArrayView data) noexcept : BasicStringView{data.data(), data.size(), flags} {} // Used by the char* constructor, delinlined because it calls into std::strlen() explicit BasicStringView(T* data, StringViewFlags flags, std::nullptr_t) noexcept; // Used by slice() to skip unneeded checks in the public constexpr constructor - constexpr explicit BasicStringView(T* data, std::size_t sizePlusFlags, std::nullptr_t) noexcept : _data { data }, _sizePlusFlags { sizePlusFlags } {} + constexpr explicit BasicStringView(T* data, std::size_t sizePlusFlags, std::nullptr_t) noexcept : _data{data}, _sizePlusFlags{sizePlusFlags} {} T* _data; std::size_t _sizePlusFlags; @@ -825,7 +828,7 @@ namespace Death::Containers */ constexpr StringView operator"" _s(const char* data, std::size_t size) { // Using plain bit ops instead of EnumSet to speed up debug builds - return StringView { data, size, StringViewFlags(std::size_t(StringViewFlags::Global) | std::size_t(StringViewFlags::NullTerminated)) }; + return StringView{data, size, StringViewFlags(std::size_t(StringViewFlags::Global) | std::size_t(StringViewFlags::NullTerminated))}; } } @@ -966,7 +969,7 @@ namespace Death::Containers template<> struct ArrayViewConverter> { static ArrayView from(const BasicStringView& other); }; - template struct ErasedArrayViewConverter> : ArrayViewConverter> { }; - template struct ErasedArrayViewConverter> : ArrayViewConverter> { }; + template struct ErasedArrayViewConverter> : ArrayViewConverter> {}; + template struct ErasedArrayViewConverter> : ArrayViewConverter> {}; } } \ No newline at end of file diff --git a/Sources/Shared/IO/Stream.h b/Sources/Shared/IO/Stream.h index 33ade27b..12ffcb5d 100644 --- a/Sources/Shared/IO/Stream.h +++ b/Sources/Shared/IO/Stream.h @@ -75,7 +75,7 @@ namespace Death::IO template::value>::type* = nullptr> DEATH_ALWAYS_INLINE T ReadValue() { - T buffer; + T buffer = { }; Read(&buffer, sizeof(T)); return buffer; } diff --git a/Sources/nCine/AppConfiguration.cpp b/Sources/nCine/AppConfiguration.cpp index 7be38f51..42e37842 100644 --- a/Sources/nCine/AppConfiguration.cpp +++ b/Sources/nCine/AppConfiguration.cpp @@ -52,7 +52,6 @@ namespace nCine glMajorVersion_(3), glMinorVersion_(3), #endif - argc_(0), argv_(nullptr) { #if defined(DEATH_TARGET_ANDROID) @@ -81,21 +80,8 @@ namespace nCine return dataPath_; } -#if defined(DEATH_TARGET_WINDOWS) - const String AppConfiguration::argv(int index) const - { - if (index < argc_) { - return Death::Utf8::FromUtf16(argv_[index]); - } - return { }; - } -#else const StringView AppConfiguration::argv(int index) const { - if (index < argc_) { - return argv_[index]; - } - return { }; + return argv_[index]; } -#endif } diff --git a/Sources/nCine/AppConfiguration.h b/Sources/nCine/AppConfiguration.h index c9ee5cba..cd8c631c 100644 --- a/Sources/nCine/AppConfiguration.h +++ b/Sources/nCine/AppConfiguration.h @@ -9,12 +9,6 @@ using namespace Death::Containers; namespace nCine { -#if defined(DEATH_TARGET_WINDOWS) - typedef wchar_t* NativeArgument; -#else - typedef char* NativeArgument; -#endif - /// The class storing initialization settings for an nCine application class AppConfiguration { @@ -96,15 +90,11 @@ namespace nCine } /// \returns The number of arguments passed on the command-line - inline int argc() const { - return argc_; + inline std::size_t argc() const { + return argv_.size(); } /// \returns The selected argument from the ones passed on the command-line -#if defined(DEATH_TARGET_WINDOWS) - const String argv(int index) const; -#else const StringView argv(int index) const; -#endif private: // Pre-configured compile-time variables @@ -113,8 +103,11 @@ namespace nCine const unsigned int glMajorVersion_; const unsigned int glMinorVersion_; - int argc_; - NativeArgument* argv_; +#if defined(DEATH_TARGET_WINDOWS) + Array argv_; +#else + Array argv_; +#endif String dataPath_; friend class MainApplication; diff --git a/Sources/nCine/Application.cpp b/Sources/nCine/Application.cpp index 12e121da..3df36e99 100644 --- a/Sources/nCine/Application.cpp +++ b/Sources/nCine/Application.cpp @@ -1,5 +1,13 @@ #include "../Common.h" +#if defined(DEATH_TARGET_WINDOWS) +extern "C" +{ + _declspec(dllexport) unsigned long int NvOptimusEnablement = 0x00000001; + _declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 0x00000001; +}; +#endif + #if defined(DEATH_TARGET_WINDOWS) && !defined(CMAKE_BUILD) # pragma comment(lib, "opengl32.lib") # if defined(_M_X64) @@ -33,13 +41,6 @@ # else # error Unsupported architecture # endif - -extern "C" -{ - _declspec(dllexport) unsigned long int NvOptimusEnablement = 0x00000001; - _declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; -}; - #endif #include "Application.h" diff --git a/Sources/nCine/Base/Algorithms.cpp b/Sources/nCine/Base/Algorithms.cpp index 10aefa5a..adbc3391 100644 --- a/Sources/nCine/Base/Algorithms.cpp +++ b/Sources/nCine/Base/Algorithms.cpp @@ -12,7 +12,7 @@ namespace nCine { - int copyStringFirst(char* dest, int destSize, const char* source, int count) + std::int32_t copyStringFirst(char* dest, std::int32_t destSize, const char* source, std::int32_t count) { if (destSize == 0) { return 0; @@ -44,24 +44,24 @@ namespace nCine return n; } - int formatString(char* buffer, size_t maxLen, const char* format, ...) + std::int32_t formatString(char* buffer, std::size_t maxLen, const char* format, ...) { va_list args; va_start(args, format); #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_MINGW) - const int writtenChars = vsnprintf_s(buffer, maxLen, maxLen - 1, format, args); - const int result = (writtenChars > -1 ? writtenChars : maxLen - 1); + const std::int32_t writtenChars = vsnprintf_s(buffer, maxLen, maxLen - 1, format, args); + const std::int32_t result = (writtenChars > -1 ? writtenChars : maxLen - 1); #else - const int result = ::vsnprintf(buffer, maxLen, format, args); + const std::int32_t result = ::vsnprintf(buffer, maxLen, format, args); #endif va_end(args); return result; } - inline unsigned CountDecimalDigit32(uint32_t n) + inline std::uint32_t CountDecimalDigit32(std::uint32_t n) { #if defined(DEATH_TARGET_MSVC) || defined(DEATH_TARGET_GCC) - static constexpr uint32_t powers_of_10[] = { + static constexpr std::uint32_t powers_of_10[] = { 0, 10, 100, @@ -77,9 +77,9 @@ namespace nCine # if defined(DEATH_TARGET_MSVC) unsigned long i = 0; _BitScanReverse(&i, n | 1); - uint32_t t = (i + 1) * 1233 >> 12; + std::uint32_t t = (i + 1) * 1233 >> 12; # elif defined(DEATH_TARGET_GCC) - uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; + std::uint32_t t = (32 - __builtin_clz(n | 1)) * 1233 >> 12; # endif return t - (n < powers_of_10[t]) + 1; #else @@ -97,10 +97,10 @@ namespace nCine #endif } - inline unsigned CountDecimalDigit64(uint64_t n) + inline std::uint32_t CountDecimalDigit64(std::uint64_t n) { #if defined(DEATH_TARGET_MSVC) || defined(DEATH_TARGET_GCC) - static constexpr uint64_t powers_of_10[] = { + static constexpr std::uint64_t powers_of_10[] = { 0, 10, 100, @@ -124,20 +124,20 @@ namespace nCine }; # if defined(DEATH_TARGET_GCC) - uint32_t t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; + std::uint32_t t = (64 - __builtin_clzll(n | 1)) * 1233 >> 12; # elif defined(DEATH_TARGET_32BIT) unsigned long i = 0; - uint64_t m = n | 1; + std::uint64_t m = n | 1; if (_BitScanReverse(&i, m >> 32)) { i += 32; } else { _BitScanReverse(&i, m & 0xFFFFFFFF); } - uint32_t t = (i + 1) * 1233 >> 12; + std::uint32_t t = (i + 1) * 1233 >> 12; # else unsigned long i = 0; _BitScanReverse64(&i, n | 1); - uint32_t t = (i + 1) * 1233 >> 12; + std::uint32_t t = (i + 1) * 1233 >> 12; # endif return t - (n < powers_of_10[t]) + 1; #else @@ -165,9 +165,9 @@ namespace nCine #endif } - void u32tos(uint32_t value, char* buffer) + void u32tos(std::uint32_t value, char* buffer) { - unsigned digit = CountDecimalDigit32(value); + std::uint32_t digit = CountDecimalDigit32(value); buffer += digit; *buffer = '\0'; @@ -177,9 +177,9 @@ namespace nCine } while (value > 0); } - void i32tos(int32_t value, char* buffer) + void i32tos(std::int32_t value, char* buffer) { - uint32_t u = static_cast(value); + std::uint32_t u = static_cast(value); if (value < 0) { *buffer++ = '-'; u = ~u + 1; @@ -187,9 +187,9 @@ namespace nCine u32tos(u, buffer); } - void u64tos(uint64_t value, char* buffer) + void u64tos(std::uint64_t value, char* buffer) { - unsigned digit = CountDecimalDigit64(value); + std::uint32_t digit = CountDecimalDigit64(value); buffer += digit; *buffer = '\0'; @@ -199,9 +199,9 @@ namespace nCine } while (value > 0); } - void i64tos(int64_t value, char* buffer) + void i64tos(std::int64_t value, char* buffer) { - uint64_t u = static_cast(value); + std::uint64_t u = static_cast(value); if (value < 0) { *buffer++ = '-'; u = ~u + 1; @@ -209,26 +209,26 @@ namespace nCine u64tos(u, buffer); } - void ftos(double value, char* buffer, int bufferSize) + void ftos(double value, char* buffer, std::int32_t bufferSize) { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_MINGW) - int length = sprintf_s(buffer, bufferSize, "%f", value); + std::int32_t length = sprintf_s(buffer, bufferSize, "%f", value); #else - int length = snprintf(buffer, bufferSize, "%f", value); + std::int32_t length = snprintf(buffer, bufferSize, "%f", value); #endif if (length <= 0) { buffer[0] = '\0'; return; } - int n = length - 1; + std::int32_t n = length - 1; while (n >= 0 && buffer[n] == '0') { n--; } n++; bool separatorFound = false; - for (int i = 0; i < n; i++) { + for (std::int32_t i = 0; i < n; i++) { if (buffer[i] == '.' || buffer[i] == ',') { separatorFound = true; break; @@ -244,42 +244,4 @@ namespace nCine buffer[n] = '\0'; } } - - uint64_t parseVersion(const Containers::StringView version) - { - auto parts = version.split('.'); - size_t partsCount = parts.size(); - if (partsCount == 0) { - return 0; - } - - uint64_t major = 0, minor = 0, patch = 0; - size_t partSize; - char stringBuffer[32]; - - partSize = std::min(parts[0].size(), sizeof(stringBuffer) - 1); - std::memcpy(stringBuffer, parts[0].data(), partSize); - stringBuffer[partSize] = '\0'; - major = strtol(stringBuffer, nullptr, 10); - - if (partsCount >= 2 && !parts[1].empty()) { - partSize = std::min(parts[1].size(), sizeof(stringBuffer) - 1); - std::memcpy(stringBuffer, parts[1].data(), partSize); - stringBuffer[partSize] = '\0'; - minor = strtol(stringBuffer, nullptr, 10); - } - if (partsCount >= 3 && !parts[2].empty()) { - if (parts[2][0] == 'r') { - // GIT Revision - always the latest - patch = 0x0FFFFFFFull; - } else { - partSize = std::min(parts[2].size(), sizeof(stringBuffer) - 1); - std::memcpy(stringBuffer, parts[2].data(), partSize); - stringBuffer[partSize] = '\0'; - patch = strtol(stringBuffer, nullptr, 10); - } - } - - return (patch & 0xFFFFFFFFull) | ((minor & 0xFFFFull) << 32) | ((major & 0xFFFFull) << 48); - } } diff --git a/Sources/nCine/Base/Algorithms.h b/Sources/nCine/Base/Algorithms.h index ee007803..df9ab338 100644 --- a/Sources/nCine/Base/Algorithms.h +++ b/Sources/nCine/Base/Algorithms.h @@ -13,32 +13,32 @@ using namespace Death; namespace nCine { // Traits - template + template struct isIntegral { static constexpr bool value = false; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; @@ -48,46 +48,46 @@ namespace nCine { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template <> + template<> struct isIntegral { static constexpr bool value = true; }; - template + template inline bool IsLess(const T &a, const T &b) { return a < b; } - template + template inline bool IsNotLess(const T &a, const T &b) { return !(a < b); } /// Returns true if the range is sorted into ascending order - template + template inline bool isSorted(Iterator first, const Iterator last) { if (first == last) @@ -104,7 +104,7 @@ namespace nCine } /// Returns true if the range is sorted, using a custom comparison - template + template inline bool isSorted(Iterator first, const Iterator last, Compare comp) { if (first == last) @@ -121,7 +121,7 @@ namespace nCine } /// Returns an iterator to the first element in the range which does not follow an ascending order, or last if sorted - template + template inline const Iterator isSortedUntil(Iterator first, const Iterator last) { if (first == last) @@ -138,7 +138,7 @@ namespace nCine } /// Returns an iterator to the first element in the range which does not follow the custom comparison, or last if sorted - template + template inline const Iterator isSortedUntil(Iterator first, const Iterator last, Compare comp) { if (first == last) @@ -155,7 +155,7 @@ namespace nCine } /// Partition function for quicksort with iterators - template + template inline Iterator partition(Iterator first, Iterator last, Compare comp) { Iterator pivot = last; @@ -183,10 +183,10 @@ namespace nCine namespace { /// Quicksort implementation with random access iterators and custom compare function - template + template inline void quicksort(Iterator first, Iterator last, RandomAccessIteratorTag, Compare comp) { - int size = distance(first, last, RandomAccessIteratorTag()); + std::int32_t size = distance(first, last, RandomAccessIteratorTag()); if (size > 1) { Iterator p = prev(last); std::swap(*next(first, size / 2), *p); @@ -198,7 +198,7 @@ namespace nCine } /// Quicksort implementation with bidirectional iterators and custom compare function - template + template inline void quicksort(Iterator first, Iterator last, BidirectionalIteratorTag, Compare comp) { if (first != last) { @@ -214,28 +214,28 @@ namespace nCine } /// Quicksort implementation with iterators and custom compare function - template + template inline void quicksort(Iterator first, Iterator last, Compare comp) { quicksort(first, last, IteratorTraits::IteratorCategory(), comp); } /// Quicksort implementation with iterators, ascending order - template + template inline void quicksort(Iterator first, Iterator last) { quicksort(first, last, IteratorTraits::IteratorCategory(), IsLess::ValueType>); } /// Quicksort implementation with iterators, descending order - template + template inline void quicksortDesc(Iterator first, Iterator last) { quicksort(first, last, IteratorTraits::IteratorCategory(), IsNotLess::ValueType>); } /// A container for functions to destruct objects and arrays of objects - template + template struct destructHelpers { template @@ -245,16 +245,16 @@ namespace nCine } template - inline static void destructArray(T* ptr, unsigned int numElements) + inline static void destructArray(T* ptr, std::uint32_t numElements) { - for (unsigned int i = 0; i < numElements; i++) + for (std::uint32_t i = 0; i < numElements; i++) ptr[numElements - i - 1].~T(); } }; namespace detail { - template + template struct typeIdentity { using type = T; @@ -266,18 +266,18 @@ namespace nCine auto tryAddRValueReference(...)->typeIdentity; } - template + template struct addRValueReference : decltype(detail::tryAddRValueReference(0)) {}; - template + template typename addRValueReference::type declVal(); /// Specialization for trivially destructible types - template + template struct isDestructible { static constexpr bool value = false; }; - template + template struct isDestructible().~T())> { static constexpr bool value = (true && !__is_union(T)); @@ -285,47 +285,47 @@ namespace nCine // Use `__has_trivial_destructor()` only on GCC #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) - template + template struct hasTrivialDestructor { static constexpr bool value = __has_trivial_destructor(T); }; - template + template struct isTriviallyDestructible { static constexpr bool value = isDestructible::value && hasTrivialDestructor::value; }; #else - template + template struct isTriviallyDestructible { static constexpr bool value = __is_trivially_destructible(T); }; #endif - template <> + template<> struct destructHelpers { - template + template inline static void destructObject(T* ptr) { } - template + template inline static void destructArray(T* ptr, unsigned int numElements) { } }; - template + template void destructObject(T* ptr) { destructHelpers::value>::destructObject(ptr); } - template - void destructArray(T* ptr, unsigned int numElements) + template + void destructArray(T* ptr, std::uint32_t numElements) { destructHelpers::value>::destructArray(ptr, numElements); } @@ -335,9 +335,9 @@ namespace nCine return a + ratio * (b - a); } - inline int lerp(int a, int b, float ratio) + inline std::int32_t lerp(std::int32_t a, std::int32_t b, float ratio) { - return (int)std::round(a + ratio * (float)(b - a)); + return (std::int32_t)std::round(a + ratio * (float)(b - a)); } inline void lowercaseInPlace(const Containers::MutableStringView string) @@ -363,20 +363,80 @@ namespace nCine } } - int copyStringFirst(char* dest, int destSize, const char* source, int count = -1); + std::int32_t copyStringFirst(char* dest, std::int32_t destSize, const char* source, std::int32_t count = -1); + + template + inline std::int32_t copyStringFirst(char(&dest)[size], const char* source, std::int32_t count = -1) { + return copyStringFirst(dest, size, source, count); + } + + int formatString(char* buffer, std::size_t maxLen, const char* format, ...); - template - inline int copyStringFirst(char(&dest)[N], const char* source, int count = -1) { - return copyStringFirst(dest, N, source, count); + void u32tos(std::uint32_t value, char* buffer); + void i32tos(std::int32_t value, char* buffer); + void u64tos(std::uint64_t value, char* buffer); + void i64tos(std::int64_t value, char* buffer); + void ftos(double value, char* buffer, std::int32_t bufferSize); + + constexpr bool isDigit(char c) + { + return (c >= '0' && c <= '9'); + } + + constexpr std::uint32_t stou32(const char* str, std::size_t length) + { + std::uint32_t n = 0; + while (length > 0) { + if (!isDigit(*str)) { + break; + } + n *= 10; + n += (*str++ - '0'); + length--; + } + return n; + } + + constexpr std::uint64_t stou64(const char* str, std::size_t length) + { + std::uint64_t n = 0; + while (length > 0) { + if (!isDigit(*str)) { + break; + } + n *= 10; + n += (*str++ - '0'); + length--; + } + return n; } - int formatString(char* buffer, size_t maxLen, const char* format, ...); + constexpr std::uint64_t parseVersion(const Containers::StringView& version) + { + std::size_t versionLength = version.size(); + std::size_t dotIndices[3] { }; + std::size_t foundCount = 0; - void u32tos(uint32_t value, char* buffer); - void i32tos(int32_t value, char* buffer); - void u64tos(uint64_t value, char* buffer); - void i64tos(int64_t value, char* buffer); - void ftos(double value, char* buffer, int bufferSize); + for (std::size_t i = 0; i < versionLength; i++) { + if (version[i] == '.') { + dotIndices[foundCount++] = i; + if (foundCount >= countof(dotIndices) - 1) { + // Save only indices of the first 2 dots and keep the last index for string length + break; + } + } + } + + dotIndices[foundCount] = versionLength; - uint64_t parseVersion(const Containers::StringView version); + std::uint64_t major = stou32(&version[0], dotIndices[0]); + std::uint64_t minor = (foundCount >= 1 ? stou32(&version[dotIndices[0] + 1], dotIndices[1] - dotIndices[0] - 1) : 0); + std::uint64_t patch = (foundCount >= 2 + ? (version[dotIndices[1] + 1] != 'r' + ? stou32(&version[dotIndices[1] + 1], dotIndices[2] - dotIndices[1] - 1) + : 0x0FFFFFFFULL) // GIT Revision - use special value, so it's always the latest (without upper 4 bits) + : 0); + + return (patch & 0xFFFFFFFFULL) | ((minor & 0xFFFFULL) << 32) | ((major & 0xFFFFULL) << 48); + } } diff --git a/Sources/nCine/Graphics/BinaryShaderCache.cpp b/Sources/nCine/Graphics/BinaryShaderCache.cpp index e22b7859..2bf2d10c 100644 --- a/Sources/nCine/Graphics/BinaryShaderCache.cpp +++ b/Sources/nCine/Graphics/BinaryShaderCache.cpp @@ -179,6 +179,10 @@ namespace nCine void BinaryShaderCache::prune() { + if (path_.empty()) { + return; + } + fs::Directory dir(path_); while (const StringView shaderPath = dir.GetNext()) { if (fs::GetExtension(shaderPath) != "shader"_s) { @@ -186,17 +190,20 @@ namespace nCine } StringView filename = fs::GetFileNameWithoutExtension(shaderPath); + + bool shouldRemove; if (filename.size() != 32) { - fs::RemoveFile(shaderPath); - continue; + shouldRemove = true; + } else { + char componentString[17]; + std::memcpy(componentString, &filename[16], 16); + componentString[16] = '\0'; + + std::uint64_t platformHash = strtoull(componentString, nullptr, 16); + shouldRemove = (platformHash != platformHash_); } - char componentString[17]; - std::memcpy(componentString, &filename[16], 16); - componentString[16] = '\0'; - - std::uint64_t platformHash = strtoull(componentString, nullptr, 16); - if (platformHash != platformHash_) { + if (shouldRemove) { fs::RemoveFile(shaderPath); } } @@ -204,8 +211,16 @@ namespace nCine void BinaryShaderCache::clear() { + if (path_.empty()) { + return; + } + fs::Directory dir(path_); while (const StringView shaderPath = dir.GetNext()) { + if (fs::GetExtension(shaderPath) != "shader"_s) { + continue; + } + fs::RemoveFile(shaderPath); } } diff --git a/Sources/nCine/Input/JoyMapping.cpp b/Sources/nCine/Input/JoyMapping.cpp index 80c8bb4d..8b3af82b 100644 --- a/Sources/nCine/Input/JoyMapping.cpp +++ b/Sources/nCine/Input/JoyMapping.cpp @@ -4,7 +4,6 @@ #include "../Primitives/Vector2.h" #include // for memcpy() -#include // for strtoul() #include #include diff --git a/Sources/nCine/MainApplication.cpp b/Sources/nCine/MainApplication.cpp index 8ac9915a..29fd4c55 100644 --- a/Sources/nCine/MainApplication.cpp +++ b/Sources/nCine/MainApplication.cpp @@ -17,13 +17,15 @@ #if defined(DEATH_TARGET_EMSCRIPTEN) # include -#endif -#if defined(DEATH_TARGET_SWITCH) +#elif defined(DEATH_TARGET_SWITCH) # include +#elif defined(DEATH_TARGET_WINDOWS) +# include #endif #include "tracy.h" +using namespace Death; using namespace Death::Containers::Literals; using namespace Death::IO; @@ -44,6 +46,7 @@ extern "C" IMAGE_DOS_HEADER __ImageBase; bool __showLogConsole; bool __hasVirtualTerminal; +Array __consolePrompt; static bool CreateLogConsole(const StringView& title) { @@ -68,6 +71,51 @@ static bool CreateLogConsole(const StringView& title) ::setvbuf(stdin, NULL, _IONBF, 0); } + // Try to get command prompt to be able to reprint it when the game exits + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (::GetConsoleScreenBufferInfo(consoleHandleOut, &csbi)) { + DWORD dwConsoleColumnWidth = (DWORD)(csbi.srWindow.Right - csbi.srWindow.Left + 1); + SHORT xEnd = csbi.dwCursorPosition.X; + SHORT yEnd = csbi.dwCursorPosition.Y; + if (xEnd != 0 || yEnd != 0) { + DWORD dwNumberOfChars; + SHORT yBegin = yEnd; + if (dwConsoleColumnWidth > 16) { + Array tmp(NoInit, dwConsoleColumnWidth); + while (yBegin > 0) { + COORD dwReadCoord = { 0, yBegin }; + if (!::ReadConsoleOutputCharacter(consoleHandleOut, tmp.data(), dwConsoleColumnWidth, dwReadCoord, &dwNumberOfChars)) { + break; + } + + for (DWORD i = dwNumberOfChars - 8; i < dwNumberOfChars; i++) { + wchar_t wchar = tmp[i]; + if (wchar != L' ') { + yBegin--; + continue; + } + } + + if (yBegin < yEnd) { + yBegin++; + } + break; + } + } + + DWORD promptLength = (yEnd - yBegin) * dwConsoleColumnWidth + xEnd; + __consolePrompt = Array(NoInit, promptLength); + COORD dwPromptCoord = { 0, yEnd }; + if (::ReadConsoleOutputCharacter(consoleHandleOut, __consolePrompt.data(), promptLength, dwPromptCoord, &dwNumberOfChars)) { + if (::SetConsoleCursorPosition(consoleHandleOut, dwPromptCoord)) { + ::FillConsoleOutputCharacter(consoleHandleOut, L' ', promptLength, dwPromptCoord, &dwNumberOfChars); + } + } else { + __consolePrompt = {}; + } + } + } + return true; } else if (::AllocConsole()) { ::freopen_s(&fDummy, "CONOUT$", "w", stdout); @@ -98,21 +146,20 @@ static bool CreateLogConsole(const StringView& title) static void DestroyLogConsole() { - // The "Enter" key is only sent if the console window is in focus - if (::GetConsoleWindow() == ::GetForegroundWindow()) { - // Send the "Enter" key to the console to release the command prompt - INPUT ip; - ip.type = INPUT_KEYBOARD; - ip.ki.wScan = 0; - ip.ki.time = 0; - ip.ki.dwExtraInfo = 0; - - ip.ki.wVk = 0x0D; // virtual-key code for the "Enter" key - ip.ki.dwFlags = 0; // 0 for key press - ::SendInput(1, &ip, sizeof(INPUT)); - - ip.ki.dwFlags = KEYEVENTF_KEYUP; // `KEYEVENTF_KEYUP` for key release - ::SendInput(1, &ip, sizeof(INPUT)); + if (!__consolePrompt.empty()) { + HANDLE consoleHandleOut = ::GetStdHandle(STD_OUTPUT_HANDLE); + if (consoleHandleOut != INVALID_HANDLE_VALUE) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + if (::GetConsoleScreenBufferInfo(consoleHandleOut, &csbi)) { + DWORD xEnd = csbi.dwCursorPosition.X; + DWORD yEnd = csbi.dwCursorPosition.Y; + if (xEnd != 0 || yEnd != 0) { + DWORD dwNumberOfCharsWritten; + ::WriteConsole(consoleHandleOut, L"\r\n", countof(L"\r\n") - 1, &dwNumberOfCharsWritten, NULL); + ::WriteConsole(consoleHandleOut, __consolePrompt.data(), (DWORD)__consolePrompt.size(), &dwNumberOfCharsWritten, NULL); + } + } + } } ::FreeConsole(); @@ -120,18 +167,14 @@ static void DestroyLogConsole() static bool EnableVirtualTerminalProcessing() { - HANDLE hOut = ::GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut == INVALID_HANDLE_VALUE) { + HANDLE consoleHandleOut = ::GetStdHandle(STD_OUTPUT_HANDLE); + if (consoleHandleOut == INVALID_HANDLE_VALUE) { return false; } + DWORD dwMode = 0; - if (!::GetConsoleMode(hOut, &dwMode)) { - return false; - } - if (!::SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) { - return false; - } - return true; + return (::GetConsoleMode(consoleHandleOut, &dwMode) && + ::SetConsoleMode(consoleHandleOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)); } #elif defined(DEATH_TRACE) && (defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_EMSCRIPTEN) || defined(DEATH_TARGET_UNIX)) @@ -228,7 +271,8 @@ namespace nCine #if defined(DEATH_TRACE) # if defined(DEATH_TARGET_APPLE) - __hasVirtualTerminal = isatty(1); + // Xcode's console reports that it is a TTY, but it doesn't support colors, TERM is not defined in this case + __hasVirtualTerminal = isatty(1) && ::getenv("TERM"); # elif defined(DEATH_TARGET_EMSCRIPTEN) char* userAgent = (char*)EM_ASM_PTR({ return (typeof navigator !== 'undefined' && navigator !== null && @@ -244,14 +288,14 @@ namespace nCine } # elif defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) __showLogConsole = false; - for (int i = 0; i < argc; i++) { + for (std::int32_t i = 0; i < argc; i++) { if (wcscmp(argv[i], L"/log") == 0) { __showLogConsole = true; break; } } if (__showLogConsole) { - CreateLogConsole(NCINE_APP_NAME " Console"); + CreateLogConsole(NCINE_APP_NAME " [Console]"); __hasVirtualTerminal = EnableVirtualTerminalProcessing(); } else { __hasVirtualTerminal = false; @@ -259,17 +303,24 @@ namespace nCine # elif defined(DEATH_TARGET_UNIX) ::setvbuf(stdout, nullptr, _IONBF, 0); ::setvbuf(stderr, nullptr, _IONBF, 0); - - // Xcode's console reports that it is a TTY, but it doesn't support colors, but TERM is not defined - __hasVirtualTerminal = isatty(1) && std::getenv("TERM"); + __hasVirtualTerminal = isatty(1); # endif #endif appEventHandler_ = createAppEventHandler(); // Only `OnPreInit()` can modify the application configuration - appCfg_.argc_ = argc; - appCfg_.argv_ = argv; +#if defined(DEATH_TARGET_WINDOWS) + appCfg_.argv_ = Array(argc - 1); + for (std::int32_t i = 1; i < argc; i++) { + appCfg_.argv_[i - 1] = Utf8::FromUtf16(argv[i]); + } +#else + appCfg_.argv_ = Array(argc - 1); + for (std::int32_t i = 1; i < argc; i++) { + appCfg_.argv_[i - 1] = argv[i]; + } +#endif appEventHandler_->OnPreInit(appCfg_); LOGI("IAppEventHandler::OnPreInit() invoked"); diff --git a/Sources/nCine/MainApplication.h b/Sources/nCine/MainApplication.h index 78ec3201..1993d101 100644 --- a/Sources/nCine/MainApplication.h +++ b/Sources/nCine/MainApplication.h @@ -4,6 +4,12 @@ namespace nCine { +#if defined(DEATH_TARGET_WINDOWS) + typedef wchar_t* NativeArgument; +#else + typedef char* NativeArgument; +#endif + #if defined(WITH_QT5) class Qt5Widget; #endif diff --git a/cmake/ncine_headers.cmake b/cmake/ncine_headers.cmake index fa7c4ee9..e44b701c 100644 --- a/cmake/ncine_headers.cmake +++ b/cmake/ncine_headers.cmake @@ -331,6 +331,7 @@ list(APPEND HEADERS ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/InGameMenu.h ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/InputDiagnosticsSection.h ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/LanguageSelectSection.h + ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/LoadingSection.h ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/MainMenu.h ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/OptionsSection.h ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/PauseSection.h diff --git a/cmake/ncine_sources.cmake b/cmake/ncine_sources.cmake index 200ac429..e6fcbfbe 100644 --- a/cmake/ncine_sources.cmake +++ b/cmake/ncine_sources.cmake @@ -256,6 +256,7 @@ list(APPEND SOURCES ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/InGameMenu.cpp ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/InputDiagnosticsSection.cpp ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/LanguageSelectSection.cpp + ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/LoadingSection.cpp ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/MainMenu.cpp ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/OptionsSection.cpp ${NCINE_SOURCE_DIR}/Jazz2/UI/Menu/PauseSection.cpp