842 lines
22 KiB
C++
842 lines
22 KiB
C++
// ================================================================================================
|
|
// DTFluxRequestManager.cpp - Implémentation du gestionnaire de requêtes
|
|
// ================================================================================================
|
|
|
|
#include "DTFluxQueuedManager.h"
|
|
|
|
#include "DTFluxAsyncParser.h"
|
|
#include "DTFluxNetworkModule.h"
|
|
#include "Struct/DTFluxServerResponseStruct.h"
|
|
#include "Struct/DTFluxRequestStructs.h"
|
|
#include "JsonObjectConverter.h"
|
|
|
|
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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
case EDTFluxApiDataType::RaceData:
|
|
{
|
|
FDTFluxRaceDataRequest RaceData;
|
|
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
|
|
break;
|
|
}
|
|
case EDTFluxApiDataType::TeamList:
|
|
{
|
|
const FDTFluxTeamListRequest TeamList;
|
|
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
|
|
break;
|
|
}
|
|
case EDTFluxApiDataType::ContestRanking:
|
|
{
|
|
FDTFluxContestRankingRequest ContestRanking(ContestId);
|
|
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
|
|
break;
|
|
}
|
|
case EDTFluxApiDataType::StageRanking:
|
|
{
|
|
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
|
|
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
|
|
break;
|
|
}
|
|
case EDTFluxApiDataType::SplitRanking:
|
|
{
|
|
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
|
|
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
|
|
break;
|
|
}
|
|
default:
|
|
JSONString = "";
|
|
break;
|
|
}
|
|
return JSONString;
|
|
}
|
|
|
|
FDTFluxQueuedRequestManager::FDTFluxQueuedRequestManager()
|
|
{
|
|
AsyncParser = MakeUnique<FDTFluxAsyncParser>();
|
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager created"));
|
|
}
|
|
|
|
FDTFluxQueuedRequestManager::~FDTFluxQueuedRequestManager()
|
|
{
|
|
Shutdown();
|
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager destroyed"));
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefaultConfig)
|
|
{
|
|
if (bIsInitialized.load())
|
|
{
|
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("RequestManager already initialized"));
|
|
return;
|
|
}
|
|
|
|
DefaultConfig = InDefaultConfig;
|
|
bIsInitialized.store(true);
|
|
|
|
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs, cache=%.1fs"),
|
|
DefaultConfig.TimeoutSeconds, DefaultConfig.CacheValiditySeconds);
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::Shutdown()
|
|
{
|
|
if (!bIsInitialized.load())
|
|
return;
|
|
|
|
bIsInitialized.store(false);
|
|
// Nettoyer toutes les données
|
|
{
|
|
FScopeLock RequestsLock_Local(&RequestsLock);
|
|
FScopeLock CallbacksLock_Local(&CallbacksLock);
|
|
|
|
AllRequests.Empty();
|
|
CacheKeyToRequestId.Empty();
|
|
SuccessCallbacks.Empty();
|
|
ErrorCallbacks.Empty();
|
|
}
|
|
|
|
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager shutdown"));
|
|
}
|
|
|
|
FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
|
|
EDTFluxApiDataType RequestType,
|
|
int32 ContestId,
|
|
int32 StageId,
|
|
int32 SplitId,
|
|
const FDTFluxRequestConfig& CustomConfig)
|
|
{
|
|
if (!bIsInitialized.load())
|
|
{
|
|
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;
|
|
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
AllRequests.Add(CacheRequestId, CachedRequest);
|
|
}
|
|
|
|
RecordCacheHit();
|
|
return CacheRequestId;
|
|
}
|
|
|
|
// 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;
|
|
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
AllRequests.Add(RequestId, NewRequest);
|
|
TotalRequests++;
|
|
}
|
|
|
|
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())
|
|
{
|
|
FScopeLock Lock(&CallbacksLock);
|
|
|
|
if (OnSuccess.IsBound())
|
|
{
|
|
SuccessCallbacks.Add(RequestId, OnSuccess);
|
|
}
|
|
if (OnError.IsBound())
|
|
{
|
|
ErrorCallbacks.Add(RequestId, OnError);
|
|
}
|
|
}
|
|
|
|
return RequestId;
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::MarkRequestAsSent(const FGuid& RequestId)
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
|
{
|
|
TSharedPtr<FDTFluxTrackedRequest> Request = *RequestPtr;
|
|
Request->SentAt = FDateTime::Now();
|
|
Request->LastAttemptTime = FDateTime::Now();
|
|
ChangeRequestState(Request, EDTFluxRequestState::Sent);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const FString& RawResponseData,
|
|
bool bUseAsyncParsing)
|
|
{
|
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
|
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
|
{
|
|
Request = *RequestPtr;
|
|
}
|
|
}
|
|
|
|
if (!Request.IsValid())
|
|
{
|
|
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;
|
|
}
|
|
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);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
|
|
{
|
|
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;
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::RetryRequest(const FGuid& RequestId)
|
|
{
|
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
|
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
|
{
|
|
Request = *RequestPtr;
|
|
}
|
|
}
|
|
|
|
if (!Request.IsValid() || !Request->CanRetry())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::FindPendingRequest(
|
|
FGuid& OutRequestId,
|
|
EDTFluxApiDataType RequestType,
|
|
int32 ContestId,
|
|
int32 StageId,
|
|
int32 SplitId) const
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
for (const auto& [RequestId, Request] : AllRequests)
|
|
{
|
|
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);
|
|
|
|
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
|
|
{
|
|
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
|
|
{
|
|
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
|
|
|
|
if (CachedRequest->IsCacheValid() && !CachedRequest->RawResponseData.IsEmpty())
|
|
{
|
|
OutRawResponse = CachedRequest->RawResponseData;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FDTFluxQueuedRequestManager::GetParsedFromCache(
|
|
EDTFluxApiDataType RequestType,
|
|
TSharedPtr<FDTFluxServerResponse>& OutResponse,
|
|
int32 ContestId,
|
|
int32 StageId,
|
|
int32 SplitId) const
|
|
{
|
|
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
|
|
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
|
|
{
|
|
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
|
|
{
|
|
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
|
|
|
|
if (CachedRequest->IsCacheValid())
|
|
{
|
|
// 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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
const FDTFluxTrackedRequest* FDTFluxQueuedRequestManager::GetRequestPtr(const FGuid& RequestId) const
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
|
{
|
|
return RequestPtr->Get();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
TArray<FDTFluxTrackedRequest> FDTFluxQueuedRequestManager::GetRequestsByState(EDTFluxRequestState State) const
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
TArray<FDTFluxTrackedRequest> Results;
|
|
|
|
for (const auto& [RequestId, Request] : AllRequests)
|
|
{
|
|
if (Request->State == State)
|
|
{
|
|
Results.Add(*Request);
|
|
}
|
|
}
|
|
|
|
return Results;
|
|
}
|
|
|
|
int32 FDTFluxQueuedRequestManager::GetRequestCount(EDTFluxRequestState State) const
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
int32 Count = 0;
|
|
|
|
for (const auto& [RequestId, Request] : AllRequests)
|
|
{
|
|
if (Request->State == State)
|
|
{
|
|
Count++;
|
|
}
|
|
}
|
|
|
|
return Count;
|
|
}
|
|
|
|
FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::GetStatistics() const
|
|
{
|
|
FScopeLock RequestsLock_Local(&RequestsLock);
|
|
FScopeLock MetricsLock_Local(&MetricsLock);
|
|
|
|
FRequestStatistics Stats;
|
|
|
|
for (const auto& [RequestId, Request] : AllRequests)
|
|
{
|
|
switch (Request->State)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
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())
|
|
{
|
|
ExpiredRequests.Add(RequestId);
|
|
}
|
|
}
|
|
|
|
for (const FGuid& RequestId : ExpiredRequests)
|
|
{
|
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
|
{
|
|
const TSharedPtr<FDTFluxTrackedRequest>& Request = *RequestPtr;
|
|
CacheKeyToRequestId.Remove(Request->GetCacheKey());
|
|
AllRequests.Remove(RequestId);
|
|
}
|
|
}
|
|
|
|
return ExpiredRequests.Num();
|
|
}
|
|
|
|
int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds)
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
TArray<FGuid> OldRequests;
|
|
const FDateTime Threshold = FDateTime::Now() - FTimespan::FromSeconds(OlderThanSeconds);
|
|
|
|
for (const auto& [RequestId, Request] : AllRequests)
|
|
{
|
|
if ((Request->State == EDTFluxRequestState::Completed || Request->State == EDTFluxRequestState::Failed ||
|
|
Request->State == EDTFluxRequestState::TimedOut) && Request->CompletedAt < Threshold)
|
|
{
|
|
OldRequests.Add(RequestId);
|
|
}
|
|
}
|
|
|
|
for (const FGuid& RequestId : OldRequests)
|
|
{
|
|
AllRequests.Remove(RequestId);
|
|
}
|
|
|
|
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
|
|
{
|
|
ChangeRequestState(Request, EDTFluxRequestState::TimedOut);
|
|
TriggerCallbacks(*Request);
|
|
OnRequestFailed.Broadcast(*Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::ProcessRetries()
|
|
{
|
|
FScopeLock Lock(&RequestsLock);
|
|
|
|
const FDateTime Now = FDateTime::Now();
|
|
TArray<TSharedPtr<FDTFluxTrackedRequest>> ReadyToRetry;
|
|
|
|
for (const auto& Pair : AllRequests)
|
|
{
|
|
const auto& Request = Pair.Value;
|
|
if (Request->State == EDTFluxRequestState::Retrying)
|
|
{
|
|
const float ElapsedSinceLastAttempt = (Now - Request->LastAttemptTime).GetTotalSeconds();
|
|
if (ElapsedSinceLastAttempt >= Request->GetRetryDelay())
|
|
{
|
|
ReadyToRetry.Add(Request);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& Request : ReadyToRetry)
|
|
{
|
|
Request->LastAttemptTime = Now;
|
|
ChangeRequestState(Request, EDTFluxRequestState::Pending);
|
|
}
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::ProcessCacheCleanup()
|
|
{
|
|
CleanupExpiredCache();
|
|
CleanupCompletedRequests(600.0f);
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
|
|
{
|
|
FScopeLock Lock(&CallbacksLock);
|
|
|
|
if (Request.State == EDTFluxRequestState::Completed || Request.State == EDTFluxRequestState::Cached)
|
|
{
|
|
// Success Cb
|
|
const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId);
|
|
if (SuccessCallback && SuccessCallback->IsBound())
|
|
{
|
|
SuccessCallback->Execute(Request);
|
|
}
|
|
}
|
|
else if (Request.State == EDTFluxRequestState::Failed || Request.State == EDTFluxRequestState::TimedOut)
|
|
{
|
|
// Error Cb
|
|
const FOnDTFluxRequestError* ErrorCallback = ErrorCallbacks.Find(Request.RequestId);
|
|
if (ErrorCallback && ErrorCallback->IsBound())
|
|
{
|
|
ErrorCallback->Execute(Request, Request.LastErrorMessage);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::CleanupCallbacks(const FGuid& RequestId)
|
|
{
|
|
FScopeLock Lock(&CallbacksLock);
|
|
SuccessCallbacks.Remove(RequestId);
|
|
ErrorCallbacks.Remove(RequestId);
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::RecordCacheHit() const
|
|
{
|
|
FScopeLock Lock(&MetricsLock);
|
|
CacheHits++;
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::RecordCacheMiss() const
|
|
{
|
|
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;
|
|
|
|
auto Request = *RequestPtr;
|
|
|
|
if (bSuccess && ParsedResponse.IsValid())
|
|
{
|
|
Request->ParsedResponse = ParsedResponse;
|
|
Request->bIsResponseParsed = true;
|
|
|
|
UE_LOG(logDTFluxNetwork, VeryVerbose,
|
|
TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"),
|
|
*RequestId.ToString());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s"),
|
|
*RequestId.ToString());
|
|
}
|
|
}
|
|
|
|
void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage)
|
|
{
|
|
UE_LOG(logDTFluxNetwork, Error, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s: %s"),
|
|
*RequestId.ToString(),
|
|
*ErrorMessage);
|
|
}
|
|
|
|
FString FDTFluxQueuedRequestManager::GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
|
|
int32 SplitId)
|
|
{
|
|
return FString::Printf(TEXT("%s_%d_%d_%d"),
|
|
*UEnum::GetValueAsString(RequestType),
|
|
ContestId,
|
|
StageId,
|
|
SplitId);
|
|
}
|