diff --git a/CMakeLists.txt b/CMakeLists.txt index 1cbf11cb16..15b1e214c9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,39 @@ option(ENABLE_AUDIO "Enable Audio" ON) option(ENABLE_MICROPROFILE "Enable microprofile" OFF) option(ENABLE_ANGELSCRIPT "Enable AngelScript" ON) option(ENABLE_MOFILEREADER "Enable MofileReader" ON) +option(CROSSBUILD_EMSCRIPTEN "Build for the EmScripten Platform" OFF) + +if (CROSSBUILD_EMSCRIPTEN) + # Turn off not features that are not compatible with emscripten + + include(~/.conan/data/emsdk_installer/1.39.6/bincrafters/stable/package/1492a59deb6efdf29776a5734de63fc5f5c58650/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake ) +endif() + +if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") + find_package(OpenGL REQUIRED) + include_directories(${OPENGL_INCLUDE_DIR}) + + list( APPEND conan_cmake_run_params SETTINGS os.threads=true ) + + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features") + + #Debug information + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling-funcs -Oz -g4 -s PTHREAD_POOL_SIZE=8 -s DEMANGLE_SUPPORT=1 -pthread -s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=1 -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" ) + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling-funcs -O3 -g0 --llvm-lto 1 --llvm-opts 3 -s PTHREAD_POOL_SIZE=8 -pthread -s DISABLE_EXCEPTION_CATCHING=1 -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" ) + + + set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -pthread -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" ) + set(SDL2_LIBRARIES "-s USE_SDL=2") + set(SDL2_IMAGE_LIBRARIES "-s USE_PTHREADS=1 -s USE_SDL_IMAGE=2") + #set(SDL2_MIXER_LIBRARIES "-s USE_SDL_MIXER=2") + set(SDL2_TTF_LIBRARIES "--no-check-features -s USE_SDL_TTF=2") + set(SDL2_ZLIB_LIBRARIES "-s USE_ZLIB=1") + +endif() + # Comment-out uneeded libs if (NOT ENABLE_AUDIO) @@ -72,14 +105,27 @@ if (USE_PACKAGE_MANAGER) include(pmm) - pmm(CONAN - REMOTES - AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages - catchorg https://api.bintray.com/conan/catchorg/Catch2 - ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies - BINCRAFTERS - CMakeCM ROLLING - ) + if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") + + pmm(CONAN + PROFILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/conan_profiles/emscripten_profile" + REMOTES + AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages + catchorg https://api.bintray.com/conan/catchorg/Catch2 + ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies + BINCRAFTERS + CMakeCM ROLLING + ) + else() + pmm(CONAN + REMOTES + AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages + catchorg https://api.bintray.com/conan/catchorg/Catch2 + ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies + BINCRAFTERS + CMakeCM ROLLING + ) + endif() if (DEFINED ENV{CI}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) @@ -95,6 +141,32 @@ if (USE_PACKAGE_MANAGER) list(APPEND CMAKE_MODULE_PATH "${CONAN_LIB_DIRS_CATCH2}/cmake/Catch2") endif () +# these compiler options are mostly for debugging and trying to get it to work + if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten") + list(APPEND _link_libraries + #"-s ERROR_ON_UNDEFINED_SYMBOLS=0" + "--preload-file ${CMAKE_CURRENT_BINARY_DIR}/resources@/resources" #include resources dir in .data file + "-s USE_PTHREADS=1" + "--use-preload-plugins" + # "--source-map-base" + "-s WASM=1" + "-s WASM_MEM_MAX=512MB" # ALLOW_MEMORY_GROWTH demands MEM_MAX to be set + "-s ALLOW_MEMORY_GROWTH=1" #seems necessary for pthreads and dynamic memory allocation + "-o index.html" + # "-s DISABLE_EXCEPTION_CATCHING=0" # 0 -> catch exceptions! 1-> is disabled + "-s TOTAL_MEMORY=33554432" + # "-s SAFE_HEAP=1" + #demangle C++ functions + # "-s ASSERTIONS=0" # use assertions + "-O3" # no optimization + "-g0" # most debug info + CONAN_PKG::libnoise + ${SDL2_LIBRARIES} + ${SDL2_IMAGE_LIBRARIES} + ${SDL2TTF_LIBRARIES} + ${SDL2_ZLIB_LIBRARIES} + ) + else() list(APPEND _link_libraries CONAN_PKG::sdl2 CONAN_PKG::sdl2_image @@ -103,6 +175,7 @@ if (USE_PACKAGE_MANAGER) CONAN_PKG::libnoise CONAN_PKG::zlib ) + endif() if (ENABLE_AUDIO) list(APPEND _link_libraries CONAN_PKG::sdl2_mixer) @@ -197,7 +270,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") link_libraries(dbghelp.lib) else() # Needed for filesystem library - if(NOT APPLE) + if(NOT APPLE AND NOT EMSCRIPTEN) # TODO: Link with stdc++fs on Apple with XCode11 list(APPEND _link_libraries "stdc++fs") endif() diff --git a/cmake/conan_profiles/emscripten_profile b/cmake/conan_profiles/emscripten_profile new file mode 100644 index 0000000000..0ad5440e0d --- /dev/null +++ b/cmake/conan_profiles/emscripten_profile @@ -0,0 +1,17 @@ +[settings] +os=Emscripten +arch=x86 +#arch=wasm +os_build=Linux +arch_build=x86_64 +compiler=clang +compiler.version=10 +#compiler.version=6.0 +compiler.libcxx=libc++ +[options] +threads=true +[build_requires] +emsdk_installer/1.39.6@bincrafters/stable +[options] +libjpeg-turbo:SIMD = False +sdl2_image:tif = False diff --git a/src/Game.cxx b/src/Game.cxx index f63b7e8c45..78dc5217eb 100644 --- a/src/Game.cxx +++ b/src/Game.cxx @@ -12,6 +12,12 @@ #include #include +#ifdef __EMSCRIPTEN__ + #include +#endif + +void gameLoop(void* arg_); + #ifdef USE_ANGELSCRIPT #include "Scripting/ScriptEngine.hxx" #endif @@ -27,6 +33,17 @@ template void Game::LoopMain(Game::GameContext &, Game::GameVisitor); template void Game::LoopMain(Game::GameContext &, Game::UIVisitor); +typedef struct loop_arg { + Engine *engine; + EventManager *evManager; + UIManager *uiManager; +} loop_arg; + + // FPS Counter variables + const float fpsIntervall = 1.0; // interval the fps counter is refreshed in seconds. + Uint32 fpsLastTime = SDL_GetTicks(); + Uint32 fpsFrames = 0; + Game::Game() : m_GameContext(&m_UILoopMQ, &m_GameLoopMQ, #ifdef USE_AUDIO @@ -240,24 +257,32 @@ void Game::mainMenu() void Game::run(bool SkipMenu) { + loop_arg* arg = new loop_arg; Timer benchmarkTimer; LOG(LOG_INFO) << VERSION; + Engine &engine = Engine::instance(); + EventManager &evManager = EventManager::instance(); + UIManager &uiManager = UIManager::instance(); + + if (SkipMenu) { Engine::instance().newGame(); } benchmarkTimer.start(); - Engine &engine = Engine::instance(); - LOG(LOG_DEBUG) << "Map initialized in " << benchmarkTimer.getElapsedTime() << "ms"; + // SDL_Event event; + + arg->engine = &engine; + arg->evManager = &evManager; + arg->uiManager = &uiManager; + + LOG(LOG_DEBUG) << "Map initialized in " << benchmarkTimer.getElapsedTime() << "ms"; Camera::centerScreenOnMapCenter(); - SDL_Event event; - EventManager &evManager = EventManager::instance(); - UIManager &uiManager = UIManager::instance(); uiManager.init(); #ifdef USE_ANGELSCRIPT @@ -278,55 +303,18 @@ void Game::run(bool SkipMenu) } #endif // USE_AUDIO - // FPS Counter variables - const float fpsIntervall = 1.0; // interval the fps counter is refreshed in seconds. - Uint32 fpsLastTime = SDL_GetTicks(); - Uint32 fpsFrames = 0; +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop_arg(&gameLoop, arg, 0, 1); +#else // GameLoop while (engine.isGameRunning()) { -#ifdef MICROPROFILE_ENABLED - MICROPROFILE_SCOPEI("Map", "Gameloop", MP_GREEN); -#endif - SDL_RenderClear(WindowManager::instance().getRenderer()); - - evManager.checkEvents(event, engine); - - // render the tileMap - if (engine.map != nullptr) - { - engine.map->renderMap(); - } - - // render the ui - // TODO: This is only temporary until the new UI is ready. Remove this afterwards - if (GameStates::instance().drawUI) - { - uiManager.drawUI(); - } - - // reset renderer color back to black - SDL_SetRenderDrawColor(WindowManager::instance().getRenderer(), 0, 0, 0, SDL_ALPHA_OPAQUE); - - // Render the Frame - SDL_RenderPresent(WindowManager::instance().getRenderer()); - - fpsFrames++; - - if (fpsLastTime < SDL_GetTicks() - fpsIntervall * 1000) - { - fpsLastTime = SDL_GetTicks(); - uiManager.setFPSCounterText(std::to_string(fpsFrames) + " FPS"); - fpsFrames = 0; - } - - SDL_Delay(1); - -#ifdef MICROPROFILE_ENABLED - MicroProfileFlip(nullptr); -#endif + gameLoop( arg); } + #endif + + delete arg; } void Game::shutdown() @@ -370,3 +358,50 @@ template void Game::LoopMain(GameContext &co // @todo: Call shutdown() here in a safe way } } + +void gameLoop(void* arg_) +// void gameLoop(Engine &engine, EventManager &evManager, UIManager &uiManager) +{ + +loop_arg* arg = (loop_arg*)arg_; + #ifdef MICROPROFILE_ENABLED + MICROPROFILE_SCOPEI("Map", "Gameloop", MP_GREEN); +#endif + SDL_RenderClear(WindowManager::instance().getRenderer()); +SDL_Event event; + arg->evManager->checkEvents(event, *arg->engine); + + // render the tileMap + if (arg->engine->map != nullptr) + arg->engine->map->renderMap(); + + // render the ui + // TODO: This is only temporary until the new UI is ready. Remove this afterwards + if (GameStates::instance().drawUI) + { + uiManager.drawUI(); + } + + // reset renderer color back to black + SDL_SetRenderDrawColor(WindowManager::instance().getRenderer(), 0, 0, 0, SDL_ALPHA_OPAQUE); + + // Render the Frame + SDL_RenderPresent(WindowManager::instance().getRenderer()); + + fpsFrames++; + + if (fpsLastTime < SDL_GetTicks() - fpsIntervall * 1000) + { + fpsLastTime = SDL_GetTicks(); + arg->uiManager->setFPSCounterText(std::to_string(fpsFrames) + " FPS"); + fpsFrames = 0; + } + +#ifndef __EMSCRIPTEN__ + SDL_Delay(1); +#endif + +#ifdef MICROPROFILE_ENABLED + MicroProfileFlip(nullptr); +#endif +} diff --git a/src/main.cxx b/src/main.cxx index c3710da235..061018e315 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -4,8 +4,10 @@ #include "Exception.hxx" #include "LOG.hxx" +#ifndef __EMSCRIPTEN__ #include void SIG_handler(int signal); +#endif SDL_AssertState AssertionHandler(const SDL_AssertData *, void *); @@ -24,6 +26,10 @@ int protected_main(int argc, char **argv) skipMenu = true; } } + + #ifdef __EMSCRIPTEN__ + skipMenu = true; + #endif LOG(LOG_DEBUG) << "Launching Cytopia"; @@ -51,11 +57,12 @@ int protected_main(int argc, char **argv) int main(int argc, char **argv) { +#ifndef __EMSCRIPTEN__ /* Register handler for Segmentation Fault, Interrupt, Terminate */ signal(SIGSEGV, SIG_handler); signal(SIGINT, SIG_handler); signal(SIGTERM, SIG_handler); - +#endif /* All SDL2 Assertion failures must be handled * by our handler */ SDL_SetAssertionHandler(AssertionHandler, 0); diff --git a/src/util/Exception.cxx b/src/util/Exception.cxx index 36e3684961..7c266033b1 100644 --- a/src/util/Exception.cxx +++ b/src/util/Exception.cxx @@ -42,12 +42,14 @@ void SIG_handler(int signal) exit(1); } -#else +// #else +#elif __unix__ && !__EMSCRIPTEN__ #include -#include #include +#include + void SIG_handler(int signal) { switch (signal) @@ -93,7 +95,7 @@ SDL_AssertState AssertionHandler(const SDL_AssertData *data, void *) SYMBOL_INFO symbol; for (int i = 0; i < size; ++i) std::cout << "\tat " << symbol.Name << "\n"; -#else +#elif __unix__ && !__EMSCRIPTEN__ /* We print the last 10 calls */ void *buffer[10]; size_t size;