Fixing Various Bugs On Delegates
This commit is contained in:
@ -1,66 +1,444 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
// ================================================================================================
|
||||
// DTFluxRequestManager.h - Gestionnaire C++ optimisé avec cache, timeout et retry
|
||||
// ================================================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Containers/Queue.h"
|
||||
#include "Tickable.h"
|
||||
#include "Struct/DTFluxRequestStructs.h"
|
||||
#include "HAL/CriticalSection.h"
|
||||
#include "Struct/DTFluxServerResponseStruct.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "DTFluxQueuedManager.generated.h"
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestTimedOut, const FDTFluxQueuedRequest&, TimedOutRequest);
|
||||
class FDTFluxAsyncParser;
|
||||
|
||||
/**
|
||||
* @brief Classe Tickable gérant les requêtes WebSockets qui ne sont pas traçables nativement par l'API.
|
||||
* Cette classe utilise TQueue pour gérer efficacement les requêtes en attente et vérifie leur état dans le tick.
|
||||
*/
|
||||
UCLASS()
|
||||
class DTFLUXNETWORK_API UDTFluxQueuedManager : public UObject, public FTickableGameObject
|
||||
|
||||
// ================================================================================================
|
||||
// ENUMS ET STRUCTURES POUR LES REQUÊTES
|
||||
// ================================================================================================
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EDTFluxRequestState : uint8
|
||||
{
|
||||
Pending UMETA(DisplayName = "Pending"),
|
||||
Sent UMETA(DisplayName = "Sent"),
|
||||
Completed UMETA(DisplayName = "Completed"),
|
||||
Failed UMETA(DisplayName = "Failed"),
|
||||
TimedOut UMETA(DisplayName = "TimedOut"),
|
||||
Cached UMETA(DisplayName = "Cached"),
|
||||
Retrying UMETA(DisplayName = "Retrying")
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct DTFLUXNETWORK_API FDTFluxRequestConfig
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Constructeur par défaut */
|
||||
UDTFluxQueuedManager();
|
||||
virtual ~UDTFluxQueuedManager() override;
|
||||
void Initialize();
|
||||
FGuid QueueRequest(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1,
|
||||
const FString& RawMessage = "");
|
||||
bool MarkRequestAsError(const FGuid& TargetRequestGuid);
|
||||
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid);
|
||||
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
|
||||
bool IsRequestPending(FGuid& OutRequestId, EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1);
|
||||
FDTFluxQueuedRequest* GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1);
|
||||
const FDTFluxQueuedRequest* GetRequest(const FGuid& SearchedGuid);
|
||||
int32 GetPendingRequestCount();
|
||||
int32 CleanupTimedOutRequests();
|
||||
int32 CleanCashedRequests();
|
||||
void ClearAllRequests();
|
||||
// bool TryProcessResponse(const FDTFluxServerResponse& Response);
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
float TimeoutSeconds = 5.0f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
int32 MaxRetries = 3;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
float RetryBackoffMultiplier = 1.5f;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
bool bEnableCache = true;
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||
float CacheValiditySeconds = 60.0f;
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct DTFLUXNETWORK_API FDTFluxTrackedRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
// === IDENTIFICATION ===
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FGuid RequestId = FGuid::NewGuid();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
EDTFluxApiDataType RequestType = EDTFluxApiDataType::None;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 ContestId = -1;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 StageId = -1;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 SplitId = -1;
|
||||
|
||||
// === ÉTAT ET TIMING ===
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
EDTFluxRequestState State = EDTFluxRequestState::Pending;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FDateTime CreatedAt = FDateTime::Now();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FDateTime SentAt = FDateTime::MinValue();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FDateTime CompletedAt = FDateTime::MinValue();
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FDateTime LastAttemptTime = FDateTime::Now();
|
||||
|
||||
// === CONFIGURATION ===
|
||||
FDTFluxRequestConfig Config;
|
||||
|
||||
// === RETRY LOGIC ===
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
int32 CurrentRetries = 0;
|
||||
|
||||
// === DONNÉES DE RÉPONSE ===
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FString RawResponseData;
|
||||
|
||||
// Réponse parsée (lazy loading)
|
||||
mutable TOptional<TSharedPtr<FDTFluxServerResponse>> ParsedResponse;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
bool bIsResponseParsed = false;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly)
|
||||
FString LastErrorMessage;
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
bool HasTimedOut() const;
|
||||
bool CanRetry() const;
|
||||
bool IsCacheValid() const;
|
||||
float GetRetryDelay() const;
|
||||
bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const;
|
||||
FString GetCacheKey() const;
|
||||
void SetRawResponse(const FString& RawData);
|
||||
FString Serialize() const;
|
||||
};
|
||||
|
||||
// ================================================================================================
|
||||
// DELEGATES POUR LES CALLBACKS
|
||||
// ================================================================================================
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnDTFluxRequestSuccess, const FDTFluxTrackedRequest&);
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestError, const FDTFluxTrackedRequest&, const FString& /*ErrorMessage*/);
|
||||
|
||||
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnRequestStateChangedNative, const FGuid& /*RequestId*/,
|
||||
EDTFluxRequestState& /*NewState*/);
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestCompletedNative, const FDTFluxTrackedRequest& /*CompletedRequest*/);
|
||||
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestFailedNative, const FDTFluxTrackedRequest& /*FailedRequest*/);
|
||||
|
||||
// ================================================================================================
|
||||
// REQUEST MANAGER - Classe C++ principale avec SmartPointers
|
||||
// ================================================================================================
|
||||
|
||||
/**
|
||||
* Gestionnaire de requêtes trackées avec cache, timeout, retry et parsing asynchrone
|
||||
* Implémentation C++ pure avec SmartPointers pour des performances optimales
|
||||
*/
|
||||
class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject
|
||||
{
|
||||
public:
|
||||
FDTFluxQueuedRequestManager();
|
||||
virtual ~FDTFluxQueuedRequestManager();
|
||||
|
||||
// === LIFECYCLE ===
|
||||
|
||||
/**
|
||||
* Initialiser le gestionnaire de requêtes
|
||||
* @param DefaultConfig Configuration par défaut pour les nouvelles requêtes
|
||||
*/
|
||||
void Initialize(const FDTFluxRequestConfig& DefaultConfig = FDTFluxRequestConfig());
|
||||
|
||||
/**
|
||||
* Arrêter le gestionnaire et nettoyer toutes les ressources
|
||||
*/
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
* Vérifier si le gestionnaire est initialisé
|
||||
*/
|
||||
bool IsInitialized() const { return bIsInitialized.load(); }
|
||||
|
||||
// === CRÉATION DE REQUÊTES ===
|
||||
|
||||
/**
|
||||
* Créer une nouvelle requête trackée
|
||||
* @param RequestType Type de requête (ContestRanking, StageRanking, etc.)
|
||||
* @param ContestId ID du contest (-1 si non applicable)
|
||||
* @param StageId ID du stage (-1 si non applicable)
|
||||
* @param SplitId ID du split (-1 si non applicable)
|
||||
* @param CustomConfig Configuration spécifique pour cette requête
|
||||
* @return GUID de la requête créée
|
||||
*/
|
||||
FGuid CreateTrackedRequest(
|
||||
EDTFluxApiDataType RequestType,
|
||||
int32 ContestId = -1,
|
||||
int32 StageId = -1,
|
||||
int32 SplitId = -1,
|
||||
const FDTFluxRequestConfig& CustomConfig = FDTFluxRequestConfig()
|
||||
);
|
||||
|
||||
/**
|
||||
* Créer une requête trackée avec callbacks C++
|
||||
* @param RequestType Type de requête
|
||||
* @param ContestId ID du contest
|
||||
* @param StageId ID du stage
|
||||
* @param SplitId ID du split
|
||||
* @param OnSuccess Callback appelé en cas de succès
|
||||
* @param OnError Callback appelé en cas d'erreur
|
||||
* @param CustomConfig Configuration spécifique
|
||||
* @return GUID de la requête créée
|
||||
*/
|
||||
FGuid CreateTrackedRequestWithCallbacks(
|
||||
EDTFluxApiDataType RequestType,
|
||||
int32 ContestId,
|
||||
int32 StageId,
|
||||
int32 SplitId,
|
||||
FOnDTFluxRequestSuccess OnSuccess,
|
||||
FOnDTFluxRequestError OnError,
|
||||
const FDTFluxRequestConfig& CustomConfig = FDTFluxRequestConfig()
|
||||
);
|
||||
|
||||
// === GESTION DES REQUÊTES ===
|
||||
|
||||
/**
|
||||
* Marquer une requête comme envoyée
|
||||
*/
|
||||
bool MarkRequestAsSent(const FGuid& RequestId);
|
||||
|
||||
/**
|
||||
* Compléter une requête avec la réponse reçue
|
||||
* @param RequestId ID de la requête
|
||||
* @param RawResponseData Données JSON brutes de la réponse
|
||||
* @param bUseAsyncParsing Utiliser le parsing asynchrone (recommandé)
|
||||
*/
|
||||
bool CompleteRequest(const FGuid& RequestId, const FString& RawResponseData, bool bUseAsyncParsing = true);
|
||||
|
||||
/**
|
||||
* Marquer une requête comme échouée
|
||||
*/
|
||||
bool FailRequest(const FGuid& RequestId, const FString& ErrorMessage);
|
||||
|
||||
/**
|
||||
* Relancer une requête (si retry possible)
|
||||
*/
|
||||
bool RetryRequest(const FGuid& RequestId);
|
||||
|
||||
// === RECHERCHE ET CACHE ===
|
||||
|
||||
/**
|
||||
* Chercher une requête en attente correspondant aux critères
|
||||
*/
|
||||
bool FindPendingRequest(
|
||||
FGuid& OutRequestId,
|
||||
EDTFluxApiDataType RequestType,
|
||||
int32 ContestId = -1,
|
||||
int32 StageId = -1,
|
||||
int32 SplitId = -1
|
||||
) const;
|
||||
|
||||
/**
|
||||
* Récupérer une réponse depuis le cache (données brutes)
|
||||
*/
|
||||
bool GetFromCache(
|
||||
EDTFluxApiDataType RequestType,
|
||||
FString& OutRawResponse,
|
||||
int32 ContestId = -1,
|
||||
int32 StageId = -1,
|
||||
int32 SplitId = -1
|
||||
) const;
|
||||
|
||||
/**
|
||||
* Récupérer une réponse parsée depuis le cache
|
||||
*/
|
||||
bool GetParsedFromCache(
|
||||
EDTFluxApiDataType RequestType,
|
||||
TSharedPtr<FDTFluxServerResponse>& OutResponse,
|
||||
int32 ContestId = -1,
|
||||
int32 StageId = -1,
|
||||
int32 SplitId = -1
|
||||
) const;
|
||||
|
||||
// === ACCESSEURS ===
|
||||
|
||||
/**
|
||||
* Récupérer une requête par son ID
|
||||
*/
|
||||
bool GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const;
|
||||
|
||||
/**
|
||||
* Récupérer un pointeur vers une requête (plus efficace)
|
||||
*/
|
||||
const FDTFluxTrackedRequest* GetRequestPtr(const FGuid& RequestId) const;
|
||||
|
||||
/**
|
||||
* Récupérer toutes les requêtes dans un état donné
|
||||
*/
|
||||
TArray<FDTFluxTrackedRequest> GetRequestsByState(EDTFluxRequestState State) const;
|
||||
|
||||
/**
|
||||
* Compter les requêtes dans un état donné
|
||||
*/
|
||||
int32 GetRequestCount(EDTFluxRequestState State = EDTFluxRequestState::Pending) const;
|
||||
|
||||
// === STATISTIQUES ===
|
||||
|
||||
/**
|
||||
* Statistiques complètes du gestionnaire de requêtes
|
||||
*/
|
||||
struct FRequestStatistics
|
||||
{
|
||||
int32 Pending = 0;
|
||||
int32 Cached = 0;
|
||||
int32 Completed = 0;
|
||||
int32 Failed = 0;
|
||||
int32 TotalRequests = 0;
|
||||
int32 CacheHits = 0;
|
||||
int32 CacheMisses = 0;
|
||||
float HitRate = 0.0f;
|
||||
};
|
||||
|
||||
FRequestStatistics GetStatistics() const;
|
||||
|
||||
// === NETTOYAGE ===
|
||||
|
||||
/**
|
||||
* Nettoyer les entrées de cache expirées
|
||||
* @return Nombre d'entrées supprimées
|
||||
*/
|
||||
int32 CleanupExpiredCache();
|
||||
|
||||
/**
|
||||
* Nettoyer les requêtes terminées anciennes
|
||||
* @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai
|
||||
* @return Nombre de requêtes supprimées
|
||||
*/
|
||||
int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f);
|
||||
|
||||
/**
|
||||
* Vider toutes les requêtes et le cache
|
||||
*/
|
||||
void ClearAllRequests();
|
||||
|
||||
// === EVENTS ===
|
||||
|
||||
FOnRequestStateChangedNative OnRequestStateChanged;
|
||||
FOnRequestCompletedNative OnRequestCompleted;
|
||||
FOnRequestFailedNative OnRequestFailed;
|
||||
|
||||
// === INTERFACE TICKABLE ===
|
||||
|
||||
// Interface FTickableGameObject
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual bool IsTickable() const override;
|
||||
virtual TStatId GetStatId() const override;
|
||||
virtual bool IsTickable() const override { return true; };
|
||||
|
||||
virtual TStatId GetStatId() const override
|
||||
{
|
||||
RETURN_QUICK_DECLARE_CYCLE_STAT(FDTFluxQueuedRequestManager, STATGROUP_Tickables);
|
||||
};
|
||||
virtual bool IsTickableWhenPaused() const override { return true; }
|
||||
virtual bool IsTickableInEditor() const override { return true; }
|
||||
// Interface ~FTickableGameObject
|
||||
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
|
||||
FOnRequestTimedOut OnRequestTimedOut;
|
||||
// === ACCESSEUR POUR LE PARSER (debug/stats) ===
|
||||
const FDTFluxAsyncParser* GetAsyncParser() const { return AsyncParser.Get(); }
|
||||
|
||||
private:
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> PendingRequestsQueue;
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> CompletedRequestsQueue;
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TimedOutRequestsQueue;
|
||||
// === CONFIGURATION ===
|
||||
FDTFluxRequestConfig DefaultConfig;
|
||||
std::atomic<bool> bIsInitialized{false};
|
||||
|
||||
bool bIsInitialized;
|
||||
float CheckInterval;
|
||||
float TimeSinceLastCheck;
|
||||
// === TIMING POUR LE TICK ===
|
||||
float TimeSinceLastTimeoutCheck = 0.0f;
|
||||
float TimeSinceLastCacheCleanup = 0.0f;
|
||||
float TimeSinceLastRetryCheck = 0.0f;
|
||||
|
||||
static constexpr float TimeoutCheckInterval = 1.0f;
|
||||
static constexpr float CacheCleanupInterval = 30.0f;
|
||||
static constexpr float RetryCheckInterval = 0.5f;
|
||||
|
||||
// === STOCKAGE THREAD-SAFE ===
|
||||
mutable FCriticalSection RequestsLock;
|
||||
TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests;
|
||||
TMap<FString, FGuid> CacheKeyToRequestId;
|
||||
|
||||
// === CALLBACKS C++ ===
|
||||
mutable FCriticalSection CallbacksLock;
|
||||
TMap<FGuid, FOnDTFluxRequestSuccess> SuccessCallbacks;
|
||||
TMap<FGuid, FOnDTFluxRequestError> ErrorCallbacks;
|
||||
|
||||
// === MÉTRIQUES ===
|
||||
mutable FCriticalSection MetricsLock;
|
||||
mutable int32 TotalRequests = 0;
|
||||
mutable int32 CacheHits = 0;
|
||||
mutable int32 CacheMisses = 0;
|
||||
|
||||
|
||||
// === PARSER ASYNCHRONE ===
|
||||
TUniquePtr<FDTFluxAsyncParser> AsyncParser;
|
||||
|
||||
// === MÉTHODES PRIVÉES ===
|
||||
|
||||
/**
|
||||
* Changer l'état d'une requête et notifier les observers
|
||||
*/
|
||||
void ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request, EDTFluxRequestState NewState);
|
||||
|
||||
/**
|
||||
* Traiter les requêtes en timeout (appelé périodiquement)
|
||||
*/
|
||||
void ProcessTimeouts();
|
||||
|
||||
/**
|
||||
* Traiter les requêtes à relancer (appelé périodiquement)
|
||||
*/
|
||||
void ProcessRetries();
|
||||
|
||||
/**
|
||||
* Nettoyer le cache périodiquement
|
||||
*/
|
||||
void ProcessCacheCleanup();
|
||||
|
||||
/**
|
||||
* Déclencher les callbacks pour une requête
|
||||
*/
|
||||
void TriggerCallbacks(const FDTFluxTrackedRequest& Request);
|
||||
|
||||
/**
|
||||
* Nettoyer les callbacks d'une requête
|
||||
*/
|
||||
void CleanupCallbacks(const FGuid& RequestId);
|
||||
|
||||
/**
|
||||
* Enregistrer un hit cache dans les métriques
|
||||
*/
|
||||
void RecordCacheHit() const;
|
||||
|
||||
/**
|
||||
* Enregistrer un miss cache dans les métriques
|
||||
*/
|
||||
void RecordCacheMiss() const;
|
||||
|
||||
|
||||
// === CALLBACKS POUR LE PARSING ASYNCHRONE ===
|
||||
|
||||
/**
|
||||
* Callback appelé quand le parsing asynchrone réussit
|
||||
*/
|
||||
void OnParsingCompleted(const FGuid& RequestId, TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess);
|
||||
|
||||
/**
|
||||
* Callback appelé quand le parsing asynchrone échoue
|
||||
*/
|
||||
void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage);
|
||||
|
||||
// === UTILITAIRES STATIQUES ===
|
||||
|
||||
/**
|
||||
* Générer une clé de cache unique pour une requête
|
||||
*/
|
||||
static FString GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user