// ================================================================================================ // DTFluxRequestManager.h - Gestionnaire C++ optimisé avec cache, timeout et retry // ================================================================================================ #pragma once #include "CoreMinimal.h" #include "Tickable.h" #include "HAL/CriticalSection.h" #include "Struct/DTFluxServerResponseStruct.h" #include "Types/Enum/DTFluxCoreEnum.h" #include "DTFluxQueuedManager.generated.h" class FDTFluxAsyncParser; // ================================================================================================ // 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() 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> 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& 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 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 === virtual void Tick(float DeltaTime) 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; } // === ACCESSEUR POUR LE PARSER (debug/stats) === const FDTFluxAsyncParser* GetAsyncParser() const { return AsyncParser.Get(); } private: // === CONFIGURATION === FDTFluxRequestConfig DefaultConfig; std::atomic bIsInitialized{false}; // === 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> AllRequests; TMap CacheKeyToRequestId; // === CALLBACKS C++ === mutable FCriticalSection CallbacksLock; TMap SuccessCallbacks; TMap ErrorCallbacks; // === MÉTRIQUES === mutable FCriticalSection MetricsLock; mutable int32 TotalRequests = 0; mutable int32 CacheHits = 0; mutable int32 CacheMisses = 0; // === PARSER ASYNCHRONE === TUniquePtr AsyncParser; // === MÉTHODES PRIVÉES === /** * Changer l'état d'une requête et notifier les observers */ void ChangeRequestState(TSharedPtr 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 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); };