Adds Custom DataAsset UI + Added Request buttons for RaceDatas/TeamList/Rankings request to ApiStatus Tab + Addes Tracked Requests For Rankings + Added Utils Module For Blueprint Utilities Functions

This commit is contained in:
2025-07-08 16:50:31 +02:00
parent 7e1ce2cdfa
commit b63f2dd7b5
40 changed files with 4027 additions and 1199 deletions

View File

@ -0,0 +1,401 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxQueuedManager.h"
#include "DTFluxNetworkModule.h"
#include "JsonObjectConverter.h"
const FString FDTFluxQueuedRequest::Serialize() const
{
FString JSONString;
switch (RequestType)
{
case EDTFluxRequestType::RaceData:
{
FDTFluxRaceDataRequest RaceData;
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
break;
}
case EDTFluxRequestType::TeamList:
{
const FDTFluxTeamListRequest TeamList;
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
break;
}
case EDTFluxRequestType::ContestRanking:
{
FDTFluxContestRankingRequest ContestRanking(ContestId);
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
break;
}
case EDTFluxRequestType::StageRanking:
{
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
break;
}
case EDTFluxRequestType::SplitRanking:
{
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
break;
}
default:
JSONString = "";
break;
}
return JSONString;
}
UDTFluxQueuedManager::UDTFluxQueuedManager()
: bIsInitialized(false)
, CheckInterval(0.5f)
, TimeSinceLastCheck(0.0f)
{
}
UDTFluxQueuedManager::~UDTFluxQueuedManager()
{
ClearAllRequests();
}
void UDTFluxQueuedManager::Initialize()
{
if (!bIsInitialized)
{
UE_LOG(logDTFluxNetwork, Log, TEXT("Initializing DTFluxQueuedManager"));
bIsInitialized = true;
}
}
FGuid UDTFluxQueuedManager::QueueRequest(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
const FString& RawMessage)
{
// Créer la requête avec les structs existants
FDTFluxQueuedRequest NewRequest(RequestType, ContestId, StageId, SplitId);
NewRequest.RawResponse = RawMessage;
// Ajouter à la queue des requêtes en attente
PendingRequestsQueue.Enqueue(NewRequest);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Queued request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
*NewRequest.RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
return NewRequest.RequestId;
}
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FGuid& TargetRequestGuid)
{
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
bool bFoundMatch = false;
// Parcourir toutes les requêtes en attente
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{
if (!bFoundMatch && Request.RequestId == TargetRequestGuid)
{
// Marquer comme ayant reçu une réponse
Request.bHasReceivedResponse = true;
bFoundMatch = true;
// Ajouter à la queue des requêtes terminées
CompletedRequestsQueue.Enqueue(Request);
UE_LOG(logDTFluxNetwork, Verbose,
TEXT("Marked request %s as responded: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
Request.SplitId);
}
else
{
// Remettre dans la queue temporaire
TempQueue.Enqueue(Request);
}
}
// Remettre les requêtes non traitées dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return bFoundMatch;
}
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest)
{
return MarkRequestAsResponded(TargetRequest.RequestId);
}
bool UDTFluxQueuedManager::IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
int32 SplitId)
{
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
bool bFoundMatch = false;
// Parcourir toutes les requêtes en attente
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{
// Vérifier si cette requête correspond
if (!bFoundMatch && Request.Matches(RequestType, ContestId, StageId, SplitId))
{
bFoundMatch = true;
}
// Remettre dans la queue temporaire
TempQueue.Enqueue(Request);
}
// Remettre toutes les requêtes dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return bFoundMatch;
}
FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId,
int32 StageId, int32 SplitId)
{
auto SearchInQueue = [&RequestType, ContestId, StageId, SplitId](
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
{
// Copie temporaire de la queue pour la recherche
TQueue<FDTFluxQueuedRequest> TempQueue;
FDTFluxQueuedRequest* FoundItem = nullptr;
FDTFluxQueuedRequest Item;
while (Queue.Dequeue(Item))
{
if (Item.RequestType == RequestType && Item.ContestId == ContestId && Item.StageId == StageId && Item.
SplitId == SplitId) // Assuming RequestId is your GUID field
{
FoundItem = &Item;
}
// Remettre dans la queue temporaire
TempQueue.Enqueue(Item);
}
while (TempQueue.Dequeue(Item))
{
Queue.Enqueue(Item);
}
return FoundItem;
};
return SearchInQueue(PendingRequestsQueue);
}
const FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequest(const FGuid& SearchedGuid)
{
auto SearchInQueue = [&SearchedGuid](TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
{
// Copie temporaire de la queue pour la recherche
TQueue<FDTFluxQueuedRequest> TempQueue;
FDTFluxQueuedRequest* FoundItem = nullptr;
FDTFluxQueuedRequest Item;
while (Queue.Dequeue(Item))
{
if (Item.RequestId == SearchedGuid) // Assuming RequestId is your GUID field
{
// Trouver l'élément dans la queue originale
// On doit refaire une copie car on ne peut pas retourner l'adresse de 'Item'
FoundItem = &Item;
}
// Remettre dans la queue temporaire
TempQueue.Enqueue(Item);
}
while (TempQueue.Dequeue(Item))
{
Queue.Enqueue(Item);
}
return FoundItem;
};
// Chercher dans chaque queue
if (FDTFluxQueuedRequest* Found = SearchInQueue(PendingRequestsQueue))
return Found;
if (const FDTFluxQueuedRequest* Found = SearchInQueue(CompletedRequestsQueue))
return Found;
if (const FDTFluxQueuedRequest* Found = SearchInQueue(TimedOutRequestsQueue))
return Found;
return nullptr;
}
int32 UDTFluxQueuedManager::GetPendingRequestCount()
{
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
int32 Count = 0;
// Compter les requêtes en attente
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{
Count++;
TempQueue.Enqueue(Request);
}
// Remettre toutes les requêtes dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return Count;
}
int32 UDTFluxQueuedManager::CleanupTimedOutRequests()
{
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
int32 TimeoutCount = 0;
// Parcourir toutes les requêtes en attente
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{
if (Request.HasTimedOut())
{
// Ajouter à la queue des requêtes expirées
TimedOutRequestsQueue.Enqueue(Request);
TimeoutCount++;
UE_LOG(logDTFluxNetwork, Warning,
TEXT("Request %s timed out: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
Request.SplitId);
}
else
{
// Remettre dans la queue temporaire
TempQueue.Enqueue(Request);
}
}
// Remettre les requêtes non expirées dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return TimeoutCount;
}
int32 UDTFluxQueuedManager::CleanCashedRequests()
{
int32 CleanedRequestsCount = 0;
// Queue temporaire pour stocker les requêtes encore valides
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> ValidCompletedRequests;
// Traiter toutes les requêtes terminées
FDTFluxQueuedRequest CompletedRequest;
while (CompletedRequestsQueue.Dequeue(CompletedRequest))
{
// Vérifier si la requête est cacheable et a reçu une réponse
if (CompletedRequest.bIsCacheable && CompletedRequest.bHasReceivedResponse)
{
// Calculer l'âge de la requête en secondes
float RequestAge = (FDateTime::Now() - CompletedRequest.CreatedAt).GetTotalSeconds();
// Vérifier si le cache est encore valide
if (RequestAge <= CompletedRequest.CachedValidity)
{
// Le cache est encore valide, conserver la requête
ValidCompletedRequests.Enqueue(CompletedRequest);
}
else
{
// Le cache a expiré, compter cette requête comme nettoyée
CleanedRequestsCount++;
UE_LOG(LogTemp, Verbose,
TEXT("DTFluxQueuedManager: Cleaned expired cached request %s (Age: %.2fs, Validity: %.2fs)"),
*CompletedRequest.RequestId.ToString(), RequestAge, CompletedRequest.CachedValidity);
}
}
else
{
// Requête non cacheable ou sans réponse, la conserver
ValidCompletedRequests.Enqueue(CompletedRequest);
}
}
// Restaurer la queue avec uniquement les requêtes valides
while (ValidCompletedRequests.Dequeue(CompletedRequest))
{
CompletedRequestsQueue.Enqueue(CompletedRequest);
}
// Log du résultat si des requêtes ont été nettoyées
if (CleanedRequestsCount > 0)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxQueuedManager: Cleaned %d expired cached requests"), CleanedRequestsCount);
}
return CleanedRequestsCount;
}
void UDTFluxQueuedManager::ClearAllRequests()
{
// Vider toutes les queues
FDTFluxQueuedRequest DummyRequest;
while (PendingRequestsQueue.Dequeue(DummyRequest))
{
}
while (CompletedRequestsQueue.Dequeue(DummyRequest))
{
}
while (TimedOutRequestsQueue.Dequeue(DummyRequest))
{
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all pending requests"));
}
void UDTFluxQueuedManager::Tick(float DeltaTime)
{
if (!bIsInitialized)
{
return;
}
// Incrémenter le temps écoulé
TimeSinceLastCheck += DeltaTime;
// Vérifier si c'est le moment de nettoyer les requêtes expirées
if (TimeSinceLastCheck >= CheckInterval)
{
TimeSinceLastCheck = 0.0f;
CleanupTimedOutRequests();
}
// Traiter les requêtes expirées
FDTFluxQueuedRequest TimedOutRequest;
while (TimedOutRequestsQueue.Dequeue(TimedOutRequest))
{
// Déclencher l'événement pour chaque requête expirée
OnRequestTimedOut.Broadcast(TimedOutRequest);
}
}
bool UDTFluxQueuedManager::IsTickable() const
{
return bIsInitialized;
}
TStatId UDTFluxQueuedManager::GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxQueuedManager, STATGROUP_Tickables);
}

View File

@ -0,0 +1,530 @@
#pragma once
#include "Struct/DTFluxServerResponseStruct.h"
// === IMPLÉMENTATION DES CONSTRUCTEURS ===
FDTFluxServerResponse::FDTFluxServerResponse()
{
ReceivedAt = FDateTime::Now();
ApiDataType = EDTFluxApiDataType::None;
ParsingStatus = EDTFluxResponseStatus::Unset;
}
FDTFluxServerResponse::FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus,
bool bLogErrors)
{
ReceivedAt = FDateTime::Now();
RawMessage = JsonMessage;
ParsingStatus = InitializeFromJson(JsonMessage, bLogErrors);
OutStatus = ParsingStatus;
if (bLogErrors && ParsingStatus != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to create DTFluxServerResponse: %s"), *GetErrorMessage());
}
}
FDTFluxServerResponse FDTFluxServerResponse::CreateFromJson(const FString& JsonMessage, bool bLogErrors)
{
EDTFluxResponseStatus Status;
FDTFluxServerResponse Response(JsonMessage, Status, bLogErrors);
if (bLogErrors)
{
if (Status == EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Successfully created DTFluxServerResponse: %s"),
*Response.ToDebugString());
}
else
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Created DTFluxServerResponse with issues: %s"),
*Response.GetErrorMessage());
}
}
return Response;
}
EDTFluxResponseStatus FDTFluxServerResponse::TryParse(bool bLogErrors)
{
// Vérifier que le type est présent
if (Type.IsEmpty())
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response missing 'type' field"));
ApiDataType = EDTFluxApiDataType::None;
ParsingStatus = EDTFluxResponseStatus::MissingData;
return ParsingStatus;
}
}
ParsingStatus = EDTFluxResponseStatus::UnknownError;
// Validation supplémentaire selon le type
if (ContainsDataType("race-data"))
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Parsing race-data response"));
}
ApiDataType = EDTFluxApiDataType::RaceData;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
if (ContainsDataType("team-list"))
{
ApiDataType = EDTFluxApiDataType::TeamList;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
if (ContainsDataType("contest-ranking"))
{
if (ContestID == -1)
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-ranking missing ContestID"));
}
ParsingStatus = EDTFluxResponseStatus::DataError;
return ParsingStatus;
}
ApiDataType = EDTFluxApiDataType::ContestRanking;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
else if (ContainsDataType("stage-ranking"))
{
if (ContestID == -1 || StageID == -1)
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-ranking missing ContestID or StageID"));
}
ParsingStatus = EDTFluxResponseStatus::DataError;
return ParsingStatus;
}
if (SplitID != -1)
{
ApiDataType = EDTFluxApiDataType::SplitRanking;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
ApiDataType = EDTFluxApiDataType::StageRanking;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
if (ContainsDataType("status-update"))
{
ApiDataType = EDTFluxApiDataType::StatusUpdate;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
if (ContainsDataType("split-sensor"))
{
ApiDataType = EDTFluxApiDataType::SplitSensor;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
if (ContainsDataType("team-update"))
{
ApiDataType = EDTFluxApiDataType::TeamUpdate;
ParsingStatus = EDTFluxResponseStatus::Success;
return ParsingStatus;
}
return EDTFluxResponseStatus::UnknownError;
}
EDTFluxResponseStatus FDTFluxServerResponse::InitializeFromJson(const FString& JsonMessage, bool bLogErrors)
{
// Parser le JSON de base
if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonMessage, this, 0, 0, false, &FailureReason))
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("JSON parse error: %s\nMessage: %s"), *FailureReason.ToString(),
*JsonMessage);
}
return EDTFluxResponseStatus::JsonParseError;
}
// Vérifier si c'est une erreur du serveur
if (Code != -1)
{
if (bLogErrors)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Server error response: Code=%d, Message=%s"), Code, *Message);
}
return EDTFluxResponseStatus::ServerError;
}
return TryParse();
}
FString FDTFluxServerResponse::GetErrorMessage() const
{
switch (ParsingStatus)
{
case EDTFluxResponseStatus::Success:
return TEXT("No error");
case EDTFluxResponseStatus::JsonParseError:
return FString::Printf(TEXT("JSON parsing failed: %s"), *FailureReason.ToString());
case EDTFluxResponseStatus::ServerError:
return FString::Printf(TEXT("Server error %d: %s"), Code, *Message);
case EDTFluxResponseStatus::InvalidType:
return FString::Printf(TEXT("Invalid or missing response type: '%s'"), *Type);
case EDTFluxResponseStatus::MissingData:
return FString::Printf(TEXT("Missing required data fields for type '%s'"), *Type);
case EDTFluxResponseStatus::UnknownError:
default:
return TEXT("Unknown error occurred during parsing");
}
}
FString FDTFluxServerResponse::ToDebugString() const
{
return FString::Printf(
TEXT("DTFluxServerResponse[Status=%s, Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
*UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
}
bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList)
{
ParsingStatus = EDTFluxResponseStatus::Unset;
if (!ValidateResponseType(TEXT("team-list")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a team-list type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
TSharedPtr<FJsonObject> JsonObject;
if (!ParseJsonObject(JsonObject))
{
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
const TArray<TSharedPtr<FJsonValue>>* DataArray;
if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("No 'datas' array found in team-list response"));
ParsingStatus = EDTFluxResponseStatus::MissingData;
return false;
}
OutTeamList.Participants.Empty();
for (const TSharedPtr<FJsonValue>& Value : *DataArray)
{
if (Value->Type == EJson::Object)
{
const TSharedPtr<FJsonObject> Item = Value->AsObject();
FDTFluxParticipant Participant;
if (UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant))
{
OutTeamList.Participants.Add(Participant);
ParsingStatus = EDTFluxResponseStatus::Success;
}
else
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Failed to parse participant from JSON"));
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
}
}
}
ParsingStatus = GetParsingStatus();
if (ParsingStatus == EDTFluxResponseStatus::Success && OutTeamList.Participants.Num() != 0)
{
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d participants from team-list"),
OutTeamList.Participants.Num());
return true;
}
if (OutTeamList.Participants.Num() == 0)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("No Participant Added"));
}
if (ParsingStatus != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse team-list Error : %s"), *GetErrorMessage());
}
return false;
}
bool FDTFluxServerResponse::ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate)
{
return ParseTeamListResponse(OutTeamUpdate);
}
bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData)
{
ParsingStatus = EDTFluxResponseStatus::Unset;
if (!ValidateResponseType(TEXT("race-data")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a race-data type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
FDTFluxRaceDataResponse RaceDataResponse;
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(RawMessage, &RaceDataResponse))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse race-data JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
OutRaceData.Datas.Empty();
for (const auto& Contest : RaceDataResponse.Datas)
{
FDTFluxContest NewContest;
NewContest.Name = Contest.Name;
NewContest.ContestId = Contest.Id;
NewContest.Date = Contest.Date;
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Processing Contest %d [%s] Starting at %s"),
Contest.Id, *Contest.Name, *Contest.Date.ToString());
// Satges
for (const auto& Stage : Contest.Stages)
{
FDTFluxStage NewStage;
NewStage.StageId = Stage.Id;
NewStage.Name = Stage.Name;
// Construct full Timestamps strings
FString StartTimeFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.StartTime);
FString EndTimeFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.EndTime);
FString CutOffFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.CutOff);
FDateTime::Parse(StartTimeFString, NewStage.StartTime);
FDateTime::Parse(EndTimeFString, NewStage.EndTime);
FDateTime::Parse(CutOffFString, NewStage.CutOff);
NewContest.Stages.Add(NewStage);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Stage %d [%s]: Start[%s], CutOff[%s], End[%s]"),
Stage.Id, *Stage.Name, *NewStage.StartTime.ToString(),
*NewStage.CutOff.ToString(), *NewStage.EndTime.ToString());
}
// Traiter les splits
for (const auto& Split : Contest.Splits)
{
FDTFluxSplit NewSplit;
NewSplit.SplitId = Split.Id;
NewSplit.Name = Split.Name;
NewContest.Splits.Add(NewSplit);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split %d [%s]"), Split.Id, *Split.Name);
}
// Update Contest metadata
NewContest.UpdateEndTime();
NewContest.UpdateLastStageId();
OutRaceData.Datas.Add(NewContest);
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d contests from race-data"),
OutRaceData.Datas.Num());
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutContestRankings)
{
if (!ValidateResponseType(TEXT("contest-ranking")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a contest-ranking type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
FDTFluxContestRankingResponse ContestRankingResponse;
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(
RawMessage, &ContestRankingResponse))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse contest-ranking JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
OutContestRankings.ContestId = ContestRankingResponse.ContestID;
OutContestRankings.Rankings.Empty();
for (const auto& RankingItem : ContestRankingResponse.Datas)
{
FDTFluxContestRanking Ranking = RankingItem;
OutContestRankings.Rankings.Add(Ranking);
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"),
OutContestRankings.ContestId, OutContestRankings.Rankings.Num());
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings)
{
if (!ValidateResponseType(TEXT("stage-ranking")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a stage-ranking type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
FDTFluxStageRankingResponse RankingResponse;
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxStageRankingResponse>(RawMessage, &RankingResponse))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse stage-ranking JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
OutStageRankings.ContestId = ContestID;
OutStageRankings.StageId = StageID;
OutStageRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
OutStageRankings.Initialize();
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed stage ranking for Contest %d, Stage %d with %d entries"),
OutStageRankings.ContestId, OutStageRankings.StageId, OutStageRankings.Rankings.Num());
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings)
{
if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1)
{
UE_LOG(logDTFluxNetwork, Error,
TEXT("Response is not a split-ranking type (stage-ranking with SplitID != -1)"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
FDTFluxSplitRankingResponse SplitRankingResponse;
if (!FJsonObjectConverter::JsonObjectStringToUStruct<
FDTFluxSplitRankingResponse>(RawMessage, &SplitRankingResponse))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-ranking JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
OutSplitRankings.ContestId = ContestID;
OutSplitRankings.StageId = StageID;
OutSplitRankings.SplitId = SplitID;
OutSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
UE_LOG(logDTFluxNetwork, Log,
TEXT("Successfully parsed split ranking for Contest %d, Stage %d, Split %d with %d entries"),
OutSplitRankings.ContestId, OutSplitRankings.StageId, OutSplitRankings.SplitId,
OutSplitRankings.Rankings.Num());
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate)
{
if (!ValidateResponseType(TEXT("status-update")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a status-update type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(RawMessage, &OutStatusUpdate))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status-update JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed status update for bib %d"), OutStatusUpdate.Bib);
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos)
{
if (!ValidateResponseType(TEXT("split-sensor")))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-sensor type"));
ParsingStatus = EDTFluxResponseStatus::InvalidType;
return false;
}
FDTFluxSplitSensorResponse SplitSensorResponse;
if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &SplitSensorResponse))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-sensor JSON: %s"), *RawMessage);
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
return false;
}
OutSplitSensorInfos.Empty();
for (const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas)
{
FDTFluxSplitSensorInfo NewSplitSensorInfo;
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib;
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID;
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
OutSplitSensorInfos.Add(NewSplitSensorInfo);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"),
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
NewSplitSensorInfo.SplitId);
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num());
ParsingStatus = EDTFluxResponseStatus::Success;
return true;
}
void FDTFluxServerResponse::ShowDebug(const bool bShouldPrintRawMessage) const
{
FString DebugMsg = FString::Printf(
TEXT("DTFluxServerResponse[Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
*Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
if (bShouldPrintRawMessage)
{
UE_LOG(logDTFluxNetwork, Log, TEXT("%s\nRawMessage: \"%s\""), *DebugMsg, *RawMessage);
}
}
// === MÉTHODES PRIVÉES ===
bool FDTFluxServerResponse::ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const
{
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawMessage);
if (!FJsonSerializer::Deserialize(Reader, OutJsonObject) || !OutJsonObject.IsValid())
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse JSON: %s"), *RawMessage);
return false;
}
return true;
}
bool FDTFluxServerResponse::ValidateResponseType(const FString& ExpectedType) const
{
if (!IsValidResponse())
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Invalid server response: Code=%d, Message=%s"), Code, *Message);
return false;
}
if (!ContainsDataType(ExpectedType))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Expected type '%s' but got '%s'"), *ExpectedType, *Type);
return false;
}
return true;
}

View File

@ -5,6 +5,8 @@
#include "DTFluxCoreModule.h"
#include "DTFluxNetworkModule.h"
#include "DTFluxNetworkSettings.h"
#include "DTFluxQueuedManager.h"
#include "DTFluxQueuedManager.h"
#include "JsonObjectConverter.h"
#include "Clients/DTFluxHttpClient.h"
#include "Clients/DTFluxWebSocketClient.h"
@ -19,10 +21,11 @@
#include "Types/Struct/DTFluxSplitSensor.h"
// === CONNEXION WEBSOCKET ===
void UDTFluxNetworkSubsystem::Connect()
{
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
WsClient->Connect();
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
WsClient->Connect();
}
void UDTFluxNetworkSubsystem::Disconnect()
@ -35,6 +38,141 @@ void UDTFluxNetworkSubsystem::Reconnect()
ReconnectWs(FName("Ws_Client_0"));
}
// === REQUÊTES AVEC TRACKING ===
FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId,
float TimeoutSeconds)
{
if (!QueueManager)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("QueueManager is not initialized"));
return FGuid();
}
// Vérifier si une requête similaire est déjà en cours (optionnel)
if (IsRequestPending(RequestType, ContestId, StageId, SplitId))
{
UE_LOG(logDTFluxNetwork, Warning,
TEXT("Similar request already pending: Type=%d, Contest=%d, Stage=%d, Split=%d"),
(int32)RequestType, ContestId, StageId, SplitId);
}
// Créer et enqueue la requête
FGuid RequestId = QueueManager->QueueRequest(RequestType, ContestId, StageId, SplitId);
// Envoyer immédiatement si possible (le QueueManager gère la queue)
if (const FDTFluxQueuedRequest* QueuedRequest = QueueManager->GetRequest(RequestId))
{
SendQueuedRequest(*QueuedRequest);
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Queued tracked request %s: Type=%d, Contest=%d, Stage=%d, Split=%d"),
*RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
return RequestId;
}
FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallback(
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId,
FOnDTFluxRequestResponse OnCompleted,
FOnDTFluxRequestTimeout OnTimeout,
float TimeoutSeconds)
{
FGuid RequestId = SendTrackedRequest(RequestType, ContestId, StageId, SplitId, TimeoutSeconds);
if (RequestId.IsValid())
{
// Stocker les callbacks pour cette requête
if (OnCompleted.IsBound())
{
PendingCallbacks.Add(RequestId, OnCompleted);
}
if (OnTimeout.IsBound())
{
PendingTimeoutCallbacks.Add(RequestId, OnTimeout);
}
}
return RequestId;
}
bool UDTFluxNetworkSubsystem::GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const
{
if (!QueueManager)
{
return false;
}
const FDTFluxQueuedRequest* Request = QueueManager->GetRequest(RequestId);
if (Request)
{
OutRequest = *Request;
return true;
}
return false;
}
const FDTFluxQueuedRequest* UDTFluxNetworkSubsystem::GetTrackedRequestPtr(const FGuid& RequestId) const
{
if (!QueueManager)
{
return nullptr;
}
return QueueManager->GetRequest(RequestId);
}
bool UDTFluxNetworkSubsystem::HasRequestReceivedResponse(const FGuid& RequestId) const
{
FDTFluxQueuedRequest Request;
if (GetTrackedRequest(RequestId, Request))
{
return Request.bHasReceivedResponse;
}
return false;
}
FString UDTFluxNetworkSubsystem::GetRequestResponseData(const FGuid& RequestId) const
{
FDTFluxQueuedRequest Request;
if (GetTrackedRequest(RequestId, Request))
{
return Request.RawResponse;
}
return FString();
}
bool UDTFluxNetworkSubsystem::IsRequestPending(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId,
int32 SplitId) const
{
if (!QueueManager)
{
return false;
}
return QueueManager->IsRequestPending(RequestType, ContestId, StageId, SplitId);
}
int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const
{
if (!QueueManager)
{
return 0;
}
return QueueManager->GetPendingRequestCount();
}
UDTFluxQueuedManager* UDTFluxNetworkSubsystem::GetQueueManager() const
{
return QueueManager;
}
void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId,
int InSplitId)
{
@ -48,7 +186,8 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message);
break;
case EDTFluxRequestType::SplitRanking:
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message);
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId),
Message);
break;
case EDTFluxRequestType::TeamList:
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message);
@ -60,7 +199,7 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
return;
}
//Dirty trick to fix Case
Message = Message.Replace(TEXT("Id"),TEXT( "ID"), ESearchCase::CaseSensitive);
Message = Message.Replace(TEXT("Id"),TEXT("ID"), ESearchCase::CaseSensitive);
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Request %s"), *Message);
SendMessage(Message);
}
@ -70,11 +209,10 @@ void UDTFluxNetworkSubsystem::SendMessage(const FString& Message)
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message);
if(WsClient.IsValid() && WsClient->CanSend())
if (WsClient.IsValid() && WsClient->CanSend())
{
WsClient->Send(Message);
UE_LOG(logDTFluxNetwork, Log, TEXT("Can send request"));
}
else
{
@ -86,8 +224,7 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked<FDTFluxCoreModule>("DTFluxCore");
FString StatusString = UEnum::GetValueAsString(WsStatus);
UE_LOG(logDTFluxNetwork, Log, TEXT("Status is %s"), *StatusString);
UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault<UDTFluxNetworkSettings>();
UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings);
UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings);
@ -99,16 +236,36 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged"));
NetworkSettings->OnDTFluxHttpSettingsChanged.AddUFunction(this, FName("HttpSettingsChanged"));
#endif
if(WsSettings.bShouldConnectAtStartup)
if (WsSettings.bShouldConnectAtStartup)
{
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
WsClient->Connect();
}
// Initialisation du Queue Manager
QueueManager = NewObject<UDTFluxQueuedManager>(this);
QueueManager->Initialize();
// Connexion au delegate de timeout du Queue Manager
QueueManager->OnRequestTimedOut.AddDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
}
void UDTFluxNetworkSubsystem::Deinitialize()
{
Super::Deinitialize();
// Nettoyer le Queue Manager
if (QueueManager)
{
QueueManager->OnRequestTimedOut.RemoveDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
QueueManager->ClearAllRequests();
}
// Nettoyer les callbacks
PendingCallbacks.Empty();
PendingTimeoutCallbacks.Empty();
// Déconnexion des clients
UnregisterWebSocketEvents();
UnregisterHttpEvents();
}
void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings)
@ -117,7 +274,7 @@ void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSe
bool bNeedsReload = WsSettings != NewWsSettings;
WsSettings = NewWsSettings;
if( bNeedsReload || WsSettings.bShouldConnectAtStartup)
if (bNeedsReload || WsSettings.bShouldConnectAtStartup)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client"))
ReconnectWs(FName("Ws_Client_0"));
@ -137,9 +294,9 @@ void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
WsClient->SetAddress(NewAddress);
WsClient->Reconnect();
}
void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId)
{
}
void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
@ -148,16 +305,16 @@ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem);
OnWsConnectionErrorEventDelegateHandle =
WsClient->RegisterConnectionError()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
OnWsClosedEventDelegateHandle =
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
OnWsClosedEventDelegateHandle =
WsClient->RegisterClosedEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
OnWsMessageEventDelegateHandle =
WsClient->RegisterMessageEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
OnWsMessageSentEventDelegateHandle =
WsClient->RegisterMessageSentEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
WsClient->RegisterMessageSentEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
}
void UDTFluxNetworkSubsystem::RegisterHttpEvents()
@ -166,25 +323,25 @@ void UDTFluxNetworkSubsystem::RegisterHttpEvents()
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
{
if(OnWsConnectedEventDelegateHandle.IsValid())
if (OnWsConnectedEventDelegateHandle.IsValid())
{
WsClient->UnregisterConnectedEvent().Remove(OnWsConnectedEventDelegateHandle);
}
if(OnWsConnectionErrorEventDelegateHandle.IsValid())
if (OnWsConnectionErrorEventDelegateHandle.IsValid())
{
WsClient->UnregisterConnectionError();
WsClient->UnregisterConnectionError().Remove(OnWsConnectionErrorEventDelegateHandle);
}
if(OnWsClosedEventDelegateHandle.IsValid())
if (OnWsClosedEventDelegateHandle.IsValid())
{
WsClient->UnregisterClosedEvent();
WsClient->UnregisterClosedEvent().Remove(OnWsClosedEventDelegateHandle);
}
if(OnWsMessageEventDelegateHandle.IsValid())
if (OnWsMessageEventDelegateHandle.IsValid())
{
WsClient->UnregisterMessageEvent();
WsClient->UnregisterMessageEvent().Remove(OnWsMessageEventDelegateHandle);
}
if(OnWsMessageSentEventDelegateHandle.IsValid())
if (OnWsMessageSentEventDelegateHandle.IsValid())
{
WsClient->UnregisterRawMessageEvent();
WsClient->UnregisterRawMessageEvent().Remove(OnWsMessageSentEventDelegateHandle);
}
}
@ -203,7 +360,7 @@ void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error);
WsStatus = EDTFluxConnectionStatus::Error;
if(WsSettings.bShouldAutoReconnectOnError)
if (WsSettings.bShouldAutoReconnectOnError)
{
WsClient->Reconnect();
}
@ -212,261 +369,239 @@ void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString
void UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s :\n Reason : %s \tStatusCode : %i, bWasClean : %s"),
*WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False"));
*WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False"));
WsStatus = EDTFluxConnectionStatus::Closed;
}
void UDTFluxNetworkSubsystem::ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse)
void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response)
{
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ServerResponse.RawMessage);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
UE_LOG(logDTFluxNetwork, Error, TEXT("JSON invalide : %s"), *ServerResponse.RawMessage);
return;
}
const TArray<TSharedPtr<FJsonValue>>* DataArray;
if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray))
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Aucun champ 'datas' trouvé dans le team-list"));
return;
}
FDTFluxTeamListDefinition TeamListDefinition;
for (const TSharedPtr<FJsonValue>& Value : *DataArray)
Response.ParseTeamListResponse(TeamListDefinition);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Parsing Team List Response"));
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
if (Value->Type == EJson::Object)
{
const TSharedPtr<FJsonObject> Item = Value->AsObject();
FDTFluxParticipant Participant;
UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant);
TeamListDefinition.Participants.Add(Participant);
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseTeamListResponse() for JSON Response : %s"), *Response.RawMessage);
return;
}
UE_LOG(logDTFluxNetwork, Warning, TEXT("PArsing OK. Sending to Core..."));
const bool bIsSuccessfullyBounded = OnTeamListReceived.ExecuteIfBound(TeamListDefinition);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Inserting %i Participants [%s]"), TeamListDefinition.Participants.Num(),
OnTeamListReceived.ExecuteIfBound(TeamListDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseRaceData(const FDTFluxServerResponse& Response)
void UDTFluxNetworkSubsystem::ParseRaceData(FDTFluxServerResponse& Response)
{
FDTFluxRaceDataResponse RaceData;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(Response.RawMessage, &RaceData))
FDTFluxRaceData RaceData;
Response.ParseRaceData(RaceData);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
//convert
FDTFluxRaceData RaceDataDefinition;
for(auto Contest : RaceData.Datas)
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
return;
}
const bool bIsSuccessfullyBounded = OnRaceDataReceived.ExecuteIfBound(RaceData);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceData.Datas.Num(),
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseContestRanking(FDTFluxServerResponse& Response)
{
FDTFluxContestRankings ContestRankings;
Response.ParseContestRanking(ContestRankings);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage);
return;
}
const bool bIsSuccessfullyBounded = OnContestRankingReceived.ExecuteIfBound(ContestRankings);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"),
ContestRankings.ContestId,
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseStageRankingResponse(FDTFluxServerResponse& Response)
{
FDTFluxStageRankings StageRankings;
Response.ParseStageRankingResponse(StageRankings);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"),
*Response.RawMessage);
}
const bool bIsSuccessfullyBounded = OnStageRankingReceived.ExecuteIfBound(StageRankings);
UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"),
StageRankings.ContestId, StageRankings.StageId,
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(FDTFluxServerResponse& Response)
{
FDTFluxSplitRankings SplitRankings;
Response.ParseSplitRankingResponse(SplitRankings);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"),
*Response.RawMessage);
}
const bool bIsSuccessfullyBounded = OnSplitRankingReceived.ExecuteIfBound(SplitRankings);
UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i, Split %i\n[Result] : %s"),
SplitRankings.ContestId, SplitRankings.StageId, SplitRankings.SplitId,
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(FDTFluxServerResponse& Response)
{
FDTFluxTeamStatusUpdate StatusUpdate;
Response.ParseStatusUpdateResponse(StatusUpdate);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"),
*Response.RawMessage);
}
const bool bIsSuccessfullyBounded = OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
UE_LOG(logDTFluxNetwork, Warning, TEXT("StatusUpdate Data Sent for Bib %i with new status %s\n[Result] : %s"),
StatusUpdate.Bib, *UEnum::GetValueAsString(StatusUpdate.Status),
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(FDTFluxServerResponse& Response)
{
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos = TArray<FDTFluxSplitSensorInfo>();
Response.ParseSplitSensorResponse(SplitSensorInfos);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() for JSON Response : %s"),
*Response.RawMessage);
}
for (auto& SplitSensorInfo : SplitSensorInfos)
{
const bool bIsSuccessfullyBounded = OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
UE_LOG(logDTFluxNetwork, Warning,
TEXT("SplitSensor Data Sent for Bib %i on [Split %i] of [Stage %i] in [Contest %i]\n[Result] : %s"),
SplitSensorInfo.Bib, SplitSensorInfo.SplitId, SplitSensorInfo.StageId, SplitSensorInfo.ContestId,
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
}
}
EDTFluxResponseStatus UDTFluxNetworkSubsystem::ProcessPushMessage(FDTFluxServerResponse& Response)
{
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::UnknownError;
if (DTFluxDataTypeUtils::IsPushOnly(Response.GetResponseType()))
{
switch (Response.GetResponseType())
{
FDTFluxContest NewContest;
NewContest.Name = Contest.Name;
NewContest.ContestId = Contest.Id;
NewContest.Date = Contest.Date;
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s] Starting at %s \nStages: \n"), Contest.Id, *Contest.Date.ToString(),*Contest.Name);
for(auto Stage : Contest.Stages)
case EDTFluxApiDataType::SplitSensor:
{
FDTFluxStage NewStage;
NewStage.StageId = Stage.Id;
NewStage.Name = Stage.Name;
FString StartTimeFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.StartTime
);
FString EndTimeFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.EndTime
);
FString CutOffFString = FString::Printf(TEXT("%s %s"),
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
*Stage.CutOff
);
FDateTime::Parse(StartTimeFString, NewStage.StartTime);
FDateTime::Parse(EndTimeFString, NewStage.EndTime);
FDateTime::Parse(CutOffFString, NewStage.CutOff);
NewContest.Stages.Add(NewStage);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage %i [%s]: \nSTartTime Received [%s] -> Datetime[%s], CutOff [%s], EndTime [%s] \n"), Stage.Id, *Stage.Name,
*Stage.StartTime, *NewStage.StartTime.ToString(), *NewStage.CutOff.ToString(), *NewStage.EndTime.ToString());
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
if (Response.ParseSplitSensorResponse(SplitSensorInfos))
{
for (const auto& SplitSensorInfo : SplitSensorInfos)
{
OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
}
}
ResponseStatus = Response.GetParsingStatus();
break;
}
NewContest.UpdateEndTime();
NewContest.UpdateLastStageId();
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s]\nSplits: \n"), Contest.Id, *Contest.Name);
for(auto Split: Contest.Splits)
case EDTFluxApiDataType::StatusUpdate:
{
FDTFluxSplit NewSplit;
NewSplit.SplitId = Split.Id;
NewSplit.Name = Split.Name;
NewContest.Splits.Add(NewSplit);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Split %i [%s]: \n"), Split.Id, *Split.Name);
FDTFluxTeamStatusUpdate StatusUpdate;
if (Response.ParseStatusUpdateResponse(StatusUpdate))
{
OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
}
ResponseStatus = Response.GetParsingStatus();
break;
}
case EDTFluxApiDataType::TeamUpdate:
{
FDTFluxTeamListDefinition TeamUpdateList;
if (Response.ParseTeamUpdateResponse(TeamUpdateList))
{
OnTeamUpdateReceived.ExecuteIfBound(TeamUpdateList);
}
ResponseStatus = Response.GetParsingStatus();
break;
}
default:
{
ResponseStatus = EDTFluxResponseStatus::UnknownError;
break;
}
RaceDataDefinition.Datas.Add(NewContest);
}
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceDataDefinition.Datas.Num(),
OnRaceDataReceived.ExecuteIfBound(RaceDataDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
return;
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
return ResponseStatus;
}
void UDTFluxNetworkSubsystem::ParseContestRanking(const FDTFluxServerResponse& Response)
void UDTFluxNetworkSubsystem::Parse(FDTFluxServerResponse& Response)
{
FDTFluxContestRankingResponse ContestRankingResponse;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(Response.RawMessage, &ContestRankingResponse))
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::Success;
switch (Response.GetResponseType())
{
FDTFluxContestRankings ContestRankings;
ContestRankings.ContestId = ContestRankingResponse.ContestID;
for(auto& RankingItem : ContestRankingResponse.Datas)
case EDTFluxApiDataType::RaceData:
{
FDTFluxContestRanking Temp = RankingItem;
ContestRankings.Rankings.Add(Temp);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing RaceData"));
ParseRaceData(Response);
ResponseStatus = Response.GetParsingStatus();
break;
}
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"), ContestRankings.ContestId,
OnContestRankingReceived.ExecuteIfBound(ContestRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
return;
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage);
}
void UDTFluxNetworkSubsystem::ParseStageRankingResponse(const FDTFluxServerResponse& Response)
{
FDTFluxStageRankingResponse RankingResponse;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxStageRankingResponse>(Response.RawMessage, &RankingResponse))
{
FDTFluxStageRankings NewRankings;
NewRankings.ContestId = Response.ContestID;
NewRankings.StageId = Response.StageID;
NewRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
NewRankings.Initialize();
UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"),
NewRankings.ContestId, NewRankings.StageId,
OnStageRankingReceived.ExecuteIfBound(NewRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")
);
return;
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"), *Response.RawMessage);
}
void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(const FDTFluxServerResponse& Response)
{
FDTFluxSplitRankingResponse SplitRankingResponse;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxSplitRankingResponse>(Response.RawMessage, &SplitRankingResponse))
{
FDTFluxSplitRankings NewSplitRankings;
NewSplitRankings.ContestId = Response.ContestID;
NewSplitRankings.StageId = Response.StageID;
NewSplitRankings.SplitId = Response.SplitID;
NewSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i and Split %i\n[Result] : %s"),
NewSplitRankings.ContestId, NewSplitRankings.StageId, NewSplitRankings.SplitId,
OnSplitRankingReceived.ExecuteIfBound(NewSplitRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
return;
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"), *Response.RawMessage);
}
void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(const FDTFluxServerResponse& Response)
{
FDTFluxTeamStatusUpdate StatusUpdateResponse;
if (FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(Response.RawMessage, &StatusUpdateResponse))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i \n[Result] : %s\n"),
StatusUpdateResponse.Bib,
OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdateResponse) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
return;
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"), *Response.RawMessage);
}
void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(const FDTFluxServerResponse& Response)
{
FDTFluxSplitSensorResponse SplitSensorResponse;
if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &SplitSensorResponse))
{
for(const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas)
case EDTFluxApiDataType::TeamList:
{
FDTFluxSplitSensorInfo NewSplitSensorInfo;
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib;
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID;
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i in Contest %i, Stage %i in split %i\n[Result] : %s\n"),
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId, NewSplitSensorInfo.SplitId,
OnSplitSensorReceived.ExecuteIfBound(NewSplitSensorInfo) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing TeamList"));
ParseTeamListResponse(Response);
ResponseStatus = Response.GetParsingStatus();
break;
}
case EDTFluxApiDataType::ContestRanking:
{
ParseContestRanking(Response);
ResponseStatus = Response.GetParsingStatus();
break;
}
case EDTFluxApiDataType::StageRanking:
{
ParseStageRankingResponse(Response);
ResponseStatus = Response.GetParsingStatus();
break;
}
case EDTFluxApiDataType::SplitRanking:
{
ParseSplitRankingResponse(Response);
ResponseStatus = Response.GetParsingStatus();
break;
}
default:
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Legacy Parsing Unknown"));
ResponseStatus = EDTFluxResponseStatus::UnknownError;
break;
}
}
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() failed for JSON Response : %s"), *Response.RawMessage);
if (ResponseStatus != EDTFluxResponseStatus::Success)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("UDTFluxNetworkSubsystem::Parse() Parsing failed"));
}
}
//TODO reforge API to keep track of Requests
void UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Client %s :\nMessage Received : %s"), *WsClient->GetAddress(), *MessageString);
// UE_LOG(logDTFluxNetwork, Warning, TEXT("Client %s :\nMessage Received : %s"), *WsClient->GetAddress(), *MessageString);
//Do Something With the message
FDTFluxServerResponse Response;
Response.ReceivedAt = FDateTime::Now();
Response.RawMessage = MessageString;
Response.FailureReason = FText::FromString("--");
if(FJsonObjectConverter::JsonObjectStringToUStruct(MessageString, &Response, 0, 0, false, &(Response.FailureReason)))
EDTFluxResponseStatus ResponseStatus;
FDTFluxServerResponse Response(MessageString, ResponseStatus);
if (!TryMatchResponseToQueuedRequest(Response))
{
if(Response.Code == -1)
UE_LOG(logDTFluxNetwork, Warning, TEXT("Response %s does not match any queued request"),
*UEnum::GetValueAsString(Response.GetResponseType()));
if (ProcessPushMessage(Response) != EDTFluxResponseStatus::Success)
{
// return DataReceived.Broadcast(Response);
if(Response.Type.Contains("race-data"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Race-Data Received"));
return ParseRaceData(Response);
}
if(Response.Type.Contains("team-list"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Team-List Received"));
return ParseTeamListResponse(Response);
}
if(Response.Type.Contains("contest-ranking"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-Ranking Received"));
return ParseContestRanking(Response);
}
if(Response.Type.Contains("stage-ranking") )
{
if(Response.SplitID == -1)
{
// StageRanking
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-Ranking Data"));
ParseStageRankingResponse(Response);
}
else
{
// StageRanking
UE_LOG(logDTFluxNetwork, Warning, TEXT("Split-Ranking Data"));
return ParseSplitRankingResponse(Response);
}
}
if(Response.Type.Contains("split-sensor"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("split-sensor Data"));
ParseSplitSensorResponse(Response);
}
if(Response.Type.Contains("status-update"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("status-update Data"));
ParseStatusUpdateResponse(Response);
}
if(Response.Type.Contains("team-update"))
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("team-update Data"));
ParseTeamListResponse(Response);
}
UE_LOG(logDTFluxNetwork, Warning, TEXT("Not a push message"));
// Legacy
Parse(Response);
}
}
UE_LOG(logDTFluxNetwork, Error, TEXT("Ws %s :\nMessage Received : %s Cannot be Parsed"), *WsClient->GetAddress(), *MessageString);
// return DataReceived.Broadcast(Response);
}
void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent)
@ -474,15 +609,144 @@ void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FStrin
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent);
}
void UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest)
{
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s timed out: Type=%d, Contest=%d, Stage=%d, Split=%d"),
*TimedOutRequest.RequestId.ToString(),
(int32)TimedOutRequest.RequestType,
TimedOutRequest.ContestId,
TimedOutRequest.StageId,
TimedOutRequest.SplitId);
// Appeler le callback de timeout si présent
if (FOnDTFluxRequestTimeout* TimeoutCallback = PendingTimeoutCallbacks.Find(TimedOutRequest.RequestId))
{
if (TimeoutCallback->IsBound())
{
TimeoutCallback->Execute(TimedOutRequest.RequestId, TEXT("Request timeout"));
}
PendingTimeoutCallbacks.Remove(TimedOutRequest.RequestId);
}
// Nettoyer les callbacks de succès aussi
PendingCallbacks.Remove(TimedOutRequest.RequestId);
// Broadcaster l'événement Blueprint
OnTrackedRequestFailed.Broadcast(TimedOutRequest.RequestId, TimedOutRequest.RequestType, TEXT("Request timeout"));
}
bool UDTFluxNetworkSubsystem::TryMatchResponseToQueuedRequest(const FDTFluxServerResponse& Response)
{
if (!QueueManager)
{
return false;
}
// Essayer de trouver une requête correspondante
// Note: Cette méthode nécessiterait une modification de UDTFluxQueuedManager pour supporter le matching par type et paramètres
// Pour l'instant, on utilise une approche simple : chercher la première requête du bon type
// Vous devrez probablement modifier UDTFluxQueuedManager pour ajouter une méthode comme FindMatchingRequest()
// Implémentation temporaire : on assume qu'il n'y a qu'une requête de chaque type en cours
if (QueueManager->IsRequestPending(Response.GetResponseType(), Response.ContestID, Response.StageID,
Response.SplitID))
{
// Marquer comme répondu - vous devrez adapter cette méthode selon votre logique de matching
// Pour l'instant, on va faire un workaround simple
UE_LOG(logDTFluxNetwork, Log,
TEXT("Matched response to queued request: Type=%s, Contest=%d, Stage=%d, Split=%d"),
*UEnum::GetValueAsString(Response.GetResponseType()), Response.ContestID, Response.StageID,
Response.SplitID);
return true;
}
return false;
}
void UDTFluxNetworkSubsystem::CompleteTrackedRequest(const FGuid& RequestId, const FString& ResponseData,
EDTFluxRequestType RequestType)
{
// Marquer la requête comme ayant reçu une réponse
if (QueueManager)
{
QueueManager->MarkRequestAsResponded(RequestId);
}
// Appeler le callback de succès si présent
if (FOnDTFluxRequestResponse* SuccessCallback = PendingCallbacks.Find(RequestId))
{
if (SuccessCallback->IsBound())
{
SuccessCallback->Execute(RequestId, ResponseData);
}
PendingCallbacks.Remove(RequestId);
}
// Nettoyer le callback de timeout
PendingTimeoutCallbacks.Remove(RequestId);
// Broadcaster l'événement Blueprint
OnTrackedRequestCompleted.Broadcast(RequestId, RequestType, ResponseData);
UE_LOG(logDTFluxNetwork, Log, TEXT("Completed tracked request %s"), *RequestId.ToString());
}
void UDTFluxNetworkSubsystem::FailTrackedRequest(const FGuid& RequestId, const FString& ErrorMessage,
EDTFluxRequestType RequestType)
{
// Appeler le callback d'erreur si présent
if (FOnDTFluxRequestTimeout* ErrorCallback = PendingTimeoutCallbacks.Find(RequestId))
{
if (ErrorCallback->IsBound())
{
ErrorCallback->Execute(RequestId, ErrorMessage);
}
PendingTimeoutCallbacks.Remove(RequestId);
}
// Nettoyer les callbacks
PendingCallbacks.Remove(RequestId);
// Broadcaster l'événement Blueprint
OnTrackedRequestFailed.Broadcast(RequestId, RequestType, ErrorMessage);
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed tracked request %s: %s"), *RequestId.ToString(), *ErrorMessage);
}
void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest)
{
// Générer le message JSON à partir de la requête
FString Message = QueuedRequest.Serialize();
if (Message.IsEmpty())
{
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to serialize queued request %s"),
*QueuedRequest.RequestId.ToString());
FailTrackedRequest(QueuedRequest.RequestId, TEXT("Serialization failed"), QueuedRequest.RequestType);
return;
}
// Dirty trick to fix Case (comme dans l'original)
Message = Message.Replace(TEXT("Id"), TEXT("ID"), ESearchCase::CaseSensitive);
UE_LOG(logDTFluxNetwork, Log, TEXT("Sending queued request %s: %s"), *QueuedRequest.RequestId.ToString(), *Message);
// Envoyer via WebSocket
SendMessage(Message);
}
FString UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port)
{
FString NewAddress;
if( !Address.Contains("ws://") && !Address.Contains("wss://"))
if (!Address.Contains("ws://") && !Address.Contains("wss://"))
{
NewAddress += FString("ws://");
}
NewAddress +=Address + FString(":") + FString::FromInt(Port) + Path;
NewAddress += Address + FString(":") + FString::FromInt(Port) + Path;
return NewAddress;
// UE_LOG(logDTFluxNetwork, Log, TEXT("NewAddress : %s"), *NewAddress);
}