✅
Unreal Engine C++ の非同期処理を JavaScript の Promise ライクに書く方法
方法
TPromise
や TFuture
を利用することで、デリゲートや std::function
/ TFunction
の複雑な処理の受け渡しを減らすことができ、コードが簡潔になる。
TFuture
の Next()
の呼び出しは TPromise
の結果を待たずに処理を終了するため(TPromise
に値が与えられたタイミングで実行されるため)、GameThread
で以下の呼び出しをしてもブロックすることはない。
TFuture<TOptional<FString>> SendHttpRequest(const FString& Url, const FString& Verb) {
auto Promise = MakeShared<TPromise<TOptional<FString>>>();
TFuture<TOptional<FString>> FutureResult = Promise->GetFuture();
const auto Req = FHttpModule::Get().CreateRequest();
Req->OnProcessRequestComplete().BindLambda([CapturedPromise = MoveTemp(Promise)](FHttpRequestPtr Req, FHttpResponsePtr Res, bool Suc) mutable {
if (Suc && EHttpResponseCodes::IsOk(Res->GetResponseCode())) {
CapturedPromise->SetValue(Res->GetContentAsString());
}
else {
CapturedPromise->SetValue(NullOpt);
}
});
Req->SetURL(Url);
Req->SetVerb(Verb);
Req->ProcessRequest();
return FutureResult;
}
TFuture<TOptional<FString>> ParseResponseData() {
return SendHttpRequest("https://jsonplaceholder.typicode.com/todos/1", "GET").Next([](TOptional<FString> Response) {
// 実際は何かする
return Response;
});
}
TFuture<TOptional<FString>> ProcessBusinessLogic() {
return ParseResponseData().Next([](TOptional<FString> ParsedData) {
// 実際は何かする
return ParsedData;
});
}
void ExecuteWorkflow()
{
UE_LOG(LogTemp, Log, TEXT("Start request"));
ProcessBusinessLogic().Next([](TOptional<FString> Result) {
UE_LOG(LogTemp, Log, TEXT("Result: %s"), *(Result.Get("Something went wrong.")));
});
UE_LOG(LogTemp, Log, TEXT("Exiting function."));
}
log
LogTemp: Start request
LogTemp: Exiting function.
LogTemp: Result: {
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}
注意点
TPromise
の SetValue()
メソッドは一度しか呼び出してはいけない。複数回呼ばれる可能性がある場合は特に注意が必要。
(5.4.3 の Engine コード。SetValue()
)
おまけ(Async 関数)
Async 関数を使うと、並列タスクの結果を TFuture
として受け取ることができ、ConstructAndDispatchWhenReady()
を使うよりも便利。GameThread
で呼び出してもスレッドの実行をブロックしない。
void TestFunction()
{
TFuture<int32> Res = Async(EAsyncExecution::TaskGraphMainThread, []() {
UE_LOG(LogTemp, Log, TEXT("Executing async task."));
return 33;
});
Res.Next([](int32 Value) {
UE_LOG(LogTemp, Log, TEXT("Async task completed. Result: %d"), Value);
});
UE_LOG(LogTemp, Log, TEXT("Exiting function."));
}
// LogTemp: Exiting function.
// LogTemp: Executing async task.
// LogTemp: Async task completed. Result: 33
スクラップ
Discussion