using System; using System.Threading.Tasks; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using UnityEngine; using UnityEngine.Networking; namespace MyInfoMate.Vr.Manifest { /// /// Lecture et validation d'un manifeste de scène. /// /// La validation n'est pas de la défense de principe : le manifeste est écrit /// par le serveur, mais il est lu par une app qui tourne hors ligne, sur site, /// sans personne pour regarder une console. Un manifeste qu'on ne sait pas lire /// doit produire une phrase lisible dans le casque, jamais une scène vide /// (§2.3, et le livrable explicite de S2). /// public static class ManifestReader { static readonly JsonSerializerSettings Settings = new JsonSerializerSettings { // Le manifeste est en camelCase (§2.2), les champs C# en PascalCase. ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }, MissingMemberHandling = MissingMemberHandling.Ignore, // Un champ absent doit garder la valeur par défaut du champ C#, pas // devenir null : c'est ce qui rend les sections optionnelles réellement // optionnelles. NullValueHandling = NullValueHandling.Ignore }; public class Result { public SceneManifest Manifest; /// Null si tout va bien ; sinon une phrase destinée au visiteur. public string Error; public bool Ok => Error == null; } public static Result Parse(string json) { if (string.IsNullOrWhiteSpace(json)) return Fail("Le manifeste de scène est vide."); SceneManifest manifest; try { manifest = JsonConvert.DeserializeObject(json, Settings); } catch (JsonException e) { Debug.LogError($"[Manifest] JSON illisible : {e.Message}"); return Fail("Le manifeste de scène est illisible."); } if (manifest == null) return Fail("Le manifeste de scène est illisible."); return Validate(manifest); } static Result Validate(SceneManifest manifest) { if (manifest.ManifestVersion != SceneManifest.SupportedManifestVersion) return Fail($"Cette scène utilise un format de manifeste " + $"(version {manifest.ManifestVersion}) que cette version de " + $"l'application ne sait pas lire. Mettez l'application à jour."); // Vérifié comme un littéral, volontairement : c'est le seul garde-fou // contre un manifeste produit dans la convention Unity par erreur, et // un miroir d'axes est invisible sur une scène symétrique (§2.1). if (manifest.CoordinateSystem != SceneManifest.ExpectedCoordinateSystem) return Fail($"Le repère de cette scène est inattendu " + $"({manifest.CoordinateSystem ?? "absent"}). " + $"Attendu : {SceneManifest.ExpectedCoordinateSystem}."); if (manifest.World == null || string.IsNullOrEmpty(manifest.World.AssetId)) return Fail("Cette scène n'a pas de décor."); switch (manifest.World.ParsedKind) { case World3DKind.Mesh: break; case World3DKind.Splat: return Fail("Ce décor est un nuage de splats gaussiens, que cette " + "version de l'application ne sait pas afficher."); case World3DKind.Panorama: return Fail("Ce décor est un panorama 360°, que cette version de " + "l'application ne sait pas afficher."); default: return Fail($"Type de décor inconnu : « {manifest.World.Kind} »."); } if (manifest.FindAsset(manifest.World.AssetId) == null) return Fail("Le fichier du décor est introuvable dans cette scène."); if (manifest.Navigation.RadiusMeters > NavigationManifest.MaxRadiusMeters) Debug.LogWarning( $"[Manifest] Rayon de navigation {manifest.Navigation.RadiusMeters} m " + $"au-delà du plafond de {NavigationManifest.MaxRadiusMeters} m : ramené au " + "plafond. Le serveur aurait dû le refuser."); return new Result { Manifest = manifest }; } /// /// Sur Android, un fichier de StreamingAssets est dans l'APK : seule /// UnityWebRequest sait le lire. Même raison que /// . /// public static async Task LoadAsync(string url) { using var request = UnityWebRequest.Get(url); var operation = request.SendWebRequest(); while (!operation.isDone) await Task.Yield(); if (request.result != UnityWebRequest.Result.Success) { Debug.LogError($"[Manifest] {url} : {request.error}"); return Fail("Le manifeste de scène est introuvable."); } return Parse(request.downloadHandler.text); } static Result Fail(string message) { Debug.LogError($"[Manifest] {message}"); return new Result { Error = message }; } } }