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 };
}
}
}