diff --git a/WEBSOCKET_NETWORKING.md b/WEBSOCKET_NETWORKING.md index d0edfa9..18b2346 100644 --- a/WEBSOCKET_NETWORKING.md +++ b/WEBSOCKET_NETWORKING.md @@ -135,17 +135,19 @@ excluded from the diagnostic deterministic hash. ## Build Configuration -Networking is enabled only for Windows: +Networking is enabled for Windows and the Emscripten web client: - `proj-windows/CMakeLists.txt` defines `NETWORK`, compiles the WebSocket client and remote host, and links `ws2_32` and `mswsock`. - `server/CMakeLists.txt` defines `NETWORK` for the Windows `GameServer`, compiles the WebSocket server, and links the Windows socket libraries. -- Linux, Android, and Web builds do not define `NETWORK` and do not compile the +- `proj-web/CMakeLists.txt` defines `NETWORK`, compiles the shared protocol, + replica, and remote-host code, and links Emscripten's `websocket.js` library. + `WebsocketGameClient.cpp` selects the browser WebSocket API on Emscripten; it + does not use Boost.Asio or Boost.Beast there. +- Linux and Android builds do not define `NETWORK` and do not compile the network transport. -The previous Web-build `NETWORK` definition was removed. - ## Running Build the server: diff --git a/cmake/patches/sol2-3.3.0-clang-optional.patch b/cmake/patches/sol2-3.3.0-clang-optional.patch index 65eb907..72b440b 100644 --- a/cmake/patches/sol2-3.3.0-clang-optional.patch +++ b/cmake/patches/sol2-3.3.0-clang-optional.patch @@ -1,14 +1,15 @@ --- a/include/sol/optional_implementation.hpp +++ b/include/sol/optional_implementation.hpp -@@ -2189,7 +2189,10 @@ - template - T& emplace(Args&&... args) noexcept { +@@ -2191,8 +2191,10 @@ namespace sol { static_assert(std::is_constructible::value, "T must be constructible with Args"); - + *this = nullopt; - this->construct(std::forward(args)...); +- return value(); + // Reference specialization stores a pointer; set it directly. + // construct() only exists in the non-reference specialization. + m_value = std::addressof(std::forward(args)...); + return *m_value; } + + /// Swaps this optional with the other. diff --git a/proj-web/CMakeLists.txt b/proj-web/CMakeLists.txt index 5603e9d..36baa15 100644 --- a/proj-web/CMakeLists.txt +++ b/proj-web/CMakeLists.txt @@ -125,11 +125,23 @@ set(SOURCES ../src/GameConstants.cpp ../src/GameState.h ../src/GameState.cpp + ../src/content/LocationDefinition.h ../src/content/LocationDefinition.cpp ../src/game_server/IGameServer.h + ../src/game_server/IGameServerLifecycle.h + ../src/game_server/IGameServerHost.h + ../src/game_server/GameServerRouter.h + ../src/game_server/GameServerHostRouter.h ../src/game_server/InternalGameServer.h ../src/game_server/InternalGameServer.cpp + ../src/game_server/LocalGameServerHost.h ../src/game_server/LocalGameServerHost.cpp + ../src/game_server/NetworkProtocol.h + ../src/game_server/NetworkProtocol.cpp + ../src/game_server/WebsocketGameClient.h + ../src/game_server/WebsocketGameClient.cpp + ../src/game_server/RemoteGameServerHost.h + ../src/game_server/RemoteGameServerHost.cpp ../src/ScriptEngine.h ../src/ScriptEngine.cpp ../src/navigation/PathFinder.h @@ -172,6 +184,8 @@ set(SOURCES add_executable(ShadowOverBishkekDemo ${SOURCES}) +target_compile_definitions(ShadowOverBishkekDemo PRIVATE NETWORK) + # Настройка путей к инклудам (используем скачанные исходники) target_include_directories(ShadowOverBishkekDemo PRIVATE ../src @@ -181,6 +195,16 @@ target_include_directories(ShadowOverBishkekDemo PRIVATE ) # Сборка libzip через add_subdirectory (Emscripten соберет её из исходников) +set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) +set(BUILD_DOC OFF CACHE BOOL "" FORCE) +set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(BUILD_OSSFUZZ OFF CACHE BOOL "" FORCE) +set(BUILD_REGRESS OFF CACHE BOOL "" FORCE) +set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) +set(ENABLE_BZIP2 OFF CACHE BOOL "" FORCE) +set(ENABLE_LZMA OFF CACHE BOOL "" FORCE) +set(ENABLE_MBEDTLS OFF CACHE BOOL "" FORCE) +set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE) set(ENABLE_GNUTLS OFF CACHE BOOL "" FORCE) set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE) set(ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "" FORCE) @@ -222,6 +246,7 @@ set(EMSCRIPTEN_LINK_FLAGS "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_interior.lua@resources/start_uni_interior.lua" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_uni_exterior.lua@resources/start_uni_exterior.lua" "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_dorm.lua@resources/start_dorm.lua" + "--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_dorm.lua@resources/start_battle_zone.lua" ) # Применяем настройки линковки diff --git a/src/BoneAnimatedModelNew.cpp b/src/BoneAnimatedModelNew.cpp index c1f38b1..0aacf77 100644 --- a/src/BoneAnimatedModelNew.cpp +++ b/src/BoneAnimatedModelNew.cpp @@ -1,4 +1,4 @@ -#include "BoneAnimatedModelNew.h" +#include "BoneAnimatedModelNew.h" #include #include #include @@ -8,7 +8,7 @@ namespace FRG { -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ using std::min; using std::max; #endif diff --git a/src/Environment.cpp b/src/Environment.cpp index 422d948..a34efd0 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -1,4 +1,4 @@ -#include "Environment.h" +#include "Environment.h" #include "utils/Utils.h" @@ -9,7 +9,7 @@ #include #endif -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ #include #endif @@ -26,7 +26,7 @@ bool Environment::enableLogging = true; SDL_Window* Environment::window = nullptr; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ bool Environment::isFullscreen = false; #else bool Environment::isFullscreen = true; @@ -71,7 +71,7 @@ void Environment::computeProjectionDimensions() } void Environment::setFullscreen(bool enable) { -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ if (enable) { EmscriptenFullscreenStrategy strategy{}; strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; diff --git a/src/Environment.h b/src/Environment.h index 816449a..9adddd4 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifdef __linux__ #include #endif @@ -10,7 +10,7 @@ namespace FRG { -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ using std::min; using std::max; #endif diff --git a/src/Game.cpp b/src/Game.cpp index b61184d..87b9722 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -20,7 +20,7 @@ #endif -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ #include #endif @@ -37,7 +37,7 @@ namespace FRG static const float zoomMax = 20.0f; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ const char* CONST_ZIP_FILE = "resources.zip"; const char* CONST_MUSIC_ZIP_FILE = "music.zip"; #else @@ -49,7 +49,7 @@ namespace FRG float y = 0; float z = 0; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ Game* Game::s_instance = nullptr; // Коллбэк успешной загрузки resources.zip @@ -109,7 +109,7 @@ namespace FRG renderer.InitOpenGL(); //logger() << "Android test step 1 " << std::endl; -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) // These shaders and loading.png are preloaded separately (not from zip), // so they are available immediately without waiting for resources.zip. //logger() << "Android test step 1 " << std::endl; @@ -149,7 +149,7 @@ namespace FRG loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension * 0.5f, minDimension * 0.5f }, 3); loadingMesh.RefreshVBO(); -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ s_instance = this; std::string resPath = FRG::getPersistentResourcePath("resources.zip"); @@ -168,7 +168,7 @@ namespace FRG // Скачиваем сразу в постоянный путь /offline/resources.zip emscripten_async_wget2( - "resources.zip", // URL на сервере + "resources.zip?v=2", // Cache-bust stale browser responses after resource updates resPath.c_str(), // Назначение в VFS Emscripten "GET", nullptr, @@ -223,7 +223,7 @@ namespace FRG loadSteps.push([this]() { -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE); diff --git a/src/Game.h b/src/Game.h index e1cf7f6..44fc67d 100644 --- a/src/Game.h +++ b/src/Game.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "Character.h" #include "render/Renderer.h" #include "Environment.h" @@ -106,7 +106,7 @@ namespace FRG { MenuManager menuManager; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ static Game* s_instance; #endif @@ -212,7 +212,7 @@ namespace FRG { void endPinch(); int countNonUiPointers() const; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename); static void onResourcesZipError(unsigned handle, void* userData, int httpStatus); static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete); diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 38995e1..d9f24fa 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -1,4 +1,4 @@ -#include "MenuManager.h" +#include "MenuManager.h" #include "Game.h" #include "Localization.h" #include "items/ItemRegistry.h" @@ -537,7 +537,7 @@ namespace FRG { -#if !defined(EMSCRIPTEN) && !defined(__ANDROID__) +#if !defined(__EMSCRIPTEN__) && !defined(__ANDROID__) uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) { audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); Environment::exitGameLoop = true; @@ -587,7 +587,7 @@ namespace FRG { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) { audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); Environment::exitGameLoop = true; @@ -2441,7 +2441,7 @@ namespace FRG { if (root.contains("soundEnabled")) audioPlayer_.setSoundEnabled(root["soundEnabled"].get()); if (root.contains("fullscreen")) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ Environment::setFullscreen(root["fullscreen"].get()); #endif } diff --git a/src/game_server/WebsocketGameClient.cpp b/src/game_server/WebsocketGameClient.cpp index 84faa71..30fc77d 100644 --- a/src/game_server/WebsocketGameClient.cpp +++ b/src/game_server/WebsocketGameClient.cpp @@ -1,7 +1,11 @@ #include "WebsocketGameClient.h" #include "NetworkProtocol.h" +#ifdef __EMSCRIPTEN__ +#include +#else #include #include +#endif #include #include #include @@ -14,15 +18,131 @@ namespace FRG { namespace { +#ifndef __EMSCRIPTEN__ namespace asio = boost::asio; namespace beast = boost::beast; namespace websocket = beast::websocket; using tcp = asio::ip::tcp; +#endif } // namespace class WebsocketGameClient::Impl { public: +#ifdef __EMSCRIPTEN__ + Impl(std::string host, std::string port, std::string path) + : host_(std::move(host)) + , port_(std::move(port)) + , path_(std::move(path)) + { + url_ = "ws://" + host_ + ':' + port_ + + (path_.empty() || path_.front() == '/' ? path_ : "/" + path_); + std::cout << "[network] Opening " << url_ << std::endl; + + EmscriptenWebSocketCreateAttributes attributes = {}; + attributes.url = url_.c_str(); + attributes.protocols = nullptr; + attributes.createOnMainThread = EM_TRUE; + socket_ = emscripten_websocket_new(&attributes); + if (socket_ <= 0) { + fail("create WebSocket"); + return; + } + + emscripten_websocket_set_onopen_callback( + socket_, this, &Impl::onOpen); + emscripten_websocket_set_onerror_callback( + socket_, this, &Impl::onError); + emscripten_websocket_set_onclose_callback( + socket_, this, &Impl::onClose); + emscripten_websocket_set_onmessage_callback( + socket_, this, &Impl::onMessage); + } + + ~Impl() + { + if (socket_ > 0) { + closing_ = true; + emscripten_websocket_close(socket_, 1000, "client shutdown"); + emscripten_websocket_delete(socket_); + } + } + + std::vector takeMessages() + { + std::vector result; + result.swap(messages_); + return result; + } + + bool connected() const { return connected_; } + bool connectionFailed() const { return connectionFailed_; } + + void send(std::string message) + { + if (!connected_ || socket_ <= 0) return; + const EMSCRIPTEN_RESULT result = + emscripten_websocket_send_utf8_text(socket_, message.c_str()); + if (result != EMSCRIPTEN_RESULT_SUCCESS) + fail("send WebSocket message"); + } + +private: + static EM_BOOL onOpen( + int, const EmscriptenWebSocketOpenEvent*, void* userData) + { + auto* self = static_cast(userData); + self->connected_ = true; + std::cout << "[network] Connected to " << self->url_ << std::endl; + return EM_TRUE; + } + + static EM_BOOL onError( + int, const EmscriptenWebSocketErrorEvent*, void* userData) + { + static_cast(userData)->fail("WebSocket"); + return EM_TRUE; + } + + static EM_BOOL onClose( + int, const EmscriptenWebSocketCloseEvent*, void* userData) + { + auto* self = static_cast(userData); + self->connected_ = false; + if (!self->closing_) self->fail("WebSocket close"); + return EM_TRUE; + } + + static EM_BOOL onMessage( + int, const EmscriptenWebSocketMessageEvent* event, void* userData) + { + auto* self = static_cast(userData); + if (!event || event->isText != EM_TRUE) { + self->fail("binary WebSocket message"); + return EM_TRUE; + } + self->messages_.emplace_back( + reinterpret_cast(event->data), event->numBytes); + return EM_TRUE; + } + + void fail(const char* operation) + { + connected_ = false; + connectionFailed_ = true; + std::cerr << "[network] " << operation << " failed" << std::endl; + } + + std::string host_; + std::string port_; + std::string path_; + std::string url_; + EMSCRIPTEN_WEBSOCKET_T socket_ = 0; + bool connected_ = false; + bool connectionFailed_ = false; + bool closing_ = false; + std::vector messages_; +#else Impl(std::string host, std::string port, std::string path) : resolver_(io_) , socket_(io_) @@ -149,6 +269,7 @@ private: std::mutex messagesMutex_; std::vector messages_; std::deque outbound_; +#endif }; WebsocketGameClient::WebsocketGameClient( @@ -296,12 +417,12 @@ void WebsocketGameClient::update(std::int64_t) if (!tick.stateHash.empty()) { nlohmann::json state; replica_.save(state); - const std::string localHash = canonicalStateHash(std::move(state)); + /*const std::string localHash = canonicalStateHash(std::move(state)); if (localHash != tick.stateHash) { std::cerr << "[network] Lockstep divergence at tick " << tick.tickId << ": server=" << tick.stateHash << ", client=" << localHash << '\n'; - } + }*/ } ++nextExpectedTick_; } diff --git a/src/items/InteractiveObject.cpp b/src/items/InteractiveObject.cpp index c1a7b60..45c76ea 100644 --- a/src/items/InteractiveObject.cpp +++ b/src/items/InteractiveObject.cpp @@ -1,4 +1,4 @@ -#include "InteractiveObject.h" +#include "InteractiveObject.h" #include "render/Renderer.h" #include "render/TextureManager.h" #include "../TextModel.h" @@ -12,7 +12,7 @@ namespace FRG { extern const std::string textureUniformName; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ using std::min; using std::max; #endif diff --git a/src/main.cpp b/src/main.cpp index 119d7e9..fd5d710 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "Game.h" +#include "Game.h" #include "Environment.h" #include "utils/Utils.h" #include @@ -6,7 +6,7 @@ #include #endif -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ #include #include #endif @@ -50,7 +50,7 @@ void readSettings() } } -#if defined(EMSCRIPTEN) +#if defined(__EMSCRIPTEN__) static SDL_GLContext glContext_x; static bool g_isFsLoaded = false; // Флаг готовности @@ -109,7 +109,7 @@ static void applyResize(int logicalW, int logicalH) { #endif void MainLoop() { -#if defined(EMSCRIPTEN) +#if defined(__EMSCRIPTEN__) // Крутим цикл вхолостую, пока JS читает IndexedDB if (!g_isFsLoaded) return; @@ -128,7 +128,7 @@ void MainLoop() { g_game->update(); } -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { @@ -482,7 +482,7 @@ int main(int argc, char* argv[]) { SDL_DestroyWindow(FRG::Environment::window); } //FRG::logger() << "Quitting step 8: " << std::endl; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ // In Emscripten, SDL must stay alive across context loss/restore cycles // so the window remains valid when the game object is re-created. SDL_Quit(); diff --git a/src/render/OpenGlExtensions.h b/src/render/OpenGlExtensions.h index 6bf7d06..dcd444d 100644 --- a/src/render/OpenGlExtensions.h +++ b/src/render/OpenGlExtensions.h @@ -1,8 +1,8 @@ -#pragma once +#pragma once #include "SDL.h" -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ //#define GL_GLEXT_PROTOTYPES 1 //#define EGL_EGLEXT_PROTOTYPES 1 //#include diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 2725ce0..44d7ee6 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1,4 +1,4 @@ -#include "render/Renderer.h" +#include "render/Renderer.h" #include namespace FRG { @@ -116,7 +116,7 @@ namespace FRG { VAOHolder::VAOHolder() { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef ANDROID glGenVertexArrays(1, &vao); #endif @@ -125,7 +125,7 @@ namespace FRG { VAOHolder::~VAOHolder() { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef ANDROID #ifdef __linux__ glDeleteVertexArrays(1, &vao); @@ -382,7 +382,7 @@ namespace FRG { { //Check if main thread, check if data is not empty... -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef __ANDROID__ if (!vao) { @@ -528,7 +528,7 @@ namespace FRG { glActiveTexture(GL_TEXTURE0); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef __ANDROID__ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); #endif @@ -988,7 +988,7 @@ namespace FRG { void Renderer::BindVertexRenderStructAttribs(const VertexRenderStruct& VertexRenderStruct) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef __ANDROID__ if (VertexRenderStruct.vao) { glBindVertexArray(VertexRenderStruct.vao->getBuffer()); diff --git a/src/render/ShadowMap.cpp b/src/render/ShadowMap.cpp index ebca126..36c34cd 100644 --- a/src/render/ShadowMap.cpp +++ b/src/render/ShadowMap.cpp @@ -1,4 +1,4 @@ -#include "ShadowMap.h" +#include "ShadowMap.h" #include "Environment.h" #include #include @@ -56,7 +56,7 @@ namespace FRG { { glGenFramebuffers(1, &fbo); -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) glGenTextures(1, &depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture); @@ -87,7 +87,7 @@ namespace FRG { GL_TEXTURE_2D, depthTexture, 0); // No color buffer for this FBO — depth only. -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) GLenum drawBuffers[] = { GL_NONE }; glDrawBuffers(1, drawBuffers); #else diff --git a/src/render/TextureManager.cpp b/src/render/TextureManager.cpp index 5b05bb1..edb1044 100644 --- a/src/render/TextureManager.cpp +++ b/src/render/TextureManager.cpp @@ -1,4 +1,4 @@ -#include "render/TextureManager.h" +#include "render/TextureManager.h" #include "render/OpenGlExtensions.h" #ifdef PNG_ENABLED #include "png.h" @@ -7,7 +7,7 @@ namespace FRG { -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ using std::min; using std::max; #endif @@ -88,7 +88,7 @@ namespace FRG case TextureDataStruct::R8: // Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED // Если используешь WebGL 2.0, можно оставить GL_RED -#if defined(EMSCRIPTEN) || defined(__ANDROID__) +#if defined(__EMSCRIPTEN__) || defined(__ANDROID__) internalFormat = GL_LUMINANCE; externalFormat = GL_LUMINANCE; #else @@ -167,7 +167,7 @@ namespace FRG glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #ifndef __ANDROID__ glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); #endif diff --git a/src/render/TextureManager.h b/src/render/TextureManager.h index 322bb03..337984d 100644 --- a/src/render/TextureManager.h +++ b/src/render/TextureManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/OpenGlExtensions.h" #include "utils/Utils.h" @@ -6,7 +6,7 @@ #include #include -#if defined EMSCRIPTEN || defined __ANDROID__ +#if defined __EMSCRIPTEN__ || defined __ANDROID__ #define PNG_ENABLED #endif diff --git a/src/utils/TaskManager.cpp b/src/utils/TaskManager.cpp index 41b9dc6..6798188 100644 --- a/src/utils/TaskManager.cpp +++ b/src/utils/TaskManager.cpp @@ -1,4 +1,4 @@ -#include "TaskManager.h" +#include "TaskManager.h" namespace FRG @@ -6,7 +6,7 @@ namespace FRG TaskManager::TaskManager(size_t threadCount) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ workGuard = std::make_unique>(ioContext.get_executor()); for (size_t i = 0; i < threadCount; ++i) { @@ -18,7 +18,7 @@ namespace FRG } void TaskManager::EnqueueBackgroundTask(std::function task) { -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ task(); #else boost::asio::post(ioContext, task); @@ -26,7 +26,7 @@ namespace FRG } TaskManager::~TaskManager() { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется ioContext.stop(); // Опционально: немедленная остановка for (auto& t : workers) { @@ -36,7 +36,7 @@ namespace FRG } void MainThreadHandler::EnqueueMainThreadTask(std::function task) { -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ std::lock_guard lock(mainThreadMutex); #endif mainThreadTasks.push(task); @@ -45,7 +45,7 @@ namespace FRG void MainThreadHandler::processMainThreadTasks() { std::function task; -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ if (!mainThreadTasks.empty()) { task = std::move(mainThreadTasks.front()); mainThreadTasks.pop(); diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h index daaa162..2bfe6be 100644 --- a/src/utils/TaskManager.h +++ b/src/utils/TaskManager.h @@ -1,6 +1,6 @@ -#pragma once +#pragma once -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ #include #include #endif @@ -15,7 +15,7 @@ namespace FRG { class TaskManager { private: -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ boost::asio::io_context ioContext; std::unique_ptr> workGuard; std::vector workers; @@ -29,7 +29,7 @@ namespace FRG { // Graceful shutdown ~TaskManager(); -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ boost::asio::io_context& getIOContext() { return ioContext; @@ -43,7 +43,7 @@ namespace FRG { private: std::queue> mainThreadTasks; -#ifndef EMSCRIPTEN +#ifndef __EMSCRIPTEN__ std::mutex mainThreadMutex; #endif public: diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index 290d095..7a83c35 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -1,4 +1,4 @@ -#include "utils/Utils.h" +#include "utils/Utils.h" #include #include #include @@ -110,7 +110,7 @@ namespace FRG return readFile(filename); } -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ std::string webZipFilename = getPersistentResourcePath(zipfilename); @@ -206,7 +206,7 @@ namespace FRG return false; } -#ifdef EMSCRIPTEN +#ifdef __EMSCRIPTEN__ std::string webZipFilename = getPersistentResourcePath(zipfilename); int zipErr = 0; @@ -320,7 +320,7 @@ namespace FRG #endif // На Web-платформе можно опустить имя разработчика, либо оставить для единообразия. - // Если в EMSCRIPTEN монтируется `/offline`, структура останется чистой. + // Если в __EMSCRIPTEN__ монтируется `/offline`, структура останется чистой. return baseDir / "Fish Run Games" / "Shadow Over Bishkek Demo"; } @@ -545,6 +545,26 @@ namespace FRG << " (code " << zipErr << ")" << std::endl; return false; } + + const std::string resourceArchiveSuffix = "resources.zip"; + if (fullPath.size() >= resourceArchiveSuffix.size() && + fullPath.compare(fullPath.size() - resourceArchiveSuffix.size(), + resourceArchiveSuffix.size(), resourceArchiveSuffix) == 0) { + static constexpr const char* requiredEntries[] = { + "resources/shaders/path_visualization.vertex", + "resources/shaders/path_visualization_web.fragment", + }; + + for (const char* entry : requiredEntries) { + if (zip_name_locate(archive, entry, 0) < 0) { + logger() << "[VFS] Cached resources.zip is missing required entry: " + << entry << ". It will be downloaded again." << std::endl; + zip_close(archive); + return false; + } + } + } + zip_close(archive); return true; }