Skip to content

Latest commit

 

History

History
200 lines (160 loc) · 4.91 KB

Background tasks.md

File metadata and controls

200 lines (160 loc) · 4.91 KB

2020-08-17_13:41:16

Background tasks

Look at ObjectTools::DeleteObjects. Uses FAssetDeleteModel, DeleteModel->Tick, DeleteModel->GetState, DeleteModel::GetProgress, and GWawn->StatusUpdate.

Somewhat reduced:

TSharedRef<FAssetDeleteModel> DeleteModel = MakeShareable(
    new FAssetDeleteModel(ObjectsToDelete));
bool bUserCanceled = false;
GWarn->BeginSlowTask(
    NSLOCTEXT("UnrealEd", "VerifyingDelete", "Verifying Delete"),
    true, true);
while (!bUserCanceled && 
       DeleteModel->GetState() != FAssetDeleteModel::Finished)
{
    DeleteModel->Tick(0);
    GWarn->StatusUpdate(
        (int32)(DeleteModel->GetProgress() * 100),
        100, DeleteModel->GetProgressText());
    bUserCanceled = GWarn->ReceivedUserCancel();
}
GWarn->EndSlowTask();

FRunnable

Example of how to setup a background task using FRunnable. This example may be incomplete, more reading necessary. It seems to create a new thread for each execution. How do I use a thread pool instead?

class AMyActor : public AActor
{
public:
    void BeginPlay();
private:
    void CheckIsWorkerDone();
private:
    // What should the pointer type be? Should it be marked UPROPERTY?
    FMyWorker* MyWorker;

    FTimerHandle WorkerTimerHandle;
}

void AMyActor::BeginPlay()
{
    MyWorker = FMyWorker::Launch();
    if (!GetWorldTimerManager().IsTimerActive(WorkerTimerHandle))
    {
        GetWorldTimerManager().SetTimer(
            WorkerTimerHandle, this, &AMyActor::CheckIsWorkerDone, 0.5f, true);
    }
}


FMyWorker* FMyWorker::Launch()
{
    if (!FPlatformProcess::SupportsMultithreading())
    {
        return nullptr;
    }
    if (Runnable != nullptr)
    {
        return Runnable;
    }
    Runnable = new FMyWorker(); 
    return Runnable;
}

class FMyWorker : public FRunnable /* I think, or a  subclass of FRunnable. */
{
public:
    static FMyWorker* Launch();
    virtual uint32 Run() override; /* The API reference says that Run isn't virtual. Odd. */
    virtual void Stop() override;
private:
    static FMyWorker* Runnable;

    // What should the pointer type be? Should it be marked UPROPERTY?
    FRunnableThread* Thread;
}

FMyWorker::FMyWorker()
{ 
    Thread = FRunnableThread::Create(this, TEXT("FMyWorker"), 0, TPri_BelowNormal);
}

uint32 FMyWorker::Run()
{
    /* Put your background code here. */
}

Async

TFuture<void> future = Async(EAsyncExecution::ThreadPool, [/*captures*/]() {
    // Do the background work here.
    // Not safe to do GameThread-only stuff here, such as SetActorLocation.

    // Schedule an "apply" task to apply the result of the background work onto any U-objects.
    AsyncTask(ENamedThreads::GameThread, [/*captures*/]() {
        // This is run on the game thread, so safe to do SetActorLocation and such.
    });
});
void Example()
{
    // Create an Async Task defined in MyAsyncTask
    FAsyncTask<ExampleAsyncTask>* MyTask = new FAsyncTask<MyAsyncTask>(5);

    // Do either...

    // Start in a background thread:
    MyTask->StartBackgroundTask();

    // ...or...

    // Run in this thread
    MyTask->StartSynchronousTask();

    // Check if the task is done:
    if (MyTask->IsDone())
    {
    }

    // Spinning on IsDone is not acceptable (see EnsureCompletion), but it is ok to check once a frame.

    // Ensure the task is done, doing the task on the current thread if it has not been started, waiting until completion in all cases.
    MyTask->EnsureCompletion();

    // We're done, delete the task.
    delete Task;
}

FEvent

This is an example of a background task that does some work on a set of Actors when asked to. The asking is done by signaling a Start Event.

Header:

class MYMODULE_API FMyRunnable final : public FRunnable
{
private:
    bool bKeepRunning = true;
    FRunnableThread* Thread = nullptr;

public:
    FEvent* StartEvent = nullptr;
    TArray<TWeakObjectPtr<AMyActor>> MyActors;

    FMyRunnable(const TArray<TWeakObjectPtr<AMyActor>>& InMyActors)
        : MyActors(InMyActors)
    {
        StartEvent = FGenericPlatformProcess::GetSynchEventFromPool(false);
        Thread = FRunnableThread::Create(this, TEXT("yRunnable"), 0, TPri_Normal);
    }
};

Implementation:

uint32 FMyRunnable::Run()
{
    // This explicit pinning stuff might not be necessary, pin while operating
    // on a particular instance instead. Not sure what's better.
    TArray<TSharedObjectPtr<AMyActor>> PinnedActors;
    PinnedActors.Reserve(MyActors.Num());
    for (TWeakPtr<AArmyManagerNEW> MyActor : MyActors)
    {
        PinnedActors.Add(MyActor.Pin());
    }

    while (bKeepRunning)
    {
        StartEvent->Wait();

        for (TSharedObjectPtr<AMyActor> MyActor : PinnedActors)
        {
            // Do something with MyActor.
        }
    }

    return 0;
}

[[2021-04-28_13:14:13]] Editor progress bar

Unreal 4 Threading - Running a Task on a Worker Thread @ YouTube