Compare commits

..

6 Commits

16 changed files with 688 additions and 766 deletions

View File

@ -100,6 +100,20 @@ void FDTFluxModelAssetCustomization::CustomizeDetailsWithoutRawDataAsset(IDetail
void FDTFluxModelAssetCustomization::CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder) void FDTFluxModelAssetCustomization::CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder)
{ {
// Edit object
TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
if (ObjectsBeingCustomized.Num() > 0)
{
ModelAsset = Cast<UDTFluxModelAsset>(ObjectsBeingCustomized[0].Get());
}
if (!ModelAsset.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("No valid DTFluxModelAsset found"));
return;
}
// ===== WIDGET PRINCIPAL ===== // ===== WIDGET PRINCIPAL =====
IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory( IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory(
"DTFlux Model Explorer", "DTFlux Model Explorer",

View File

@ -1,3 +1,67 @@
// Fill out your copyright notice in the Description page of Project Settings. // Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxRaceDataStructs.h"
bool FDTFluxContest::IsFinished() const
{
return EndTime <= FDateTime::Now();
}
void FDTFluxContest::UpdateEndTime()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.EndTime < B.EndTime;
});
EndTime = TempStages.Last().EndTime;
}
int FDTFluxContest::GetLastStageId()
{
if (LastStageId <= 0)
{
UpdateLastStageId();
}
return LastStageId;
}
void FDTFluxContest::UpdateLastStageId()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
LastStageId = TempStages.Last().StageId;
}
FDTFluxStage& FDTFluxContest::GetLastStage() const
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
return TempStages.Last();
}
bool FDTFluxContest::GetStage(const int StageID, FDTFluxStage& OutStage) const
{
if (Stages.Num() == 0)
{
return false;
}
for (const FDTFluxStage& Stage : Stages)
{
if (Stage.StageId == StageID)
{
OutStage = Stage;
return true;
}
}
return false;
}

View File

@ -1,138 +1,145 @@
// Fill out your copyright notice in the Description page of Project Settings. // Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Struct/DTFluxTeamListStruct.h" #include "Types/Struct/DTFluxTeamListStruct.h"
#include "DTFluxCoreModule.h" #include "DTFluxCoreModule.h"
#include "Dom/JsonObject.h"
// ===================================
// FDTFluxPerson Implementation
// ===================================
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person) bool FDTFluxPerson::operator==(const FDTFluxPerson& Right) const
{ {
Teammate.Add(Person); return GetNormalizedString() == Right.GetNormalizedString();
} }
void FDTFluxParticipant::AddTeammate(const FString LastName, const FString FirstName, const FString Gender) bool FDTFluxPerson::operator!=(const FDTFluxPerson& Right) const
{ {
return !(*this == Right);
} }
FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars) const bool FDTFluxPerson::operator==(const int Length) const
{ {
{ return GetNormalizedString().Len() == Length;
if (MaxChar <= 0)
{
return "";
}
FString FirstName;
FString LastName;
if (IsTeam())
{
LastName = Team;
}
else
{
FirstName = Teammate[0].FirstName;
LastName = Teammate[0].LastName;
}
FString Initial;
if (!FirstName.IsEmpty())
{
Initial = FirstName.Left(1).ToUpper() + " ";
}
FString FormattedLastName = LastName.ToUpper();
FString FullName = Initial + FormattedLastName;
UE_LOG(logDTFluxCore, Error, TEXT("FullName for Bib %i is %s"), Bib, *FullName);
if (FullName.Len() <= MaxChar)
{
return FullName;
}
const int32 OverflowLength = OverflowChars.Len();
if (OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
if (Initial.Len() + OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
const int32 AvailableForLastName = MaxChar - Initial.Len() - OverflowLength;
if (AvailableForLastName <= 0)
{
return FullName.Left(MaxChar);
}
FString TruncatedName = Initial + FormattedLastName.Left(AvailableForLastName) + OverflowChars;
if (TruncatedName.Len() > MaxChar)
{
return TruncatedName.Left(MaxChar);
}
return TruncatedName;
}
} }
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) const bool FDTFluxPerson::operator!=(const int Length) const
{ {
FString BibText = FString::FromInt(Bib) + " "; return !(*this == Length);
FString FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar); }
return BibText + FormattedName;
FString FDTFluxPerson::GetNormalizedString() const
{
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower();
}
bool FDTFluxPerson::IsValid() const
{
return !FirstName.TrimStartAndEnd().IsEmpty() &&
!LastName.TrimStartAndEnd().IsEmpty() &&
!Gender.TrimStartAndEnd().IsEmpty();
}
FDTFluxParticipant::FDTFluxParticipant()
: Bib(-1)
, ContestId(-1)
, Elite(false)
, Status(static_cast<EDTFluxParticipantStatusType>(0))
, bIsMassStartParticipant(false)
, CurrentSplit(-1)
{
Teammate.Reset();
} }
// Constructeur privé depuis JSON
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject) FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
: Bib(JsonObject->GetIntegerField(TEXT("bib"))) : Bib(JsonObject->GetIntegerField(TEXT("bib")))
, ContestId(JsonObject->GetIntegerField(TEXT("contestId"))) , ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
, Category(JsonObject->GetStringField(TEXT("category"))) , Category(JsonObject->GetStringField(TEXT("category")))
, Club(JsonObject->GetStringField(TEXT("club"))) , Club(JsonObject->GetStringField(TEXT("club")))
, Elite(JsonObject->GetBoolField(TEXT("elite"))) , Elite(JsonObject->GetBoolField(TEXT("elite")))
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status")))) , Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
, Team(JsonObject->GetStringField(TEXT("team"))) , Team(JsonObject->GetStringField(TEXT("team")))
, bIsMassStartParticipant(false) , CurrentSplit(-1)
, CurrentSplit(-1)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object")) UE_LOG(logDTFluxCore, Log, TEXT("Creating participant from JSON - Bib: %d, Contest: %d"), Bib, ContestId);
for (uint8 Index = 1; ; Index++)
for (uint8 Index = 1; Index <= 10; Index++)
{ {
FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index); FString FirstNameKey = Index == 1 ? TEXT("firstName") : FString::Printf(TEXT("firstName%d"), Index);
FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index); FString LastNameKey = Index == 1 ? TEXT("lastName") : FString::Printf(TEXT("lastName%d"), Index);
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index); FString GenderKey = Index == 1 ? TEXT("gender") : FString::Printf(TEXT("gender%d"), Index);
// max 10 Persons
if (Index >= 10) // Vérifie si au moins un des champs existe
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey) && !JsonObject->HasField(GenderKey))
{ {
break; break;
} }
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
&& !JsonObject->HasField(GenderKey))
{
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
break;
}
const FString FirstName = JsonObject->GetStringField(FirstNameKey); const FString FirstName = JsonObject->GetStringField(FirstNameKey);
const FString LastName = JsonObject->GetStringField(LastNameKey); const FString LastName = JsonObject->GetStringField(LastNameKey);
const FString Gender = JsonObject->GetStringField(GenderKey); const FString Gender = JsonObject->GetStringField(GenderKey);
if (FirstName.IsEmpty() && LastName.IsEmpty())
if (FirstName.TrimStartAndEnd().IsEmpty() && LastName.TrimStartAndEnd().IsEmpty())
{
continue; continue;
}
FDTFluxPerson Person; FDTFluxPerson Person;
Person.FirstName = FirstName; Person.FirstName = FirstName.TrimStartAndEnd();
Person.LastName = LastName; Person.LastName = LastName.TrimStartAndEnd();
Person.Gender = Gender; Person.Gender = Gender.TrimStartAndEnd();
Teammate.Add(Person);
if (Person.IsValid())
{
Teammate.Add(Person);
UE_LOG(logDTFluxCore, Verbose, TEXT("Added person %d: %s %s (%s)"),
Index, *Person.FirstName, *Person.LastName, *Person.Gender);
}
else
{
UE_LOG(logDTFluxCore, Warning, TEXT("Invalid person data at index %d: '%s' '%s' '%s'"),
Index, *FirstName, *LastName, *Gender);
}
} }
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
UE_LOG(logDTFluxCore, Log, TEXT("Participant created with %d teammates"), Teammate.Num());
} }
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject) bool FDTFluxParticipant::IsDefault() const
{ {
return FDTFluxParticipant(JsonObject); return Bib == -1
&& ContestId == -1
&& Category.IsEmpty()
&& Club.IsEmpty()
&& !Elite
&& Status == static_cast<EDTFluxParticipantStatusType>(0)
&& Team.IsEmpty()
&& !bIsMassStartParticipant
&& CurrentSplit == -1
&& Teammate.IsEmpty();
}
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
{
if (Person.IsValid())
{
Teammate.Add(Person);
UE_LOG(logDTFluxCore, Verbose, TEXT("Added teammate: %s %s"), *Person.FirstName, *Person.LastName);
}
else
{
UE_LOG(logDTFluxCore, Warning, TEXT("Cannot add invalid teammate: %s %s"), *Person.FirstName, *Person.LastName);
}
}
void FDTFluxParticipant::AddTeammate(const FString& LastName, const FString& FirstName, const FString& Gender)
{
FDTFluxPerson Person;
Person.FirstName = FirstName.TrimStartAndEnd();
Person.LastName = LastName.TrimStartAndEnd();
Person.Gender = Gender.TrimStartAndEnd();
AddTeammate(Person);
} }
int FDTFluxParticipant::GetTeammateNum() const int FDTFluxParticipant::GetTeammateNum() const
@ -142,5 +149,157 @@ int FDTFluxParticipant::GetTeammateNum() const
bool FDTFluxParticipant::IsTeam() const bool FDTFluxParticipant::IsTeam() const
{ {
return Teammate.Num() < 1; return Teammate.Num() > 1;
} }
const TArray<FDTFluxPerson>& FDTFluxParticipant::GetTeammate() const
{
return Teammate;
}
FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString& Separator, const FString& OverflowChar) const
{
if (MaxChar <= 0)
{
return TEXT("");
}
FString FirstName;
FString LastName;
if (IsTeam())
{
if (!Team.IsEmpty())
{
LastName = Team;
}
else
{
TArray<FString> Names;
for (const FDTFluxPerson& Person : Teammate)
{
Names.Add(Person.LastName);
}
LastName = FString::Join(Names, TEXT("/"));
}
}
else if (Teammate.Num() > 0)
{
FirstName = Teammate[0].FirstName;
LastName = Teammate[0].LastName;
}
else
{
LastName = TEXT("Unknown");
}
FString Initial;
if (!FirstName.IsEmpty())
{
Initial = FirstName.Left(1).ToUpper() + Separator;
}
FString FormattedLastName = LastName.ToUpper();
FString FullName = Initial + FormattedLastName;
if (FullName.Len() <= MaxChar)
{
return FullName;
}
const int32 OverflowLength = OverflowChar.Len();
if (OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
if (Initial.Len() + OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
const int32 AvailableForLastName = MaxChar - Initial.Len() - OverflowLength;
if (AvailableForLastName <= 0)
{
return FullName.Left(MaxChar);
}
FString TruncatedName = Initial + FormattedLastName.Left(AvailableForLastName) + OverflowChar;
if (TruncatedName.Len() > MaxChar)
{
return TruncatedName.Left(MaxChar);
}
return TruncatedName;
}
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString& Separator,
const FString& OverflowChar, const FString& BibSeparator) const
{
FString BibText = FString::FromInt(Bib) + BibSeparator;
int32 RemainingChars = MaxChar - BibText.Len();
if (RemainingChars <= 0)
{
return BibText.Left(MaxChar);
}
FString FormattedName = GetFormattedName(RemainingChars, Separator, OverflowChar);
return BibText + FormattedName;
}
FText FDTFluxParticipant::GetFormattedNameText(const int MaxChar, const FString& Separator, const FString& OverflowChar) const
{
return FText::FromString(GetFormattedName(MaxChar, Separator, OverflowChar));
}
FText FDTFluxParticipant::GetConcatFormattedNameText(const int MaxChar, const FString& Separator,
const FString& OverflowChar, const FString& BibSeparator) const
{
return FText::FromString(GetConcatFormattedName(MaxChar, Separator, OverflowChar, BibSeparator));
}
FString FDTFluxParticipant::GetFormattedName(const FDTFluxParticipant& Participant, const int MaxChar,
const FString& Separator, const FString& OverflowChar)
{
return Participant.GetFormattedName(MaxChar, Separator, OverflowChar);
}
FString FDTFluxParticipant::GetConcatFormattedName(const FDTFluxParticipant& Participant, const int MaxChar,
const FString& Separator, const FString& OverflowChar,
const FString& BibSeparator)
{
return Participant.GetConcatFormattedName(MaxChar, Separator, OverflowChar, BibSeparator);
}
FText FDTFluxParticipant::GetFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar,
const FString& Separator, const FString& OverflowChar)
{
return Participant.GetFormattedNameText(MaxChar, Separator, OverflowChar);
}
FText FDTFluxParticipant::GetConcatFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar,
const FString& Separator, const FString& OverflowChar,
const FString& BibSeparator)
{
return Participant.GetConcatFormattedNameText(MaxChar, Separator, OverflowChar, BibSeparator);
}
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
{
if (!JsonObject.IsValid())
{
UE_LOG(logDTFluxCore, Error, TEXT("Cannot create participant from invalid JSON object"));
return FDTFluxParticipant();
}
return FDTFluxParticipant(JsonObject);
}
FDTFluxTeamStatusUpdate::FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
: Bib(InBib)
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
{
}

View File

@ -89,68 +89,6 @@ public:
bool GetStage(const int StageID, FDTFluxStage& OutStage) const; bool GetStage(const int StageID, FDTFluxStage& OutStage) const;
}; };
inline bool FDTFluxContest::IsFinished() const
{
return EndTime <= FDateTime::Now();
}
inline void FDTFluxContest::UpdateEndTime()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.EndTime < B.EndTime;
});
EndTime = TempStages.Last().EndTime;
}
inline int FDTFluxContest::GetLastStageId()
{
if (LastStageId <= 0)
{
UpdateLastStageId();
}
return LastStageId;
}
inline void FDTFluxContest::UpdateLastStageId()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
LastStageId = TempStages.Last().StageId;
}
inline FDTFluxStage& FDTFluxContest::GetLastStage() const
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
return TempStages.Last();
}
inline bool FDTFluxContest::GetStage(const int StageID, FDTFluxStage& OutStage) const
{
if (Stages.Num() == 0)
{
return false;
}
for (const FDTFluxStage& Stage : Stages)
{
if (Stage.StageId == StageID)
{
OutStage = Stage;
return true;
}
}
return false;
}
USTRUCT() USTRUCT()
struct DTFLUXCORE_API FDTFluxRaceData struct DTFLUXCORE_API FDTFluxRaceData
{ {
@ -160,4 +98,4 @@ public:
UPROPERTY() UPROPERTY()
// ReSharper disable once IdentifierTypo // ReSharper disable once IdentifierTypo
TArray<FDTFluxContest> Datas; TArray<FDTFluxContest> Datas;
}; };

View File

@ -165,6 +165,7 @@ struct FDTFluxStageRankings : public FDTFluxDetailedRankings
Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true); Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true);
if (Exploded.Num() == 3) if (Exploded.Num() == 3)
{ {
//TODO: Pas sur que ce soit super de le mettre à ce jour ???
FDateTime Now = FDateTime::Now(); FDateTime Now = FDateTime::Now();
RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(), RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(),
FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]), FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]),

View File

@ -7,6 +7,10 @@
#include "Types/Enum/DTFluxModelEnums.h" #include "Types/Enum/DTFluxModelEnums.h"
#include "DTFluxTeamListStruct.generated.h" #include "DTFluxTeamListStruct.generated.h"
// Forward declarations
class UDTFluxModelAsset;
class UDTFluxParticipantFactory;
USTRUCT() USTRUCT()
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
{ {
@ -15,76 +19,78 @@ struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
public: public:
UPROPERTY() UPROPERTY()
FString Type = "team-list-item"; FString Type = "team-list-item";
UPROPERTY() UPROPERTY()
int ContestId; int ContestId = 0;
UPROPERTY() UPROPERTY()
int Bib; int Bib = 0;
UPROPERTY() UPROPERTY()
FString FirstName; FString FirstName;
UPROPERTY() UPROPERTY()
FString LastName; FString LastName;
UPROPERTY() UPROPERTY()
FString FirstName2 = ""; FString FirstName2 = "";
UPROPERTY() UPROPERTY()
FString LastName2 = ""; FString LastName2 = "";
UPROPERTY() UPROPERTY()
FString Team = ""; FString Team = "";
UPROPERTY() UPROPERTY()
FString Gender; FString Gender;
UPROPERTY() UPROPERTY()
FString Gender2; FString Gender2;
UPROPERTY() UPROPERTY()
bool Elite; bool Elite = false;
UPROPERTY() UPROPERTY()
FString Category; FString Category;
UPROPERTY() UPROPERTY()
int Status; int Status = 0;
UPROPERTY() UPROPERTY()
FString Club; FString Club;
}; };
USTRUCT(BlueprintType, Category="DTFlux|Model") USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXCORE_API FDTFluxPerson struct DTFLUXCORE_API FDTFluxPerson
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString FirstName; FString FirstName;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString LastName; FString LastName;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString Gender; FString Gender;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString FunctionLine1 = TEXT(""); FString FunctionLine1 = TEXT("");
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString FunctionLine2 = TEXT(""); FString FunctionLine2 = TEXT("");
bool operator==(const FDTFluxPerson& Right) const bool operator==(const FDTFluxPerson& Right) const;
{ bool operator!=(const FDTFluxPerson& Right) const;
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower() bool operator==(const int Length) const;
== Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower(); bool operator!=(const int Length) const;
}
bool operator==(const int Length) const FString GetNormalizedString() const;
{
return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length;
}
bool operator!=(const int Length) const bool IsValid() const;
{
return !(*this == Length);
}
bool operator!=(const FDTFluxPerson& Right) const
{
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
!= Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
}
}; };
USTRUCT(BlueprintType, Category="DTFlux|Model") USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXCORE_API FDTFluxParticipant struct DTFLUXCORE_API FDTFluxParticipant
{ {
@ -94,117 +100,98 @@ struct DTFLUXCORE_API FDTFluxParticipant
friend class UDTFluxParticipantFactory; friend class UDTFluxParticipantFactory;
public: public:
// Constructeur public par défaut requis par Unreal FDTFluxParticipant();
FDTFluxParticipant()
: Bib(-1)
, ContestId(-1)
, Elite(false)
, Status(static_cast<EDTFluxParticipantStatusType>(0))
, bIsMassStartParticipant(false)
, CurrentSplit(-1)
{
Teammate.Reset();
}
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
/**
* Vérifie si le participant est dans son état par défaut (non initialisé)
* @return True si tous les champs sont à leur valeur par défaut
*/
bool IsDefault() const
{
return Bib == -1
&& ContestId == -1
&& Category.IsEmpty()
&& Club.IsEmpty()
&& !Elite
&& Status == static_cast<EDTFluxParticipantStatusType>(0)
&& Team.IsEmpty()
&& !bIsMassStartParticipant
&& CurrentSplit == -1
&& Teammate.IsEmpty();
}
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
int Bib = -1; int Bib = -1;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
int ContestId = -1; int ContestId = -1;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
FString Category; FString Category;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString Club; FString Club;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
bool Elite; UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere) bool Elite = false;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
EDTFluxParticipantStatusType Status; EDTFluxParticipantStatusType Status;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
FString Team; FString Team;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
bool bIsMassStartParticipant = false; bool bIsMassStartParticipant = false;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
int CurrentSplit = -1; int CurrentSplit = -1;
// void Dump() const; bool IsDefault() const;
void AddTeammate(const FDTFluxPerson& Person); void AddTeammate(const FDTFluxPerson& Person);
void AddTeammate(const FString LastName, const FString FirstName, const FString Gender); void AddTeammate(const FString& LastName, const FString& FirstName, const FString& Gender);
FText GetFormattedNameText(const int MaxChar = 15, const FString OverflowChar = FString("...")) const int GetTeammateNum() const;
{
return FText::FromString(GetFormattedName(MaxChar, OverflowChar));
};
FText GetConcatFormattedNameText(const int MaxChar = 20, const FString OverflowChar = FString("...")) const bool IsTeam() const;
{
return FText::FromString(GetConcatFormattedName(MaxChar, OverflowChar));
};
FString GetFormattedName(const int MaxChar = 15, const FString OverflowChar = FString("...")) const;
FString GetConcatFormattedName(const int MaxChar = 20, const FString OverflowChar = FString("...")) const;
static FString GetFormattedName(const FDTFluxParticipant& Participant, const int MaxChar = 15,
const FString OverflowChar = FString("..."))
{
return Participant.GetFormattedName(MaxChar, OverflowChar);
};
static FString GetConcatFormattedName(const FDTFluxParticipant& Participant, const int MaxChar = 15, const TArray<FDTFluxPerson>& GetTeammate() const;
const FString OverflowChar = FString("..."))
{
return Participant.GetConcatFormattedName(MaxChar, OverflowChar);
};
static FText GetFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15, FString GetFormattedName(const int MaxChar = 15,
const FString OverflowChar = FString("...")) const FString& Separator = FString(". "),
{ const FString& OverflowChar = FString("...")) const;
return Participant.GetFormattedNameText();
};
static FText GetConcatFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15, FString GetConcatFormattedName(const int MaxChar = 20,
const FString OverflowChar = FString("...")) const FString& Separator = FString(". "),
{ const FString& OverflowChar = FString("..."),
return Participant.GetConcatFormattedNameText(); const FString& BibSeparator = FString(". ")) const;
};
const TArray<FDTFluxPerson> GetTeammate() const { return Teammate; }
private: FText GetFormattedNameText(const int MaxChar = 15,
// --- Constructeur privé --- const FString& Separator = FString(". "),
explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject); const FString& OverflowChar = FString("...")) const;
FText GetConcatFormattedNameText(const int MaxChar = 20,
const FString& Separator = FString(". "),
const FString& OverflowChar = FString("..."),
const FString& BibSeparator = FString(". ")) const;
static FString GetFormattedName(const FDTFluxParticipant& Participant,
const int MaxChar = 15,
const FString& Separator = FString(". "),
const FString& OverflowChar = FString("..."));
static FString GetConcatFormattedName(const FDTFluxParticipant& Participant,
const int MaxChar = 15,
const FString& Separator = FString(". "),
const FString& OverflowChar = FString("..."),
const FString& BibSeparator = FString(". "));
static FText GetFormattedNameText(const FDTFluxParticipant& Participant,
const int MaxChar = 15,
const FString& Separator = FString(". "),
const FString& OverflowChar = FString("..."));
static FText GetConcatFormattedNameText(const FDTFluxParticipant& Participant,
const int MaxChar = 15,
const FString& Separator = FString(". "),
const FString& OverflowChar = FString("..."),
const FString& BibSeparator = FString(". "));
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
protected: protected:
UPROPERTY(Category="DTFlux|model", VisibleAnywhere) UPROPERTY(Category="DTFlux|Model", VisibleAnywhere)
TArray<FDTFluxPerson> Teammate; TArray<FDTFluxPerson> Teammate;
// Méthode publique pour construire à partir d'un JSON (utilisée par la factory)
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject); private:
int GetTeammateNum() const; explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject);
bool IsTeam() const;
}; };
/**
* @struct FDTFluxTeamListDefinition
* Struct representing the Participant List definition
* Used to exchange data between Objects in the system
*/
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxTeamListDefinition struct DTFLUXCORE_API FDTFluxTeamListDefinition
{ {
@ -212,27 +199,22 @@ struct DTFLUXCORE_API FDTFluxTeamListDefinition
public: public:
UPROPERTY() UPROPERTY()
// ReSharper disable once IdentifierTypo
TArray<FDTFluxParticipant> Participants; TArray<FDTFluxParticipant> Participants;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDTFluxTeamStatusUpdate struct DTFLUXCORE_API FDTFluxTeamStatusUpdate
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxTeamStatusUpdate() = default; FDTFluxTeamStatusUpdate() = default;
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus);
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
: Bib(InBib)
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
{
};
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant") UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
int Bib = -1; int Bib = -1;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant") UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
EDTFluxParticipantStatusType Status = EDTFluxParticipantStatusType::Unknown; EDTFluxParticipantStatusType Status = EDTFluxParticipantStatusType::Unknown;
}; };

View File

@ -62,6 +62,7 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxContestRankings Rankings; FDTFluxContestRankings Rankings;
if (InResponse.ParseContestRanking(Rankings)) if (InResponse.ParseContestRanking(Rankings))
{ {
OnContestRankings.Broadcast(Rankings.ContestId, Rankings);
ProcessContestRanking(Rankings); ProcessContestRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"),
*Rankings.ContestName); *Rankings.ContestName);
@ -77,6 +78,8 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxStageRankings Rankings; FDTFluxStageRankings Rankings;
if (InResponse.ParseStageRanking(Rankings)) if (InResponse.ParseStageRanking(Rankings))
{ {
FDTFluxStageKey StageKey(Rankings.ContestId, Rankings.StageId);
OnStageRankings.Broadcast(StageKey, Rankings);
ProcessStageRanking(Rankings); ProcessStageRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"),
Rankings.StageId, Rankings.ContestId); Rankings.StageId, Rankings.ContestId);
@ -92,6 +95,8 @@ void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InRespo
FDTFluxSplitRankings Rankings; FDTFluxSplitRankings Rankings;
if (InResponse.ParseSplitRanking(Rankings)) if (InResponse.ParseSplitRanking(Rankings))
{ {
FDTFluxSplitKey SplitKey(Rankings.ContestId, Rankings.StageId, Rankings.SplitId);
OnSplitRankings.Broadcast(SplitKey, Rankings);
ProcessSplitRanking(Rankings); ProcessSplitRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"), TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"),
@ -275,7 +280,6 @@ void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& Spli
SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName()); SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName());
} }
void UDTFluxCoreSubsystem::SendRequest(const FString& Message) void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
@ -360,7 +364,7 @@ bool UDTFluxCoreSubsystem::GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKe
} }
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<int> ForContests) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -386,7 +390,7 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<i
for (auto ContestId : ForContests) for (auto ContestId : ForContests)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking,
ContestId, -1, -1, OnSuccess, OnError); ContestId, -1, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;
@ -394,7 +398,8 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<i
return TArray<FGuid>(); return TArray<FGuid>();
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages,
bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -420,7 +425,7 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDT
for (auto StageKey : ForStages) for (auto StageKey : ForStages)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking,
StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError); StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;
@ -428,7 +433,8 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDT
return TArray<FGuid>(); return TArray<FGuid>();
} }
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits) TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits,
bool bEnableCache)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
@ -454,7 +460,7 @@ TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDT
for (auto SplitKey : ForSplits) for (auto SplitKey : ForSplits)
{ {
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking, FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking,
SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError); SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest); RequestIds.Add(ContestRequest);
} }
return RequestIds; return RequestIds;

View File

@ -45,61 +45,136 @@ void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup NextFo
} }
} }
void UDTFluxPursuitManager::DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext)
{
FString FocusBibs;
for (const auto& Pursuit : OutPursuitFocusNext)
{
FocusBibs += FString::Printf(TEXT("%d "), Pursuit.Bib);
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus Bibs: %s"), *FocusBibs);
}
void UDTFluxPursuitManager::DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext)
{
FString NextBibs;
for (int32 i = 0; i < OutPursuitNext.Num(); i++)
{
NextBibs += FString::Printf(TEXT("%d "), OutPursuitNext[i].Bib);
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next Bibs: %s"), *NextBibs);
}
void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext, void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext,
TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate, TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate,
const int MaxSimultaneousPursuit) const int MaxSimultaneousPursuit)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== GetPursuit CALLED ==="));
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Available groups: %d"), GroupedPursuit.Num());
// Validation
if (MaxSimultaneousPursuit <= 0) if (MaxSimultaneousPursuit <= 0)
{ {
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("MaxSimultaneousPursuit must be > 0")); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Invalid MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
OutPursuitFocusNext = TArray<FDTFluxPursuitInfo>(); OutPursuitFocusNext.Reset();
OutPursuitNext = TArray<FDTFluxPursuitInfo>(); OutPursuitNext.Reset();
BIsFocusTruncate = false; BIsFocusTruncate = false;
return; return;
} }
if (bIsSequenceDone && MaxSimultaneousPursuit <= 0)
if (bIsSequenceDone || GroupedPursuit.IsEmpty())
{ {
OutPursuitFocusNext = TArray<FDTFluxPursuitInfo>(); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No groups available or sequence completed"));
OutPursuitNext = TArray<FDTFluxPursuitInfo>(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset();
BIsFocusTruncate = false; BIsFocusTruncate = false;
return; return;
} }
OutPursuitFocusNext.Reset(); OutPursuitFocusNext.Reset();
OutPursuitNext.Reset(); OutPursuitNext.Reset();
if (!GroupedPursuit.IsEmpty())
// === ÉTAPE 1: FOCUS = PREMIER GROUPE (et le supprimer) ===
FDTFluxPursuitGroup FocusGroup = GroupedPursuit[0]; // Copie du premier groupe
GroupedPursuit.RemoveAt(0); // ✅ SUPPRIMER le premier groupe
SetPursuitInfoIsMassStart(FocusGroup);
OutPursuitFocusNext = FocusGroup.PursuitGroup;
BIsFocusTruncate = FocusGroup.PursuitGroup.Num() > 1;
UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("Focus Group: StartTime=%s, Participants=%d"),
*FocusGroup.StartTimeGlobal.ToString(),
FocusGroup.PursuitGroup.Num());
// === ÉTAPE 2: NEXT = GROUPES SUIVANTS (SANS les supprimer) ===
int32 TargetNextCount = MaxSimultaneousPursuit - 1; // -1 pour le focus
int32 AddedNextCount = 0;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Target Next Count: %d"), TargetNextCount);
// ✅ PARCOURIR les groupes restants SANS les modifier
for (int32 GroupIndex = 0;
GroupIndex < GroupedPursuit.Num() && AddedNextCount < TargetNextCount;
GroupIndex++)
{ {
FDTFluxPursuitGroup NextFocusGroup = GroupedPursuit[0]; FDTFluxPursuitGroup& NextGroup = GroupedPursuit[GroupIndex]; // Référence (pour SetPursuitInfoIsMassStart)
GroupedPursuit.RemoveAt(0);
SetPursuitInfoIsMassStart(NextFocusGroup); if (NextGroup.PursuitGroup.Num() == 0)
OutPursuitFocusNext = NextFocusGroup.PursuitGroup;
bFocusIsTruncate = NextFocusGroup.PursuitGroup.Num() > 1;
for (int RemainingPursuitNum = MaxSimultaneousPursuit - 1; RemainingPursuitNum != 0;)
{ {
if (!GroupedPursuit.IsEmpty()) continue; // Groupe vide
{
FDTFluxPursuitGroup NextGroup = GroupedPursuit[0];
SetPursuitInfoIsMassStart(NextGroup);
if (NextGroup.PursuitGroup.Num() >= RemainingPursuitNum)
{
// extract the number we need
for (int i = 0; i < RemainingPursuitNum; i++)
{
FDTFluxPursuitInfo Pursuit = NextGroup.PursuitGroup[0];
OutPursuitNext.Add(Pursuit);
}
break;
}
else
{
OutPursuitNext.Append(NextGroup.PursuitGroup);
RemainingPursuitNum -= NextGroup.PursuitGroup.Num();
}
}
else
{
break;
}
} }
int32 AvailableInGroup = NextGroup.PursuitGroup.Num();
int32 NeededFromGroup = FMath::Min(TargetNextCount - AddedNextCount, AvailableInGroup);
UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("Processing Next Group %d: StartTime=%s, Available=%d, Taking=%d"),
GroupIndex,
*NextGroup.StartTimeGlobal.ToString(),
AvailableInGroup,
NeededFromGroup);
// ✅ COPIER les participants nécessaires (SANS les supprimer du groupe)
for (int32 ParticipantIndex = 0; ParticipantIndex < NeededFromGroup; ParticipantIndex++)
{
FDTFluxPursuitInfo NextParticipant = NextGroup.PursuitGroup[ParticipantIndex]; // Copie
// Appliquer MassStart
NextParticipant.bIsMassStart = NextParticipant.StartTime >= MassStartTime;
OutPursuitNext.Add(NextParticipant);
AddedNextCount++;
UE_LOG(logDTFluxCoreSubsystem, VeryVerbose,
TEXT("Added to Next: Bib %d from Group %d"),
NextParticipant.Bib, GroupIndex);
}
}
// === LOGS DE RÉSUMÉ ===
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ==="));
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus: %d participants"), OutPursuitFocusNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next: %d participants"), OutPursuitNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Remaining groups for future: %d"), GroupedPursuit.Num());
if (OutPursuitFocusNext.Num() > 0)
{
DebugFocusNext(OutPursuitFocusNext);
}
// Log détaillé des Next (limité pour éviter spam)
if (OutPursuitNext.Num() > 0)
{
DebugOutPoursuitNext(OutPursuitNext);
}
// Vérifier si la séquence est terminée
if (GroupedPursuit.IsEmpty())
{
bIsSequenceDone = true;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Pursuit sequence will be completed after this round"));
} }
} }
@ -119,7 +194,7 @@ bool UDTFluxPursuitManager::BindRankings()
{ {
if (!bIsRankingBounded) if (!bIsRankingBounded)
{ {
CoreSubsystem->OnRequestedStageRankings.AddDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived); CoreSubsystem->OnStageRankings.AddDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
bIsRankingBounded = true; bIsRankingBounded = true;
} }
return bIsRankingBounded; return bIsRankingBounded;
@ -134,7 +209,7 @@ void UDTFluxPursuitManager::UnbindRankings()
{ {
if (bIsRankingBounded) if (bIsRankingBounded)
{ {
CoreSubsystem->OnRequestedStageRankings.RemoveDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived); CoreSubsystem->OnStageRankings.RemoveDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
bIsRankingBounded = false; bIsRankingBounded = false;
return; return;
} }
@ -178,18 +253,30 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
AllPursuits.Add(PursuitInfo); AllPursuits.Add(PursuitInfo);
} }
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("AllPursuits.Num() = %i"), AllPursuits.Num());
for (auto& Pursuit : AllPursuits) for (auto& Pursuit : AllPursuits)
{ {
if (TempGroups.Contains(Pursuit.StartTime)) if (TempGroups.Contains(Pursuit.StartTime))
{ {
TempGroups[Pursuit.StartTime].PursuitGroup.Add(Pursuit); FDTFluxPursuitGroup& Group = TempGroups[Pursuit.StartTime];
Group.PursuitGroup.Add(Pursuit);
UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("Adding [%i] To PursuitGroup starting At %s, PursuitGroup.Num() %i"),
Pursuit.Bib, *Pursuit.StartTime.ToString(), Group.PursuitGroup.Num());
} }
else else
{ {
FDTFluxPursuitGroup Group; FDTFluxPursuitGroup NewGroup;
Group.StartTimeGlobal = Pursuit.StartTime; UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("New Group starting At %s, Adding Bib [%i]"),
Group.PursuitGroup.Add(Pursuit); *Pursuit.StartTime.ToString(), Pursuit.Bib);
TempGroups.Add(Pursuit.StartTime, Group); NewGroup.StartTimeGlobal = Pursuit.StartTime;
NewGroup.PursuitGroup.Add(Pursuit);
TempGroups.Add(Pursuit.StartTime, NewGroup);
for (const auto& Group : TempGroups)
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Group.StartTime = %s, Group.PursuitGroup.Num() = %i"),
*Group.Key.ToString(), Group.Value.PursuitGroup.Num());
}
} }
} }
TempGroups.KeySort([](const FDateTime& A, const FDateTime& B) TempGroups.KeySort([](const FDateTime& A, const FDateTime& B)
@ -199,18 +286,18 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
TMap<FDateTime, int> StartTimeFrequency; TMap<FDateTime, int> StartTimeFrequency;
int32 MaxFrequency = 0; int32 MaxFrequency = 0;
GroupedPursuit.Reserve(TempGroups.Num()); GroupedPursuit.Reserve(TempGroups.Num());
// parcours du TMap
for (const auto& Pair : TempGroups) for (const auto& Pair : TempGroups)
{ {
if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue()) if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue())
{ {
StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal)++; // récuperation de la ref de la valeur actuel de la fréquence dans la TMap Freq
const FDateTime& PropertyValue = Pair.Value.StartTimeGlobal; // Votre propriété int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0);
int32& Count = StartTimeFrequency.FindOrAdd(PropertyValue, 0); CurrentFreq = Pair.Value.PursuitGroup.Num();
Count++; if (CurrentFreq > MaxFrequency)
if (Count > MaxFrequency)
{ {
MaxFrequency = Count; MaxFrequency = CurrentFreq;
MassStartTime = PropertyValue; MassStartTime = Pair.Value.StartTimeGlobal;
} }
} }
GroupedPursuit.Add(Pair.Value); GroupedPursuit.Add(Pair.Value);
@ -226,7 +313,7 @@ bool UDTFluxPursuitManager::LaunchPursuitSequence()
bool bIsFocusTruncate = false; bool bIsFocusTruncate = false;
GetPursuit(FocusPursuits, NextPursuits, bIsFocusTruncate); GetPursuit(FocusPursuits, NextPursuits, bIsFocusTruncate);
FPursuitStaterData PursuitData = FPursuitStaterData(FocusPursuits, NextPursuits, MassStartTime, bIsFocusTruncate); FPursuitStarterData PursuitData = FPursuitStarterData(FocusPursuits, NextPursuits, MassStartTime, bIsFocusTruncate);
CoreSubsystem->OnPursuitSequenceReady.Broadcast(PursuitData); OnPursuitSequenceReady.Broadcast(PursuitData);
return true; return true;
} }

View File

@ -17,34 +17,6 @@ class UDTFluxModelAsset;
class UDTFluxPursuitManager; class UDTFluxPursuitManager;
struct FDTFluxServerResponse; struct FDTFluxServerResponse;
USTRUCT(BlueprintType)
struct FPursuitStaterData
{
GENERATED_BODY()
public:
FPursuitStaterData() = default;
FPursuitStaterData(const TArray<FDTFluxPursuitInfo>& InPursuitFocusNext,
const TArray<FDTFluxPursuitInfo>& InPursuitNext, const FDateTime& InMassStartTime,
const bool InIsFocusTruncate)
: PursuitFocusNext(InPursuitFocusNext), PursuitNext(InPursuitNext), MassStartTime(InMassStartTime),
bIsFocusTruncate(InIsFocusTruncate)
{
};
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
TArray<FDTFluxPursuitInfo> PursuitFocusNext = TArray<FDTFluxPursuitInfo>();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
TArray<FDTFluxPursuitInfo> PursuitNext = TArray<FDTFluxPursuitInfo>();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
FDateTime MassStartTime = FDateTime::MinValue();
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
bool bIsFocusTruncate = false;
};
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStaterData, PursuitInfoSequenceItem);
/** /**
* *
@ -55,18 +27,24 @@ class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
GENERATED_BODY() GENERATED_BODY()
public: public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitRankings, FDTFluxSplitRankings, SplitRankings); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDTFluxSplitKey, SplitKey, FDTFluxSplitRankings,
SplitRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnSplitRankings OnSplitRankings; FOnSplitRankings OnSplitRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStageRankings, FDTFluxStageRankings, StageRankings);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDTFluxStageKey, StageKey, FDTFluxStageRankings,
StageRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnStageRankings OnStageRankings; FOnStageRankings OnStageRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnContestRankings, FDTFluxContestRankings, ContestRankings);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankings, const int, ContestId, FDTFluxContestRankings,
ContestRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnContestRankings OnContestRankings; FOnContestRankings OnContestRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
@ -76,14 +54,8 @@ public:
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamStatusUpdate OnTeamStatusUpdate; FOnTeamStatusUpdate OnTeamStatusUpdate;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnRequestedStageRankings, const FDTFluxStageKey, StageKey, UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
const FDTFluxStageRankings, StageRankings); UDTFluxPursuitManager* PursuitManager = nullptr;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnRequestedStageRankings OnRequestedStageRankings;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings); bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
@ -104,16 +76,14 @@ public:
const bool bShouldUseCached = true); const bool bShouldUseCached = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestContestRankings(const TArray<int> ForContests); TArray<FGuid> TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages); TArray<FGuid> TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages, bool bEnableCache = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
TArray<FGuid> TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits); TArray<FGuid> TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits, bool bEnableCache = true);
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
UDTFluxPursuitManager* PursuitManager = nullptr;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
const FDTFluxParticipant GetParticipant(int InBib); const FDTFluxParticipant GetParticipant(int InBib);

View File

@ -10,44 +10,31 @@
class UDTFluxCoreSubsystem; class UDTFluxCoreSubsystem;
USTRUCT()
struct FRequestData USTRUCT(BlueprintType)
struct FPursuitStarterData
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() public:
TArray<FGuid> RequestIds; FPursuitStarterData() = default;
UPROPERTY()
TMap<FGuid, FDTFluxStageRankings> StageRankings;
UPROPERTY()
int ContestId;
UPROPERTY() FPursuitStarterData(const TArray<FDTFluxPursuitInfo>& InPursuitFocusNext,
bool bIsReady = false; const TArray<FDTFluxPursuitInfo>& InPursuitNext, const FDateTime& InMassStartTime,
const bool InIsFocusTruncate)
: PursuitFocusNext(InPursuitFocusNext), PursuitNext(InPursuitNext), MassStartTime(InMassStartTime),
FRequestData() = default; bIsFocusTruncate(InIsFocusTruncate)
FRequestData(const TArray<FGuid>& InRequestIds, const TMap<FGuid, FDTFluxStageRankings>& InStageRankings)
: RequestIds(InRequestIds), StageRankings(InStageRankings), ContestId(-1)
{ {
}; };
/** UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
* TArray<FDTFluxPursuitInfo> PursuitFocusNext = TArray<FDTFluxPursuitInfo>();
* @param RequestId UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
* @param InRankings TArray<FDTFluxPursuitInfo> PursuitNext = TArray<FDTFluxPursuitInfo>();
* @return True if all needed requests have responses UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
*/ FDateTime MassStartTime = FDateTime::MinValue();
bool IsWaitingFor(const FGuid& RequestId, const FDTFluxStageRankings& InRankings) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
{ bool bIsFocusTruncate = false;
if (!StageRankings.Contains(RequestId))
{
StageRankings.Add(RequestId, InRankings);
}
bIsReady = StageRankings.Num() <= RequestIds.Num();
return bIsReady;
}
}; };
USTRUCT() USTRUCT()
@ -55,6 +42,7 @@ struct FDTFluxPursuitGroup
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() UPROPERTY()
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>(); TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
UPROPERTY() UPROPERTY()
@ -65,7 +53,6 @@ struct FDTFluxPursuitGroup
bool bIsFocus = false; bool bIsFocus = false;
}; };
/** /**
* *
*/ */
@ -78,14 +65,18 @@ public:
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer); UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStarterData, PursuitData);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
bool bFocusIsTruncate = false; bool bFocusIsTruncate = false;
UPROPERTY() UPROPERTY()
int PursuitMaxSimultaneousPursuit = 7; int PursuitMaxSimultaneousPursuit = 7;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit", UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit")
meta=(ClampMin="1", ClampMax="60", UIMin="0", UIMax="60"))
int MassStartDelay = 10; int MassStartDelay = 10;
UPROPERTY() UPROPERTY()
@ -117,6 +108,10 @@ public:
UFUNCTION() UFUNCTION()
void OnRankingsReceived(const FDTFluxStageKey NewStageKey, const FDTFluxStageRankings NewStageRankings); void OnRankingsReceived(const FDTFluxStageKey NewStageKey, const FDTFluxStageRankings NewStageRankings);
void DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext);
void DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext);
private: private:
TMap<FDTFluxStageKey, bool> PendingStageRanking; TMap<FDTFluxStageKey, bool> PendingStageRanking;
TArray<FDTFluxStageRankings> AllRankings; TArray<FDTFluxStageRankings> AllRankings;

View File

@ -23,11 +23,6 @@ bool FDTFluxTrackedRequest::CanRetry() const
(State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut); (State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut);
} }
bool FDTFluxTrackedRequest::IsCacheValid() const
{
if (State != EDTFluxRequestState::Cached) return false;
return (FDateTime::Now() - CompletedAt).GetTotalSeconds() < Config.CacheValiditySeconds;
}
float FDTFluxTrackedRequest::GetRetryDelay() const float FDTFluxTrackedRequest::GetRetryDelay() const
{ {
@ -40,11 +35,6 @@ bool FDTFluxTrackedRequest::Matches(EDTFluxApiDataType InType, int32 InContestId
return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId; return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId;
} }
FString FDTFluxTrackedRequest::GetCacheKey() const
{
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
}
void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData) void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData)
{ {
@ -118,8 +108,8 @@ void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefau
DefaultConfig = InDefaultConfig; DefaultConfig = InDefaultConfig;
bIsInitialized.store(true); bIsInitialized.store(true);
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs, cache=%.1fs"), UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs"),
DefaultConfig.TimeoutSeconds, DefaultConfig.CacheValiditySeconds); DefaultConfig.TimeoutSeconds);
} }
void FDTFluxQueuedRequestManager::Shutdown() void FDTFluxQueuedRequestManager::Shutdown()
@ -134,7 +124,6 @@ void FDTFluxQueuedRequestManager::Shutdown()
FScopeLock CallbacksLock_Local(&CallbacksLock); FScopeLock CallbacksLock_Local(&CallbacksLock);
AllRequests.Empty(); AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty(); SuccessCallbacks.Empty();
ErrorCallbacks.Empty(); ErrorCallbacks.Empty();
} }
@ -154,37 +143,7 @@ FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized")); UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized"));
return FGuid(); return FGuid();
} }
// Create new request
// Vérifier le cache d'abord
FString CachedResponse;
if (CustomConfig.bEnableCache && GetFromCache(RequestType, CachedResponse, ContestId, StageId, SplitId))
{
UE_LOG(logDTFluxNetwork, Log, TEXT("Request served from cache: Type=%s"),
*UEnum::GetValueAsString(RequestType));
// Créer une "fausse" requête pour représenter le hit cache
auto CachedRequest = MakeShared<FDTFluxTrackedRequest>();
CachedRequest->RequestType = RequestType;
CachedRequest->ContestId = ContestId;
CachedRequest->StageId = StageId;
CachedRequest->SplitId = SplitId;
CachedRequest->Config = CustomConfig.bEnableCache ? CustomConfig : DefaultConfig;
CachedRequest->State = EDTFluxRequestState::Cached;
CachedRequest->RawResponseData = CachedResponse;
CachedRequest->CompletedAt = FDateTime::Now();
FGuid CacheRequestId = CachedRequest->RequestId;
{
FScopeLock Lock(&RequestsLock);
AllRequests.Add(CacheRequestId, CachedRequest);
}
RecordCacheHit();
return CacheRequestId;
}
// Créer une nouvelle requête
auto NewRequest = MakeShared<FDTFluxTrackedRequest>(); auto NewRequest = MakeShared<FDTFluxTrackedRequest>();
NewRequest->RequestType = RequestType; NewRequest->RequestType = RequestType;
NewRequest->ContestId = ContestId; NewRequest->ContestId = ContestId;
@ -199,12 +158,8 @@ FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
AllRequests.Add(RequestId, NewRequest); AllRequests.Add(RequestId, NewRequest);
TotalRequests++; TotalRequests++;
} }
RecordCacheMiss();
UE_LOG(logDTFluxNetwork, Log, TEXT("Created tracked request %s: Type=%s, Contest=%d, Stage=%d, Split=%d"), UE_LOG(logDTFluxNetwork, Log, TEXT("Created tracked request %s: Type=%s, Contest=%d, Stage=%d, Split=%d"),
*RequestId.ToString(), *UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId); *RequestId.ToString(), *UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
return RequestId; return RequestId;
} }
@ -257,7 +212,6 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
{ {
UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::CompleteRequest() %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::CompleteRequest() %s"), *RequestId.ToString());
TSharedPtr<FDTFluxTrackedRequest> Request; TSharedPtr<FDTFluxTrackedRequest> Request;
{ {
FScopeLock Lock(&RequestsLock); FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId)) if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
@ -265,19 +219,18 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
Request = *RequestPtr; Request = *RequestPtr;
} }
} }
if (!Request.IsValid()) if (!Request.IsValid())
{ {
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString());
return false; return false;
} }
// Stocker la réponse brute // Store RawResponse
Request->SetRawResponse(RawResponseData); Request->SetRawResponse(RawResponseData);
Request->CompletedAt = FDateTime::Now(); Request->CompletedAt = FDateTime::Now();
UE_LOG(logDTFluxNetwork, Log, TEXT("Request %s completed at %s"), *RequestId.ToString(), UE_LOG(logDTFluxNetwork, Log, TEXT("Request %s completed at %s"), *RequestId.ToString(),
*Request->CompletedAt.ToString()); *Request->CompletedAt.ToString());
// Décider du parsing selon les callbacks et la configuration // Decide to parse based upon config
bool bHasCallbacks = false; bool bHasCallbacks = false;
{ {
FScopeLock Lock(&CallbacksLock); FScopeLock Lock(&CallbacksLock);
@ -292,7 +245,7 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
bHasCallbacks ? TEXT("true") : TEXT("false"), bUseAsyncParsing ? TEXT("true") : TEXT("false"), bHasCallbacks ? TEXT("true") : TEXT("false"), bUseAsyncParsing ? TEXT("true") : TEXT("false"),
RawResponseData.IsEmpty() ? TEXT("true") : TEXT("false")); RawResponseData.IsEmpty() ? TEXT("true") : TEXT("false"));
// Parsing asynchrone pour les callbacks // Async parsing for Cb
FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw( FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingCompleted this, &FDTFluxQueuedRequestManager::OnParsingCompleted
); );
@ -300,9 +253,8 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw( FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw(
this, &FDTFluxQueuedRequestManager::OnParsingFailed this, &FDTFluxQueuedRequestManager::OnParsingFailed
); );
// Maybe send to parser in another place
AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed); AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed);
// CleanupCallbacks(RequestId);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString());
return true; return true;
} }
@ -310,26 +262,21 @@ bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const
{ {
UE_LOG(logDTFluxNetwork, Warning, TEXT("request %s completed without sync"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Warning, TEXT("request %s completed without sync"), *RequestId.ToString());
// Compléter immédiatement sans parsing ou avec parsing sync // Compléter immédiatement sans parsing ou avec parsing sync
EDTFluxRequestState NewState = Request->Config.bEnableCache EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
? EDTFluxRequestState::Cached
: EDTFluxRequestState::Completed;
ChangeRequestState(Request, NewState); ChangeRequestState(Request, NewState);
if (Request->Config.bEnableCache)
{
FScopeLock Lock(&RequestsLock);
CacheKeyToRequestId.Add(Request->GetCacheKey(), RequestId);
}
// Déclencher les callbacks avec les données brutes // Déclencher les callbacks avec les données brutes
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
return true; return true;
} }
} }
/**
* @todo Check protocol errors ???
* @param RequestId
* @param ErrorMessage
* @return
*/
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage) bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
{ {
TSharedPtr<FDTFluxTrackedRequest> Request; TSharedPtr<FDTFluxTrackedRequest> Request;
@ -350,7 +297,6 @@ bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FStr
Request->LastErrorMessage = ErrorMessage; Request->LastErrorMessage = ErrorMessage;
ChangeRequestState(Request, EDTFluxRequestState::Failed); ChangeRequestState(Request, EDTFluxRequestState::Failed);
// Déclencher les callbacks d'erreur
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
@ -407,76 +353,6 @@ bool FDTFluxQueuedRequestManager::FindPendingRequest(
return false; return false;
} }
bool FDTFluxQueuedRequestManager::GetFromCache(
EDTFluxApiDataType RequestType,
FString& OutRawResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid() && !CachedRequest->RawResponseData.IsEmpty())
{
OutRawResponse = CachedRequest->RawResponseData;
return true;
}
}
}
return false;
}
bool FDTFluxQueuedRequestManager::GetParsedFromCache(
EDTFluxApiDataType RequestType,
TSharedPtr<FDTFluxServerResponse>& OutResponse,
int32 ContestId,
int32 StageId,
int32 SplitId) const
{
FString CacheKey = GenerateCacheKey(RequestType, ContestId, StageId, SplitId);
FScopeLock Lock(&RequestsLock);
if (const FGuid* RequestId = CacheKeyToRequestId.Find(CacheKey))
{
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(*RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& CachedRequest = *RequestPtr;
if (CachedRequest->IsCacheValid())
{
// Parsing lazy si nécessaire
if (!CachedRequest->ParsedResponse.IsSet() && !CachedRequest->RawResponseData.IsEmpty())
{
OutResponse = AsyncParser->ParseResponseSync(CachedRequest->RawResponseData, 1.0f);
if (OutResponse.IsValid())
{
CachedRequest->ParsedResponse = OutResponse;
const_cast<FDTFluxTrackedRequest*>(CachedRequest.Get())->bIsResponseParsed = true;
}
}
else if (CachedRequest->ParsedResponse.IsSet())
{
OutResponse = CachedRequest->ParsedResponse.GetValue();
}
return OutResponse.IsValid();
}
}
}
return false;
}
// === ACCESSEURS === // === ACCESSEURS ===
bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const
@ -554,9 +430,6 @@ FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::Get
case EDTFluxRequestState::Retrying: case EDTFluxRequestState::Retrying:
Stats.Pending++; Stats.Pending++;
break; break;
case EDTFluxRequestState::Cached:
Stats.Cached++;
break;
case EDTFluxRequestState::Completed: case EDTFluxRequestState::Completed:
Stats.Completed++; Stats.Completed++;
break; break;
@ -568,46 +441,10 @@ FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::Get
} }
Stats.TotalRequests = TotalRequests; Stats.TotalRequests = TotalRequests;
Stats.CacheHits = CacheHits;
Stats.CacheMisses = CacheMisses;
if (Stats.TotalRequests > 0)
{
Stats.HitRate = ((float)Stats.CacheHits / (float)Stats.TotalRequests) * 100.0f;
}
return Stats; return Stats;
} }
// === NETTOYAGE === // === NETTOYAGE ===
int32 FDTFluxQueuedRequestManager::CleanupExpiredCache()
{
FScopeLock Lock(&RequestsLock);
TArray<FGuid> ExpiredRequests;
for (const auto& [RequestId, Request] : AllRequests)
{
if (Request->State == EDTFluxRequestState::Cached && !Request->IsCacheValid())
{
ExpiredRequests.Add(RequestId);
}
}
for (const FGuid& RequestId : ExpiredRequests)
{
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{
const TSharedPtr<FDTFluxTrackedRequest>& Request = *RequestPtr;
CacheKeyToRequestId.Remove(Request->GetCacheKey());
AllRequests.Remove(RequestId);
}
}
return ExpiredRequests.Num();
}
int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds) int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds)
{ {
FScopeLock Lock(&RequestsLock); FScopeLock Lock(&RequestsLock);
@ -638,7 +475,6 @@ void FDTFluxQueuedRequestManager::ClearAllRequests()
FScopeLock CallbacksLock_Local(&CallbacksLock); FScopeLock CallbacksLock_Local(&CallbacksLock);
AllRequests.Empty(); AllRequests.Empty();
CacheKeyToRequestId.Empty();
SuccessCallbacks.Empty(); SuccessCallbacks.Empty();
ErrorCallbacks.Empty(); ErrorCallbacks.Empty();
@ -652,7 +488,6 @@ void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
// Mise à jour des timers // Mise à jour des timers
TimeSinceLastTimeoutCheck += DeltaTime; TimeSinceLastTimeoutCheck += DeltaTime;
TimeSinceLastCacheCleanup += DeltaTime;
TimeSinceLastRetryCheck += DeltaTime; TimeSinceLastRetryCheck += DeltaTime;
// Vérifier les timeouts // Vérifier les timeouts
@ -668,13 +503,6 @@ void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
ProcessRetries(); ProcessRetries();
TimeSinceLastRetryCheck = 0.0f; TimeSinceLastRetryCheck = 0.0f;
} }
// Nettoyage du cache
if (TimeSinceLastCacheCleanup >= CacheCleanupInterval)
{
ProcessCacheCleanup();
TimeSinceLastCacheCleanup = 0.0f;
}
} }
void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request, void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request,
@ -756,17 +584,11 @@ void FDTFluxQueuedRequestManager::ProcessRetries()
} }
} }
void FDTFluxQueuedRequestManager::ProcessCacheCleanup()
{
CleanupExpiredCache();
CleanupCompletedRequests(600.0f);
}
void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request) void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
{ {
FScopeLock Lock(&CallbacksLock); FScopeLock Lock(&CallbacksLock);
if (Request.State == EDTFluxRequestState::Completed || Request.State == EDTFluxRequestState::Cached) if (Request.State == EDTFluxRequestState::Completed)
{ {
// Success Cb // Success Cb
const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId); const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId);
@ -793,18 +615,6 @@ void FDTFluxQueuedRequestManager::CleanupCallbacks(const FGuid& RequestId)
ErrorCallbacks.Remove(RequestId); ErrorCallbacks.Remove(RequestId);
} }
void FDTFluxQueuedRequestManager::RecordCacheHit() const
{
FScopeLock Lock(&MetricsLock);
CacheHits++;
}
void FDTFluxQueuedRequestManager::RecordCacheMiss() const
{
FScopeLock Lock(&MetricsLock);
CacheMisses++;
}
void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId, void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess) TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess)
{ {
@ -833,16 +643,9 @@ void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
{ {
Request->ParsedResponse = ParsedResponse; Request->ParsedResponse = ParsedResponse;
Request->bIsResponseParsed = true; Request->bIsResponseParsed = true;
EDTFluxRequestState NewState = Request->Config.bEnableCache EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
? EDTFluxRequestState::Cached
: EDTFluxRequestState::Completed;
ChangeRequestState(Request, NewState); ChangeRequestState(Request, NewState);
if (Request->Config.bEnableCache)
{
FScopeLock Lock(&RequestsLock);
CacheKeyToRequestId.Add(Request->GetCacheKey(), RequestId);
}
UE_LOG(logDTFluxNetwork, Log, UE_LOG(logDTFluxNetwork, Log,
TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"), TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"),
*RequestId.ToString()); *RequestId.ToString());
@ -854,7 +657,6 @@ void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
UE_LOG(logDTFluxNetwork, Error, TEXT("Async parsing failed for request %s"), *RequestId.ToString()); UE_LOG(logDTFluxNetwork, Error, TEXT("Async parsing failed for request %s"), *RequestId.ToString());
} }
// ✅ FIX: Déclencher les callbacks maintenant !
TriggerCallbacks(*Request); TriggerCallbacks(*Request);
CleanupCallbacks(RequestId); CleanupCallbacks(RequestId);
} }
@ -866,13 +668,3 @@ void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const
*ErrorMessage); *ErrorMessage);
FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage)); FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage));
} }
FString FDTFluxQueuedRequestManager::GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
int32 SplitId)
{
return FString::Printf(TEXT("%s_%d_%d_%d"),
*UEnum::GetValueAsString(RequestType),
ContestId,
StageId,
SplitId);
}

View File

@ -36,8 +36,6 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
DefaultConfig.TimeoutSeconds = 5.0f; DefaultConfig.TimeoutSeconds = 5.0f;
DefaultConfig.MaxRetries = 3; DefaultConfig.MaxRetries = 3;
DefaultConfig.RetryBackoffMultiplier = 1.5f; DefaultConfig.RetryBackoffMultiplier = 1.5f;
DefaultConfig.bEnableCache = true;
DefaultConfig.CacheValiditySeconds = 60.0f;
RequestManager->Initialize(DefaultConfig); RequestManager->Initialize(DefaultConfig);
@ -143,8 +141,6 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
FDTFluxRequestConfig CustomConfig; FDTFluxRequestConfig CustomConfig;
CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.TimeoutSeconds = TimeoutSeconds;
CustomConfig.MaxRetries = MaxRetries; CustomConfig.MaxRetries = MaxRetries;
CustomConfig.bEnableCache = bEnableCache;
CustomConfig.CacheValiditySeconds = 60.0f;
CustomConfig.RetryBackoffMultiplier = 1.5f; CustomConfig.RetryBackoffMultiplier = 1.5f;
FGuid RequestId = RequestManager->CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig); FGuid RequestId = RequestManager->CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig);
@ -154,12 +150,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
// Récupérer la requête pour l'envoyer // Récupérer la requête pour l'envoyer
if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId))
{ {
// Si la requête est déjà en cache, pas besoin d'envoyer RequestManager->MarkRequestAsSent(RequestId);
if (Request->State != EDTFluxRequestState::Cached) SendQueuedRequest(*Request);
{
RequestManager->MarkRequestAsSent(RequestId);
SendQueuedRequest(*Request);
}
} }
} }
@ -174,8 +166,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
FOnDTFluxRequestSuccess& OnSuccess, FOnDTFluxRequestSuccess& OnSuccess,
FOnDTFluxRequestError& OnError, FOnDTFluxRequestError& OnError,
float TimeoutSeconds, float TimeoutSeconds,
int32 MaxRetries, int32 MaxRetries
bool bEnableCache) )
{ {
if (!RequestManager.IsValid()) if (!RequestManager.IsValid())
{ {
@ -186,8 +178,6 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
FDTFluxRequestConfig CustomConfig; FDTFluxRequestConfig CustomConfig;
CustomConfig.TimeoutSeconds = TimeoutSeconds; CustomConfig.TimeoutSeconds = TimeoutSeconds;
CustomConfig.MaxRetries = MaxRetries; CustomConfig.MaxRetries = MaxRetries;
CustomConfig.bEnableCache = bEnableCache;
CustomConfig.CacheValiditySeconds = 60.0f;
CustomConfig.RetryBackoffMultiplier = 1.5f; CustomConfig.RetryBackoffMultiplier = 1.5f;
FGuid RequestId = RequestManager->CreateTrackedRequestWithCallbacks( FGuid RequestId = RequestManager->CreateTrackedRequestWithCallbacks(
@ -197,11 +187,8 @@ FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallbacks(
{ {
if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId)) if (const FDTFluxTrackedRequest* Request = RequestManager->GetRequestPtr(RequestId))
{ {
if (Request->State != EDTFluxRequestState::Cached) RequestManager->MarkRequestAsSent(RequestId);
{ SendQueuedRequest(*Request);
RequestManager->MarkRequestAsSent(RequestId);
SendQueuedRequest(*Request);
}
} }
} }
@ -227,7 +214,6 @@ bool UDTFluxNetworkSubsystem::HasRequestReceivedResponse(const FGuid& RequestId)
if (GetTrackedRequest(RequestId, Request)) if (GetTrackedRequest(RequestId, Request))
{ {
return Request.State == EDTFluxRequestState::Completed || return Request.State == EDTFluxRequestState::Completed ||
Request.State == EDTFluxRequestState::Cached ||
!Request.RawResponseData.IsEmpty(); !Request.RawResponseData.IsEmpty();
} }
return false; return false;
@ -264,22 +250,19 @@ int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const
return 0; return 0;
} }
void UDTFluxNetworkSubsystem::GetRequestStatistics(int32& OutPending, int32& OutCached, int32& OutCompleted, void UDTFluxNetworkSubsystem::GetRequestStatistics(int32& OutPending, int32& OutCompleted,
int32& OutFailed, float& OutHitRate) const int32& OutFailed) const
{ {
if (RequestManager.IsValid()) if (RequestManager.IsValid())
{ {
FDTFluxQueuedRequestManager::FRequestStatistics Stats = RequestManager->GetStatistics(); FDTFluxQueuedRequestManager::FRequestStatistics Stats = RequestManager->GetStatistics();
OutPending = Stats.Pending; OutPending = Stats.Pending;
OutCached = Stats.Cached;
OutCompleted = Stats.Completed; OutCompleted = Stats.Completed;
OutFailed = Stats.Failed; OutFailed = Stats.Failed;
OutHitRate = Stats.HitRate;
} }
else else
{ {
OutPending = OutCached = OutCompleted = OutFailed = 0; OutPending = OutCompleted = OutFailed = 0;
OutHitRate = 0.0f;
} }
} }
@ -362,7 +345,7 @@ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
&UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem); &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
} }
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() const
{ {
if (!WsClient.IsValid()) return; if (!WsClient.IsValid()) return;
@ -794,11 +777,10 @@ void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxTrackedRequest& Que
bool UDTFluxNetworkSubsystem::ShouldUseAsyncParsing(const FString& JsonData) const bool UDTFluxNetworkSubsystem::ShouldUseAsyncParsing(const FString& JsonData) const
{ {
// Critères pour décider du parsing asynchrone : // Critère pour décider du parsing asynchrone :
// - Taille des données (> 1KB par défaut) // - Taille des données (> 1KB par défaut)
// - Charge actuelle du système // Pour le moment uniquement taille
// - Type de données (certains types sont plus complexes à parser)
const int32 AsyncThreshold = 1024; // 1KB constexpr int32 AsyncThreshold = 1024; // 1KB
return JsonData.Len() > AsyncThreshold; return JsonData.Len() > AsyncThreshold;
} }

View File

@ -26,7 +26,6 @@ enum class EDTFluxRequestState : uint8
Completed UMETA(DisplayName = "Completed"), Completed UMETA(DisplayName = "Completed"),
Failed UMETA(DisplayName = "Failed"), Failed UMETA(DisplayName = "Failed"),
TimedOut UMETA(DisplayName = "TimedOut"), TimedOut UMETA(DisplayName = "TimedOut"),
Cached UMETA(DisplayName = "Cached"),
Retrying UMETA(DisplayName = "Retrying") Retrying UMETA(DisplayName = "Retrying")
}; };
@ -43,12 +42,6 @@ struct DTFLUXNETWORK_API FDTFluxRequestConfig
UPROPERTY(EditAnywhere, BlueprintReadWrite) UPROPERTY(EditAnywhere, BlueprintReadWrite)
float RetryBackoffMultiplier = 1.5f; float RetryBackoffMultiplier = 1.5f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bEnableCache = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CacheValiditySeconds = 60.0f;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
@ -112,10 +105,8 @@ struct DTFLUXNETWORK_API FDTFluxTrackedRequest
bool HasTimedOut() const; bool HasTimedOut() const;
bool CanRetry() const; bool CanRetry() const;
bool IsCacheValid() const;
float GetRetryDelay() const; float GetRetryDelay() const;
bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const; bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const;
FString GetCacheKey() const;
void SetRawResponse(const FString& RawData); void SetRawResponse(const FString& RawData);
FString Serialize() const; FString Serialize() const;
}; };
@ -137,7 +128,7 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestFailedNative, const FDTFluxTracked
// ================================================================================================ // ================================================================================================
/** /**
* Gestionnaire de requêtes trackées avec cache, timeout, retry et parsing asynchrone * Gestionnaire de requêtes trackées timeout, retry et parsing asynchrone
* Implémentation C++ pure avec SmartPointers pour des performances optimales * Implémentation C++ pure avec SmartPointers pour des performances optimales
*/ */
class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject
@ -229,7 +220,7 @@ public:
*/ */
bool RetryRequest(const FGuid& RequestId); bool RetryRequest(const FGuid& RequestId);
// === RECHERCHE ET CACHE === // === RECHERCHE ===
/** /**
* Chercher une requête en attente correspondant aux critères * Chercher une requête en attente correspondant aux critères
@ -242,28 +233,6 @@ public:
int32 SplitId = -1 int32 SplitId = -1
) const; ) const;
/**
* Récupérer une réponse depuis le cache (données brutes)
*/
bool GetFromCache(
EDTFluxApiDataType RequestType,
FString& OutRawResponse,
int32 ContestId = -1,
int32 StageId = -1,
int32 SplitId = -1
) const;
/**
* Récupérer une réponse parsée depuis le cache
*/
bool GetParsedFromCache(
EDTFluxApiDataType RequestType,
TSharedPtr<FDTFluxServerResponse>& OutResponse,
int32 ContestId = -1,
int32 StageId = -1,
int32 SplitId = -1
) const;
// === ACCESSEURS === // === ACCESSEURS ===
/** /**
@ -294,25 +263,15 @@ public:
struct FRequestStatistics struct FRequestStatistics
{ {
int32 Pending = 0; int32 Pending = 0;
int32 Cached = 0;
int32 Completed = 0; int32 Completed = 0;
int32 Failed = 0; int32 Failed = 0;
int32 TotalRequests = 0; int32 TotalRequests = 0;
int32 CacheHits = 0;
int32 CacheMisses = 0;
float HitRate = 0.0f;
}; };
FRequestStatistics GetStatistics() const; FRequestStatistics GetStatistics() const;
// === NETTOYAGE === // === NETTOYAGE ===
/**
* Nettoyer les entrées de cache expirées
* @return Nombre d'entrées supprimées
*/
int32 CleanupExpiredCache();
/** /**
* Nettoyer les requêtes terminées anciennes * Nettoyer les requêtes terminées anciennes
* @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai * @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai
@ -321,7 +280,7 @@ public:
int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f); int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f);
/** /**
* Vider toutes les requêtes et le cache * Vider toutes les requêtes
*/ */
void ClearAllRequests(); void ClearAllRequests();
@ -353,17 +312,14 @@ private:
// === TIMING POUR LE TICK === // === TIMING POUR LE TICK ===
float TimeSinceLastTimeoutCheck = 0.0f; float TimeSinceLastTimeoutCheck = 0.0f;
float TimeSinceLastCacheCleanup = 0.0f;
float TimeSinceLastRetryCheck = 0.0f; float TimeSinceLastRetryCheck = 0.0f;
static constexpr float TimeoutCheckInterval = 1.0f; static constexpr float TimeoutCheckInterval = 1.0f;
static constexpr float CacheCleanupInterval = 30.0f;
static constexpr float RetryCheckInterval = 0.5f; static constexpr float RetryCheckInterval = 0.5f;
// === STOCKAGE THREAD-SAFE === // === STOCKAGE THREAD-SAFE ===
mutable FCriticalSection RequestsLock; mutable FCriticalSection RequestsLock;
TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests; TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests;
TMap<FString, FGuid> CacheKeyToRequestId;
// === CALLBACKS C++ === // === CALLBACKS C++ ===
mutable FCriticalSection CallbacksLock; mutable FCriticalSection CallbacksLock;
@ -373,8 +329,6 @@ private:
// === MÉTRIQUES === // === MÉTRIQUES ===
mutable FCriticalSection MetricsLock; mutable FCriticalSection MetricsLock;
mutable int32 TotalRequests = 0; mutable int32 TotalRequests = 0;
mutable int32 CacheHits = 0;
mutable int32 CacheMisses = 0;
// === PARSER ASYNCHRONE === // === PARSER ASYNCHRONE ===
@ -397,11 +351,6 @@ private:
*/ */
void ProcessRetries(); void ProcessRetries();
/**
* Nettoyer le cache périodiquement
*/
void ProcessCacheCleanup();
/** /**
* Déclencher les callbacks pour une requête * Déclencher les callbacks pour une requête
*/ */
@ -412,17 +361,6 @@ private:
*/ */
void CleanupCallbacks(const FGuid& RequestId); void CleanupCallbacks(const FGuid& RequestId);
/**
* Enregistrer un hit cache dans les métriques
*/
void RecordCacheHit() const;
/**
* Enregistrer un miss cache dans les métriques
*/
void RecordCacheMiss() const;
// === CALLBACKS POUR LE PARSING ASYNCHRONE === // === CALLBACKS POUR LE PARSING ASYNCHRONE ===
/** /**
@ -434,11 +372,4 @@ private:
* Callback appelé quand le parsing asynchrone échoue * Callback appelé quand le parsing asynchrone échoue
*/ */
void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage); void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage);
// === UTILITAIRES STATIQUES ===
/**
* Générer une clé de cache unique pour une requête
*/
static FString GenerateCacheKey(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId);
}; };

View File

@ -125,8 +125,7 @@ public:
FOnDTFluxRequestSuccess& OnSuccess, FOnDTFluxRequestSuccess& OnSuccess,
FOnDTFluxRequestError& OnError, FOnDTFluxRequestError& OnError,
float TimeoutSeconds = 5.0f, float TimeoutSeconds = 5.0f,
int32 MaxRetries = 3, int32 MaxRetries = 3
bool bEnableCache = true
); );
// === ACCESSEURS BLUEPRINT POUR LES REQUÊTES TRACKÉES === // === ACCESSEURS BLUEPRINT POUR LES REQUÊTES TRACKÉES ===
@ -166,8 +165,7 @@ public:
* Récupérer les statistiques du gestionnaire de requêtes * Récupérer les statistiques du gestionnaire de requêtes
*/ */
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
void GetRequestStatistics(int32& OutPending, int32& OutCached, int32& OutCompleted, int32& OutFailed, void GetRequestStatistics(int32& OutPending, int32& OutCompleted, int32& OutFailed) const;
float& OutHitRate) const;
// === REQUÊTES LEGACY (Compatibilité totale) === // === REQUÊTES LEGACY (Compatibilité totale) ===
@ -249,7 +247,7 @@ private:
// === GESTION DES ÉVÉNEMENTS WEBSOCKET === // === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
void RegisterWebSocketEvents(); void RegisterWebSocketEvents();
void UnregisterWebSocketEvents(); void UnregisterWebSocketEvents() const;
void OnWebSocketConnected_Subsystem(); void OnWebSocketConnected_Subsystem();
void OnWebSocketConnectionError_Subsystem(const FString& Error); void OnWebSocketConnectionError_Subsystem(const FString& Error);
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean); void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);

View File

@ -5,15 +5,17 @@
#include "DTFluxCoreSubsystem.h" #include "DTFluxCoreSubsystem.h"
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString OverFlowChar) FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString Separator,
const FString OverFlowChar)
{ {
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>(); UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib); const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib);
return OutParticipant.GetFormattedNameText(MaxChar, OverFlowChar); return OutParticipant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
} }
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar, FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
const FString Separator,
const FString OverFlowChar) const FString OverFlowChar)
{ {
return Participant.GetFormattedNameText(MaxChar, OverFlowChar); return Participant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
} }

View File

@ -17,9 +17,10 @@ class DTFLUXUTILITIES_API UFTDFluxUtils : public UBlueprintFunctionLibrary
public: public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant")) UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetFormatedName(const int& Bib, const int MaxChar = 10, static FText GetFormatedName(const int& Bib, const int MaxChar = 10, const FString Separator = ".",
const FString OverFlowChar = "..."); const FString OverFlowChar = "...");
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant")) UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10, static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
const FString Separator = ".",
const FString OverFlowChar = "..."); const FString OverFlowChar = "...");
}; };