From 1e85a77276793d26379372a00c8560f9ac0d5192 Mon Sep 17 00:00:00 2001 From: Vincent Mercier Date: Fri, 21 Aug 2026 12:45:55 +0200 Subject: [PATCH] perf: accelerate web startup and French pack extraction --- Dockerfile | 15 +++++++++++---- README.fr.md | 2 +- compose.yaml | 2 +- folder_unzipper.gd | 6 ++++-- nginx.conf | 2 ++ .../language_selection/package_downloader.gd | 12 +++++++++--- 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 224b8e16..afd95c14 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,6 +5,7 @@ ARG GODOT_VERSION=4.6.3 FROM ubuntu:24.04 AS web-builder ARG GODOT_VERSION ARG SQLITE_VERSION=4.8 +ARG WEB_BUILD_ID=3.0.1-fr-joy-fast ARG TARGETARCH RUN apt-get update \ @@ -43,11 +44,11 @@ RUN mkdir -p dist \ && find .godot/imported -name 'kalulu_sprite_sheet.png-*.ctex' ! -name '*.etc2.ctex' ! -name '*.s3tc.ctex' -type f -size +0c | grep -q . RUN godot --headless --path /src --export-release "Web Test" dist/index.html \ - && mv dist/index.pck dist/index-3.0.1-fr-joy.pck \ - && sed -i 's|"index.pck":|"index-3.0.1-fr-joy.pck":|; s|"focusCanvas":true,|"focusCanvas":true,"mainPack":"index-3.0.1-fr-joy.pck",|' dist/index.html \ + && mv dist/index.pck "dist/index-${WEB_BUILD_ID}.pck" \ + && sed -i "s|\"index.pck\":|\"index-${WEB_BUILD_ID}.pck\":|; s|\"focusCanvas\":true,|\"focusCanvas\":true,\"mainPack\":\"index-${WEB_BUILD_ID}.pck\",|" dist/index.html \ && test -s dist/index.html \ && test -s dist/index.wasm \ - && test -s dist/index-3.0.1-fr-joy.pck + && test -s "dist/index-${WEB_BUILD_ID}.pck" FROM alpine:3.22 AS french-content ARG FRENCH_PACK_VERSION=v20260615-9e5d0d4 @@ -56,9 +57,15 @@ RUN apk add --no-cache ca-certificates curl \ && curl -fsSL "https://github.com/Excello-Recherche-Education/Kalulu-Languages/releases/download/${FRENCH_PACK_VERSION}/fr_FR.zip" -o /content/fr_FR.zip \ && test -s /content/fr_FR.zip +FROM alpine:3.22 AS compressed-web +RUN apk add --no-cache gzip +COPY --from=web-builder /src/dist/ /web/ +RUN find /web -type f \( -name '*.js' -o -name '*.pck' -o -name '*.wasm' \) \ + -exec gzip -9 -k {} \; + FROM nginx:1.29.1-alpine AS runtime COPY nginx.conf /etc/nginx/conf.d/default.conf -COPY --from=web-builder /src/dist/ /usr/share/nginx/html/ +COPY --from=compressed-web /web/ /usr/share/nginx/html/ COPY --from=french-content /content/fr_FR.zip /usr/share/nginx/html/content/fr_FR.zip EXPOSE 8080 diff --git a/README.fr.md b/README.fr.md index ffa779cb..09e55542 100644 --- a/README.fr.md +++ b/README.fr.md @@ -15,7 +15,7 @@ docker compose up -d kalulu ./scripts/verify-live.sh ``` -Le premier lancement charge environ 34 Mo d’application puis télécharge 66 Mo de contenu pédagogique depuis la même origine. Le pack est ensuite extrait et conservé localement ; cette première installation peut prendre quelques minutes. Effacer les données du site dans le navigateur réinitialise le profil et la progression. +Le premier lancement charge environ 45 Mo d’application compressée puis télécharge 66 Mo de contenu pédagogique depuis la même origine. Le pack est extrait directement sur le thread Web principal, par lots, puis conservé localement. L’extraction testée atteint le premier jardin en moins d’une minute, contre plus de huit minutes avec l’extracteur multithread d’origine ; le téléchargement dépend naturellement de la connexion. Effacer les données du site dans le navigateur réinitialise le profil et la progression. L’image construit l’export avec Godot 4.6.3, les modèles d’export officiels et Godot-SQLite 4.8 (la version compilée pour Godot 4.6.3). Nginx sert WebAssembly avec les en-têtes COOP/COEP requis pour les threads. diff --git a/compose.yaml b/compose.yaml index 3d7cda1d..f13e555b 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,6 +1,6 @@ services: kalulu: - image: kalulu-web:3.0.1-fr-joy + image: kalulu-web:3.0.1-fr-joy-fast build: context: . dockerfile: Dockerfile diff --git a/folder_unzipper.gd b/folder_unzipper.gd index d9716bdd..3ce65ee3 100644 --- a/folder_unzipper.gd +++ b/folder_unzipper.gd @@ -33,6 +33,7 @@ func extract(zip_path: String, extract_path: String, extract_in_subfolder: bool Log.trace("FolderUnzipper: %d files found" % total_files) var copied_file: int = 0 + var progress_step: int = 25 if OS.has_feature("web") else 1 var first_folder: String = "" var last_sub_path: String = "" @@ -78,9 +79,10 @@ func extract(zip_path: String, extract_path: String, extract_in_subfolder: bool file.store_buffer(data) file.close() - Log.trace("FolderUnzipper: Copied %s" % file_name) copied_file += 1 - file_copied.emit(copied_file, file_name) + if copied_file % progress_step == 0 or copied_file == total_files: + Log.trace("FolderUnzipper: Copied %s" % file_name) + file_copied.emit(copied_file, file_name) close() finished.emit() diff --git a/nginx.conf b/nginx.conf index 52b67fda..471ccd2e 100644 --- a/nginx.conf +++ b/nginx.conf @@ -3,6 +3,8 @@ server { server_name _; root /usr/share/nginx/html; index index.html; + gzip_static on; + gzip_vary on; add_header Cross-Origin-Opener-Policy "same-origin" always; add_header Cross-Origin-Embedder-Policy "require-corp" always; diff --git a/sources/menus/language_selection/package_downloader.gd b/sources/menus/language_selection/package_downloader.gd index 81f42213..94ef1a1b 100644 --- a/sources/menus/language_selection/package_downloader.gd +++ b/sources/menus/language_selection/package_downloader.gd @@ -291,11 +291,17 @@ func _on_http_request_request_completed(_result: int, response_code: int, _heade Log.trace("PackageDownloader: Download completed with HTTP code %d" % response_code) if response_code == 200: mutex = Mutex.new() - thread = Thread.new() download_label.hide() copy_label.show() - Log.trace("PackageDownloader: Starting extraction thread") - thread.start(_copy_data.bind(self)) + if (ServerManager as ServerManagerClass).is_local_web_mode(): + # Web filesystem calls from a pthread are proxied one by one to the + # browser main thread. Extracting directly is dramatically faster. + Log.trace("PackageDownloader: Starting optimized Web extraction") + call_deferred("_copy_data", self) + else: + thread = Thread.new() + Log.trace("PackageDownloader: Starting extraction thread") + thread.start(_copy_data.bind(self)) else: Log.warn("PackageDownloader: Download failed with HTTP code %d" % response_code) error_label.show()