Compare commits

..

3 Commits

48 changed files with 4353 additions and 1131 deletions

View File

@ -49,6 +49,11 @@
"Name": "DTFluxAPIStatus", "Name": "DTFluxAPIStatus",
"Type": "Editor", "Type": "Editor",
"LoadingPhase": "Default" "LoadingPhase": "Default"
},
{
"Name": "DTFluxPursuitSystem",
"Type": "Runtime",
"LoadingPhase": "Default"
} }
], ],
"Plugins": [ "Plugins": [

View File

@ -2,33 +2,36 @@
public class DTFluxAPIStatus : ModuleRules public class DTFluxAPIStatus : ModuleRules
{ {
public DTFluxAPIStatus(ReadOnlyTargetRules Target) : base(Target) public DTFluxAPIStatus(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core", "DTFluxCoreSubsystem",
} }
); );
PrivateDependencyModuleNames.AddRange( PrivateDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate", "Slate",
"SlateCore", "SlateCore",
"Projects", "Projects",
"DTFluxNetwork", "DTFluxNetwork",
"DTFluxProjectSettings", "DTFluxProjectSettings",
"DTFluxCore", "DTFluxCore",
"EditorStyle", "EditorStyle",
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder "ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
"UnrealEd", "UnrealEd",
"Settings" "Settings",
} "DTFluxCoreSubsystem",
); "InputCore",
} "OutputLog",
}
);
}
} }

View File

@ -5,12 +5,11 @@
#include "SlateOptMacros.h" #include "SlateOptMacros.h"
#include "DTFluxAPIStatusModule.h" #include "DTFluxAPIStatusModule.h"
#include "EditorStyleSet.h" #include "DTFluxCoreSubsystem.h"
#include "ISettingsCategory.h"
#include "ISettingsContainer.h"
#include "ISettingsModule.h" #include "ISettingsModule.h"
#include "ISettingsSection.h" #include "MovieSceneSequenceID.h"
#include "Styling/SlateIconFinder.h" #include "OutputLogCreationParams.h"
#include "OutputLogModule.h"
#include "Types/Enum/DTFluxCoreEnum.h" #include "Types/Enum/DTFluxCoreEnum.h"
#include "Subsystems/DTFluxNetworkSubsystem.h" #include "Subsystems/DTFluxNetworkSubsystem.h"
#include "Framework/MultiBox/MultiBoxBuilder.h" #include "Framework/MultiBox/MultiBoxBuilder.h"
@ -27,32 +26,32 @@ void SDTFluxStatusWidget::OnOpenSettingsClicked()
FReply SDTFluxStatusWidget::OnRaceDatasClicked() FReply SDTFluxStatusWidget::OnRaceDatasClicked()
{ {
DTFlux->SendRequest(EDTFluxRequestType::RaceData); DTFluxNetwork->SendRequest(EDTFluxRequestType::RaceData);
return FReply::Handled(); return FReply::Handled();
} }
FReply SDTFluxStatusWidget::OnTeamListClicked() FReply SDTFluxStatusWidget::OnTeamListClicked()
{ {
DTFlux->SendRequest(EDTFluxRequestType::TeamList); DTFluxNetwork->SendRequest(EDTFluxRequestType::TeamList);
return FReply::Handled(); return FReply::Handled();
} }
void SDTFluxStatusWidget::Construct(const FArguments& InArgs) void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
{ {
DTFluxNetwork =
DTFlux = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>(); DTFluxCore =
GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
ConnectionActionButtonText.Set( ConnectionActionButtonText.Set(
DTFlux->WsStatus != EDTFluxConnectionStatus::Connected ? DTFluxNetwork->WsStatus != EDTFluxConnectionStatus::Connected
FText::FromString("Connect") : ? FText::FromString("Connect")
FText::FromString("Disconnect") : FText::FromString("Disconnect")
); );
FOutputLogModule& OutputLogModule = FModuleManager::LoadModuleChecked<FOutputLogModule>("OutputLog");
const FOutputLogCreationParams Params;
bCanSupportFocus = true; bCanSupportFocus = true;
PopulateComboBoxItems();
FSlimHorizontalToolBarBuilder ToolBarBuilder( FSlimHorizontalToolBarBuilder ToolBarBuilder(
nullptr, nullptr,
FMultiBoxCustomization::None, FMultiBoxCustomization::None,
@ -72,234 +71,200 @@ void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
} }
ToolBarBuilder.EndSection(); ToolBarBuilder.EndSection();
FSlateFontInfo TitleTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText")); FSlateFontInfo TitleTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
TitleTextFont.Size = 15; TitleTextFont.Size = 15;
ChildSlot ChildSlot
[ [
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
#pragma region ToolBarSection #pragma region ToolBarSection
SNew(SVerticalBox) SNew(SBox)
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
.FillWidth(2.0)
[ [
SNew(SSpacer) SNew(SHorizontalBox)
] + SHorizontalBox::Slot()
+SHorizontalBox::Slot() .FillWidth(2.0)
.AutoWidth() [
.FillWidth(1.0) SNew(SSpacer)
.VAlign(VAlign_Center) ]
.HAlign(HAlign_Right) + SHorizontalBox::Slot()
[ .AutoWidth()
ToolBarBuilder.MakeWidget() .FillWidth(1.0)
.VAlign(VAlign_Center)
.HAlign(HAlign_Right)
[
ToolBarBuilder.MakeWidget()
]
] ]
#pragma endregion
] ]
] + SVerticalBox::Slot()
#pragma endregion .AutoHeight()
#pragma region WebsocketStatusSection
// Main VerticalBox
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBorder)
.Padding(6.0f)
[ [
SNew(SHorizontalBox) #pragma region WebsocketStatusSection
+SHorizontalBox::Slot() SNew(SBorder)
.VAlign(VAlign_Center) .Padding(6.0f)
.HAlign(HAlign_Center)
.MaxWidth(175.0)
.MinWidth(150.0)
[ [
SNew(STextBlock ) SNew(SHorizontalBox)
.Text(FText::FromString(TEXT("Websocket connection :"))) + SHorizontalBox::Slot()
.Justification(ETextJustify::Left)
]
+SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.MinWidth(50.0)
.MaxWidth(100.0)
[
SAssignNew( WsStatusText, STextBlock)
.Text(this, &SDTFluxStatusWidget::GetWebSocketStatusText)
.Justification(ETextJustify::Left)
.ColorAndOpacity(this, &SDTFluxStatusWidget::GetWebSocketStatusColor)
]
+SHorizontalBox::Slot()
.MaxWidth(100.0)
.MinWidth(30.0)
[
SAssignNew(ConnectionActionButton, SButton)
.Text(this, &SDTFluxStatusWidget::GetWebConnectActionButtonText)
.ForegroundColor_Raw(this, &SDTFluxStatusWidget::GetWebConnectActionButtonColor)
.OnClicked(this,&SDTFluxStatusWidget::OnConnectionActionButtonClicked)
.VAlign(VAlign_Center) .VAlign(VAlign_Center)
.HAlign(HAlign_Center) .HAlign(HAlign_Center)
.ContentPadding(1.5f) .MaxWidth(175.0)
.MinWidth(150.0)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Websocket connection :")))
.Justification(ETextJustify::Left)
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.MinWidth(50.0)
.MaxWidth(100.0)
[
SAssignNew(WsStatusText, STextBlock)
.Text(this, &SDTFluxStatusWidget::GetWebSocketStatusText)
.Justification(ETextJustify::Left)
.ColorAndOpacity(this, &SDTFluxStatusWidget::GetWebSocketStatusColor)
]
+ SHorizontalBox::Slot()
.MaxWidth(100.0)
.MinWidth(30.0)
[
SAssignNew(ConnectionActionButton, SButton)
.Text(this, &SDTFluxStatusWidget::GetWebConnectActionButtonText)
.ForegroundColor_Raw(this, &SDTFluxStatusWidget::GetWebConnectActionButtonColor)
.OnClicked(this, &SDTFluxStatusWidget::OnConnectionActionButtonClicked)
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
.ContentPadding(1.5f)
]
] ]
]
]
#pragma endregion #pragma endregion
#pragma region DataModelControlSection ]
+SVerticalBox::Slot() + SVerticalBox::Slot()
.VAlign(VAlign_Fill) .VAlign(VAlign_Fill)
.HAlign(HAlign_Fill) .HAlign(HAlign_Fill)
.AutoHeight() .AutoHeight()
[
SNew(SBox)
[ [
SNew(SHorizontalBox) #pragma region DataModelControlSection
+SHorizontalBox::Slot() SNew(SBorder)
.Padding(6.0f)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
[ [
SNew(SButton) SNew(SHorizontalBox)
.Text(FText::FromString("Get RaceDatas")) + SHorizontalBox::Slot()
.OnClicked(this, &SDTFluxStatusWidget::OnRaceDatasClicked) [
] SNew(SButton)
+SHorizontalBox::Slot() .Text(FText::FromString("Get RaceDatas"))
[ .OnClicked(this, &SDTFluxStatusWidget::OnRaceDatasClicked)
SNew(SButton) ]
.Text(FText::FromString("Get TeamList")) + SHorizontalBox::Slot()
.OnClicked(this, &SDTFluxStatusWidget::OnTeamListClicked) [
] SNew(SButton)
] .Text(FText::FromString("Get TeamList"))
] .OnClicked(this, &SDTFluxStatusWidget::OnTeamListClicked)
#pragma endregion ]
#pragma region HTTPStatusSection
// +SVerticalBox::Slot()
// .AutoHeight()
// [
// SNew(SHorizontalBox)
// +SHorizontalBox::Slot()
// [
// SNew(STextBlock)
// .Text(FText::FromString(TEXT("HTTP connection :")))
// .Justification(ETextJustify::Left)
// ]
// +SHorizontalBox::Slot()
// [
// SNew(STextBlock)
// .Text(FText::FromString(TEXT("invalid")))
// .Justification(ETextJustify::Center)
// .ColorAndOpacity(FColor::Red)
// ]
// +SHorizontalBox::Slot()
// [
// SNew(SButton)
// .Text(FText::FromString(TEXT("Connection test")))
// ]
// ]
#pragma endregion
#pragma region ContestsDataSection
// +SVerticalBox::Slot()
// .AutoHeight()
// .VAlign(VAlign_Fill)
// .HAlign(HAlign_Fill)
// [
// SNew(SBorder)
// .Padding(1.5f)
// .VAlign(VAlign_Fill)
// .HAlign(HAlign_Fill)
// [
// SNew(STextBlock)
// .Justification(ETextJustify::Left)
// .Text(FText::FromString("Contest :"))
// .ColorAndOpacity(FColor::White)
// ]
// ]
// +SVerticalBox::Slot()
// .AutoHeight()
// .VAlign(VAlign_Center)
// .HAlign(HAlign_Fill)
// [
// SNew(SBorder)
// .Padding(1.0f)
// .VAlign(VAlign_Center)
// .HAlign(HAlign_Fill)
// [
// SNew(SDatastorageView, DTFlux)
// ]
// ]
#pragma endregion
#pragma region ParticipantsDataSection
// +SVerticalBox::Slot()
// .AutoHeight()
// [
// SNew(SHorizontalBox)
// +SHorizontalBox::Slot()
// [
// SNew(STextBlock)
// .Text(FText::FromString(TEXT("Participants")))
// .Justification(ETextJustify::Left)
// ]
// +SHorizontalBox::Slot()
// [
// SNew(SButton)
// .Text(FText::FromString(TEXT("Show")))
// ]
// ]
#pragma endregion
#pragma region EventsSection
// +SVerticalBox::Slot()
// .AutoHeight()
// [
// SNew(SHorizontalBox)
// +SHorizontalBox::Slot()
// [
// SNew(STextBlock)
// .Text(FText::FromString(TEXT("Future Events")))
// .Justification(ETextJustify::Left)
// ]
// +SHorizontalBox::Slot()
// [
// SNew(SButton)
// .Text(FText::FromString(TEXT("Show")))
// ]
// ]
#pragma endregion
#pragma region SequencesSection
// +SVerticalBox::Slot()
// .AutoHeight()
// [
// SNew(SHorizontalBox)
// +SHorizontalBox::Slot()
// [
// SNew(STextBlock)
// .Text(FText::FromString(TEXT("Sequence On Air")))
// .Justification(ETextJustify::Left)
// ]
// +SHorizontalBox::Slot()
// [
// SNew(SButton)
// .Text(FText::FromString(TEXT("Show")))
// ]
// ]
#pragma endregion
]
#pragma endregion
]
+ SVerticalBox::Slot()
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
.AutoHeight()
[
#pragma region RankingSection
SNew(SBorder)
.Padding(6.0f)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SNew(SBorder)
.Padding(6.0f)
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
.AutoWidth()
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Contest Rankings:")))
.Margin(FMargin(0.0f, 0.0f, 8.0f, 0.0f))
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.FillWidth(1.0f)
.MaxWidth(200.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
[
SAssignNew(ContestComboBox, SComboBox<TSharedPtr<FComboBoxItem>>)
.OptionsSource(&ContestComboBoxItems)
.OnGenerateWidget(this, &SDTFluxStatusWidget::OnGeneRankingComboWidget)
.OnSelectionChanged(this, &SDTFluxStatusWidget::OnComboContestRankingChanged)
.Content()
[
SNew(STextBlock)
.Text(this, &SDTFluxStatusWidget::GetCurrContestComboBoxValue)
]
]
]
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
.Padding(FMargin(8.0f, 0.0f, 0.0f, 0.0f))
[
SNew(SButton)
.Text(FText::FromString(TEXT("RequestRanking")))
.OnClicked(this, &SDTFluxStatusWidget::OnRankingButtonClicked)
.ContentPadding(FMargin(12.0f, 4.0f))
.VAlign(VAlign_Center)
.HAlign(HAlign_Center)
]
]
]
]
#pragma endregion
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(3.0f)
[
OutputLogModule.MakeOutputLogWidget(Params)
]
]
]; ];
} }
FText SDTFluxStatusWidget::GetWebSocketStatusText() const FText SDTFluxStatusWidget::GetWebSocketStatusText() const
{ {
FString Status = FString Status =
UEnum::GetDisplayValueAsText(DTFlux->WsStatus).ToString(); UEnum::GetDisplayValueAsText(DTFluxNetwork->WsStatus).ToString();
return return
FText::FromString(Status); FText::FromString(Status);
// FText::FromString("Unknown"); // FText::FromString("Unknown");
} }
FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
{ {
switch (DTFlux->WsStatus) switch (DTFluxNetwork->WsStatus)
{ {
case EDTFluxConnectionStatus::Connected: case EDTFluxConnectionStatus::Connected:
return FText::FromString("Disconnect"); return FText::FromString("Disconnect");
@ -310,39 +275,40 @@ FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked() FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked()
{ {
if(DTFlux) if (DTFluxNetwork)
{ {
switch (DTFlux->WsStatus) switch (DTFluxNetwork->WsStatus)
{ {
case EDTFluxConnectionStatus::Connected: case EDTFluxConnectionStatus::Connected:
DTFlux->Reconnect(); DTFluxNetwork->Reconnect();
break; break;
default: default:
DTFlux->Connect(); DTFluxNetwork->Connect();
break; break;
} }
} }
return FReply::Handled(); return FReply::Handled();
} }
FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
{ {
FColor Color; FColor Color;
switch (DTFlux->WsStatus) switch (DTFluxNetwork->WsStatus)
{ {
case EDTFluxConnectionStatus::Unset: case EDTFluxConnectionStatus::Unset:
Color = FColor::Orange; Color = FColor::Orange;
break; break;
case EDTFluxConnectionStatus::Connected: case EDTFluxConnectionStatus::Connected:
Color = FColor::Green; Color = FColor::Green;
break; break;
case EDTFluxConnectionStatus::NotConnected: case EDTFluxConnectionStatus::NotConnected:
Color = FColor::Orange; Color = FColor::Orange;
break; break;
case EDTFluxConnectionStatus::Closed: case EDTFluxConnectionStatus::Closed:
Color = FColor::Magenta; Color = FColor::Magenta;
break; break;
default: default:
Color = FColor::Red; Color = FColor::Red;
break; break;
} }
return FSlateColor(Color); return FSlateColor(Color);
@ -350,8 +316,8 @@ FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
{ {
FColor Color= FColor::Green; FColor Color = FColor::Green;
switch (DTFlux->WsStatus) switch (DTFluxNetwork->WsStatus)
{ {
case EDTFluxConnectionStatus::Connected: case EDTFluxConnectionStatus::Connected:
Color = FColor::Red; Color = FColor::Red;
@ -364,4 +330,119 @@ FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
} }
void SDTFluxStatusWidget::PopulateComboBoxItems()
{
ContestComboBoxItems.Empty();
if (DTFluxNetwork)
{
FString Separator = " | ";
FString RootSeparator = " -> ";
TArray<FDTFluxContest> DataFromSubsystem = DTFluxCore->GetContests();
for (const auto& Contest : DataFromSubsystem)
{
ContestComboBoxItems.Add(
FComboBoxItem::CreateItem(EComboBoxItemType::Contest, Contest.Name + RootSeparator, Contest.ContestId));
for (const auto& Stage : Contest.Stages)
{
int StageId = Stage.StageId;
FString StageDisplayName = Contest.Name + Separator + Stage.Name;
ContestComboBoxItems.Add(FComboBoxItem::CreateItem(EComboBoxItemType::Stage,
StageDisplayName + RootSeparator, Contest.ContestId,
StageId));
for (const auto& Split : Contest.Splits)
{
ContestComboBoxItems.Add(FComboBoxItem::CreateItem(EComboBoxItemType::Split,
StageDisplayName + Separator + Split.Name,
Contest.ContestId, StageId, Split.SplitId));
}
}
}
}
if (ContestComboBox.IsValid())
{
ContestComboBox->RefreshOptions();
if (ContestComboBoxItems.Num() > 0)
{
ContestComboBox->SetSelectedItem(ContestComboBoxItems[0]);
SelectedContestComboBoxItem = ContestComboBoxItems[0];
}
}
}
TSharedRef<SWidget> SDTFluxStatusWidget::OnGeneRankingComboWidget(TSharedPtr<FComboBoxItem> InItem)
{
return SNew(STextBlock)
.Text(FText::FromString(InItem->DisplayText))
.ColorAndOpacity(GetComboItemRankingColor(InItem))
.Margin(FMargin(2.0f, 1.0f));
}
void SDTFluxStatusWidget::OnComboContestRankingChanged(TSharedPtr<FComboBoxItem> NewSelection,
ESelectInfo::Type SelectInfo)
{
SelectedContestComboBoxItem = NewSelection;
}
FText SDTFluxStatusWidget::GetCurrContestComboBoxValue() const
{
if (SelectedContestComboBoxItem.IsValid())
{
return FText::FromString(SelectedContestComboBoxItem->DisplayText);
}
return FText::FromString("None");
}
FSlateColor SDTFluxStatusWidget::GetComboItemRankingColor(const TSharedPtr<FComboBoxItem> Item)
{
switch (Item->Type)
{
case EComboBoxItemType::Contest:
return FSlateColor(FLinearColor(0.2f, 0.6f, 1.0f));
case EComboBoxItemType::Stage:
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.2f));
case EComboBoxItemType::Split:
return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
case EComboBoxItemType::None:
return FSlateColor(FLinearColor(0.6f, 0.28f, 0.28f));
default:
return FSlateColor(FLinearColor::White);
}
}
FReply SDTFluxStatusWidget::OnRankingButtonClicked() const
{
if (DTFluxNetwork)
{
// Exemple d'envoi de requête basée sur la sélection
int ForContest = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->ContestId : -1;
int ForStage = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->StageId : -1;
int ForSplit = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->SplitId : -1;
EDTFluxApiDataType RequestType = EDTFluxApiDataType::None;
if (ForContest == -1)
{
UE_LOG(logDTFluxStatus, Error, TEXT("Contest not selected !!!!"));
return FReply::Handled();
}
if (ForStage == -1)
{
UE_LOG(logDTFluxStatus, Warning, TEXT("Stage not selected !!!! Requesting contest Ranking"));
RequestType = EDTFluxApiDataType::ContestRanking;
DTFluxNetwork->SendRequest(RequestType, ForContest);
return FReply::Handled();
}
RequestType = ForSplit == -1 ? EDTFluxApiDataType::StageRanking : EDTFluxApiDataType::SplitRanking;
UE_LOG(logDTFluxStatus, Warning, TEXT("Requesting %s Ranking"), *UEnum::GetValueAsString(RequestType));
DTFluxNetwork->SendRequest(RequestType, ForContest, ForStage, ForSplit);
}
return FReply::Handled();
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@ -10,14 +10,16 @@
* *
*/ */
class UDTFluxNetworkSubsystem; class UDTFluxNetworkSubsystem;
class UDTFluxCoreSubsystem;
class SSuperListView; class SSuperListView;
class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget
{ {
public: public:
SLATE_BEGIN_ARGS(SDTFluxStatusWidget) SLATE_BEGIN_ARGS(SDTFluxStatusWidget)
{ {
} }
SLATE_END_ARGS() SLATE_END_ARGS()
@ -28,9 +30,43 @@ public:
void Construct(const FArguments& InArgs); void Construct(const FArguments& InArgs);
TAttribute<FText> ConnectionActionButtonText; TAttribute<FText> ConnectionActionButtonText;
FReply OnConnectionActionButtonClicked(); FReply OnConnectionActionButtonClicked();
enum class EComboBoxItemType
{
Contest,
Stage,
Split,
None
};
struct FComboBoxItem
{
FString DisplayText;
int ContestId = -1;
int StageId = -1;
int SplitId = -1;
EComboBoxItemType Type = EComboBoxItemType::None;
FComboBoxItem(const EComboBoxItemType InType, const FString& InDisplayText, const int InContestId,
const int InStageId = -1, const int InSplitId = -1)
: DisplayText(InDisplayText), ContestId(InContestId), StageId(InStageId), SplitId(InSplitId), Type(InType)
{
}
static TSharedPtr<FComboBoxItem> CreateItem(const EComboBoxItemType& InType, const FString& InDisplayText,
const int InContestId, const int InStageId = -1,
const int InSplitId = -1)
{
TSharedPtr<FComboBoxItem> Item = MakeShareable(
new FComboBoxItem(InType, InDisplayText, InContestId, InStageId, InSplitId));
return Item;
}
};
private: private:
UDTFluxNetworkSubsystem* DTFlux = nullptr; UDTFluxNetworkSubsystem* DTFluxNetwork = nullptr;
UDTFluxCoreSubsystem* DTFluxCore = nullptr;
// // TODO make a struct // // TODO make a struct
FText GetWebSocketStatusText() const; FText GetWebSocketStatusText() const;
FText GetWebConnectActionButtonText() const; FText GetWebConnectActionButtonText() const;
@ -39,6 +75,18 @@ private:
TSharedPtr<STextBlock> WsStatusText; TSharedPtr<STextBlock> WsStatusText;
TSharedPtr<SButton> ConnectionActionButton; TSharedPtr<SButton> ConnectionActionButton;
TSharedPtr<SComboBox<TSharedPtr<FComboBoxItem>>> ContestComboBox;
TArray<TSharedPtr<FComboBoxItem>> ContestComboBoxItems;
TSharedPtr<FComboBoxItem> SelectedContestComboBoxItem;
// Méthodes pour le ComboBox
void PopulateComboBoxItems();
TSharedRef<SWidget> OnGeneRankingComboWidget(TSharedPtr<FComboBoxItem> InItem);
void OnComboContestRankingChanged(TSharedPtr<FComboBoxItem> NewSelection, ESelectInfo::Type SelectInfo);
FText GetCurrContestComboBoxValue() const;
static FSlateColor GetComboItemRankingColor(const TSharedPtr<FComboBoxItem> Item);
FReply OnRankingButtonClicked() const;
}; };

View File

@ -2,28 +2,37 @@
public class DTFluxAssetsEditor : ModuleRules public class DTFluxAssetsEditor : ModuleRules
{ {
public DTFluxAssetsEditor(ReadOnlyTargetRules Target) : base(Target) public DTFluxAssetsEditor(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core"
} }
); );
PrivateDependencyModuleNames.AddRange( PrivateDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate", "SlateCore",
"AssetTools", "Slate",
"SlateCore", "AssetTools",
"UnrealEd", "UnrealEd",
"DTFluxCore", "DTFluxCore",
} "ToolMenus",
); "EditorWidgets",
} "EditorStyle",
"PropertyEditor",
"SharedSettingsWidgets",
"PropertyEditor",
"DesktopWidgets",
"ApplicationCore",
"InputCore"
}
);
}
} }

View File

@ -3,28 +3,60 @@
#include "DTFluxAssetModelTypeActions.h" #include "DTFluxAssetModelTypeActions.h"
#include "IAssetTools.h" #include "IAssetTools.h"
#include "AssetToolsModule.h" #include "AssetToolsModule.h"
#include "DTFluxModelAssetDetailCustomization.h"
#include "Assets/DTFluxModelAsset.h"
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule" #define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor) DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
void FDTFluxAssetsEditorModule::StartupModule() void FDTFluxAssetsEditorModule::StartupModule()
{ {
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get(); IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux")); EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category)); DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef()); AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
RegisterCustomizations();
} }
void FDTFluxAssetsEditorModule::ShutdownModule() void FDTFluxAssetsEditorModule::ShutdownModule()
{ {
if(DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools")) if (DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools"))
{ {
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get(); IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef()); AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
} }
UnregisterCustomizations();
}
void FDTFluxAssetsEditorModule::RegisterCustomizations()
{
// Enregistrer la customization pour DTFluxModelAsset
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout(
UDTFluxModelAsset::StaticClass()->GetFName(),
FOnGetDetailCustomizationInstance::CreateStatic(&FDTFluxModelAssetCustomization::MakeInstance)
);
PropertyModule.NotifyCustomizationModuleChanged();
}
void FDTFluxAssetsEditorModule::UnregisterCustomizations()
{
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
{
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(
"PropertyEditor");
PropertyModule.UnregisterCustomClassLayout(UDTFluxModelAsset::StaticClass()->GetFName());
PropertyModule.NotifyCustomizationModuleChanged();
}
} }
#undef LOCTEXT_NAMESPACE #undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FDTFluxAssetsEditorModule, DTFluxAssetsEditor) IMPLEMENT_MODULE(FDTFluxAssetsEditorModule, DTFluxAssetsEditor)

View File

@ -0,0 +1,217 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxModelAssetDetailCustomization.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "DetailWidgetRow.h"
#include "Widgets/Layout/SBox.h"
#include "Assets/DTFluxModelAsset.h"
TSharedRef<IDetailCustomization> FDTFluxModelAssetCustomization::MakeInstance()
{
return MakeShareable(new FDTFluxModelAssetCustomization);
}
EActiveTimerReturnType SDTFluxAssetModelDetailsWidget::ForceInitialLayout(double InCurrentTime, float InDeltaTime)
{
// Forcer la mise à jour des TreeViews
if (ContestTreeView.IsValid())
{
ContestTreeView->RequestTreeRefresh();
}
if (ParticipantTreeView.IsValid())
{
ParticipantTreeView->RequestTreeRefresh();
}
// Forcer l'invalidation du layout
Invalidate(EInvalidateWidget::Layout);
UE_LOG(LogTemp, Log, TEXT("Forced initial layout refresh"));
// Arrêter le timer (exécution unique)
return EActiveTimerReturnType::Stop;
}
void FDTFluxModelAssetCustomization::CustomizeDetails(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;
}
// ===== Hiding Categories/Props =====
DetailBuilder.HideCategory("General");
DetailBuilder.HideCategory("Default");
DetailBuilder.HideCategory("Transform");
DetailBuilder.HideCategory("Rendering");
DetailBuilder.HideCategory("Input");
DetailBuilder.HideCategory("Actor");
DetailBuilder.HideCategory("Advanced");
// Hide individual Props
DetailBuilder.HideProperty("EventName");
DetailBuilder.HideProperty("Persons");
DetailBuilder.HideProperty("Participants");
DetailBuilder.HideProperty("Contests");
DetailBuilder.HideProperty("ContestRankings");
DetailBuilder.HideProperty("StageRankings");
DetailBuilder.HideProperty("SplitRankings");
IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory(
"DTFlux Model Explorer",
FText::FromString("DTFlux Model Explorer"),
ECategoryPriority::Important
);
// Créer le widget hiérarchique
DetailsWidget = SNew(SDTFluxAssetModelDetailsWidget)
.ModelAsset(ModelAsset.Get());
MainCategory.AddCustomRow(FText::FromString("Data Explorer"))
.WholeRowContent()
[
SNew(SBox)
.MinDesiredHeight(800.0f)
[
DetailsWidget.ToSharedRef()
]
];
UE_LOG(LogTemp, Log, TEXT("DTFluxModelAsset custom-only interface applied"));
}
void FDTFluxModelAssetCustomization::CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder)
{
// ===== WIDGET PRINCIPAL =====
IDetailCategoryBuilder& MainCategory = DetailBuilder.EditCategory(
"DTFlux Model Explorer",
FText::FromString("DTFlux Model Explorer"),
ECategoryPriority::Important
);
DetailsWidget = SNew(SDTFluxAssetModelDetailsWidget)
.ModelAsset(ModelAsset.Get());
MainCategory.AddCustomRow(FText::FromString("Data Explorer"))
.WholeRowContent()
[
SNew(SBox)
.MinDesiredHeight(650.0f)
[
DetailsWidget.ToSharedRef()
]
];
IDetailCategoryBuilder& QuickActionsCategory = DetailBuilder.EditCategory(
"Quick Actions",
FText::FromString("Quick Actions"),
ECategoryPriority::Default
);
QuickActionsCategory.AddCustomRow(FText::FromString("Raw Data Access"))
.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString("Raw Data Access"))
.Font(FAppStyle::GetFontStyle("NormalText"))
]
.ValueContent()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 5, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "Button")
.Text(FText::FromString("Edit Raw Properties"))
.ToolTipText(FText::FromString("Temporarily show standard properties for advanced editing"))
.OnClicked_Lambda([this, &DetailBuilder]() -> FReply
{
// Forcer le rafraîchissement du DetailsPanel pour montrer les propriétés standard
DetailBuilder.ForceRefreshDetails();
UE_LOG(LogTemp, Warning,
TEXT(
"Tip: To edit raw data, right-click the asset and choose 'Edit' or use the Content Browser"
));
return FReply::Handled();
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 5, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "Button")
.Text(FText::FromString("Log All Data"))
.ToolTipText(FText::FromString("Print all data to Output Log"))
.OnClicked_Lambda([this]() -> FReply
{
if (ModelAsset.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("=== DTFLUX MODEL DUMP ==="));
UE_LOG(LogTemp, Warning, TEXT("Event: %s"), *ModelAsset->EventName);
UE_LOG(LogTemp, Warning, TEXT("--- CONTESTS (%d) ---"), ModelAsset->Contests.Num());
for (const auto& Contest : ModelAsset->Contests)
{
UE_LOG(LogTemp, Warning, TEXT("Contest '%s' (ID: %d) - %d stages, %d participants"),
*Contest.Key, Contest.Value.ContestId,
Contest.Value.Stages.Num(), Contest.Value.ParticipantsBib.Num());
}
UE_LOG(LogTemp, Warning, TEXT("--- PARTICIPANTS (%d) ---"), ModelAsset->Participants.Num());
for (const auto& Participant : ModelAsset->Participants)
{
UE_LOG(LogTemp, Warning, TEXT("Bib %d: %s (%s) - %d teammates"),
Participant.Value.Bib, *Participant.Value.Team,
*Participant.Value.Category, Participant.Value.GetTeammate().Num());
}
UE_LOG(LogTemp, Warning, TEXT("--- PERSONS (%d) ---"), ModelAsset->Persons.Num());
for (int32 i = 0; i < ModelAsset->Persons.Num(); ++i)
{
const auto& Person = ModelAsset->Persons[i];
UE_LOG(LogTemp, Warning, TEXT("Person %d: %s %s (%s)"),
i, *Person.FirstName, *Person.LastName, *Person.Gender);
}
UE_LOG(LogTemp, Warning, TEXT("======================="));
}
return FReply::Handled();
})
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
.Text(FText::FromString("Refresh"))
.ToolTipText(FText::FromString("Refresh the hierarchy view"))
.OnClicked_Lambda([this]() -> FReply
{
if (DetailsWidget.IsValid())
{
DetailsWidget->RefreshData();
}
return FReply::Handled();
})
]
];
}

View File

@ -0,0 +1,645 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/DTFluxAssetModelDetailsWidget.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Views/STableRow.h"
#include "Widgets/Views/SHeaderRow.h"
void SHierarchicalTreeItemRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView)
{
Item = InArgs._Item;
ParentWidget = InArgs._ParentWidget;
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::Construct(
SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>::FArguments(),
InOwnerTableView
);
}
TSharedRef<SWidget> SHierarchicalTreeItemRow::GenerateWidgetForColumn(const FName& ColumnName)
{
if (!Item.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Item"));
}
if (!ParentWidget)
{
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"),
*ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Parent"));
}
UE_LOG(LogTemp, VeryVerbose, TEXT("GenerateWidgetForColumn: %s for item %s"),
*ColumnName.ToString(), *Item->Name);
if (ColumnName == "Name")
{
return SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0, 0, 5, 0)
[
SNew(SImage)
.Image(ParentWidget->GetItemIcon(Item->Type))
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(FText::FromString(Item->Name))
.ColorAndOpacity(ParentWidget->GetItemTypeColor(Item->Type))
.Font_Lambda([this]() -> FSlateFontInfo
{
return Item->Type == FHierarchicalTreeItem::EItemType::Contest
? FAppStyle::GetFontStyle("HeadingText")
: FAppStyle::GetFontStyle("NormalText");
})
];
}
else if (ColumnName == "ID")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->ID))
.Font(FAppStyle::GetFontStyle("NormalText"))
.Justification(ETextJustify::Center);
}
else if (ColumnName == "Details")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Details))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
else if (ColumnName == "Status")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Status))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
else if (ColumnName == "Extra")
{
return SNew(STextBlock)
.Text(FText::FromString(Item->Extra))
.Font(FAppStyle::GetFontStyle("NormalText"))
.OverflowPolicy(ETextOverflowPolicy::Ellipsis);
}
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Unknown column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("Unknown: %s"), *ColumnName.ToString())));
}
void SDTFluxAssetModelDetailsWidget::Construct(const FArguments& InArgs)
{
ModelAsset = InArgs._ModelAsset;
ChildSlot
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
// === SECTION STATISTIQUES ===
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
.Padding(10)
[
SAssignNew(StatsText, STextBlock)
.Text(this, &SDTFluxAssetModelDetailsWidget::GetStatsText)
.Font(FAppStyle::GetFontStyle("HeadingText"))
.Justification(ETextJustify::Center)
]
]
// === SECTION BOUTONS DE NAVIGATION ===
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 5, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.Text(FText::FromString("Expand All Contests"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnExpandAllClicked)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0, 0, 10, 0)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.Text(FText::FromString("Collapse All Contests"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNew(SSpacer)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "PrimaryButton")
.Text(FText::FromString("Refresh"))
.OnClicked(this, &SDTFluxAssetModelDetailsWidget::OnRefreshClicked)
]
]
]
#pragma region ListView
+ SVerticalBox::Slot()
.FillHeight(1.0f)
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(0)
[
SNew(SBox)
[
#pragma region ScrollBox
SNew(SScrollBox)
.Orientation(Orient_Vertical)
.ScrollBarVisibility(EVisibility::Visible)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.ScrollBarAlwaysVisible(true) // Force la scrollbar à être toujours visible
#pragma region ListView.Contest
+ SScrollBox::Slot()
.Padding(0, 0, 0, 10)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10, 5)
[
SNew(STextBlock)
.Text(FText::FromString("CONTESTS HIERARCHY"))
.Font(FAppStyle::GetFontStyle("HeadingText"))
.ColorAndOpacity(FLinearColor(0.2f, 0.6f, 1.0f))
]
]
// TreeView Contests
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
[
SAssignNew(ContestTreeView, STreeView<FHierarchicalTreeItemPtr>)
.TreeItemsSource(&RootItems)
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
.OnSelectionChanged(
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
.OnSetExpansionRecursive(
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
.SelectionMode(ESelectionMode::Single)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.HeaderRow
(
SNew(SHeaderRow)
.CanSelectGeneratedColumn(true)
+ SHeaderRow::Column("Name")
.DefaultLabel(FText::FromString("Contest / Stage / Split"))
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("ID")
.DefaultLabel(FText::FromString("ID"))
.SortMode(EColumnSortMode::None)
.FillWidth(.02f)
+ SHeaderRow::Column("Details")
.DefaultLabel(FText::FromString("Details"))
.SortMode(EColumnSortMode::None)
.FillWidth(.3f)
+ SHeaderRow::Column("Status")
.DefaultLabel(FText::FromString("Status / Time"))
.SortMode(EColumnSortMode::None)
.FillWidth(.1f)
+ SHeaderRow::Column("Extra")
.DefaultLabel(FText::FromString("Extra Info"))
.FillWidth(.2f)
.SortMode(EColumnSortMode::None)
)
]
]
]
]
#pragma endregion
#pragma region ListView.Participant
+ SScrollBox::Slot()
.Padding(0, 10, 0, 0)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
[
SNew(SVerticalBox)
// Header "Participants"
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10, 5)
[
SNew(STextBlock)
.Text(FText::FromString("PARTICIPANTS LIST"))
.Font(FAppStyle::GetFontStyle("HeadingText"))
.ColorAndOpacity(FLinearColor(0.2f, 0.8f, 0.8f))
]
]
// TreeView Participants
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SBox)
[
SAssignNew(ParticipantTreeView, STreeView<FHierarchicalTreeItemPtr>)
.TreeItemsSource(&ParticipantItems)
.OnGenerateRow(this, &SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree)
.OnGetChildren(this, &SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree)
.OnSelectionChanged(
this, &SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged)
.OnSetExpansionRecursive(
this, &SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive)
.SelectionMode(ESelectionMode::Single)
.ConsumeMouseWheel(EConsumeMouseWheel::WhenScrollingPossible)
.HeaderRow
(
SNew(SHeaderRow)
.CanSelectGeneratedColumn(true)
+ SHeaderRow::Column("Name")
.DefaultLabel(FText::FromString("Participant"))
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("ID")
.DefaultLabel(FText::FromString("Bib"))
.SortMode(EColumnSortMode::None)
.FillWidth(.08f)
+ SHeaderRow::Column("Details")
.DefaultLabel(FText::FromString("Category & Teammates"))
.SortMode(EColumnSortMode::None)
.FillWidth(.2f)
+ SHeaderRow::Column("Status")
.DefaultLabel(FText::FromString("Status"))
.FillWidth(.1f)
.SortMode(EColumnSortMode::None)
+ SHeaderRow::Column("Extra")
.DefaultLabel(FText::FromString("Club"))
.FillWidth(.2f)
.SortMode(EColumnSortMode::None)
)
]
]
]
]
#pragma endregion
]
#pragma endregion
]
#pragma endregion
]
]
#pragma region DetailView.Participant
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(5)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("DetailsView.CategoryTop"))
.Padding(10)
[
SAssignNew(SelectionText, STextBlock)
.Text(FText::FromString("Select an item to see details. Use expand/collapse arrows in the tree."))
.Font(FAppStyle::GetFontStyle("NormalText"))
.AutoWrapText(true)
]
]
#pragma endregion
]
];
RefreshData();
RegisterActiveTimer(
0.1f, FWidgetActiveTimerDelegate::CreateSP(this, &SDTFluxAssetModelDetailsWidget::ForceInitialLayout));
}
// ===== CONSTRUCTION DE LA HIÉRARCHIE =====
void SDTFluxAssetModelDetailsWidget::BuildContestHierarchy()
{
RootItems.Empty();
if (!ModelAsset)
return;
// Construire la hiérarchie Contest → Stages → Splits
for (const auto& ContestPair : ModelAsset->Contests)
{
const FString& ContestName = ContestPair.Key;
const FDTFluxContest& Contest = ContestPair.Value;
// Créer l'élément Contest racine
auto ContestItem = FHierarchicalTreeItem::CreateContest(ContestName, Contest);
// Ajouter les Stages comme enfants
for (const FDTFluxStage& Stage : Contest.Stages)
{
auto StageItem = FHierarchicalTreeItem::CreateStage(Stage, Contest.ContestId);
ContestItem->AddChild(StageItem);
}
// Ajouter les Splits comme enfants directs du Contest
for (const FDTFluxSplit& Split : Contest.Splits)
{
auto SplitItem = FHierarchicalTreeItem::CreateSplit(Split, Contest.ContestId);
ContestItem->AddChild(SplitItem);
}
RootItems.Add(ContestItem);
}
UE_LOG(LogTemp, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num());
}
void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
{
ParticipantItems.Empty();
if (!ModelAsset)
{
UE_LOG(LogTemp, Warning, TEXT("BuildParticipantList: ModelAsset is null!"));
return;
}
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: ModelAsset has %d participants"), ModelAsset->Participants.Num());
// Créer la liste des participants (pas de hiérarchie pour les participants)
for (const auto& ParticipantPair : ModelAsset->Participants)
{
const FDTFluxParticipant& Participant = ParticipantPair.Value;
auto ParticipantItem = FHierarchicalTreeItem::CreateParticipant(Participant);
ParticipantItems.Add(ParticipantItem);
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"),
*ParticipantItem->Name, ParticipantItem->Bib);
}
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Built participant list with %d participants"),
ParticipantItems.Num());
}
// ===== CALLBACKS TREEVIEW =====
TSharedRef<ITableRow> SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree(
FHierarchicalTreeItemPtr Item, const TSharedRef<STableViewBase>& OwnerTable)
{
if (!Item.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("OnGenerateRowForTree: Invalid item!"));
return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable);
}
UE_LOG(LogTemp, Log, TEXT("OnGenerateRowForTree: Generating row for %s (Type: %d)"),
*Item->Name, (int32)Item->Type);
return SNew(SHierarchicalTreeItemRow, OwnerTable)
.Item(Item)
.ParentWidget(this);
}
void SDTFluxAssetModelDetailsWidget::OnGetChildrenForTree(FHierarchicalTreeItemPtr Item,
TArray<FHierarchicalTreeItemPtr>& OutChildren)
{
if (Item.IsValid())
{
OutChildren = Item->Children;
}
}
void SDTFluxAssetModelDetailsWidget::OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem,
ESelectInfo::Type SelectInfo)
{
if (SelectionText.IsValid())
{
if (SelectedItem.IsValid())
{
FString TypeString;
switch (SelectedItem->Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
TypeString = "Contest";
break;
case FHierarchicalTreeItem::EItemType::Stage:
TypeString = "Stage";
break;
case FHierarchicalTreeItem::EItemType::Split:
TypeString = "Split";
break;
case FHierarchicalTreeItem::EItemType::Participant:
TypeString = "Participant";
break;
}
FString SelectionInfo = FString::Printf(
TEXT("📋 Selected: %s (%s)\n🔢 ID: %s\n📄 Details: %s\n📊 Status: %s\n Extra: %s\n🌟 Children: %d"),
*SelectedItem->Name,
*TypeString,
*SelectedItem->ID,
*SelectedItem->Details,
*SelectedItem->Status,
*SelectedItem->Extra,
SelectedItem->Children.Num());
SelectionText->SetText(FText::FromString(SelectionInfo));
}
else
{
SelectionText->SetText(FText::FromString("Select an item to see details"));
}
}
}
void SDTFluxAssetModelDetailsWidget::OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded)
{
if (Item.IsValid() && ContestTreeView.IsValid())
{
ContestTreeView->SetItemExpansion(Item, bIsExpanded);
// Expansion récursive des enfants
for (auto Child : Item->Children)
{
OnSetExpansionRecursive(Child, bIsExpanded);
}
}
}
// ===== CALLBACKS DES BOUTONS =====
FReply SDTFluxAssetModelDetailsWidget::OnExpandAllClicked()
{
if (ContestTreeView.IsValid())
{
for (auto& RootItem : RootItems)
{
OnSetExpansionRecursive(RootItem, true);
}
}
UE_LOG(LogTemp, Log, TEXT("Expanded all contests"));
return FReply::Handled();
}
FReply SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked()
{
if (ContestTreeView.IsValid())
{
for (auto& RootItem : RootItems)
{
OnSetExpansionRecursive(RootItem, false);
}
}
UE_LOG(LogTemp, Log, TEXT("Collapsed all contests"));
return FReply::Handled();
}
FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked()
{
RefreshData();
UE_LOG(LogTemp, Log, TEXT("Data refreshed"));
return FReply::Handled();
}
// ===== REFRESHDATA =====
void SDTFluxAssetModelDetailsWidget::RefreshData()
{
if (!ModelAsset)
{
UE_LOG(LogTemp, Warning, TEXT("ModelAsset is null!"));
return;
}
UE_LOG(LogTemp, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName());
// Nettoyer les données existantes
RootItems.Empty();
ParticipantItems.Empty();
// Construire la hiérarchie
BuildContestHierarchy();
BuildParticipantList();
// Refresh les vues
if (ContestTreeView.IsValid())
{
ContestTreeView->RequestTreeRefresh();
}
if (ParticipantTreeView.IsValid())
{
ParticipantTreeView->RequestTreeRefresh();
}
UE_LOG(LogTemp, Log, TEXT("RefreshData: Completed successfully - %d contests, %d participants"), RootItems.Num(),
ParticipantItems.Num());
}
// ===== MÉTHODES UTILITAIRES =====
FSlateColor SDTFluxAssetModelDetailsWidget::GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const
{
switch (Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
return FSlateColor(FLinearColor(0.2f, 0.6f, 1.0f));
case FHierarchicalTreeItem::EItemType::Stage:
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.2f));
case FHierarchicalTreeItem::EItemType::Split:
return FSlateColor(FLinearColor(1.0f, 0.8f, 0.2f));
case FHierarchicalTreeItem::EItemType::Participant:
return FSlateColor(FLinearColor(0.2f, 0.8f, 0.8f));
default:
return FSlateColor(FLinearColor::White);
}
}
const FSlateBrush* SDTFluxAssetModelDetailsWidget::GetItemIcon(FHierarchicalTreeItem::EItemType Type) const
{
switch (Type)
{
case FHierarchicalTreeItem::EItemType::Contest:
return FAppStyle::GetBrush("TreeArrow_Collapsed");
case FHierarchicalTreeItem::EItemType::Stage:
case FHierarchicalTreeItem::EItemType::Split:
return FAppStyle::GetBrush("TreeArrow_Expanded");
case FHierarchicalTreeItem::EItemType::Participant:
return FAppStyle::GetBrush("Icons.User");
default:
return FAppStyle::GetBrush("Icons.Help");
}
}
FText SDTFluxAssetModelDetailsWidget::GetStatsText() const
{
if (!ModelAsset)
return FText::FromString("No data");
return FText::FromString(FString::Printf(
TEXT("Contests: [%d] Participants: [%d] Persons: [%d]"),
ModelAsset->Contests.Num(),
ModelAsset->Participants.Num(),
ModelAsset->Persons.Num()
));
}

View File

@ -21,9 +21,11 @@ DTFLUXASSETSEDITOR_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxAssetEditor, Log, Al
class FDTFluxAssetsEditorModule : public IModuleInterface class FDTFluxAssetsEditorModule : public IModuleInterface
{ {
public: public:
virtual void StartupModule() override; virtual void StartupModule() override;
virtual void ShutdownModule() override; virtual void ShutdownModule() override;
private: private:
TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions; TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions;
void RegisterCustomizations();
void UnregisterCustomizations();
}; };

View File

@ -0,0 +1,26 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "IDetailCustomization.h"
#include "Widget/DTFluxAssetModelDetailsWidget.h"
class FDTFluxModelAssetCustomization : public IDetailCustomization
{
public:
// IDetailCustomization interface
virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override;
void CustomizeDetailsWithRawDataAccess(IDetailLayoutBuilder& DetailBuilder);
// Crée une instance de cette customization
static TSharedRef<IDetailCustomization> MakeInstance();
private:
// Handle vers l'objet en cours d'édition
TWeakObjectPtr<UDTFluxModelAsset> ModelAsset;
// Widget personnalisé
TSharedPtr<SDTFluxAssetModelDetailsWidget> DetailsWidget;
};

View File

@ -0,0 +1,191 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Types/Struct/DTFluxRaceDataStructs.h"
#include "Widgets/Views/STreeView.h"
#include "Widgets/Views/SHeaderRow.h"
#include "Assets/DTFluxModelAsset.h"
struct FHierarchicalTreeItem;
class SDTFluxAssetModelDetailsWidget;
// ✅ SOUS-CLASSE DE SMultiColumnTableRow
class SHierarchicalTreeItemRow : public SMultiColumnTableRow<TSharedPtr<FHierarchicalTreeItem>>
{
public:
SLATE_BEGIN_ARGS(SHierarchicalTreeItemRow)
{
}
SLATE_ARGUMENT(TSharedPtr<FHierarchicalTreeItem>, Item)
SLATE_ARGUMENT(SDTFluxAssetModelDetailsWidget*, ParentWidget)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView);
protected:
virtual TSharedRef<SWidget> GenerateWidgetForColumn(const FName& ColumnName) override;
private:
TSharedPtr<FHierarchicalTreeItem> Item;
SDTFluxAssetModelDetailsWidget* ParentWidget;
};
// ✅ STRUCTURE SIMPLE POUR TREEVIEW
struct FHierarchicalTreeItem
{
enum class EItemType
{
Contest,
Stage,
Split,
Participant
};
EItemType Type;
FString Name;
FString ID;
FString Details;
FString Status;
FString Extra;
// Données pour retrouver l'élément original
int32 ContestId = -1;
int32 StageId = -1;
int32 SplitId = -1;
int32 Bib = -1;
// Enfants pour la hiérarchie
TArray<TSharedPtr<FHierarchicalTreeItem>> Children;
FHierarchicalTreeItem(EItemType InType, const FString& InName)
: Type(InType), Name(InName)
{
}
void AddChild(TSharedPtr<FHierarchicalTreeItem> Child)
{
if (Child.IsValid())
{
Children.Add(Child);
}
}
// Factory methods pour créer les éléments
static TSharedPtr<FHierarchicalTreeItem> CreateContest(const FString& ContestName, const FDTFluxContest& Contest)
{
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(
new FHierarchicalTreeItem(EItemType::Contest, ContestName));
Item->ContestId = Contest.ContestId;
Item->ID = FString::Printf(TEXT("%d"), Contest.ContestId);
Item->Details = FString::Printf(
TEXT("%d stages, %d participants"), Contest.Stages.Num(), Contest.ParticipantsBib.Num());
Item->Status = Contest.Date.ToString(TEXT("%Y-%m-%d"));
Item->Extra = Contest.IsFinished() ? TEXT("Finished") : TEXT("Active");
return Item;
}
static TSharedPtr<FHierarchicalTreeItem> CreateStage(const FDTFluxStage& Stage, int32 InContestId)
{
FString StageName = Stage.Name.IsEmpty() ? FString::Printf(TEXT("Stage %d"), Stage.StageId) : Stage.Name;
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(new FHierarchicalTreeItem(EItemType::Stage, StageName));
Item->ContestId = InContestId;
Item->StageId = Stage.StageId;
Item->ID = FString::Printf(TEXT("%d"), Stage.StageId);
Item->Details = FString::Printf(TEXT("Start: %s"), *Stage.StartTime.ToString(TEXT("%H:%M")));
Item->Status = FString::Printf(TEXT("End: %s"), *Stage.EndTime.ToString(TEXT("%H:%M")));
Item->Extra = FString::Printf(TEXT("Cutoff: %s"), *Stage.CutOff.ToString(TEXT("%H:%M")));
return Item;
}
static TSharedPtr<FHierarchicalTreeItem> CreateSplit(const FDTFluxSplit& Split, int32 InContestId)
{
FString SplitName = Split.Name.IsEmpty() ? FString::Printf(TEXT("Split %d"), Split.SplitId) : Split.Name;
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(new FHierarchicalTreeItem(EItemType::Split, SplitName));
Item->ContestId = InContestId;
Item->SplitId = Split.SplitId;
Item->ID = FString::Printf(TEXT("%d"), Split.SplitId);
Item->Details = FString::Printf(TEXT("%d rankings"), Split.SplitRankings.Num());
Item->Status = TEXT("-");
Item->Extra = TEXT("-");
return Item;
}
static TSharedPtr<FHierarchicalTreeItem> CreateParticipant(const FDTFluxParticipant& Participant,
UDTFluxModelAsset* InModelAsset = nullptr)
{
FString ParticipantName = Participant.GetConcatFormattedName();
TSharedPtr<FHierarchicalTreeItem> Item = MakeShareable(
new FHierarchicalTreeItem(EItemType::Participant, ParticipantName));
Item->Bib = Participant.Bib;
Item->ContestId = Participant.ContestId;
Item->ID = FString::Printf(TEXT("%d"), Participant.Bib);
Item->Details = FString::Printf(TEXT("%s - %d teammates"), *Participant.Category,
Participant.GetTeammate().Num());
FString Status = UEnum::GetValueAsString(Participant.Status);
TArray<FString> StatusArray;
Status.ParseIntoArray(StatusArray, TEXT("::"));
Item->Status = StatusArray.Last();
Item->Extra = Participant.Club;
return Item;
}
};
typedef TSharedPtr<FHierarchicalTreeItem> FHierarchicalTreeItemPtr;
/**
* Widget avec STreeView simple et efficace
*/
class DTFLUXASSETSEDITOR_API SDTFluxAssetModelDetailsWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SDTFluxAssetModelDetailsWidget)
{
}
SLATE_ARGUMENT(UDTFluxModelAsset*, ModelAsset)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
void RefreshData();
// Méthodes publiques pour la sous-classe Row
FSlateColor GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const;
const FSlateBrush* GetItemIcon(FHierarchicalTreeItem::EItemType Type) const;
private:
// Données
UDTFluxModelAsset* ModelAsset = nullptr;
TArray<FHierarchicalTreeItemPtr> RootItems; // Contests racines avec hiérarchie
TArray<FHierarchicalTreeItemPtr> ParticipantItems; // Participants séparés
// Widgets - TreeView simple
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ContestTreeView;
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ParticipantTreeView;
TSharedPtr<STextBlock> StatsText;
TSharedPtr<STextBlock> SelectionText;
// Méthodes de construction
void BuildContestHierarchy();
void BuildParticipantList();
// Callbacks TreeView
TSharedRef<ITableRow> OnGenerateRowForTree(FHierarchicalTreeItemPtr Item,
const TSharedRef<STableViewBase>& OwnerTable);
void OnGetChildrenForTree(FHierarchicalTreeItemPtr Item, TArray<FHierarchicalTreeItemPtr>& OutChildren);
void OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem, ESelectInfo::Type SelectInfo);
void OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded);
// Callbacks des boutons
FReply OnRefreshClicked();
FReply OnExpandAllClicked();
FReply OnCollapseAllClicked();
EActiveTimerReturnType ForceInitialLayout(double InCurrentTime, float InDeltaTime);
// Utilitaires
FText GetStatsText() const;
};

View File

@ -10,21 +10,31 @@ UDTFluxModelAsset::UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer
{ {
} }
void UDTFluxModelAsset::AddContest(const FDTFluxContest &Contest) void UDTFluxModelAsset::AddContest(const FDTFluxContest& Contest)
{ {
Contests.Add(Contest.Name, Contest); Contests.Add(Contest.Name, Contest);
} }
bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest) bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest)
{ {
for(auto& ContestItem : Contests) for (auto& ContestItem : Contests)
{ {
if(ContestItem.Value.ContestId == InContestId) if (ContestItem.Value.ContestId == InContestId)
{ {
OutContest = ContestItem.Value; OutContest = ContestItem.Value;
return true; return true;
} }
}
return false;
}
bool UDTFluxModelAsset::GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage)
{
FDTFluxContest TargetContest;
int TargetStageId = StageKey.StageId;
if (GetContestById(StageKey.ContestId, TargetContest))
{
return TargetContest.GetStage(TargetStageId, OutStage);
} }
return false; return false;
} }
@ -36,19 +46,20 @@ void UDTFluxModelAsset::AddPerson(const FDTFluxPerson& InPerson)
void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId) void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("%i Person in Participant %i"), InParticipant.GetTeammateNum(), InParticipant.Bib); UE_LOG(logDTFluxCore, Error, TEXT("%i Person in Participant %i"), InParticipant.GetTeammateNum(),
InParticipant.Bib);
FDTFluxContest TargetContest; FDTFluxContest TargetContest;
if(GetContestById(ContestId, TargetContest)) if (GetContestById(ContestId, TargetContest))
{ {
TArray<FDTFluxPerson> Teammate = InParticipant.Teammate; TArray<FDTFluxPerson> Teammate = InParticipant.Teammate;
for(auto& Person : InParticipant.Teammate) for (auto& Person : InParticipant.Teammate)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s"), UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s"),
*Person.FirstName, *Person.LastName, *Person.Gender); *Person.FirstName, *Person.LastName, *Person.Gender);
if(!PersonExists(Person)) if (!PersonExists(Person))
{ {
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s doesnot exists, adding..."), UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s doesnot exists, adding..."),
*Person.FirstName, *Person.LastName, *Person.Gender); *Person.FirstName, *Person.LastName, *Person.Gender);
AddPerson(Person); AddPerson(Person);
} }
} }
@ -68,10 +79,11 @@ bool UDTFluxModelAsset::PersonExists(const FDTFluxPerson& InPerson) const
FString UDTFluxModelAsset::GetContestNameForId(const int InContestID) FString UDTFluxModelAsset::GetContestNameForId(const int InContestID)
{ {
FDTFluxContest Contest; FDTFluxContest Contest;
if(!GetContestById(InContestID, Contest)) if (!GetContestById(InContestID, Contest))
{ {
UE_LOG(logDTFluxCore, Warning, TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"), UE_LOG(logDTFluxCore, Warning,
InContestID); TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"),
InContestID);
} }
return Contest.Name; return Contest.Name;
} }
@ -85,7 +97,7 @@ void UDTFluxModelAsset::UpdateParticipant(const FDTFluxParticipant& Participant)
{ {
// TODO : If update is on Bib we are totally lost as we search by bib. // TODO : If update is on Bib we are totally lost as we search by bib.
int Bib = Participant.Bib; int Bib = Participant.Bib;
if(Participants.Contains(Bib)) if (Participants.Contains(Bib))
{ {
TArray<FDTFluxPerson> InTeammate = Participant.Teammate; TArray<FDTFluxPerson> InTeammate = Participant.Teammate;
Participants[Bib].Elite = Participant.Elite; Participants[Bib].Elite = Participant.Elite;
@ -95,7 +107,7 @@ void UDTFluxModelAsset::UpdateParticipant(const FDTFluxParticipant& Participant)
Participants[Bib].Team = Participant.Team; Participants[Bib].Team = Participant.Team;
Participants[Bib].Status = Participant.Status; Participants[Bib].Status = Participant.Status;
//TODO : Update Person //TODO : Update Person
for(const auto& Person : InTeammate) for (const auto& Person : InTeammate)
{ {
//Don't know what to do... //Don't know what to do...
} }
@ -104,12 +116,23 @@ void UDTFluxModelAsset::UpdateParticipant(const FDTFluxParticipant& Participant)
void UDTFluxModelAsset::UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus) void UDTFluxModelAsset::UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
{ {
if(Participants.Contains(NewParticipantStatus.Bib)) if (Participants.Contains(NewParticipantStatus.Bib))
{ {
Participants[NewParticipantStatus.Bib].Status = NewParticipantStatus.Status; Participants[NewParticipantStatus.Bib].Status = NewParticipantStatus.Status;
} }
} }
bool UDTFluxModelAsset::GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant)
{
if (Participants.Contains(Bib))
{
OutParticipant = Participants[Bib];
return true;
}
return false;
}
void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings) void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings)
{ {
FDTFluxStageKey StageKey = InStageRankings.GetCompositeKey(); FDTFluxStageKey StageKey = InStageRankings.GetCompositeKey();

View File

@ -1,43 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Objects/DTFluxPursuitManager.h"
void UDTFluxPursuitManager::InitForStage(const FDTFluxStageRankings& StageRankings)
{
}
TArray<FDTFluxPursuit> UDTFluxPursuitManager::GetNextPursuits(int MaxPursuit)
{
//TODO : Implement me !!!
return PursuitParticipants;
}
TArray<FDTFluxPursuit> UDTFluxPursuitManager::GetPursuits(int FromIndex, int MaxPursuit)
{
//TODO : Implement me !!!
return PursuitParticipants;
}
FDateTime UDTFluxPursuitManager::GetMassStart()
{
//TODO : Implement me !!!
return MassStart;
}
FText UDTFluxPursuitManager::GetFormattedName(FDTFluxPursuit& InPursuit, const int MaxChar,
const FString OverflowChar)
{
return InPursuit.GetFormattedName(MaxChar, OverflowChar);
}
FText UDTFluxPursuitManager::DisplayPursuit(FDTFluxPursuit& InPursuit, const int MaxWidth,
const FString NameOverflowChar)
{
return InPursuit.DisplayPursuit(MaxWidth, NameOverflowChar);
}
bool UDTFluxPursuitManager::IsUnique(const FDTFluxPursuit& InPursuit)
{
return InPursuit.IsUnique();
}

View File

@ -18,8 +18,9 @@ bool UDTFluxParticipantFactory::CreateParticipantFomJson(const FString& JsonStri
} }
} }
bool UDTFluxParticipantFactory::CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject, FDTFluxParticipant& OutParticipant) bool UDTFluxParticipantFactory::CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject,
FDTFluxParticipant& OutParticipant)
{ {
OutParticipant = FDTFluxParticipant::CreateFromJson(JsonObject); OutParticipant = FDTFluxParticipant::CreateFromJson(JsonObject);
return OutParticipant == 0; return !OutParticipant.IsDefault();
} }

View File

@ -1,30 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Struct/DTFluxPursuitStructs.h"
FDTFluxPursuit::FDTFluxPursuit()
{
}
FDTFluxPursuit::~FDTFluxPursuit()
{
}
FText FDTFluxPursuit::GetFormattedName(const int MaxChar, const FString OverflowChar)
{
//TODO: Implement Me !!!
return Participants[0].GetConcatFormattedName(MaxChar, OverflowChar);
}
FText FDTFluxPursuit::DisplayPursuit(const int MaxWidth, const FString NameOverflowChar)
{
//TODO: Implement Me !!!
return Participants[0].GetConcatFormattedName(MaxWidth, NameOverflowChar);
}
bool FDTFluxPursuit::IsUnique() const
{
return Participants.Num() == 1;
}

View File

@ -4,8 +4,6 @@
#include "Types/Struct/DTFluxTeamListStruct.h" #include "Types/Struct/DTFluxTeamListStruct.h"
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person) void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
{ {
Teammate.Add(Person); Teammate.Add(Person);
@ -15,19 +13,24 @@ void FDTFluxParticipant::AddTeammate(const FString LastName, const FString First
{ {
} }
FText FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars) FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars) const
{ {
// Vérifie les cas limites // Vérifie les cas limites
if (MaxChar <= 0) if (MaxChar <= 0)
{ {
return FText::GetEmpty(); return "";
} }
FString FirstName; FString FirstName;
FString LastName; FString LastName;
if(IsTeam()) if (IsTeam())
{ {
LastName = Team; LastName = Team;
} }
else
{
FirstName = Teammate[0].FirstName;
LastName = Teammate[0].LastName;
}
// Récupère la première lettre du prénom en majuscule // Récupère la première lettre du prénom en majuscule
FString Initial; FString Initial;
if (!FirstName.IsEmpty()) if (!FirstName.IsEmpty())
@ -40,6 +43,7 @@ FText FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString Over
// Construction du nom final // Construction du nom final
FString FullName = Initial + FormattedLastName; FString FullName = Initial + FormattedLastName;
UE_LOG(logDTFluxCore, Error, TEXT("FullName for Bib %i is %s"), Bib, *FullName);
// Tronque si nécessaire // Tronque si nécessaire
if (FullName.Len() > MaxChar) if (FullName.Len() > MaxChar)
@ -48,8 +52,7 @@ FText FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString Over
const int32 AvailableLength = MaxChar - Initial.Len(); const int32 AvailableLength = MaxChar - Initial.Len();
if (AvailableLength <= 0) if (AvailableLength <= 0)
{ {
// Pas assez de place pour le nom → juste l'initiale ? return Initial;
return FText::FromString(Initial);
} }
// Coupe le nom pour quil rentre dans la limite // Coupe le nom pour quil rentre dans la limite
@ -83,41 +86,41 @@ FText FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString Over
} }
} }
return FText::FromString(FullName); return FullName;
} }
FText FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) const
{ {
FString BibText = FString::FromInt(Bib) + " "; FString BibText = FString::FromInt(Bib) + " ";
FText FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar ); FString FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar);
return FText::FromString(BibText + FormattedName.ToString()); return BibText + FormattedName;
} }
// Constructeur privé depuis JSON // 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) , bIsMassStartParticipant(false)
, CurrentSplit(-1) , CurrentSplit(-1)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object")) UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object"))
for(uint8 Index = 1; ; Index++) for (uint8 Index = 1; ; Index++)
{ {
FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index); FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index);
FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index); FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index);
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index); FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index);
// max 10 Persons // max 10 Persons
if(Index >= 10) if (Index >= 10)
{ {
break; break;
} }
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey) if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
&& !JsonObject->HasField(GenderKey)) && !JsonObject->HasField(GenderKey))
{ {
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!")) UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
break; break;
@ -134,7 +137,6 @@ FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject
Teammate.Add(Person); Teammate.Add(Person);
} }
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num()); UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
} }
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject) FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
@ -147,7 +149,7 @@ int FDTFluxParticipant::GetTeammateNum() const
return Teammate.Num(); return Teammate.Num();
} }
bool FDTFluxParticipant::IsTeam() bool FDTFluxParticipant::IsTeam() const
{ {
return Teammate.Num() < 1; return Teammate.Num() < 1;
} }

View File

@ -1,9 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Struct/FDTFluxPoursuiteStruct.h"
FText FDTFluxPoursuite::GetParticipantFormatedName() const
{
return FText();
}

View File

@ -0,0 +1,4 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Types/Struct/FDTFluxPursuitInfo.h"

View File

@ -7,6 +7,7 @@
#include "Dom/JsonObject.h" #include "Dom/JsonObject.h"
#include "Types/Struct/DTFluxCompositeKey.h" #include "Types/Struct/DTFluxCompositeKey.h"
#include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxRaceDataStructs.h"
#include "Types/Struct/DTFluxSplitSensor.h"
#include "DTFluxModelAsset.generated.h" #include "DTFluxModelAsset.generated.h"
@ -19,8 +20,8 @@ class DTFLUXCORE_API UDTFluxModelAsset : public UObject
{ {
GENERATED_BODY() GENERATED_BODY()
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer); UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
public:
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere) UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString EventName = "MyEvent"; FString EventName = "MyEvent";
@ -30,7 +31,7 @@ public:
UPROPERTY(BlueprintReadWrite, EditAnywhere) UPROPERTY(BlueprintReadWrite, EditAnywhere)
TMap<int /* Bib */, FDTFluxParticipant> Participants; TMap<int /* Bib */, FDTFluxParticipant> Participants;
UPROPERTY(BlueprintReadOnly, EditAnywhere) UPROPERTY(BlueprintReadOnly, EditAnywhere)
TMap<FString /* ContestName */, FDTFluxContest> Contests; TMap<FString /* ContestName */, FDTFluxContest> Contests;
@ -44,11 +45,15 @@ public:
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings; TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
void AddContest(const FDTFluxContest &Contest); void AddContest(const FDTFluxContest& Contest);
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
bool GetContestById(const int InContestId, FDTFluxContest& OutContest); bool GetContestById(const int InContestId, FDTFluxContest& OutContest);
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
bool GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage);
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
void AddPerson(const FDTFluxPerson& InPerson); void AddPerson(const FDTFluxPerson& InPerson);
@ -81,4 +86,7 @@ public:
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
void UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus); void UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
bool GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant);
}; };

View File

@ -7,23 +7,68 @@
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EDTFluxRequestType : uint8 enum class EDTFluxApiDataType : uint8
{ {
None = 0 UMETA(DisplayName="None"), None = 0 UMETA(DisplayName="None"),
ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
StageRanking = 2 UMETA(DisplayName="stage-ranking"), // Types bidirectionnels (requête/réponse)
SplitRanking = 3 UMETA(DisplayName="split-ranking"), ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
TeamList = 4 UMETA(DisplayName="team-list"), StageRanking = 2 UMETA(DisplayName="stage-ranking"),
RaceData = 5 UMETA(DisplayName="race-data"), SplitRanking = 3 UMETA(DisplayName="split-ranking"),
TeamList = 4 UMETA(DisplayName="team-list"),
RaceData = 5 UMETA(DisplayName="race-data"),
// Types uniquement réponse (push du serveur)
TeamUpdate = 10 UMETA(DisplayName="team-update"),
StatusUpdate = 11 UMETA(DisplayName="status-update"),
SplitSensor = 12 UMETA(DisplayName="split-sensor"),
// Types système
Error = 99 UMETA(DisplayName="error"),
}; };
// Alias pour clarifier l'usage
using EDTFluxRequestType = EDTFluxApiDataType;
using EDTFluxResponseType = EDTFluxApiDataType;
// Utilitaires pour valider les usages
namespace DTFluxDataTypeUtils
{
inline bool CanBeRequested(EDTFluxApiDataType Type)
{
return static_cast<uint8>(Type) >= 1 && static_cast<uint8>(Type) <= 5;
}
inline bool IsPushOnly(EDTFluxApiDataType Type)
{
return static_cast<uint8>(Type) >= 10 && static_cast<uint8>(Type) <= 12;
}
inline bool IsValidResponseType(EDTFluxApiDataType Type)
{
return Type != EDTFluxApiDataType::None;
}
}
//
// UENUM(BlueprintType)
// enum class EDTFluxRequestType : uint8
// {
// None = 0 UMETA(DisplayName="None"),
// ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
// StageRanking = 2 UMETA(DisplayName="stage-ranking"),
// SplitRanking = 3 UMETA(DisplayName="split-ranking"),
// TeamList = 4 UMETA(DisplayName="team-list"),
// RaceData = 5 UMETA(DisplayName="race-data"),
// };
UENUM(BlueprintType) UENUM(BlueprintType)
enum class EDTFluxConnectionStatus : uint8 enum class EDTFluxConnectionStatus : uint8
{ {
Unset = 0 UMETA(DisplayName="Unset"), Unset = 0 UMETA(DisplayName="Unset"),
Connected = 1 << 0 UMETA(DisplayName="Connected"), Connected = 1 << 0 UMETA(DisplayName="Connected"),
Error = 1 << 1 UMETA(DisplayName="Error"), Error = 1 << 1 UMETA(DisplayName="Error"),
Closed = 1 << 2 UMETA(DisplayName="Closed"), Closed = 1 << 2 UMETA(DisplayName="Closed"),
NotConnected= 1 << 3 UMETA(DisplayName="NotConnected") NotConnected = 1 << 3 UMETA(DisplayName="NotConnected")
}; };

View File

@ -56,7 +56,7 @@ ENUM_CLASS_FLAGS(EDTFluxSplitType);
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true)) UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
enum EDTFluxSortingFilter : uint8 enum class EDTFluxSortingFilter : uint8
{ {
None = 0b00000000 UMETA(DisplayName="No Sorting"), None = 0b00000000 UMETA(DisplayName="No Sorting"),
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"), IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
@ -70,3 +70,20 @@ enum EDTFluxSortingFilter : uint8
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank") DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank")
}; };
ENUM_CLASS_FLAGS(EDTFluxSortingFilter); ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
UENUM(BlueprintType)
enum class EDTFluxSortingRankingType: uint8
{
Rank = 0b00000000 UMETA(DisplayName="Rank (Default)"),
Name = 0b00000001 UMETA(DisplayName="Name"),
Bib = 0b00000010 UMETA(DisplayName="Bib"),
TimeSwim = 0b00000100 UMETA(DisplayName="Swimming Time"),
TimeTransition = 0b00001000 UMETA(DisplayName="Transition Time"),
TimeRun = TimeSwim|TimeTransition UMETA(DisplayName="Running Time"),
StartTime = 0b00001110 UMETA(DisplayName="StartTime"),
Gap = 0b00010000 UMETA(DisplayName="StartTime"),
SwimSpeed = 0b00100000 UMETA(DisplayName="StartTime"),
RunningSpeed = 0b01000000 UMETA(DisplayName="StartTime"),
TotalSpeed = 0b10000000 UMETA(DisplayName="StartTime"),
};

View File

@ -1,63 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Types/Struct/DTFluxPursuitStructs.h"
#include "Types/Struct/DTFluxRankingStructs.h"
#include "UObject/Object.h"
#include "DTFluxPursuitManager.generated.h"
/**
*
*/
UCLASS(BlueprintType)
class DTFLUXCORE_API UDTFluxPursuitManager : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"), Transient)
TArray<FDTFluxPursuit> PursuitParticipants;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
FDateTime MassStart;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
int ContestId;
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
int StageId;
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
void InitForStage(const FDTFluxStageRankings& StageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
TArray<FDTFluxPursuit> GetNextPursuits(int MaxPursuit);
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
TArray<FDTFluxPursuit> GetPursuits(int FromIndex = 0, int MaxPursuit=10);
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
FDateTime GetMassStart();
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
static FText GetFormattedName(FDTFluxPursuit& InPursuit, const int MaxChar = 10, const FString OverflowChar = FString(TEXT("...")));
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
static FText DisplayPursuit(FDTFluxPursuit& InPursuit, const int MaxWidth = 14, const FString NameOverflowChar = FString(TEXT("...")));
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
static bool IsUnique(const FDTFluxPursuit& InPursuit);
protected:
private:
UPROPERTY(VisibleAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
int CurrentIndex;
};

View File

@ -6,95 +6,106 @@
#include "UObject/Object.h" #include "UObject/Object.h"
#include "DTFluxCompositeKey.generated.h" #include "DTFluxCompositeKey.generated.h"
/** USTRUCT()
* struct FDTFluxCompositeKey
*/
USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxStageKey
{ {
GENERATED_BODY() GENERATED_BODY()
FDTFluxStageKey() = default;
FDTFluxStageKey(const int InContestId, const int InStageId )
:ContestId(InContestId)
, StageId(InStageId){};
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int ContestId = -1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int StageId = -1;
friend uint32 GetTypeHash(const FDTFluxStageKey& Key)
{
return HashCombine(
GetTypeHash(Key.ContestId),
GetTypeHash(Key.StageId)
);
}
bool operator==(const FDTFluxStageKey& Other) const
{
return ContestId == Other.ContestId && StageId == Other.StageId;
}
FString GetDisplayName() const
{
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
}
FText GetTooltipText() const
{
return FText::Format(INVTEXT("Contest{0}|Stage{1}"),
FText::AsNumber(ContestId),
FText::AsNumber(StageId));
}
}; };
/** /**
* *
*/ */
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxSplitKey struct DTFLUXCORE_API FDTFluxStageKey : public FDTFluxCompositeKey
{ {
GENERATED_BODY() GENERATED_BODY()
FDTFluxSplitKey() = default; FDTFluxStageKey() = default;
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId )
:ContestId(InContestId)
, StageId(InStageId)
, SplitId(InSplitId){};
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int ContestId = -1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int StageId = -1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int SplitId = -1;
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key) FDTFluxStageKey(const int InContestId, const int InStageId)
{ : ContestId(InContestId)
return HashCombine( , StageId(InStageId)
GetTypeHash(Key.ContestId), {
GetTypeHash(Key.StageId), };
GetTypeHash(Key.SplitId)
);
}
bool operator==(const FDTFluxSplitKey& Other) const
{
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
}
FString GetDisplayName() const
{
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
}
FText GetTooltipText() const UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
{ int ContestId = -1;
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"), UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
FText::AsNumber(ContestId), int StageId = -1;
FText::AsNumber(StageId),
FText::AsNumber(SplitId) friend uint32 GetTypeHash(const FDTFluxStageKey& Key)
); {
} return HashCombine(
GetTypeHash(Key.ContestId),
GetTypeHash(Key.StageId)
);
}
bool operator==(const FDTFluxStageKey& Other) const
{
return ContestId == Other.ContestId && StageId == Other.StageId;
}
FString GetDisplayName() const
{
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
}
FText GetTooltipText() const
{
return FText::Format(INVTEXT("Contest{0}|Stage{1}"),
FText::AsNumber(ContestId),
FText::AsNumber(StageId));
}
}; };
/**
*
*/
USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxSplitKey : public FDTFluxCompositeKey
{
GENERATED_BODY()
FDTFluxSplitKey() = default;
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId)
: ContestId(InContestId)
, StageId(InStageId)
, SplitId(InSplitId)
{
};
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int ContestId = -1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int StageId = -1;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
int SplitId = -1;
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key)
{
return HashCombine(
GetTypeHash(Key.ContestId),
GetTypeHash(Key.StageId),
GetTypeHash(Key.SplitId)
);
}
bool operator==(const FDTFluxSplitKey& Other) const
{
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
}
FString GetDisplayName() const
{
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
}
FText GetTooltipText() const
{
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
FText::AsNumber(ContestId),
FText::AsNumber(StageId),
FText::AsNumber(SplitId)
);
}
};

View File

@ -1,38 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxTeamListStruct.h"
#include "DTFluxPursuitStructs.generated.h"
/**
*
*/
USTRUCT(BlueprintType, Blueprintable)
struct DTFLUXCORE_API FDTFluxPursuit
{
GENERATED_BODY()
public:
FDTFluxPursuit();
FDTFluxPursuit(const TArray<FDTFluxParticipant>& InParticipants) : Participants(InParticipants){};
~FDTFluxPursuit();
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="DTFlux|Pursuit")
TArray<FDTFluxParticipant> Participants;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="DTFlux|Pursuit")
FDateTime StartTime;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category="DTFlux|Pursuit")
int IndexMultiple = 0;
FText GetFormattedName(const int MaxChar = 15, const FString OverflowChar = FString(TEXT("...")));
FText DisplayPursuit(const int MaxWidth = 20, const FString NameOverflowChar = FString(TEXT("...")));
bool IsUnique() const;
};

View File

@ -17,6 +17,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
struct DTFLUXCORE_API FDTFluxSplit struct DTFLUXCORE_API FDTFluxSplit
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
int SplitId = -1; int SplitId = -1;
@ -28,7 +29,6 @@ public:
// // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp); // // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp);
// void SortByRank(); // void SortByRank();
// TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0); // TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0);
}; };
/** /**
@ -39,6 +39,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
struct DTFLUXCORE_API FDTFluxStage struct DTFLUXCORE_API FDTFluxStage
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
int StageId; int StageId;
@ -60,6 +61,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
struct DTFLUXCORE_API FDTFluxContest struct DTFLUXCORE_API FDTFluxContest
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
int ContestId = -1; int ContestId = -1;
@ -73,18 +75,89 @@ public:
TArray<FDTFluxSplit> Splits; TArray<FDTFluxSplit> Splits;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
FDateTime Date; FDateTime Date;
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
FDateTime EndTime;
UPROPERTY()
int LastStageId = -1;
bool IsFinished() const;
inline void UpdateEndTime();
int GetLastStageId();
void UpdateLastStageId();
FDTFluxStage& GetLastStage() 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
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY() UPROPERTY()
// ReSharper disable once IdentifierTypo // ReSharper disable once IdentifierTypo
TArray<FDTFluxContest> Datas; TArray<FDTFluxContest> Datas;
}; };

View File

@ -16,6 +16,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXCORE_API FDTFluxContestRanking struct DTFLUXCORE_API FDTFluxContestRanking
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
int Bib; int Bib;
@ -31,7 +32,7 @@ public:
FString SpeedRunningAverage; FString SpeedRunningAverage;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere); UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
FString SpeedTotalAverage; FString SpeedTotalAverage;
void Dump () const; void Dump() const;
}; };
@ -39,13 +40,14 @@ USTRUCT(BlueprintType)
struct FDTFluxContestRankings struct FDTFluxContestRankings
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
TArray<FDTFluxContestRanking> Rankings; TArray<FDTFluxContestRanking> Rankings;
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
int ContestId; int ContestId;
//TODO check if necessary ??? //TODO check if necessary ???
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
FString ContestName; FString ContestName;
void SetName(const FString Name) void SetName(const FString Name)
@ -62,6 +64,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
struct DTFLUXCORE_API FDTFluxDetailedRankingItem struct DTFLUXCORE_API FDTFluxDetailedRankingItem
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
int Bib; int Bib;
@ -82,24 +85,24 @@ public:
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
FDateTime StartTime; FDateTime StartTime;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
float SpeedRunning; float SpeedRunning;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
float SpeedTotal; float SpeedTotal;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
float SpeedSwim; float SpeedSwim;
void Dump() const; void Dump() const;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDTFluxDetailedRankings struct FDTFluxDetailedRankings
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
int ContestId; int ContestId = -1;
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
int StageId; int StageId = -1;
TArray<FDTFluxDetailedRankingItem> Rankings; TArray<FDTFluxDetailedRankingItem> Rankings;
}; };
@ -139,29 +142,61 @@ struct FDTFluxStageRankings : public FDTFluxDetailedRankings
{ {
return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId); return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId);
} }
inline FDTFluxStageKey GetCompositeKey() const inline FDTFluxStageKey GetCompositeKey() const
{ {
return FDTFluxStageKey(ContestId, StageId); return FDTFluxStageKey(ContestId, StageId);
} }
};
inline bool IsInitialized() const
{
return ContestId > 0 && StageId > 0;
}
void Initialize()
{
for (auto& Ranking : Rankings)
{
FDateTime RankingStartTime;
if (Ranking.TimeStart != "")
{
TArray<FString> Exploded;
Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true);
if (Exploded.Num() == 3)
{
FDateTime Now = FDateTime::Now();
RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(),
FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]),
FCString::Atoi(*Exploded[2]));
}
}
Ranking.StartTime = RankingStartTime;
}
}
};
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDTFluxSplitRankings : public FDTFluxDetailedRankings struct FDTFluxSplitRankings : public FDTFluxDetailedRankings
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere) UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
int SplitId; int SplitId;
inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings) inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings)
{ {
return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId); return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId);
} }
inline FDTFluxSplitKey GetCompositeKey() const inline FDTFluxSplitKey GetCompositeKey() const
{ {
return FDTFluxSplitKey(ContestId, StageId, SplitId); return FDTFluxSplitKey(ContestId, StageId, SplitId);
} }
inline bool IsInitialized() const
{
return ContestId > 0 && StageId > 0 && SplitId > 0;
}
}; };

View File

@ -4,7 +4,6 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UObject/Object.h" #include "UObject/Object.h"
#include "DTFluxCoreModule.h"
#include "Types/Enum/DTFluxModelEnums.h" #include "Types/Enum/DTFluxModelEnums.h"
#include "DTFluxTeamListStruct.generated.h" #include "DTFluxTeamListStruct.generated.h"
@ -12,6 +11,7 @@ USTRUCT()
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY() UPROPERTY()
FString Type = "team-list-item"; FString Type = "team-list-item";
@ -44,13 +44,11 @@ public:
}; };
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;
@ -66,20 +64,23 @@ public:
bool operator==(const FDTFluxPerson& Right) const bool operator==(const FDTFluxPerson& Right) const
{ {
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower() return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
== Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower(); == Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
} }
bool operator==(const int Length) const bool operator==(const int Length) const
{ {
return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length; return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length;
} }
bool operator!=(const int Length) const bool operator!=(const int Length) const
{ {
return !(*this == Length); return !(*this == Length);
} }
bool operator!=(const FDTFluxPerson& Right) const bool operator!=(const FDTFluxPerson& Right) const
{ {
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower() return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
!= Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower(); != Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
} }
}; };
@ -91,30 +92,42 @@ struct DTFLUXCORE_API FDTFluxParticipant
friend class UDTFluxModelAsset; friend class UDTFluxModelAsset;
friend class UDTFluxParticipantFactory; friend class UDTFluxParticipantFactory;
public: public:
// Constructeur public par défaut requis par Unreal // Constructeur public par défaut requis par Unreal
FDTFluxParticipant() FDTFluxParticipant()
: Bib(-1) : Bib(-1)
,ContestId(-1) , ContestId(-1)
, Elite(false) , Elite(false)
, Status(static_cast<EDTFluxParticipantStatusType>(0)) , Status(static_cast<EDTFluxParticipantStatusType>(0))
, bIsMassStartParticipant(false) , bIsMassStartParticipant(false)
, CurrentSplit(-1) , CurrentSplit(-1)
{ {
Teammate.Reset(); Teammate.Reset();
} }
bool operator == ( int Rhs) const /**
* 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 Rhs == 0 && Bib == -1 && Team.IsEmpty() && Club.IsEmpty() && ContestId == -1 return Bib == -1
&& Teammate.IsEmpty(); && 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) 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)
@ -135,21 +148,55 @@ public:
// void Dump() const; // void Dump() 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 GetFormattedName(const int MaxChar = 15, const FString OverflowChar = FString("..."));
FText GetConcatFormattedName(const int MaxChar = 20, const FString OverflowChar = FString("...")); FText GetFormattedNameText(const int MaxChar = 15, const FString OverflowChar = FString("...")) const
{
return FText::FromString(GetFormattedName(MaxChar, OverflowChar));
};
FText GetConcatFormattedNameText(const int MaxChar = 20, const FString OverflowChar = FString("...")) 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 FString OverflowChar = FString("..."))
{
return Participant.GetConcatFormattedName(MaxChar, OverflowChar);
};
static FText GetFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15,
const FString OverflowChar = FString("..."))
{
return Participant.GetFormattedNameText();
};
static FText GetConcatFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar = 15,
const FString OverflowChar = FString("..."))
{
return Participant.GetConcatFormattedNameText();
};
const TArray<FDTFluxPerson> GetTeammate() const { return Teammate; }
private: private:
// --- Constructeur privé --- // --- Constructeur privé ---
explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject); explicit FDTFluxParticipant(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) // Méthode publique pour construire à partir d'un JSON (utilisée par la factory)
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject); static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
int GetTeammateNum() const; int GetTeammateNum() const;
bool IsTeam(); bool IsTeam() const;
}; };
@ -162,10 +209,11 @@ USTRUCT(BlueprintType)
struct DTFLUXCORE_API FDTFluxTeamListDefinition struct DTFLUXCORE_API FDTFluxTeamListDefinition
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY() UPROPERTY()
// ReSharper disable once IdentifierTypo // ReSharper disable once IdentifierTypo
TArray<FDTFluxParticipant> Participants; TArray<FDTFluxParticipant> Participants;
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
@ -175,11 +223,14 @@ struct FDTFluxTeamStatusUpdate
public: public:
FDTFluxTeamStatusUpdate() = default; FDTFluxTeamStatusUpdate() = default;
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
:Bib(InBib)
, Status(static_cast<EDTFluxParticipantStatusType>(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")

View File

@ -1,27 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxTeamListStruct.h"
#include "UObject/Object.h"
#include "FDTFluxPoursuiteStruct.generated.h"
/**
* @struct FDTFluxPoursuite
* Representing a
*/
USTRUCT(BlueprintType, Category="DTFlux|Poursuite")
struct FDTFluxPoursuite
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Poursuite")
FDTFluxParticipant Participant;
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Poursuite")
FDateTime TimeStart;
FText GetParticipantFormatedName() const;
};

View File

@ -0,0 +1,32 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxTeamListStruct.h"
#include "UObject/Object.h"
#include "FDTFluxPursuitInfo.generated.h"
USTRUCT(Blueprintable, BlueprintType)
struct FDTFluxPursuitInfo
{
GENERATED_BODY()
FDTFluxPursuitInfo() = default;
FDTFluxPursuitInfo(int InBib, FDateTime InStartTime, bool InbIsMassStart = false) :
bIsMassStart(InbIsMassStart),
Bib(InBib),
StartTime(InStartTime)
{
};
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
bool bIsMassStart = false;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
int Bib = -1;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FDateTime StartTime;
};

View File

@ -2,31 +2,31 @@
public class DTFluxCoreSubsystem : ModuleRules public class DTFluxCoreSubsystem : ModuleRules
{ {
public DTFluxCoreSubsystem(ReadOnlyTargetRules Target) : base(Target) public DTFluxCoreSubsystem(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core",
} }
); );
PrivateDependencyModuleNames.AddRange( PrivateDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate", "Slate",
"SlateCore", "SlateCore",
"UnrealEd", "UnrealEd",
"DTFluxNetwork", "DTFluxNetwork",
"DTFluxProjectSettings", "DTFluxProjectSettings",
"DTFluxCore", "DTFluxCore",
"JsonUtilities", "JsonUtilities",
"Json" "Json"
} }
); );
} }
} }

View File

@ -15,20 +15,19 @@ void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{ {
Super::Initialize(Collection); Super::Initialize(Collection);
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("[UDTFluxCoreSubsystem] Initializing...")); UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("[UDTFluxCoreSubsystem] Initializing..."));
if(!DataStorage) if (!DataStorage)
{ {
const UDTFluxGeneralSettings* GeneralSettings = GetDefault<UDTFluxGeneralSettings>(); const UDTFluxGeneralSettings* GeneralSettings = GetDefault<UDTFluxGeneralSettings>();
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GeneralSettings->ModelAsset; TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GeneralSettings->ModelAsset;
DataStorage = ModelAsset.LoadSynchronous(); DataStorage = ModelAsset.LoadSynchronous();
if(!DataStorage) if (!DataStorage)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("DataStorage Not Valid")); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Not Valid"));
} }
} }
//TODO REMOVE This as it's only for testing purpose //TODO REMOVE This as it's only for testing purpose
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>(); NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
if(NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected) if (NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected)
{ {
RegisterDelegates(); RegisterDelegates();
} }
@ -41,11 +40,11 @@ void UDTFluxCoreSubsystem::Deinitialize()
void UDTFluxCoreSubsystem::SaveDataStorage() void UDTFluxCoreSubsystem::SaveDataStorage()
{ {
if(!DataStorage->MarkPackageDirty()) if (!DataStorage->MarkPackageDirty())
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to make package dirty !!!")) UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to make package dirty !!!"))
} }
if(DataStorage) if (DataStorage)
{ {
UEditorLoadingAndSavingUtils::SavePackages({DataStorage->GetPackage()}, true); UEditorLoadingAndSavingUtils::SavePackages({DataStorage->GetPackage()}, true);
} }
@ -53,31 +52,66 @@ void UDTFluxCoreSubsystem::SaveDataStorage()
void UDTFluxCoreSubsystem::RegisterDelegates() void UDTFluxCoreSubsystem::RegisterDelegates()
{ {
if(NetworkSubsystem) if (NetworkSubsystem)
{ {
NetworkSubsystem->OnReceivedRaceData().BindUFunction(this, "ProcessRaceData"); // ✅ Binding avec vérification automatique des signatures
NetworkSubsystem->OnReceivedTeamList().BindUFunction(this, "ProcessTeamList"); // Si la signature ne correspond pas, erreur de compilation !
NetworkSubsystem->OnReceivedContestRanking().BindUFunction(this, "ProcessContestRanking");
NetworkSubsystem->OnReceivedStageRanking().BindUFunction(this, "ProcessStageRanking"); NetworkSubsystem->OnReceivedRaceData().BindUObject(
NetworkSubsystem->OnReceivedSplitRanking().BindUFunction(this, "ProcessSplitRanking"); this,
NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamList"); &UDTFluxCoreSubsystem::ProcessRaceData
NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUFunction(this, "ProcessTeamStatusUpdate"); );
NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamUpdate");
NetworkSubsystem->OnReceivedSplitSensor().BindUFunction(this, "ProcessSplitSensor"); NetworkSubsystem->OnReceivedTeamList().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessTeamList
);
NetworkSubsystem->OnReceivedContestRanking().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessContestRanking
);
NetworkSubsystem->OnReceivedStageRanking().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessStageRanking
);
NetworkSubsystem->OnReceivedSplitRanking().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessSplitRanking
);
// ⚠️ ATTENTION : Vous avez un doublon ici !
// NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamList");
NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessTeamStatusUpdate
);
NetworkSubsystem->OnReceivedTeamUpdate().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessTeamUpdate
);
NetworkSubsystem->OnReceivedSplitSensor().BindUObject(
this,
&UDTFluxCoreSubsystem::ProcessSplitSensor
);
} }
} }
void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition) void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition)
{ {
if (RaceDataDefinition.Datas.Num() > 0)
if( RaceDataDefinition.Datas.Num() > 0 )
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"), *RaceDataDefinition.Datas[0].Name); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"),
if(DataStorage != nullptr) *RaceDataDefinition.Datas[0].Name);
if (DataStorage != nullptr)
{ {
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName);
for(auto Contest : RaceDataDefinition.Datas) for (auto Contest : RaceDataDefinition.Datas)
{ {
DataStorage->AddContest(Contest); DataStorage->AddContest(Contest);
} }
@ -90,32 +124,31 @@ void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefini
return; return;
} }
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!")); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!"));
} }
void UDTFluxCoreSubsystem::ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition) void UDTFluxCoreSubsystem::ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"), TeamListDefinition.Participants.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"),
for(const auto& Participant : TeamListDefinition.Participants) TeamListDefinition.Participants.Num());
for (const auto& Participant : TeamListDefinition.Participants)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %i in %i ContestId"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %i in %i ContestId"),
Participant.Bib, Participant.ContestId ); Participant.Bib, Participant.ContestId);
DataStorage->AddParticipant(Participant, Participant.ContestId); DataStorage->AddParticipant(Participant, Participant.ContestId);
} }
SaveDataStorage();
} }
void UDTFluxCoreSubsystem::ProcessContestRanking(const FDTFluxContestRankings& ContestRankings) void UDTFluxCoreSubsystem::ProcessContestRanking(const FDTFluxContestRankings& ContestRankings)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received ContestRankings with %i Items"), ContestRankings.Rankings.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received ContestRankings with %i Items"),
ContestRankings.Rankings.Num());
FDTFluxContestRankings NewContestRankings = ContestRankings; FDTFluxContestRankings NewContestRankings = ContestRankings;
NewContestRankings.SetName( DataStorage->GetContestNameForId(ContestRankings.ContestId)); NewContestRankings.SetName(DataStorage->GetContestNameForId(ContestRankings.ContestId));
DataStorage->AddContestRanking(NewContestRankings); DataStorage->AddContestRanking(NewContestRankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"), *NewContestRankings.ContestName); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"),
*NewContestRankings.ContestName);
SaveDataStorage(); SaveDataStorage();
} }
@ -125,12 +158,12 @@ void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& Stage
DataStorage->UpdateOrCreateStageRanking(StageRankings); DataStorage->UpdateOrCreateStageRanking(StageRankings);
SaveDataStorage(); SaveDataStorage();
} }
void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings) void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num());
DataStorage->UpdateOrCreateSplitRanking(SplitRankings); DataStorage->UpdateOrCreateSplitRanking(SplitRankings);
SaveDataStorage(); SaveDataStorage();
} }
void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus) void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
@ -138,25 +171,33 @@ void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate
DataStorage->UpdateParticipantStatus(NewParticipantStatus); DataStorage->UpdateParticipantStatus(NewParticipantStatus);
} }
void UDTFluxCoreSubsystem::ProcessTeamUpdate(const FDTFluxParticipant& Participant) void UDTFluxCoreSubsystem::ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinitiont)
{ {
DataStorage->UpdateParticipant(Participant); for (const auto& Participant : TeamListDefinitiont.Participants)
{
DataStorage->UpdateParticipant(Participant);
}
} }
void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo) void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo)
{ {
FDTFluxContest Contest; FDTFluxContest Contest;
FDTFluxStageKey StageKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId); FDTFluxStageKey StageKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId);
FDTFluxStage Stage;
DataStorage->GetStage(StageKey, Stage);
FDTFluxParticipant Participant;
DataStorage->GetParticipantByBib(SplitSensorInfo.Bib, Participant);
DataStorage->GetContestById(SplitSensorInfo.ContestId, Contest); DataStorage->GetContestById(SplitSensorInfo.ContestId, Contest);
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("%s %s Split %i Sensor for Participant [Bib] %i "), UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("%s|%s Split %i Sensor for Participant [Bib] %i [FullName] %s"),
*Contest.Name, *Contest.Stages[SplitSensorInfo.StageId].Name, *Contest.Name, *Stage.Name,
SplitSensorInfo.SplitId , SplitSensorInfo.Bib); SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName());
} }
void UDTFluxCoreSubsystem::SendRequest(const FString& Message) void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
{ {
if(NetworkSubsystem) if (NetworkSubsystem)
{ {
NetworkSubsystem->SendMessage(Message); NetworkSubsystem->SendMessage(Message);
} }
@ -192,7 +233,7 @@ void UDTFluxCoreSubsystem::SendStageRankingRequest(int InContestId, int InStageI
} }
void UDTFluxCoreSubsystem::RequestAllStageRankingOfContest(int InContestId, int InStageId, void UDTFluxCoreSubsystem::RequestAllStageRankingOfContest(int InContestId, int InStageId,
bool bShouldIncludeSplitRanking) bool bShouldIncludeSplitRanking)
{ {
// TODO Implement this // TODO Implement this
} }
@ -207,6 +248,16 @@ void UDTFluxCoreSubsystem::RequestAllSplitRankingOfContest(int InContestId, int
// TODO Implement this // TODO Implement this
} }
FDTFluxStageRankings UDTFluxCoreSubsystem::GetStageRankings(FDTFluxStageKey StageKey)
{
if (DataStorage->StageRankings.Contains(StageKey))
{
return DataStorage->StageRankings[StageKey];
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Cannot find StageRankings for key [%s]"), *StageKey.GetDisplayName());
return FDTFluxStageRankings();
}
void UDTFluxCoreSubsystem::RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId) void UDTFluxCoreSubsystem::RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId)
{ {
// TODO Implement this // TODO Implement this
@ -216,3 +267,58 @@ void UDTFluxCoreSubsystem::RefreshStorage()
{ {
// TODO Implement this // TODO Implement this
} }
TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId()
{
return GetContestsIdForTime(FDateTime::Now());
}
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetCurrentContests()
{
return GetContestsForTime(FDateTime::Now());
}
TArray<int> UDTFluxCoreSubsystem::GetContestsIdForTime(const FDateTime Time)
{
TArray<int> Contests;
for (const auto& Pair : DataStorage->Contests)
{
FDTFluxContest Contest = Pair.Value;
int ContestId = Contest.ContestId;
if (Contest.Date < Time && Contest.EndTime > Time)
{
Contests.Add(ContestId);
}
}
return Contests;
}
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContestsForTime(const FDateTime Time)
{
TArray<FDTFluxContest> Contests;
for (const auto& Pair : DataStorage->Contests)
{
FDTFluxContest Contest = Pair.Value;
int ContestId = Contest.ContestId;
if (Contest.Date < Time && Contest.EndTime > Time)
{
Contests.Add(Contest);
}
}
return Contests;
}
void UDTFluxCoreSubsystem::RequestRankingsForStages(TArray<FDTFluxStage> RequestedStages) const
{
}
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContests()
{
if (DataStorage)
{
TArray<FDTFluxContest> OutContests;
DataStorage->Contests.GenerateValueArray(OutContests);
return OutContests;
}
return TArray<FDTFluxContest>();
}

View File

@ -0,0 +1,103 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "DTFluxCoreSubsystemTools.h"
void UDTFluxCoreSubsystemTools::FilterContestRankings(FDTFluxContestRankings& ContestRankings,
const EDTFluxSortingRankingType RankingType, TArray<FDTFluxContestRanking>& OutContestRankings, bool bAscendant)
{
// On fait une copie locale des Rankings
TArray<FDTFluxContestRanking> ContestArray = ContestRankings.Rankings;
// Tri par type + direction
ContestArray.Sort([RankingType, bAscendant](const FDTFluxContestRanking& A, const FDTFluxContestRanking& B)
{
switch (RankingType)
{
case EDTFluxSortingRankingType::Rank:
return bAscendant ? A.Rank < B.Rank : A.Rank > B.Rank;
case EDTFluxSortingRankingType::Bib:
return bAscendant ? A.Bib < B.Bib : A.Bib > B.Bib;
case EDTFluxSortingRankingType::Gap:
return CompareTimeString(A.Gap, B.Gap, bAscendant);
case EDTFluxSortingRankingType::SwimSpeed:
return CompareSpeed(A.SpeedSwimAverage, B.SpeedSwimAverage, bAscendant);
case EDTFluxSortingRankingType::RunningSpeed:
return CompareSpeed(A.SpeedRunningAverage, B.SpeedRunningAverage, bAscendant);
case EDTFluxSortingRankingType::TotalSpeed:
return CompareSpeed(A.SpeedTotalAverage, B.SpeedTotalAverage, bAscendant);
default:
return CompareTimeString(A.Time, B.Time, bAscendant);
}
});
// Réaffecte les données triées
ContestRankings.Rankings = ContestArray;
OutContestRankings = ContestArray;
}
void UDTFluxCoreSubsystemTools::FilterStageRankings(FDTFluxStageRankings& InStageRankings,
const EDTFluxSortingRankingType RankingType, FDTFluxStageRankings& OutStageRankings, bool bAscendant)
{
// TArray<FDTFluxDetailedRankings> StageArray = static_cast<TDF>()InStageRankings.Rankings;
}
void UDTFluxCoreSubsystemTools::FilterSplitRankings(FDTFluxSplitRankings& SplitRankings,
const EDTFluxSortingRankingType RankinType, FDTFluxSplitRankings& OutSplitRankings, bool bAscendant)
{
}
float UDTFluxCoreSubsystemTools::ConvertTimeStringToSeconds(const FString& TimeString)
{
// Format attendu : "HH:MM:SS"
TArray<FString> Parts;
TimeString.ParseIntoArray(Parts, TEXT(":"), true);
if (Parts.Num() == 3)
{
const int32 Hours = FCString::Atoi(*Parts[0]);
const int32 Minutes = FCString::Atoi(*Parts[1]);
const int32 Seconds = FCString::Atoi(*Parts[2]);
return Hours * 3600 + Minutes * 60 + Seconds;
}
if (Parts.Num() == 2)
{
const int32 Minutes = FCString::Atoi(*Parts[0]);
const int32 Seconds = FCString::Atoi(*Parts[1]);
return 3600 + Minutes * 60 + Seconds;
}
if (Parts.Num() == 1)
{
return FCString::Atoi(*Parts[0]);
}
return -1.0f;
}
bool UDTFluxCoreSubsystemTools::CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant)
{
const float A_Time = ConvertTimeStringToSeconds(A_TimeStr);
const float B_Time = ConvertTimeStringToSeconds(B_TimeStr);
return bAscendant ? A_Time < B_Time : A_Time > B_Time;
}
bool UDTFluxCoreSubsystemTools::CompareSpeed(const FString& A_SpeedStr, const FString& B_SpeedStr, bool bAscendant)
{
float A_Speed = FCString::Atof(*A_SpeedStr);
float B_Speed = FCString::Atof(*B_SpeedStr);
if (bAscendant)
{
return A_Speed < B_Speed;
}
return A_Speed > B_Speed;
}

View File

@ -3,8 +3,8 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "Containers/Deque.h"
#include "Subsystems/EngineSubsystem.h" #include "Subsystems/EngineSubsystem.h"
#include "Types/Enum/DTfluxCoreEnum.h"
#include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxRaceDataStructs.h"
#include "Types/Struct/DTFluxTeamListStruct.h" #include "Types/Struct/DTFluxTeamListStruct.h"
#include "Types/Struct/DTFluxRankingStructs.h" #include "Types/Struct/DTFluxRankingStructs.h"
@ -12,8 +12,6 @@
#include "DTFluxCoreSubsystem.generated.h" #include "DTFluxCoreSubsystem.generated.h"
class UDTFluxNetworkSubsystem; class UDTFluxNetworkSubsystem;
/** Forward Decl */ /** Forward Decl */
class UDTFluxModelAsset; class UDTFluxModelAsset;
@ -26,70 +24,94 @@ class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitRankings, FDTFluxSplitRankings&, SplitRankings);
// TSharedPtr<FDTFluxParser> Parser;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDateTime, ReceivedAt, TArray<FDTFluxStageRanking>, SplitRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnSplitRankings OnSplitRankings; FOnSplitRankings OnSplitRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDateTime, ReceivedAt, TArray<FDTFluxStageRanking>, StageRankings); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStageRankings, FDTFluxStageRankings&, StageRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnStageRankings OnStageRankings; FOnStageRankings OnStageRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankings, FDateTime, ReceivedAt, TArray<FDTFluxContestRanking>, ContestRankings); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnContestRankings, FDTFluxContestRankings&, ContestRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnContestRankings OnContestRankings; FOnContestRankings OnContestRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamList, FDateTime, ReceivedAt, TArray<FDTFluxParticipant>, TeamList); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamList OnTeamList; FOnTeamList OnTeamList;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamStatusUpdate, FDTFluxParticipant, TeamUpdated);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamUpdate OnTeamUpdate;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamStatusUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdated);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamStatusUpdate OnTeamStatusUpdate; FOnTeamStatusUpdate OnTeamStatusUpdate;
DECLARE_DELEGATE_TwoParams(FOnRequestedStageRankings, const FDTFluxStageKey&, const FDTFluxContestRankings&);
FOnRequestedStageRankings OnRequestedStageRankings;
//
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList);
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
// FOnTeamUpdate OnTeamUpdate;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendTeamListRequest(); void SendTeamListRequest();
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendRaceDataRequest(); void SendRaceDataRequest();
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendContestRankingRequest(int InContestId); void SendContestRankingRequest(int InContestId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendStageRankingRequest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true); void SendStageRankingRequest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RequestAllStageRankingOfContest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true); void RequestAllStageRankingOfContest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendSplitRankingRequest(int InContestId, int InStageId, int InSplitId); void SendSplitRankingRequest(int InContestId, int InStageId, int InSplitId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RequestAllSplitRankingOfContest(int InContestId, int InStageId); void RequestAllSplitRankingOfContest(int InContestId, int InStageId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
FDTFluxStageRankings GetStageRankings(FDTFluxStageKey StageKey);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId); void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RefreshStorage(); void RefreshStorage();
UFUNCTION()
TArray<int> GetCurrentContestsId();
UFUNCTION()
TArray<FDTFluxContest> GetCurrentContests();
UFUNCTION()
TArray<int> GetContestsIdForTime(const FDateTime Time);
UFUNCTION()
TArray<FDTFluxContest> GetContestsForTime(const FDateTime Time);
UFUNCTION()
void RequestRankingsForStages(const TArray<FDTFluxStage> RequestedStages) const;
UFUNCTION()
TArray<FDTFluxContest> GetContests();
protected: protected:
// ~Subsystem Interface // ~Subsystem Interface
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
// ~Subsystem Interface // ~Subsystem Interface
UFUNCTION() UFUNCTION()
void SaveDataStorage(); void SaveDataStorage();
private: private:
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr; UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
@ -104,16 +126,17 @@ private:
UFUNCTION() UFUNCTION()
void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings); void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings);
UFUNCTION() UFUNCTION()
void ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus); void ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
UFUNCTION() UFUNCTION()
void ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo); void ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo);
UFUNCTION() UFUNCTION()
void ProcessTeamUpdate(const FDTFluxParticipant& Participant); void ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinition);
UFUNCTION() UFUNCTION()
void SendRequest(const FString& Message); void SendRequest(const FString& Message);
UFUNCTION() UFUNCTION()
void RegisterDelegates(); void RegisterDelegates();
UPROPERTY() UPROPERTY()
UDTFluxModelAsset* DataStorage = nullptr; UDTFluxModelAsset* DataStorage = nullptr;
}; };

View File

@ -0,0 +1,37 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/Enum/DTFluxModelEnums.h"
#include "Types/Struct/DTFluxRankingStructs.h"
#include "DTFluxCoreSubsystemTools.generated.h"
/**
*
*/
UCLASS()
class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystemTools : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static void FilterContestRankings(FDTFluxContestRankings& ContestRankings,
const EDTFluxSortingRankingType RankingType, TArray<FDTFluxContestRanking>& OutContestRankings, bool bAscendant);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static void FilterStageRankings(FDTFluxStageRankings& InStageRankings, const EDTFluxSortingRankingType RankingType, FDTFluxStageRankings& OutStageRankings, bool bAscendant = true);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static void FilterSplitRankings(FDTFluxSplitRankings& SplitRankings, const EDTFluxSortingRankingType RankinType, FDTFluxSplitRankings& OutSplitRankings, bool bAscendant = true);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static float ConvertTimeStringToSeconds(const FString& TimeString);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static bool CompareTimeString(const FString& A_TimeStr, const FString& B_TimeStr, bool bAscendant = true);
UFUNCTION(Blueprintable, Category="DTFlux|Core Subsystem|Tools")
static bool CompareSpeed(const FString& A_SpeedStr, const FString& B_SpeedStr, bool bAscendant=true);
};

View File

@ -2,31 +2,31 @@
public class DTFluxNetwork : ModuleRules public class DTFluxNetwork : ModuleRules
{ {
public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target) public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core"
} }
); );
PrivateDependencyModuleNames.AddRange( PrivateDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate", "Slate",
"SlateCore", "SlateCore",
"WebSockets", "WebSockets",
"HTTP", "HTTP",
"DTFluxCore", "DTFluxCore",
"DTFluxProjectSettings", "DTFluxProjectSettings",
"JsonUtilities", "JsonUtilities",
"Json", "Json",
} }
); );
} }
} }

View File

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

View File

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

View File

@ -5,6 +5,8 @@
#include "DTFluxCoreModule.h" #include "DTFluxCoreModule.h"
#include "DTFluxNetworkModule.h" #include "DTFluxNetworkModule.h"
#include "DTFluxNetworkSettings.h" #include "DTFluxNetworkSettings.h"
#include "DTFluxQueuedManager.h"
#include "DTFluxQueuedManager.h"
#include "JsonObjectConverter.h" #include "JsonObjectConverter.h"
#include "Clients/DTFluxHttpClient.h" #include "Clients/DTFluxHttpClient.h"
#include "Clients/DTFluxWebSocketClient.h" #include "Clients/DTFluxWebSocketClient.h"
@ -19,10 +21,11 @@
#include "Types/Struct/DTFluxSplitSensor.h" #include "Types/Struct/DTFluxSplitSensor.h"
// === CONNEXION WEBSOCKET ===
void UDTFluxNetworkSubsystem::Connect() void UDTFluxNetworkSubsystem::Connect()
{ {
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
WsClient->Connect(); WsClient->Connect();
} }
void UDTFluxNetworkSubsystem::Disconnect() void UDTFluxNetworkSubsystem::Disconnect()
@ -35,6 +38,141 @@ void UDTFluxNetworkSubsystem::Reconnect()
ReconnectWs(FName("Ws_Client_0")); 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, void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId,
int InSplitId) int InSplitId)
{ {
@ -48,7 +186,8 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message); FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message);
break; break;
case EDTFluxRequestType::SplitRanking: case EDTFluxRequestType::SplitRanking:
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message); FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId),
Message);
break; break;
case EDTFluxRequestType::TeamList: case EDTFluxRequestType::TeamList:
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message); FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message);
@ -60,7 +199,7 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
return; return;
} }
//Dirty trick to fix Case //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); UE_LOG(logDTFluxCore, Warning, TEXT("Sending Request %s"), *Message);
SendMessage(Message); SendMessage(Message);
} }
@ -70,11 +209,10 @@ void UDTFluxNetworkSubsystem::SendMessage(const FString& Message)
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message); UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message);
if(WsClient.IsValid() && WsClient->CanSend()) if (WsClient.IsValid() && WsClient->CanSend())
{ {
WsClient->Send(Message); WsClient->Send(Message);
UE_LOG(logDTFluxNetwork, Log, TEXT("Can send request")); UE_LOG(logDTFluxNetwork, Log, TEXT("Can send request"));
} }
else else
{ {
@ -86,8 +224,7 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{ {
Super::Initialize(Collection); Super::Initialize(Collection);
FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked<FDTFluxCoreModule>("DTFluxCore"); 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* NetworkSettings = GetMutableDefault<UDTFluxNetworkSettings>();
UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings); UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings);
UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings); UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings);
@ -99,16 +236,36 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged")); NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged"));
NetworkSettings->OnDTFluxHttpSettingsChanged.AddUFunction(this, FName("HttpSettingsChanged")); NetworkSettings->OnDTFluxHttpSettingsChanged.AddUFunction(this, FName("HttpSettingsChanged"));
#endif #endif
if(WsSettings.bShouldConnectAtStartup) if (WsSettings.bShouldConnectAtStartup)
{ {
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port)); WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
WsClient->Connect(); 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() void UDTFluxNetworkSubsystem::Deinitialize()
{ {
Super::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) void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings)
@ -117,7 +274,7 @@ void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSe
bool bNeedsReload = WsSettings != NewWsSettings; bool bNeedsReload = WsSettings != NewWsSettings;
WsSettings = NewWsSettings; WsSettings = NewWsSettings;
if( bNeedsReload || WsSettings.bShouldConnectAtStartup) if (bNeedsReload || WsSettings.bShouldConnectAtStartup)
{ {
UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client")) UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client"))
ReconnectWs(FName("Ws_Client_0")); ReconnectWs(FName("Ws_Client_0"));
@ -137,9 +294,9 @@ void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
WsClient->SetAddress(NewAddress); WsClient->SetAddress(NewAddress);
WsClient->Reconnect(); WsClient->Reconnect();
} }
void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId) void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId)
{ {
} }
void UDTFluxNetworkSubsystem::RegisterWebSocketEvents() void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
@ -148,16 +305,16 @@ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem); WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem);
OnWsConnectionErrorEventDelegateHandle = OnWsConnectionErrorEventDelegateHandle =
WsClient->RegisterConnectionError() WsClient->RegisterConnectionError()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem); .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
OnWsClosedEventDelegateHandle = OnWsClosedEventDelegateHandle =
WsClient->RegisterClosedEvent() WsClient->RegisterClosedEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem); .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
OnWsMessageEventDelegateHandle = OnWsMessageEventDelegateHandle =
WsClient->RegisterMessageEvent() WsClient->RegisterMessageEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem); .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
OnWsMessageSentEventDelegateHandle = OnWsMessageSentEventDelegateHandle =
WsClient->RegisterMessageSentEvent() WsClient->RegisterMessageSentEvent()
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem); .AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
} }
void UDTFluxNetworkSubsystem::RegisterHttpEvents() void UDTFluxNetworkSubsystem::RegisterHttpEvents()
@ -166,25 +323,25 @@ void UDTFluxNetworkSubsystem::RegisterHttpEvents()
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents() void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
{ {
if(OnWsConnectedEventDelegateHandle.IsValid()) if (OnWsConnectedEventDelegateHandle.IsValid())
{ {
WsClient->UnregisterConnectedEvent().Remove(OnWsConnectedEventDelegateHandle); 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); UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error);
WsStatus = EDTFluxConnectionStatus::Error; WsStatus = EDTFluxConnectionStatus::Error;
if(WsSettings.bShouldAutoReconnectOnError) if (WsSettings.bShouldAutoReconnectOnError)
{ {
WsClient->Reconnect(); WsClient->Reconnect();
} }
@ -212,258 +369,239 @@ void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString
void UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean) 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"), 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; 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; 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) UE_LOG(logDTFluxNetwork, Error, TEXT("ParseTeamListResponse() for JSON Response : %s"), *Response.RawMessage);
{ return;
const TSharedPtr<FJsonObject> Item = Value->AsObject();
FDTFluxParticipant Participant;
UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant);
TeamListDefinition.Participants.Add(Participant);
}
} }
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(), 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; FDTFluxRaceData RaceData;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(Response.RawMessage, &RaceData)) Response.ParseRaceData(RaceData);
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
{ {
//convert UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
FDTFluxRaceData RaceDataDefinition; return;
for(auto Contest : RaceData.Datas) }
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; case EDTFluxApiDataType::SplitSensor:
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)
{ {
FDTFluxStage NewStage; TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
NewStage.StageId = Stage.Id; if (Response.ParseSplitSensorResponse(SplitSensorInfos))
NewStage.Name = Stage.Name; {
FString StartTimeFString = FString::Printf(TEXT("%s %s"), for (const auto& SplitSensorInfo : SplitSensorInfos)
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), {
*Stage.StartTime OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
); }
FString EndTimeFString = FString::Printf(TEXT("%s %s"), }
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")), ResponseStatus = Response.GetParsingStatus();
*Stage.EndTime break;
);
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());
} }
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s]\nSplits: \n"), Contest.Id, *Contest.Name); case EDTFluxApiDataType::StatusUpdate:
for(auto Split: Contest.Splits)
{ {
FDTFluxSplit NewSplit; FDTFluxTeamStatusUpdate StatusUpdate;
NewSplit.SplitId = Split.Id; if (Response.ParseStatusUpdateResponse(StatusUpdate))
NewSplit.Name = Split.Name; {
NewContest.Splits.Add(NewSplit); OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
UE_LOG(logDTFluxNetwork, Warning, TEXT("Split %i [%s]: \n"), Split.Id, *Split.Name); }
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; EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::Success;
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(Response.RawMessage, &ContestRankingResponse)) switch (Response.GetResponseType())
{ {
FDTFluxContestRankings ContestRankings; case EDTFluxApiDataType::RaceData:
ContestRankings.ContestId = ContestRankingResponse.ContestID;
for(auto& RankingItem : ContestRankingResponse.Datas)
{ {
FDTFluxContestRanking Temp = RankingItem; UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing RaceData"));
ContestRankings.Rankings.Add(Temp); ParseRaceData(Response);
ResponseStatus = Response.GetParsingStatus();
break;
} }
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"), ContestRankings.ContestId, case EDTFluxApiDataType::TeamList:
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);
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)
{ {
FDTFluxSplitSensorInfo NewSplitSensorInfo; UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing TeamList"));
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib; ParseTeamListResponse(Response);
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID; ResponseStatus = Response.GetParsingStatus();
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID; break;
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID; }
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time; case EDTFluxApiDataType::ContestRanking:
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, ParseContestRanking(Response);
OnSplitSensorReceived.ExecuteIfBound(NewSplitSensorInfo) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")); 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 //TODO reforge API to keep track of Requests
void UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString) 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 //Do Something With the message
FDTFluxServerResponse Response; EDTFluxResponseStatus ResponseStatus;
Response.ReceivedAt = FDateTime::Now(); FDTFluxServerResponse Response(MessageString, ResponseStatus);
Response.RawMessage = MessageString; if (!TryMatchResponseToQueuedRequest(Response))
Response.FailureReason = FText::FromString("--");
if(FJsonObjectConverter::JsonObjectStringToUStruct(MessageString, &Response, 0, 0, false, &(Response.FailureReason)))
{ {
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); UE_LOG(logDTFluxNetwork, Warning, TEXT("Not a push message"));
if(Response.Type.Contains("race-data")) // Legacy
{ Parse(Response);
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, Error, TEXT("Ws %s :\nMessage Received : %s Cannot be Parsed"), *WsClient->GetAddress(), *MessageString);
// return DataReceived.Broadcast(Response);
} }
void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent) void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent)
@ -471,15 +609,144 @@ void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FStrin
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent); 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 UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port)
{ {
FString NewAddress; FString NewAddress;
if( !Address.Contains("ws://") && !Address.Contains("wss://")) if (!Address.Contains("ws://") && !Address.Contains("wss://"))
{ {
NewAddress += FString("ws://"); NewAddress += FString("ws://");
} }
NewAddress +=Address + FString(":") + FString::FromInt(Port) + Path; NewAddress += Address + FString(":") + FString::FromInt(Port) + Path;
return NewAddress; return NewAddress;
// UE_LOG(logDTFluxNetwork, Log, TEXT("NewAddress : %s"), *NewAddress); // UE_LOG(logDTFluxNetwork, Log, TEXT("NewAddress : %s"), *NewAddress);
} }

View 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;
};

View File

@ -15,9 +15,13 @@ USTRUCT()
struct FDTFluxRequestBase struct FDTFluxRequestBase
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY() UPROPERTY()
FString Path = ""; 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. * RaceData represents all data concerning the Race and its different Contests, Stages and Splits.
*/ */
USTRUCT() USTRUCT()
struct FDTFluxRaceDataRequest: public FDTFluxRequestBase struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxRaceDataRequest(){ FDTFluxRaceDataRequest()
{
Path = "race-datas"; Path = "race-datas";
} }
}; };
@ -39,11 +45,13 @@ public:
* TeamList is the list of participants of the events * TeamList is the list of participants of the events
*/ */
USTRUCT() USTRUCT()
struct FDTFluxTeamListRequest: public FDTFluxRequestBase struct FDTFluxTeamListRequest : public FDTFluxRequestBase
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxTeamListRequest(){ FDTFluxTeamListRequest()
{
Path = "team-list"; Path = "team-list";
} }
}; };
@ -52,16 +60,17 @@ public:
* Struct representing a Ranking json request object for a specific to the server * Struct representing a Ranking json request object for a specific to the server
*/ */
USTRUCT() USTRUCT()
struct FDTFluxContestRankingRequest: public FDTFluxRequestBase struct FDTFluxContestRankingRequest : public FDTFluxRequestBase
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxContestRankingRequest() FDTFluxContestRankingRequest()
{ {
Path = "contest-ranking"; Path = "contest-ranking";
ContestID = -1; ContestID = -1;
} }
FDTFluxContestRankingRequest(int InContestID) FDTFluxContestRankingRequest(int InContestID)
{ {
Path = "contest-ranking"; Path = "contest-ranking";
@ -76,10 +85,10 @@ public:
* Struct representing a Ranking json request object for a specific Stage to the server * Struct representing a Ranking json request object for a specific Stage to the server
*/ */
USTRUCT() USTRUCT()
struct FDTFluxStageRankingRequest: public FDTFluxRequestBase struct FDTFluxStageRankingRequest : public FDTFluxRequestBase
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxStageRankingRequest() FDTFluxStageRankingRequest()
{ {
@ -88,6 +97,7 @@ public:
StageID = -1; StageID = -1;
SplitID = -1; SplitID = -1;
} }
FDTFluxStageRankingRequest(int InContestID, int InStageId) FDTFluxStageRankingRequest(int InContestID, int InStageId)
{ {
Path = "stage-ranking"; Path = "stage-ranking";
@ -102,18 +112,16 @@ public:
int StageID; int StageID;
UPROPERTY() UPROPERTY()
int SplitID; int SplitID;
}; };
/** /**
* Struct representing a Ranking json request object for a specific Split to the server * Struct representing a Ranking json request object for a specific Split to the server
*/ */
USTRUCT() USTRUCT()
struct FDTFluxSplitRankingRequest: public FDTFluxStageRankingRequest struct FDTFluxSplitRankingRequest : public FDTFluxStageRankingRequest
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
FDTFluxSplitRankingRequest() FDTFluxSplitRankingRequest()
{ {
@ -122,6 +130,7 @@ public:
StageID = -1; StageID = -1;
SplitID = -1; SplitID = -1;
} }
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId) FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
{ {
Path = "stage-ranking"; Path = "stage-ranking";
@ -129,5 +138,4 @@ public:
StageID = InStageId; StageID = InStageId;
SplitID = InSplitId; SplitID = InSplitId;
} }
}; };

View File

@ -4,13 +4,38 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UObject/Object.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" #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() USTRUCT()
struct DTFLUXNETWORK_API FDTFluxServerResponse struct DTFLUXNETWORK_API FDTFluxServerResponse
@ -20,35 +45,76 @@ struct DTFLUXNETWORK_API FDTFluxServerResponse
public: public:
UPROPERTY() UPROPERTY()
FString Type = ""; FString Type = "";
UPROPERTY() UPROPERTY()
int Code = -1; int Code = -1;
UPROPERTY() UPROPERTY()
FString Message = ""; FString Message = "";
UPROPERTY() UPROPERTY()
FString Trigger = ""; FString Trigger = "";
UPROPERTY() UPROPERTY()
int ContestID = -1; int ContestID = -1;
UPROPERTY() UPROPERTY()
int StageID = -1; int StageID = -1;
UPROPERTY() UPROPERTY()
int SplitID = -1; int SplitID = -1;
UPROPERTY() UPROPERTY()
FDateTime ReceivedAt; FDateTime ReceivedAt;
UPROPERTY() UPROPERTY()
FString RawMessage; FString RawMessage;
UPROPERTY() UPROPERTY()
FName RequestId = FName(""); FName RequestId = FName("");
UPROPERTY() UPROPERTY()
FText FailureReason; 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);
}; };

View File

@ -3,6 +3,7 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "DTFluxQueuedManager.h"
#include "Struct/DTFluxServerResponseStruct.h" #include "Struct/DTFluxServerResponseStruct.h"
#include "Subsystems/EngineSubsystem.h" #include "Subsystems/EngineSubsystem.h"
#include "Types/DTFluxNetworkSettingsTypes.h" #include "Types/DTFluxNetworkSettingsTypes.h"
@ -14,12 +15,24 @@
#include "DTFluxNetworkSubsystem.generated.h" #include "DTFluxNetworkSubsystem.generated.h"
class FDTFluxWebSocketClient; class FDTFluxWebSocketClient;
class UDTFluxQueuedManager;
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP; typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
class FDTFluxHttpClient; class FDTFluxHttpClient;
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP; 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 class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
{ {
GENERATED_BODY() GENERATED_BODY()
public:
public:
UPROPERTY() UPROPERTY()
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset; EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network") UPROPERTY(BlueprintAssignable, Category="DTFlux|Network")
FOnWebSocketConnected OnWebSocketConnected; FOnWebSocketConnected OnWebSocketConnected;
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/); DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
FOnRaceDataReceived OnRaceDataReceived; FOnRaceDataReceived OnRaceDataReceived;
FOnRaceDataReceived& OnReceivedRaceData() FOnRaceDataReceived& OnReceivedRaceData()
{ {
return OnRaceDataReceived; return OnRaceDataReceived;
}; };
// === DELEGATES POUR LES DONNÉES REÇUES (PUSH) ===
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/); DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
FOnTeamListReceived OnTeamListReceived; FOnTeamListReceived OnTeamListReceived;
FOnTeamListReceived& OnReceivedTeamList() FOnTeamListReceived& OnReceivedTeamList()
{ {
return OnTeamListReceived; return OnTeamListReceived;
@ -53,32 +69,40 @@ public:
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/); DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
FOnStageRankingReceived OnStageRankingReceived; FOnStageRankingReceived OnStageRankingReceived;
FOnStageRankingReceived& OnReceivedStageRanking() FOnStageRankingReceived& OnReceivedStageRanking()
{ {
return OnStageRankingReceived; return OnStageRankingReceived;
} }
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/); DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
FOnSplitRankingReceived OnSplitRankingReceived; FOnSplitRankingReceived OnSplitRankingReceived;
FOnSplitRankingReceived& OnReceivedSplitRanking() FOnSplitRankingReceived& OnReceivedSplitRanking()
{ {
return OnSplitRankingReceived; return OnSplitRankingReceived;
} }
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/); DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
FOnContestRankingReceived OnContestRankingReceived; FOnContestRankingReceived OnContestRankingReceived;
FOnContestRankingReceived& OnReceivedContestRanking() FOnContestRankingReceived& OnReceivedContestRanking()
{ {
return OnContestRankingReceived; return OnContestRankingReceived;
}; };
// === DELEGATES POUR LES DONNÉES REÇUES (PULL) ===
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/); DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
FOnSplitSensorReceived OnSplitSensorReceived; FOnSplitSensorReceived OnSplitSensorReceived;
FOnSplitSensorReceived& OnReceivedSplitSensor() FOnSplitSensorReceived& OnReceivedSplitSensor()
{ {
return OnSplitSensorReceived; return OnSplitSensorReceived;
}; };
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxParticipant& /*ParticipantToUpdate*/); DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*ParticipantToUpdate*/);
FOnTeamUpdateReceived OnTeamUpdateReceived; FOnTeamUpdateReceived OnTeamUpdateReceived;
FOnTeamUpdateReceived& OnReceivedTeamUpdate() FOnTeamUpdateReceived& OnReceivedTeamUpdate()
{ {
return OnTeamUpdateReceived; return OnTeamUpdateReceived;
@ -86,6 +110,7 @@ public:
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/); DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived; FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate() FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate()
{ {
return OnTeamStatusUpdateReceived; return OnTeamStatusUpdateReceived;
@ -99,13 +124,41 @@ public:
void Reconnect(); 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") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendRequest(const EDTFluxRequestType RequestType, int InContestId = -1, int InStageId = -1, int InSplitId = -1);
// === REQUÊTES DIRECTES (LEGACY) ===
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
UFUNCTION(BlueprintCallable, Category="DTFlux|Network") int InSplitId = -1);
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
void SendMessage(const FString& Message); void SendMessage(const FString& Message);
protected: protected:
// ~Subsystem Interface // ~Subsystem Interface
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
@ -114,9 +167,22 @@ protected:
private: private:
// === CONFIGURATION ===
FDTFluxWsSettings WsSettings; FDTFluxWsSettings WsSettings;
FDTFluxHttpSettings HttpSettings; 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() UFUNCTION()
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings); void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
UFUNCTION() UFUNCTION()
@ -124,40 +190,46 @@ private:
void ReconnectWs(const FName WsClientId); void ReconnectWs(const FName WsClientId);
void ReconnectHttp(const FName WsClientId); void ReconnectHttp(const FName WsClientId);
// === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
void RegisterWebSocketEvents(); void RegisterWebSocketEvents();
void RegisterHttpEvents();
void UnregisterWebSocketEvents(); void UnregisterWebSocketEvents();
void UnregisterHttpEvents();
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);
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
FDelegateHandle OnWsConnectedEventDelegateHandle; FDelegateHandle OnWsConnectedEventDelegateHandle;
FDelegateHandle OnWsConnectionErrorEventDelegateHandle; FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
FDelegateHandle OnWsClosedEventDelegateHandle; FDelegateHandle OnWsClosedEventDelegateHandle;
FDelegateHandle OnWsMessageEventDelegateHandle; FDelegateHandle OnWsMessageEventDelegateHandle;
FDelegateHandle OnWsMessageSentEventDelegateHandle; 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); static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
}; };

View File

@ -0,0 +1,28 @@
using UnrealBuildTool;
public class DTFluxPursuitSystem : ModuleRules
{
public DTFluxPursuitSystem(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"DTFluxCore",
"DTFluxCoreSubsystem"
}
);
}
}

View File

@ -0,0 +1,19 @@
#include "DTFluxPursuitSystemModule.h"
#define LOCTEXT_NAMESPACE "FDTFluxPursuitSystemModule"
DEFINE_LOG_CATEGORY(logDTFluxPursuitSystem);
void FDTFluxPursuitSystem::StartupModule()
{
}
void FDTFluxPursuitSystem::ShutdownModule()
{
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FDTFluxPursuitSystem, DTFluxPursuitSystem)

View File

@ -0,0 +1,13 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
DECLARE_LOG_CATEGORY_EXTERN(logDTFluxPursuitSystem, All, All)
class DTFLUXPURSUITSYSTEM_API FDTFluxPursuitSystem : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
};

View File

@ -2,25 +2,26 @@
public class DTFluxUtilities : ModuleRules public class DTFluxUtilities : ModuleRules
{ {
public DTFluxUtilities(ReadOnlyTargetRules Target) : base(Target) public DTFluxUtilities(ReadOnlyTargetRules Target) : base(Target)
{ {
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core",
} }
); );
PrivateDependencyModuleNames.AddRange( PrivateDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"CoreUObject", "CoreUObject",
"Engine", "Engine",
"Slate", "Slate",
"SlateCore" "SlateCore",
} "DTFluxCore"
); }
} );
}
} }

View File

@ -0,0 +1,9 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "FTDFluxUtils.h"
FText UFTDFluxUtils::GetFormatedName(FDTFluxParticipant& Participant, const int MaxChar, const FString OverFlowChar)
{
return Participant.GetFormattedNameText(MaxChar, OverFlowChar);
}

View File

@ -0,0 +1,22 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "FTDFluxUtils.generated.h"
/**
*
*/
UCLASS()
class DTFLUXUTILITIES_API UFTDFluxUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
const FString OverFlowChar = "...");
};