SetTimer in 4.27.1 not working

I’ve upgraded the project from version 4.26.2 → 4.27.1

The same code does not work anymore. Has the timer manager implementation changed ?

I have a class FKPlayerManager working with a simple recurring timer

The header file

#pragma once


#include "KLocalPlayer.h"


#if !UE_SERVER

/**
 * @brief Information about the player managed by the FKPlayerManager
 */
struct FKPlayerInfo
{
	bool bConnected;
	int32 Level;
	int32 Elo;

	FKPlayerInfo(const bool InConnected = false, const int32 InLevel = 0, const int32 InElo = 0)
	{
		bConnected = InConnected;
		Level = InLevel;
		Elo = InElo;
	}
};


/**
 * The player manager is in charge of discovering if player is online,
 * invite player in a group,
 */
class FKPlayerManager : public TSharedFromThis<FKPlayerManager>
{
public:

	/** constructor. */
	FKPlayerManager(TWeakObjectPtr<UKLocalPlayer> InLocalPlayer);

	/** destructor. */
	virtual ~FKPlayerManager();

	/**
	* @return the global instance of the FKPlayerManager.
	*/
	static TSharedPtr<FKPlayerManager> Get()
	{
		return FKPlayerManager::Instance;
	}

	/**
	 * @brief Initializes the server manager with a local player.
	 *
	 * @param InLocalPlayer The local player.
	 */
	static void Initialize(TWeakObjectPtr<UKLocalPlayer> InLocalPlayer)
	{
		UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::Initialize"));
		if (FKPlayerManager::Instance.IsValid())
		{
			FKPlayerManager::Instance.Reset();
		}
		FKPlayerManager::Instance = MakeShareable(new FKPlayerManager(InLocalPlayer));
	};

	/**
	* @brief Shutdown the server manager.
	*/
	static void Shutdown()
	{
		UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::Shutdown"));
		if (FKPlayerManager::Instance.IsValid())
		{
			FKPlayerManager::Instance->Cleanup();
			FKPlayerManager::Instance.Reset();
		}
	};

	/**
	 * @brief Is the player's connected.
	 * @param  InUniqueID Unique identifier for the player
	 * @returns True if connected, false if not.
	 */
	bool IsPlayerConnected(const FString InUniqueID);
	/**
	 * @brief Gets the player's level.
	 * @param  InUniqueID Unique identifier for the player
	 * @returns The level if connected, 0 if not.
	 */
	int32 GetPlayerLevel(const FString InUniqueID);
	/**
	 * @brief Gets the player's ELO rating.
	 * @param  InUniqueID Unique identifier for the player
	 * @returns The ELO if connected, -1 if not.
	 */
	int32 GetPlayerElo(const FString InUniqueID);

	/**
	 * @brief Player exist?
	 * @param  InUniqueID Unique identifier for the player.
	 * @returns True if it exists, false if not.
	 */
	bool PlayerExists(const FString InUniqueID) const;

	/**
	 * @brief Gets all players' information array.
	 * @return The array reference.
	 */
	TMap<FString, struct FKPlayerInfo>& GetAllPlayers();

	/**
	 * @brief Refresh the connection status of all players contained within the map.
	 */
	void RefreshConnectionStatus();

	/**
	 * @brief Starts refresh connection status loop.
	 * @param  InRecTime (Optional) The recurring time to refresh the connection status of all players.
	 */
	void StartRefreshConnectionStatus(float InRecTime = 15.0f);

	/**
	 * @brief Stops refresh connection status loop.
	 */
	void StopRefreshConnectionStatus();

protected:
	void Cleanup();

	// Adds a player information
	void AddPlayer(const FString InUniqueID);

	/**
	 * @brief The call-back to known the result of a player connected.
	 * @param  InUniqueID Unique identifier for the in.
	 * @param  bConnected True if connected.
	 * @param  InLevel The level of the player. 0 if not connected
	 * @param  InElo The ELO of the player. -1 if not connected
	 */
	void OnIsPlayerConnectedComplete(const FString InUniqueID, bool bConnected, int32 InLevel, int32 InElo);

private:
	/** @brief The Player Owner. */
	TWeakObjectPtr<UKLocalPlayer> LocalPlayer;
	/** A shared pointer to the global instance of the server manager. */
	static TSharedPtr<FKPlayerManager> Instance;
	/** @brief A map of players.
	 *		   The key is the unique player ID
	 */
	TMap<FString, struct FKPlayerInfo> MapPlayers;

	/** @brief The timer handle of the refresh connection status. */
	FTimerHandle RefreshConnectionStatusHandle;
};

inline TMap<FString, struct FKPlayerInfo>& FKPlayerManager::GetAllPlayers()
{
	return MapPlayers;
}

inline bool FKPlayerManager::PlayerExists(const FString InUniqueID) const
{
	return MapPlayers.Contains(InUniqueID.ToLower());
}

/** @remark we add to unique id in lower case to avoid problem when   
 *			with some functions it is return in lower case
 */
inline void FKPlayerManager::AddPlayer(const FString InUniqueID)
{
	MapPlayers.Add(InUniqueID.ToLower(), FKPlayerInfo(false));
}

#endif // End of !UE_SERVER

And the source file

#include "KPlayerManager.h"
#include "KBasePlayerController.h"
#include "KLocalPlayerHelpers.h"
#include "TimerManager.h"

#if !UE_SERVER

TSharedPtr<FKPlayerManager> FKPlayerManager::Instance = nullptr;

FKPlayerManager::FKPlayerManager(TWeakObjectPtr<UKLocalPlayer> InLocalPlayer)
{
	LocalPlayer = InLocalPlayer;
}

FKPlayerManager::~FKPlayerManager() 
{
	LocalPlayer = nullptr;
}


void FKPlayerManager::Cleanup()
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::Cleanup"));
	if (LocalPlayer != nullptr && LocalPlayer.IsValid())
	{
		if (LocalPlayer->GetWorld())
			LocalPlayer->GetWorldTimerManager().ClearTimer(RefreshConnectionStatusHandle);
	}
	MapPlayers.Empty();
	LocalPlayer = nullptr;
}

bool FKPlayerManager::IsPlayerConnected(const FString InUniqueID)
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::IsPlayerConnected      InUniqueID=%s"), *InUniqueID);
	
	// REMOVE IT WHEN WORKING AGAIN.
	// To check why it is not working
	LocalPlayer->GetWorldTimerManager().ListTimers();


	if (PlayerExists(InUniqueID))
		return MapPlayers[InUniqueID].bConnected;
	// Not found, create it
	AddPlayer(InUniqueID);
	return false;
}

int32 FKPlayerManager::GetPlayerLevel(const FString InUniqueID)
{
	if (PlayerExists(InUniqueID))
		return MapPlayers[InUniqueID].Level;
	// Not found, create it
	AddPlayer(InUniqueID);
	return 0;
}

int32 FKPlayerManager::GetPlayerElo(const FString InUniqueID)
{
	if (PlayerExists(InUniqueID))
		return MapPlayers[InUniqueID].Elo;
	// Not found, create it
	AddPlayer(InUniqueID);
	return -1;
}

void FKPlayerManager::StopRefreshConnectionStatus()
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::StopRefreshConnectionStatus"));
	if (LocalPlayer != nullptr && LocalPlayer.IsValid())
	{
		if (LocalPlayer->GetWorldTimerManager().IsTimerActive(RefreshConnectionStatusHandle))
			LocalPlayer->GetWorldTimerManager().ClearTimer(RefreshConnectionStatusHandle);
	}
}

void FKPlayerManager::StartRefreshConnectionStatus(float InRecTime /*= 15.0f*/)
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::StartRefreshConnectionStatus    InRecTime=%f"), InRecTime);
	if (LocalPlayer != nullptr && LocalPlayer.IsValid())
	{
		if (LocalPlayer->GetWorldTimerManager().IsTimerActive(RefreshConnectionStatusHandle))
			LocalPlayer->GetWorldTimerManager().ClearTimer(RefreshConnectionStatusHandle);

		LocalPlayer->GetWorldTimerManager().SetTimer(RefreshConnectionStatusHandle, FTimerDelegate::CreateRaw(this, &FKPlayerManager::RefreshConnectionStatus), InRecTime, true, 1.0f);
	}
}

void FKPlayerManager::RefreshConnectionStatus()
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::RefreshConnectionStatus"));

	if (AKBasePlayerController* PC = GetBasePlayerController(LocalPlayer.Get()))
	{
		for (auto& Elem : MapPlayers)
		{
			PC->IsPlayerConnected(Elem.Key, FKBindableEventStringBoolInt2::CreateSP(this, &FKPlayerManager::OnIsPlayerConnectedComplete));
		}
	}
}


void FKPlayerManager::OnIsPlayerConnectedComplete(const FString InUniqueID, bool bConnected, int32 InLevel, int32 InElo)
{
	UE_LOG(LogKSGMMessage, Verbose, TEXT("FKPlayerManager::OnIsPlayerConnectedComplete  -   InUniqueID=%s    bConnected=%i   InLevel=%i   InElo=%i"), *InUniqueID, bConnected, InLevel, InElo);

	if (PlayerExists(InUniqueID))
	{
		MapPlayers[InUniqueID.ToLower()].bConnected = bConnected;
		MapPlayers[InUniqueID.ToLower()].Level = InLevel;
		MapPlayers[InUniqueID.ToLower()].Elo = InElo;
	}
}


#endif // End of !UE_SERVER

In version 4.26.2, the timer was calling the function RefreshConnectionStatus()

In version 4.27.1 the timer just call the function the first time, and then never.

[2021.11.10-10.15.03:031][625]LogKSGMMessage: Verbose: FKPlayerManager::StartRefreshConnectionStatus    InRecTime=15.000000
[2021.11.10-10.15.03:848][937]LogKSGMMessage: Verbose: FKPlayerManager::RefreshConnectionStatus

If I check the log, I can see the function is binded with the timer, but the ExpireTime never changed. It stays at ExpireTime=16.066159

[2021.11.10-10.20.54:205][102]LogEngine: ------- 1 Active Timers -------
[2021.11.10-10.20.54:744][102]TimerData 000001FCB55E0100 : bLoop=true, bRequiresDelegate=true, Status=1, Rate=15.000000, ExpireTime=16.066159, Delegate=DELEGATE,NO OBJ,vtbl: 00007FF657BEF510 func: 0x7ff651634000 FKPlayerManager::RefreshConnectionStatus() [D:\KadeoGames\UDK\KSGM\Source\KSGM\Online\KPlayerManager.cpp:105]

even after 1 minute and the callback never called

[2021.11.10-10.21.40:166][629]LogEngine: ------- 1 Active Timers -------
[2021.11.10-10.21.40:166][629]TimerData 000001FCB55E0100 : bLoop=true, bRequiresDelegate=true, Status=1, Rate=15.000000, ExpireTime=16.066159, Delegate=DELEGATE,NO OBJ,vtbl: 00007FF657BEF510 func: 0x7ff651634000 FKPlayerManager::RefreshConnectionStatus() [D:\KadeoGames\UDK\KSGM\Source\KSGM\Online\KPlayerManager.cpp:105]

Something should have changed.

Have a nice day.
D.

I found the problem with the timer not called.
When displaying the main menu, I pause the player controller when the player is in the main menu
PlayerController->SetPause(true);

I remove the pause when in the main menu, and now the timermanager is Tick() in LevelTick.cpp

Sorry for the mistake.
D.