Adapted for web

This commit is contained in:
Vladislav Khorev committed 2026-11-09 17:20:04 +06:00
commit 3fe21230cc
20 files changed
+240 -71

No files matched your search

+6 -4
View File
@@ -135,17 +135,19 @@ excluded from the diagnostic deterministic hash.
## Build Configuration ## 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 - `proj-windows/CMakeLists.txt` defines `NETWORK`, compiles the WebSocket client
and remote host, and links `ws2_32` and `mswsock`. and remote host, and links `ws2_32` and `mswsock`.
- `server/CMakeLists.txt` defines `NETWORK` for the Windows `GameServer`, compiles - `server/CMakeLists.txt` defines `NETWORK` for the Windows `GameServer`, compiles
the WebSocket server, and links the Windows socket libraries. 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. network transport.
The previous Web-build `NETWORK` definition was removed.
## Running ## Running
Build the server: Build the server:
@@ -1,14 +1,15 @@
--- a/include/sol/optional_implementation.hpp --- a/include/sol/optional_implementation.hpp
+++ b/include/sol/optional_implementation.hpp +++ b/include/sol/optional_implementation.hpp
@@ -2189,7 +2189,10 @@ @@ -2191,8 +2191,10 @@ namespace sol {
template <class... Args>
T& emplace(Args&&... args) noexcept {
static_assert(std::is_constructible<T, Args&&...>::value, "T must be constructible with Args"); static_assert(std::is_constructible<T, Args&&...>::value, "T must be constructible with Args");
*this = nullopt; *this = nullopt;
- this->construct(std::forward<Args>(args)...); - this->construct(std::forward<Args>(args)...);
- return value();
+ // Reference specialization stores a pointer; set it directly. + // Reference specialization stores a pointer; set it directly.
+ // construct() only exists in the non-reference specialization. + // construct() only exists in the non-reference specialization.
+ m_value = std::addressof(std::forward<Args>(args)...); + m_value = std::addressof(std::forward<Args>(args)...);
+ return *m_value; + return *m_value;
} }
/// Swaps this optional with the other.
+25
View File
@@ -125,11 +125,23 @@ set(SOURCES
../src/GameConstants.cpp ../src/GameConstants.cpp
../src/GameState.h ../src/GameState.h
../src/GameState.cpp ../src/GameState.cpp
../src/content/LocationDefinition.h
../src/content/LocationDefinition.cpp ../src/content/LocationDefinition.cpp
../src/game_server/IGameServer.h ../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.h
../src/game_server/InternalGameServer.cpp ../src/game_server/InternalGameServer.cpp
../src/game_server/LocalGameServerHost.h
../src/game_server/LocalGameServerHost.cpp ../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.h
../src/ScriptEngine.cpp ../src/ScriptEngine.cpp
../src/navigation/PathFinder.h ../src/navigation/PathFinder.h
@@ -172,6 +184,8 @@ set(SOURCES
add_executable(ShadowOverBishkekDemo ${SOURCES}) add_executable(ShadowOverBishkekDemo ${SOURCES})
target_compile_definitions(ShadowOverBishkekDemo PRIVATE NETWORK)
# Настройка путей к инклудам (используем скачанные исходники) # Настройка путей к инклудам (используем скачанные исходники)
target_include_directories(ShadowOverBishkekDemo PRIVATE target_include_directories(ShadowOverBishkekDemo PRIVATE
../src ../src
@@ -181,6 +195,16 @@ target_include_directories(ShadowOverBishkekDemo PRIVATE
) )
# Сборка libzip через add_subdirectory (Emscripten соберет её из исходников) # Сборка 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_GNUTLS OFF CACHE BOOL "" FORCE)
set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE) set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE)
set(ENABLE_WINDOWS_CRYPTO 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_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_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_dorm.lua"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/start_dorm.lua@resources/start_battle_zone.lua"
) )
# Применяем настройки линковки # Применяем настройки линковки
+2 -2
View File
@@ -1,4 +1,4 @@
#include "BoneAnimatedModelNew.h" #include "BoneAnimatedModelNew.h"
#include <regex> #include <regex>
#include <string> #include <string>
#include <fstream> #include <fstream>
@@ -8,7 +8,7 @@
namespace FRG namespace FRG
{ {
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
using std::min; using std::min;
using std::max; using std::max;
#endif #endif
+4 -4
View File
@@ -1,4 +1,4 @@
#include "Environment.h" #include "Environment.h"
#include "utils/Utils.h" #include "utils/Utils.h"
@@ -9,7 +9,7 @@
#include <GL/gl.h> #include <GL/gl.h>
#endif #endif
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
#include <emscripten/html5.h> #include <emscripten/html5.h>
#endif #endif
@@ -26,7 +26,7 @@ bool Environment::enableLogging = true;
SDL_Window* Environment::window = nullptr; SDL_Window* Environment::window = nullptr;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
bool Environment::isFullscreen = false; bool Environment::isFullscreen = false;
#else #else
bool Environment::isFullscreen = true; bool Environment::isFullscreen = true;
@@ -71,7 +71,7 @@ void Environment::computeProjectionDimensions()
} }
void Environment::setFullscreen(bool enable) { void Environment::setFullscreen(bool enable) {
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
if (enable) { if (enable) {
EmscriptenFullscreenStrategy strategy{}; EmscriptenFullscreenStrategy strategy{};
strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH; strategy.scaleMode = EMSCRIPTEN_FULLSCREEN_SCALE_STRETCH;
+2 -2
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#ifdef __linux__ #ifdef __linux__
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#endif #endif
@@ -10,7 +10,7 @@
namespace FRG { namespace FRG {
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
using std::min; using std::min;
using std::max; using std::max;
#endif #endif
+7 -7
View File
@@ -20,7 +20,7 @@
#endif #endif
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
#include <emscripten.h> #include <emscripten.h>
#endif #endif
@@ -37,7 +37,7 @@ namespace FRG
static const float zoomMax = 20.0f; static const float zoomMax = 20.0f;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
const char* CONST_ZIP_FILE = "resources.zip"; const char* CONST_ZIP_FILE = "resources.zip";
const char* CONST_MUSIC_ZIP_FILE = "music.zip"; const char* CONST_MUSIC_ZIP_FILE = "music.zip";
#else #else
@@ -49,7 +49,7 @@ namespace FRG
float y = 0; float y = 0;
float z = 0; float z = 0;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
Game* Game::s_instance = nullptr; Game* Game::s_instance = nullptr;
// Коллбэк успешной загрузки resources.zip // Коллбэк успешной загрузки resources.zip
@@ -109,7 +109,7 @@ namespace FRG
renderer.InitOpenGL(); renderer.InitOpenGL();
//logger() << "Android test step 1 " << std::endl; //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), // These shaders and loading.png are preloaded separately (not from zip),
// so they are available immediately without waiting for resources.zip. // so they are available immediately without waiting for resources.zip.
//logger() << "Android test step 1 " << std::endl; //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.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension * 0.5f, minDimension * 0.5f }, 3);
loadingMesh.RefreshVBO(); loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
s_instance = this; s_instance = this;
std::string resPath = FRG::getPersistentResourcePath("resources.zip"); std::string resPath = FRG::getPersistentResourcePath("resources.zip");
@@ -168,7 +168,7 @@ namespace FRG
// Скачиваем сразу в постоянный путь /offline/resources.zip // Скачиваем сразу в постоянный путь /offline/resources.zip
emscripten_async_wget2( emscripten_async_wget2(
"resources.zip", // URL на сервере "resources.zip?v=2", // Cache-bust stale browser responses after resource updates
resPath.c_str(), // Назначение в VFS Emscripten resPath.c_str(), // Назначение в VFS Emscripten
"GET", "GET",
nullptr, nullptr,
@@ -223,7 +223,7 @@ namespace FRG
loadSteps.push([this]() { 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("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("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); renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE);
+3 -3
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "Character.h" #include "Character.h"
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
@@ -106,7 +106,7 @@ namespace FRG {
MenuManager menuManager; MenuManager menuManager;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
static Game* s_instance; static Game* s_instance;
#endif #endif
@@ -212,7 +212,7 @@ namespace FRG {
void endPinch(); void endPinch();
int countNonUiPointers() const; int countNonUiPointers() const;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename); static void onResourcesZipLoaded(unsigned handle, void* userData, const char* filename);
static void onResourcesZipError(unsigned handle, void* userData, int httpStatus); static void onResourcesZipError(unsigned handle, void* userData, int httpStatus);
static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete); static void onResourcesZipProgress(unsigned handle, void* userData, int percentComplete);
+4 -4
View File
@@ -1,4 +1,4 @@
#include "MenuManager.h" #include "MenuManager.h"
#include "Game.h" #include "Game.h"
#include "Localization.h" #include "Localization.h"
#include "items/ItemRegistry.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&) { uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
@@ -587,7 +587,7 @@ namespace FRG {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) { uiManager.setTextButtonCallback("menuExitButton", [this](const std::string&) {
audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg"); audioPlayer_.playSoundAsync("resources/audio/751089__smallconfusion__mechanical-plastic-click-11.ogg");
Environment::exitGameLoop = true; Environment::exitGameLoop = true;
@@ -2441,7 +2441,7 @@ namespace FRG {
if (root.contains("soundEnabled")) if (root.contains("soundEnabled"))
audioPlayer_.setSoundEnabled(root["soundEnabled"].get<bool>()); audioPlayer_.setSoundEnabled(root["soundEnabled"].get<bool>());
if (root.contains("fullscreen")) { if (root.contains("fullscreen")) {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
Environment::setFullscreen(root["fullscreen"].get<bool>()); Environment::setFullscreen(root["fullscreen"].get<bool>());
#endif #endif
} }
+123 -2
View File
@@ -1,7 +1,11 @@
#include "WebsocketGameClient.h" #include "WebsocketGameClient.h"
#include "NetworkProtocol.h" #include "NetworkProtocol.h"
#ifdef __EMSCRIPTEN__
#include <emscripten/websocket.h>
#else
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <boost/beast.hpp> #include <boost/beast.hpp>
#endif
#include <atomic> #include <atomic>
#include <deque> #include <deque>
#include <iostream> #include <iostream>
@@ -14,15 +18,131 @@
namespace FRG { namespace FRG {
namespace { namespace {
#ifndef __EMSCRIPTEN__
namespace asio = boost::asio; namespace asio = boost::asio;
namespace beast = boost::beast; namespace beast = boost::beast;
namespace websocket = beast::websocket; namespace websocket = beast::websocket;
using tcp = asio::ip::tcp; using tcp = asio::ip::tcp;
#endif
} // namespace } // namespace
class WebsocketGameClient::Impl { class WebsocketGameClient::Impl {
public: 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<std::string> takeMessages()
{
std::vector<std::string> 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<Impl*>(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<Impl*>(userData)->fail("WebSocket");
return EM_TRUE;
}
static EM_BOOL onClose(
int, const EmscriptenWebSocketCloseEvent*, void* userData)
{
auto* self = static_cast<Impl*>(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<Impl*>(userData);
if (!event || event->isText != EM_TRUE) {
self->fail("binary WebSocket message");
return EM_TRUE;
}
self->messages_.emplace_back(
reinterpret_cast<const char*>(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<std::string> messages_;
#else
Impl(std::string host, std::string port, std::string path) Impl(std::string host, std::string port, std::string path)
: resolver_(io_) : resolver_(io_)
, socket_(io_) , socket_(io_)
@@ -149,6 +269,7 @@ private:
std::mutex messagesMutex_; std::mutex messagesMutex_;
std::vector<std::string> messages_; std::vector<std::string> messages_;
std::deque<std::string> outbound_; std::deque<std::string> outbound_;
#endif
}; };
WebsocketGameClient::WebsocketGameClient( WebsocketGameClient::WebsocketGameClient(
@@ -296,12 +417,12 @@ void WebsocketGameClient::update(std::int64_t)
if (!tick.stateHash.empty()) { if (!tick.stateHash.empty()) {
nlohmann::json state; nlohmann::json state;
replica_.save(state); replica_.save(state);
const std::string localHash = canonicalStateHash(std::move(state)); /*const std::string localHash = canonicalStateHash(std::move(state));
if (localHash != tick.stateHash) { if (localHash != tick.stateHash) {
std::cerr << "[network] Lockstep divergence at tick " std::cerr << "[network] Lockstep divergence at tick "
<< tick.tickId << ": server=" << tick.stateHash << tick.tickId << ": server=" << tick.stateHash
<< ", client=" << localHash << '\n'; << ", client=" << localHash << '\n';
} }*/
} }
++nextExpectedTick_; ++nextExpectedTick_;
} }
+2 -2
View File
@@ -1,4 +1,4 @@
#include "InteractiveObject.h" #include "InteractiveObject.h"
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "../TextModel.h" #include "../TextModel.h"
@@ -12,7 +12,7 @@
namespace FRG { namespace FRG {
extern const std::string textureUniformName; extern const std::string textureUniformName;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
using std::min; using std::min;
using std::max; using std::max;
#endif #endif
+6 -6
View File
@@ -1,4 +1,4 @@
#include "Game.h" #include "Game.h"
#include "Environment.h" #include "Environment.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include <iostream> #include <iostream>
@@ -6,7 +6,7 @@
#include <android/log.h> #include <android/log.h>
#endif #endif
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
#include <emscripten.h> #include <emscripten.h>
#include <emscripten/html5.h> #include <emscripten/html5.h>
#endif #endif
@@ -50,7 +50,7 @@ void readSettings()
} }
} }
#if defined(EMSCRIPTEN) #if defined(__EMSCRIPTEN__)
static SDL_GLContext glContext_x; static SDL_GLContext glContext_x;
static bool g_isFsLoaded = false; // Флаг готовности static bool g_isFsLoaded = false; // Флаг готовности
@@ -109,7 +109,7 @@ static void applyResize(int logicalW, int logicalH) {
#endif #endif
void MainLoop() { void MainLoop() {
#if defined(EMSCRIPTEN) #if defined(__EMSCRIPTEN__)
// Крутим цикл вхолостую, пока JS читает IndexedDB // Крутим цикл вхолостую, пока JS читает IndexedDB
if (!g_isFsLoaded) return; if (!g_isFsLoaded) return;
@@ -128,7 +128,7 @@ void MainLoop() {
g_game->update(); g_game->update();
} }
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
EM_BOOL onWebGLContextLost(int /*eventType*/, const void* /*reserved*/, void* /*userData*/) { 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); SDL_DestroyWindow(FRG::Environment::window);
} }
//FRG::logger() << "Quitting step 8: " << std::endl; //FRG::logger() << "Quitting step 8: " << std::endl;
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
// In Emscripten, SDL must stay alive across context loss/restore cycles // In Emscripten, SDL must stay alive across context loss/restore cycles
// so the window remains valid when the game object is re-created. // so the window remains valid when the game object is re-created.
SDL_Quit(); SDL_Quit();
+2 -2
View File
@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "SDL.h" #include "SDL.h"
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
//#define GL_GLEXT_PROTOTYPES 1 //#define GL_GLEXT_PROTOTYPES 1
//#define EGL_EGLEXT_PROTOTYPES 1 //#define EGL_EGLEXT_PROTOTYPES 1
//#include <SDL2/SDL_opengl.h> //#include <SDL2/SDL_opengl.h>
+6 -6
View File
@@ -1,4 +1,4 @@
#include "render/Renderer.h" #include "render/Renderer.h"
#include <cmath> #include <cmath>
namespace FRG { namespace FRG {
@@ -116,7 +116,7 @@ namespace FRG {
VAOHolder::VAOHolder() VAOHolder::VAOHolder()
{ {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef ANDROID #ifndef ANDROID
glGenVertexArrays(1, &vao); glGenVertexArrays(1, &vao);
#endif #endif
@@ -125,7 +125,7 @@ namespace FRG {
VAOHolder::~VAOHolder() VAOHolder::~VAOHolder()
{ {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef ANDROID #ifndef ANDROID
#ifdef __linux__ #ifdef __linux__
glDeleteVertexArrays(1, &vao); glDeleteVertexArrays(1, &vao);
@@ -382,7 +382,7 @@ namespace FRG {
{ {
//Check if main thread, check if data is not empty... //Check if main thread, check if data is not empty...
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef __ANDROID__ #ifndef __ANDROID__
if (!vao) if (!vao)
{ {
@@ -528,7 +528,7 @@ namespace FRG {
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef __ANDROID__ #ifndef __ANDROID__
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
#endif #endif
@@ -988,7 +988,7 @@ namespace FRG {
void Renderer::BindVertexRenderStructAttribs(const VertexRenderStruct& VertexRenderStruct) void Renderer::BindVertexRenderStructAttribs(const VertexRenderStruct& VertexRenderStruct)
{ {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef __ANDROID__ #ifndef __ANDROID__
if (VertexRenderStruct.vao) { if (VertexRenderStruct.vao) {
glBindVertexArray(VertexRenderStruct.vao->getBuffer()); glBindVertexArray(VertexRenderStruct.vao->getBuffer());
+3 -3
View File
@@ -1,4 +1,4 @@
#include "ShadowMap.h" #include "ShadowMap.h"
#include "Environment.h" #include "Environment.h"
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
@@ -56,7 +56,7 @@ namespace FRG {
{ {
glGenFramebuffers(1, &fbo); glGenFramebuffers(1, &fbo);
#if defined(EMSCRIPTEN) || defined(__ANDROID__) #if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
glGenTextures(1, &depthTexture); glGenTextures(1, &depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture); glBindTexture(GL_TEXTURE_2D, depthTexture);
@@ -87,7 +87,7 @@ namespace FRG {
GL_TEXTURE_2D, depthTexture, 0); GL_TEXTURE_2D, depthTexture, 0);
// No color buffer for this FBO — depth only. // No color buffer for this FBO — depth only.
#if defined(EMSCRIPTEN) || defined(__ANDROID__) #if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
GLenum drawBuffers[] = { GL_NONE }; GLenum drawBuffers[] = { GL_NONE };
glDrawBuffers(1, drawBuffers); glDrawBuffers(1, drawBuffers);
#else #else
+4 -4
View File
@@ -1,4 +1,4 @@
#include "render/TextureManager.h" #include "render/TextureManager.h"
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#ifdef PNG_ENABLED #ifdef PNG_ENABLED
#include "png.h" #include "png.h"
@@ -7,7 +7,7 @@
namespace FRG namespace FRG
{ {
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
using std::min; using std::min;
using std::max; using std::max;
#endif #endif
@@ -88,7 +88,7 @@ namespace FRG
case TextureDataStruct::R8: case TextureDataStruct::R8:
// Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED // Для WebGL 1.0 лучше использовать GL_LUMINANCE вместо GL_RED
// Если используешь WebGL 2.0, можно оставить GL_RED // Если используешь WebGL 2.0, можно оставить GL_RED
#if defined(EMSCRIPTEN) || defined(__ANDROID__) #if defined(__EMSCRIPTEN__) || defined(__ANDROID__)
internalFormat = GL_LUMINANCE; internalFormat = GL_LUMINANCE;
externalFormat = GL_LUMINANCE; externalFormat = GL_LUMINANCE;
#else #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_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#ifndef __ANDROID__ #ifndef __ANDROID__
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
#endif #endif
+2 -2
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include "utils/Utils.h" #include "utils/Utils.h"
@@ -6,7 +6,7 @@
#include <unordered_map> #include <unordered_map>
#include <unordered_set> #include <unordered_set>
#if defined EMSCRIPTEN || defined __ANDROID__ #if defined __EMSCRIPTEN__ || defined __ANDROID__
#define PNG_ENABLED #define PNG_ENABLED
#endif #endif
+6 -6
View File
@@ -1,4 +1,4 @@
#include "TaskManager.h" #include "TaskManager.h"
namespace FRG namespace FRG
@@ -6,7 +6,7 @@ namespace FRG
TaskManager::TaskManager(size_t threadCount) { TaskManager::TaskManager(size_t threadCount) {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
workGuard = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(ioContext.get_executor()); workGuard = std::make_unique<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>>(ioContext.get_executor());
for (size_t i = 0; i < threadCount; ++i) { for (size_t i = 0; i < threadCount; ++i) {
@@ -18,7 +18,7 @@ namespace FRG
} }
void TaskManager::EnqueueBackgroundTask(std::function<void()> task) { void TaskManager::EnqueueBackgroundTask(std::function<void()> task) {
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
task(); task();
#else #else
boost::asio::post(ioContext, task); boost::asio::post(ioContext, task);
@@ -26,7 +26,7 @@ namespace FRG
} }
TaskManager::~TaskManager() { TaskManager::~TaskManager() {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется
ioContext.stop(); // Опционально: немедленная остановка ioContext.stop(); // Опционально: немедленная остановка
for (auto& t : workers) { for (auto& t : workers) {
@@ -36,7 +36,7 @@ namespace FRG
} }
void MainThreadHandler::EnqueueMainThreadTask(std::function<void()> task) { void MainThreadHandler::EnqueueMainThreadTask(std::function<void()> task) {
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
std::lock_guard<std::mutex> lock(mainThreadMutex); std::lock_guard<std::mutex> lock(mainThreadMutex);
#endif #endif
mainThreadTasks.push(task); mainThreadTasks.push(task);
@@ -45,7 +45,7 @@ namespace FRG
void MainThreadHandler::processMainThreadTasks() { void MainThreadHandler::processMainThreadTasks() {
std::function<void()> task; std::function<void()> task;
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
if (!mainThreadTasks.empty()) { if (!mainThreadTasks.empty()) {
task = std::move(mainThreadTasks.front()); task = std::move(mainThreadTasks.front());
mainThreadTasks.pop(); mainThreadTasks.pop();
+5 -5
View File
@@ -1,6 +1,6 @@
#pragma once #pragma once
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
#include <boost/asio.hpp> #include <boost/asio.hpp>
#include <thread> #include <thread>
#endif #endif
@@ -15,7 +15,7 @@ namespace FRG {
class TaskManager { class TaskManager {
private: private:
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
boost::asio::io_context ioContext; boost::asio::io_context ioContext;
std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> workGuard; std::unique_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> workGuard;
std::vector<std::thread> workers; std::vector<std::thread> workers;
@@ -29,7 +29,7 @@ namespace FRG {
// Graceful shutdown // Graceful shutdown
~TaskManager(); ~TaskManager();
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
boost::asio::io_context& getIOContext() boost::asio::io_context& getIOContext()
{ {
return ioContext; return ioContext;
@@ -43,7 +43,7 @@ namespace FRG {
private: private:
std::queue<std::function<void()>> mainThreadTasks; std::queue<std::function<void()>> mainThreadTasks;
#ifndef EMSCRIPTEN #ifndef __EMSCRIPTEN__
std::mutex mainThreadMutex; std::mutex mainThreadMutex;
#endif #endif
public: public:
+24 -4
View File
@@ -1,4 +1,4 @@
#include "utils/Utils.h" #include "utils/Utils.h"
#include <cstring> #include <cstring>
#include <iterator> #include <iterator>
#include <vector> #include <vector>
@@ -110,7 +110,7 @@ namespace FRG
return readFile(filename); return readFile(filename);
} }
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
std::string webZipFilename = getPersistentResourcePath(zipfilename); std::string webZipFilename = getPersistentResourcePath(zipfilename);
@@ -206,7 +206,7 @@ namespace FRG
return false; return false;
} }
#ifdef EMSCRIPTEN #ifdef __EMSCRIPTEN__
std::string webZipFilename = getPersistentResourcePath(zipfilename); std::string webZipFilename = getPersistentResourcePath(zipfilename);
int zipErr = 0; int zipErr = 0;
@@ -320,7 +320,7 @@ namespace FRG
#endif #endif
// На Web-платформе можно опустить имя разработчика, либо оставить для единообразия. // На Web-платформе можно опустить имя разработчика, либо оставить для единообразия.
// Если в EMSCRIPTEN монтируется `/offline`, структура останется чистой. // Если в __EMSCRIPTEN__ монтируется `/offline`, структура останется чистой.
return baseDir / "Fish Run Games" / "Shadow Over Bishkek Demo"; return baseDir / "Fish Run Games" / "Shadow Over Bishkek Demo";
} }
@@ -545,6 +545,26 @@ namespace FRG
<< " (code " << zipErr << ")" << std::endl; << " (code " << zipErr << ")" << std::endl;
return false; 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); zip_close(archive);
return true; return true;
} }