When we make an editor plugin, we may have slow tasks. And if we don’t want to freeze the editor window without any notification, we need to add a progress bar, so the user can keep a track of the progress of the task.


Unreal Engine editor’s progress bar

Unreal Engine provides an easy-to-use class for this: FScopedSlowTask. In this small article, we will see how to use it.

Using the FScopedSlowTask

Let’s say we have a slow task in our code. Usually, this kind of task implies a for-loop with a lot of iterations. It could be like this (extract of the code of our Productivity Tools plugin):


// For each folder found in path
for (FString SubFolder : SubFolders)
{
	// Retrieve assets inside this folder
	TArray AssetDatasFromPath = FTools::GetAssetsFromPath(SubFolder);
	// If there isn't any assets
	if (AssetDatasFromPath.Num() == 0)
	{
		// Remove the folder
		AssetRegistryModule.Get().RemovePath(SubFolder);
	}
}

In this loop, we are iterating over a list of folders to check if there are assets in these folders. For a large project with a large number of folders, it can take a lot of time. And, if we don’t use a progress bar, the user will think the task has crashed.

Thankfully, it’s pretty easy to integrate one: to use a progress bar in an editor function, you just have to declare an FScopedSlowTask object before the slow task:


FScopedSlowTask ExplorePathsTask(SelectedPaths.Num(), LOCTEXT("LookingForUnusedAssetsText", "Looking for assets to remove"));
ExplorePathsTask.MakeDialog();

The first line given here is creating the FScopeSlowTask: it takes as first parameter a number which usually represents the total number of iterations, and the second parameter will be the text to display.
The second line is displaying the progress bar in the editor.

Then, we want to update this bar, we use the EnterProgressFrame() method:


ExploreFoldersTask.EnterProgressFrame();

With this line, we are saying that we are running an iteration. Therefore this line should usually be called for each iteration, and the total number of calls of this function should not exceed the total number of iterations given as parameter of the slow task.

And that’s all we need. The full code becomes this:


FScopedSlowTask ExploreFoldersTask(SubFolders.Num(), LOCTEXT("LookingForUnusedAssetsText", "Looking for assets to remove"));
ExploreFoldersTask.MakeDialog();

// For each folder found in path
for (FString SubFolder : SubFolders)
{
	ExploreFoldersTask.EnterProgressFrame();
	// Retrieve assets inside this folder
	TArray AssetDatasFromPath = FTools::GetAssetsFromPath(SubFolder);
	// If there isn't any assets
	if (AssetDatasFromPath.Num() == 0)
	{
		// Remove the folder
		AssetRegistryModule.Get().RemovePath(SubFolder);
	}
}

As we can see, there is no instruction to dismiss the dialog: as soon as the FScopedSlowTask object will be out of the scope, its destructor will be called and the dialog will be dismissed.

More about the FScopedSlowTask

There are three things, we need to know about the FScopeSlowTask:

  • It’s possible to have nested slow tasks
  • It’s possible to make the tasks cancellable
  • It’s possible to specify a given amount of work

To have nested slow tasks, we just need to declare two successive FScopedSlowTask objects. It’s useful when we have nested for loops. When we do this, the progress of the second slow task will contribute to the progress of the first one.

To make a task cancellable, we just have to set the parameter bShowCancelButton to true when we make the dialog. Thereafter, we will check before each iteration if the button has been pressed, if so we will break the loop. The previous code would become this:


FScopedSlowTask ExploreFoldersTask(SubFolders.Num(), LOCTEXT("LookingForUnusedAssetsText", "Looking for assets to remove"));
ExploreFoldersTask.MakeDialog(true);  // We display the Cancel button here

for (FString SubFolder : SubFolders)
{
	// We check if the cancel button has been pressed, if so we break the execution of the loop
	if(ExploreFoldersTask.ShouldCancel())
		break;
	ExploreFoldersTask.EnterProgressFrame();
	// Instructions...
}

Finally to work we a specific amount (useful when we have slow tasks which are not in a for-loop), we specify the total amount of work in the FScopedSlowTask constructor, then before each following task we will call EnterProgressFrame() with, as parameter, the fraction of work which will be done. It can look like this:


// Here the total amount of work is set to 100.0f
FScopedSlowTask SlowTask(100.0f, LOCTEXT("SlowTaskText", "Slow Task"));
SlowTask.MakeDialog(); 

// The first task should represent 10% of the time, so it will be 10% of 100.0f i 10.0f
SlowTask.EnterProgressFrame(10.0f);
// Instructions of the first task...

// The second one should represent 35% of the total time
SlowTask.EnterProgressFrame(35.0f);
// Instructions of the second task...

// The third and last one will be the rest
SlowTask.EnterProgressFrame(55.0f);
// Instructions of the last task...

Conclusion

That’s all for the basics of FScopedSlowTask, we could have discussed more about some functionnalities, but here we have an overview of the main features. For more details, you can read the official documentation on FSlowTask and FScopedSlowTask.