How can I make a radial progress bar to load async/startcoroutine when looping over FileInfo array ?

When I’m running the game the part of the GetTextures method is taking time and the game is freezing until it’s loading all the images. I want it to show the loop progress in the radial progressbar.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;

public class StreamVideo : MonoBehaviour
{
   public Texture2D[] frames;                // array of textures
   public float framesPerSecond = 2.0f;    // delay between frames
   public RawImage image;
   public int currentFrameIndex;

   public GameObject LoadingText;
   public Text ProgressIndicator;
   public Image LoadingBar;
   float currentValue;
   public float speed;

   void Start()
   {
       DirectoryInfo dir = new DirectoryInfo(@"C:\tmp");
       // since you use ToLower() the capitalized version are quite redundant btw ;)
       string[] extensions = new[] { ".jpg", ".jpeg", ".png" };
       FileInfo[] info = dir.GetFiles().Where(f => extensions.Contains(f.Extension.ToLower())).ToArray();

       if (!image)
       {
           //Get Raw Image Reference
           image = gameObject.GetComponent<RawImage>();
       }

       frames = GetTextures(info);
       foreach (var frame in frames)
           frame.Apply(true, true);

   }

   private Texture2D[] GetTextures(FileInfo[] fileInfos)
   {
       var output = new Texture2D[fileInfos.Length];
       for (var i = 0; i < fileInfos.Length; i++)
       {
           var bytes = File.ReadAllBytes(fileInfos[i].FullName);
           output[i] = new Texture2D(1, 1);
           if (!ImageConversion.LoadImage((Texture2D)output[i], bytes, false))
           {
               Debug.LogError($"Could not load image from {fileInfos.Length}!", this);
           }
       }

       return output;
   }

   void Update()
   {
       int index = (int)(Time.time * framesPerSecond) % frames.Length;
       image.texture = frames[index]; //Change The Image

       if (currentValue < 100)
       {
           currentValue += speed * Time.deltaTime;
           ProgressIndicator.text = ((int)currentValue).ToString() + "%";
           LoadingText.SetActive(true);
       }
       else
       {
           LoadingText.SetActive(false);
           ProgressIndicator.text = "Done";
       }

       LoadingBar.fillAmount = currentValue / 100;
   }

   private void OnDestroy()
   {
       foreach (var frame in frames)
           Destroy(frame);
   }
}

Now just for testing I added some code for the radial progressbar in the Update :

if (currentValue < 100)
       {
           currentValue += speed * Time.deltaTime;
           ProgressIndicator.text = ((int)currentValue).ToString() + "%";
           LoadingText.SetActive(true);
       }
       else
       {
           LoadingText.SetActive(false);
           ProgressIndicator.text = "Done";
       }

       LoadingBar.fillAmount = currentValue / 100;

but it also start only after the GetTextures method finish it’s operation. the main goal is to show the progress of the operation in the GetTextures in the radial progressbar.

You’re not using any async methods or coroutines that I see. You’ve written GetTextures() as a synchronous method, and it’s being called from Start(), which means the Unity main game loop can’t proceed (and therefore can’t draw any new frames) until it returns.

Your progress bar code looks like it’s probably functional, it just doesn’t get a chance to run because your loading code is blocking the main thread.

1 Like

This is what I tried now but the calculation of the percentages with the radial progressbar in the ProcessImages method is not accurate something is wrong :

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Video;

public class StreamVideo : MonoBehaviour
{
    public Texture2D[] frames;                // array of textures
    public float framesPerSecond = 2.0f;    // delay between frames
    public RawImage image;
    public int currentFrameIndex;

    public GameObject LoadingText;
    public Text ProgressIndicator;
    public Image LoadingBar;
    float currentValue;
    public float speed;
    FileInfo[] info;

    void Start()
    {
        DirectoryInfo dir = new DirectoryInfo(@"C:\tmp");
        // since you use ToLower() the capitalized version are quite redundant btw ;)
        string[] extensions = new[] { ".jpg", ".jpeg", ".png" };
        info = dir.GetFiles().Where(f => extensions.Contains(f.Extension.ToLower())).ToArray();

        if (!image)
        {
            //Get Raw Image Reference
            image = gameObject.GetComponent<RawImage>();
        }

        StartCoroutine(ProcessImages(info));

        foreach (var frame in frames)
            frame.Apply(true, true);

    }

    public IEnumerator ProcessImages(FileInfo[] fileInfos)
    {
        var output = new Texture2D[fileInfos.Length];
        for (var i = 0; i < fileInfos.Length; i++)
        {
            var bytes = File.ReadAllBytes(fileInfos[i].FullName);
            output[i] = new Texture2D(1, 1);
            if (!ImageConversion.LoadImage((Texture2D)output[i], bytes, false))
            {
                UnityEngine.Debug.LogError($"Could not load image from {fileInfos.Length}!", this);
            }

            if (currentValue < 100)
            {
                currentValue += speed * Time.deltaTime;
                ProgressIndicator.text = ((int)currentValue).ToString() + "%";
                LoadingText.SetActive(true);
            }
            else
            {
                LoadingText.SetActive(false);
                ProgressIndicator.text = "Done";
            }

            LoadingBar.fillAmount = currentValue / 100;

            currentFrameIndex = i;

            yield return new WaitForSeconds(0.1f);
        }
    }

    private Texture2D[] GetTextures(FileInfo[] fileInfos)
    {
        var output = new Texture2D[fileInfos.Length];
        for (var i = 0; i < fileInfos.Length; i++)
        {
            var bytes = File.ReadAllBytes(fileInfos[i].FullName);
            output[i] = new Texture2D(1, 1);
            if (!ImageConversion.LoadImage((Texture2D)output[i], bytes, false))
            {
                UnityEngine.Debug.LogError($"Could not load image from {fileInfos.Length}!", this);
            }
        }

        return output;
    }

    private void OnDestroy()
    {
        foreach (var frame in frames)
            Destroy(frame);
    }
}

You are currently telling the progress bar to fill at a specific rate (controlled by the variable “speed”) regardless of the actual loading. People do sometimes do something like that if they have no way to guess how long something is going to take (although usually a nonlinear guess is used to make it less obvious that the progress bar is a lie).

If you can, you want to fill the progress bar based on how much progress you’ve actually made. You might try (files read) / (total files).

By the way, you probably want to replace yield return new WaitForSeconds(0.1f) with yield return null in order to wait just one frame between loop iterations; it’s unlikely the extra waiting is helping anything.

1 Like

What is files read How do I get it ?

I guess this is wrong :

LoadingBar.fillAmount = currentValue / i;;