/// <summary>
        /// GetHaircutMeshAsync implementation
        /// </summary>
        private IEnumerator GetHaircutMeshFunc(string avatarCode, string haircutId, AsyncRequest <TexturedMesh> request)
        {
            DateTime startTime = DateTime.Now;
            // In order to display the haircut in a scene correctly we need three things: mesh, texture, and coordinates of
            // vertices adjusted specifically for our avatar (this is called "haircut point cloud"). We need this because
            // algorithms automatically adjust haircuts for each model to provide better fitness.
            // Haircut texture and mesh (number of points and mesh topology) are equal for all avatars, but "point cloud"
            // should be downloaded separately for each model.
            // If mesh and texture are not cached yet, lets download and save them.
            string haircutMeshFilename       = AvatarSdkMgr.Storage().GetHaircutFilename(haircutId, HaircutFile.HAIRCUT_MESH_PLY);
            string haircutTextureFilename    = AvatarSdkMgr.Storage().GetHaircutFilename(haircutId, HaircutFile.HAIRCUT_TEXTURE);
            string haircutPointCloudFilename = AvatarSdkMgr.Storage().GetAvatarHaircutPointCloudFilename(avatarCode, haircutId);

            bool existMeshFiles  = File.Exists(haircutMeshFilename) && File.Exists(haircutTextureFilename);
            bool existPointcloud = File.Exists(haircutPointCloudFilename);

            if (!existMeshFiles || !existPointcloud)
            {
                var haircutDataRequest = GetHaircutDataAsync(avatarCode, haircutId);
                yield return(request.AwaitSubrequest(haircutDataRequest, 0.05f));

                if (request.IsError)
                {
                    yield break;
                }

                List <AsyncRequest> downloadRequests = new List <AsyncRequest>();
                if (!existMeshFiles)
                {
                    downloadRequests.Add(DownloadAndSaveHaircutMeshAsync(haircutDataRequest.Result));
                }
                if (!existPointcloud)
                {
                    downloadRequests.Add(DownloadAndSaveHaircutPointsAsync(avatarCode, haircutDataRequest.Result));
                }

                yield return(request.AwaitSubrequests(0.9f, downloadRequests.ToArray()));

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

            var loadHaircutRequest = CoreTools.LoadHaircutFromDiskAsync(avatarCode, haircutId);

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

            if (request.IsError)
            {
                yield break;
            }

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