/// <summary>
        /// DownloadAndSaveAvatarModelAsync implementation.
        /// </summary>
        private static IEnumerator DownloadAndSaveAvatarModel(
            Connection connection,
            AvatarData avatar,
            bool withHaircutPointClouds,
            bool withBlendshapes,
            AsyncRequest <AvatarData> request
            )
        {
            // By initializing multiple requests at the same time (without yield between them) we're
            // starting them in parallel. In this particular case we're downloading multiple files at the same time,
            // which is usually a bit faster than sequential download.
            var meshZip        = connection.DownloadMeshZipAsync(avatar);
            var textureRequest = connection.DownloadTextureBytesAsync(avatar);

            var download = new List <AsyncRequest> {
                meshZip, textureRequest
            };
            AsyncWebRequest <byte[]> allHaircutPointCloudsZip = null, blendshapesZip = null;

                        #if BLENDSHAPES_IN_PLY_OR_FBX
            // just a sample of how to get blendshapes in a different format

            AsyncWebRequest <byte[]> blendshapesZipFbx = null, blendshapesZipPly = null;
                        #endif

            if (withHaircutPointClouds)
            {
                allHaircutPointCloudsZip = connection.DownloadAllHaircutPointCloudsZipAsync(avatar);
                download.Add(allHaircutPointCloudsZip);
            }

            if (withBlendshapes)
            {
                blendshapesZip = connection.DownloadBlendshapesZipAsync(avatar);
                download.Add(blendshapesZip);

                                #if BLENDSHAPES_IN_PLY_OR_FBX
                // just a sample of how to get blendshapes in a different format

                blendshapesZipFbx = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.FBX);
                download.Add(blendshapesZipFbx);

                blendshapesZipPly = connection.DownloadBlendshapesZipAsync(avatar, BlendshapesFormat.PLY);
                download.Add(blendshapesZipPly);
                                #endif
            }

            // continue execution when all requests finish
            yield return(request.AwaitSubrequests(0.9f, download.ToArray()));

            // return if any of the requests failed
            if (request.IsError)
            {
                yield break;
            }

            // save all the results to disk, also in parallel
            var saveMeshZip = CoreTools.SaveAvatarFileAsync(meshZip.Result, avatar.code, AvatarFile.MESH_ZIP);
            var saveTexture = CoreTools.SaveAvatarFileAsync(textureRequest.Result, avatar.code, AvatarFile.TEXTURE);

            var save = new List <AsyncRequest> ()
            {
                saveMeshZip, saveTexture
            };
            AsyncRequest <string> saveHaircutPointsZip = null, saveBlendshapesZip = null;
            if (allHaircutPointCloudsZip != null)
            {
                saveHaircutPointsZip = CoreTools.SaveAvatarFileAsync(allHaircutPointCloudsZip.Result, avatar.code, AvatarFile.ALL_HAIRCUT_POINTS_ZIP);
                save.Add(saveHaircutPointsZip);
            }

            if (blendshapesZip != null)
            {
                saveBlendshapesZip = CoreTools.SaveAvatarFileAsync(blendshapesZip.Result, avatar.code, AvatarFile.BLENDSHAPES_ZIP);
                save.Add(saveBlendshapesZip);
            }

                        #if BLENDSHAPES_IN_PLY_OR_FBX
            // just a sample of how to get blendshapes in a different format

            if (blendshapesZipFbx != null)
            {
                var saveBlendshapesZipFbx = CoreTools.SaveAvatarFileAsync(blendshapesZipFbx.Result, avatar.code, AvatarFile.BLENDSHAPES_FBX_ZIP);
                save.Add(saveBlendshapesZipFbx);
            }

            if (blendshapesZipPly != null)
            {
                var saveBlendshapesZipPly = CoreTools.SaveAvatarFileAsync(blendshapesZipPly.Result, avatar.code, AvatarFile.BLENDSHAPES_PLY_ZIP);
                save.Add(saveBlendshapesZipPly);
            }
                        #endif

            yield return(request.AwaitSubrequests(0.95f, save.ToArray()));

            if (request.IsError)
            {
                yield break;
            }

            var unzipMesh = CoreTools.UnzipFileAsync(saveMeshZip.Result);

            var unzip = new List <AsyncRequest> ()
            {
                unzipMesh
            };
            AsyncRequest <string> unzipHaircutPoints = null, unzipBlendshapes = null;
            if (saveHaircutPointsZip != null)
            {
                unzipHaircutPoints = CoreTools.UnzipFileAsync(saveHaircutPointsZip.Result);
                unzip.Add(unzipHaircutPoints);
            }
            if (saveBlendshapesZip != null)
            {
                unzipBlendshapes = UnzipBlendshapes(saveBlendshapesZip.Result, avatar.code);
                unzip.Add(unzipBlendshapes);
            }

            yield return(request.AwaitSubrequests(0.99f, unzip.ToArray()));

            if (request.IsError)
            {
                yield break;
            }

            // delete all .zip files we don't need anymore
            try {
                foreach (var fileToDelete in new AvatarFile[] { AvatarFile.MESH_ZIP, AvatarFile.ALL_HAIRCUT_POINTS_ZIP, AvatarFile.BLENDSHAPES_ZIP })
                {
                    CoreTools.DeleteAvatarFile(avatar.code, fileToDelete);
                }
            } catch (Exception ex) {
                // error here is not critical, we can just ignore it
                Debug.LogException(ex);
            }

            request.Result = avatar;
            request.IsDone = true;
        }
        /// <summary>
        /// GetHeadMeshAsync implementation
        /// </summary>
        private IEnumerator GetHeadMeshFunc(string avatarCode, bool withBlendshapes, int detailsLevel, AsyncRequest <TexturedMesh> request)
        {
            // Need to verify if this avatar supports Level Of Details
            if (detailsLevel > 0)
            {
                var avatarRequest = GetAvatarAsync(avatarCode);
                yield return(avatarRequest);

                if (avatarRequest.IsError)
                {
                    yield break;
                }
                if (string.Compare(avatarRequest.Result.pipeline, PipelineType.FACE.GetPipelineTypeName()) != 0)
                {
                    Debug.LogWarningFormat("Avatar created by {0} doesn't support Level Of Details. Will be used LOD 0.", avatarRequest.Result.pipeline);
                    detailsLevel = 0;
                }
            }

            string meshFilename    = AvatarSdkMgr.Storage().GetAvatarFilename(avatarCode, AvatarFile.MESH_PLY);
            string textureFilename = AvatarSdkMgr.Storage().GetAvatarFilename(avatarCode, AvatarFile.TEXTURE);

            //If there are no required files, will download them.
            if (!File.Exists(meshFilename) || !File.Exists(textureFilename))
            {
                var avatarRequest = connection.GetAvatarAsync(avatarCode);
                yield return(avatarRequest);

                if (avatarRequest.IsError)
                {
                    yield break;
                }

                var downloadRequest = DownloadAndSaveAvatarModelAsync(avatarRequest.Result, false, withBlendshapes);
                yield return(request.AwaitSubrequest(downloadRequest, 0.5f));

                if (request.IsError)
                {
                    yield break;
                }
            }

            //if there are no blendshapes, will download them
            var  blendshapesDir   = AvatarSdkMgr.Storage().GetAvatarSubdirectory(avatarCode, AvatarSubdirectory.BLENDSHAPES);
            bool blendshapesExist = Directory.GetFiles(blendshapesDir).Length > 0;

            if (withBlendshapes && !blendshapesExist)
            {
                var avatarRequest = connection.GetAvatarAsync(avatarCode);
                yield return(avatarRequest);

                if (avatarRequest.IsError)
                {
                    yield break;
                }

                var downloadBlendshapes = connection.DownloadBlendshapesZipAsync(avatarRequest.Result, BlendshapesFormat.BIN, detailsLevel);
                yield return(request.AwaitSubrequest(downloadBlendshapes, 0.8f));

                if (request.IsError)
                {
                    yield break;
                }

                byte[] blendshapesZipBytes = downloadBlendshapes.Result;
                if (blendshapesZipBytes != null && blendshapesZipBytes.Length > 0)
                {
                    var saveBlendshapesZip = CoreTools.SaveAvatarFileAsync(downloadBlendshapes.Result, avatarCode, AvatarFile.BLENDSHAPES_ZIP);
                    yield return(request.AwaitSubrequest(saveBlendshapesZip, 0.9f));

                    if (request.IsError)
                    {
                        yield break;
                    }

                    var unzipBlendshapes = UnzipBlendshapesAsync(saveBlendshapesZip.Result, avatarCode);
                    yield return(request.AwaitSubrequest(unzipBlendshapes, 0.95f));

                    if (request.IsError)
                    {
                        yield break;
                    }

                    CoreTools.DeleteAvatarFile(avatarCode, AvatarFile.BLENDSHAPES_ZIP);
                }
            }

            // At this point all avatar files are already saved to disk. Let's load the files to Unity.
            var loadAvatarHeadRequest = CoreTools.LoadAvatarHeadFromDiskAsync(avatarCode, withBlendshapes, detailsLevel);

            yield return(request.AwaitSubrequest(loadAvatarHeadRequest, 1.0f));

            if (request.IsError)
            {
                yield break;
            }

            request.Result = loadAvatarHeadRequest.Result;
            request.IsDone = true;
        }