From 722ba7c32372f74ca35dbc8009d085d652bd9b06 Mon Sep 17 00:00:00 2001 From: Thomas Fransolet Date: Wed, 16 Sep 2026 15:49:19 +0200 Subject: [PATCH] Menu bootstrap update --- .../Assets/Scripts/Boot/MenuBootstrap.cs | 86 ++++++++++++++----- 1 file changed, 64 insertions(+), 22 deletions(-) diff --git a/unity/MyInfoMateVR/Assets/Scripts/Boot/MenuBootstrap.cs b/unity/MyInfoMateVR/Assets/Scripts/Boot/MenuBootstrap.cs index 4561604..c4c0cc5 100644 --- a/unity/MyInfoMateVR/Assets/Scripts/Boot/MenuBootstrap.cs +++ b/unity/MyInfoMateVR/Assets/Scripts/Boot/MenuBootstrap.cs @@ -77,9 +77,10 @@ namespace MyInfoMate.Vr.Boot _client = new ApiClient(pairing.BaseUrl, pairing.ApiKey); var client = _client; - var content = new ContentService(client, pairing.ConfigurationId, pairing.DeviceId); + _deviceId = pairing.DeviceId; + _content = new ContentService(client, pairing.ConfigurationId, _deviceId); - var loaded = await content.LoadAsync(); + var loaded = await _content.LoadAsync(); if (!loaded.Ok) { Show(loaded.Error); @@ -95,27 +96,50 @@ namespace MyInfoMate.Vr.Boot if (fleet != null) fleet.ConfigurationChanged += OnConfigurationChanged; - BuildMenu(loaded.Export); - await _menu.LoadImagesAsync(loaded.Export, client); - await ApplyBackdrop(loaded.Export, client); - - // E4 — le reste de la visite part sur le disque pendant que le visiteur - // regarde le menu. Sans await : c'est long, et personne ne doit l'attendre. - _ = ContentPreloader.RunAsync(loaded.Export, client); + await ApplyAsync(loaded.Export); if (!loaded.FromCache) return; // Le menu est déjà utilisable : le rafraîchissement ne fait attendre personne, // et ne se voit que s'il apporte quelque chose. - var fresh = await content.RefreshAsync(); + await RefreshContentAsync(); + } + + /// + /// Pose un contenu : le menu, ses vignettes, le décor, puis le téléchargement du + /// reste. Les trois chemins qui changent de contenu — démarrage, rafraîchissement, + /// changement de configuration — passent tous par ici, sinon l'un des quatre gestes + /// finit par manquer à l'un d'eux. + /// + async Task ApplyAsync(ConfigurationExport export) + { + BuildMenu(export); + await _menu.LoadImagesAsync(export, _client); + await ApplyBackdrop(export, _client); + + // E4 — le reste de la visite part sur le disque pendant que le visiteur + // regarde le menu. Sans await : c'est long, et personne ne doit l'attendre. + _ = ContentPreloader.RunAsync(export, _client); + } + + /// + /// Va voir si le contenu a changé, et ne reconstruit que s'il a changé — + /// RefreshAsync rend null quand l'export est identique à celui en + /// cache, et quand le serveur n'a pas répondu. + /// + /// ⚠️ À n'appeler qu'aux moments où personne ne regarde le menu : au + /// démarrage, et entre deux visites. Un menu qui se reconstruit sous les yeux d'un + /// visiteur rejoue la cascade d'apparition et déplace ce qu'il visait. + /// + async Task RefreshContentAsync() + { + if (_content == null) return; + + var fresh = await _content.RefreshAsync(); if (fresh == null || _menu == null) return; Debug.Log("[Menu] Contenu mis à jour, le menu se reconstruit."); - BuildMenu(fresh); - await _menu.LoadImagesAsync(fresh, client); - await ApplyBackdrop(fresh, client); - - _ = ContentPreloader.RunAsync(fresh, client); + await ApplyAsync(fresh); } void BuildMenu(ConfigurationExport export) @@ -145,6 +169,12 @@ namespace MyInfoMate.Vr.Boot _menu?.Recenter(); _telemetry?.StartNewSession(); Debug.Log("[Kiosk] Nouvelle visite."); + + // Le seul moment où reconstruire le menu ne dérange personne. Sans ça, un + // casque allumé toute la journée montre le contenu qu'il avait au démarrage : + // le battement de FleetReporter ne voit qu'un changement de configuration, + // pas une modification de son contenu. + _ = RefreshContentAsync(); } async void OnSectionChosen(string sectionId) @@ -225,13 +255,18 @@ namespace MyInfoMate.Vr.Boot /// ⚠️ On ferme ce qui est ouvert d'abord. Un visiteur qui regardait une galerie /// au moment du changement doit revenir au menu — pas rester dans un contenu qui /// n'appartient plus à sa visite. + /// + /// ⚠️ Le deviceId repart avec : c'est lui qui désigne le lien de ce casque, + /// donc son agencement — bento, ordre des sections, sections masquées pour le seul + /// canal VR. Sans lui, le casque bascule sur la configuration nue et perd sa mise + /// en page jusqu'au prochain démarrage. /// async void OnConfigurationChanged(string configurationId) { CloseSection(); - var content = new ContentService(_client, configurationId); - var loaded = await content.LoadAsync(); + _content = new ContentService(_client, configurationId, _deviceId); + var loaded = await _content.LoadAsync(); if (!loaded.Ok) { @@ -239,14 +274,13 @@ namespace MyInfoMate.Vr.Boot return; } - BuildMenu(loaded.Export); + // Avant l'application : recentrer ne dépend que du menu déjà en place, et + // attendre le chargement des vignettes le laisserait le temps d'une requête + // dans le dos du visiteur. _menu?.Recenter(); _telemetry?.StartNewSession(); - await _menu.LoadImagesAsync(loaded.Export, _client); - await ApplyBackdrop(loaded.Export, _client); - - _ = ContentPreloader.RunAsync(loaded.Export, _client); + await ApplyAsync(loaded.Export); } /// @@ -311,6 +345,14 @@ namespace MyInfoMate.Vr.Boot ImmersiveBackdrop _backdrop; Scene3DView _model; ApiClient _client; + ContentService _content; + + /// + /// Ce casque, gardé pour toute la vie de l'app : il désigne le lien de + /// configuration, donc l'agencement propre au canal VR. Tout + /// construit ici doit le recevoir. + /// + string _deviceId; Kiosk.KioskSession _kiosk; string _openedSectionId; float _openedAt;