diff --git a/Sources/Jazz2.vcxproj b/Sources/Jazz2.vcxproj index cb00cbde..45512532 100644 --- a/Sources/Jazz2.vcxproj +++ b/Sources/Jazz2.vcxproj @@ -709,6 +709,7 @@ + @@ -1038,6 +1039,7 @@ + diff --git a/Sources/Jazz2.vcxproj.filters b/Sources/Jazz2.vcxproj.filters index dbda9da7..2cc9f221 100644 --- a/Sources/Jazz2.vcxproj.filters +++ b/Sources/Jazz2.vcxproj.filters @@ -1287,9 +1287,6 @@ Header Files\simdjson - - Header Files\nCine\Threading - Header Files\Shared\IO @@ -1539,6 +1536,9 @@ Header Files\Jazz2\UI\Menu + + Header Files\Shared\IO + @@ -2384,9 +2384,6 @@ Source Files\simdjson - - Source Files\nCine\Threading - Source Files\nCine\Graphics @@ -2525,6 +2522,9 @@ Source Files\Jazz2\UI\Menu + + Source Files\Shared\IO + diff --git a/Sources/Jazz2/PreferencesCache.cpp b/Sources/Jazz2/PreferencesCache.cpp index 16e47ca5..577bf63d 100644 --- a/Sources/Jazz2/PreferencesCache.cpp +++ b/Sources/Jazz2/PreferencesCache.cpp @@ -17,6 +17,9 @@ using namespace nCine; namespace Jazz2 { bool PreferencesCache::FirstRun = false; +#if defined(DEATH_TARGET_EMSCRIPTEN) + bool PreferencesCache::IsStandalone = false; +#endif #if defined(WITH_MULTIPLAYER) String PreferencesCache::InitialState; #endif @@ -393,6 +396,11 @@ namespace Jazz2 } else if (arg == "/reset-controls"_s) { UI::ControlScheme::Reset(); } +# if defined(DEATH_TARGET_EMSCRIPTEN) + else if (arg == "/standalone"_s) { + IsStandalone = true; + } +# endif # if defined(WITH_MULTIPLAYER) else if (InitialState.empty() && (arg == "/server"_s || arg.hasPrefix("/connect:"_s))) { InitialState = arg; diff --git a/Sources/Jazz2/PreferencesCache.h b/Sources/Jazz2/PreferencesCache.h index 2e9963eb..11910b54 100644 --- a/Sources/Jazz2/PreferencesCache.h +++ b/Sources/Jazz2/PreferencesCache.h @@ -95,6 +95,9 @@ namespace Jazz2 static constexpr std::int32_t UseVsync = -1; static bool FirstRun; +#if defined(DEATH_TARGET_EMSCRIPTEN) + static bool IsStandalone; +#endif #if defined(WITH_MULTIPLAYER) static String InitialState; #endif diff --git a/Sources/Jazz2/UI/Menu/BeginSection.cpp b/Sources/Jazz2/UI/Menu/BeginSection.cpp index d1511c6c..9711c2a2 100644 --- a/Sources/Jazz2/UI/Menu/BeginSection.cpp +++ b/Sources/Jazz2/UI/Menu/BeginSection.cpp @@ -92,7 +92,11 @@ namespace Jazz2::UI::Menu _items.emplace_back(ItemData { Item::Options, _("Options") }); // TRANSLATORS: Menu item in main menu _items.emplace_back(ItemData { Item::About, _("About") }); -#if !defined(DEATH_TARGET_EMSCRIPTEN) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) +#if !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) +# if defined(DEATH_TARGET_EMSCRIPTEN) + // Show quit button only in PWA/standalone environment + if (PreferencesCache::IsStandalone) +# endif // TRANSLATORS: Menu item in main menu _items.emplace_back(ItemData { Item::Quit, _("Quit") }); #endif @@ -407,7 +411,7 @@ namespace Jazz2::UI::Menu _root->PlaySfx("MenuSelect"_s, 0.6f); _root->SwitchToSection(); break; -#if !defined(DEATH_TARGET_EMSCRIPTEN) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) +#if !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) case Item::Quit: theApplication().Quit(); break; diff --git a/Sources/Shared/IO/BoundedFileStream.cpp b/Sources/Shared/IO/BoundedFileStream.cpp new file mode 100644 index 00000000..7f4e415b --- /dev/null +++ b/Sources/Shared/IO/BoundedFileStream.cpp @@ -0,0 +1,86 @@ +#include "BoundedFileStream.h" + +namespace Death { namespace IO { +//###==##====#=====--==~--~=~- --- -- - - - - + + BoundedFileStream::BoundedFileStream(const Containers::StringView path, std::uint64_t offset, std::uint32_t size) + : BoundedFileStream(Containers::String{path}, offset, size) + { + } + + BoundedFileStream::BoundedFileStream(Containers::String&& path, std::uint64_t offset, std::uint32_t size) + : _underlyingStream(path, FileAccess::Read), _offset(offset), _size(size) + { + _underlyingStream.Seek(static_cast(offset), SeekOrigin::Begin); + } + + void BoundedFileStream::Dispose() + { + _underlyingStream.Dispose(); + } + + std::int64_t BoundedFileStream::Seek(std::int64_t offset, SeekOrigin origin) + { + std::int64_t newPos; + switch (origin) { + case SeekOrigin::Begin: newPos = _offset + offset; break; + case SeekOrigin::Current: newPos = _underlyingStream.GetPosition() + offset; break; + case SeekOrigin::End: newPos = _offset + _size + offset; break; + default: return Stream::OutOfRange; + } + + if (newPos < static_cast(_offset) || newPos > static_cast(_offset + _size)) { + newPos = Stream::OutOfRange; + } else { + newPos = _underlyingStream.Seek(newPos, SeekOrigin::Begin); + if (newPos >= static_cast(_offset)) { + newPos -= _offset; + } + } + return newPos; + } + + std::int64_t BoundedFileStream::GetPosition() const + { + return _underlyingStream.GetPosition() - static_cast(_offset); + } + + std::int64_t BoundedFileStream::Read(void* destination, std::int64_t bytesToRead) + { + if (bytesToRead <= 0) { + return 0; + } + + DEATH_ASSERT(destination != nullptr, "destination is null", 0); + + std::int64_t pos = _underlyingStream.GetPosition() - _offset; + if (bytesToRead > _size - pos) { + bytesToRead = _size - pos; + } + + return _underlyingStream.Read(destination, bytesToRead); + } + + std::int64_t BoundedFileStream::Write(const void* source, std::int64_t bytesToWrite) + { + // Not supported + return Stream::Invalid; + } + + bool BoundedFileStream::Flush() + { + // Not supported + return true; + } + + bool BoundedFileStream::IsValid() + { + return _underlyingStream.IsValid(); + } + + std::int64_t BoundedFileStream::GetSize() const + { + return _size; + } + +}} \ No newline at end of file diff --git a/Sources/Shared/IO/BoundedFileStream.h b/Sources/Shared/IO/BoundedFileStream.h new file mode 100644 index 00000000..03604b2e --- /dev/null +++ b/Sources/Shared/IO/BoundedFileStream.h @@ -0,0 +1,32 @@ +#pragma once + +#include "FileStream.h" + +namespace Death { namespace IO { +//###==##====#=====--==~--~=~- --- -- - - - - + + class BoundedFileStream : public Stream + { + public: + BoundedFileStream(const Containers::StringView path, std::uint64_t offset, std::uint32_t size); + BoundedFileStream(Containers::String&& path, std::uint64_t offset, std::uint32_t size); + + BoundedFileStream(const BoundedFileStream&) = delete; + BoundedFileStream& operator=(const BoundedFileStream&) = delete; + + void Dispose() override; + std::int64_t Seek(std::int64_t offset, SeekOrigin origin) override; + std::int64_t GetPosition() const override; + std::int64_t Read(void* destination, std::int64_t bytesToRead) override; + std::int64_t Write(const void* source, std::int64_t bytesToWrite) override; + bool Flush() override; + bool IsValid() override; + std::int64_t GetSize() const override; + + private: + FileStream _underlyingStream; + std::uint64_t _offset; + std::uint64_t _size; + }; + +}} \ No newline at end of file diff --git a/Sources/Shared/IO/PakFile.cpp b/Sources/Shared/IO/PakFile.cpp index d624f5c1..71611a33 100644 --- a/Sources/Shared/IO/PakFile.cpp +++ b/Sources/Shared/IO/PakFile.cpp @@ -1,4 +1,5 @@ #include "PakFile.h" +#include "BoundedFileStream.h" #include "DeflateStream.h" #include "FileSystem.h" #include "../Containers/GrowableArray.h" @@ -11,104 +12,6 @@ using namespace Death::Containers; namespace Death { namespace IO { //###==##====#=====--==~--~=~- --- -- - - - - - class BoundedStream : public Stream - { - public: - BoundedStream(const String& path, std::uint64_t offset, std::uint32_t size); - - BoundedStream(const BoundedStream&) = delete; - BoundedStream& operator=(const BoundedStream&) = delete; - - void Dispose() override; - std::int64_t Seek(std::int64_t offset, SeekOrigin origin) override; - std::int64_t GetPosition() const override; - std::int64_t Read(void* destination, std::int64_t bytesToRead) override; - std::int64_t Write(const void* source, std::int64_t bytesToWrite) override; - bool Flush() override; - bool IsValid() override; - std::int64_t GetSize() const override; - - private: - FileStream _underlyingStream; - std::uint64_t _offset; - std::uint64_t _size; - }; - - BoundedStream::BoundedStream(const String& path, std::uint64_t offset, std::uint32_t size) - : _underlyingStream(path, FileAccess::Read), _offset(offset), _size(size) - { - _underlyingStream.Seek(static_cast(offset), SeekOrigin::Begin); - } - - void BoundedStream::Dispose() - { - _underlyingStream.Dispose(); - } - - std::int64_t BoundedStream::Seek(std::int64_t offset, SeekOrigin origin) - { - std::int64_t newPos; - switch (origin) { - case SeekOrigin::Begin: newPos = _offset + offset; break; - case SeekOrigin::Current: newPos = _underlyingStream.GetPosition() + offset; break; - case SeekOrigin::End: newPos = _offset + _size + offset; break; - default: return Stream::OutOfRange; - } - - if (newPos < static_cast(_offset) || newPos > static_cast(_offset + _size)) { - newPos = Stream::OutOfRange; - } else { - newPos = _underlyingStream.Seek(newPos, SeekOrigin::Begin); - if (newPos >= static_cast(_offset)) { - newPos -= _offset; - } - } - return newPos; - } - - std::int64_t BoundedStream::GetPosition() const - { - return _underlyingStream.GetPosition() - static_cast(_offset); - } - - std::int64_t BoundedStream::Read(void* destination, std::int64_t bytesToRead) - { - if (bytesToRead <= 0) { - return 0; - } - - DEATH_ASSERT(destination != nullptr, "destination is null", 0); - - std::int64_t pos = _underlyingStream.GetPosition() - _offset; - if (bytesToRead > _size - pos) { - bytesToRead = _size - pos; - } - - return _underlyingStream.Read(destination, bytesToRead); - } - - std::int64_t BoundedStream::Write(const void* source, std::int64_t bytesToWrite) - { - // Not supported - return Stream::Invalid; - } - - bool BoundedStream::Flush() - { - // Not supported - return true; - } - - bool BoundedStream::IsValid() - { - return _underlyingStream.IsValid(); - } - - std::int64_t BoundedStream::GetSize() const - { - return _size; - } - #if defined(WITH_ZLIB) || defined(WITH_MINIZ) class ZlibCompressedBoundedStream : public Stream @@ -129,7 +32,7 @@ namespace Death { namespace IO { std::int64_t GetSize() const override; private: - BoundedStream _underlyingStream; + BoundedFileStream _underlyingStream; DeflateStream _deflateStream; std::int64_t _uncompressedSize; }; @@ -311,7 +214,7 @@ namespace Death { namespace IO { #endif } - return std::make_unique(_path, foundItem->Offset, foundItem->UncompressedSize); + return std::make_unique(_path, foundItem->Offset, foundItem->UncompressedSize); } PakFile::Item* PakFile::FindItem(StringView path) diff --git a/Sources/nCine/Application.cpp b/Sources/nCine/Application.cpp index 2ea0f07d..3f153ca4 100644 --- a/Sources/nCine/Application.cpp +++ b/Sources/nCine/Application.cpp @@ -412,6 +412,11 @@ namespace nCine return ((!hasFocus_ && autoSuspension_) || isSuspended_); } + void Application::Quit() + { + shouldQuit_ = true; + } + void Application::PreInitCommon(std::unique_ptr appEventHandler) { #if defined(DEATH_TRACE) diff --git a/Sources/nCine/Application.h b/Sources/nCine/Application.h index ec434d20..0eb0088b 100644 --- a/Sources/nCine/Application.h +++ b/Sources/nCine/Application.h @@ -145,9 +145,8 @@ namespace nCine } /** @brief Raises the quit flag */ - inline void Quit() { - shouldQuit_ = true; - } + virtual void Quit(); + /** @brief Returns the quit flag value */ inline bool ShouldQuit() const { return shouldQuit_; diff --git a/Sources/nCine/Graphics/ImGuiDebugOverlay.cpp b/Sources/nCine/Graphics/ImGuiDebugOverlay.cpp index 209148f7..90ed6f21 100644 --- a/Sources/nCine/Graphics/ImGuiDebugOverlay.cpp +++ b/Sources/nCine/Graphics/ImGuiDebugOverlay.cpp @@ -319,7 +319,7 @@ namespace nCine theApplication().SetAutoSuspension(!disableAutoSuspension); /*ImGui::SameLine(); if (ImGui::Button("Quit")) { - theApplication().quit(); + theApplication().Quit(); }*/ guiConfigureGui(); diff --git a/Sources/nCine/MainApplication.cpp b/Sources/nCine/MainApplication.cpp index b6aa6e17..30f235f3 100644 --- a/Sources/nCine/MainApplication.cpp +++ b/Sources/nCine/MainApplication.cpp @@ -106,6 +106,16 @@ namespace nCine return EXIT_SUCCESS; } + void MainApplication::Quit() + { + Application::Quit(); + +#if defined(DEATH_TARGET_EMSCRIPTEN) + // `window.close()` usually works only in PWA/standalone environment + emscripten_run_script("window.close();"); +#endif + } + void MainApplication::Init(std::unique_ptr(*createAppEventHandler)(), int argc, NativeArgument* argv) { ZoneScopedC(0x81A861); diff --git a/Sources/nCine/MainApplication.h b/Sources/nCine/MainApplication.h index 39fc5aa0..d6f2e64d 100644 --- a/Sources/nCine/MainApplication.h +++ b/Sources/nCine/MainApplication.h @@ -20,6 +20,8 @@ namespace nCine /** @brief Entry point method to be called in the `main()`/`wWinMain()` function */ static int Run(std::unique_ptr(*createAppEventHandler)(), int argc, NativeArgument* argv); + void Quit() override; + private: bool wasSuspended_; diff --git a/cmake/ncine_headers.cmake b/cmake/ncine_headers.cmake index 8988ade1..332f24d1 100644 --- a/cmake/ncine_headers.cmake +++ b/cmake/ncine_headers.cmake @@ -35,6 +35,7 @@ set(HEADERS ${NCINE_SOURCE_DIR}/Shared/Core/ITraceSink.h ${NCINE_SOURCE_DIR}/Shared/Core/Logger.h ${NCINE_SOURCE_DIR}/Shared/IO/AndroidAssetStream.h + ${NCINE_SOURCE_DIR}/Shared/IO/BoundedFileStream.h ${NCINE_SOURCE_DIR}/Shared/IO/DeflateStream.h ${NCINE_SOURCE_DIR}/Shared/IO/FileAccess.h ${NCINE_SOURCE_DIR}/Shared/IO/FileStream.h diff --git a/cmake/ncine_sources.cmake b/cmake/ncine_sources.cmake index f4436bb6..38574871 100644 --- a/cmake/ncine_sources.cmake +++ b/cmake/ncine_sources.cmake @@ -9,6 +9,7 @@ set(SOURCES ${NCINE_SOURCE_DIR}/Shared/Containers/StringView.cpp ${NCINE_SOURCE_DIR}/Shared/Core/Logger.cpp ${NCINE_SOURCE_DIR}/Shared/IO/AndroidAssetStream.cpp + ${NCINE_SOURCE_DIR}/Shared/IO/BoundedFileStream.cpp ${NCINE_SOURCE_DIR}/Shared/IO/DeflateStream.cpp ${NCINE_SOURCE_DIR}/Shared/IO/FileStream.cpp ${NCINE_SOURCE_DIR}/Shared/IO/FileSystem.cpp