We are using TFuture & TPromise quite extensively in our code. But we had to inherit from TPromise and override the destructor because we don’t want to crash the game if a promise is not fulfilled (the destructor contains a check which is hit if the promise is unfulfilled) e.g. if the user exits the game.
[Image Removed]
In 5.6 TPromise and TFuture have been marked as final https://github.com/EpicGames/UnrealEngine/commit/98baa8890a6849a6c8e089c0cd82289bbd64b0e4 which means our code doesn’t work anymore. Is there another way to achieve the same result? Or would it be possible to remove the final from TPromise? We are aware that we could use a custom build, but we would like to use the prebuilt version if possible.
Hi Benjamin,
They have been marked final because they are not designed to be inherited from, and allow direct access to internals (like TFutureState) which are not intended to be exposed. When inheritability and access to non-public state is expected by users, it hinders our ability to make changes and fixes while supporting backwards compatibility - including the kinds of fixes that were necessary which led to final being added to guarantee this going forward.
You are free to removal final from your copy of header so that you can still inherit it. If you do this, it should not require you to rebuild the engine as the ABI will be unaffected. However, it will be an unsupported and you would be taking future incompatibility risks into your own hands.
An alternative would be to wrap rather than inherit - it doesn’t seem too bad, as long as you can accept imperfect perfect forwarding for SetValue:
`template
class TResultPromise final
{
public:
TResultPromise() = default;
TResultPromise(TUniqueFunction<void()>&& CompletionCallback)
: Promise(MoveTemp(CompletionCallback))
{
}
~TResultPromise()
{
if (!bFulfilled)
{
Promise.SetValue(Aborted());
}
}
template <typename… ArgTypes>
void EmplaceValue(ArgTypes&&… Args) → decltype(Promise.EmplaceValue(Forward(Args)…))
{
bFulfilled = true;
Promise.EmplaceValue(Forward(Args)…);
}
template <typename… ArgTypes>
void SetValue(ArgTypes&&… Args) → decltype(Promise.SetValue(Forward(Args)…))
{
bFulfilled = true;
Promise.SetValue(Forward(Args)…);
}
TFuture<TResult> GetFuture()
{
return Promise.GetFuture();
}
private:
TPromise<TResult> Promise;
bool bFulfilled = false;
};`Steve
Thanks a lot for the response. I think removing final would not be an optimal solution, as every team member would need to do this locally & on the build server (and everytime we update the engine). But the second solution is promising, seems to work perfectly.
I understand the reason for adding final. We use Futures for a lot of async tasks like http requests and we don’t want to crash in the check of the destructor if a user exits the application while e.g. a web request is still running in the background. It would help us a lot, if the standard implementation could support that workflow somehow.
The problem is that there isn’t a particular value we can assume that we can assign to the promise in the event of it not being fulfilled. Your particular example is tied to TResult<T> and its Aborted<T>() value, but there’s no equivalent for a generic T.
One thing we could do is expose an IsFulfilled() function, and then you could write a macro which uses ON_SCOPE_EXIT() to give it a value if it is unfulfilled when going out of scope:
`#define ON_UNFULFILLED_PROMISE(Promise, DefaultValue)
ON_SCOPE_EXIT
{
if (!(Promise).IsFulfilled())
{
(Promise).SetValue((DefaultValue))
}
}
…
TPromise<TResult> Promise = …;
ON_UNFULFILLED_PROMISE(Promise, Aborted());
if (bReturnEarly)
{
return; // Promise will be set to Aborted here
}
Promise.Set(ActualResult);
return; // Promise gets the actual result`This would keep TPromise free of any type-specific behaviour but allow users complete control over the return value.
Steve
Oh i see the problem. I think we will stick with the promise wrapper solution, as it fixes the problem without the need to add extra code in other places. Thanks a lot for the replies and the different solutions.