vr-app/unity-overlay/Assets/Scripts/Net/ContentService.cs
Thomas Fransolet 0310d28b5e Canal VR Meta Quest : projet Unity, viewer web et documentation
Premier commit du cinquième front. Trois parties :

- `unity/MyInfoMateVR/` : le projet Unity (6000.0.83f1, URP, Meta XR SDK 205),
  un APK unique pour tous les clients. Menu flottant à sélection au regard,
  appairage, chargement de scène GLB, POI, cache de contenu, télémétrie.
- `unity-overlay/` : les mêmes scripts à recopier sur un projet Unity neuf, avec
  les pièges rencontrés consignés dans son README.
- `viewer/` : viewer et éditeur de scène web autonome (Vite, TypeScript,
  three.js), partagé avec les autres fronts.
- `docs/` : état des lieux, setup Unity, décisions d'architecture et plan
  d'exécution en 9 étapes.

La scène est décrite par un `scene.json` poussé par `adb push` : l'app le
préfère à celui embarqué dans l'APK.

Les binaires (GLB, textures de l'échantillon Sponza, DLL Meta XR) passent par
Git LFS dès ce premier commit — les y faire entrer après coup demanderait de
réécrire l'historique. Les artefacts régénérés par l'éditeur et par CMake
(`Library/`, `.utmp/`, Burst debug) sont ignorés.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 15:26:07 +02:00

113 lines
3.8 KiB
C#

using System;
using System.Threading.Tasks;
using UnityEngine;
namespace MyInfoMate.Vr.Net
{
/// <summary>
/// Le contenu d'un lieu, servi <b>cache d'abord</b> — item <b>E4</b> du lot XR-4.
///
/// Deux chemins, jamais mélangés :
/// <list type="bullet">
/// <item><b>Au démarrage</b> — le cache s'il existe, affiché tout de suite ;
/// le réseau seulement s'il n'y a rien en cache.</item>
/// <item><b>Ensuite</b> — un rafraîchissement en tâche de fond, dont l'échec est
/// silencieux : une borne hors ligne affiche son contenu de la veille, et
/// ce n'est pas une erreur à montrer au visiteur.</item>
/// </list>
///
/// C'est aussi ce qui rendra la reprise après coupure gratuite : au redémarrage, le
/// cache est déjà là.
/// </summary>
public class ContentService
{
readonly ApiClient _client;
readonly string _configurationId;
public ContentService(ApiClient client, string configurationId)
{
_client = client;
_configurationId = configurationId;
}
public class Loaded
{
public ConfigurationExport Export;
/// <summary>Vrai si le contenu vient du disque et non du serveur.</summary>
public bool FromCache;
/// <summary>Date du cache servi, pour l'afficher en mode dégradé.</summary>
public DateTime? CachedAt;
public string Error;
public bool Ok => Error == null;
}
public async Task<Loaded> LoadAsync()
{
var cached = ContentCache.ReadExport(_configurationId);
if (cached != null)
{
var parsed = ConfigurationExport.Parse(cached);
if (parsed.Ok)
return new Loaded
{
Export = parsed.Value,
FromCache = true,
CachedAt = ContentCache.ExportDate(_configurationId)
};
// Un cache illisible est un cache mort : on ne le garde pas pour
// rejouer le même échec au prochain démarrage.
Debug.LogWarning("[Content] Cache illisible, il est écarté.");
}
return await FetchAsync();
}
/// <summary>
/// Rafraîchit le cache sans bloquer l'affichage. Renvoie le contenu neuf, ou null
/// si rien n'a changé ou si le serveur n'a pas répondu — dans les deux cas
/// l'appelant garde ce qu'il montre.
/// </summary>
public async Task<ConfigurationExport> RefreshAsync()
{
var before = ContentCache.ReadExport(_configurationId);
var raw = await _client.GetRawAsync(
$"/api/configuration/{_configurationId}/export");
if (!raw.Ok)
{
Debug.Log($"[Content] Rafraîchissement impossible ({raw.Error}) — " +
"le contenu en cache reste affiché.");
return null;
}
if (raw.Value == before) return null;
var parsed = ConfigurationExport.Parse(raw.Value);
if (!parsed.Ok) return null;
ContentCache.WriteExport(_configurationId, raw.Value);
return parsed.Value;
}
async Task<Loaded> FetchAsync()
{
var raw = await _client.GetRawAsync(
$"/api/configuration/{_configurationId}/export");
if (!raw.Ok) return new Loaded { Error = raw.Error };
var parsed = ConfigurationExport.Parse(raw.Value);
if (!parsed.Ok) return new Loaded { Error = parsed.Error };
ContentCache.WriteExport(_configurationId, raw.Value);
return new Loaded { Export = parsed.Value };
}
}
}