Files
DTFluxAPI/Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp

842 lines
22 KiB
C++
Raw Normal View History

2025-07-11 19:04:37 +02:00
// ================================================================================================
// DTFluxRequestManager.cpp - Implémentation du gestionnaire de requêtes
// ================================================================================================
#include "DTFluxQueuedManager.h"
2025-07-11 19:04:37 +02:00
#include "DTFluxAsyncParser.h"
#include "DTFluxNetworkModule.h"
2025-07-11 19:04:37 +02:00
#include "Struct/DTFluxServerResponseStruct.h"
#include "Struct/DTFluxRequestStructs.h"
#include "JsonObjectConverter.h"
2025-07-11 19:04:37 +02:00
bool FDTFluxTrackedRequest::HasTimedOut() const
{
if (State != EDTFluxRequestState::Pending && State != EDTFluxRequestState::Sent)
return false;
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > Config.TimeoutSeconds;
}
bool FDTFluxTrackedRequest::CanRetry() const
{
return CurrentRetries < Config.MaxRetries &&
(State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut);
}
bool FDTFluxTrackedRequest::IsCacheValid() const
{
if (State != EDTFluxRequestState::Cached) return false;
return (FDateTime::Now() - CompletedAt).GetTotalSeconds() < Config.CacheValiditySeconds;
}
float FDTFluxTrackedRequest::GetRetryDelay() const
{
return FMath::Pow(Config.RetryBackoffMultiplier, CurrentRetries);
}
bool FDTFluxTrackedRequest::Matches(EDTFluxApiDataType InType, int32 InContestId, int32 InStageId,
int32 InSplitId) const
{
return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId;
}
2025-07-11 19:04:37 +02:00
FString FDTFluxTrackedRequest::GetCacheKey() const
{
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
}
void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData)
{
RawResponseData = RawData;
ParsedResponse.Reset();
bIsResponseParsed = false;
}
FString FDTFluxTrackedRequest::Serialize() const
{
FString JSONString;
switch (RequestType)
{
2025-07-11 19:04:37 +02:00
case EDTFluxApiDataType::RaceData:
{
FDTFluxRaceDataRequest RaceData;
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
break;
}
2025-07-11 19:04:37 +02:00
case EDTFluxApiDataType::TeamList:
{
const FDTFluxTeamListRequest TeamList;
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
break;
}
2025-07-11 19:04:37 +02:00
case EDTFluxApiDataType::ContestRanking:
{
FDTFluxContestRankingRequest ContestRanking(ContestId);
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
break;
}
2025-07-11 19:04:37 +02:00
case EDTFluxApiDataType::StageRanking:
{
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
break;
}
2025-07-11 19:04:37 +02:00
case EDTFluxApiDataType::SplitRanking:
{
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
break;
}
default:
JSONString = "";
break;
}
return JSONString;
}
2025-07-11 19:04:37 +02:00
FDTFluxQueuedRequestManager::FDTFluxQueuedRequestManager()
{
2025-07-11 19:04:37 +02:00
AsyncParser = MakeUnique<FDTFluxAsyncParser>();
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager created"));
}
2025-07-11 19:04:37 +02:00
FDTFluxQueuedRequestManager::~FDTFluxQueuedRequestManager()
{
2025-07-11 19:04:37 +02:00
Shutdown();
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager destroyed"));
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefaultConfig)
{
2025-07-11 19:04:37 +02:00
if (bIsInitialized.load())
{
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Warning, TEXT("RequestManager already initialized"));
return;
}
2025-07-11 19:04:37 +02:00
DefaultConfig = InDefaultConfig;
bIsInitialized.store(true);
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs, cache=%.1fs"),
DefaultConfig.TimeoutSeconds, DefaultConfig.CacheValiditySeconds);
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::Shutdown()
{
2025-07-11 19:04:37 +02:00
if (!bIsInitialized.load())
return;
2025-07-11 19:04:37 +02:00
bIsInitialized.store(false);
// Nettoyer toutes les données
{
FScopeLock RequestsLock_Local(&RequestsLock);
FScopeLock CallbacksLock_Local(&CallbacksLock);
2025-07-11 19:04:37 +02:00
AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty();
ErrorCallbacks.Empty();
}
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager shutdown"));
}
2025-07-11 19:04:37 +02:00
FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId,
const FDTFluxRequestConfig& CustomConfig)
{
2025-07-11 19:04:37 +02:00
if (!bIsInitialized.load())
{
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized"));
return FGuid();
}
// Vérifier le cache d'abord
FString CachedResponse;
if (CustomConfig.bEnableCache && GetFromCache(RequestType, CachedResponse, ContestId, StageId, SplitId))
{
UE_LOG(logDTFluxNetwork, Log, TEXT("Request served from cache: Type=%s"),
*UEnum::GetValueAsString(RequestType));
// Créer une "fausse" requête pour représenter le hit cache
auto CachedRequest = MakeShared<FDTFluxTrackedRequest>();
CachedRequest->RequestType = RequestType;
CachedRequest->ContestId = ContestId;
CachedRequest->StageId = StageId;
CachedRequest->SplitId = SplitId;
CachedRequest->Config = CustomConfig.bEnableCache ? CustomConfig : DefaultConfig;
CachedRequest->State = EDTFluxRequestState::Cached;
CachedRequest->RawResponseData = CachedResponse;
CachedRequest->CompletedAt = FDateTime::Now();
FGuid CacheRequestId = CachedRequest->RequestId;
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
AllRequests.Add(CacheRequestId, CachedRequest);
}
2025-07-11 19:04:37 +02:00
RecordCacheHit();
return CacheRequestId;
}
2025-07-11 19:04:37 +02:00
// Créer une nouvelle requête
auto NewRequest = MakeShared<FDTFluxTrackedRequest>();
NewRequest->RequestType = RequestType;
NewRequest->ContestId = ContestId;
NewRequest->StageId = StageId;
NewRequest->SplitId = SplitId;
NewRequest->Config = (CustomConfig.TimeoutSeconds > 0) ? CustomConfig : DefaultConfig;
FGuid RequestId = NewRequest->RequestId;
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
AllRequests.Add(RequestId, NewRequest);
TotalRequests++;
}
2025-07-11 19:04:37 +02:00
RecordCacheMiss();
UE_LOG(logDTFluxNetwork, Log, TEXT("Created tracked request %s: Type=%s, Contest=%d, Stage=%d, Split=%d"),
*RequestId.ToString(), *UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
return RequestId;
}
FGuid FDTFluxQueuedRequestManager::CreateTrackedRequestWithCallbacks(
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId,
FOnDTFluxRequestSuccess OnSuccess,
FOnDTFluxRequestError OnError,
const FDTFluxRequestConfig& CustomConfig)
{
FGuid RequestId = CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig);
if (RequestId.IsValid())
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&CallbacksLock);
if (OnSuccess.IsBound())
{
SuccessCallbacks.Add(RequestId, OnSuccess);
}
if (OnError.IsBound())
{
ErrorCallbacks.Add(RequestId, OnError);
}
}
2025-07-11 19:04:37 +02:00
return RequestId;
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::MarkRequestAsSent(const FGuid& RequestId)
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
2025-07-11 19:04:37 +02:00
TSharedPtr<FDTFluxTrackedRequest> Request = *RequestPtr;
Request->SentAt = FDateTime::Now();
Request->LastAttemptTime = FDateTime::Now();
ChangeRequestState(Request, EDTFluxRequestState::Sent);
return true;
}
2025-07-11 19:04:37 +02:00
return false;
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const FString& RawResponseData,
bool bUseAsyncParsing)
{
TSharedPtr<FDTFluxTrackedRequest> Request;
{
FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
2025-07-11 19:04:37 +02:00
Request = *RequestPtr;
}
}
2025-07-11 19:04:37 +02:00
if (!Request.IsValid())
{
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString());
return false;
}
// Stocker la réponse brute
Request->SetRawResponse(RawResponseData);
Request->CompletedAt = FDateTime::Now();
// Décider du parsing selon les callbacks et la configuration
bool bHasCallbacks = false;
{
FScopeLock Lock(&CallbacksLock);
bHasCallbacks = SuccessCallbacks.Contains(RequestId) || ErrorCallbacks.Contains(RequestId);
}
if (bHasCallbacks && bUseAsyncParsing && !RawResponseData.IsEmpty())
{
// Parsing asynchrone pour les callbacks
FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingCompleted
);
FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingFailed
);
AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString());
return true;
}
2025-07-11 19:04:37 +02:00
else
{
// Compléter immédiatement sans parsing ou avec parsing sync
EDTFluxRequestState NewState = Request->Config.bEnableCache
? EDTFluxRequestState::Cached
: EDTFluxRequestState::Completed;
ChangeRequestState(Request, NewState);
if (Request->Config.bEnableCache)
{
FScopeLock Lock(&RequestsLock);
CacheKeyToRequestId.Add(Request->GetCacheKey(), RequestId);
}
// Déclencher les callbacks avec les données brutes
TriggerCallbacks(*Request);
CleanupCallbacks(RequestId);
2025-07-11 19:04:37 +02:00
return true;
}
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
{
2025-07-11 19:04:37 +02:00
TSharedPtr<FDTFluxTrackedRequest> Request;
{
FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
Request = *RequestPtr;
}
}
if (!Request.IsValid())
{
return false;
}
Request->LastErrorMessage = ErrorMessage;
ChangeRequestState(Request, EDTFluxRequestState::Failed);
// Déclencher les callbacks d'erreur
TriggerCallbacks(*Request);
CleanupCallbacks(RequestId);
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed request %s: %s"), *RequestId.ToString(), *ErrorMessage);
return true;
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::RetryRequest(const FGuid& RequestId)
{
2025-07-11 19:04:37 +02:00
TSharedPtr<FDTFluxTrackedRequest> Request;
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
2025-07-11 19:04:37 +02:00
Request = *RequestPtr;
}
}
2025-07-11 19:04:37 +02:00
if (!Request.IsValid() || !Request->CanRetry())
{
2025-07-11 19:04:37 +02:00
return false;
}
2025-07-11 19:04:37 +02:00
Request->CurrentRetries++;
Request->LastAttemptTime = FDateTime::Now();
ChangeRequestState(Request, EDTFluxRequestState::Retrying);
UE_LOG(logDTFluxNetwork, Log, TEXT("Retrying request %s (attempt %d/%d)"),
*RequestId.ToString(), Request->CurrentRetries, Request->Config.MaxRetries);
return true;
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::FindPendingRequest(
FGuid& OutRequestId,
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
for (const auto& [RequestId, Request] : AllRequests)
{
2025-07-11 19:04:37 +02:00
if ((Request->State == EDTFluxRequestState::Pending || Request->State == EDTFluxRequestState::Sent) &&
Request->Matches(RequestType, ContestId, StageId, SplitId))
{
OutRequestId = RequestId;
return true;
}
}
return false;
}
bool FDTFluxQueuedRequestManager::GetFromCache(
EDTFluxApiDataType RequestType,
FString& OutRawResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
2025-07-11 19:04:37 +02:00
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid() && !CachedRequest->RawResponseData.IsEmpty())
{
2025-07-11 19:04:37 +02:00
OutRawResponse = CachedRequest->RawResponseData;
return true;
}
}
2025-07-11 19:04:37 +02:00
}
return false;
}
2025-07-11 19:04:37 +02:00
bool FDTFluxQueuedRequestManager::GetParsedFromCache(
EDTFluxApiDataType RequestType,
TSharedPtr<FDTFluxServerResponse>& OutResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
2025-07-11 19:04:37 +02:00
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
2025-07-11 19:04:37 +02:00
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid())
{
2025-07-11 19:04:37 +02:00
// Parsing lazy si nécessaire
if (!CachedRequest->ParsedResponse.IsSet() && !CachedRequest->RawResponseData.IsEmpty())
{
OutResponse = AsyncParser->ParseResponseSync(CachedRequest->RawResponseData, 1.0f);
if (OutResponse.IsValid())
{
CachedRequest->ParsedResponse = OutResponse;
const_cast<FDTFluxTrackedRequest*>(CachedRequest.Get())->bIsResponseParsed = true;
}
}
else if (CachedRequest->ParsedResponse.IsSet())
{
OutResponse = CachedRequest->ParsedResponse.GetValue();
}
return OutResponse.IsValid();
}
}
2025-07-11 19:04:37 +02:00
}
2025-07-11 19:04:37 +02:00
return false;
}
// === ACCESSEURS ===
bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const
{
FScopeLock Lock(&RequestsLock);
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
OutRequest = **RequestPtr;
return true;
}
return false;
}
2025-07-11 19:04:37 +02:00
const FDTFluxTrackedRequest* FDTFluxQueuedRequestManager::GetRequestPtr(const FGuid& RequestId) const
{
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
return RequestPtr->Get();
}
return nullptr;
}
2025-07-11 19:04:37 +02:00
TArray<FDTFluxTrackedRequest> FDTFluxQueuedRequestManager::GetRequestsByState(EDTFluxRequestState State) const
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
TArray<FDTFluxTrackedRequest> Results;
2025-07-11 19:04:37 +02:00
for (const auto& [RequestId, Request] : AllRequests)
{
2025-07-11 19:04:37 +02:00
if (Request->State == State)
{
Results.Add(*Request);
}
}
2025-07-11 19:04:37 +02:00
return Results;
}
int32 FDTFluxQueuedRequestManager::GetRequestCount(EDTFluxRequestState State) const
{
FScopeLock Lock(&RequestsLock);
int32 Count = 0;
for (const auto& [RequestId, Request] : AllRequests)
{
2025-07-11 19:04:37 +02:00
if (Request->State == State)
{
Count++;
}
}
return Count;
}
2025-07-11 19:04:37 +02:00
FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::GetStatistics() const
{
2025-07-11 19:04:37 +02:00
FScopeLock RequestsLock_Local(&RequestsLock);
FScopeLock MetricsLock_Local(&MetricsLock);
2025-07-11 19:04:37 +02:00
FRequestStatistics Stats;
for (const auto& [RequestId, Request] : AllRequests)
{
2025-07-11 19:04:37 +02:00
switch (Request->State)
{
2025-07-11 19:04:37 +02:00
case EDTFluxRequestState::Pending:
case EDTFluxRequestState::Sent:
case EDTFluxRequestState::Retrying:
Stats.Pending++;
break;
case EDTFluxRequestState::Cached:
Stats.Cached++;
break;
case EDTFluxRequestState::Completed:
Stats.Completed++;
break;
case EDTFluxRequestState::Failed:
case EDTFluxRequestState::TimedOut:
Stats.Failed++;
break;
}
2025-07-11 19:04:37 +02:00
}
Stats.TotalRequests = TotalRequests;
Stats.CacheHits = CacheHits;
Stats.CacheMisses = CacheMisses;
if (Stats.TotalRequests > 0)
{
Stats.HitRate = ((float)Stats.CacheHits / (float)Stats.TotalRequests) * 100.0f;
}
return Stats;
}
// === NETTOYAGE ===
int32 FDTFluxQueuedRequestManager::CleanupExpiredCache()
{
FScopeLock Lock(&RequestsLock);
TArray<FGuid> ExpiredRequests;
for (const auto& [RequestId, Request] : AllRequests)
{
if (Request->State == EDTFluxRequestState::Cached && !Request->IsCacheValid())
{
2025-07-11 19:04:37 +02:00
ExpiredRequests.Add(RequestId);
}
}
2025-07-11 19:04:37 +02:00
for (const FGuid& RequestId : ExpiredRequests)
{
2025-07-11 19:04:37 +02:00
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& Request = *RequestPtr;
CacheKeyToRequestId.Remove(Request->GetCacheKey());
AllRequests.Remove(RequestId);
}
}
2025-07-11 19:04:37 +02:00
return ExpiredRequests.Num();
}
2025-07-11 19:04:37 +02:00
int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds)
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
TArray<FGuid> OldRequests;
const FDateTime Threshold = FDateTime::Now() - FTimespan::FromSeconds(OlderThanSeconds);
2025-07-11 19:04:37 +02:00
for (const auto& [RequestId, Request] : AllRequests)
{
2025-07-11 19:04:37 +02:00
if ((Request->State == EDTFluxRequestState::Completed || Request->State == EDTFluxRequestState::Failed ||
Request->State == EDTFluxRequestState::TimedOut) && Request->CompletedAt < Threshold)
{
2025-07-11 19:04:37 +02:00
OldRequests.Add(RequestId);
}
}
2025-07-11 19:04:37 +02:00
for (const FGuid& RequestId : OldRequests)
{
AllRequests.Remove(RequestId);
}
2025-07-11 19:04:37 +02:00
return OldRequests.Num();
}
void FDTFluxQueuedRequestManager::ClearAllRequests()
{
FScopeLock RequestsLock_Local(&RequestsLock);
FScopeLock CallbacksLock_Local(&CallbacksLock);
AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty();
ErrorCallbacks.Empty();
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all requests"));
}
void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
{
if (!bIsInitialized.load())
return;
// Mise à jour des timers
TimeSinceLastTimeoutCheck += DeltaTime;
TimeSinceLastCacheCleanup += DeltaTime;
TimeSinceLastRetryCheck += DeltaTime;
// Vérifier les timeouts
if (TimeSinceLastTimeoutCheck >= TimeoutCheckInterval)
{
ProcessTimeouts();
TimeSinceLastTimeoutCheck = 0.0f;
}
// Vérifier les retries
if (TimeSinceLastRetryCheck >= RetryCheckInterval)
{
ProcessRetries();
TimeSinceLastRetryCheck = 0.0f;
}
// Nettoyage du cache
if (TimeSinceLastCacheCleanup >= CacheCleanupInterval)
{
ProcessCacheCleanup();
TimeSinceLastCacheCleanup = 0.0f;
}
}
void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request,
EDTFluxRequestState NewState)
{
if (!Request.IsValid())
return;
const EDTFluxRequestState OldState = Request->State;
Request->State = NewState;
// Déclencher l'événement de changement d'état
OnRequestStateChanged.Broadcast(Request->RequestId, NewState);
UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("DTFluxQueuedRequestManager: Request %s state changed from %s to %s"),
*Request->RequestId.ToString(),
*UEnum::GetValueAsString(OldState),
*UEnum::GetValueAsString(NewState));
}
void FDTFluxQueuedRequestManager::ProcessTimeouts()
{
FScopeLock Lock(&RequestsLock);
TArray<TSharedPtr<FDTFluxTrackedRequest>> TimedOutRequests;
for (const auto& Pair : AllRequests)
{
const auto& Request = Pair.Value;
if (Request->HasTimedOut())
{
TimedOutRequests.Add(Request);
}
}
for (const auto& Request : TimedOutRequests)
{
Request->LastErrorMessage = FString::Printf(
TEXT("Request timed out after %.1f seconds"), Request->Config.TimeoutSeconds);
if (Request->CanRetry())
{
Request->CurrentRetries++;
ChangeRequestState(Request, EDTFluxRequestState::Retrying);
}
else
{
2025-07-11 19:04:37 +02:00
ChangeRequestState(Request, EDTFluxRequestState::TimedOut);
TriggerCallbacks(*Request);
OnRequestFailed.Broadcast(*Request);
}
}
2025-07-11 19:04:37 +02:00
}
void FDTFluxQueuedRequestManager::ProcessRetries()
{
FScopeLock Lock(&RequestsLock);
2025-07-11 19:04:37 +02:00
const FDateTime Now = FDateTime::Now();
TArray<TSharedPtr<FDTFluxTrackedRequest>> ReadyToRetry;
for (const auto& Pair : AllRequests)
{
2025-07-11 19:04:37 +02:00
const auto& Request = Pair.Value;
if (Request->State == EDTFluxRequestState::Retrying)
{
const float ElapsedSinceLastAttempt = (Now - Request->LastAttemptTime).GetTotalSeconds();
if (ElapsedSinceLastAttempt >= Request->GetRetryDelay())
{
ReadyToRetry.Add(Request);
}
}
}
2025-07-11 19:04:37 +02:00
for (const auto& Request : ReadyToRetry)
{
2025-07-11 19:04:37 +02:00
Request->LastAttemptTime = Now;
ChangeRequestState(Request, EDTFluxRequestState::Pending);
}
2025-07-11 19:04:37 +02:00
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::ProcessCacheCleanup()
{
CleanupExpiredCache();
CleanupCompletedRequests(600.0f);
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&CallbacksLock);
if (Request.State == EDTFluxRequestState::Completed || Request.State == EDTFluxRequestState::Cached)
{
2025-07-11 19:04:37 +02:00
// Success Cb
const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId);
if (SuccessCallback && SuccessCallback->IsBound())
{
SuccessCallback->Execute(Request);
}
}
2025-07-11 19:04:37 +02:00
else if (Request.State == EDTFluxRequestState::Failed || Request.State == EDTFluxRequestState::TimedOut)
{
2025-07-11 19:04:37 +02:00
// Error Cb
const FOnDTFluxRequestError* ErrorCallback = ErrorCallbacks.Find(Request.RequestId);
if (ErrorCallback && ErrorCallback->IsBound())
{
ErrorCallback->Execute(Request, Request.LastErrorMessage);
}
}
2025-07-11 19:04:37 +02:00
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::CleanupCallbacks(const FGuid& RequestId)
{
FScopeLock Lock(&CallbacksLock);
SuccessCallbacks.Remove(RequestId);
ErrorCallbacks.Remove(RequestId);
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::RecordCacheHit() const
{
FScopeLock Lock(&MetricsLock);
CacheHits++;
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::RecordCacheMiss() const
{
2025-07-11 19:04:37 +02:00
FScopeLock Lock(&MetricsLock);
CacheMisses++;
}
void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess)
{
FScopeLock Lock(&RequestsLock);
auto* RequestPtr = AllRequests.Find(RequestId);
if (!RequestPtr || !RequestPtr->IsValid())
return;
2025-07-11 19:04:37 +02:00
auto Request = *RequestPtr;
2025-07-11 19:04:37 +02:00
if (bSuccess && ParsedResponse.IsValid())
{
2025-07-11 19:04:37 +02:00
Request->ParsedResponse = ParsedResponse;
Request->bIsResponseParsed = true;
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, VeryVerbose,
TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"),
*RequestId.ToString());
}
else
{
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Warning, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s"),
*RequestId.ToString());
}
}
2025-07-11 19:04:37 +02:00
void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage)
{
2025-07-11 19:04:37 +02:00
UE_LOG(logDTFluxNetwork, Error, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s: %s"),
*RequestId.ToString(),
*ErrorMessage);
}
2025-07-11 19:04:37 +02:00
FString FDTFluxQueuedRequestManager::GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
int32 SplitId)
{
2025-07-11 19:04:37 +02:00
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType),
ContestId,
StageId,
SplitId);
}