< Summary

Class:NFTShapeLoaderController
Assembly:MainScripts
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/Scripts/MainScripts/DCL/Components/LoadableShapes/NFTShape/NFTShapeLoaderController.cs
Covered lines:117
Uncovered lines:55
Coverable lines:172
Total lines:397
Line coverage:68% (117 of 172)
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%220100%
LoadAsset(...)0%28.0812051.85%
UpdateBackgroundColor(...)0%2.062075%
FetchNFTImage()0%110100%
NFTInfoFetched(...)0%110100%
NFTInfoFetchedFail()0%2100%
FetchNFTInfoSuccess(...)0%220100%
FetchNFTImageCoroutine()0%14.5510064.29%
FinishLoading(...)0%4.594066.67%
SetFrameImage(...)0%7.237083.33%
UpdateTexture(...)0%2.032080%
InitializePerlinNoise()0%26.6410045%
OnDestroy()0%7.077088.89%
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]
 3849    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    internal 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
 3865    int BASEMAP_SHADER_PROPERTY = Shader.PropertyToID("_BaseMap");
 3866    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    internal IWrappedTextureHelper wrappedTextureHelper;
 75
 76    void Awake()
 77    {
 1578        Material[] meshMaterials = new Material[materials.Length];
 12079        for (int i = 0; i < materials.Length; i++)
 80        {
 4581            switch (materials[i].type)
 82            {
 83                case NFTShapeMaterial.MaterialType.BACKGROUND:
 1584                    backgroundMaterial = new Material(materials[i].material);
 1585                    meshMaterials[i] = backgroundMaterial;
 1586                    break;
 87                case NFTShapeMaterial.MaterialType.FRAME:
 1588                    frameMaterial = materials[i].material;
 1589                    meshMaterials[i] = frameMaterial;
 1590                    break;
 91                case NFTShapeMaterial.MaterialType.IMAGE:
 1592                    imageMaterial = new Material(materials[i].material);
 1593                    meshMaterials[i] = imageMaterial;
 94                    break;
 95            }
 96        }
 97
 98
 1599        meshRenderer.materials = meshMaterials;
 100
 101        // NOTE: we use half scale to keep backward compatibility cause we are using 512px to normalize the scale with a
 15102        meshRenderer.transform.localScale = new Vector3(0.5f, 0.5f, 1);
 103
 15104        InitializePerlinNoise();
 15105        nftInfoFetcher = new NFTInfoFetcher();
 15106        wrappedTextureHelper = new WrappedTextureUtils();
 15107    }
 108
 20109    private void Start() { spinner.layer = LayerMask.NameToLayer("ViewportCullingIgnored"); }
 110
 3050111    void Update() { hqTextureHandler?.Update(); }
 112
 113    public void LoadAsset(string url, bool loadEvenIfAlreadyLoaded = false)
 114    {
 10115        if (string.IsNullOrEmpty(url) || (!loadEvenIfAlreadyLoaded && alreadyLoadedAsset))
 0116            return;
 117
 10118        ShowErrorFeedback(false);
 10119        UpdateBackgroundColor(backgroundColor);
 120
 121        // Check the src follows the needed format e.g.: 'ethereum://0x06012c8cf97BEaD5deAe237070F9587f8E7A266d/558536'
 10122        var regexMatches = Regex.Matches(url, "(?<protocol>[^:]+)://(?<registry>0x([A-Fa-f0-9])+)(?:/(?<asset>.+))?");
 10123        if (regexMatches.Count == 0)
 124        {
 0125            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + ACCEPTED_URL_FORMAT, url));
 0126            ShowErrorFeedback(true);
 0127            OnLoadingAssetFail?.Invoke();
 128
 0129            return;
 130        }
 131
 10132        Match match = regexMatches[0];
 10133        if (match.Groups["protocol"] == null || match.Groups["registry"] == null || match.Groups["asset"] == null)
 134        {
 0135            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + ACCEPTED_URL_FORMAT, url));
 0136            ShowErrorFeedback(true);
 0137            OnLoadingAssetFail?.Invoke();
 138
 0139            return;
 140        }
 141
 10142        darURLProtocol = match.Groups["protocol"].ToString();
 10143        if (darURLProtocol != "ethereum")
 144        {
 0145            Debug.LogError(string.Format(COULD_NOT_FETCH_DAR_URL + " " + SUPPORTED_PROTOCOL + " " + ACCEPTED_URL_FORMAT,
 0146            ShowErrorFeedback(true);
 0147            OnLoadingAssetFail?.Invoke();
 148
 0149            return;
 150        }
 151
 10152        darURLRegistry = match.Groups["registry"].ToString();
 10153        darURLAsset = match.Groups["asset"].ToString();
 154
 10155        alreadyLoadedAsset = false;
 156
 10157        FetchNFTImage();
 10158    }
 159
 160    public void UpdateBackgroundColor(Color newColor)
 161    {
 13162        if (backgroundMaterial == null)
 0163            return;
 164
 13165        backgroundMaterial.SetColor(COLOR_SHADER_PROPERTY, newColor);
 13166    }
 167
 168    private void FetchNFTImage()
 169    {
 10170        ShowLoading(true);
 10171        nftInfoFetcher.FetchNFTImage(darURLRegistry, darURLAsset, NFTInfoFetched, NFTInfoFetchedFail);
 10172    }
 173
 174    private void NFTInfoFetched(NFTInfo nftInfo)
 175    {
 7176        fetchNftImageCoroutine = StartCoroutine(FetchNFTImageCoroutine(nftInfo));
 7177    }
 178
 179    private void NFTInfoFetchedFail()
 180    {
 0181        ShowErrorFeedback(true);
 0182        FinishLoading(false);
 0183    }
 184
 185    private void FetchNFTInfoSuccess(ITexture asset, NFTInfo info, bool isPreview)
 186    {
 9187        SetFrameImage(asset, resizeFrameMesh: true);
 9188        SetupGifPlayer(asset);
 189
 9190        if (isPreview)
 191        {
 9192            var hqImageHandlerConfig = new NFTShapeHQImageConfig()
 193            {
 194                controller = this,
 195                nftConfig = config,
 196                nftInfo = info,
 197                asset = NFTAssetFactory.CreateAsset(asset, config, UpdateTexture, gifPlayer)
 198            };
 9199            hqTextureHandler = NFTShapeHQImageHandler.Create(hqImageHandlerConfig);
 200        }
 201
 9202        FinishLoading(true);
 9203    }
 204
 205    internal IEnumerator FetchNFTImageCoroutine(NFTInfo nftInfo)
 206    {
 9207        bool isError = false;
 208
 18209        yield return new DCL.WaitUntil(() => (CommonScriptableObjects.playerUnityPosition - transform.position).sqrMagni
 210
 211        // We download the "preview" 256px image
 9212        bool foundDCLImage = false;
 9213        if (!string.IsNullOrEmpty(nftInfo.previewImageUrl))
 214        {
 9215            yield return wrappedTextureHelper.Fetch(nftInfo.previewImageUrl, (promise) =>
 216            {
 9217                foundDCLImage = true;
 9218                assetPromise?.Forget();
 9219                this.assetPromise = promise;
 9220                FetchNFTInfoSuccess(promise.asset, nftInfo, true);
 9221            });
 222        }
 223
 224        //We fall back to the nft original image which can have a really big size
 9225        if (!foundDCLImage && !string.IsNullOrEmpty(nftInfo.originalImageUrl))
 226        {
 0227            yield return wrappedTextureHelper.Fetch(nftInfo.originalImageUrl,
 228                (promise) =>
 229                {
 0230                    foundDCLImage = true;
 0231                    assetPromise?.Forget();
 0232                    this.assetPromise = promise;
 0233                    FetchNFTInfoSuccess(promise.asset, nftInfo, false);
 0234                }, () => isError = true);
 235        }
 236
 9237        if (isError)
 238        {
 0239            Debug.LogError(string.Format(COULD_NOT_FETCH_NFT_IMAGE, darURLRegistry, darURLAsset, nftInfo.originalImageUr
 0240            ShowErrorFeedback(true);
 0241            OnLoadingAssetFail?.Invoke();
 0242            yield break;
 243        }
 244
 245        // TODO(Brian): This code will be called but FetchNFTInfoSuccess call in the promise
 246        //              above also triggers it, so the event is invoked twice!. Fix me!.
 9247        FinishLoading(foundDCLImage);
 9248    }
 249
 250    void FinishLoading(bool loadedSuccessfully)
 251    {
 18252        if (loadedSuccessfully)
 253        {
 18254            ShowLoading(false);
 18255            OnLoadingAssetSuccess?.Invoke();
 14256        }
 257        else
 258        {
 0259            OnLoadingAssetFail?.Invoke();
 260        }
 0261    }
 262
 263    void SetFrameImage(ITexture newAsset, bool resizeFrameMesh = false)
 264    {
 9265        if (newAsset == null)
 2266            return;
 267
 7268        UpdateTexture(newAsset.texture);
 269
 7270        if (resizeFrameMesh && !isDestroyed && meshRenderer != null)
 271        {
 272            float w, h;
 7273            w = h = 0.5f;
 7274            if (newAsset.width > newAsset.height)
 0275                h *= newAsset.height / (float)newAsset.width;
 7276            else if (newAsset.width < newAsset.height)
 0277                w *= newAsset.width / (float)newAsset.height;
 7278            Vector3 newScale = new Vector3(w, h, 1f);
 279
 7280            meshRenderer.transform.localScale = newScale;
 281        }
 7282    }
 283
 284    public void UpdateTexture(Texture2D texture)
 285    {
 7286        if (imageMaterial == null)
 0287            return;
 288
 7289        imageMaterial.SetTexture(BASEMAP_SHADER_PROPERTY, texture);
 7290        imageMaterial.SetColor(COLOR_SHADER_PROPERTY, Color.white);
 7291    }
 292
 293    void InitializePerlinNoise()
 294    {
 15295        if (frameMaterial == null)
 0296            return;
 297
 15298        frameMaterial.shaderKeywords = null;
 299
 15300        if (noiseType == NoiseType.None)
 0301            return;
 302
 15303        switch (noiseType)
 304        {
 305            case NoiseType.ClassicPerlin:
 0306                frameMaterial.EnableKeyword("CNOISE");
 0307                break;
 308            case NoiseType.PeriodicPerlin:
 0309                frameMaterial.EnableKeyword("PNOISE");
 0310                break;
 311            case NoiseType.Simplex:
 15312                frameMaterial.EnableKeyword("SNOISE");
 15313                break;
 314            case NoiseType.SimplexNumericalGrad:
 0315                frameMaterial.EnableKeyword("SNOISE_NGRAD");
 0316                break;
 317            default: // SimplexAnalyticalGrad
 0318                frameMaterial.EnableKeyword("SNOISE_AGRAD");
 319                break;
 320        }
 321
 15322        if (noiseIs3D)
 0323            frameMaterial.EnableKeyword("THREED");
 324
 15325        if (noiseIsFractal)
 0326            frameMaterial.EnableKeyword("FRACTAL");
 15327    }
 328
 329    void OnDestroy()
 330    {
 15331        isDestroyed = true;
 332
 15333        nftInfoFetcher.Dispose();
 334
 15335        if (fetchNftImageCoroutine != null)
 7336            StopCoroutine(fetchNftImageCoroutine);
 337
 15338        if (assetPromise != null)
 339        {
 8340            assetPromise.Forget();
 8341            assetPromise = null;
 342        }
 343
 15344        if (hqTextureHandler != null)
 345        {
 7346            hqTextureHandler.Dispose();
 7347            hqTextureHandler = null;
 348        }
 349
 15350        if (gifPlayer != null)
 351        {
 0352            gifPlayer.OnFrameTextureChanged -= UpdateTexture;
 0353            gifPlayer.Dispose();
 354        }
 355
 15356        if (backgroundMaterial != null)
 357        {
 15358            Object.Destroy(backgroundMaterial);
 359        }
 360
 15361        if (imageMaterial != null)
 362        {
 15363            Object.Destroy(imageMaterial);
 364        }
 15365    }
 366
 367    private void SetupGifPlayer(ITexture asset)
 368    {
 9369        if (!(asset is Asset_Gif gifAsset))
 370        {
 9371            return;
 372        }
 373
 0374        gifPlayer = new GifPlayer(gifAsset);
 0375        gifPlayer.Play();
 0376        gifPlayer.OnFrameTextureChanged += UpdateTexture;
 0377    }
 378
 379    private void ShowLoading(bool isVisible)
 380    {
 28381        if (spinner == null)
 0382            return;
 383
 28384        spinner.SetActive(isVisible);
 28385    }
 386
 387    private void ShowErrorFeedback(bool isVisible)
 388    {
 10389        if (errorFeedback == null)
 0390            return;
 391
 10392        if (isVisible)
 0393            ShowLoading(false);
 394
 10395        errorFeedback.SetActive(isVisible);
 10396    }
 397}