Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waiting for a coroutine in a coroutine takes 1-Tick for the process to return #18

Closed
aziogroup opened this issue Sep 27, 2023 · 5 comments

Comments

@aziogroup
Copy link

aziogroup commented Sep 27, 2023

Hello, I'm currently using UE5Coro to perform various tests.
When I call a coroutine from within another coroutine and wait for it, I've noticed that there's a 1-Tick delay before the internal coroutine finishes and the external coroutine resumes processing.
It turns out that I can avoid this delay by using co_await WhenAny(TCoroutine<>) instead of a simple co_await TCoroutine<> .
Is this behavior intentional?

If it is intentional, is there a way to achieve the same behavior as WhenAny without using WhenAny when simply using co_await TCoroutine<>?

Thank you.

// -------- ATestActor. ------------- //
void ATestActor::BeginPlay()
{
    Super::BeginPlay();

    NestCoroutine(3, false);
    NestCoroutine(3, true);
}

UE5Coro::TCoroutine<> ATestActor::NestCoroutine(int DepthCount, bool bUseWhenAny, FForceLatentCoroutine)
{
    UE_LOG(LogTemp, Log, TEXT("[%llu] Depth = %d Coroutine Start"), GFrameCounter, DepthCount);

    if (DepthCount == 0)
    {
        co_await UE5Coro::Latent::NextTick();
        UE_LOG(LogTemp, Log, TEXT("[%llu] Depth = %d Coroutine End / bUseWhenAny = %d"), GFrameCounter, DepthCount, bUseWhenAny);
        co_return;
    }

    if (bUseWhenAny)
    {
        // Wait FAnyAwaiter
        co_await WhenAny(NestCoroutine(DepthCount - 1, bUseWhenAny));
    }
    else
    {
        // Wait TCoroutine<>
        co_await NestCoroutine(DepthCount - 1, bUseWhenAny);
    }
    
    UE_LOG(LogTemp, Log, TEXT("[%llu] Depth = %d Coroutine End / bUseWhenAny = %d"), GFrameCounter, DepthCount, bUseWhenAny);

Result

LogTemp: [4071] Depth = 3 Coroutine Start
LogTemp: [4071] Depth = 2 Coroutine Start
LogTemp: [4071] Depth = 1 Coroutine Start
LogTemp: [4071] Depth = 0 Coroutine Start
LogTemp: [4071] Depth = 3 Coroutine Start
LogTemp: [4071] Depth = 2 Coroutine Start
LogTemp: [4071] Depth = 1 Coroutine Start
LogTemp: [4071] Depth = 0 Coroutine Start
LogTemp: [4072] Depth = 0 Coroutine End / bUseWhenAny = 0
LogTemp: [4072] Depth = 0 Coroutine End / bUseWhenAny = 1
LogTemp: [4072] Depth = 1 Coroutine End / bUseWhenAny = 1
LogTemp: [4072] Depth = 2 Coroutine End / bUseWhenAny = 1
LogTemp: [4072] Depth = 3 Coroutine End / bUseWhenAny = 1
LogTemp: [4073] Depth = 1 Coroutine End / bUseWhenAny = 0
LogTemp: [4074] Depth = 2 Coroutine End / bUseWhenAny = 0
LogTemp: [4075] Depth = 3 Coroutine End / bUseWhenAny = 0
@aziogroup aziogroup changed the title When Waiting for Coroutines in a Coroutine, Resume Delay of 1-Tick. Waiting for a coroutine in a coroutine takes 1-Tick for the process to return Sep 27, 2023
@aziogroup
Copy link
Author

Remarks:
Since LatentActionManager is processed in the order of registration, the problem seems to be caused by the fact that the resumption process of the external coroutine is not called until the next frame after the internal coroutine has finished.

WhenAll, WhenAny, etc. were registered and processed after the internal coroutine, so they behaved as intended.

@landelare
Copy link
Owner

This behavior is by design and documented. Latent coroutines are fundamentally owned and controlled by the latent action manager and can only complete when it processes them on its own tick. This matches BP and most existing Latent UFUNCTIONs from the engine, which is the primary focus of this mode of execution.

In some situations that I encountered, I managed to skip the extra tick by returning an inner TCoroutine instead of co_awaiting it.

@landelare landelare closed this as not planned Won't fix, can't repro, duplicate, stale Sep 27, 2023
@aziogroup
Copy link
Author

aziogroup commented Sep 27, 2023

Thank you for your response,
Sorry for the inconvenience,

In some situations that I encountered, I managed to skip the extra tick by returning an inner TCoroutine instead of co_awaiting it.

If you don't mind, I would appreciate it if you could tell me what exactly you did in response.

@landelare
Copy link
Owner

@aziogroup Instead of

TCoroutine<> Fn1();
TCoroutine<> Fn2()
{
    DoStuff();
    co_await Fn1();
}

it's

TCoroutine<> Fn1();
TCoroutine<> Fn2()
{
    DoStuff();
    return Fn1();
}

@aziogroup
Copy link
Author

Thank you very much!
It seemed difficult to apply it to my case, but in a simple case this seemed to be the way to get around it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants