landelare / ue5coro Goto Github PK
View Code? Open in Web Editor NEWA gameplay-focused C++17/20 coroutine implementation for Unreal Engine 5.
License: BSD 3-Clause Clear License
A gameplay-focused C++17/20 coroutine implementation for Unreal Engine 5.
License: BSD 3-Clause Clear License
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!
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.
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
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
?
Whats wrong with exceptions and coroutines together in UE? Is it even possible?
0>F:\UnrealProjects\Ue5CoroTest\Plugins\UE5Coro\Source\UE5CoroTests\Private\ReturnValueTest.cpp(121): Error C2065 : 'bInnerComplete': undeclared identifier
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! 😊
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.
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?
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();
};
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:
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.
Source for required classes CoroParent and CoroChild are here:
CoroCrashOnGameExitSource.zip
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;
}
auto* Copy = new std::function(std::move(Fn));
Why not just Fn(...)
to get the co-routine object?
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
}
(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
)
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.
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);
}
}
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;
}
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;)
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.
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.
This issue reproduces for me 100% of the time.
FForceLatentCoroutine
.GetWorld()
frequently. My test uses co_await UE5Coro::Latent::Seconds
to access GetWorld()
at an interval of 0.1 seconds.BeginPlay()
.UGameplayStatics::LoadStreamLevelBySoftObjectPtr
.UGameplayStatics::UnloadStreamLevelBySoftObjectPtr
.__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
.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
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.
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
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
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:
Repro Case B:
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.
co_yield float{} in generator ==> your promise saves pointer to float value in void*, so next step .Current() returns reference to int, but float under it! And dont use static cast with void* please!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.