Skip to content

Commit aed96b1

Browse files
authored
implement sideloaded assets and asynchronous loading for Wasm (MUME#487)
- Move assets (maps, audio, images) from Qt Resources (.qrc) to a dedicated external assets directory. - Add CMake scripts to generate a JSON manifest for assets and patch the WASM HTML launcher. - Implement asynchronous network fetching for assets on the Emscripten platform. - Update file resolution logic in Filenames.cpp to look in the new system asset paths. - Update Dockerfile and installation logic for all platforms (Linux, Win, Mac) to include the assets directory.
1 parent 6ba1453 commit aed96b1

23 files changed

Lines changed: 748 additions & 214 deletions

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ if(WIN32 AND MINGW)
3737
option(WITH_DRMINGW "Include Dr. Mingw crash dumping (Windows only)" ON)
3838
endif()
3939
set(PACKAGE_TYPE "Source" CACHE STRING "Set the package type for update dialog")
40+
set(ASSETS_NAME "assets" CACHE STRING "Name of the sideloaded assets directory")
4041

4142
set(VALID_PACKAGE_TYPES Source Deb Dmg Nsis AppImage AppX Flatpak Snap Wasm)
4243
set(PACKAGE_TYPE_NORMALIZED "")

Dockerfile

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# --- Stage 1: The Builder Stage ---
22
ARG JOBS
33

4-
FROM --platform=${BUILDPLATFORM} ubuntu:24.04 AS builder
4+
FROM ubuntu:24.04 AS builder
55

66
ENV DEBIAN_FRONTEND=noninteractive
77
ENV QTSDK_DIR=/opt/Qt
@@ -45,28 +45,15 @@ RUN ./emsdk activate 4.0.7 && \
4545
-DWITH_OPENSSL=OFF -DWITH_TESTS=OFF -DWITH_WEBSOCKET=ON -DWITH_UPDATER=OFF -DPACKAGE_TYPE=Wasm && \
4646
ACTUAL_JOBS=${JOBS:-$(nproc)} && \
4747
cmake --build /build --parallel ${ACTUAL_JOBS} && \
48-
ls /build/src && \
49-
mkdir /dist && \
50-
cd /build/src && \
51-
cp mmapper.js /dist/ && \
52-
cp mmapper.wasm /dist/ && \
53-
cp qtloader.js /dist/ && \
54-
cp mmapper.html /dist/index.html && \
55-
cp /app/src/resources/win32/m-release.ico /dist/favicon.ico && \
56-
cp /app/src/resources/icons/m-release.png /dist/logo.png && \
57-
cd /dist && \
58-
sed -i 's|<head>|<head>\n <link rel="icon" type="image/x-icon" href="favicon.ico">|' index.html && \
59-
sed -i 's|src="qtlogo.svg" width="320" height="200"|src="logo.png"|g' index.html && \
60-
wget https://raw.githubusercontent.com/gzuidhof/coi-serviceworker/refs/heads/master/coi-serviceworker.js && \
61-
sed -i 's|<head>|<head>\n <script src="./coi-serviceworker.js"></script>|' index.html && \
48+
cmake --install /build --prefix /dist && \
6249
rm -rf /build /app
6350

6451
# --- Stage 2: The Final Runtime Stage (Nginx) ---
6552
FROM nginx:alpine
6653

6754
WORKDIR /usr/share/nginx/html
6855
RUN rm -rf ./*
69-
COPY --from=builder /dist/* .
56+
COPY --from=builder /dist/ .
7057

7158
EXPOSE 80
7259

appdata/CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Linux Install Settings
2-
if(UNIX AND NOT APPLE)
2+
if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN)
33
configure_file(org.mume.MMapper.appdata.xml.in ${CMAKE_CURRENT_BINARY_DIR}/org.mume.MMapper.appdata.xml)
44
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.mume.MMapper.appdata.xml
55
DESTINATION share/metainfo
66
COMPONENT desktopintegration
77
)
8-
endif(UNIX AND NOT APPLE)
8+
endif()

cmake/GenerateQRC.cmake

Lines changed: 0 additions & 34 deletions
This file was deleted.

cmake/WasmHtml.cmake

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# cmake/WasmHtml.cmake
2+
# Post-processes the Qt-generated Wasm HTML file:
3+
# - Injects a favicon link tag
4+
# - Replaces the Qt logo reference with the MMapper logo
5+
# - Injects the COI service worker script tag
6+
# - Copies favicon.ico and logo.png alongside the HTML
7+
# - Downloads coi-serviceworker.js alongside the HTML
8+
#
9+
# Usage (as a post-build cmake -P script):
10+
# cmake -DHTML_FILE=<path/mmapper.html>
11+
# -DFAVICON_SRC=<path/m-release.ico>
12+
# -DLOGO_SRC=<path/m-release.png>
13+
# -P WasmHtml.cmake
14+
15+
foreach(var HTML_FILE FAVICON_SRC LOGO_SRC)
16+
if(NOT ${var})
17+
message(FATAL_ERROR "${var} not specified for WasmHtml.cmake")
18+
endif()
19+
endforeach()
20+
21+
file(READ "${HTML_FILE}" HTML_CONTENT)
22+
23+
# Inject favicon link and COI service worker script after <head>
24+
string(REPLACE
25+
"<head>"
26+
"<head>\n <link rel=\"icon\" type=\"image/x-icon\" href=\"favicon.ico\">\n <script src=\"./coi-serviceworker.js\"></script>"
27+
HTML_CONTENT "${HTML_CONTENT}"
28+
)
29+
30+
# Replace Qt logo with MMapper logo
31+
string(REPLACE
32+
"src=\"qtlogo.svg\" width=\"320\" height=\"200\""
33+
"src=\"logo.png\""
34+
HTML_CONTENT "${HTML_CONTENT}"
35+
)
36+
37+
file(WRITE "${HTML_FILE}" "${HTML_CONTENT}")
38+
39+
# Copy assets alongside the HTML (use configure_file for portable rename support)
40+
get_filename_component(OUT_DIR "${HTML_FILE}" DIRECTORY)
41+
configure_file("${FAVICON_SRC}" "${OUT_DIR}/favicon.ico" COPYONLY)
42+
configure_file("${LOGO_SRC}" "${OUT_DIR}/logo.png" COPYONLY)
43+
44+
# Download coi-serviceworker.js if not already present
45+
set(COI_JS "${OUT_DIR}/coi-serviceworker.js")
46+
if(NOT EXISTS "${COI_JS}")
47+
message(STATUS "Downloading coi-serviceworker.js...")
48+
file(DOWNLOAD
49+
"https://raw.githubusercontent.com/gzuidhof/coi-serviceworker/refs/heads/master/coi-serviceworker.js"
50+
"${COI_JS}"
51+
STATUS DOWNLOAD_STATUS
52+
)
53+
list(GET DOWNLOAD_STATUS 0 DOWNLOAD_CODE)
54+
if(NOT DOWNLOAD_CODE EQUAL 0)
55+
list(GET DOWNLOAD_STATUS 1 DOWNLOAD_ERROR)
56+
message(WARNING "Failed to download coi-serviceworker.js: ${DOWNLOAD_ERROR}. "
57+
"The Wasm build may not function correctly in cross-origin contexts.")
58+
endif()
59+
endif()
60+
61+
message(STATUS "Patched Wasm HTML: ${HTML_FILE}")

cmake/WasmManifest.cmake

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# cmake/WasmManifest.cmake
2+
# Scans an assets directory and generates a JSON manifest to asset paths.
3+
#
4+
# Usage:
5+
# cmake -DASSETS_DIR=<path> -DOUTPUT_FILE=<path> -P WasmManifest.cmake
6+
#
7+
# The manifest maps "subdir/basename.ext" for each
8+
# audio (.mp3, .ogg, .opus, .webm) and image (.jpg, .png) file found, plus the
9+
# map file "arda" if present.
10+
11+
if(NOT ASSETS_DIR)
12+
message(FATAL_ERROR "ASSETS_DIR not specified for WasmManifest.cmake")
13+
endif()
14+
if(NOT OUTPUT_FILE)
15+
message(FATAL_ERROR "OUTPUT_FILE not specified for WasmManifest.cmake")
16+
endif()
17+
18+
file(GLOB_RECURSE AUDIO_FILES RELATIVE "${ASSETS_DIR}"
19+
"${ASSETS_DIR}/*.mp3"
20+
"${ASSETS_DIR}/*.ogg"
21+
"${ASSETS_DIR}/*.opus"
22+
"${ASSETS_DIR}/*.webm"
23+
)
24+
25+
file(GLOB_RECURSE IMAGE_FILES RELATIVE "${ASSETS_DIR}"
26+
"${ASSETS_DIR}/*.jpg"
27+
"${ASSETS_DIR}/*.png"
28+
)
29+
30+
file(GLOB MAP_FILES RELATIVE "${ASSETS_DIR}"
31+
"${ASSETS_DIR}/map/arda"
32+
)
33+
34+
set(JSON_CONTENT "[\n")
35+
set(FIRST_ENTRY TRUE)
36+
37+
foreach(rel_path IN LISTS AUDIO_FILES IMAGE_FILES MAP_FILES)
38+
if(NOT FIRST_ENTRY)
39+
string(APPEND JSON_CONTENT ",\n")
40+
endif()
41+
string(APPEND JSON_CONTENT " \"${rel_path}\"")
42+
set(FIRST_ENTRY FALSE)
43+
endforeach()
44+
45+
string(APPEND JSON_CONTENT "\n]\n")
46+
47+
file(WRITE "${OUTPUT_FILE}" "${JSON_CONTENT}")
48+
message(STATUS "Generated manifest: ${OUTPUT_FILE}")
49+
50+
51+
file(SIZE "${OUTPUT_FILE}" _FILE_SIZE)
52+
message(STATUS "Audio: ${AUDIO_FILES}")
53+
message(STATUS "Found Images: ${IMAGE_FILES}")
54+
message(STATUS "Manifest Size: ${_FILE_SIZE}")
55+
56+
if(_FILE_SIZE LESS 10)
57+
message(FATAL_ERROR "${OUTPUT_FILE} is empty or contains no assets!")
58+
endif()

external/audio/CMakeLists.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ if(WITH_AUDIO_UPPER STREQUAL "OFF" OR WITH_AUDIO_UPPER STREQUAL "FALSE")
44
message(STATUS "Audio resources disabled.")
55
else()
66
set(AUDIO_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/audio")
7-
set(MMAPPER_QRC_FILE "${AUDIO_DOWNLOAD_DIR}/audio.qrc")
87
set(AUDIO_TARGET_DEPENDENCY "")
98

109
file(MAKE_DIRECTORY "${AUDIO_DOWNLOAD_DIR}")
@@ -47,16 +46,18 @@ else()
4746
set(AUDIO_TARGET_DEPENDENCY audio_repo)
4847
endif()
4948

50-
# Generate QRC from audio
49+
# For all platforms: copy assets to the sideloaded assets directory
50+
set(AUDIO_ASSETS_STAMP "${AUDIO_DOWNLOAD_DIR}/CMakeLists.txt.assets_stamp")
51+
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${ASSETS_NAME}")
5152
add_custom_command(
52-
OUTPUT "${MMAPPER_QRC_FILE}"
53-
COMMAND ${CMAKE_COMMAND} -DINPUT_DIR=${AUDIO_DOWNLOAD_DIR} -DOUTPUT_FILE=${MMAPPER_QRC_FILE} -P "${CMAKE_SOURCE_DIR}/cmake/GenerateQRC.cmake"
53+
OUTPUT "${AUDIO_ASSETS_STAMP}"
54+
COMMAND ${CMAKE_COMMAND} -E copy_directory "${AUDIO_DOWNLOAD_DIR}" "${CMAKE_BINARY_DIR}/${ASSETS_NAME}"
55+
COMMAND ${CMAKE_COMMAND} -E touch "${AUDIO_ASSETS_STAMP}"
5456
DEPENDS "${AUDIO_TARGET_DEPENDENCY}"
55-
COMMENT "Generating audio.qrc"
56-
WORKING_DIRECTORY "${AUDIO_DOWNLOAD_DIR}"
57+
COMMENT "Copying audio assets to ${ASSETS_NAME} directory"
5758
VERBATIM
5859
)
59-
add_custom_target(audio_qrc ALL DEPENDS "${MMAPPER_QRC_FILE}")
60-
add_dependencies(audio_qrc ${AUDIO_TARGET_DEPENDENCY})
61-
message(STATUS "Audio QRC file will be included: ${MMAPPER_QRC_FILE}")
60+
add_custom_target(audio_assets ALL DEPENDS "${AUDIO_ASSETS_STAMP}")
61+
add_dependencies(audio_assets ${AUDIO_TARGET_DEPENDENCY})
62+
message(STATUS "Audio assets will be sideloaded: ${CMAKE_BINARY_DIR}/${ASSETS_NAME}")
6263
endif()

external/images/CMakeLists.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ if(WITH_IMAGES_UPPER STREQUAL "OFF" OR WITH_IMAGES_UPPER STREQUAL "FALSE")
44
message(STATUS "Image resources disabled.")
55
else()
66
set(IMAGES_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/images")
7-
set(MMAPPER_QRC_FILE "${IMAGES_DOWNLOAD_DIR}/images.qrc")
87
set(IMAGES_TARGET_DEPENDENCY "")
98

109
file(MAKE_DIRECTORY "${IMAGES_DOWNLOAD_DIR}")
@@ -47,16 +46,18 @@ else()
4746
set(IMAGES_TARGET_DEPENDENCY images_repo)
4847
endif()
4948

50-
# Generate QRC from images
49+
# For all platforms: copy assets to the sideloaded assets directory
50+
set(IMAGES_ASSETS_STAMP "${IMAGES_DOWNLOAD_DIR}/CMakeLists.txt.assets_stamp")
51+
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${ASSETS_NAME}")
5152
add_custom_command(
52-
OUTPUT "${MMAPPER_QRC_FILE}"
53-
COMMAND ${CMAKE_COMMAND} -DINPUT_DIR=${IMAGES_DOWNLOAD_DIR} -DOUTPUT_FILE=${MMAPPER_QRC_FILE} -P "${CMAKE_SOURCE_DIR}/cmake/GenerateQRC.cmake"
53+
OUTPUT "${IMAGES_ASSETS_STAMP}"
54+
COMMAND ${CMAKE_COMMAND} -E copy_directory "${IMAGES_DOWNLOAD_DIR}" "${CMAKE_BINARY_DIR}/${ASSETS_NAME}"
55+
COMMAND ${CMAKE_COMMAND} -E touch "${IMAGES_ASSETS_STAMP}"
5456
DEPENDS "${IMAGES_TARGET_DEPENDENCY}"
55-
COMMENT "Generating images.qrc"
56-
WORKING_DIRECTORY "${IMAGES_DOWNLOAD_DIR}"
57+
COMMENT "Copying image assets to ${ASSETS_NAME} directory"
5758
VERBATIM
5859
)
59-
add_custom_target(images_qrc ALL DEPENDS "${MMAPPER_QRC_FILE}")
60-
add_dependencies(images_qrc ${IMAGES_TARGET_DEPENDENCY})
61-
message(STATUS "Images QRC file will be included: ${MMAPPER_QRC_FILE}")
60+
add_custom_target(images_assets ALL DEPENDS "${IMAGES_ASSETS_STAMP}")
61+
add_dependencies(images_assets ${IMAGES_TARGET_DEPENDENCY})
62+
message(STATUS "Image assets will be sideloaded: ${CMAKE_BINARY_DIR}/${ASSETS_NAME}")
6263
endif()

external/map/CMakeLists.txt

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ if(WITH_MAP_UPPER STREQUAL "OFF" OR WITH_MAP_UPPER STREQUAL "FALSE")
44
message(STATUS "Map integration disabled.")
55
else()
66
set(MAP_DOWNLOAD_DIR "${CMAKE_BINARY_DIR}/map")
7-
set(MAP_QRC_FILE "${MAP_DOWNLOAD_DIR}/map.qrc")
87
set(MAP_FILE "${MAP_DOWNLOAD_DIR}/arda")
98
set(MAP_TARGET_DEPENDENCY "")
109

@@ -46,16 +45,20 @@ else()
4645
set(MAP_TARGET_DEPENDENCY map_repo)
4746
endif()
4847

49-
# Generate QRC
48+
# For all platforms: copy map to the sideloaded assets directory
49+
set(MAP_ASSETS_DIR "${CMAKE_BINARY_DIR}/${ASSETS_NAME}/map")
50+
set(MAP_ASSETS_FILE "${MAP_ASSETS_DIR}/arda")
51+
set(MAP_ASSETS_STAMP "${MAP_DOWNLOAD_DIR}/CMakeLists.txt.assets_stamp")
52+
file(MAKE_DIRECTORY "${MAP_ASSETS_DIR}")
5053
add_custom_command(
51-
OUTPUT "${MAP_QRC_FILE}"
52-
COMMAND ${CMAKE_COMMAND} -DINPUT_DIR=${MAP_DOWNLOAD_DIR} -DOUTPUT_FILE=${MAP_QRC_FILE} -P "${CMAKE_SOURCE_DIR}/cmake/GenerateQRC.cmake"
54+
OUTPUT "${MAP_ASSETS_STAMP}"
55+
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${MAP_FILE}" "${MAP_ASSETS_FILE}"
56+
COMMAND ${CMAKE_COMMAND} -E touch "${MAP_ASSETS_STAMP}"
5357
DEPENDS "${MAP_FILE}"
54-
COMMENT "Generating map.qrc"
55-
WORKING_DIRECTORY "${MAP_DOWNLOAD_DIR}"
58+
COMMENT "Copying map to ${ASSETS_NAME} directory"
5659
VERBATIM
5760
)
58-
add_custom_target(map_qrc ALL DEPENDS "${MAP_QRC_FILE}")
59-
add_dependencies(map_qrc ${MAP_TARGET_DEPENDENCY})
60-
message(STATUS "Map QRC file will be included: ${MAP_QRC_FILE}")
61+
add_custom_target(map_assets ALL DEPENDS "${MAP_ASSETS_STAMP}")
62+
add_dependencies(map_assets ${MAP_TARGET_DEPENDENCY})
63+
message(STATUS "Map will be sideloaded: ${MAP_ASSETS_FILE}")
6164
endif()

0 commit comments

Comments
 (0)