Getting UWorld reference inside lambda function after level travel

Context: I have to trigger a lambda function using a timer, there’s a situation where the trigger could be made after a level travel, that is: set up the timer, level travel, timer triggers.

what would you recommend to use inside a lambda? I’m using the “GEngine->GameViewport” trick to get a world reference, but I’ve read that it could cause a crash, as the GameViewport could be null in some instances.

More specifically I have to get access to the game state inside the lambda? the lambda is triggered before a level travel, so I can’t pass a World context object unless it survives level travel as well.

Here’s abstract code of the situation (only variable names and class names are different):

void CustomGameState::Function(const FCustomStruct& variable, float time)
{
    FTimerHandle Timer;
    FTimerHandle Timer;
    const FTimerDelegate RemoveDelegate = FTimerDelegate::CreateLambda(
        [variable]()
        {
            const UWorld* World = GEngine->GameViewport->GetWorld();
            if(!World)
                return;
            AGameStateBase* GameState = World->GetGameState();
            if(GameState && GameState->Implements<UCustomGameStateInterface>())
                ICustomGameStateInterface::Execute_OtherFunction1(GameState, variable);
        });
    this->GetWorld()->GetTimerManager().SetTimer(Timer, RemoveDelegate, time, false);
    this->Execute_OtherFunction2(this, variable);
}

I know it’s super convoluted and prone to error, but that’s why I’d like to fix it somehow and make it more robust so it doesn’t cause any CTDs to future players.

Things I tried:

  • Capturing the “World” reference of the original GameState object before level travel, didn’t work (World is not null, but returns a null game state)
  • Capturing the GameState object before level travel (null access violation)

Maybe I could use the game instance as an object reference? I still don’t like the idea much, but it’s the only way I could make it work between level travels.

PD: for those curious, I’m applying a temporary change to a GameState variable, before level travel came into the soup and ruined everything, I had a normal TimerDelegate without all the convolution of the lambda and getting a new GameState reference, I was not responsible of making sure the GameState variables were maintained between level travel though, but I kinda know the game state recovers some of the previous instance from the game instance.

thanks in advance

Nothing wrong with that, the game instance lives on while the game plays.

This on its own sounds fragile. You don’t know if the level finishes loadling before the timer triggers. Even if the timer survives. Even if the player doesn’t crash / quit during loading skipping the timer entirely. Why not use delegates to when a level loads / unloads and manage the trigger you speak of in a GameInstanceSubsystem?

sorry for the late answer, we had a rough schedule at the studio and haven’t had the time to check up on things here

thanks for the answer! I ended up using the game instance object and it works just fine

a fine solution for the level loading race condition would be to pause the timer while level travel is being done and resume them when loading is finished, i think adding these timer handles to a list on a game instance subsystem would work very well, as this jank solution is not only being done here, and managing the timers and the delegates on such a subsystem would work well

if the player quits during loading and skips the timer that’s fine as well, the custom struct has a unique identifier, and the function i’m running is to remove it from a list on the players game state, so if the player quits, the list gets refreshed and no harm is done