Compare commits
10 Commits
801e946a89
...
8f884f6224
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f884f6224 | |||
| 9bb5e760f2 | |||
| 760a764816 | |||
| 43a7fb7400 | |||
| b63f2dd7b5 | |||
| 7e1ce2cdfa | |||
| a2be97cfe4 | |||
| e0edf5ab8d | |||
| 8387319bf8 | |||
| 27822229d0 |
@ -49,6 +49,11 @@
|
|||||||
"Name": "DTFluxAPIStatus",
|
"Name": "DTFluxAPIStatus",
|
||||||
"Type": "Editor",
|
"Type": "Editor",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DTFluxPursuitSystem",
|
||||||
|
"Type": "Runtime",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
|
|||||||
9
Notes.md
Normal file
9
Notes.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DEV NOTES
|
||||||
|
|
||||||
|
# TO THINK
|
||||||
|
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas ou Participant supprimé avec réaffectation de son Bib
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas de réaffectation de Bib d'un Concurrent.
|
||||||
|
__Solutions Possibles__:
|
||||||
|
- Si le Bib de l'update n'existe pas alors, le participant à changer de Bib. ça signifie qu'il faut que les organisateur ne réaffecte pas un Bib déja existant.~~
|
||||||
|
- [ ] Extraire Stages et Splits des Contest Pour les Mettre à plat.
|
||||||
@ -1 +1 @@
|
|||||||
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.4.2)
|
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.6)
|
||||||
|
|||||||
@ -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",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -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,31 +26,31 @@ 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,
|
||||||
@ -73,233 +72,199 @@ 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()
|
||||||
|
.FillWidth(2.0)
|
||||||
|
[
|
||||||
|
SNew(SSpacer)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.FillWidth(1.0)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Right)
|
||||||
|
[
|
||||||
|
ToolBarBuilder.MakeWidget()
|
||||||
|
]
|
||||||
]
|
]
|
||||||
+SHorizontalBox::Slot()
|
|
||||||
.AutoWidth()
|
|
||||||
.FillWidth(1.0)
|
|
||||||
.VAlign(VAlign_Center)
|
|
||||||
.HAlign(HAlign_Right)
|
|
||||||
[
|
|
||||||
ToolBarBuilder.MakeWidget()
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
#pragma region WebsocketStatusSection
|
]
|
||||||
// Main VerticalBox
|
+ SVerticalBox::Slot()
|
||||||
+SVerticalBox::Slot()
|
.AutoHeight()
|
||||||
.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
|
||||||
]
|
]
|
||||||
|
+ 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)
|
||||||
|
]
|
||||||
|
|
||||||
]
|
]
|
||||||
#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
|
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@ -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()
|
||||||
|
|
||||||
@ -29,8 +31,42 @@ public:
|
|||||||
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -3,26 +3,58 @@
|
|||||||
#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
|
||||||
|
|||||||
@ -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();
|
||||||
|
})
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -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()
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -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();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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();
|
||||||
|
|||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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 qu’il rentre dans la limite
|
// Coupe le nom pour qu’il 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;
|
||||||
}
|
}
|
||||||
@ -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();
|
|
||||||
}
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||||
@ -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";
|
||||||
|
|
||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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")
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"),
|
||||||
|
};
|
||||||
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -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")
|
FDTFluxStageKey(const int InContestId, const int InStageId)
|
||||||
int ContestId = -1;
|
: ContestId(InContestId)
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
, StageId(InStageId)
|
||||||
int StageId = -1;
|
{
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
};
|
||||||
int SplitId = -1;
|
|
||||||
|
|
||||||
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key)
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
{
|
int ContestId = -1;
|
||||||
return HashCombine(
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
GetTypeHash(Key.ContestId),
|
int StageId = -1;
|
||||||
GetTypeHash(Key.StageId),
|
|
||||||
GetTypeHash(Key.SplitId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const FDTFluxSplitKey& Other) const
|
friend uint32 GetTypeHash(const FDTFluxStageKey& Key)
|
||||||
{
|
{
|
||||||
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
|
return HashCombine(
|
||||||
}
|
GetTypeHash(Key.ContestId),
|
||||||
FString GetDisplayName() const
|
GetTypeHash(Key.StageId)
|
||||||
{
|
);
|
||||||
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
FText GetTooltipText() const
|
bool operator==(const FDTFluxStageKey& Other) const
|
||||||
{
|
{
|
||||||
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
|
return ContestId == Other.ContestId && StageId == Other.StageId;
|
||||||
FText::AsNumber(ContestId),
|
}
|
||||||
FText::AsNumber(StageId),
|
|
||||||
FText::AsNumber(SplitId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
@ -88,18 +91,18 @@ public:
|
|||||||
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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,28 +92,40 @@ 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)
|
||||||
@ -135,11 +148,45 @@ 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);
|
||||||
|
|
||||||
@ -149,7 +196,7 @@ protected:
|
|||||||
// 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,6 +209,7 @@ 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
|
||||||
@ -175,9 +223,12 @@ struct FDTFluxTeamStatusUpdate
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxTeamStatusUpdate() = default;
|
FDTFluxTeamStatusUpdate() = default;
|
||||||
|
|
||||||
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
||||||
:Bib(InBib)
|
: Bib(InBib)
|
||||||
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus)){};
|
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
||||||
|
|||||||
@ -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;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
35
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
35
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// 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)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
bool bIsMassStart = false;
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
int Bib = -1;
|
||||||
|
|
||||||
|
//TODO : Set this property to BlueprintReadOnly
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
|
FDateTime StartTime;
|
||||||
|
};
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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->OnReceivedSplitRanking().BindUFunction(this, "ProcessSplitRanking");
|
|
||||||
NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamList");
|
|
||||||
NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUFunction(this, "ProcessTeamStatusUpdate");
|
|
||||||
NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamUpdate");
|
|
||||||
NetworkSubsystem->OnReceivedSplitSensor().BindUFunction(this, "ProcessSplitSensor");
|
|
||||||
|
|
||||||
|
NetworkSubsystem->OnReceivedRaceData().BindUObject(
|
||||||
|
this,
|
||||||
|
&UDTFluxCoreSubsystem::ProcessRaceData
|
||||||
|
);
|
||||||
|
|
||||||
|
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,12 +248,100 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib)
|
||||||
|
{
|
||||||
|
if (DataStorage->Participants.Contains(InBib))
|
||||||
|
{
|
||||||
|
return DataStorage->Participants[InBib];
|
||||||
|
}
|
||||||
|
return FDTFluxParticipant();
|
||||||
|
}
|
||||||
|
|
||||||
void UDTFluxCoreSubsystem::RefreshStorage()
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxCoreSubsystem::GetContestForId(const int Id, FDTFluxContest& OutContest)
|
||||||
|
{
|
||||||
|
for (auto KeyPair : DataStorage->Contests)
|
||||||
|
{
|
||||||
|
if (KeyPair.Value.ContestId == Id)
|
||||||
|
{
|
||||||
|
OutContest = KeyPair.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Cannot find Contest for Id [%i]"), Id);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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>();
|
||||||
|
}
|
||||||
|
|||||||
103
Source/DTFluxCoreSubsystem/Private/DTFluxCoreSubsystemTools.cpp
Normal file
103
Source/DTFluxCoreSubsystem/Private/DTFluxCoreSubsystemTools.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
@ -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,36 +24,40 @@ 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();
|
||||||
|
|
||||||
@ -77,19 +79,44 @@ public:
|
|||||||
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")
|
||||||
|
const FDTFluxParticipant GetParticipant(int InBib);
|
||||||
|
|
||||||
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()
|
||||||
|
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
|
||||||
|
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 +131,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;
|
||||||
};
|
};
|
||||||
|
|||||||
37
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.h
Normal file
37
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.h
Normal 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);
|
||||||
|
};
|
||||||
@ -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",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
401
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
401
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
|
||||||
|
|
||||||
|
const FString FDTFluxQueuedRequest::Serialize() const
|
||||||
|
{
|
||||||
|
FString JSONString;
|
||||||
|
switch (RequestType)
|
||||||
|
{
|
||||||
|
case EDTFluxRequestType::RaceData:
|
||||||
|
|
||||||
|
{
|
||||||
|
FDTFluxRaceDataRequest RaceData;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::TeamList:
|
||||||
|
{
|
||||||
|
const FDTFluxTeamListRequest TeamList;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::ContestRanking:
|
||||||
|
{
|
||||||
|
FDTFluxContestRankingRequest ContestRanking(ContestId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::StageRanking:
|
||||||
|
{
|
||||||
|
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case EDTFluxRequestType::SplitRanking:
|
||||||
|
{
|
||||||
|
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
JSONString = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return JSONString;
|
||||||
|
}
|
||||||
|
|
||||||
|
UDTFluxQueuedManager::UDTFluxQueuedManager()
|
||||||
|
: bIsInitialized(false)
|
||||||
|
, CheckInterval(0.5f)
|
||||||
|
, TimeSinceLastCheck(0.0f)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UDTFluxQueuedManager::~UDTFluxQueuedManager()
|
||||||
|
{
|
||||||
|
ClearAllRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::Initialize()
|
||||||
|
{
|
||||||
|
if (!bIsInitialized)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Initializing DTFluxQueuedManager"));
|
||||||
|
bIsInitialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid UDTFluxQueuedManager::QueueRequest(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
|
||||||
|
const FString& RawMessage)
|
||||||
|
{
|
||||||
|
// Créer la requête avec les structs existants
|
||||||
|
FDTFluxQueuedRequest NewRequest(RequestType, ContestId, StageId, SplitId);
|
||||||
|
NewRequest.RawResponse = RawMessage;
|
||||||
|
|
||||||
|
// Ajouter à la queue des requêtes en attente
|
||||||
|
PendingRequestsQueue.Enqueue(NewRequest);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Queued request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*NewRequest.RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
|
||||||
|
|
||||||
|
return NewRequest.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FGuid& TargetRequestGuid)
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
bool bFoundMatch = false;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
if (!bFoundMatch && Request.RequestId == TargetRequestGuid)
|
||||||
|
{
|
||||||
|
// Marquer comme ayant reçu une réponse
|
||||||
|
Request.bHasReceivedResponse = true;
|
||||||
|
bFoundMatch = true;
|
||||||
|
|
||||||
|
// Ajouter à la queue des requêtes terminées
|
||||||
|
CompletedRequestsQueue.Enqueue(Request);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose,
|
||||||
|
TEXT("Marked request %s as responded: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre les requêtes non traitées dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bFoundMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest)
|
||||||
|
{
|
||||||
|
return MarkRequestAsResponded(TargetRequest.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId,
|
||||||
|
int32 SplitId)
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
bool bFoundMatch = false;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
// Vérifier si cette requête correspond
|
||||||
|
if (!bFoundMatch && Request.Matches(RequestType, ContestId, StageId, SplitId))
|
||||||
|
{
|
||||||
|
bFoundMatch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre toutes les requêtes dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bFoundMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId,
|
||||||
|
int32 StageId, int32 SplitId)
|
||||||
|
{
|
||||||
|
auto SearchInQueue = [&RequestType, ContestId, StageId, SplitId](
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
|
||||||
|
{
|
||||||
|
// Copie temporaire de la queue pour la recherche
|
||||||
|
TQueue<FDTFluxQueuedRequest> TempQueue;
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* FoundItem = nullptr;
|
||||||
|
FDTFluxQueuedRequest Item;
|
||||||
|
while (Queue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
if (Item.RequestType == RequestType && Item.ContestId == ContestId && Item.StageId == StageId && Item.
|
||||||
|
SplitId == SplitId) // Assuming RequestId is your GUID field
|
||||||
|
{
|
||||||
|
FoundItem = &Item;
|
||||||
|
}
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
while (TempQueue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
Queue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
return FoundItem;
|
||||||
|
};
|
||||||
|
return SearchInQueue(PendingRequestsQueue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequest(const FGuid& SearchedGuid)
|
||||||
|
{
|
||||||
|
auto SearchInQueue = [&SearchedGuid](TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
|
||||||
|
{
|
||||||
|
// Copie temporaire de la queue pour la recherche
|
||||||
|
TQueue<FDTFluxQueuedRequest> TempQueue;
|
||||||
|
|
||||||
|
FDTFluxQueuedRequest* FoundItem = nullptr;
|
||||||
|
FDTFluxQueuedRequest Item;
|
||||||
|
while (Queue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
if (Item.RequestId == SearchedGuid) // Assuming RequestId is your GUID field
|
||||||
|
{
|
||||||
|
// Trouver l'élément dans la queue originale
|
||||||
|
// On doit refaire une copie car on ne peut pas retourner l'adresse de 'Item'
|
||||||
|
FoundItem = &Item;
|
||||||
|
}
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
while (TempQueue.Dequeue(Item))
|
||||||
|
{
|
||||||
|
Queue.Enqueue(Item);
|
||||||
|
}
|
||||||
|
return FoundItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Chercher dans chaque queue
|
||||||
|
if (FDTFluxQueuedRequest* Found = SearchInQueue(PendingRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
if (const FDTFluxQueuedRequest* Found = SearchInQueue(CompletedRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
if (const FDTFluxQueuedRequest* Found = SearchInQueue(TimedOutRequestsQueue))
|
||||||
|
return Found;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::GetPendingRequestCount()
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
int32 Count = 0;
|
||||||
|
|
||||||
|
// Compter les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
Count++;
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre toutes les requêtes dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::CleanupTimedOutRequests()
|
||||||
|
{
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||||
|
int32 TimeoutCount = 0;
|
||||||
|
|
||||||
|
// Parcourir toutes les requêtes en attente
|
||||||
|
FDTFluxQueuedRequest Request;
|
||||||
|
while (PendingRequestsQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
if (Request.HasTimedOut())
|
||||||
|
{
|
||||||
|
// Ajouter à la queue des requêtes expirées
|
||||||
|
TimedOutRequestsQueue.Enqueue(Request);
|
||||||
|
TimeoutCount++;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning,
|
||||||
|
TEXT("Request %s timed out: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||||
|
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||||
|
Request.SplitId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Remettre dans la queue temporaire
|
||||||
|
TempQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remettre les requêtes non expirées dans la queue principale
|
||||||
|
while (TempQueue.Dequeue(Request))
|
||||||
|
{
|
||||||
|
PendingRequestsQueue.Enqueue(Request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TimeoutCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 UDTFluxQueuedManager::CleanCashedRequests()
|
||||||
|
{
|
||||||
|
int32 CleanedRequestsCount = 0;
|
||||||
|
|
||||||
|
// Queue temporaire pour stocker les requêtes encore valides
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> ValidCompletedRequests;
|
||||||
|
|
||||||
|
// Traiter toutes les requêtes terminées
|
||||||
|
FDTFluxQueuedRequest CompletedRequest;
|
||||||
|
while (CompletedRequestsQueue.Dequeue(CompletedRequest))
|
||||||
|
{
|
||||||
|
// Vérifier si la requête est cacheable et a reçu une réponse
|
||||||
|
if (CompletedRequest.bIsCacheable && CompletedRequest.bHasReceivedResponse)
|
||||||
|
{
|
||||||
|
// Calculer l'âge de la requête en secondes
|
||||||
|
float RequestAge = (FDateTime::Now() - CompletedRequest.CreatedAt).GetTotalSeconds();
|
||||||
|
|
||||||
|
// Vérifier si le cache est encore valide
|
||||||
|
if (RequestAge <= CompletedRequest.CachedValidity)
|
||||||
|
{
|
||||||
|
// Le cache est encore valide, conserver la requête
|
||||||
|
ValidCompletedRequests.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Le cache a expiré, compter cette requête comme nettoyée
|
||||||
|
CleanedRequestsCount++;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Verbose,
|
||||||
|
TEXT("DTFluxQueuedManager: Cleaned expired cached request %s (Age: %.2fs, Validity: %.2fs)"),
|
||||||
|
*CompletedRequest.RequestId.ToString(), RequestAge, CompletedRequest.CachedValidity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Requête non cacheable ou sans réponse, la conserver
|
||||||
|
ValidCompletedRequests.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restaurer la queue avec uniquement les requêtes valides
|
||||||
|
while (ValidCompletedRequests.Dequeue(CompletedRequest))
|
||||||
|
{
|
||||||
|
CompletedRequestsQueue.Enqueue(CompletedRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log du résultat si des requêtes ont été nettoyées
|
||||||
|
if (CleanedRequestsCount > 0)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxQueuedManager: Cleaned %d expired cached requests"), CleanedRequestsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CleanedRequestsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::ClearAllRequests()
|
||||||
|
{
|
||||||
|
// Vider toutes les queues
|
||||||
|
FDTFluxQueuedRequest DummyRequest;
|
||||||
|
while (PendingRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
while (CompletedRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
while (TimedOutRequestsQueue.Dequeue(DummyRequest))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all pending requests"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxQueuedManager::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
if (!bIsInitialized)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incrémenter le temps écoulé
|
||||||
|
TimeSinceLastCheck += DeltaTime;
|
||||||
|
|
||||||
|
// Vérifier si c'est le moment de nettoyer les requêtes expirées
|
||||||
|
if (TimeSinceLastCheck >= CheckInterval)
|
||||||
|
{
|
||||||
|
TimeSinceLastCheck = 0.0f;
|
||||||
|
CleanupTimedOutRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter les requêtes expirées
|
||||||
|
FDTFluxQueuedRequest TimedOutRequest;
|
||||||
|
while (TimedOutRequestsQueue.Dequeue(TimedOutRequest))
|
||||||
|
{
|
||||||
|
// Déclencher l'événement pour chaque requête expirée
|
||||||
|
OnRequestTimedOut.Broadcast(TimedOutRequest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxQueuedManager::IsTickable() const
|
||||||
|
{
|
||||||
|
return bIsInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
TStatId UDTFluxQueuedManager::GetStatId() const
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxQueuedManager, STATGROUP_Tickables);
|
||||||
|
}
|
||||||
@ -0,0 +1,530 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
|
||||||
|
// === IMPLÉMENTATION DES CONSTRUCTEURS ===
|
||||||
|
|
||||||
|
FDTFluxServerResponse::FDTFluxServerResponse()
|
||||||
|
{
|
||||||
|
ReceivedAt = FDateTime::Now();
|
||||||
|
ApiDataType = EDTFluxApiDataType::None;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxServerResponse::FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus,
|
||||||
|
bool bLogErrors)
|
||||||
|
{
|
||||||
|
ReceivedAt = FDateTime::Now();
|
||||||
|
RawMessage = JsonMessage;
|
||||||
|
|
||||||
|
ParsingStatus = InitializeFromJson(JsonMessage, bLogErrors);
|
||||||
|
OutStatus = ParsingStatus;
|
||||||
|
|
||||||
|
if (bLogErrors && ParsingStatus != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to create DTFluxServerResponse: %s"), *GetErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxServerResponse FDTFluxServerResponse::CreateFromJson(const FString& JsonMessage, bool bLogErrors)
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus Status;
|
||||||
|
FDTFluxServerResponse Response(JsonMessage, Status, bLogErrors);
|
||||||
|
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
if (Status == EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Successfully created DTFluxServerResponse: %s"),
|
||||||
|
*Response.ToDebugString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Created DTFluxServerResponse with issues: %s"),
|
||||||
|
*Response.GetErrorMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDTFluxResponseStatus FDTFluxServerResponse::TryParse(bool bLogErrors)
|
||||||
|
{
|
||||||
|
// Vérifier que le type est présent
|
||||||
|
if (Type.IsEmpty())
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response missing 'type' field"));
|
||||||
|
ApiDataType = EDTFluxApiDataType::None;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::MissingData;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::UnknownError;
|
||||||
|
// Validation supplémentaire selon le type
|
||||||
|
if (ContainsDataType("race-data"))
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Parsing race-data response"));
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::RaceData;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("team-list"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::TeamList;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("contest-ranking"))
|
||||||
|
{
|
||||||
|
if (ContestID == -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-ranking missing ContestID"));
|
||||||
|
}
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::DataError;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::ContestRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
else if (ContainsDataType("stage-ranking"))
|
||||||
|
{
|
||||||
|
if (ContestID == -1 || StageID == -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-ranking missing ContestID or StageID"));
|
||||||
|
}
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::DataError;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (SplitID != -1)
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::SplitRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
ApiDataType = EDTFluxApiDataType::StageRanking;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("status-update"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::StatusUpdate;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("split-sensor"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::SplitSensor;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
if (ContainsDataType("team-update"))
|
||||||
|
{
|
||||||
|
ApiDataType = EDTFluxApiDataType::TeamUpdate;
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return ParsingStatus;
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::UnknownError;
|
||||||
|
}
|
||||||
|
|
||||||
|
EDTFluxResponseStatus FDTFluxServerResponse::InitializeFromJson(const FString& JsonMessage, bool bLogErrors)
|
||||||
|
{
|
||||||
|
// Parser le JSON de base
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct(JsonMessage, this, 0, 0, false, &FailureReason))
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("JSON parse error: %s\nMessage: %s"), *FailureReason.ToString(),
|
||||||
|
*JsonMessage);
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::JsonParseError;
|
||||||
|
}
|
||||||
|
// Vérifier si c'est une erreur du serveur
|
||||||
|
if (Code != -1)
|
||||||
|
{
|
||||||
|
if (bLogErrors)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Server error response: Code=%d, Message=%s"), Code, *Message);
|
||||||
|
}
|
||||||
|
return EDTFluxResponseStatus::ServerError;
|
||||||
|
}
|
||||||
|
return TryParse();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
FString FDTFluxServerResponse::GetErrorMessage() const
|
||||||
|
{
|
||||||
|
switch (ParsingStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxResponseStatus::Success:
|
||||||
|
return TEXT("No error");
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::JsonParseError:
|
||||||
|
return FString::Printf(TEXT("JSON parsing failed: %s"), *FailureReason.ToString());
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::ServerError:
|
||||||
|
return FString::Printf(TEXT("Server error %d: %s"), Code, *Message);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::InvalidType:
|
||||||
|
return FString::Printf(TEXT("Invalid or missing response type: '%s'"), *Type);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::MissingData:
|
||||||
|
return FString::Printf(TEXT("Missing required data fields for type '%s'"), *Type);
|
||||||
|
|
||||||
|
case EDTFluxResponseStatus::UnknownError:
|
||||||
|
default:
|
||||||
|
return TEXT("Unknown error occurred during parsing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxServerResponse::ToDebugString() const
|
||||||
|
{
|
||||||
|
return FString::Printf(
|
||||||
|
TEXT("DTFluxServerResponse[Status=%s, Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
|
||||||
|
*UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList)
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
if (!ValidateResponseType(TEXT("team-list")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a team-list type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
if (!ParseJsonObject(JsonObject))
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TArray<TSharedPtr<FJsonValue>>* DataArray;
|
||||||
|
if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("No 'datas' array found in team-list response"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::MissingData;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutTeamList.Participants.Empty();
|
||||||
|
for (const TSharedPtr<FJsonValue>& Value : *DataArray)
|
||||||
|
{
|
||||||
|
if (Value->Type == EJson::Object)
|
||||||
|
{
|
||||||
|
const TSharedPtr<FJsonObject> Item = Value->AsObject();
|
||||||
|
FDTFluxParticipant Participant;
|
||||||
|
if (UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant))
|
||||||
|
{
|
||||||
|
OutTeamList.Participants.Add(Participant);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Failed to parse participant from JSON"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParsingStatus = GetParsingStatus();
|
||||||
|
if (ParsingStatus == EDTFluxResponseStatus::Success && OutTeamList.Participants.Num() != 0)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d participants from team-list"),
|
||||||
|
OutTeamList.Participants.Num());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (OutTeamList.Participants.Num() == 0)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("No Participant Added"));
|
||||||
|
}
|
||||||
|
if (ParsingStatus != EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse team-list Error : %s"), *GetErrorMessage());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate)
|
||||||
|
{
|
||||||
|
return ParseTeamListResponse(OutTeamUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData)
|
||||||
|
{
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||||
|
if (!ValidateResponseType(TEXT("race-data")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a race-data type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRaceDataResponse RaceDataResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(RawMessage, &RaceDataResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse race-data JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OutRaceData.Datas.Empty();
|
||||||
|
for (const auto& Contest : RaceDataResponse.Datas)
|
||||||
|
{
|
||||||
|
FDTFluxContest NewContest;
|
||||||
|
NewContest.Name = Contest.Name;
|
||||||
|
NewContest.ContestId = Contest.Id;
|
||||||
|
NewContest.Date = Contest.Date;
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Processing Contest %d [%s] Starting at %s"),
|
||||||
|
Contest.Id, *Contest.Name, *Contest.Date.ToString());
|
||||||
|
// Satges
|
||||||
|
for (const auto& Stage : Contest.Stages)
|
||||||
|
{
|
||||||
|
FDTFluxStage NewStage;
|
||||||
|
NewStage.StageId = Stage.Id;
|
||||||
|
NewStage.Name = Stage.Name;
|
||||||
|
|
||||||
|
// Construct full Timestamps strings
|
||||||
|
FString StartTimeFString = FString::Printf(TEXT("%s %s"),
|
||||||
|
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||||
|
*Stage.StartTime);
|
||||||
|
FString EndTimeFString = FString::Printf(TEXT("%s %s"),
|
||||||
|
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||||
|
*Stage.EndTime);
|
||||||
|
FString CutOffFString = FString::Printf(TEXT("%s %s"),
|
||||||
|
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||||
|
*Stage.CutOff);
|
||||||
|
FDateTime::Parse(StartTimeFString, NewStage.StartTime);
|
||||||
|
FDateTime::Parse(EndTimeFString, NewStage.EndTime);
|
||||||
|
FDateTime::Parse(CutOffFString, NewStage.CutOff);
|
||||||
|
NewContest.Stages.Add(NewStage);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Stage %d [%s]: Start[%s], CutOff[%s], End[%s]"),
|
||||||
|
Stage.Id, *Stage.Name, *NewStage.StartTime.ToString(),
|
||||||
|
*NewStage.CutOff.ToString(), *NewStage.EndTime.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traiter les splits
|
||||||
|
for (const auto& Split : Contest.Splits)
|
||||||
|
{
|
||||||
|
FDTFluxSplit NewSplit;
|
||||||
|
NewSplit.SplitId = Split.Id;
|
||||||
|
NewSplit.Name = Split.Name;
|
||||||
|
NewContest.Splits.Add(NewSplit);
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split %d [%s]"), Split.Id, *Split.Name);
|
||||||
|
}
|
||||||
|
// Update Contest metadata
|
||||||
|
NewContest.UpdateEndTime();
|
||||||
|
NewContest.UpdateLastStageId();
|
||||||
|
|
||||||
|
OutRaceData.Datas.Add(NewContest);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d contests from race-data"),
|
||||||
|
OutRaceData.Datas.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutContestRankings)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("contest-ranking")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a contest-ranking type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxContestRankingResponse ContestRankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(
|
||||||
|
RawMessage, &ContestRankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse contest-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutContestRankings.ContestId = ContestRankingResponse.ContestID;
|
||||||
|
OutContestRankings.Rankings.Empty();
|
||||||
|
for (const auto& RankingItem : ContestRankingResponse.Datas)
|
||||||
|
{
|
||||||
|
FDTFluxContestRanking Ranking = RankingItem;
|
||||||
|
OutContestRankings.Rankings.Add(Ranking);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed contest ranking for Contest %d with %d entries"),
|
||||||
|
OutContestRankings.ContestId, OutContestRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("stage-ranking")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a stage-ranking type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxStageRankingResponse RankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxStageRankingResponse>(RawMessage, &RankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse stage-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutStageRankings.ContestId = ContestID;
|
||||||
|
OutStageRankings.StageId = StageID;
|
||||||
|
OutStageRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
|
||||||
|
OutStageRankings.Initialize();
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed stage ranking for Contest %d, Stage %d with %d entries"),
|
||||||
|
OutStageRankings.ContestId, OutStageRankings.StageId, OutStageRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error,
|
||||||
|
TEXT("Response is not a split-ranking type (stage-ranking with SplitID != -1)"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxSplitRankingResponse SplitRankingResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<
|
||||||
|
FDTFluxSplitRankingResponse>(RawMessage, &SplitRankingResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-ranking JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutSplitRankings.ContestId = ContestID;
|
||||||
|
OutSplitRankings.StageId = StageID;
|
||||||
|
OutSplitRankings.SplitId = SplitID;
|
||||||
|
OutSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
|
||||||
|
UE_LOG(logDTFluxNetwork, Log,
|
||||||
|
TEXT("Successfully parsed split ranking for Contest %d, Stage %d, Split %d with %d entries"),
|
||||||
|
OutSplitRankings.ContestId, OutSplitRankings.StageId, OutSplitRankings.SplitId,
|
||||||
|
OutSplitRankings.Rankings.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("status-update")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a status-update type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(RawMessage, &OutStatusUpdate))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status-update JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed status update for bib %d"), OutStatusUpdate.Bib);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos)
|
||||||
|
{
|
||||||
|
if (!ValidateResponseType(TEXT("split-sensor")))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-sensor type"));
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxSplitSensorResponse SplitSensorResponse;
|
||||||
|
if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &SplitSensorResponse))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-sensor JSON: %s"), *RawMessage);
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OutSplitSensorInfos.Empty();
|
||||||
|
for (const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas)
|
||||||
|
{
|
||||||
|
FDTFluxSplitSensorInfo NewSplitSensorInfo;
|
||||||
|
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib;
|
||||||
|
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID;
|
||||||
|
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
|
||||||
|
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
|
||||||
|
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
|
||||||
|
|
||||||
|
OutSplitSensorInfos.Add(NewSplitSensorInfo);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"),
|
||||||
|
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
|
||||||
|
NewSplitSensorInfo.SplitId);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num());
|
||||||
|
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxServerResponse::ShowDebug(const bool bShouldPrintRawMessage) const
|
||||||
|
{
|
||||||
|
FString DebugMsg = FString::Printf(
|
||||||
|
TEXT("DTFluxServerResponse[Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
|
||||||
|
*Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
|
||||||
|
if (bShouldPrintRawMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("%s\nRawMessage: \"%s\""), *DebugMsg, *RawMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === MÉTHODES PRIVÉES ===
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const
|
||||||
|
{
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawMessage);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, OutJsonObject) || !OutJsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse JSON: %s"), *RawMessage);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxServerResponse::ValidateResponseType(const FString& ExpectedType) const
|
||||||
|
{
|
||||||
|
if (!IsValidResponse())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Invalid server response: Code=%d, Message=%s"), Code, *Message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ContainsDataType(ExpectedType))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Expected type '%s' but got '%s'"), *ExpectedType, *Type);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@ -5,6 +5,8 @@
|
|||||||
#include "DTFluxCoreModule.h"
|
#include "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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
166
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
166
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "UObject/Object.h"
|
||||||
|
#include "Containers/Queue.h"
|
||||||
|
#include "Tickable.h"
|
||||||
|
#include "Struct/DTFluxRequestStructs.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "DTFluxQueuedManager.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure représentant une requête en file d'attente avec ses métadonnées
|
||||||
|
*/
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxQueuedRequest : public FDTFluxRequestBase
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
/** L'identifiant unique de la requête */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FGuid RequestId;
|
||||||
|
|
||||||
|
/** L'heure à laquelle la requête a été envoyée */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FDateTime CreatedAt;
|
||||||
|
|
||||||
|
/** Le type de requête */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
EDTFluxApiDataType RequestType = EDTFluxRequestType::None;
|
||||||
|
|
||||||
|
/** Identifiant de la compétition (ContestId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 ContestId = -1;
|
||||||
|
|
||||||
|
/** Identifiant de l'étape (StageId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 StageId = -1;
|
||||||
|
|
||||||
|
/** Identifiant du split (SplitId) */
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
int32 SplitId = -1;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Request")
|
||||||
|
FString RawResponse = "";
|
||||||
|
|
||||||
|
/** Délai maximum avant que la requête soit considérée comme expirée (en secondes) */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
float TimeoutSeconds = 2.0f;
|
||||||
|
|
||||||
|
/** Determine si la requête peut être mise en cache */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
bool bIsCacheable = false;
|
||||||
|
|
||||||
|
/** Validité du cache si bIsCacheable est mis à true après reception de la réponse (en secondes) */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
float CachedValidity = 50.0f;
|
||||||
|
|
||||||
|
/** Indicateur si la requête a reçu une réponse */
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux|Request")
|
||||||
|
bool bHasReceivedResponse = false;
|
||||||
|
|
||||||
|
/** Constructeur par défaut */
|
||||||
|
FDTFluxQueuedRequest()
|
||||||
|
{
|
||||||
|
RequestId = FGuid::NewGuid();
|
||||||
|
CreatedAt = FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructeur avec paramètres */
|
||||||
|
FDTFluxQueuedRequest(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||||
|
int32 InSplitId = -1)
|
||||||
|
: RequestType(InRequestType)
|
||||||
|
, ContestId(InContestId)
|
||||||
|
, StageId(InStageId)
|
||||||
|
, SplitId(InSplitId)
|
||||||
|
{
|
||||||
|
RequestId = FGuid::NewGuid();
|
||||||
|
CreatedAt = FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FDTFluxQueuedRequest& Left) const
|
||||||
|
{
|
||||||
|
return RequestId == Left.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const FDTFluxQueuedRequest& Left) const
|
||||||
|
{
|
||||||
|
return RequestId != Left.RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const FString Serialize() const;
|
||||||
|
|
||||||
|
/** Vérifie si la requête a expiré */
|
||||||
|
bool HasTimedOut() const
|
||||||
|
{
|
||||||
|
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > TimeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Vérifie si cette requête correspond aux paramètres spécifiés */
|
||||||
|
bool Matches(EDTFluxRequestType InRequestType, int32 InContestId = -1, int32 InStageId = -1,
|
||||||
|
int32 InSplitId = -1) const
|
||||||
|
{
|
||||||
|
return RequestType == InRequestType &&
|
||||||
|
(InContestId == -1 || ContestId == InContestId) &&
|
||||||
|
(InStageId == -1 || StageId == InStageId) &&
|
||||||
|
(InSplitId == -1 || SplitId == InSplitId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestTimedOut, const FDTFluxQueuedRequest&, TimedOutRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Classe Tickable gérant les requêtes WebSockets qui ne sont pas traçables nativement par l'API.
|
||||||
|
* Cette classe utilise TQueue pour gérer efficacement les requêtes en attente et vérifie leur état dans le tick.
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class DTFLUXNETWORK_API UDTFluxQueuedManager : public UObject, public FTickableGameObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** Constructeur par défaut */
|
||||||
|
UDTFluxQueuedManager();
|
||||||
|
virtual ~UDTFluxQueuedManager() override;
|
||||||
|
void Initialize();
|
||||||
|
FGuid QueueRequest(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1,
|
||||||
|
const FString& RawMessage = "");
|
||||||
|
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid);
|
||||||
|
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
|
||||||
|
bool IsRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1);
|
||||||
|
FDTFluxQueuedRequest* GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||||
|
int32 SplitId = -1);
|
||||||
|
const FDTFluxQueuedRequest* GetRequest(const FGuid& SearchedGuid);
|
||||||
|
int32 GetPendingRequestCount();
|
||||||
|
int32 CleanupTimedOutRequests();
|
||||||
|
int32 CleanCashedRequests();
|
||||||
|
void ClearAllRequests();
|
||||||
|
// bool TryProcessResponse(const FDTFluxServerResponse& Response);
|
||||||
|
|
||||||
|
|
||||||
|
// Interface FTickableGameObject
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool IsTickable() const override;
|
||||||
|
virtual TStatId GetStatId() const override;
|
||||||
|
virtual bool IsTickableWhenPaused() const override { return true; }
|
||||||
|
virtual bool IsTickableInEditor() const override { return true; }
|
||||||
|
// Interface ~FTickableGameObject
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
|
||||||
|
FOnRequestTimedOut OnRequestTimedOut;
|
||||||
|
|
||||||
|
private:
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> PendingRequestsQueue;
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> CompletedRequestsQueue;
|
||||||
|
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TimedOutRequestsQueue;
|
||||||
|
|
||||||
|
bool bIsInitialized;
|
||||||
|
float CheckInterval;
|
||||||
|
float TimeSinceLastCheck;
|
||||||
|
};
|
||||||
@ -15,9 +15,13 @@ USTRUCT()
|
|||||||
struct FDTFluxRequestBase
|
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,7 +60,7 @@ 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()
|
||||||
|
|
||||||
@ -62,6 +70,7 @@ public:
|
|||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
ContestID = -1;
|
ContestID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxContestRankingRequest(int InContestID)
|
FDTFluxContestRankingRequest(int InContestID)
|
||||||
{
|
{
|
||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
@ -76,7 +85,7 @@ 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()
|
||||||
|
|
||||||
@ -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,15 +112,13 @@ 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()
|
||||||
|
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -29,23 +42,26 @@ 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,12 +69,15 @@ 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;
|
||||||
@ -66,19 +85,24 @@ public:
|
|||||||
|
|
||||||
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,11 +124,39 @@ 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) ===
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
|
||||||
|
int InSplitId = -1);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||||
void SendMessage(const FString& Message);
|
void SendMessage(const FString& Message);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -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,30 +190,13 @@ 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;
|
||||||
@ -155,9 +204,32 @@ private:
|
|||||||
FDelegateHandle OnWsMessageEventDelegateHandle;
|
FDelegateHandle OnWsMessageEventDelegateHandle;
|
||||||
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
||||||
|
|
||||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
// === GESTION DES ÉVÉNEMENTS HTTP ===
|
||||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
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);
|
||||||
};
|
};
|
||||||
|
|||||||
28
Source/DTFluxPursuitSystem/DTFluxPursuitSystem.Build.cs
Normal file
28
Source/DTFluxPursuitSystem/DTFluxPursuitSystem.Build.cs
Normal 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"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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)
|
||||||
@ -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;
|
||||||
|
};
|
||||||
@ -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"
|
||||||
);
|
}
|
||||||
}
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
19
Source/DTFluxUtilities/Private/FTDFluxUtils.cpp
Normal file
19
Source/DTFluxUtilities/Private/FTDFluxUtils.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "FTDFluxUtils.h"
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib);
|
||||||
|
return OutParticipant.GetFormattedNameText(MaxChar, OverFlowChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedNameText(MaxChar, OverFlowChar);
|
||||||
|
}
|
||||||
25
Source/DTFluxUtilities/Public/FTDFluxUtils.h
Normal file
25
Source/DTFluxUtilities/Public/FTDFluxUtils.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// 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(const int& Bib, const int MaxChar = 10,
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
|
||||||
|
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user