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:
@ -2,31 +2,31 @@
|
||||
|
||||
public class DTFluxNetwork : ModuleRules
|
||||
{
|
||||
public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"WebSockets",
|
||||
"HTTP",
|
||||
"DTFluxCore",
|
||||
"DTFluxProjectSettings",
|
||||
"JsonUtilities",
|
||||
"Json",
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"WebSockets",
|
||||
"HTTP",
|
||||
"DTFluxCore",
|
||||
"DTFluxProjectSettings",
|
||||
"JsonUtilities",
|
||||
"Json",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
401
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
401
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal 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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
|
||||
}
|
||||
|
||||
166
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
166
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
@ -0,0 +1,166 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "Containers/Queue.h"
|
||||
#include "Tickable.h"
|
||||
#include "Struct/DTFluxRequestStructs.h"
|
||||
#include "Struct/DTFluxServerResponseStruct.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "DTFluxQueuedManager.generated.h"
|
||||
|
||||
/**
|
||||
* @brief Structure représentant une requête en file d'attente avec ses métadonnées
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxQueuedRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
/** L'identifiant unique de la requête */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
FGuid RequestId;
|
||||
|
||||
/** L'heure à laquelle la requête a été envoyée */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
FDateTime CreatedAt;
|
||||
|
||||
/** Le type de requête */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
EDTFluxApiDataType RequestType = EDTFluxRequestType::None;
|
||||
|
||||
/** Identifiant de la compétition (ContestId) */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
int32 ContestId = -1;
|
||||
|
||||
/** Identifiant de l'étape (StageId) */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
int32 StageId = -1;
|
||||
|
||||
/** Identifiant du split (SplitId) */
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
int32 SplitId = -1;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||
FString RawResponse = "";
|
||||
|
||||
/** Délai maximum avant que la requête soit considérée comme expirée (en secondes) */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||
float TimeoutSeconds = 2.0f;
|
||||
|
||||
/** Determine si la requête peut être mise en cache */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||
bool bIsCacheable = false;
|
||||
|
||||
/** Validité du cache si bIsCacheable est mis à true après reception de la réponse (en secondes) */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||
float CachedValidity = 50.0f;
|
||||
|
||||
/** Indicateur si la requête a reçu une réponse */
|
||||
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||
bool bHasReceivedResponse = false;
|
||||
|
||||
/** Constructeur par défaut */
|
||||
FDTFluxQueuedRequest()
|
||||
{
|
||||
RequestId = FGuid::NewGuid();
|
||||
CreatedAt = FDateTime::Now();
|
||||
}
|
||||
|
||||
/** Constructeur avec paramètres */
|
||||
FDTFluxQueuedRequest(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||
int32 InSplitId = -1)
|
||||
: RequestType(InRequestType)
|
||||
, ContestId(InContestId)
|
||||
, StageId(InStageId)
|
||||
, SplitId(InSplitId)
|
||||
{
|
||||
RequestId = FGuid::NewGuid();
|
||||
CreatedAt = FDateTime::Now();
|
||||
}
|
||||
|
||||
bool operator==(const FDTFluxQueuedRequest& Left) const
|
||||
{
|
||||
return RequestId == Left.RequestId;
|
||||
}
|
||||
|
||||
bool operator!=(const FDTFluxQueuedRequest& Left) const
|
||||
{
|
||||
return RequestId != Left.RequestId;
|
||||
}
|
||||
|
||||
|
||||
const FString Serialize() const;
|
||||
|
||||
/** Vérifie si la requête a expiré */
|
||||
bool HasTimedOut() const
|
||||
{
|
||||
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > TimeoutSeconds;
|
||||
}
|
||||
|
||||
|
||||
/** Vérifie si cette requête correspond aux paramètres spécifiés */
|
||||
bool Matches(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||
int32 InSplitId = -1) const
|
||||
{
|
||||
return RequestType == InRequestType &&
|
||||
(InContestId == -1 || ContestId == InContestId) &&
|
||||
(InStageId == -1 || StageId == InStageId) &&
|
||||
(InSplitId == -1 || SplitId == InSplitId);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestTimedOut, const FDTFluxQueuedRequest&, TimedOutRequest);
|
||||
|
||||
/**
|
||||
* @brief Classe Tickable gérant les requêtes WebSockets qui ne sont pas traçables nativement par l'API.
|
||||
* Cette classe utilise TQueue pour gérer efficacement les requêtes en attente et vérifie leur état dans le tick.
|
||||
*/
|
||||
UCLASS()
|
||||
class DTFLUXNETWORK_API UDTFluxQueuedManager : public UObject, public FTickableGameObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/** Constructeur par défaut */
|
||||
UDTFluxQueuedManager();
|
||||
virtual ~UDTFluxQueuedManager() override;
|
||||
void Initialize();
|
||||
FGuid QueueRequest(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1,
|
||||
const FString& RawMessage = "");
|
||||
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid);
|
||||
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
|
||||
bool IsRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1);
|
||||
FDTFluxQueuedRequest* GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1);
|
||||
const FDTFluxQueuedRequest* GetRequest(const FGuid& SearchedGuid);
|
||||
int32 GetPendingRequestCount();
|
||||
int32 CleanupTimedOutRequests();
|
||||
int32 CleanCashedRequests();
|
||||
void ClearAllRequests();
|
||||
// bool TryProcessResponse(const FDTFluxServerResponse& Response);
|
||||
|
||||
|
||||
// Interface FTickableGameObject
|
||||
virtual void Tick(float DeltaTime) override;
|
||||
virtual bool IsTickable() const override;
|
||||
virtual TStatId GetStatId() const override;
|
||||
virtual bool IsTickableWhenPaused() const override { return true; }
|
||||
virtual bool IsTickableInEditor() const override { return true; }
|
||||
// Interface ~FTickableGameObject
|
||||
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
|
||||
FOnRequestTimedOut OnRequestTimedOut;
|
||||
|
||||
private:
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> PendingRequestsQueue;
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> CompletedRequestsQueue;
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TimedOutRequestsQueue;
|
||||
|
||||
bool bIsInitialized;
|
||||
float CheckInterval;
|
||||
float TimeSinceLastCheck;
|
||||
};
|
||||
@ -15,9 +15,13 @@ USTRUCT()
|
||||
struct FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
FString Path = "";
|
||||
|
||||
FDateTime CreatedAt = FDateTime::Now();
|
||||
FGuid RequestId = FGuid::NewGuid();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -25,11 +29,13 @@ public:
|
||||
* RaceData represents all data concerning the Race and its different Contests, Stages and Splits.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxRaceDataRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
FDTFluxRaceDataRequest(){
|
||||
FDTFluxRaceDataRequest()
|
||||
{
|
||||
Path = "race-datas";
|
||||
}
|
||||
};
|
||||
@ -39,11 +45,13 @@ public:
|
||||
* TeamList is the list of participants of the events
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxTeamListRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxTeamListRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
FDTFluxTeamListRequest(){
|
||||
FDTFluxTeamListRequest()
|
||||
{
|
||||
Path = "team-list";
|
||||
}
|
||||
};
|
||||
@ -52,16 +60,17 @@ public:
|
||||
* Struct representing a Ranking json request object for a specific to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxContestRankingRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxContestRankingRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
public:
|
||||
FDTFluxContestRankingRequest()
|
||||
{
|
||||
Path = "contest-ranking";
|
||||
ContestID = -1;
|
||||
}
|
||||
|
||||
FDTFluxContestRankingRequest(int InContestID)
|
||||
{
|
||||
Path = "contest-ranking";
|
||||
@ -76,10 +85,10 @@ public:
|
||||
* Struct representing a Ranking json request object for a specific Stage to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxStageRankingRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxStageRankingRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
public:
|
||||
FDTFluxStageRankingRequest()
|
||||
{
|
||||
@ -88,6 +97,7 @@ public:
|
||||
StageID = -1;
|
||||
SplitID = -1;
|
||||
}
|
||||
|
||||
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
||||
{
|
||||
Path = "stage-ranking";
|
||||
@ -102,18 +112,16 @@ public:
|
||||
int StageID;
|
||||
UPROPERTY()
|
||||
int SplitID;
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct representing a Ranking json request object for a specific Split to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxSplitRankingRequest: public FDTFluxStageRankingRequest
|
||||
struct FDTFluxSplitRankingRequest : public FDTFluxStageRankingRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
public:
|
||||
FDTFluxSplitRankingRequest()
|
||||
{
|
||||
@ -122,6 +130,7 @@ public:
|
||||
StageID = -1;
|
||||
SplitID = -1;
|
||||
}
|
||||
|
||||
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
||||
{
|
||||
Path = "stage-ranking";
|
||||
@ -129,5 +138,4 @@ public:
|
||||
StageID = InStageId;
|
||||
SplitID = InSplitId;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -4,13 +4,38 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "DTFluxNetworkModule.h"
|
||||
#include "DTFluxRaceDataServerResponse.h"
|
||||
#include "DTFluxRankingServerResponse.h"
|
||||
#include "DTFluxSplitSensorServerResponse.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "Types/Objects/UDTFluxParticipantFactory.h"
|
||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
#include "DTFluxServerResponseStruct.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
* Enum pour indiquer le statut du parsing
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDTFluxResponseStatus : uint8
|
||||
{
|
||||
Unset = 0b00000000 UMETA(DisplayName="Unset"),
|
||||
Success = 0b10000000 UMETA(DisplayName="Success"),
|
||||
JsonParseError = 0b00000001 UMETA(DisplayName="JsonParseError"),
|
||||
ServerError = 0b00000010 UMETA(DisplayName="ServerError"),
|
||||
InvalidType = 0b00000100 UMETA(DisplayName="InvalidType"),
|
||||
MissingData = 0b00001000 UMETA(DisplayName="MissingData"),
|
||||
DataError = 0b00010000 UMETA(DisplayName="MissingData"),
|
||||
UnknownError = 0b00100000 UMETA(DisplayName="UnknownError")
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Struct representing a mixed root json server response
|
||||
* Struct representing a mixed root json server response with integrated parsing capabilities
|
||||
*/
|
||||
USTRUCT()
|
||||
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||
@ -20,35 +45,76 @@ struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||
public:
|
||||
UPROPERTY()
|
||||
FString Type = "";
|
||||
|
||||
UPROPERTY()
|
||||
int Code = -1;
|
||||
|
||||
UPROPERTY()
|
||||
FString Message = "";
|
||||
|
||||
UPROPERTY()
|
||||
FString Trigger = "";
|
||||
|
||||
UPROPERTY()
|
||||
int ContestID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
int StageID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
int SplitID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
FDateTime ReceivedAt;
|
||||
|
||||
UPROPERTY()
|
||||
FString RawMessage;
|
||||
|
||||
UPROPERTY()
|
||||
FName RequestId = FName("");
|
||||
|
||||
UPROPERTY()
|
||||
FText FailureReason;
|
||||
|
||||
|
||||
// === CONSTRUCTEURS ===
|
||||
FDTFluxServerResponse();
|
||||
FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus, bool bLogErrors = true);
|
||||
static FDTFluxServerResponse CreateFromJson(const FString& JsonMessage, bool bLogErrors = true);
|
||||
|
||||
// === MÉTHODES DE PARSING ===
|
||||
|
||||
EDTFluxResponseStatus TryParse(bool bLogErrors = true);
|
||||
|
||||
bool ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList);
|
||||
bool ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate);
|
||||
bool ParseRaceData(FDTFluxRaceData& OutRaceData);
|
||||
bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings);
|
||||
bool ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings);
|
||||
bool ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings);
|
||||
bool ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate);
|
||||
bool ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos);
|
||||
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
bool IsValidResponse() const { return Code == -1; }
|
||||
bool IsSuccessfullyParsed() const { return ParsingStatus == EDTFluxResponseStatus::Success; }
|
||||
EDTFluxResponseStatus GetParsingStatus() const { return ParsingStatus; }
|
||||
EDTFluxApiDataType GetResponseType() const { return ApiDataType; }
|
||||
FString GetDataType() const { return Type; }
|
||||
bool ContainsDataType(const FString& DataType) const { return Type.Contains(DataType); }
|
||||
FString ToDebugString() const;
|
||||
void ShowDebug(const bool bShouldPrintRawMessage = false) const;
|
||||
FString GetErrorMessage() const;
|
||||
|
||||
private:
|
||||
// === DONNÉES INTERNES ===
|
||||
EDTFluxApiDataType ApiDataType;
|
||||
// Statut du parsing initial
|
||||
EDTFluxResponseStatus ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||
|
||||
// === MÉTHODES PRIVÉES DE PARSING ===
|
||||
bool ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const;
|
||||
bool ValidateResponseType(const FString& ExpectedType) const;
|
||||
EDTFluxResponseStatus InitializeFromJson(const FString& JsonMessage, bool bLogErrors);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DTFluxQueuedManager.h"
|
||||
#include "Struct/DTFluxServerResponseStruct.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "Types/DTFluxNetworkSettingsTypes.h"
|
||||
@ -14,12 +15,24 @@
|
||||
#include "DTFluxNetworkSubsystem.generated.h"
|
||||
|
||||
|
||||
|
||||
|
||||
class FDTFluxWebSocketClient;
|
||||
class UDTFluxQueuedManager;
|
||||
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
||||
class FDTFluxHttpClient;
|
||||
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
|
||||
|
||||
|
||||
// Delegates pour les requêtes avec callback
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestResponse, const FGuid&, const FString&);
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestTimeout, const FGuid&, const FString&);
|
||||
// Delegates Blueprint pour les requêtes avec tracking
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestCompleted, const FGuid&, RequestId,
|
||||
EDTFluxApiDataType, RequestType, const FString&, ResponseData);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestFailed, const FGuid&, RequestId,
|
||||
EDTFluxApiDataType, RequestType, const FString&, ErrorMessage);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@ -27,25 +40,28 @@ UCLASS(Blueprintable)
|
||||
class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
|
||||
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network")
|
||||
FOnWebSocketConnected OnWebSocketConnected;
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
|
||||
FOnRaceDataReceived OnRaceDataReceived;
|
||||
|
||||
FOnRaceDataReceived& OnReceivedRaceData()
|
||||
{
|
||||
return OnRaceDataReceived;
|
||||
};
|
||||
|
||||
|
||||
// === DELEGATES POUR LES DONNÉES REÇUES (PUSH) ===
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
|
||||
FOnTeamListReceived OnTeamListReceived;
|
||||
|
||||
FOnTeamListReceived& OnReceivedTeamList()
|
||||
{
|
||||
return OnTeamListReceived;
|
||||
@ -53,32 +69,40 @@ public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
|
||||
FOnStageRankingReceived OnStageRankingReceived;
|
||||
|
||||
FOnStageRankingReceived& OnReceivedStageRanking()
|
||||
{
|
||||
return OnStageRankingReceived;
|
||||
}
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
|
||||
FOnSplitRankingReceived OnSplitRankingReceived;
|
||||
|
||||
FOnSplitRankingReceived& OnReceivedSplitRanking()
|
||||
{
|
||||
return OnSplitRankingReceived;
|
||||
}
|
||||
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
|
||||
FOnContestRankingReceived OnContestRankingReceived;
|
||||
|
||||
FOnContestRankingReceived& OnReceivedContestRanking()
|
||||
{
|
||||
return OnContestRankingReceived;
|
||||
};
|
||||
|
||||
// === DELEGATES POUR LES DONNÉES REÇUES (PULL) ===
|
||||
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
|
||||
FOnSplitSensorReceived OnSplitSensorReceived;
|
||||
|
||||
FOnSplitSensorReceived& OnReceivedSplitSensor()
|
||||
{
|
||||
return OnSplitSensorReceived;
|
||||
};
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxParticipant& /*ParticipantToUpdate*/);
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*ParticipantToUpdate*/);
|
||||
FOnTeamUpdateReceived OnTeamUpdateReceived;
|
||||
|
||||
FOnTeamUpdateReceived& OnReceivedTeamUpdate()
|
||||
{
|
||||
return OnTeamUpdateReceived;
|
||||
@ -86,6 +110,7 @@ public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
|
||||
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
|
||||
|
||||
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate()
|
||||
{
|
||||
return OnTeamStatusUpdateReceived;
|
||||
@ -99,13 +124,41 @@ public:
|
||||
void Reconnect();
|
||||
|
||||
|
||||
// === REQUÊTES AVEC QUEUE ET TRACKING ===
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
FGuid SendTrackedRequest(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1, float TimeoutSeconds = 30.0f);
|
||||
FGuid SendTrackedRequestWithCallback(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
|
||||
FOnDTFluxRequestResponse OnCompleted, FOnDTFluxRequestTimeout OnTimeout,
|
||||
float TimeoutSeconds = 30.0f);
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const;
|
||||
const FDTFluxQueuedRequest* GetTrackedRequestPtr(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests", CallInEditor)
|
||||
bool HasRequestReceivedResponse(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
FString GetRequestResponseData(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
bool IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
int32 GetPendingRequestCount() const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
UDTFluxQueuedManager* GetQueueManager() const;
|
||||
|
||||
// === EVENTS BLUEPRINT POUR LE TRACKING ===
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
void SendRequest(const EDTFluxRequestType RequestType, int InContestId = -1, int InStageId = -1, int InSplitId = -1);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||
|
||||
// === REQUÊTES DIRECTES (LEGACY) ===
|
||||
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
|
||||
int InSplitId = -1);
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||
void SendMessage(const FString& Message);
|
||||
|
||||
|
||||
protected:
|
||||
// ~Subsystem Interface
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
@ -114,9 +167,22 @@ protected:
|
||||
|
||||
|
||||
private:
|
||||
// === CONFIGURATION ===
|
||||
FDTFluxWsSettings WsSettings;
|
||||
FDTFluxHttpSettings HttpSettings;
|
||||
|
||||
UPROPERTY()
|
||||
UDTFluxQueuedManager* QueueManager;
|
||||
|
||||
// === MAPPING DES CALLBACKS C++ ===
|
||||
TMap<FGuid, FOnDTFluxRequestResponse> PendingCallbacks;
|
||||
TMap<FGuid, FOnDTFluxRequestTimeout> PendingTimeoutCallbacks;
|
||||
|
||||
// === CLIENTS RÉSEAU ===
|
||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
||||
|
||||
// === MÉTHODES DE CONFIGURATION ===
|
||||
UFUNCTION()
|
||||
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
||||
UFUNCTION()
|
||||
@ -124,40 +190,46 @@ private:
|
||||
void ReconnectWs(const FName WsClientId);
|
||||
void ReconnectHttp(const FName WsClientId);
|
||||
|
||||
// === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
|
||||
void RegisterWebSocketEvents();
|
||||
void RegisterHttpEvents();
|
||||
void UnregisterWebSocketEvents();
|
||||
void UnregisterHttpEvents();
|
||||
|
||||
void OnWebSocketConnected_Subsystem();
|
||||
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode , const FString& Reason, bool bWasClean);
|
||||
void ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse);
|
||||
void ParseRaceData(const FDTFluxServerResponse& Response);
|
||||
void ParseContestRanking(const FDTFluxServerResponse& Response);
|
||||
void ParseStageRankingResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseSplitRankingResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseStatusUpdateResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseSplitSensorResponse(const FDTFluxServerResponse& Response);
|
||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||
|
||||
// TODO : Allow multiple instances of network clients.
|
||||
// // For Future use of Multi-Connections
|
||||
// TArray<FDTFluxWebSocketClientSP> WsClients;
|
||||
// // For Future use of Multi-Connections
|
||||
// TArray<FDTFluxHttpClientSP> HttpClient;
|
||||
// Fo now we jest stick to only one client for each protocol
|
||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
|
||||
|
||||
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
||||
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
||||
FDelegateHandle OnWsClosedEventDelegateHandle;
|
||||
FDelegateHandle OnWsMessageEventDelegateHandle;
|
||||
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
||||
|
||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
||||
|
||||
// === GESTION DES ÉVÉNEMENTS HTTP ===
|
||||
void RegisterHttpEvents();
|
||||
void UnregisterHttpEvents();
|
||||
|
||||
// === PARSING DES RÉPONSES ===
|
||||
void ParseTeamListResponse(FDTFluxServerResponse& ServerResponse);
|
||||
void ParseRaceData(FDTFluxServerResponse& Response);
|
||||
void ParseContestRanking(FDTFluxServerResponse& Response);
|
||||
void ParseStageRankingResponse(FDTFluxServerResponse& Response);
|
||||
void ParseSplitRankingResponse(FDTFluxServerResponse& Response);
|
||||
void ParseStatusUpdateResponse(FDTFluxServerResponse& Response);
|
||||
void ParseSplitSensorResponse(FDTFluxServerResponse& Response);
|
||||
EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response);
|
||||
|
||||
void Parse(FDTFluxServerResponse& Response);
|
||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||
|
||||
// === GESTION DES REQUÊTES TRACKÉES ===
|
||||
UFUNCTION()
|
||||
void OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest);
|
||||
bool TryMatchResponseToQueuedRequest(const FDTFluxServerResponse& Response);
|
||||
void CompleteTrackedRequest(const FGuid& RequestId, const FString& ResponseData, EDTFluxRequestType RequestType);
|
||||
void FailTrackedRequest(const FGuid& RequestId, const FString& ErrorMessage, EDTFluxRequestType RequestType);
|
||||
void SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest);
|
||||
|
||||
// === UTILITAIRES ===
|
||||
static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user