Giter Site home page Giter Site logo

landelare / ue5coro Goto Github PK

View Code? Open in Web Editor NEW
564.0 18.0 49.0 931 KB

A C++20 coroutine implementation for Unreal Engine 5 that feels almost native.

License: BSD 3-Clause Clear License

C++ 95.70% C# 2.68% C 1.62%
coroutines cpp unreal-engine async-await

ue5coro's Introduction

UE5Coro

These plugins implement C++ coroutines for Unreal Engine 5 with a focus on gameplay logic and BP integration.

Note

You're looking at UE5Coro 1. UE5Coro 2.0 is on a different branch.

Installation

Download the release that you wish to use from the Releases page, and copy the contents of its Plugins folder into your project's Plugins folder. Done correctly, you should end up with YourProject\Plugins\UE5Coro\UE5Coro.uplugin.

Note that 1.8 and earlier versions had a different folder structure. Refer to the README.md file from your chosen release for matching instructions.

Project setup

Depending on your targeted language/engine version, you might need additional setup to enable coroutines in your compiler:

C++17

In your Target.cs files for projects where you wish to use coroutines, add this line:

bEnableCppCoroutinesForEvaluation = true;

Potential UE5.1 bug: if you're building the engine from source and it seems to be rebuilding itself for no reason once you've done the Target.cs change above, edit TargetRules.cs in the engine instead so that this flag is true by default.

C++20, BuildSettingsVersion.V3 or older

In modules where you wish to use coroutines, add or change this line in the corresponding Build.cs file:

CppStandard = CppStandardVersion.Cpp20;

C++20, BuildSettingsVersion.V4

No additional setup is required.

Usage

The UE5Coro plugin containing core functionality is enabled by default. Reference the "UE5Coro" module from your Build.cs as you would any other module and #include "UE5Coro.h".

Other plugins, such as UE5CoroAI, UE5CoroGAS need to be manually enabled and referenced normally: e.g., "UE5CoroAI" in Build.cs, #include "UE5CoroAI.h" in your code. All UE5Coro plugins follow this pattern of providing a single header.

Using these meta-headers is the recommended and supported approach. You may opt to IWYU the various smaller headers, but no guidance is given as to which feature requires which header. IDEs most commonly used with Unreal Engine are known to fail to suggest the correct header for some features.

Feature overview

Click these links for the detailed description of the main features provided by these plugins, or keep reading for a few highlights.

UE5Coro

  • Async coroutines control their own resumption by awaiting various awaiter objects. They can be used to implement BP latent actions such as Delay, or as a generic fork in code execution like AsyncTask, but not necessarily involving multithreading.
  • Generators are caller-controlled and return a variable number of results without having to allocate and go through a temporary TArray.
  • Overview of built-in awaiters that you can use with async coroutines.

UE5CoroAI

UE5CoroGAS

Async coroutine examples

Return UE5Coro::TCoroutine<> from a function to make it coroutine enabled and support co_await inside. UFUNCTIONs need to use the FAsyncCoroutine wrapper.

Having a FLatentActionInfo parameter makes the coroutine implement a BP latent action. You do not need to do anything with this parameter, just have it and UE5Coro will register it with the latent action manager. World context objects are also supported and automatically processed. It's recommended to have them as the first parameter. Don't forget the necessary UFUNCTION metadata to make this a latent node in BP!

UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
FAsyncCoroutine AExampleActor::Latent(FLatentActionInfo LatentInfo)
{
    // This will *not* block the game thread for a second!
    co_await UE5Coro::Latent::Seconds(1.0);
    OneSecondLater();
}

The returned struct has no use in BP and is automatically hidden: AExampleActor::Latent as a BP node

You're not limited to BP latent actions, or UCLASS members:

UE5Coro::TCoroutine<> MyGlobalHelperFunction()
{
    co_await UE5Coro::Latent::Seconds(1.0);
    OneSecondLater();
}

Or even regular functions:

void Example(int Value)
{
    auto Lambda = [Value]() -> UE5Coro::TCoroutine<int>
    {
        co_await UE5Coro::Tasks::MoveToTask();
        co_return PerformExpensiveTask(Value);
    };
    int ExpensiveResult = Lambda().GetResult();
}

Both BP latent actions and free-running asynchronous coroutines have a unified feature set: you can seamlessly co_await the same things from both and if needed, your BP latent action becomes a threading placeholder or additional behind-the-scenes latent actions are started as needed.

BP Latent actions are considered complete for BP when control leaves the scope of the coroutine body completely, either implicitly (running to the final }) or explicitly via co_return;.

Asynchronous coroutines (in both modes) synchronously return to their callers at the first co_await or co_return that they encounter and the rest of the function body runs either independently (in async mode) or through the latent action manager (in latent mode).

Everything co_awaitable works in every asynchronous coroutine, regardless of its BP integration:

using namespace UE5Coro;

UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
FAsyncCoroutine UExampleFunctionLibrary::K2_Foo(FLatentActionInfo LatentInfo)
{
    // You can freely hop between threads even though this is BP:
    co_await Async::MoveToThread(ENamedThreads::AnyBackgroundThreadNormalTask);
    DoExpensiveThingOnBackgroundThread();

    // However, awaiting latent actions has to be started from the game thread:
    co_await Async::MoveToGameThread();
    co_await Latent::Seconds(1.0f);
}

There are various other engine features with coroutine support including some engine types that are made directly co_awaitable in TCoroutines. Check out the Awaiters page for an overview.

Generator examples

Generators can be used to return an arbitrary number of items from a function without having to pass them through temp arrays, etc. In C# they're known as iterators.

Returning UE5Coro::TGenerator<T> makes a function coroutine enabled, supporting co_yield:

using namespace UE5Coro;

TGenerator<FString> MakeParkingSpaces(int Num)
{
    for (int i = 1; i <= Num; ++i)
        co_yield FString::Printf(TEXT("๐Ÿ…ฟ๏ธ %d"), i);
}

// Elsewhere
for (const FString& Str : MakeParkingSpaces(123))
    Process(Str);

co_yield and co_await cannot be mixed. Asynchronous coroutines control their own execution and wait for certain events, while generators are caller-controlled and yield values on demand.

In particular, it's not guaranteed that your generator function body will even run to completion if your caller decides to stop early. This enables scenarios where generators may co_yield an infinite number of elements and callers only taking a finite few:

using namespace UE5Coro;

TGenerator<int> NotTrulyInfinite()
{
    FString WillBeDestroyed = TEXT("Read on");
    int* Dangerous = new int;
    for (;;)
        co_yield 1;
    delete Dangerous;
}

// Elsewhere:
TGenerator<int> Generator = NotTrulyInfinite();
for (int i = 0; i < 5; ++i)
    Generator.Resume();

In this case, your coroutine stack will be unwound when the TGenerator object is destroyed, and the destructors of locals within the coroutine run like usual, as if the last co_yield was a throw (but no exceptions are involved).

In the example above, the FString will be freed but the delete line will never run. Use RAII or helpers such as ON_SCOPE_EXIT if you expect to not run to completion.

ue5coro's People

Contributors

landelare avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ue5coro's Issues

Ability for coroutine to await its own cancellation

Hey there and thanks for the amazing plugin.

I'm looking for some certain functionality regarding cancellation - in particular, having a coroutine respect its own cancellation while awaiting another nested coroutine.
I've read the cancellation documentation but can't find an example of this use case. Maybe this is already possible and I'm missing something.

Let's say I have a simple setup of one coroutine awaiting another:

TCoroutine<> Nested()
{
	co_await Latent::WaitSeconds(60.f);
}

TCoroutine<> Outer()
{
	co_await Nested();
}

And let's say that the Outer() coroutine is started and then has its Cancel() called while it's still running.
In this case, it seems the cancellation will not actually stop the outer coroutine before the nested one is complete.

Suppose I actually want the Outer coroutine to pick up its cancellation immediately and run some cleanup logic, before Nested() completes which may be a long time later. (Maybe forward that cancellation on to a nested coroutine to interrupt it earlier)
I feel like what I need is the ability to do something like this:

TCoroutine<> Outer()
{
	co_await WhenAny(Nested(), WaitForCancellation());
}

where WaitForCancellation() awaits and only resumes if Outer() is cancelled.
This way Outer() can handle its cancellation immediately.

I thought FinishNowIfCanceled() looked promising, however I need it to suspend if it's not cancelled rather than resuming immediately.

I also tried Latent::Until({ IsCurrentCoroutineCanceled(); }); but of course it gets evaluated outside of the context of the coroutine itself so can't get the cancellation status.

Does this make sense? Is it possible in the plugin already?

Thanks a lot.

Canceling Nested Latent Awaiters

Hello!
Love this plugin, trying to get it to do all the equivalent things Unity's IEnumerator could handle.

I've got just about everything figured out except for canceling a latent coroutine that itself awaits other coroutines.

void UCoroTesterComponent::BeginPlay()
{
    Super::BeginPlay();

    co_parent = MakeShared<TCoroutine<>>(WaitParent({}));
    CancelParent({});
}

TCoroutine<> UCoroTesterComponent::WaitParent(FForceLatentCoroutine)
{
    while(this)
    {
        co_await Latent::Seconds(1.f);
	co_await WaitChild({});
    }
}

TCoroutine<> UCoroTesterComponent::WaitChild(FForceLatentCoroutine)
{
    UE_LOG(LogTemp, Warning, TEXT("Waiting Child"));
    co_await Latent::Seconds(1.f);
    UE_LOG(LogTemp, Warning, TEXT("NESTED"));
    co_await WaitNested({});
}

TCoroutine<> UCoroTesterComponent::WaitNested(FForceLatentCoroutine)
{
    UE_LOG(LogTemp, Warning, TEXT("Waiting NESTED"));
    co_await Latent::Seconds(1.f);
    UE_LOG(LogTemp, Warning, TEXT("Done NESTED"));
}

TCoroutine<> UCoroTesterComponent::CancelParent(FForceLatentCoroutine)
{
    co_await Latent::Seconds(5.f);
    UE_LOG(LogTemp, Warning, TEXT("Canceling Parent!"));
    co_parent->Cancel();
}

This was a test to determine the behavior of nested coroutines after the parent coroutine is canceled.
It appears that even after WaitParent is canceled, WaitChild will continue to run, calling WaitNested.

Example output:

Waiting Child
NESTED
Waiting NESTED
Done NESTED
Waiting Child
Canceling Parent!  (at this point, I'd hope that the child would be auto-canceled as well!)
NESTED  (but we see it continues after it's await, and started to await on WaitNested)
Waiting NESTED
Done NESTED

Is this expected behavior, or am I doing something wrong?
The only alternative I can think of is to just cancel all the coroutines on the object using:

GetWorld()->GetLatentActionManager().RemoveActionsForObject(this);

however I'd like the ability to only cancel 1 "set" instead of everything on the object if possible.

Thank you in advance! ๐Ÿ˜Š

[Question] TPromise Example

First of all, thank you for such an excellent plugin. As a C# guy, I was very happy to find this plugin. It gives me the comfort of C#. But although I could do many things I wanted, I could not see a class that I could use like TaskCompletionSource in C#. I found the TPromise class as the closest alternative. But I think this works a little differently than that.

A 3rd party SDK I use returns the results of API calls via events. I want to wait for these results with TPromise and have my function continue where it left off after the event is called.

1-Is TPromise suitable for this job?
2-An example of usage for this would be great.

Thanks

co_await Promise.GetFuture(); crash

Hey,
the following seems to cause a crash in UE 5.0, Develop/Debug editor builds, initiated from a UFUNCTION(CallInEditor)

TPromise<int> Promise;
Promise.SetValue(123);
auto val = co_await Promise.GetFuture(); // `0xC0000005: Access violation reading location 0x0000000000000000.`

Not sure if I'm doing something wrong, thought I would mention it.

Event-driven corotines

It looks like in current implementation, most of the coroutines are tick based.

I want to use a custom Scheduler to schedule these awaited awaiter objects, would this usage planed to be supported?

If not, I guess i need to extend the awaiter types and related stuff;)

Live coding breaks generators

While some simple generators work for most of the time when rebuilding using live coding, there are cases in which generators are broken, i.e.

template <typename TItem>
class TWrappedMap
{
public:
	TMap<FString, TItem> Map;

	TGenerator<TPair<FString, TItem>> SomeGenerator()
	{
		for (const auto& Item : Map)
			co_yield TPair<FString, TItem>(Item);
	}

	TGenerator<TPair<FString, TItem>> OtherGenerator() { ... }
}

Rebuilding the code with live coding will result in crash where stacktrace looks like wrong method is getting called (i.e. SomeGenerator gets swapped with OtherGenerator, for SomeGenerator call stacktrace shows OtherGenerator$_ResumeCoro$1(...), crash further points to constructor of TConstSetBitIterator breaking at range check.

ๅ‘ๅธƒๅฎ‰ๅ“ๆŠฅ้”™

support for std::experimental::coroutine_traits will be removed in LLVM 15; use std::coroutine_traits instead [-Werror,-Wdeprecated-experimental-coroutine]็”จ็š„ๆ˜ฏUE5.3 NDK r25

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

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

How to cancel TCoroutine<> if its already running

Hi, thanks for the plugin but I am unsure of how to cancel a coroutine that is already running.
For example:
On input -> run coroutine
On input again before previous coroutine finished -> cancel the running coroutine -> restart a new one

Can you please explain or show some examples on how to achieve this?

Latent Coroutine Resumes for Unloaded Streamed Actor, Crash

Context:

The issue occurs when using a latent coroutine (with FForceLatentCoroutine) on an Actor that gets unloaded as a result of asynchronously unloading a level using level streaming. Specifically in my case, UGameplayStatics::UnloadStreamLevelBySoftObjectPtr.

The apparent issue observed is that coroutines resume on actors that have been unloaded from the world. At the point of resuming, the __coro_frame_ptr's __this points to the Actor that has been unloaded: Their UWorld * is null, and their ActorHasBegunPlay data member has been set to HasNotBegunPlay. While using this appears to be a valid memory access, the coroutine has resumed for an actor who is no longer a part of any UWorld, resulting in crashes for any functionality that requires the actor to have a world. It's a valid Actor in terms of existing in memory, but you certainly would not want to do anything with it at that point.

I'm assuming this behavior is undesirable, as the latent coroutine continues running on the actor's original world that it was loaded into, despite that actor no longer existing in the world, but still existing in memory. While we have latent this protection, there doesn't seem to be protection against resuming on actors that have been unloaded and shouldn't be executing its usual gameplay code.

Expected/Desired behavior:

Some mechanism should prevent coroutines from resuming if the owning Actor has been unloaded via streaming. Additionally, if possible, other UObject's who have that unloaded Actor set as their Outer will also have their coroutines cancelled in a way that prevents resuming, so they also are not left executing without a UWorld in their outer chain or with an owner that shouldn't be used.

Repro steps:

This issue reproduces for me 100% of the time.

  1. Ensure "Use Background Level Streaming" is enabled in project settings.
  2. Create an Actor class which defines a coroutine with FForceLatentCoroutine.
  3. For ease reproducing a crash, define the coroutine such that it does something with the pointer from GetWorld() frequently. My test uses co_await UE5Coro::Latent::Seconds to access GetWorld() at an interval of 0.1 seconds.
  4. Run this Coroutine on BeginPlay().
  5. Place the actor in a level which you can load via level streaming.
  6. Load the level using UGameplayStatics::LoadStreamLevelBySoftObjectPtr.
  7. Wait at least a moment before unloading, to ensure the coroutine has began.
  8. Unload the level using UGameplayStatics::UnloadStreamLevelBySoftObjectPtr.
  9. During the unload process, the game crashes due to the coroutine resuming after the actor has been unloaded and its world is null.
  10. While debugging the crash, check the __coro_frame_ptr and look into the data of __this to see that object is in an unloaded state. ActorHasBegunPlay will be HasNotBegunPlay, and the Actor will have no UWorld.

Return values from coroutines

Sometimes, when I awaited some data, for example of async loading of resources. I need return it from my coroutine. The best thing that comes to mind is using TTask with co_return. Can you add support for this?

For example:

UE::Tasks::TTask<UItemInstance*> GrantItem(TSoftObjectPtr<UItemAsset> ItemAssetSoft)
{
	UItemAsset* ItemAsset = co_await UE5Coro::Latent::AsyncLoadObject(ItemAssetSoft);

	UItemInstance* ItemInstance = CreateItemInstance(ItemAsset);
	
	co_return ItemInstance;
}

But TTask is not awaitable? Maybe add new type of task, like TCoroTask?

Coroutine crash when opening or leaving level in editor (game not running)

I encountered this crash while attempting to create a clean repro for a different crash I was encountering. I'm unsure if anyone would realistically encounter this specific situation, but in case it might help improve the systems, figured I would also report this.

This was tested including the recent changes in your commit 0cf2485.

Source for required class CoroExitTest attached to this issue:
CoroEditorLevelCrash.zip

Repro Case A:

  1. Make empty level I'll refer to as "TestLevel".
  2. Ensure the game mode is set to the blank GameModeBase.
  3. Place an actor of type CoroExitTest in TestLevel.
  4. Open another level in editor.
  5. The editor will crash at this point.

Repro Case B:

  1. Reuse the TestLevel from repro case A.
  2. Start the editor.
  3. Open TestLevel.
  4. The editor will crash at this point.

Images here for the call stack of the crash and information about the FLatentPromise. The coro frame ptr is not available at the point of crashing, so no image of its state is provided.
CrashPoint1
LatentPromise1

Compile error?

0>F:\UnrealProjects\Ue5CoroTest\Plugins\UE5Coro\Source\UE5CoroTests\Private\ReturnValueTest.cpp(121): Error C2065 : 'bInnerComplete': undeclared identifier

Latent Coroutine Crash On Exiting Game - Repro included

This repro case is based upon a crash in a real use case of mine. The use case in the game is that one parent actor manages the lifetime of other actors. When the parent actor gets destroyed, it calls a coroutine on its child actors which they use to destroy themselves latently at their own pace. This works fine during gameplay, but if the parent actor gets destroyed by the game being stopped in editor, the coroutines in the child actors will cause a crash.

This was tested including the recent changes in your commit 0cf2485.

Repro Case:

  1. Make empty level I'll refer to as "CrashLevel".
  2. Ensure the game mode is set to the blank GameModeBase.
  3. Place an actor of type CoroParent in CrashLevel.
  4. Run the game in-editor.
  5. Observe the world outliner to verify CoroChild actors have been created, as well as blank actors.
  6. Stop running the game by clicking the stop button in-editor.
  7. The game crashes at this point.

One unknown detail is whether this crash will also occur when closing a standalone build, but the repro setup for that would look similar to this in-editor setup. I've also seen this crash occurs with non-latent coroutines anecdotally, but this is not included in the repro case.

Attached is an image of the coro frame pointer and callstack at time of the crash. Most of the time the crash occurs outside of the scope of a coro frame pointer, though.
CoroFramePointerOnCrash

Source for required classes CoroParent and CoroChild are here:
CoroCrashOnGameExitSource.zip

Coroutine awaiting another coroutine

Hey there and thanks for the plugin, looks amazingly useful.

I'm trying to figure out how to await a coroutine from within another coroutine.

The docs seem to say this is possible here: https://github.com/landelare/ue5coro/blob/master/Docs/Async.md#other-coroutines
However co_await doesn't appear to work on them in my tests on 1.6.2.

FAsyncCoroutine AMyActor::TestCoroOuter()
{
	co_await TestCoroInner();
}

FAsyncCoroutine AMyActor::TestCoroInner()
{
	co_await UE5Coro::Latent::Seconds(1.0);
}

[C2027] use of undefined type 'UE5Coro::Private::FAsyncAwaiter'

Is this the correct way of doing this? Are there any examples of this anywhere?
Much thanks!

UObject destruction causes it to leak its incomplete(suspended) latent coroutines

If you create a latent coroutine bound to some object, then that couroutine suspends, then the object is destroyed, the coroutine leaks.

For clarity consider this example:

UCLASS()
class UTestCoroObj : public UObject
{
public:
    GENERATED_BODY()

    UE5Coro::TCoroutine<void> Test(FForceLatentCoroutine _ = {})
    {
        UE_LOG(LogTemp, Warning, TEXT("Entering coroutine UTestCoroObj"));
        ON_SCOPE_EXIT
        {
            UE_LOG(LogTemp, Warning, TEXT("Destroying coroutine stack UTestCoroObj"));
        };

        co_await UE5Coro::Async::PlatformSeconds(100000.0f);
    }

    UFUNCTION(BlueprintCallable)
    static void Create(UWorld* World)
    {
        NewObject<UTestCoroObj>(World)->Test();
    }
};

Calling the Create method, then forcing a garbage collection, results in the coroutine being leaked. If the awaited expression never resumes, then the ON_SCOPE_EXIT never runs and the suspended awaiter is never destroyed. Here it is PlatformSeconds (which technically will resume in the far future) but it could be any awaiter, in particular one which can never resume once the object is destroyed.

This is what happens:

  1. Test reaches a suspend point, calling TAwaiter::await_suspend
  2. That marks the promise as "detached from game thread" via DetachFromGameThread (https://github.com/landelare/ue5coro/blob/master/Plugins/UE5Coro/Source/UE5Coro/Public/UE5Coro/AsyncCoroutine.h#L93-L100)
  3. The object is gc'ed some time later, so the latent action manager calls ~FPendingLatentCoroutine which cancels the coroutine and resumes it.
  4. FLatentPromise::Resume will explicitly do nothing, not destroying or resuming the coro handle, because the promise is marked "detached from game thread" (https://github.com/landelare/ue5coro/blob/master/Plugins/UE5Coro/Source/UE5Coro/Private/LatentPromise.cpp#L203-L217)

The comment in FLatentPromise::Resume says "If ownership is borrowed, let the guaranteed future Resume call handle this" but there is not guaranteed to be another Resume call.

I don't fully understand the meaning of "detached from game thread" and why every Latent promise is marked as such, because of that I'm not sure if this is really a bug or intended behavior. The documentation specifically calls out delegate awaiters as suffering from this issue, but also other awaiters (in fact, all async awaiters?) suffer from this issue as well. There is a workaround described for delegates specifically (which doesn't let you capture the delegates arguments, so is strictly not a replacement) but no such workaround for the general case.

Is it dangerous to destroy the coroutine right away, i.e. by not calling DetachFromGameThread in the first place or by calling AttachToGameThread from ~FPendingLatentCoroutine?

SIGSEGV: invalid attempt to access memory at address 0x0

Hello, I'm new to cpp and I have encountered the following issue and do not know what I can do to move forward.
It occurs right after the invocation of the Latent function. Tell me please what is wrong with it or with my code, and how can I fix it?

Caught signal

Unknown() Address = 0x0 (filename not found) [in ???]
UE5Coro::Private::FPromise::Resume(bool) Address = 0x28b8f0598 [/some_path/Plugins/UE5Coro/Source/UE5Coro/Private/Promise.cpp, line 143] [in UnrealEditor-UE5Coro.dylib]
(anonymous namespace)::FPendingLatentCoroutine::UpdateOperation(FLatentResponse&) Address = 0x28b8e7338 [/some_path/Plugins/UE5Coro/Source/UE5Coro/Private/LatentPromise.cpp, line 94] [in UnrealEditor-UE5Coro.dylib]
FLatentActionManager::TickLatentActionForObject(float, TMultiMap<int, FPendingLatentAction*, FDefaultSetAllocator, TDefaultMapHashableKeyFuncs<int, FPendingLatentAction*, true>>&, UObject*) Address = 0x118712178 (filename not found) [in UnrealEditor-Engine.dylib]
FLatentActionManager::ProcessLatentActions(UObject*, float) Address = 0x118710f78 (filename not found) [in UnrealEditor-Engine.dylib]
UWorld::Tick(ELevelTick, float) Address = 0x1187cfbc4 (filename not found) [in UnrealEditor-Engine.dylib]
UEditorEngine::Tick(float, bool) Address = 0x1125b077c (filename not found) [in UnrealEditor-UnrealEd.dylib]
UUnrealEdEngine::Tick(float, bool) Address = 0x1133076b8 (filename not found) [in UnrealEditor-UnrealEd.dylib]
FEngineLoop::Tick() Address = 0x102908844 (filename not found) [in UnrealEditor]
GuardedMain(char16_t const*) Address = 0x102914b10 (filename not found) [in UnrealEditor]
-[UEAppDelegate runGameThread:] Address = 0x10292fcd0 (filename not found) [in UnrealEditor]
-[FCocoaGameThread main] Address = 0x1067753d8 (filename not found) [in UnrealEditor-Core.dylib]
Unknown() Address = 0x18d8a7d14 (filename not found) [in Foundation]
Unknown() Address = 0x18c6cb034 (filename not found) [in libsystem_pthread.dylib]
Unknown() Address = 0x18c6c5e3c (filename not found) [in libsystem_pthread.dylib]
#include "OKAuthComponent.h"
#include "UAuthorisationUseCase.h"
#include "UE5Coro.h"

UFUNCTION(BlueprintCallable, Meta = (Latent, LatentInfo = "LatentInfo"))
FAsyncCoroutine UOKAuthComponent::HandleOKSuccessLink(
	FLatentActionInfo LatentInfo,
	const FString Link,
	FAuthorisationResponse& Response
)
{
	UE_LOG(LogTemp, Display, TEXT("Link data %s"), *Link);

	TArray<FString> Out;
	Link.ParseIntoArray(Out, TEXT("&"), true);

	FString ServerCode = "";
	FString SessionSecretKey = "";

	for (FString String : Out)
	{
		if (String.Contains("code="))
		{
			String.Split("=", nullptr, &ServerCode);
		}
	}

	FAuthorisationRequest RequestBody = FAuthorisationRequest();
	RequestBody.Code = ServerCode;
	RequestBody.ProviderName = TEXT("ok");
	co_await UAuthorisationUseCase::RunAction(RequestBody, Response);
	co_return;
}

[Question] About TCoroutine lifetime

I want to start introducing coroutine to my project by using it to solve one replication race condition:

void CallBack()
{
    // normal function part
    
    auto Coro = [Pawn]() -> UE5Coro::TCoroutine<>
    {
        ON_SCOPE_EXIT
        {
            UE_LOG(LogTemp, Log, TEXT("I am dead")); // (1
        };

        // wait until Pawn has a valid PlayerState
        const auto* PlayerState = co_await Coro_GetPlayerStateFromPawn(Pawn); // (2

        // do stuff with PlayerState

        co_return;
    };
    Coro(); // (3
}

Q1: Why the (1 part not executed after Coro();

the object that Coro() created should get out of scope and destructed, so the related coroutine frame also get destroyed?
("out of scope but can still run" is what a want though, I noticed it was turning into a latent action in FLatentAwaiter::Suspend)

Q2 How to not executing (2 when PIE exit?

I found that if I can't get the PlayerState until PIE exit, the coroutine is resumed to (2,
so I need to check PlayerState to prevent crash, but I hope it do not run in this situation.

Tried to cancel, but:
image

Compile error : A coroutine cannot have a return type containing 'auto'

Using Visual Studio 2022 14.35.32217 toolchain (C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215) and Windows 10.0.22621.0 SDK (C:\Program Files (x86)\Windows Kits\10).

Error C7617 : 'SetupPlayerInputComponent::<lambda_4>::operator ()': A coroutine cannot have a return type containing 'auto'

[C7617] Please use '/await' command-line option to enable relevant extensions

I got this error when trying to capture variable in the coroutine lambda:

auto LoadingLambda = [This{this}]()
{
    auto Coroutine = [WeakThis = FWeakObjectPtr{This}] -> UE5Coro::TCoroutine<>
    {
        // co_await something

        // validate WeakThis

        co_return;
    };
    Coroutine();
};

After changing it to this, it compiles fine:

auto LoadingLambda = [This{this}]()
{
    auto Coroutine = [This] -> UE5Coro::TCoroutine<>
    {
        FWeakObjectPtr WeakThis{This};

        // co_await something

        // validate WeakThis

        co_return;
    };
    Coroutine();
};

but still get a warning from rider:
image

Stop or cancel coroutine

Hello, sorry for my question. I use your UE5Coro to learn Coroutines and UE5. I need to stop or cancel running/suspended coroutine. Is there any possibility to do that under FAsyncCoroutine? Big thanks in advance.

Build crashes with "Exceptions are not supported"

No Stacktrace informations, and it does not happen nor log anything in the editor.
I have no idea how to debug it.

I just have a simple FAsyncCoroutine with a single co_await UE5Coro::Latent::UnpausedSeconds(TimerDelay); line in it.

it also does not crash EVERY time, but across multiple machines it happens often enough to consider removing ue5coro, but I really like the convencience it provides.

Attaching a debugger at a published build also does not give me that much more information.
last stackframes are "ResumeCoro$1::catch", 3 unknown stackframes and then "ResumeCoro".

I changed the method to use the UE TimerManager and hadn't had the issue afterwards (or at least could not force enforce it).

UE5.3 | using currently version UE5coro 1.10.1

Is it dangerous for the UObject to be destroyed without canceling the coroutine?

While testing the coroutine, the following code sometimes causes a crash when the PIE is terminated.
Also, if you are lucky and it does not crash, it seems that the coroutine continues to run even though the PIE has terminated.
It seemed to be no problem if I explicitly called cancel with EndPlay, etc.

You can easily reproduce this by specifying gc.CollectGarbageEveryFrame 1.

Does the coroutine need to explicitly call cancel?

Thank you.

#include "TestActor.h"
#include "UE5Coro.h"

// -------- UTestObject. ------------- //
FAsyncCoroutine UTestObject::CoStart(UObject* Caller)
{
    TestValue = 0.1f;
    co_await UE5Coro::Latent::Seconds(1.0f);
}

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

    Object = NewObject<UTestObject>();
    CoStart();
}

FAsyncCoroutine ATestActor::CoStart()
{
    // Coroutine Start.
    co_await UE5Coro::Latent::NextTick();
    co_await Object->CoStart(this);
    co_await UE5Coro::Latent::Seconds(Object->TestValue);

    // Loop.
    CoStart();
    co_return;
}

failed to compile "DoTests" in file DelegateAwaiterTest.cpp

template<int N, typename... T>
void DoTests(FAutomationTestBase& Test)
{
	if constexpr (N < 8)
	{
		DoTest<(N & 4) != 0, (N & 2) != 0, (N & 1) != 0, T...>(Test);
		DoTests<N + 1, T...>(Test);
	}
}

image

Text utility(538): [C2338] static_assert failed: 'tuple index out of bounds' AsyncAwaiters.h(249): [C2338] see reference to class template instantiation 'std::tuple_element<18446744073709551615,std::tuple<>>' being compiled AsyncAwaiters.h(249): [C2338] see reference to alias template instantiation 'std::tuple_element_t<18446744073709551615,std::tuple<>>' being compiled AsyncAwaiters.h(257): [C2338] see reference to alias template instantiation 'UE5Coro::Private::TDecayedPayload<>::TType<18446744073709551615>' being compiled AsyncAwaiters.h(344): [C2338] see reference to class template instantiation 'UE5Coro::Private::TDecayedPayload<>' being compiled AsyncAwaiters.h(221): [C2338] see reference to function template instantiation 'UE5Coro::Private::TDynamicDelegateAwaiter::TDynamicDelegateAwaiter(T &)' being compiled AsyncAwaiters.h(221): [C2338] see reference to function template instantiation 'UE5Coro::Private::TDynamicDelegateAwaiter::TDynamicDelegateAwaiter(T &)' being compiled AsyncAwaiters.h(221): [C2338] while compiling class template member function 'UE5Coro::Private::TDynamicDelegateAwaiter UE5Coro::Private::TAwaitTransform::operator ()(T &)' AsyncCoroutine.h(259): [C2338] see reference to function template instantiation 'UE5Coro::Private::TDynamicDelegateAwaiter UE5Coro::Private::TAwaitTransform::operator ()(T &)' being compiled AsyncCoroutine.h(257): [C2338] see reference to class template instantiation 'UE5Coro::Private::TAwaitTransform' being compiled DelegateAwaiterTest.cpp(104): [C2338] see reference to function template instantiation 'UE5Coro::Private::TDynamicDelegateAwaiter UE5Coro::Private::FAsyncPromise::await_transform<`anonymous-namespace'::TSelect::FVoid&>(T)' being compiled DelegateAwaiterTest.cpp(277): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTest(FAutomationTestBase &)' being compiled DelegateAwaiterTest.cpp(277): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTests<4,>(FAutomationTestBase &)' being compiled DelegateAwaiterTest.cpp(277): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTests<3,>(FAutomationTestBase &)' being compiled DelegateAwaiterTest.cpp(277): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTests<2,>(FAutomationTestBase &)' being compiled DelegateAwaiterTest.cpp(277): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTests<1,>(FAutomationTestBase &)' being compiled DelegateAwaiterTest.cpp(333): [C2338] see reference to function template instantiation 'void `anonymous-namespace'::DoTests<0,>(FAutomationTestBase &)' being compiled utility(518): [C2794] 'type': is not a member of any direct or indirect base class of 'std::tuple_element<18446744073709551615,std::tuple<>>' AsyncAwaiters.h(249): [C2938] 'std::tuple_element_t' : Failed to specialize alias template

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.