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>
113 lines
3.8 KiB
C#
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 };
|
|
}
|
|
}
|
|
}
|