using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; using UnityEngine; using UnityGLTF; using UnityGLTF.Loader; namespace Needle.Cloud.Runtime { [ExecuteInEditMode] public class NeedleCloudAsset : MonoBehaviour, INeedleCloudAsset { public string Url { get => url; set => url = value; } internal string LocalPath => lastPath; internal static readonly List instances = new List(); public string url; public string password; public bool autoRefresh = true; [SerializeField, HideInInspector] private string lastUrl; [SerializeField, HideInInspector] private string lastPath; [SerializeField, HideInInspector] private GameObject instance; private bool isLoading; private string lastLoggedError; private bool gotErrorDuringLoading; internal bool isMissingPassword; private void OnEnable() { lastUrl = null; lastLoggedError = null; gotErrorDuringLoading = false; instances.Add(this); Load(); } private void OnDisable() { lastLoggedError = null; DestroyImmediate(instance); instances.Remove(this); } [ContextMenu("Validate")] private void OnValidate() { if (url != lastUrl && enabled) { Load(); } } [ContextMenu("ResetInternalState")] private void ResetInternalState() { isLoading = false; } [ContextMenu(nameof(Reload))] internal void Reload() { DestroyImmediate(instance); Load(); } internal async void Load() { if (string.IsNullOrEmpty(url) || !url.StartsWith("http")) { // No URL or not a valid URL return; } var newUrl = url; lastUrl = url; var options = new DownloadManager.DownloadOptions(); options.Password = password; var result = await DownloadManager.Download(url, options); if (newUrl != url) { // URL changed during download return; } isMissingPassword = false; if (!result.Success) { // Something failed during downloading... lastPath = null; isMissingPassword = result.NeedsPassword; if (result.Error != lastLoggedError) { lastLoggedError = result.Error; Debug.LogError($"Error during cloud asset download. {result.Error}", this); } // If the URL is the same but we could not download it, then delete whatever instance is possibly still displayed if (newUrl == url && instance) { DestroyImmediate(instance); } isLoading = false; } else if (!this) { // We got destroyed during download } // If the download path changed (OR we don't have an instance yet), then we want to build it else if (result.Path != lastPath || (!instance && !gotErrorDuringLoading)) { if (!isLoading) { // var logCollector = new LogCollector(); try { isLoading = true; lastPath = result.Path; DestroyImmediate(instance); var path = result.Path; var directory = Path.GetDirectoryName(path); var opts = new ImportOptions { CameraImport = CameraImportOption.None, DataLoader = new NeedleCloudUnityGLTFDataLoader( password, directory, newUrl), // logger = new Logger(logCollector) }; var importer = new GLTFSceneImporter(path, opts); importer.SceneParent = transform; await importer.LoadSceneAsync(0, false); instance = importer.LastLoadedScene; if (instance) { instance.SetActive(true); // If we show components, we potentially have issues if users try to reference these components which is currently not supported instance.gameObject.hideFlags = HideFlags.HideAndDontSave; // HideFlags.NotEditable | HideFlags.DontSave; // We never want to export this (this is also currently used for NE to prevent it from being exported!) instance.tag = "EditorOnly"; foreach (var comp in instance.GetComponentsInChildren()) { comp.gameObject.hideFlags = instance.gameObject.hideFlags; } } gotErrorDuringLoading = false; } #pragma warning disable CS0168 // Variable is declared but never used catch (Exception err) #pragma warning restore CS0168 // Variable is declared but never used { lastLoggedError = err.Message; gotErrorDuringLoading = true; Debug.LogError($"Error during cloud asset import. {err}", this); // logCollector.LogAndClear("Import messages:\n{0}"); } finally { isLoading = false; } } } } } internal class NeedleCloudUnityGLTFDataLoader : IDataLoader { private readonly string password; private readonly string directory; private readonly string baseUrl; public NeedleCloudUnityGLTFDataLoader(string password, string directory, string url) { this.password = password; this.directory = directory; var lastSlashIndex = url.LastIndexOf('/'); this.baseUrl = url.Substring(0, lastSlashIndex + 1); } public async Task LoadStreamAsync(string relativeFilePath) { if (File.Exists(relativeFilePath)) { return File.OpenRead(relativeFilePath); } var newFullPath = Path.Combine(this.directory, relativeFilePath); if (File.Exists(newFullPath)) { return File.OpenRead(newFullPath); } Debug.Log($"Download resource: \"{relativeFilePath}\" to {newFullPath}"); var newUrl = this.baseUrl + relativeFilePath; var options = new DownloadManager.DownloadOptions(); options.Filename = relativeFilePath; options.TargetDirectory = Path.GetDirectoryName(newFullPath); options.Password = password; var res = await DownloadManager.Download(newUrl, options); if (res.Success) { return File.OpenRead(res.Path); } Debug.LogError(res.Error); return null; } } }