// ================================================================================================ // DTFluxNetworkSubsystem.cpp - Implémentation complète du subsystem avec compatibilité // ================================================================================================ #include "Subsystems/DTFluxNetworkSubsystem.h" #include "DTFluxQueuedManager.h" #include "DTFluxNetworkModule.h" #include "DTFluxNetworkSettings.h" #include "JsonObjectConverter.h" #include "Clients/DTFluxWebSocketClient.h" #include "Struct/DTFluxServerResponseStruct.h" #include "Struct/DTFluxRequestStructs.h" #include "Types/Objects/UDTFluxParticipantFactory.h" // ================================================================================================ // LIFECYCLE DU SUBSYSTEM // ================================================================================================ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); UE_LOG(logDTFluxNetwork, Log, TEXT("Initializing DTFluxNetworkSubsystem...")); // === CHARGER LES PARAMÈTRES === UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault(); UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings); // === CRÉER LES CLIENTS RÉSEAU === WsClient = MakeShareable(new FDTFluxWebSocketClient()); // === CRÉER LE GESTIONNAIRE DE REQUÊTES OPTIMISÉ === RequestManager = MakeShared(); FDTFluxRequestConfig DefaultConfig; DefaultConfig.TimeoutSeconds = 5.0f; DefaultConfig.MaxRetries = 3; DefaultConfig.RetryBackoffMultiplier = 1.5f; RequestManager->Initialize(DefaultConfig); // === CONNECTER LES ÉVÉNEMENTS === RegisterWebSocketEvents(); // Connecter les événements du RequestManager aux delegates Blueprint RequestManager->OnRequestCompleted.AddUObject(this, &UDTFluxNetworkSubsystem::OnRequestCompleted_Internal); RequestManager->OnRequestFailed.AddUObject(this, &UDTFluxNetworkSubsystem::OnRequestFailed_Internal); #if WITH_EDITOR // Écouter les changements de configuration en éditeur NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged")); #endif // === CONNEXION AUTOMATIQUE === if (WsSettings.bShouldConnectAtStartup) { WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->Connect(); } UE_LOG(logDTFluxNetwork, Log, TEXT("DTFluxNetworkSubsystem initialized successfully with optimized RequestManager")); } void UDTFluxNetworkSubsystem::Deinitialize() { UE_LOG(logDTFluxNetwork, Log, TEXT("Deinitializing DTFluxNetworkSubsystem...")); // === NETTOYER LE REQUEST MANAGER === if (RequestManager.IsValid()) { RequestManager->OnRequestCompleted.RemoveAll(this); RequestManager->OnRequestFailed.RemoveAll(this); RequestManager->Shutdown(); RequestManager.Reset(); } // === DÉCONNECTER LES CLIENTS === UnregisterWebSocketEvents(); if (WsClient.IsValid()) { WsClient->Disconnect(); WsClient.Reset(); } Super::Deinitialize(); UE_LOG(logDTFluxNetwork, Log, TEXT("DTFluxNetworkSubsystem deinitialized")); } // ================================================================================================ // CONNEXION WEBSOCKET // ================================================================================================ void UDTFluxNetworkSubsystem::Connect() { if (WsClient.IsValid()) { WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->Connect(); UE_LOG(logDTFluxNetwork, Log, TEXT("Connecting to WebSocket: %s"), *WsClient->GetAddress()); } } void UDTFluxNetworkSubsystem::Disconnect() { if (WsClient.IsValid()) { WsClient->Disconnect(); UE_LOG(logDTFluxNetwork, Log, TEXT("Disconnecting from WebSocket")); } } void UDTFluxNetworkSubsystem::Reconnect() { UE_LOG(logDTFluxNetwork, Log, TEXT("Reconnecting WebSocket...")); ReconnectWs(FName("Ws_Client_0")); } // ================================================================================================ // REQUÊTES TRACKÉES (NOUVEAU SYSTÈME OPTIMISÉ) // ================================================================================================ FGuid UDTFluxNetworkSubsystem::SendTrackedRequest( EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId, float TimeoutSeconds, int32 MaxRetries, bool bEnableCache) { if (!RequestManager.IsValid()) { UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not available")); return FGuid(); } // Configuration personnalisée pour cette requête FDTFluxRequestConfig CustomConfig; CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.MaxRetries = MaxRetries; CustomConfig.RetryBackoffMultiplier = 1.5f; FGuid RequestId = RequestManager->CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig); if (RequestId.IsValid()) { // Récupérer la requête pour l'envoyer if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) { RequestManager->MarkRequestAsSent(RequestId); SendQueuedRequest(*Request); } } return RequestId; } FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks( EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId, FOnDTFluxRequestSuccess& OnSuccess, FOnDTFluxRequestError& OnError, float TimeoutSeconds, int32 MaxRetries ) { if (!RequestManager.IsValid()) { UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not available")); return FGuid(); } FDTFluxRequestConfig CustomConfig; CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.MaxRetries = MaxRetries; CustomConfig.RetryBackoffMultiplier = 1.5f; FGuid RequestId = RequestManager->CreateTrackedRequestWithCallbacks( RequestType, ContestId, StageId, SplitId, OnSuccess, OnError, CustomConfig); if (RequestId.IsValid()) { if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) { RequestManager->MarkRequestAsSent(RequestId); SendQueuedRequest(*Request); } } return RequestId; } // ================================================================================================ // ACCESSEURS BLUEPRINT POUR LES REQUÊTES TRACKÉES // ================================================================================================ bool UDTFluxNetworkSubsystem::GetTrackedRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const { if (RequestManager.IsValid()) { return RequestManager->GetRequest(RequestId, OutRequest); } return false; } bool UDTFluxNetworkSubsystem::HasRequestReceivedResponse(const FGuid& RequestId) const { FDTFluxTrackedRequest Request; if (GetTrackedRequest(RequestId, Request)) { return Request.State == EDTFluxRequestState::Completed || !Request.RawResponseData.IsEmpty(); } return false; } FString UDTFluxNetworkSubsystem::GetRequestResponseData(const FGuid& RequestId) const { FDTFluxTrackedRequest Request; if (GetTrackedRequest(RequestId, Request)) { return Request.RawResponseData; } return FString(); } bool UDTFluxNetworkSubsystem::IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId) const { if (RequestManager.IsValid()) { FGuid OutRequestId; return RequestManager->FindPendingRequest(OutRequestId, RequestType, ContestId, StageId, SplitId); } return false; } int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const { if (RequestManager.IsValid()) { return RequestManager->GetRequestCount(EDTFluxRequestState::Pending) + RequestManager->GetRequestCount(EDTFluxRequestState::Sent); } return 0; } void UDTFluxNetworkSubsystem::GetRequestStatistics(int32& OutPending, int32& OutCompleted, int32& OutFailed) const { if (RequestManager.IsValid()) { FDTFluxQueuedRequestManager::FRequestStatistics Stats = RequestManager->GetStatistics(); OutPending = Stats.Pending; OutCompleted = Stats.Completed; OutFailed = Stats.Failed; } else { OutPending = OutCompleted = OutFailed = 0; } } // ================================================================================================ // REQUÊTES LEGACY (COMPATIBILITÉ TOTALE) // ================================================================================================ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxApiDataType RequestType, int InContestId, int InStageId, int InSplitId) { FString Message; switch (RequestType) { case EDTFluxApiDataType::ContestRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxContestRankingRequest(InContestId), Message); break; case EDTFluxApiDataType::StageRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message); break; case EDTFluxApiDataType::SplitRanking: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message); break; case EDTFluxApiDataType::TeamList: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message); break; case EDTFluxApiDataType::RaceData: FJsonObjectConverter::UStructToJsonObjectString(FDTFluxRaceDataRequest(), Message); break; default: UE_LOG(logDTFluxNetwork, Warning, TEXT("Unknown request type: %s"), *UEnum::GetValueAsString(RequestType)); return; } // Dirty trick to fix Case (compatibilité avec l'ancienne API) Message = Message.Replace(TEXT("Id"), TEXT("ID"), ESearchCase::CaseSensitive); UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending Legacy Request %s"), *Message); SendMessage(Message); } void UDTFluxNetworkSubsystem::SendMessage(const FString& Message) { UE_LOG(logDTFluxNetwork, Verbose, TEXT("Sending Message: %s"), *Message); if (WsClient.IsValid() && WsClient->CanSend()) { WsClient->Send(Message); UE_LOG(logDTFluxNetwork, Log, TEXT("Message sent successfully")); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("WebSocket not connected. Connect before sending requests.")); } } // ================================================================================================ // GESTION DES ÉVÉNEMENTS WEBSOCKET // ================================================================================================ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents() { if (!WsClient.IsValid()) return; OnWsConnectedEventDelegateHandle = WsClient->RegisterConnectedEvent() .AddUObject( this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem); OnWsConnectionErrorEventDelegateHandle = WsClient->RegisterConnectionError() .AddUObject( this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem); OnWsClosedEventDelegateHandle = WsClient->RegisterClosedEvent() .AddUObject( this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem); OnWsMessageEventDelegateHandle = WsClient->RegisterMessageEvent() .AddUObject( this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem); OnWsMessageSentEventDelegateHandle = WsClient->RegisterMessageSentEvent() .AddUObject( this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem); } void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() const { if (!WsClient.IsValid()) return; if (OnWsConnectedEventDelegateHandle.IsValid()) WsClient->UnregisterConnectedEvent().Remove(OnWsConnectedEventDelegateHandle); if (OnWsConnectionErrorEventDelegateHandle.IsValid()) WsClient->UnregisterConnectionError().Remove(OnWsConnectionErrorEventDelegateHandle); if (OnWsClosedEventDelegateHandle.IsValid()) WsClient->UnregisterClosedEvent().Remove(OnWsClosedEventDelegateHandle); if (OnWsMessageEventDelegateHandle.IsValid()) WsClient->UnregisterMessageEvent().Remove(OnWsMessageEventDelegateHandle); if (OnWsMessageSentEventDelegateHandle.IsValid()) WsClient->UnregisterMessageSentEvent().Remove(OnWsMessageSentEventDelegateHandle); } void UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem() { WsStatus = EDTFluxConnectionStatus::Connected; OnWebSocketConnected.Broadcast(); UE_LOG(logDTFluxNetwork, Warning, TEXT("WebSocket connected to %s"), *WsClient->GetAddress()); } void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString& Error) { UE_LOG(logDTFluxNetwork, Warning, TEXT("WebSocket error with %s: %s"), *WsClient->GetAddress(), *Error); WsStatus = EDTFluxConnectionStatus::Error; if (WsSettings.bShouldAutoReconnectOnError) { WsClient->Reconnect(); } } void UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean) { UE_LOG(logDTFluxNetwork, Warning, TEXT("WebSocket closed: Reason=%s, StatusCode=%d, Clean=%s"), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False")); WsStatus = EDTFluxConnectionStatus::Closed; } void UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString) { UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("Received WebSocket message: %s"), *MessageString); // Essayer d'abord de matcher avec une requête trackée if (!TryMatchResponseToQueuedRequest(MessageString)) { // Si pas de match, traiter en mode legacy ProcessLegacyResponse(MessageString); } } void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent) { UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("WebSocket message sent: %s"), *MessageSent); } bool UDTFluxNetworkSubsystem::TryMatchResponseToQueuedRequest(const FString& MessageString) { if (!RequestManager.IsValid()) return false; // Parser rapidement pour identifier le type de réponse EDTFluxResponseStatus Status; FDTFluxServerResponse QuickResponse(MessageString, Status, false); if (Status != EDTFluxResponseStatus::Success) { UE_LOG(logDTFluxNetwork, Warning, TEXT("Failed to parse response for matching: %s"), *QuickResponse.GetErrorMessage()); return false; } // Chercher une requête correspondante FGuid FoundRequestId; if (RequestManager->FindPendingRequest(FoundRequestId, QuickResponse.GetResponseType(), QuickResponse.ContestID, QuickResponse.StageID, QuickResponse.SplitID)) { // Utiliser le parsing asynchrone pour les réponses volumineuses bool bUseAsyncParsing = ShouldUseAsyncParsing(MessageString); if (RequestManager->CompleteRequest(FoundRequestId, MessageString, bUseAsyncParsing)) { } UE_LOG(logDTFluxNetwork, Log, TEXT("Matched response to tracked request %s (async=%s)"), *FoundRequestId.ToString(), bUseAsyncParsing ? TEXT("true") : TEXT("false")); return true; } return false; } void UDTFluxNetworkSubsystem::ProcessLegacyResponse(const FString& MessageString) { UE_LOG(logDTFluxNetwork, Log, TEXT("Processing legacy response")); // Parsing synchrone pour le mode legacy (pour simplicité) EDTFluxResponseStatus Status; TSharedPtr ParsedResponse = MakeShared(MessageString, Status); if (Status == EDTFluxResponseStatus::Success) { ProcessParsedResponse(ParsedResponse); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse legacy response: %s"), *ParsedResponse->GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ProcessParsedResponse(TSharedPtr ParsedResponse) { if (!ParsedResponse.IsValid()) return; // Traiter les messages push (temps réel) d'abord if (ProcessPushMessage(*ParsedResponse) == EDTFluxResponseStatus::Success) { return; // Message push traité avec succès } // Traiter les réponses classiques selon le type switch (ParsedResponse->GetResponseType()) { case EDTFluxApiDataType::RaceData: ParseRaceData(*ParsedResponse); break; case EDTFluxApiDataType::TeamList: ParseTeamListResponse(*ParsedResponse); break; case EDTFluxApiDataType::ContestRanking: ParseContestRanking(*ParsedResponse); break; case EDTFluxApiDataType::StageRanking: ParseStageRankingResponse(*ParsedResponse); break; case EDTFluxApiDataType::SplitRanking: ParseSplitRankingResponse(*ParsedResponse); break; default: UE_LOG(logDTFluxNetwork, Warning, TEXT("Unknown response type in legacy processing: %s"), *UEnum::GetValueAsString(ParsedResponse->GetResponseType())); break; } } // ================================================================================================ // CALLBACKS POUR LE REQUEST MANAGER // ================================================================================================ void UDTFluxNetworkSubsystem::OnRequestCompleted_Internal(const FDTFluxTrackedRequest& CompletedRequest) { // Broadcaster l'événement Blueprint OnTrackedRequestCompleted.Broadcast( CompletedRequest.RequestId, CompletedRequest.RequestType, CompletedRequest.RawResponseData ); if (CompletedRequest.ParsedResponse.IsSet()) { UE_LOG(logDTFluxNetwork, Log, TEXT("Tracked About to process : %s"), *CompletedRequest.RequestId.ToString()); TSharedPtr Response = CompletedRequest.ParsedResponse.GetValue(); ProcessParsedResponse(Response); } } void UDTFluxNetworkSubsystem::OnRequestFailed_Internal(const FDTFluxTrackedRequest& FailedRequest) { // Broadcaster l'événement Blueprint OnTrackedRequestFailed.Broadcast( FailedRequest.RequestId, FailedRequest.RequestType, FailedRequest.LastErrorMessage ); UE_LOG(logDTFluxNetwork, Warning, TEXT("Tracked request failed: %s - %s"), *FailedRequest.RequestId.ToString(), *FailedRequest.LastErrorMessage); } // ================================================================================================ // CONFIGURATION DYNAMIQUE // ================================================================================================ void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings) { bool bNeedsReload = WsSettings != NewWsSettings; WsSettings = NewWsSettings; if (bNeedsReload || WsSettings.bShouldConnectAtStartup) { UE_LOG(logDTFluxNetwork, Warning, TEXT("WebSocket settings changed, reconnecting...")); ReconnectWs(FName("Ws_Client_0")); } } void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId) { if (WsClient.IsValid()) { FString NewAddress = ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port); WsClient->SetAddress(NewAddress); WsClient->Reconnect(); UE_LOG(logDTFluxNetwork, Log, TEXT("Reconnecting to: %s"), *NewAddress); } } // ================================================================================================ // MÉTHODES DE PARSING LEGACY (COMPATIBILITÉ TOTALE) // ================================================================================================ void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response) { FDTFluxTeamListDefinition TeamListDefinition; Response.ParseTeamList(TeamListDefinition); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnTeamListReceived.ExecuteIfBound(TeamListDefinition); UE_LOG(logDTFluxNetwork, Log, TEXT("Team list parsed: %d participants"), TeamListDefinition.Participants.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse team list: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseRaceData(FDTFluxServerResponse& Response) { FDTFluxRaceData RaceData; Response.ParseRaceData(RaceData); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnRaceDataReceived.ExecuteIfBound(RaceData); UE_LOG(logDTFluxNetwork, Log, TEXT("Race data parsed: %d contests"), RaceData.Datas.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse race data: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseContestRanking(FDTFluxServerResponse& Response) { FDTFluxContestRankings ContestRankings; Response.ParseContestRanking(ContestRankings); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnContestRankingReceived.ExecuteIfBound(ContestRankings); UE_LOG(logDTFluxNetwork, Log, TEXT("Contest ranking parsed for Contest %d: %d entries"), ContestRankings.ContestId, ContestRankings.Rankings.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse contest ranking: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseStageRankingResponse(FDTFluxServerResponse& Response) { FDTFluxStageRankings StageRankings; Response.ParseStageRanking(StageRankings); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnStageRankingReceived.ExecuteIfBound(StageRankings); UE_LOG(logDTFluxNetwork, Log, TEXT("Stage ranking parsed for Contest %d, Stage %d: %d entries"), StageRankings.ContestId, StageRankings.StageId, StageRankings.Rankings.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse stage ranking: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(FDTFluxServerResponse& Response) { FDTFluxSplitRankings SplitRankings; Response.ParseSplitRanking(SplitRankings); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnSplitRankingReceived.ExecuteIfBound(SplitRankings); UE_LOG(logDTFluxNetwork, Log, TEXT("Split ranking parsed for Contest %d, Stage %d, Split %d: %d entries"), SplitRankings.ContestId, SplitRankings.StageId, SplitRankings.SplitId, SplitRankings.Rankings.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split ranking: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(FDTFluxServerResponse& Response) { FDTFluxTeamStatusUpdate StatusUpdate; Response.ParseStatusUpdate(StatusUpdate); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate); UE_LOG(logDTFluxNetwork, Log, TEXT("Status update parsed for Bib %d: %s"), StatusUpdate.Bib, *UEnum::GetValueAsString(StatusUpdate.Status)); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status update: %s"), *Response.GetErrorMessage()); } } void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(FDTFluxServerResponse& Response) { TArray SplitSensorInfos; Response.ParseSplitSensor(SplitSensorInfos); if (Response.GetParsingStatus() == EDTFluxResponseStatus::Success) { for (const auto& SplitSensorInfo : SplitSensorInfos) { OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo); UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("Split sensor parsed for Bib %d on Contest %d, Stage %d, Split %d"), SplitSensorInfo.Bib, SplitSensorInfo.ContestId, SplitSensorInfo.StageId, SplitSensorInfo.SplitId); } UE_LOG(logDTFluxNetwork, Log, TEXT("Split sensor data parsed: %d entries"), SplitSensorInfos.Num()); } else { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split sensor: %s"), *Response.GetErrorMessage()); } } EDTFluxResponseStatus UDTFluxNetworkSubsystem::ProcessPushMessage(FDTFluxServerResponse& Response) { // Traiter les messages push (temps réel) qui ne correspondent pas à des requêtes EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::UnknownError; if (DTFluxDataTypeUtils::IsPushOnly(Response.GetResponseType())) { switch (Response.GetResponseType()) { case EDTFluxApiDataType::SplitSensor: { TArray SplitSensorInfos; if (Response.ParseSplitSensor(SplitSensorInfos)) { for (const auto& SplitSensorInfo : SplitSensorInfos) { OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo); } } ResponseStatus = Response.GetParsingStatus(); break; } case EDTFluxApiDataType::StatusUpdate: { FDTFluxTeamStatusUpdate StatusUpdate; if (Response.ParseStatusUpdate(StatusUpdate)) { OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate); } ResponseStatus = Response.GetParsingStatus(); break; } case EDTFluxApiDataType::TeamUpdate: { FDTFluxTeamListDefinition TeamUpdateList; if (Response.ParseTeamUpdate(TeamUpdateList)) { OnTeamUpdateReceived.ExecuteIfBound(TeamUpdateList); } ResponseStatus = Response.GetParsingStatus(); break; } default: { ResponseStatus = EDTFluxResponseStatus::UnknownError; break; } } UE_LOG(logDTFluxNetwork, Log, TEXT("Processed push message: %s"), *UEnum::GetValueAsString(Response.GetResponseType())); } return ResponseStatus; } // ================================================================================================ // UTILITAIRES // ================================================================================================ FString UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port) { FString NewAddress; if (!Address.Contains("ws://") && !Address.Contains("wss://")) { NewAddress += FString("ws://"); } NewAddress += Address + FString(":") + FString::FromInt(Port) + Path; return NewAddress; } void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxTrackedRequest& QueuedRequest) { FString Message = QueuedRequest.Serialize(); if (Message.IsEmpty()) { UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to serialize request %s"), *QueuedRequest.RequestId.ToString()); if (RequestManager.IsValid()) { RequestManager->FailRequest(QueuedRequest.RequestId, TEXT("Serialization failed")); } return; } // Dirty trick to fix Case (compatibilité avec l'ancienne API) Message = Message.Replace(TEXT("Id"), TEXT("ID"), ESearchCase::CaseSensitive); UE_LOG(logDTFluxNetwork, Log, TEXT("Sending tracked request %s: %s"), *QueuedRequest.RequestId.ToString(), *Message); SendMessage(Message); } bool UDTFluxNetworkSubsystem::ShouldUseAsyncParsing(const FString& JsonData) const { // Critère pour décider du parsing asynchrone : // - Taille des données (> 1KB par défaut) // Pour le moment uniquement taille constexpr int32 AsyncThreshold = 1024; // 1KB return JsonData.Len() > AsyncThreshold; }