From 076150ef42b0b331c8aed16a1e5fd55904c1f818 Mon Sep 17 00:00:00 2001 From: ololoken Date: Sun, 5 Jan 2025 04:51:56 -0500 Subject: [PATCH] Add WASM version of the engine (#9406) --- .github/workflows/make.yml | 41 ++++ .gitignore | 9 +- Makefile.emscripten | 43 ++++ Makefile.vita | 4 +- android/app/build.gradle | 10 +- files/emscripten/fheroes2.jpeg | Bin 0 -> 55373 bytes files/emscripten/index.html | 210 ++++++++++++++++++ .../timidity}/instruments/README.txt | 0 .../timidity}/instruments/acpiano.pat | Bin .../timidity}/instruments/agogohi.pat | Bin .../timidity}/instruments/agogolo.pat | Bin .../timidity}/instruments/bassoon.pat | Bin .../timidity}/instruments/belltree.pat | Bin .../timidity}/instruments/bongohi.pat | Bin .../timidity}/instruments/bongolo.pat | Bin .../timidity}/instruments/bowglass.pat | Bin .../timidity}/instruments/cabasa.pat | Bin .../timidity}/instruments/castinet.pat | Bin .../timidity}/instruments/cello.pat | Bin .../timidity}/instruments/choir.pat | Bin .../timidity}/instruments/church.pat | Bin .../timidity}/instruments/claps.pat | Bin .../timidity}/instruments/clarinet.pat | Bin .../timidity}/instruments/clave.pat | Bin .../timidity}/instruments/congahi1.pat | Bin .../timidity}/instruments/congahi2.pat | Bin .../timidity}/instruments/congalo.pat | Bin .../timidity}/instruments/contraba.pat | Bin .../timidity}/instruments/cowbell.pat | Bin .../timidity}/instruments/cuica1.pat | Bin .../timidity}/instruments/cuica2.pat | Bin .../timidity}/instruments/cymbell.pat | Bin .../timidity}/instruments/cymchina.pat | Bin .../timidity}/instruments/cymcrsh1.pat | Bin .../timidity}/instruments/cymcrsh2.pat | Bin .../timidity}/instruments/cymride1.pat | Bin .../timidity}/instruments/cymride2.pat | Bin .../timidity}/instruments/cymsplsh.pat | Bin .../timidity}/instruments/englhorn.pat | Bin .../timidity}/instruments/flute.pat | Bin .../timidity}/instruments/frenchrn.pat | Bin .../timidity}/instruments/guiro1.pat | Bin .../timidity}/instruments/guiro2.pat | Bin .../timidity}/instruments/harp.pat | Bin .../timidity}/instruments/highq.pat | Bin .../timidity}/instruments/hihatcl.pat | Bin .../timidity}/instruments/hihatop.pat | Bin .../timidity}/instruments/hihatpd.pat | Bin .../timidity}/instruments/hrpschrd.pat | Bin .../timidity}/instruments/jingles.pat | Bin .../timidity}/instruments/kick1.pat | Bin .../timidity}/instruments/kick2.pat | Bin .../timidity}/instruments/maracas.pat | Bin .../timidity}/instruments/marcato.pat | Bin .../timidity}/instruments/metbell.pat | Bin .../timidity}/instruments/metclick.pat | Bin .../timidity}/instruments/nyguitar.pat | Bin .../timidity}/instruments/oboe.pat | Bin .../timidity}/instruments/piccolo.pat | Bin .../timidity}/instruments/pizzcato.pat | Bin .../timidity}/instruments/scratch1.pat | Bin .../timidity}/instruments/scratch2.pat | Bin .../timidity}/instruments/shaker.pat | Bin .../timidity}/instruments/slap.pat | Bin .../timidity}/instruments/slowstr.pat | Bin .../timidity}/instruments/snap.pat | Bin .../timidity}/instruments/snare1.pat | Bin .../timidity}/instruments/snare2.pat | Bin .../timidity}/instruments/snarerol.pat | Bin .../timidity}/instruments/sqrclick.pat | Bin .../timidity}/instruments/stickrim.pat | Bin .../timidity}/instruments/sticks.pat | Bin .../timidity}/instruments/surdo1.pat | Bin .../timidity}/instruments/surdo2.pat | Bin .../timidity}/instruments/synstr2.pat | Bin .../timidity}/instruments/taiko.pat | Bin .../timidity}/instruments/tamborin.pat | Bin .../timidity}/instruments/timbaleh.pat | Bin .../timidity}/instruments/timbalel.pat | Bin .../timidity}/instruments/timpani.pat | Bin .../timidity}/instruments/tomhi1.pat | Bin .../timidity}/instruments/tomhi2.pat | Bin .../timidity}/instruments/tomlo1.pat | Bin .../timidity}/instruments/tomlo2.pat | Bin .../timidity}/instruments/tommid1.pat | Bin .../timidity}/instruments/tommid2.pat | Bin .../timidity}/instruments/tremstr.pat | Bin .../timidity}/instruments/triangl1.pat | Bin .../timidity}/instruments/triangl2.pat | Bin .../timidity}/instruments/trombone.pat | Bin .../timidity}/instruments/vibslap.pat | Bin .../timidity}/instruments/viola.pat | Bin .../timidity}/instruments/whistle1.pat | Bin .../timidity}/instruments/whistle2.pat | Bin .../timidity}/instruments/woodblk1.pat | Bin .../timidity}/instruments/woodblk2.pat | Bin .../assets => files/timidity}/timidity.cfg | 0 src/dist/Makefile | 10 +- src/dist/Makefile.emscripten | 43 ++++ src/dist/fheroes2-ems/Makefile | 48 ++++ src/engine/audio.cpp | 4 +- src/engine/serialize.cpp | 50 ++++- src/engine/serialize.h | 6 +- src/engine/thread.cpp | 56 ++++- src/engine/thread.h | 7 +- src/fheroes2/game/game_hotkeys.cpp | 16 +- src/fheroes2/system/settings.cpp | 15 +- 107 files changed, 541 insertions(+), 31 deletions(-) create mode 100644 Makefile.emscripten create mode 100644 files/emscripten/fheroes2.jpeg create mode 100644 files/emscripten/index.html rename {android/app/src/main/assets => files/timidity}/instruments/README.txt (100%) rename {android/app/src/main/assets => files/timidity}/instruments/acpiano.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/agogohi.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/agogolo.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/bassoon.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/belltree.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/bongohi.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/bongolo.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/bowglass.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cabasa.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/castinet.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cello.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/choir.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/church.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/claps.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/clarinet.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/clave.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/congahi1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/congahi2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/congalo.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/contraba.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cowbell.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cuica1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cuica2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymbell.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymchina.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymcrsh1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymcrsh2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymride1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymride2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/cymsplsh.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/englhorn.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/flute.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/frenchrn.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/guiro1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/guiro2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/harp.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/highq.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/hihatcl.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/hihatop.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/hihatpd.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/hrpschrd.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/jingles.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/kick1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/kick2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/maracas.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/marcato.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/metbell.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/metclick.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/nyguitar.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/oboe.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/piccolo.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/pizzcato.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/scratch1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/scratch2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/shaker.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/slap.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/slowstr.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/snap.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/snare1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/snare2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/snarerol.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/sqrclick.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/stickrim.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/sticks.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/surdo1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/surdo2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/synstr2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/taiko.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tamborin.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/timbaleh.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/timbalel.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/timpani.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tomhi1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tomhi2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tomlo1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tomlo2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tommid1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tommid2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/tremstr.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/triangl1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/triangl2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/trombone.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/vibslap.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/viola.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/whistle1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/whistle2.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/woodblk1.pat (100%) rename {android/app/src/main/assets => files/timidity}/instruments/woodblk2.pat (100%) rename {android/app/src/main/assets => files/timidity}/timidity.cfg (100%) create mode 100644 src/dist/Makefile.emscripten create mode 100644 src/dist/fheroes2-ems/Makefile diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml index b61291a4ae7..f399ae28829 100644 --- a/.github/workflows/make.yml +++ b/.github/workflows/make.yml @@ -342,3 +342,44 @@ jobs: artifactErrorsFailBuild: true prerelease: true replacesArtifacts: true + make-emscripten: + name: Make (Emscripten) + runs-on: ubuntu-latest + container: emscripten/emsdk:3.1.74 + timeout-minutes: 30 + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo apt-get -y update + sudo apt-get -y install gettext p7zip-full + - name: Build + run: | + emmake make -f Makefile.emscripten -j "$(nproc)" + env: + FHEROES2_STRICT_COMPILATION: ON + - name: Create package + run: | + # Translations and H2D files are already included in fheroes2.data + 7z a -bb1 -tzip -- fheroes2_emscripten.zip LICENSE changelog.txt fheroes2.data fheroes2.js fheroes2.wasm ./docs/README.txt ./files/emscripten/* + - uses: actions/upload-artifact@v4 + if: ${{ github.event_name == 'pull_request' }} + with: + name: fheroes2_emscripten.zip + path: fheroes2_emscripten.zip + if-no-files-found: error + - uses: ncipollo/release-action@v1 + if: ${{ github.event_name == 'push' }} + with: + artifacts: fheroes2_emscripten.zip + body: ${{ github.event.head_commit.message }} + token: ${{ secrets.GITHUB_TOKEN }} + name: Emscripten build (latest commit) + tag: fheroes2-emscripten-sdl2_dev + allowUpdates: true + artifactErrorsFailBuild: true + prerelease: true + replacesArtifacts: true diff --git a/.gitignore b/.gitignore index af8057fc82b..3793277c993 100644 --- a/.gitignore +++ b/.gitignore @@ -92,7 +92,14 @@ android/.idea android/.gradle # Automatically created Android assets -android/app/src/main/assets/files/ +android/app/src/main/assets/ # Mac magic folders .DS_Store + +# Web build artifacts +*.wasm +*.wasm.map +fheroes2.js +fheroes2.data +src/dist/web/dist/* diff --git a/Makefile.emscripten b/Makefile.emscripten new file mode 100644 index 00000000000..07feece5450 --- /dev/null +++ b/Makefile.emscripten @@ -0,0 +1,43 @@ +########################################################################### +# fheroes2: https://github.com/ihhub/fheroes2 # +# Copyright (C) 2025 # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +########################################################################### + +# Options: +# +# FHEROES2_STRICT_COMPILATION: build in strict compilation mode (turns warnings into errors) +# FHEROES2_WITH_DEBUG: build in debug mode +# FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) + +.PHONY: all clean translations + +all: fheroes2.js + +fheroes2.js: translations + $(MAKE) -C src/dist PLATFORM=emscripten + cp src/dist/fheroes2-ems/fheroes2.data . + cp src/dist/fheroes2-ems/fheroes2.js . + cp src/dist/fheroes2-ems/fheroes2.wasm . + +translations: + $(MAKE) -C files/lang + +clean: + $(MAKE) -C src/dist clean + $(MAKE) -C files/lang clean + rm -f fheroes2.data fheroes2.js fheroes2.wasm diff --git a/Makefile.vita b/Makefile.vita index 18a547c6b23..956ec8d6694 100644 --- a/Makefile.vita +++ b/Makefile.vita @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2024 # +# Copyright (C) 2021 - 2025 # # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # @@ -40,7 +40,7 @@ fheroes2.vpk: eboot.bin param.sfo translations --add files/images/platform/psv/sce_sys/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \ --add files/data=files/data \ --add files/lang/vita_temp=files/lang \ - fheroes2.vpk + fheroes2.vpk rm -r files/lang/vita_temp translations: fheroes2.elf diff --git a/android/app/build.gradle b/android/app/build.gradle index 54991ba2fac..8167f0b5abc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -99,6 +99,14 @@ tasks.register('copyH2D', Copy) { into 'src/main/assets/files/data' } +tasks.register('copyTimidity', Copy) { + from('../../files/timidity') { + include 'instruments/*.pat' + include 'timidity.cfg' + } + into 'src/main/assets' +} + tasks.register('copyTranslations', Copy) { from('../../files/lang') { include '*.mo' @@ -107,5 +115,5 @@ tasks.register('copyTranslations', Copy) { } preBuild { - dependsOn copyH2D, copyTranslations + dependsOn copyH2D, copyTimidity, copyTranslations } diff --git a/files/emscripten/fheroes2.jpeg b/files/emscripten/fheroes2.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..d0d3f9f04e45b88ce6a17994bb67f746b6828652 GIT binary patch literal 55373 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 literal 0 HcmV?d00001 diff --git a/files/emscripten/index.html b/files/emscripten/index.html new file mode 100644 index 00000000000..99352febb69 --- /dev/null +++ b/files/emscripten/index.html @@ -0,0 +1,210 @@ + + + fheroes2 + + + + + + + + + +
+ +
+
+
+ + +
+
+ + diff --git a/android/app/src/main/assets/instruments/README.txt b/files/timidity/instruments/README.txt similarity index 100% rename from android/app/src/main/assets/instruments/README.txt rename to files/timidity/instruments/README.txt diff --git a/android/app/src/main/assets/instruments/acpiano.pat b/files/timidity/instruments/acpiano.pat similarity index 100% rename from android/app/src/main/assets/instruments/acpiano.pat rename to files/timidity/instruments/acpiano.pat diff --git a/android/app/src/main/assets/instruments/agogohi.pat b/files/timidity/instruments/agogohi.pat similarity index 100% rename from android/app/src/main/assets/instruments/agogohi.pat rename to files/timidity/instruments/agogohi.pat diff --git a/android/app/src/main/assets/instruments/agogolo.pat b/files/timidity/instruments/agogolo.pat similarity index 100% rename from android/app/src/main/assets/instruments/agogolo.pat rename to files/timidity/instruments/agogolo.pat diff --git a/android/app/src/main/assets/instruments/bassoon.pat b/files/timidity/instruments/bassoon.pat similarity index 100% rename from android/app/src/main/assets/instruments/bassoon.pat rename to files/timidity/instruments/bassoon.pat diff --git a/android/app/src/main/assets/instruments/belltree.pat b/files/timidity/instruments/belltree.pat similarity index 100% rename from android/app/src/main/assets/instruments/belltree.pat rename to files/timidity/instruments/belltree.pat diff --git a/android/app/src/main/assets/instruments/bongohi.pat b/files/timidity/instruments/bongohi.pat similarity index 100% rename from android/app/src/main/assets/instruments/bongohi.pat rename to files/timidity/instruments/bongohi.pat diff --git a/android/app/src/main/assets/instruments/bongolo.pat b/files/timidity/instruments/bongolo.pat similarity index 100% rename from android/app/src/main/assets/instruments/bongolo.pat rename to files/timidity/instruments/bongolo.pat diff --git a/android/app/src/main/assets/instruments/bowglass.pat b/files/timidity/instruments/bowglass.pat similarity index 100% rename from android/app/src/main/assets/instruments/bowglass.pat rename to files/timidity/instruments/bowglass.pat diff --git a/android/app/src/main/assets/instruments/cabasa.pat b/files/timidity/instruments/cabasa.pat similarity index 100% rename from android/app/src/main/assets/instruments/cabasa.pat rename to files/timidity/instruments/cabasa.pat diff --git a/android/app/src/main/assets/instruments/castinet.pat b/files/timidity/instruments/castinet.pat similarity index 100% rename from android/app/src/main/assets/instruments/castinet.pat rename to files/timidity/instruments/castinet.pat diff --git a/android/app/src/main/assets/instruments/cello.pat b/files/timidity/instruments/cello.pat similarity index 100% rename from android/app/src/main/assets/instruments/cello.pat rename to files/timidity/instruments/cello.pat diff --git a/android/app/src/main/assets/instruments/choir.pat b/files/timidity/instruments/choir.pat similarity index 100% rename from android/app/src/main/assets/instruments/choir.pat rename to files/timidity/instruments/choir.pat diff --git a/android/app/src/main/assets/instruments/church.pat b/files/timidity/instruments/church.pat similarity index 100% rename from android/app/src/main/assets/instruments/church.pat rename to files/timidity/instruments/church.pat diff --git a/android/app/src/main/assets/instruments/claps.pat b/files/timidity/instruments/claps.pat similarity index 100% rename from android/app/src/main/assets/instruments/claps.pat rename to files/timidity/instruments/claps.pat diff --git a/android/app/src/main/assets/instruments/clarinet.pat b/files/timidity/instruments/clarinet.pat similarity index 100% rename from android/app/src/main/assets/instruments/clarinet.pat rename to files/timidity/instruments/clarinet.pat diff --git a/android/app/src/main/assets/instruments/clave.pat b/files/timidity/instruments/clave.pat similarity index 100% rename from android/app/src/main/assets/instruments/clave.pat rename to files/timidity/instruments/clave.pat diff --git a/android/app/src/main/assets/instruments/congahi1.pat b/files/timidity/instruments/congahi1.pat similarity index 100% rename from android/app/src/main/assets/instruments/congahi1.pat rename to files/timidity/instruments/congahi1.pat diff --git a/android/app/src/main/assets/instruments/congahi2.pat b/files/timidity/instruments/congahi2.pat similarity index 100% rename from android/app/src/main/assets/instruments/congahi2.pat rename to files/timidity/instruments/congahi2.pat diff --git a/android/app/src/main/assets/instruments/congalo.pat b/files/timidity/instruments/congalo.pat similarity index 100% rename from android/app/src/main/assets/instruments/congalo.pat rename to files/timidity/instruments/congalo.pat diff --git a/android/app/src/main/assets/instruments/contraba.pat b/files/timidity/instruments/contraba.pat similarity index 100% rename from android/app/src/main/assets/instruments/contraba.pat rename to files/timidity/instruments/contraba.pat diff --git a/android/app/src/main/assets/instruments/cowbell.pat b/files/timidity/instruments/cowbell.pat similarity index 100% rename from android/app/src/main/assets/instruments/cowbell.pat rename to files/timidity/instruments/cowbell.pat diff --git a/android/app/src/main/assets/instruments/cuica1.pat b/files/timidity/instruments/cuica1.pat similarity index 100% rename from android/app/src/main/assets/instruments/cuica1.pat rename to files/timidity/instruments/cuica1.pat diff --git a/android/app/src/main/assets/instruments/cuica2.pat b/files/timidity/instruments/cuica2.pat similarity index 100% rename from android/app/src/main/assets/instruments/cuica2.pat rename to files/timidity/instruments/cuica2.pat diff --git a/android/app/src/main/assets/instruments/cymbell.pat b/files/timidity/instruments/cymbell.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymbell.pat rename to files/timidity/instruments/cymbell.pat diff --git a/android/app/src/main/assets/instruments/cymchina.pat b/files/timidity/instruments/cymchina.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymchina.pat rename to files/timidity/instruments/cymchina.pat diff --git a/android/app/src/main/assets/instruments/cymcrsh1.pat b/files/timidity/instruments/cymcrsh1.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymcrsh1.pat rename to files/timidity/instruments/cymcrsh1.pat diff --git a/android/app/src/main/assets/instruments/cymcrsh2.pat b/files/timidity/instruments/cymcrsh2.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymcrsh2.pat rename to files/timidity/instruments/cymcrsh2.pat diff --git a/android/app/src/main/assets/instruments/cymride1.pat b/files/timidity/instruments/cymride1.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymride1.pat rename to files/timidity/instruments/cymride1.pat diff --git a/android/app/src/main/assets/instruments/cymride2.pat b/files/timidity/instruments/cymride2.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymride2.pat rename to files/timidity/instruments/cymride2.pat diff --git a/android/app/src/main/assets/instruments/cymsplsh.pat b/files/timidity/instruments/cymsplsh.pat similarity index 100% rename from android/app/src/main/assets/instruments/cymsplsh.pat rename to files/timidity/instruments/cymsplsh.pat diff --git a/android/app/src/main/assets/instruments/englhorn.pat b/files/timidity/instruments/englhorn.pat similarity index 100% rename from android/app/src/main/assets/instruments/englhorn.pat rename to files/timidity/instruments/englhorn.pat diff --git a/android/app/src/main/assets/instruments/flute.pat b/files/timidity/instruments/flute.pat similarity index 100% rename from android/app/src/main/assets/instruments/flute.pat rename to files/timidity/instruments/flute.pat diff --git a/android/app/src/main/assets/instruments/frenchrn.pat b/files/timidity/instruments/frenchrn.pat similarity index 100% rename from android/app/src/main/assets/instruments/frenchrn.pat rename to files/timidity/instruments/frenchrn.pat diff --git a/android/app/src/main/assets/instruments/guiro1.pat b/files/timidity/instruments/guiro1.pat similarity index 100% rename from android/app/src/main/assets/instruments/guiro1.pat rename to files/timidity/instruments/guiro1.pat diff --git a/android/app/src/main/assets/instruments/guiro2.pat b/files/timidity/instruments/guiro2.pat similarity index 100% rename from android/app/src/main/assets/instruments/guiro2.pat rename to files/timidity/instruments/guiro2.pat diff --git a/android/app/src/main/assets/instruments/harp.pat b/files/timidity/instruments/harp.pat similarity index 100% rename from android/app/src/main/assets/instruments/harp.pat rename to files/timidity/instruments/harp.pat diff --git a/android/app/src/main/assets/instruments/highq.pat b/files/timidity/instruments/highq.pat similarity index 100% rename from android/app/src/main/assets/instruments/highq.pat rename to files/timidity/instruments/highq.pat diff --git a/android/app/src/main/assets/instruments/hihatcl.pat b/files/timidity/instruments/hihatcl.pat similarity index 100% rename from android/app/src/main/assets/instruments/hihatcl.pat rename to files/timidity/instruments/hihatcl.pat diff --git a/android/app/src/main/assets/instruments/hihatop.pat b/files/timidity/instruments/hihatop.pat similarity index 100% rename from android/app/src/main/assets/instruments/hihatop.pat rename to files/timidity/instruments/hihatop.pat diff --git a/android/app/src/main/assets/instruments/hihatpd.pat b/files/timidity/instruments/hihatpd.pat similarity index 100% rename from android/app/src/main/assets/instruments/hihatpd.pat rename to files/timidity/instruments/hihatpd.pat diff --git a/android/app/src/main/assets/instruments/hrpschrd.pat b/files/timidity/instruments/hrpschrd.pat similarity index 100% rename from android/app/src/main/assets/instruments/hrpschrd.pat rename to files/timidity/instruments/hrpschrd.pat diff --git a/android/app/src/main/assets/instruments/jingles.pat b/files/timidity/instruments/jingles.pat similarity index 100% rename from android/app/src/main/assets/instruments/jingles.pat rename to files/timidity/instruments/jingles.pat diff --git a/android/app/src/main/assets/instruments/kick1.pat b/files/timidity/instruments/kick1.pat similarity index 100% rename from android/app/src/main/assets/instruments/kick1.pat rename to files/timidity/instruments/kick1.pat diff --git a/android/app/src/main/assets/instruments/kick2.pat b/files/timidity/instruments/kick2.pat similarity index 100% rename from android/app/src/main/assets/instruments/kick2.pat rename to files/timidity/instruments/kick2.pat diff --git a/android/app/src/main/assets/instruments/maracas.pat b/files/timidity/instruments/maracas.pat similarity index 100% rename from android/app/src/main/assets/instruments/maracas.pat rename to files/timidity/instruments/maracas.pat diff --git a/android/app/src/main/assets/instruments/marcato.pat b/files/timidity/instruments/marcato.pat similarity index 100% rename from android/app/src/main/assets/instruments/marcato.pat rename to files/timidity/instruments/marcato.pat diff --git a/android/app/src/main/assets/instruments/metbell.pat b/files/timidity/instruments/metbell.pat similarity index 100% rename from android/app/src/main/assets/instruments/metbell.pat rename to files/timidity/instruments/metbell.pat diff --git a/android/app/src/main/assets/instruments/metclick.pat b/files/timidity/instruments/metclick.pat similarity index 100% rename from android/app/src/main/assets/instruments/metclick.pat rename to files/timidity/instruments/metclick.pat diff --git a/android/app/src/main/assets/instruments/nyguitar.pat b/files/timidity/instruments/nyguitar.pat similarity index 100% rename from android/app/src/main/assets/instruments/nyguitar.pat rename to files/timidity/instruments/nyguitar.pat diff --git a/android/app/src/main/assets/instruments/oboe.pat b/files/timidity/instruments/oboe.pat similarity index 100% rename from android/app/src/main/assets/instruments/oboe.pat rename to files/timidity/instruments/oboe.pat diff --git a/android/app/src/main/assets/instruments/piccolo.pat b/files/timidity/instruments/piccolo.pat similarity index 100% rename from android/app/src/main/assets/instruments/piccolo.pat rename to files/timidity/instruments/piccolo.pat diff --git a/android/app/src/main/assets/instruments/pizzcato.pat b/files/timidity/instruments/pizzcato.pat similarity index 100% rename from android/app/src/main/assets/instruments/pizzcato.pat rename to files/timidity/instruments/pizzcato.pat diff --git a/android/app/src/main/assets/instruments/scratch1.pat b/files/timidity/instruments/scratch1.pat similarity index 100% rename from android/app/src/main/assets/instruments/scratch1.pat rename to files/timidity/instruments/scratch1.pat diff --git a/android/app/src/main/assets/instruments/scratch2.pat b/files/timidity/instruments/scratch2.pat similarity index 100% rename from android/app/src/main/assets/instruments/scratch2.pat rename to files/timidity/instruments/scratch2.pat diff --git a/android/app/src/main/assets/instruments/shaker.pat b/files/timidity/instruments/shaker.pat similarity index 100% rename from android/app/src/main/assets/instruments/shaker.pat rename to files/timidity/instruments/shaker.pat diff --git a/android/app/src/main/assets/instruments/slap.pat b/files/timidity/instruments/slap.pat similarity index 100% rename from android/app/src/main/assets/instruments/slap.pat rename to files/timidity/instruments/slap.pat diff --git a/android/app/src/main/assets/instruments/slowstr.pat b/files/timidity/instruments/slowstr.pat similarity index 100% rename from android/app/src/main/assets/instruments/slowstr.pat rename to files/timidity/instruments/slowstr.pat diff --git a/android/app/src/main/assets/instruments/snap.pat b/files/timidity/instruments/snap.pat similarity index 100% rename from android/app/src/main/assets/instruments/snap.pat rename to files/timidity/instruments/snap.pat diff --git a/android/app/src/main/assets/instruments/snare1.pat b/files/timidity/instruments/snare1.pat similarity index 100% rename from android/app/src/main/assets/instruments/snare1.pat rename to files/timidity/instruments/snare1.pat diff --git a/android/app/src/main/assets/instruments/snare2.pat b/files/timidity/instruments/snare2.pat similarity index 100% rename from android/app/src/main/assets/instruments/snare2.pat rename to files/timidity/instruments/snare2.pat diff --git a/android/app/src/main/assets/instruments/snarerol.pat b/files/timidity/instruments/snarerol.pat similarity index 100% rename from android/app/src/main/assets/instruments/snarerol.pat rename to files/timidity/instruments/snarerol.pat diff --git a/android/app/src/main/assets/instruments/sqrclick.pat b/files/timidity/instruments/sqrclick.pat similarity index 100% rename from android/app/src/main/assets/instruments/sqrclick.pat rename to files/timidity/instruments/sqrclick.pat diff --git a/android/app/src/main/assets/instruments/stickrim.pat b/files/timidity/instruments/stickrim.pat similarity index 100% rename from android/app/src/main/assets/instruments/stickrim.pat rename to files/timidity/instruments/stickrim.pat diff --git a/android/app/src/main/assets/instruments/sticks.pat b/files/timidity/instruments/sticks.pat similarity index 100% rename from android/app/src/main/assets/instruments/sticks.pat rename to files/timidity/instruments/sticks.pat diff --git a/android/app/src/main/assets/instruments/surdo1.pat b/files/timidity/instruments/surdo1.pat similarity index 100% rename from android/app/src/main/assets/instruments/surdo1.pat rename to files/timidity/instruments/surdo1.pat diff --git a/android/app/src/main/assets/instruments/surdo2.pat b/files/timidity/instruments/surdo2.pat similarity index 100% rename from android/app/src/main/assets/instruments/surdo2.pat rename to files/timidity/instruments/surdo2.pat diff --git a/android/app/src/main/assets/instruments/synstr2.pat b/files/timidity/instruments/synstr2.pat similarity index 100% rename from android/app/src/main/assets/instruments/synstr2.pat rename to files/timidity/instruments/synstr2.pat diff --git a/android/app/src/main/assets/instruments/taiko.pat b/files/timidity/instruments/taiko.pat similarity index 100% rename from android/app/src/main/assets/instruments/taiko.pat rename to files/timidity/instruments/taiko.pat diff --git a/android/app/src/main/assets/instruments/tamborin.pat b/files/timidity/instruments/tamborin.pat similarity index 100% rename from android/app/src/main/assets/instruments/tamborin.pat rename to files/timidity/instruments/tamborin.pat diff --git a/android/app/src/main/assets/instruments/timbaleh.pat b/files/timidity/instruments/timbaleh.pat similarity index 100% rename from android/app/src/main/assets/instruments/timbaleh.pat rename to files/timidity/instruments/timbaleh.pat diff --git a/android/app/src/main/assets/instruments/timbalel.pat b/files/timidity/instruments/timbalel.pat similarity index 100% rename from android/app/src/main/assets/instruments/timbalel.pat rename to files/timidity/instruments/timbalel.pat diff --git a/android/app/src/main/assets/instruments/timpani.pat b/files/timidity/instruments/timpani.pat similarity index 100% rename from android/app/src/main/assets/instruments/timpani.pat rename to files/timidity/instruments/timpani.pat diff --git a/android/app/src/main/assets/instruments/tomhi1.pat b/files/timidity/instruments/tomhi1.pat similarity index 100% rename from android/app/src/main/assets/instruments/tomhi1.pat rename to files/timidity/instruments/tomhi1.pat diff --git a/android/app/src/main/assets/instruments/tomhi2.pat b/files/timidity/instruments/tomhi2.pat similarity index 100% rename from android/app/src/main/assets/instruments/tomhi2.pat rename to files/timidity/instruments/tomhi2.pat diff --git a/android/app/src/main/assets/instruments/tomlo1.pat b/files/timidity/instruments/tomlo1.pat similarity index 100% rename from android/app/src/main/assets/instruments/tomlo1.pat rename to files/timidity/instruments/tomlo1.pat diff --git a/android/app/src/main/assets/instruments/tomlo2.pat b/files/timidity/instruments/tomlo2.pat similarity index 100% rename from android/app/src/main/assets/instruments/tomlo2.pat rename to files/timidity/instruments/tomlo2.pat diff --git a/android/app/src/main/assets/instruments/tommid1.pat b/files/timidity/instruments/tommid1.pat similarity index 100% rename from android/app/src/main/assets/instruments/tommid1.pat rename to files/timidity/instruments/tommid1.pat diff --git a/android/app/src/main/assets/instruments/tommid2.pat b/files/timidity/instruments/tommid2.pat similarity index 100% rename from android/app/src/main/assets/instruments/tommid2.pat rename to files/timidity/instruments/tommid2.pat diff --git a/android/app/src/main/assets/instruments/tremstr.pat b/files/timidity/instruments/tremstr.pat similarity index 100% rename from android/app/src/main/assets/instruments/tremstr.pat rename to files/timidity/instruments/tremstr.pat diff --git a/android/app/src/main/assets/instruments/triangl1.pat b/files/timidity/instruments/triangl1.pat similarity index 100% rename from android/app/src/main/assets/instruments/triangl1.pat rename to files/timidity/instruments/triangl1.pat diff --git a/android/app/src/main/assets/instruments/triangl2.pat b/files/timidity/instruments/triangl2.pat similarity index 100% rename from android/app/src/main/assets/instruments/triangl2.pat rename to files/timidity/instruments/triangl2.pat diff --git a/android/app/src/main/assets/instruments/trombone.pat b/files/timidity/instruments/trombone.pat similarity index 100% rename from android/app/src/main/assets/instruments/trombone.pat rename to files/timidity/instruments/trombone.pat diff --git a/android/app/src/main/assets/instruments/vibslap.pat b/files/timidity/instruments/vibslap.pat similarity index 100% rename from android/app/src/main/assets/instruments/vibslap.pat rename to files/timidity/instruments/vibslap.pat diff --git a/android/app/src/main/assets/instruments/viola.pat b/files/timidity/instruments/viola.pat similarity index 100% rename from android/app/src/main/assets/instruments/viola.pat rename to files/timidity/instruments/viola.pat diff --git a/android/app/src/main/assets/instruments/whistle1.pat b/files/timidity/instruments/whistle1.pat similarity index 100% rename from android/app/src/main/assets/instruments/whistle1.pat rename to files/timidity/instruments/whistle1.pat diff --git a/android/app/src/main/assets/instruments/whistle2.pat b/files/timidity/instruments/whistle2.pat similarity index 100% rename from android/app/src/main/assets/instruments/whistle2.pat rename to files/timidity/instruments/whistle2.pat diff --git a/android/app/src/main/assets/instruments/woodblk1.pat b/files/timidity/instruments/woodblk1.pat similarity index 100% rename from android/app/src/main/assets/instruments/woodblk1.pat rename to files/timidity/instruments/woodblk1.pat diff --git a/android/app/src/main/assets/instruments/woodblk2.pat b/files/timidity/instruments/woodblk2.pat similarity index 100% rename from android/app/src/main/assets/instruments/woodblk2.pat rename to files/timidity/instruments/woodblk2.pat diff --git a/android/app/src/main/assets/timidity.cfg b/files/timidity/timidity.cfg similarity index 100% rename from android/app/src/main/assets/timidity.cfg rename to files/timidity/timidity.cfg diff --git a/src/dist/Makefile b/src/dist/Makefile index 4e1c76db630..103f1a860cc 100644 --- a/src/dist/Makefile +++ b/src/dist/Makefile @@ -1,6 +1,6 @@ ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # -# Copyright (C) 2021 - 2024 # +# Copyright (C) 2021 - 2025 # # # # This program is free software; you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # @@ -159,7 +159,11 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER $(MAKE) -C thirdparty/libsmacker CCFLAGS="$(CCFLAGS_TP)" CFLAGS="$(CFLAGS_TP)" CXXFLAGS="$(CXXFLAGS_TP)" CPPFLAGS="$(CPPFLAGS_TP)" endif $(MAKE) -C engine +ifeq ($(PLATFORM),emscripten) + $(MAKE) -C fheroes2-ems +else $(MAKE) -C fheroes2 +endif ifdef FHEROES2_WITH_TOOLS $(MAKE) -C tools endif @@ -169,5 +173,9 @@ ifndef FHEROES2_WITH_SYSTEM_SMACKER $(MAKE) -C thirdparty/libsmacker clean endif $(MAKE) -C engine clean +ifeq ($(PLATFORM),emscripten) + $(MAKE) -C fheroes2-ems clean +else $(MAKE) -C fheroes2 clean +endif $(MAKE) -C tools clean diff --git a/src/dist/Makefile.emscripten b/src/dist/Makefile.emscripten new file mode 100644 index 00000000000..98e74871f08 --- /dev/null +++ b/src/dist/Makefile.emscripten @@ -0,0 +1,43 @@ +########################################################################### +# fheroes2: https://github.com/ihhub/fheroes2 # +# Copyright (C) 2025 # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +########################################################################### + +CCFLAGS := $(filter-out -pthread,$(CCFLAGS)) \ + --use-port=sdl2 \ + --use-port=sdl2_mixer \ + --use-port=zlib \ + -Wno-variadic-macro-arguments-omitted + +LDFLAGS := $(filter-out -pthread,$(LDFLAGS)) \ + --preload-file ../../../files/data/resurrection.h2d@/files/data/resurrection.h2d \ + --preload-file ../../../files/lang/@/files/lang/ \ + --preload-file ../../../files/timidity/@/etc/ \ + -sASYNCIFY \ + -sASYNCIFY_STACK_SIZE=20480 \ + -sENVIRONMENT=web \ + -sINITIAL_MEMORY=128mb \ + -sNO_DISABLE_EXCEPTION_CATCHING \ + -sSDL2_MIXER_FORMATS=mid,mp3,ogg \ + -sSTACK_SIZE=262144 \ + -lidbfs.js + +ifdef FHEROES2_WITH_DEBUG +CCFLAGS := $(CCFLAGS) -gsource-map +LDFLAGS := $(LDFLAGS) -gsource-map +endif diff --git a/src/dist/fheroes2-ems/Makefile b/src/dist/fheroes2-ems/Makefile new file mode 100644 index 00000000000..200a2fe3c13 --- /dev/null +++ b/src/dist/fheroes2-ems/Makefile @@ -0,0 +1,48 @@ +########################################################################### +# fheroes2: https://github.com/ihhub/fheroes2 # +# Copyright (C) 2025 # +# # +# This program is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 2 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program; if not, write to the # +# Free Software Foundation, Inc., # +# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # +########################################################################### + +DEPLIBS := ../engine/libengine.a +CCFLAGS := $(CCFLAGS) -I../../engine + +ifndef FHEROES2_WITH_SYSTEM_SMACKER +DEPLIBS := $(DEPLIBS) ../thirdparty/libsmacker/libsmacker.a +CCFLAGS := $(CCFLAGS) -I../../thirdparty/libsmacker +endif + +SOURCEROOT := ../../fheroes2 +SOURCEDIRS := $(filter %/,$(wildcard $(SOURCEROOT)/*/)) +SOURCES := $(wildcard $(SOURCEROOT)/*/*.cpp) + +VPATH := $(SOURCEDIRS) + +.PHONY: all clean + +all: fheroes2.js + +fheroes2.js: $(notdir $(patsubst %.cpp, %.o, $(SOURCES))) $(DEPLIBS) + $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) + +%.o: %.cpp + $(CXX) -c -MD $< $(addprefix -I, $(SOURCEDIRS)) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) + +include $(wildcard *.d) + +clean: + rm -f *.d *.o fheroes2.data fheroes2.js fheroes2.wasm diff --git a/src/engine/audio.cpp b/src/engine/audio.cpp index f152d36e63d..731954a1ca4 100644 --- a/src/engine/audio.cpp +++ b/src/engine/audio.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Andrey Afletdinov * @@ -540,6 +540,8 @@ namespace // Either there is no need to resume music playback, or the resumption failed. Let's try to // start the playback from the beginning. if ( returnCode != 0 ) { + track->setPosition( 0 ); + returnCode = Mix_FadeInMusic( mus.get(), loopCount, musicFadeInMs ); if ( returnCode != 0 ) { diff --git a/src/engine/serialize.cpp b/src/engine/serialize.cpp index d528e8b5f72..e05b777bd91 100644 --- a/src/engine/serialize.cpp +++ b/src/engine/serialize.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * @@ -29,6 +29,16 @@ #include #include +#ifdef __EMSCRIPTEN__ +#include + +#include +#include +#include + +#include "tools.h" +#endif + #include "logging.h" namespace @@ -622,3 +632,41 @@ std::string StreamFile::getString( const size_t size /* = 0 */ ) return { buf.begin(), std::find( buf.begin(), buf.end(), 0 ) }; } + +int StreamFile::closeFile( std::FILE * f ) +{ +#ifdef __EMSCRIPTEN__ + const bool needSyncFS = [f]() { + static_assert( CountBits( O_WRONLY ) == 1 && CountBits( O_RDWR ) == 1 ); + + const int fn = fileno( f ); + if ( fn < 0 ) { + ERROR_LOG( "fileno() error: " << errno ) + return false; + } + + const int flags = fcntl( fn, F_GETFL ); + if ( flags < 0 ) { + ERROR_LOG( "fcntl() error: " << errno ) + return false; + } + + return ( flags & O_WRONLY ) || ( flags & O_RDWR ); + }(); +#endif + + const int res = std::fclose( f ); + +#ifdef __EMSCRIPTEN__ + if ( needSyncFS ) { + EM_ASM( + // The following code is not C++ code, but JavaScript code. + // clang-format off + FS.syncfs( err => err && console.warn( "FS.syncfs() error:", err ) ) + // clang-format on + ); + } +#endif + + return res; +} diff --git a/src/engine/serialize.h b/src/engine/serialize.h index efc71abfb30..05883448e55 100644 --- a/src/engine/serialize.h +++ b/src/engine/serialize.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * @@ -669,7 +669,9 @@ class StreamFile final : public IStreamBase, public OStreamBase } } - std::unique_ptr _file{ nullptr, []( std::FILE * f ) { return std::fclose( f ); } }; + static int closeFile( std::FILE * f ); + + std::unique_ptr _file{ nullptr, closeFile }; }; namespace fheroes2 diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp index 10f86736900..e396ae026fa 100644 --- a/src/engine/thread.cpp +++ b/src/engine/thread.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2022 - 2023 * + * Copyright (C) 2022 - 2025 * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -23,10 +23,39 @@ #include #include +namespace +{ +#ifdef __EMSCRIPTEN__ + class MutexUnlocker + { + public: + explicit MutexUnlocker( std::mutex & mutex ) + : _mutex( mutex ) + { + _mutex.unlock(); + } + + MutexUnlocker( const MutexUnlocker & ) = delete; + + ~MutexUnlocker() + { + _mutex.lock(); + } + + MutexUnlocker & operator=( const MutexUnlocker & ) = delete; + + private: + std::mutex & _mutex; + }; +#endif +} + namespace MultiThreading { void AsyncManager::createWorker() { + // Emscripten has no pthread support. The worker thread should not be started. +#ifndef __EMSCRIPTEN__ if ( !_worker ) { _runFlag = true; _worker = std::make_unique( AsyncManager::_workerThread, this ); @@ -37,10 +66,15 @@ namespace MultiThreading _masterNotification.wait( lock, [this] { return !_runFlag; } ); } } +#endif } void AsyncManager::stopWorker() { +#ifdef __EMSCRIPTEN__ + // Emscripten has no pthread support. The worker thread should not be running here. + assert( !_worker ); +#else if ( _worker ) { { const std::scoped_lock lock( _mutex ); @@ -54,13 +88,33 @@ namespace MultiThreading _worker->join(); _worker.reset(); } +#endif } void AsyncManager::notifyWorker() { _runFlag = true; + // Emscripten has no pthread support, so instead of passing tasks to the worker thread, we will process them immediately in the current thread. +#ifdef __EMSCRIPTEN__ + assert( !_exitFlag ); + + while ( _runFlag ) { + const bool moreTasks = prepareTask(); + if ( !moreTasks ) { + _runFlag = false; + } + + { + // In accordance with the contract, the _mutex should NOT be acquired while calling the executeTask(). + MutexUnlocker unlocker( _mutex ); + + executeTask(); + } + } +#else _workerNotification.notify_all(); +#endif } void AsyncManager::_workerThread( AsyncManager * manager ) diff --git a/src/engine/thread.h b/src/engine/thread.h index d3f9bb00ae2..4885dea2252 100644 --- a/src/engine/thread.h +++ b/src/engine/thread.h @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2022 * + * Copyright (C) 2022 - 2025 * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -53,11 +53,10 @@ namespace MultiThreading // Notify the worker thread about a new task. The _mutex should be acquired while calling this method. void notifyWorker(); - // Prepare a task which requires mutex lock. Returns true if more tasks are available. + // Prepare the next task. The _mutex will be acquired while calling this method. Returns true if more tasks are available. virtual bool prepareTask() = 0; - // Task execution is done in non-thread safe mode! No mutex lock for any means of synchronizations are - // done for this call. + // Execute the next task. NOTE WELL: the _mutex will NOT be acquired while calling this method. virtual void executeTask() = 0; private: diff --git a/src/fheroes2/game/game_hotkeys.cpp b/src/fheroes2/game/game_hotkeys.cpp index be067217449..4df9820230e 100644 --- a/src/fheroes2/game/game_hotkeys.cpp +++ b/src/fheroes2/game/game_hotkeys.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -43,6 +42,7 @@ #include "localevent.h" #include "logging.h" #include "players.h" +#include "serialize.h" #include "settings.h" #include "system.h" #include "tinyconfig.h" @@ -452,17 +452,17 @@ void Game::HotKeysLoad( const std::string & filename ) void Game::HotKeySave() { - // Save the latest information into the file. const std::string filename = System::concatPath( System::GetConfigDirectory( "fheroes2" ), "fheroes2.key" ); - std::fstream file( filename.data(), std::fstream::out | std::fstream::trunc ); - if ( !file ) { - ERROR_LOG( "Unable to open hotkey settings file " << filename ) + StreamFile fileStream; + if ( !fileStream.open( filename, "w" ) ) { + ERROR_LOG( "Unable to open the hotkey settings file " << filename ) return; } - const std::string & data = getHotKeyFileContent(); - file.write( data.data(), data.size() ); + const std::string data = getHotKeyFileContent(); + + fileStream.putRaw( data.data(), data.size() ); } void Game::globalKeyDownEvent( const fheroes2::Key key, const int32_t modifier ) diff --git a/src/fheroes2/system/settings.cpp b/src/fheroes2/system/settings.cpp index 20d5e9636ed..2b822b6365a 100644 --- a/src/fheroes2/system/settings.cpp +++ b/src/fheroes2/system/settings.cpp @@ -1,6 +1,6 @@ /*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * - * Copyright (C) 2019 - 2024 * + * Copyright (C) 2019 - 2025 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * @@ -23,7 +23,6 @@ #include #include -#include #include #include @@ -348,16 +347,14 @@ bool Settings::Save( const std::string_view fileName ) const return false; } - const std::string cfgFilename = System::concatPath( System::GetConfigDirectory( "fheroes2" ), fileName ); - - std::fstream file; - file.open( cfgFilename.data(), std::fstream::out | std::fstream::trunc ); - if ( !file ) { + StreamFile fileStream; + if ( !fileStream.open( System::concatPath( System::GetConfigDirectory( "fheroes2" ), fileName ), "w" ) ) { return false; } - const std::string & data = String(); - file.write( data.data(), data.size() ); + const std::string data = String(); + + fileStream.putRaw( data.data(), data.size() ); return true; }