// Invoked when a load requeset has failed to download the material data it needs
        // Requests are retried RETRY_LIMIT times if they fail more than that the request is abandoned (error is logged)
        // When this happens if any dependent cubes want the resource that failed one request is re-queued to try again (under that request's Retry quota)
        private void FailGetMaterialDataRequest(LoadCubeRequest loadRequest, string materialPath)
        {
            CheckIfBackgroundThread();
            loadRequest.Failures++;
            lock (MaterialDataCache)
            {
                // Remove the 'in progress' marker from the cache
                MaterialDataCache.Remove(materialPath);
            }

            if (RETRY_LIMIT > loadRequest.Failures)
            {
                Debug.LogError("Retry limit hit for: " + materialPath);
                Debug.LogError("Cube load failed for " + loadRequest);

                lock (_dependentCubes)
                {
                    LinkedList <LoadCubeRequest> dependentRequests;
                    if (_dependentCubes.TryGetValue(materialPath, out dependentRequests))
                    {
                        var request = dependentRequests.Last.Value;
                        dependentRequests.RemoveLast();
                        _loadMaterialQueue.ConcurrentEnqueue(request).Wait();
                    }
                }
            }
            else
            {
                // Queue for retry
                _loadMaterialQueue.ConcurrentEnqueue(loadRequest).Wait();
            }
        }
        // Helper for locking a queue, pulling off requests and invoking a handler function for them
        private void ProcessQueue(Queue <LoadCubeRequest> queue, Func <LoadCubeRequest, IEnumerator> requestProcessFunc,
                                  int limit)
        {
            var noLimit = limit == 0;

            if (Monitor.TryEnter(queue))
            {
                try
                {
                    while (queue.Count > 0 && (noLimit || (limit-- > 0)))
                    {
                        var request = queue.Dequeue();
                        if (!request.Cancelled)
                        {
                            StartCoroutine(requestProcessFunc(request));
                        }
                        else
                        {
                            lock (MaterialDataCache)
                            {
                                if (request.MaterialData != null && request.MaterialData.DiffuseTex != null)
                                {
                                    MaterialDataCache.Release(request.MaterialData.DiffuseTex.name);
                                }
                            }
                            CancelledRequests++;
                        }
                    }
                }
                finally
                {
                    Monitor.Exit(queue);
                }
            }
        }
        void OnDestroy()
        {
            if (MaterialDataCache != null)
            {
                MaterialDataCache.Empty();
            }

            if (_geometryBufferCache != null)
            {
                _geometryBufferCache.Empty();
            }
        }
        // Used to create material data when a texture has finished downloading
        private IEnumerator FinishCreatingMaterialDataWithTexture(
            KeyValuePair <string, byte[]> materialDataKeyAndTexturePair)
        {
            var materialDataKey = materialDataKeyAndTexturePair.Key;

            while (!Monitor.TryEnter(_partiallyConstructedMaterialDatas))
            {
                yield return(null);
            }

            MaterialData inProgressMaterialData;

            try
            {
                inProgressMaterialData = _partiallyConstructedMaterialDatas[materialDataKey];
                _partiallyConstructedMaterialDatas.Remove(materialDataKey);
            }
            finally
            {
                Monitor.Exit(_partiallyConstructedMaterialDatas);
            }

            if (!CacheFill)
            {
#if UNITY_IOS
                var texture = new Texture2D(1, 1, TextureFormat.RGB24, false);
#else
                var texture = new Texture2D(1, 1, TextureFormat.DXT1, false);
#endif
                texture.LoadImage(materialDataKeyAndTexturePair.Value);

                inProgressMaterialData.DiffuseTex      = texture;
                inProgressMaterialData.DiffuseTex.name = inProgressMaterialData.Name;
            }

            while (!Monitor.TryEnter(MaterialDataCache))
            {
                yield return(null);
            }

            try
            {
                MaterialDataCache[materialDataKey] = inProgressMaterialData;
                // Add reference until we add references for dependent requests
                MaterialDataCache.AddRef(materialDataKey);
            }
            finally
            {
                Monitor.Exit(MaterialDataCache);
            }
            // Move forward dependent requests that wanted this material data
            yield return(StartCoroutine(SucceedGetMaterialDataRequests(materialDataKey, inProgressMaterialData)));
        }
        // Called when the material data has been constructed into the cache
        // The material data is constructed using a materialkey for reference
        // The method sets the material data for any dependent requests and moves them along
        private IEnumerator SucceedGetMaterialDataRequests(string materialDataKey, MaterialData materialData)
        {
            CheckIfMainThread();
            // Check to see if any other requests were waiting on this model
            LinkedList <LoadCubeRequest> dependentRequests;

            while (!Monitor.TryEnter(_dependentCubes))
            {
                yield return(null);
            }
            try
            {
                if (_dependentCubes.TryGetValue(materialDataKey, out dependentRequests))
                {
                    _dependentCubes.Remove(materialDataKey);
                }
            }
            finally
            {
                Monitor.Exit(_dependentCubes);
            }
            while (!Monitor.TryEnter(MaterialDataCache))
            {
                yield return(null);
            }
            try
            {
                // If any were send them to their next stage
                if (dependentRequests != null)
                {
                    foreach (var request in dependentRequests)
                    {
                        request.MaterialData = materialData;

                        MaterialDataCache.AddRef(request.MaterialData.DiffuseTexPath);

                        MoveRequestForward(request);
                    }
                }
                // Now that added references for the dependent requests. We can release the interim reference
                MaterialDataCache.Release(materialData.DiffuseTexPath);
            }
            finally
            {
                Monitor.Exit(MaterialDataCache);
            }
        }
        private void InternalSetup()
        {
#if !UNITY_WSA
            _mainThread = Thread.CurrentThread;
#endif

            _guiStyle.normal.textColor = Color.red;

            if (_geometryBufferCache == null)
            {
                _geometryBufferCache = new DictionaryCache <string, GeometryBuffer>(GeometryBufferCacheSize);
            }
            else
            {
                Debug.LogWarning("GeometryBuffer cache already initialized. Skipping initizliation.");
            }

            if (MaterialDataCache == null)
            {
                MaterialDataCache = new MaterialDataCache(MaterialDataCacheSize);
            }
            else
            {
                Debug.LogWarning("Material Data cache already initialized. Skipping initizliation.");
            }

            ObjectPooler.Current.CreatePoolForObject(BaseModelCube);

            // Optional pool only used in camera detection scenario
            if (PlaceHolderCube != null)
            {
                ObjectPooler.Current.CreatePoolForObject(PlaceHolderCube);
            }

            CacheWebRequest.InitializeCache(CacheSize, ProxyUrl);
        }
        // Responsible for getting the material data for a load request
        // The method works roughly as follows
        // 1. Check if material data is in cache
        //    a. If not, start a web request for the data and add this request to the dependency list for the path
        // 2. If the material data cache indicates that it is being filled (a set value of null) (including if the
        //      request just started during this invocation) add the request to the dependency list for this path
        // 3. If the material is in the cache and set then get the data for the request and move it forward
        private IEnumerator GetMaterialForRequest(LoadCubeRequest loadRequest)
        {
            var pyriteLevel =
                loadRequest.Query.DetailLevels[loadRequest.LodIndex];
            var textureCoordinates = pyriteLevel.TextureCoordinatesForCube(loadRequest.X, loadRequest.Y);
            var texturePath        = loadRequest.Query.GetTexturePath(loadRequest.LodIndex,
                                                                      (int)textureCoordinates.x,
                                                                      (int)textureCoordinates.y);

            while (!Monitor.TryEnter(MaterialDataCache))
            {
                yield return(null);
            }
            // If the material data is not in the cache or in the middle of being constructed add this request as a dependency
            if (!MaterialDataCache.ContainsKey(texturePath) || MaterialDataCache[texturePath] == null)
            {
                // Add this requst to list of requests that is waiting for the data
                yield return(StartCoroutine(AddDependentRequest(loadRequest, texturePath)));

                // Check if this is the first request for material (or it isn't in the cache)
                if (!MaterialDataCache.ContainsKey(texturePath))
                {
                    if (UseWwwForTextures)
                    {
                        // Material data was not in cache nor being constructed
                        // Cache counter
                        MaterialCacheMisses++;
                        // Set to null to signal to other tasks that the key is in the process
                        // of being filled
                        MaterialDataCache[texturePath] = null;
                        var materialData = CubeBuilderHelpers.GetDefaultMaterialData((int)textureCoordinates.x,
                                                                                     (int)textureCoordinates.y, loadRequest.LodIndex,
                                                                                     texturePath);
                        var cachePath = CacheWebRequest.GetCacheFilePath(texturePath);
                        if (!CacheFill)
                        {
                            WWW textureWww; // = new WWW(texturePath);
                            if (CacheWebRequest.IsItemInCache(cachePath))
                            {
                                FileCacheHits++;
                                textureWww = new WWW("file:///" + cachePath);
                                yield return(textureWww);
                            }
                            else
                            {
                                FileCacheMisses++;
                                textureWww = new WWW(texturePath);
                                yield return(textureWww);

                                if (textureWww.Succeeded())
                                {
                                    CacheWebRequest.AddToCache(cachePath, textureWww.bytes);
                                }
                            }
                            if (textureWww.Failed())
                            {
                                Debug.LogError("Error getting texture [" + texturePath + "] " +
                                               textureWww.error);
                                FailGetMaterialDataRequest(loadRequest, texturePath);
                            }
                            else
                            {
                                materialData.DiffuseTex      = textureWww.textureNonReadable;
                                materialData.DiffuseTex.name = materialData.Name;
                            }
                        }
                        MaterialDataCache[texturePath] = materialData;
                        // Add a reference to keep the texture around until we queue off the related load requests
                        MaterialDataCache.AddRef(texturePath);
                        // Move forward dependent requests that wanted this material data
                        yield return(StartCoroutine(SucceedGetMaterialDataRequests(texturePath, materialData)));
                    }
                    else
                    {
                        // Material data was not in cache nor being constructed
                        // Cache counter
                        MaterialCacheMisses++;
                        // Set to null to signal to other tasks that the key is in the process
                        // of being filled
                        MaterialDataCache[texturePath] = null;
                        var materialData = CubeBuilderHelpers.GetDefaultMaterialData((int)textureCoordinates.x,
                                                                                     (int)textureCoordinates.y, loadRequest.LodIndex,
                                                                                     texturePath);
                        _partiallyConstructedMaterialDatas[texturePath] = materialData;

                        CacheWebRequest.GetBytes(texturePath, textureResponse =>
                        {
                            CheckIfBackgroundThread();
                            if (textureResponse.Status == CacheWebRequest.CacheWebResponseStatus.Error)
                            {
                                Debug.LogError("Error getting texture [" + texturePath + "] " +
                                               textureResponse.ErrorMessage);
                                FailGetMaterialDataRequest(loadRequest, texturePath);
                            }
                            else if (textureResponse.Status == CacheWebRequest.CacheWebResponseStatus.Cancelled)
                            {
                                lock (MaterialDataCache)
                                {
                                    MaterialDataCache.Remove(texturePath);
                                }
                            }
                            else
                            {
                                if (textureResponse.IsCacheHit)
                                {
                                    FileCacheHits++;
                                }
                                else
                                {
                                    FileCacheMisses++;
                                }
                                _texturesReadyForMaterialDataConstruction.ConcurrentEnqueue(
                                    new KeyValuePair <string, byte[]>(texturePath, textureResponse.Content)).Wait();
                            }
                        }, DependentRequestsExistBlocking);
                    }
                }
            }
            else // The material was in the cache
            {
                // Material data ready get it and move on
                MaterialCacheHits++;
                loadRequest.MaterialData = MaterialDataCache[texturePath];
                MaterialDataCache.AddRef(texturePath);
                MoveRequestForward(loadRequest);
            }
            Monitor.Exit(MaterialDataCache);
        }