Files
AR-Menu/Library/PackageCache/com.needle.engine-exporter@8c046140a1d9/Cloud/Runtime/NeedleCloudAsset.cs
2025-11-30 08:35:03 +02:00

223 lines
7.7 KiB
C#

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<NeedleCloudAsset> instances = new List<NeedleCloudAsset>();
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<Transform>())
{
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<Stream> 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;
}
}
}