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