diff --git a/.github/workflows/Web.yml b/.github/workflows/Web.yml index ed02e981..fa2b34dc 100644 --- a/.github/workflows/Web.yml +++ b/.github/workflows/Web.yml @@ -11,7 +11,7 @@ jobs: - name: Setup emsdk uses: mymindstorm/setup-emsdk@v10 with: - version: 2.0.21 + version: 3.1.51 actions-cache-folder: 'emsdk-cache' - name: Build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index d3b563e4..6f215f5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(BUILD_STATIC_LIBS ON) set(DPP_BUILD_TEST OFF) if (WIN32) - set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_WINDOWS WIN32_LEAN_AND_MEAN) + set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_WINDOWS WIN32_LEAN_AND_MEAN NOMINMAX) set(HYDRA_WINDOWS 1) elseif(APPLE) set(HYDRA_DEFINITIONS ${HYDRA_DEFINITIONS} HYDRA_MACOS) @@ -80,9 +80,7 @@ elseif(HYDRA_IOS) elseif(HYDRA_WEB) set(LONG_INT 8) # for openssl set(CMAKE_EXECUTABLE_SUFFIX ".html") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -sEXPORTED_RUNTIME_METHODS=ccall") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -sEXPORTED_RUNTIME_METHODS=ccall") - set_target_properties(hydra PROPERTIES LINK_FLAGS "-sALLOW_MEMORY_GROWTH -s TOTAL_MEMORY=192MB -lidbfs.js -s ELIMINATE\_DUPLICATE\_FUNCTIONS=1 -flto -s USE_CLOSURE_COMPILER=0 --closure 0") + set_target_properties(hydra PROPERTIES LINK_FLAGS "-sEXPORTED_FUNCTIONS=[_main_impl,_main] -sEXPORTED_RUNTIME_METHODS=ccall -sNO_DISABLE_EXCEPTION_CATCHING -sASSERTIONS -sALLOW_MEMORY_GROWTH -s TOTAL_MEMORY=192MB -lidbfs.js -s ELIMINATE\_DUPLICATE\_FUNCTIONS=1 -flto -s USE_CLOSURE_COMPILER=0 --closure 0") else() message(FATAL_ERROR "Unsupported platform") endif() @@ -91,12 +89,9 @@ endif() if (MSVC) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT /Ox /Ob2 /Oi /Ot /GT /GF /GS- /fp:fast /fp:except- /MP /sdl- /EHsc /D_SECURE_SCL=0 /D_SCL_SECURE_NO_WARNINGS /D_ITERATOR_DEBUG_LEVEL=0 /D_HAS_ITERATOR_DEBUGGING=0") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT /Ox /Ob2 /Oi /Ot /GT /GF /GS- /fp:fast /fp:except- /MP /sdl- /EHsc /D_SECURE_SCL=0 /D_SCL_SECURE_NO_WARNINGS /D_ITERATOR_DEBUG_LEVEL=0 /D_HAS_ITERATOR_DEBUGGING=0") -elseif(NOT HYDRA_WEB) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3") else() - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -DNDEBUG") # it's best to strip out debug symbols for web builds - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -DNDEBUG") # to reduce the binary size + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O3") # TODO: it's best to strip out debug symbols for web builds + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3") # to reduce the binary size endif() option(BUILD_QT "Build the Qt frontend" OFF) diff --git a/include/compatibility.hxx b/include/compatibility.hxx index 323ed78a..b59edb7d 100644 --- a/include/compatibility.hxx +++ b/include/compatibility.hxx @@ -1,10 +1,30 @@ #pragma once #include +#include +#include +#include #include #include #include +#ifdef HYDRA_WEB +#include +#endif + +static void sync_fs() +{ +#ifdef HYDRA_WEB + EM_ASM( + FS.syncfs(false, function (err) { + if (err) { + console.log("Failed to sync FS: " + err); + } + }); + ); +#endif +} + namespace hydra { inline std::vector split(const std::string& s, char delimiter) @@ -44,4 +64,42 @@ namespace hydra return hash; } + inline std::string fread(const std::filesystem::path& path) + { + FILE* file = std::fopen(path.string().c_str(), "rb"); + if (!file) + { + file = std::fopen(path.string().c_str(), "wb"); + if (!file) + { + hydra::log("Failed to open file: %s\n", path.string().c_str()); + return ""; + } + std::fclose(file); + sync_fs(); + return ""; + } + + std::string contents; + std::fseek(file, 0, SEEK_END); + contents.resize(std::ftell(file)); + std::rewind(file); + std::fread(&contents[0], 1, contents.size(), file); + std::fclose(file); + return contents; + } + + inline void fwrite(const std::filesystem::path& path, std::span contents) + { + FILE* file = std::fopen(path.string().c_str(), "wb"); + if (!file) + { + hydra::log("Failed to open file: %s\n", path.string().c_str()); + return; + } + std::fwrite(contents.data(), 1, contents.size(), file); + std::fclose(file); + sync_fs(); + } + } // namespace hydra diff --git a/include/settings.hxx b/include/settings.hxx index 01653110..5cc749cb 100644 --- a/include/settings.hxx +++ b/include/settings.hxx @@ -44,17 +44,22 @@ public: { map().clear(); save_path() = path; - std::ifstream ifs(save_path()); - if (ifs.good()) + std::string data = hydra::fread(path); + if (data.empty()) { - json j_map; - ifs >> j_map; - map() = j_map.get>(); + return; } - else + + json j_map; + try { - throw std::runtime_error("Failed to open settings"); + j_map = json::parse(data); + } catch (std::exception& e) + { + hydra::log("Failed to parse settings file: {}", e.what()); + return; } + map() = j_map.get>(); } static std::string Get(const std::string& key) @@ -71,9 +76,9 @@ public: static void Set(const std::string& key, const std::string& value) { map()[key] = value; - std::ofstream ofs(save_path(), std::ios::trunc); json j_map(map()); - ofs << j_map << std::endl; + std::string data = j_map.dump(4); + hydra::fwrite(save_path(), std::span((uint8_t*)data.data(), data.size())); } static bool IsEmpty() @@ -98,6 +103,8 @@ public: std::string applicationName; std::getline(cmdline, applicationName, '\0'); dir = std::filesystem::path("/data") / "data" / applicationName / "files"; +#elif defined(HYDRA_WEB) + dir = std::filesystem::path("/hydra"); #endif if (dir.empty()) { @@ -161,7 +168,7 @@ public: if (!std::filesystem::exists(Settings::Get("core_path"))) { - printf("Failed to find initialize core info\n"); + printf("Failed to find core info: %s\n", Settings::Get("core_path").c_str()); return; } diff --git a/src/main.cxx b/src/main.cxx index 9469d46c..9c8221b9 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -9,6 +9,10 @@ #endif #include +#ifdef HYDRA_WEB +#include +#endif + // clang-format off const char* options = @@ -110,12 +114,34 @@ int bot_main_cb(struct argparse*, const struct argparse_option*) #endif } -int main(int argc, char* argv[]) +#if defined(HYDRA_WEB) +// Setup the offline file system +EM_JS(void, em_init_fs, (),{ + FS.mkdir('/hydra'); + // Then mount with IDBFS type + FS.mount(IDBFS, {}, '/hydra'); + FS.mkdir('/hydra/cores'); + FS.mount(IDBFS, {}, '/hydra/cores'); + FS.mkdir('/hydra/cache'); + FS.mount(IDBFS, {}, '/hydra/cache'); + // Then sync + FS.syncfs(true, function (err) { + Module.ccall('main_impl'); + }); + }); +#endif + +extern "C" int main_impl(int argc, char* argv[]) { + printf("hydra version %s\n", HYDRA_VERSION); auto settings_path = Settings::GetSavePath() / "settings.json"; Settings::Open(settings_path); Settings::InitCoreInfo(); +#ifdef HYDRA_WEB + return imgui_main(argc, argv); +#endif + if (argc == 1) { return imgui_main(argc, argv); @@ -144,3 +170,12 @@ int main(int argc, char* argv[]) argparse_parse(&argparse, argc, const_cast(argv)); return 0; } + +int main(int argc, char* argv[]) +{ +#ifdef HYDRA_WEB + em_init_fs(); +#else + return main_impl(argc, argv); +#endif +}