< Summary

Class:NFTShapeLoaderController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/LoadableShapes/NFTShape/NFTShapeLoaderController.cs
Covered lines:110
Uncovered lines:58
Coverable lines:168
Total lines:384
Line coverage:65.4% (110 of 168)
Covered branches:0
Total branches:0

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
NFTShapeLoaderController()0%110100%
Awake()0%550100%
Start()0%110100%
Update()0%2.52050%
LoadAsset(...)0%28.0812051.85%
UpdateBackgroundColor(...)0%2.062075%
FetchNFTImage()0%110100%
NFTInfoFetched(...)0%110100%
NFTInfoFetchedFail()0%2100%
FetchNFTInfoSuccess(...)0%2.092071.43%
FetchNFTImageCoroutine()0%12.3310071.43%
FinishLoading(...)0%4.594066.67%
SetFrameImage(...)0%7.777075%
UpdateTexture(...)0%2.032080%
InitializePerlinNoise()0%26.6410045%
OnDestroy()0%7.547077.78%
SetupGifPlayer(...)0%3.192033.33%
ShowLoading(...)0%2.062075%
ShowErrorFeedback(...)0%3.333066.67%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/LoadableShapes/NFTShape/NFTShapeLoaderController.cs

#LineLine coverage
 1using DCL.Components;
 2using DCL.Helpers.NFT;
 3using System.Collections;
 4using System.IO;
 5using System.Text.RegularExpressions;
 6using UnityEngine;
 7using DCL;
 8using Newtonsoft.Json;
 9using NFTShape_Internal;
 10using UnityGLTF.Loader;
 11
 12public class NFTShapeLoaderController : MonoBehaviour
 13{
 14    internal const string COULD_NOT_FETCH_DAR_URL = "Couldn't fetch DAR url '{0}' for NFTShape.";
 15    internal const string ACCEPTED_URL_FORMAT = "The accepted format is 'ethereum://ContractAddress/TokenID'.";
 16    internal const string SUPPORTED_PROTOCOL = "The only protocol currently supported is 'ethereum'.";
 17    internal const string DOES_NOT_SUPPORT_POLYGON = "Warning: OpenSea API does not support fetching Polygon assets.";
 18    internal const string COULD_NOT_FETCH_NFT_FROM_API = "Couldn't fetch NFT: '{0}/{1}'.";
 19    internal const string COULD_NOT_FETCH_NFT_IMAGE = "Couldn't fetch NFT image for: '{0}/{1}': {2}.";
 20
 21    public enum NoiseType
 22    {
 23        ClassicPerlin,
 24        PeriodicPerlin,
 25        Simplex,
 26        SimplexNumericalGrad,
 27        SimplexAnalyticalGrad,
 28        None
 29    }
 30
 31    public NFTShapeConfig config;
 32    public MeshRenderer meshRenderer;
 33    public new BoxCollider collider;
 34    public Color backgroundColor;
 35    public GameObject spinner;
 36    public GameObject errorFeedback;
 37
 38    [HideInInspector] public bool alreadyLoadedAsset = false;
 39    [HideInInspector] public INFTInfoFetcher nftInfoFetcher;
 40
 41    public event System.Action OnLoadingAssetSuccess;
 42    public event System.Action OnLoadingAssetFail;
 43
 44    [SerializeField]
 45    NFTShapeMaterial[] materials;
 46
 47    [Header("Noise Shader")]
 48    [SerializeField]
 3649    NoiseType noiseType = NoiseType.Simplex;
 50
 51    [SerializeField] bool noiseIs3D = false;
 52    [SerializeField] bool noiseIsFractal = false;
 53
 54    System.Action<LoadWrapper> OnSuccess;
 55    System.Action<LoadWrapper> OnFail;
 56    private string darURLProtocol;
 57    private string darURLRegistry;
 58    private string darURLAsset;
 59    private IPromiseLike_TextureAsset assetPromise = null;
 60
 061    public Material frameMaterial { private set; get; } = null;
 062    public Material imageMaterial { private set; get; } = null;
 063    public Material backgroundMaterial { private set; get; } = null;
 64
 3665    int BASEMAP_SHADER_PROPERTY = Shader.PropertyToID("_BaseMap");
 3666    int COLOR_SHADER_PROPERTY = Shader.PropertyToID("_BaseColor");
 67
 68    GifPlayer gifPlayer = null;
 69    NFTShapeHQImageHandler hqTextureHandler = null;
 70
 71    bool isDestroyed = false;
 72
 73    private Coroutine fetchNftImageCoroutine;
 74
 75    void Awake()
 76    {
 1377        Material[] meshMaterials = new Material[materials.Length];
 10478        for (int i = 0; i < materials.Length; i++)
 79        {
 3980            switch (materials[i].type)
 81            {
 82                case NFTShapeMaterial.MaterialType.BACKGROUND:
 1383                    backgroundMaterial = new Material(materials[i].material);
 1384                    meshMaterials[i] = backgroundMaterial;
 1385                    break;
 86                case NFTShapeMaterial.MaterialType.FRAME:
 1387                    frameMaterial = materials[i].material;
 1388                    meshMaterials[i] = frameMaterial;
 1389                    break;
 90                case NFTShapeMaterial.MaterialType.IMAGE:
 1391                    imageMaterial = new Material(materials[i].material);
 1392                    meshMaterials[i] = imageMaterial;
 93                    break;
 94            }
 95        }
 1396        meshRenderer.materials = meshMaterials;
 97
 98        // NOTE: we use half scale to keep backward compatibility cause we are using 512px to normalize the scale with a
 1399        meshRenderer.transform.localScale = new Vector3(0.5f, 0.5f, 1);
 100
 13101        InitializePerlinNoise();
 13102        nftInfoFetcher = new NFTInfoFetcher();
 13103    }
 104
 16105    private void Start() { spinner.layer = LayerMask.NameToLayer("ViewportCullingIgnored"); }
 106
 175107    void Update() { hqTextureHandler?.Update(); }
 108
 109    public void LoadAsset(string url, bool loadEvenIfAlreadyLoaded = false)
 110    {
 15111        if (string.IsNullOrEmpty(url) || (!loadEvenIfAlreadyLoaded && alreadyLoadedAsset))
 0112            return;
 113
 15114        ShowErrorFeedback(false);
 15115        UpdateBackgroundColor(backgroundColor);
 116
 117        // Check the src follows the needed format e.g.: 'ethereum://0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/558536'
 15118        var regexMatches = Regex.Matches(url, "(?<protocol>[^:]+)://(?<registry>0x([A-Fa-f0-9])+)(?:/(?<asset>.+))?");
 15119        if (regexMatches.Count == 0)
 120        {
 0121            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + ACCEPTED_URL_FORMAT, url));
 0122            ShowErrorFeedback(true);
 0123            OnLoadingAssetFail?.Invoke();
 124
 0125            return;
 126        }
 127
 15128        Match match = regexMatches[0];
 15129        if (match.Groups["protocol"] == null || match.Groups["registry"] == null || match.Groups["asset"] == null)
 130        {
 0131            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + ACCEPTED_URL_FORMAT, url));
 0132            ShowErrorFeedback(true);
 0133            OnLoadingAssetFail?.Invoke();
 134
 0135            return;
 136        }
 137
 15138        darURLProtocol = match.Groups["protocol"].ToString();
 15139        if (darURLProtocol != "ethereum")
 140        {
 0141            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + SUPPORTED_PROTOCOL + " " + ACCEPTED_URL_FORMAT,
 0142            ShowErrorFeedback(true);
 0143            OnLoadingAssetFail?.Invoke();
 144
 0145            return;
 146        }
 147
 15148        darURLRegistry = match.Groups["registry"].ToString();
 15149        darURLAsset = match.Groups["asset"].ToString();
 150
 15151        alreadyLoadedAsset = false;
 152
 15153        FetchNFTImage();
 15154    }
 155
 156    public void UpdateBackgroundColor(Color newColor)
 157    {
 18158        if (backgroundMaterial == null)
 0159            return;
 160
 18161        backgroundMaterial.SetColor(COLOR_SHADER_PROPERTY, newColor);
 18162    }
 163
 164    private void FetchNFTImage()
 165    {
 15166        ShowLoading(true);
 15167        nftInfoFetcher.FetchNFTImage(darURLRegistry, darURLAsset, NFTInfoFetched, NFTInfoFetchedFail);
 15168    }
 169
 12170    private void NFTInfoFetched(NFTInfo nftInfo) { fetchNftImageCoroutine = StartCoroutine(FetchNFTImageCoroutine(nftInf
 171
 172    private void NFTInfoFetchedFail()
 173    {
 0174        ShowErrorFeedback(true);
 0175        FinishLoading(false);
 0176    }
 177
 178    private void FetchNFTInfoSuccess(ITexture asset, NFTInfo info, bool isPreview)
 179    {
 6180        SetFrameImage(asset, resizeFrameMesh: true);
 6181        SetupGifPlayer(asset);
 182
 6183        if (isPreview)
 184        {
 0185            var hqImageHandlerConfig = new NFTShapeHQImageConfig()
 186            {
 187                controller = this,
 188                nftConfig = config,
 189                nftInfo = info,
 190                asset = NFTAssetFactory.CreateAsset(asset, config, UpdateTexture, gifPlayer)
 191            };
 0192            hqTextureHandler = NFTShapeHQImageHandler.Create(hqImageHandlerConfig);
 193        }
 194
 6195        FinishLoading(true);
 6196    }
 197
 198    IEnumerator FetchNFTImageCoroutine(NFTInfo nftInfo)
 199    {
 6200        bool isError = false;
 201
 12202        yield return new DCL.WaitUntil(() => (CommonScriptableObjects.playerUnityPosition - transform.position).sqrMagni
 203
 204        // We download the "preview" 256px image
 6205        bool foundDCLImage = false;
 6206        if (!string.IsNullOrEmpty(nftInfo.previewImageUrl))
 207        {
 6208            yield return WrappedTextureUtils.Fetch(nftInfo.previewImageUrl, (promise) =>
 209            {
 0210                foundDCLImage = true;
 0211                this.assetPromise = promise;
 0212                FetchNFTInfoSuccess(promise.asset, nftInfo, true);
 0213            });
 214        }
 215
 216        //We fall back to the nft original image which can have a really big size
 6217        if (!foundDCLImage && !string.IsNullOrEmpty(nftInfo.originalImageUrl))
 218        {
 6219            yield return WrappedTextureUtils.Fetch(nftInfo.originalImageUrl,
 220                (promise) =>
 221                {
 6222                    foundDCLImage = true;
 6223                    this.assetPromise = promise;
 6224                    FetchNFTInfoSuccess(promise.asset, nftInfo, false);
 6225                }, () => isError = true);
 226        }
 227
 6228        if (isError)
 229        {
 0230            Debug.LogError(string.Format(COULD_NOT_FETCH_NFT_IMAGE, darURLRegistry, darURLAsset, nftInfo.originalImageUr
 0231            ShowErrorFeedback(true);
 0232            OnLoadingAssetFail?.Invoke();
 0233            yield break;
 234        }
 235
 6236        FinishLoading(foundDCLImage);
 6237    }
 238
 239    void FinishLoading(bool loadedSuccessfully)
 240    {
 12241        if (loadedSuccessfully)
 242        {
 12243            ShowLoading(false);
 12244            OnLoadingAssetSuccess?.Invoke();
 12245        }
 246        else
 247        {
 0248            OnLoadingAssetFail?.Invoke();
 249        }
 0250    }
 251
 252    void SetFrameImage(ITexture newAsset, bool resizeFrameMesh = false)
 253    {
 6254        if (newAsset == null)
 0255            return;
 256
 6257        UpdateTexture(newAsset.texture);
 258
 6259        if (resizeFrameMesh && !isDestroyed && meshRenderer != null)
 260        {
 261            float w, h;
 6262            w = h = 0.5f;
 6263            if (newAsset.width > newAsset.height)
 0264                h *= newAsset.height / (float)newAsset.width;
 6265            else if (newAsset.width < newAsset.height)
 0266                w *= newAsset.width / (float)newAsset.height;
 6267            Vector3 newScale = new Vector3(w, h, 1f);
 268
 6269            meshRenderer.transform.localScale = newScale;
 270        }
 6271    }
 272
 273    public void UpdateTexture(Texture2D texture)
 274    {
 6275        if (imageMaterial == null)
 0276            return;
 277
 6278        imageMaterial.SetTexture(BASEMAP_SHADER_PROPERTY, texture);
 6279        imageMaterial.SetColor(COLOR_SHADER_PROPERTY, Color.white);
 6280    }
 281
 282    void InitializePerlinNoise()
 283    {
 13284        if (frameMaterial == null)
 0285            return;
 286
 13287        frameMaterial.shaderKeywords = null;
 288
 13289        if (noiseType == NoiseType.None)
 0290            return;
 291
 13292        switch (noiseType)
 293        {
 294            case NoiseType.ClassicPerlin:
 0295                frameMaterial.EnableKeyword("CNOISE");
 0296                break;
 297            case NoiseType.PeriodicPerlin:
 0298                frameMaterial.EnableKeyword("PNOISE");
 0299                break;
 300            case NoiseType.Simplex:
 13301                frameMaterial.EnableKeyword("SNOISE");
 13302                break;
 303            case NoiseType.SimplexNumericalGrad:
 0304                frameMaterial.EnableKeyword("SNOISE_NGRAD");
 0305                break;
 306            default: // SimplexAnalyticalGrad
 0307                frameMaterial.EnableKeyword("SNOISE_AGRAD");
 308                break;
 309        }
 310
 13311        if (noiseIs3D)
 0312            frameMaterial.EnableKeyword("THREED");
 313
 13314        if (noiseIsFractal)
 0315            frameMaterial.EnableKeyword("FRACTAL");
 13316    }
 317
 318    void OnDestroy()
 319    {
 13320        isDestroyed = true;
 321
 13322        nftInfoFetcher.Dispose();
 323
 13324        if (fetchNftImageCoroutine != null)
 6325            StopCoroutine(fetchNftImageCoroutine);
 13326        if (assetPromise != null)
 327        {
 6328            assetPromise.Forget();
 6329            assetPromise = null;
 330        }
 331
 13332        if (hqTextureHandler != null)
 333        {
 0334            hqTextureHandler.Dispose();
 0335            hqTextureHandler = null;
 336        }
 337
 13338        if (gifPlayer != null)
 339        {
 0340            gifPlayer.OnFrameTextureChanged -= UpdateTexture;
 0341            gifPlayer.Dispose();
 342        }
 343
 13344        if (backgroundMaterial != null)
 345        {
 13346            Object.Destroy(backgroundMaterial);
 347        }
 13348        if (imageMaterial != null)
 349        {
 13350            Object.Destroy(imageMaterial);
 351        }
 13352    }
 353
 354    private void SetupGifPlayer(ITexture asset)
 355    {
 6356        if (!(asset is Asset_Gif gifAsset))
 357        {
 6358            return;
 359        }
 360
 0361        gifPlayer = new GifPlayer(gifAsset);
 0362        gifPlayer.Play();
 0363        gifPlayer.OnFrameTextureChanged += UpdateTexture;
 0364    }
 365
 366    private void ShowLoading(bool isVisible)
 367    {
 27368        if (spinner == null)
 0369            return;
 370
 27371        spinner.SetActive(isVisible);
 27372    }
 373
 374    private void ShowErrorFeedback(bool isVisible)
 375    {
 15376        if (errorFeedback == null)
 0377            return;
 378
 15379        if (isVisible)
 0380            ShowLoading(false);
 381
 15382        errorFeedback.SetActive(isVisible);
 15383    }
 384}