Compare commits
13 Commits
fa5493adcf
...
feature/Ra
| Author | SHA1 | Date | |
|---|---|---|---|
| d419681172 | |||
| 03eb1132ef | |||
| 8f884f6224 | |||
| 9bb5e760f2 | |||
| 760a764816 | |||
| 43a7fb7400 | |||
| b63f2dd7b5 | |||
| 7e1ce2cdfa | |||
| a2be97cfe4 | |||
| e0edf5ab8d | |||
| 8387319bf8 | |||
| 27822229d0 | |||
| 801e946a89 |
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)
|
||||
|
||||
41
Resources/DTFluxRaceResult16x16.svg
Normal file
41
Resources/DTFluxRaceResult16x16.svg
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="RaceResult.icone16x16.svg"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
sodipodi:docname="RaceResult.icone16x16.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:document-units="px" /><defs
|
||||
id="defs1" /><g
|
||||
inkscape:label="Calque 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(3.0517578e-5)"><rect
|
||||
style="fill:#e22c2b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
id="rect1"
|
||||
width="512"
|
||||
height="512"
|
||||
x="-3.0517578e-05"
|
||||
y="0" /><path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
|
||||
d="m 154.64305,277.08769 -1.3605,-151.92206 146.48007,103.3977 v 184.12046 l 54.87334,-50.79185 V 199.53942 L 215.41187,92.967228 V 328.78654 Z"
|
||||
id="path1" /></g></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@ -2,33 +2,36 @@
|
||||
|
||||
public class DTFluxAPIStatus : ModuleRules
|
||||
{
|
||||
public DTFluxAPIStatus(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxAPIStatus(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core", "DTFluxCoreSubsystem",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"Projects",
|
||||
"DTFluxNetwork",
|
||||
"DTFluxProjectSettings",
|
||||
"DTFluxCore",
|
||||
"EditorStyle",
|
||||
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
|
||||
"UnrealEd",
|
||||
"Settings"
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"Projects",
|
||||
"DTFluxNetwork",
|
||||
"DTFluxProjectSettings",
|
||||
"DTFluxCore",
|
||||
"EditorStyle",
|
||||
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
|
||||
"UnrealEd",
|
||||
"Settings",
|
||||
"DTFluxCoreSubsystem",
|
||||
"InputCore",
|
||||
"OutputLog",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -5,12 +5,11 @@
|
||||
|
||||
#include "SlateOptMacros.h"
|
||||
#include "DTFluxAPIStatusModule.h"
|
||||
#include "EditorStyleSet.h"
|
||||
#include "ISettingsCategory.h"
|
||||
#include "ISettingsContainer.h"
|
||||
#include "DTFluxCoreSubsystem.h"
|
||||
#include "ISettingsModule.h"
|
||||
#include "ISettingsSection.h"
|
||||
#include "Styling/SlateIconFinder.h"
|
||||
#include "MovieSceneSequenceID.h"
|
||||
#include "OutputLogCreationParams.h"
|
||||
#include "OutputLogModule.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||
@ -27,31 +26,31 @@ void SDTFluxStatusWidget::OnOpenSettingsClicked()
|
||||
|
||||
FReply SDTFluxStatusWidget::OnRaceDatasClicked()
|
||||
{
|
||||
DTFlux->SendRequest(EDTFluxRequestType::RaceData);
|
||||
DTFluxNetwork->SendRequest(EDTFluxRequestType::RaceData);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FReply SDTFluxStatusWidget::OnTeamListClicked()
|
||||
{
|
||||
DTFlux->SendRequest(EDTFluxRequestType::TeamList);
|
||||
DTFluxNetwork->SendRequest(EDTFluxRequestType::TeamList);
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
|
||||
DTFlux =
|
||||
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||
DTFluxNetwork =
|
||||
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||
DTFluxCore =
|
||||
GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||
ConnectionActionButtonText.Set(
|
||||
DTFlux->WsStatus != EDTFluxConnectionStatus::Connected ?
|
||||
FText::FromString("Connect") :
|
||||
FText::FromString("Disconnect")
|
||||
);
|
||||
|
||||
DTFluxNetwork->WsStatus != EDTFluxConnectionStatus::Connected
|
||||
? FText::FromString("Connect")
|
||||
: FText::FromString("Disconnect")
|
||||
);
|
||||
FOutputLogModule& OutputLogModule = FModuleManager::LoadModuleChecked<FOutputLogModule>("OutputLog");
|
||||
const FOutputLogCreationParams Params;
|
||||
bCanSupportFocus = true;
|
||||
|
||||
|
||||
|
||||
PopulateComboBoxItems();
|
||||
|
||||
FSlimHorizontalToolBarBuilder ToolBarBuilder(
|
||||
nullptr,
|
||||
@ -73,233 +72,199 @@ void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
|
||||
ToolBarBuilder.EndSection();
|
||||
|
||||
|
||||
|
||||
FSlateFontInfo TitleTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
|
||||
TitleTextFont.Size = 15;
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
#pragma region ToolBarSection
|
||||
SNew(SVerticalBox)
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
|
||||
[
|
||||
SNew(SBox)
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.FillWidth(2.0)
|
||||
SNew(SBox)
|
||||
[
|
||||
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 region WebsocketStatusSection
|
||||
// Main VerticalBox
|
||||
+SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SBorder)
|
||||
.Padding(6.0f)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
.VAlign(VAlign_Center)
|
||||
.HAlign(HAlign_Center)
|
||||
.MaxWidth(175.0)
|
||||
.MinWidth(150.0)
|
||||
#pragma region WebsocketStatusSection
|
||||
SNew(SBorder)
|
||||
.Padding(6.0f)
|
||||
[
|
||||
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)
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
.VAlign(VAlign_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 region DataModelControlSection
|
||||
+SVerticalBox::Slot()
|
||||
.VAlign(VAlign_Fill)
|
||||
.HAlign(HAlign_Fill)
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SBox)
|
||||
]
|
||||
+ SVerticalBox::Slot()
|
||||
.VAlign(VAlign_Fill)
|
||||
.HAlign(HAlign_Fill)
|
||||
.AutoHeight()
|
||||
[
|
||||
SNew(SHorizontalBox)
|
||||
+SHorizontalBox::Slot()
|
||||
#pragma region DataModelControlSection
|
||||
SNew(SBorder)
|
||||
.Padding(6.0f)
|
||||
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString("Get RaceDatas"))
|
||||
.OnClicked(this, &SDTFluxStatusWidget::OnRaceDatasClicked)
|
||||
]
|
||||
+SHorizontalBox::Slot()
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString("Get TeamList"))
|
||||
.OnClicked(this, &SDTFluxStatusWidget::OnTeamListClicked)
|
||||
SNew(SHorizontalBox)
|
||||
+ SHorizontalBox::Slot()
|
||||
[
|
||||
SNew(SButton)
|
||||
.Text(FText::FromString("Get RaceDatas"))
|
||||
.OnClicked(this, &SDTFluxStatusWidget::OnRaceDatasClicked)
|
||||
]
|
||||
+ SHorizontalBox::Slot()
|
||||
[
|
||||
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
|
||||
{
|
||||
|
||||
FString Status =
|
||||
UEnum::GetDisplayValueAsText(DTFlux->WsStatus).ToString();
|
||||
UEnum::GetDisplayValueAsText(DTFluxNetwork->WsStatus).ToString();
|
||||
return
|
||||
FText::FromString(Status);
|
||||
FText::FromString(Status);
|
||||
// FText::FromString("Unknown");
|
||||
|
||||
}
|
||||
|
||||
FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
|
||||
{
|
||||
switch (DTFlux->WsStatus)
|
||||
switch (DTFluxNetwork->WsStatus)
|
||||
{
|
||||
case EDTFluxConnectionStatus::Connected:
|
||||
return FText::FromString("Disconnect");
|
||||
@ -310,39 +275,40 @@ FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
|
||||
|
||||
FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked()
|
||||
{
|
||||
if(DTFlux)
|
||||
if (DTFluxNetwork)
|
||||
{
|
||||
switch (DTFlux->WsStatus)
|
||||
switch (DTFluxNetwork->WsStatus)
|
||||
{
|
||||
case EDTFluxConnectionStatus::Connected:
|
||||
DTFlux->Reconnect();
|
||||
break;
|
||||
default:
|
||||
DTFlux->Connect();
|
||||
break;
|
||||
case EDTFluxConnectionStatus::Connected:
|
||||
DTFluxNetwork->Reconnect();
|
||||
break;
|
||||
default:
|
||||
DTFluxNetwork->Connect();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return FReply::Handled();
|
||||
}
|
||||
|
||||
FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
|
||||
{
|
||||
FColor Color;
|
||||
switch (DTFlux->WsStatus)
|
||||
switch (DTFluxNetwork->WsStatus)
|
||||
{
|
||||
case EDTFluxConnectionStatus::Unset:
|
||||
Color = FColor::Orange;
|
||||
break;
|
||||
case EDTFluxConnectionStatus::Connected:
|
||||
Color = FColor::Green;
|
||||
Color = FColor::Green;
|
||||
break;
|
||||
case EDTFluxConnectionStatus::NotConnected:
|
||||
Color = FColor::Orange;
|
||||
Color = FColor::Orange;
|
||||
break;
|
||||
case EDTFluxConnectionStatus::Closed:
|
||||
Color = FColor::Magenta;
|
||||
Color = FColor::Magenta;
|
||||
break;
|
||||
default:
|
||||
Color = FColor::Red;
|
||||
Color = FColor::Red;
|
||||
break;
|
||||
}
|
||||
return FSlateColor(Color);
|
||||
@ -350,8 +316,8 @@ FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
|
||||
|
||||
FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
|
||||
{
|
||||
FColor Color= FColor::Green;
|
||||
switch (DTFlux->WsStatus)
|
||||
FColor Color = FColor::Green;
|
||||
switch (DTFluxNetwork->WsStatus)
|
||||
{
|
||||
case EDTFluxConnectionStatus::Connected:
|
||||
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
|
||||
|
||||
@ -10,14 +10,16 @@
|
||||
*
|
||||
*/
|
||||
class UDTFluxNetworkSubsystem;
|
||||
class UDTFluxCoreSubsystem;
|
||||
class SSuperListView;
|
||||
|
||||
|
||||
class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SDTFluxStatusWidget)
|
||||
{
|
||||
}
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
@ -29,8 +31,42 @@ public:
|
||||
TAttribute<FText> ConnectionActionButtonText;
|
||||
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:
|
||||
UDTFluxNetworkSubsystem* DTFlux = nullptr;
|
||||
UDTFluxNetworkSubsystem* DTFluxNetwork = nullptr;
|
||||
UDTFluxCoreSubsystem* DTFluxCore = nullptr;
|
||||
// // TODO make a struct
|
||||
FText GetWebSocketStatusText() const;
|
||||
FText GetWebConnectActionButtonText() const;
|
||||
@ -39,6 +75,18 @@ private:
|
||||
TSharedPtr<STextBlock> WsStatusText;
|
||||
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 DTFluxAssetsEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxAssetsEditor(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"AssetTools",
|
||||
"SlateCore",
|
||||
"UnrealEd",
|
||||
"DTFluxCore",
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"SlateCore",
|
||||
"Slate",
|
||||
"AssetTools",
|
||||
"UnrealEd",
|
||||
"DTFluxCore",
|
||||
"ToolMenus",
|
||||
"EditorWidgets",
|
||||
"EditorStyle",
|
||||
"PropertyEditor",
|
||||
"SharedSettingsWidgets",
|
||||
"PropertyEditor",
|
||||
"DesktopWidgets",
|
||||
"ApplicationCore",
|
||||
"InputCore"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -3,26 +3,58 @@
|
||||
#include "DTFluxAssetModelTypeActions.h"
|
||||
#include "IAssetTools.h"
|
||||
#include "AssetToolsModule.h"
|
||||
#include "DTFluxModelAssetDetailCustomization.h"
|
||||
#include "Assets/DTFluxModelAsset.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
|
||||
|
||||
|
||||
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
|
||||
|
||||
void FDTFluxAssetsEditorModule::StartupModule()
|
||||
{
|
||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
|
||||
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
||||
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
||||
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
||||
RegisterCustomizations();
|
||||
}
|
||||
|
||||
void FDTFluxAssetsEditorModule::ShutdownModule()
|
||||
{
|
||||
if(DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
||||
if (DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
||||
{
|
||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||
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
|
||||
|
||||
@ -0,0 +1,222 @@
|
||||
// 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)
|
||||
{
|
||||
CustomizeDetailsWithRawDataAccess(DetailBuilder);
|
||||
}
|
||||
|
||||
void FDTFluxModelAssetCustomization::CustomizeDetailsWithoutRawDataAsset(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
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
private:
|
||||
TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions;
|
||||
void RegisterCustomizations();
|
||||
void UnregisterCustomizations();
|
||||
};
|
||||
|
||||
@ -0,0 +1,27 @@
|
||||
// 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 CustomizeDetailsWithoutRawDataAsset(IDetailLayoutBuilder& DetailBuilder);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
@ -36,19 +46,20 @@ void UDTFluxModelAsset::AddPerson(const FDTFluxPerson& InPerson)
|
||||
|
||||
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;
|
||||
if(GetContestById(ContestId, TargetContest))
|
||||
if (GetContestById(ContestId, TargetContest))
|
||||
{
|
||||
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"),
|
||||
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||
if(!PersonExists(Person))
|
||||
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||
if (!PersonExists(Person))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -68,10 +79,11 @@ bool UDTFluxModelAsset::PersonExists(const FDTFluxPerson& InPerson) const
|
||||
FString UDTFluxModelAsset::GetContestNameForId(const int InContestID)
|
||||
{
|
||||
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 !!!"),
|
||||
InContestID);
|
||||
UE_LOG(logDTFluxCore, Warning,
|
||||
TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"),
|
||||
InContestID);
|
||||
}
|
||||
return Contest.Name;
|
||||
}
|
||||
@ -81,6 +93,46 @@ void UDTFluxModelAsset::AddContestRanking(const FDTFluxContestRankings& NewConte
|
||||
ContestRankings.Add(NewContestRankings.ContestId, NewContestRankings);
|
||||
}
|
||||
|
||||
void UDTFluxModelAsset::UpdateParticipant(const FDTFluxParticipant& Participant)
|
||||
{
|
||||
// TODO : If update is on Bib we are totally lost as we search by bib.
|
||||
int Bib = Participant.Bib;
|
||||
if (Participants.Contains(Bib))
|
||||
{
|
||||
TArray<FDTFluxPerson> InTeammate = Participant.Teammate;
|
||||
Participants[Bib].Elite = Participant.Elite;
|
||||
Participants[Bib].ContestId = Participant.ContestId;
|
||||
Participants[Bib].Club = Participant.Club;
|
||||
Participants[Bib].Category = Participant.Category;
|
||||
Participants[Bib].Team = Participant.Team;
|
||||
Participants[Bib].Status = Participant.Status;
|
||||
//TODO : Update Person
|
||||
for (const auto& Person : InTeammate)
|
||||
{
|
||||
//Don't know what to do...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxModelAsset::UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
|
||||
{
|
||||
if (Participants.Contains(NewParticipantStatus.Bib))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
@ -6,24 +6,6 @@
|
||||
void FDTFluxContestRanking::Dump() const
|
||||
{
|
||||
UE_LOG(logDTFluxCore, Log,
|
||||
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
||||
Rank, Bib, *Gap, *Time );
|
||||
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
||||
Rank, Bib, *Gap, *Time);
|
||||
};
|
||||
|
||||
// void FDTFluxStageRanking::Dump() const
|
||||
// {
|
||||
// UE_LOG(logDTFluxCore, Log, TEXT("RANKING : %02d. Participant bib %d %s %s %s %s %s"),
|
||||
// Rank, Bib, *Gap, *TimeSwim,
|
||||
// *TimeTransition, *TimeRun, *StartTime.ToString());
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
|
||||
// void FDTFluxSplitRanking::Dump() const
|
||||
// {
|
||||
// UE_LOG(logDTFluxCore, Log, TEXT("SplitGapItem"))
|
||||
// // Participant.Dump();
|
||||
// UE_LOG(logDTFluxCore, Log, TEXT("Bib %02d Rank %02d Gap %s Time %s"), Bib, Rank, *Gap, *Time);
|
||||
// }
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||
|
||||
|
||||
#include "DTFluxCoreModule.h"
|
||||
|
||||
|
||||
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
|
||||
@ -15,109 +15,103 @@ 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
|
||||
if (MaxChar <= 0)
|
||||
{
|
||||
return FText::GetEmpty();
|
||||
}
|
||||
FString FirstName;
|
||||
FString LastName;
|
||||
if(IsTeam())
|
||||
{
|
||||
LastName = Team;
|
||||
}
|
||||
// Récupère la première lettre du prénom en majuscule
|
||||
FString Initial;
|
||||
if (!FirstName.IsEmpty())
|
||||
{
|
||||
Initial = FirstName.Left(1).ToUpper() + " ";
|
||||
}
|
||||
|
||||
// Nom complet en majuscules
|
||||
FString FormattedLastName = LastName.ToUpper();
|
||||
|
||||
// Construction du nom final
|
||||
FString FullName = Initial + FormattedLastName;
|
||||
|
||||
// Tronque si nécessaire
|
||||
if (FullName.Len() > MaxChar)
|
||||
{
|
||||
// On essaie de garder autant de caractères que possible
|
||||
const int32 AvailableLength = MaxChar - Initial.Len();
|
||||
if (AvailableLength <= 0)
|
||||
if (MaxChar <= 0)
|
||||
{
|
||||
// Pas assez de place pour le nom → juste l'initiale ?
|
||||
return FText::FromString(Initial);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Coupe le nom pour qu’il rentre dans la limite
|
||||
const int32 TruncateLength = FMath::Min(AvailableLength, FormattedLastName.Len());
|
||||
FullName = Initial + FormattedLastName.Left(TruncateLength);
|
||||
|
||||
// Si on a coupé trop court, on ajoute le suffixe
|
||||
if (FormattedLastName.Len() > TruncateLength)
|
||||
FString FirstName;
|
||||
FString LastName;
|
||||
if (IsTeam())
|
||||
{
|
||||
// On vérifie qu'il reste de la place pour le suffixe
|
||||
const int32 CurrentLength = FullName.Len();
|
||||
const int32 OverflowLength = OverflowChars.Len();
|
||||
|
||||
if (CurrentLength + OverflowLength <= MaxChar)
|
||||
{
|
||||
FullName += OverflowChars;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Il faut tronquer davantage pour faire de la place au suffixe
|
||||
const int32 RemainingSpace = MaxChar - CurrentLength;
|
||||
if (RemainingSpace > 0)
|
||||
{
|
||||
FullName = FullName.Left(MaxChar - OverflowLength) + OverflowChars;
|
||||
}
|
||||
else
|
||||
{
|
||||
FullName = FullName.Left(MaxChar);
|
||||
}
|
||||
}
|
||||
LastName = Team;
|
||||
}
|
||||
else
|
||||
{
|
||||
FirstName = Teammate[0].FirstName;
|
||||
LastName = Teammate[0].LastName;
|
||||
}
|
||||
FString Initial;
|
||||
if (!FirstName.IsEmpty())
|
||||
{
|
||||
Initial = FirstName.Left(1).ToUpper() + " ";
|
||||
}
|
||||
}
|
||||
|
||||
return FText::FromString(FullName);
|
||||
FString FormattedLastName = LastName.ToUpper();
|
||||
|
||||
FString FullName = Initial + FormattedLastName;
|
||||
UE_LOG(logDTFluxCore, Error, TEXT("FullName for Bib %i is %s"), Bib, *FullName);
|
||||
|
||||
if (FullName.Len() <= MaxChar)
|
||||
{
|
||||
return FullName;
|
||||
}
|
||||
|
||||
const int32 OverflowLength = OverflowChars.Len();
|
||||
|
||||
if (OverflowLength > MaxChar)
|
||||
{
|
||||
return FullName.Left(MaxChar);
|
||||
}
|
||||
|
||||
if (Initial.Len() + OverflowLength > MaxChar)
|
||||
{
|
||||
return FullName.Left(MaxChar);
|
||||
}
|
||||
|
||||
const int32 AvailableForLastName = MaxChar - Initial.Len() - OverflowLength;
|
||||
|
||||
if (AvailableForLastName <= 0)
|
||||
{
|
||||
return FullName.Left(MaxChar);
|
||||
}
|
||||
|
||||
FString TruncatedName = Initial + FormattedLastName.Left(AvailableForLastName) + OverflowChars;
|
||||
|
||||
if (TruncatedName.Len() > MaxChar)
|
||||
{
|
||||
return TruncatedName.Left(MaxChar);
|
||||
}
|
||||
|
||||
return TruncatedName;
|
||||
}
|
||||
}
|
||||
|
||||
FText FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar)
|
||||
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) const
|
||||
{
|
||||
FString BibText = FString::FromInt(Bib) + " ";
|
||||
FText FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar );
|
||||
return FText::FromString(BibText + FormattedName.ToString());
|
||||
FString FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar);
|
||||
return BibText + FormattedName;
|
||||
}
|
||||
|
||||
// Constructeur privé depuis JSON
|
||||
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
|
||||
: Bib(JsonObject->GetIntegerField(TEXT("bib")))
|
||||
, ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
|
||||
, Category(JsonObject->GetStringField(TEXT("category")))
|
||||
, Club(JsonObject->GetStringField(TEXT("club")))
|
||||
, Elite(JsonObject->GetBoolField(TEXT("elite")))
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
|
||||
, Team(JsonObject->GetStringField(TEXT("team")))
|
||||
, bIsMassStartParticipant(false)
|
||||
, LastSplitId(-1)
|
||||
, ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
|
||||
, Category(JsonObject->GetStringField(TEXT("category")))
|
||||
, Club(JsonObject->GetStringField(TEXT("club")))
|
||||
, Elite(JsonObject->GetBoolField(TEXT("elite")))
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
|
||||
, Team(JsonObject->GetStringField(TEXT("team")))
|
||||
, bIsMassStartParticipant(false)
|
||||
, CurrentSplit(-1)
|
||||
{
|
||||
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 LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index);
|
||||
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index);
|
||||
// max 10 Persons
|
||||
if(Index >= 10)
|
||||
if (Index >= 10)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
|
||||
&& !JsonObject->HasField(GenderKey))
|
||||
&& !JsonObject->HasField(GenderKey))
|
||||
{
|
||||
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
|
||||
break;
|
||||
@ -134,7 +128,6 @@ FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject
|
||||
Teammate.Add(Person);
|
||||
}
|
||||
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
|
||||
|
||||
}
|
||||
|
||||
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
|
||||
@ -147,7 +140,7 @@ int FDTFluxParticipant::GetTeammateNum() const
|
||||
return Teammate.Num();
|
||||
}
|
||||
|
||||
bool FDTFluxParticipant::IsTeam()
|
||||
bool FDTFluxParticipant::IsTeam() const
|
||||
{
|
||||
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 "Types/Struct/DTFluxCompositeKey.h"
|
||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
#include "DTFluxModelAsset.generated.h"
|
||||
|
||||
|
||||
@ -19,8 +20,8 @@ class DTFLUXCORE_API UDTFluxModelAsset : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
|
||||
public:
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||
FString EventName = "MyEvent";
|
||||
|
||||
@ -44,11 +45,15 @@ public:
|
||||
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
|
||||
void AddContest(const FDTFluxContest &Contest);
|
||||
void AddContest(const FDTFluxContest& Contest);
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
||||
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")
|
||||
void AddPerson(const FDTFluxPerson& InPerson);
|
||||
|
||||
@ -75,4 +80,13 @@ public:
|
||||
|
||||
UFUNCTION()
|
||||
void AddContestRanking(const FDTFluxContestRankings& NewContestRankings);
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||
void UpdateParticipant(const FDTFluxParticipant& Participant);
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||
void UpdateParticipantStatus(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
|
||||
|
||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
|
||||
bool GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant);
|
||||
};
|
||||
|
||||
@ -7,23 +7,68 @@
|
||||
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class EDTFluxRequestType : uint8
|
||||
enum class EDTFluxApiDataType : 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"),
|
||||
None = 0 UMETA(DisplayName="None"),
|
||||
|
||||
// Types bidirectionnels (requête/réponse)
|
||||
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"),
|
||||
|
||||
// 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)
|
||||
enum class EDTFluxConnectionStatus : uint8
|
||||
{
|
||||
Unset = 0 UMETA(DisplayName="Unset"),
|
||||
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
||||
Error = 1 << 1 UMETA(DisplayName="Error"),
|
||||
Closed = 1 << 2 UMETA(DisplayName="Closed"),
|
||||
NotConnected= 1 << 3 UMETA(DisplayName="NotConnected")
|
||||
Unset = 0 UMETA(DisplayName="Unset"),
|
||||
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
||||
Error = 1 << 1 UMETA(DisplayName="Error"),
|
||||
Closed = 1 << 2 UMETA(DisplayName="Closed"),
|
||||
NotConnected = 1 << 3 UMETA(DisplayName="NotConnected")
|
||||
};
|
||||
|
||||
@ -56,7 +56,7 @@ ENUM_CLASS_FLAGS(EDTFluxSplitType);
|
||||
|
||||
|
||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
||||
enum EDTFluxSortingFilter : uint8
|
||||
enum class EDTFluxSortingFilter : uint8
|
||||
{
|
||||
None = 0b00000000 UMETA(DisplayName="No Sorting"),
|
||||
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
|
||||
@ -70,3 +70,20 @@ enum EDTFluxSortingFilter : uint8
|
||||
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank")
|
||||
};
|
||||
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 "DTFluxCompositeKey.generated.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct DTFLUXCORE_API FDTFluxStageKey
|
||||
USTRUCT()
|
||||
struct FDTFluxCompositeKey
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
GENERATED_BODY()
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct DTFLUXCORE_API FDTFluxSplitKey
|
||||
struct DTFLUXCORE_API FDTFluxStageKey : public FDTFluxCompositeKey
|
||||
{
|
||||
GENERATED_BODY()
|
||||
FDTFluxSplitKey() = default;
|
||||
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId )
|
||||
:ContestId(InContestId)
|
||||
, StageId(InStageId)
|
||||
, SplitId(InSplitId){};
|
||||
GENERATED_BODY()
|
||||
FDTFluxStageKey() = default;
|
||||
|
||||
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;
|
||||
FDTFluxStageKey(const int InContestId, const int InStageId)
|
||||
: ContestId(InContestId)
|
||||
, StageId(InStageId)
|
||||
{
|
||||
};
|
||||
|
||||
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key)
|
||||
{
|
||||
return HashCombine(
|
||||
GetTypeHash(Key.ContestId),
|
||||
GetTypeHash(Key.StageId),
|
||||
GetTypeHash(Key.SplitId)
|
||||
);
|
||||
}
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||
int ContestId = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||
int StageId = -1;
|
||||
|
||||
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);
|
||||
}
|
||||
friend uint32 GetTypeHash(const FDTFluxStageKey& Key)
|
||||
{
|
||||
return HashCombine(
|
||||
GetTypeHash(Key.ContestId),
|
||||
GetTypeHash(Key.StageId)
|
||||
);
|
||||
}
|
||||
|
||||
FText GetTooltipText() const
|
||||
{
|
||||
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
|
||||
FText::AsNumber(ContestId),
|
||||
FText::AsNumber(StageId),
|
||||
FText::AsNumber(SplitId)
|
||||
);
|
||||
}
|
||||
bool operator==(const FDTFluxStageKey& Other) const
|
||||
{
|
||||
return ContestId == Other.ContestId && StageId == Other.StageId;
|
||||
}
|
||||
|
||||
FString GetDisplayName() const
|
||||
{
|
||||
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
|
||||
}
|
||||
|
||||
FText GetTooltipText() const
|
||||
{
|
||||
return FText::Format(INVTEXT("Contest{0}|Stage{1}"),
|
||||
FText::AsNumber(ContestId),
|
||||
FText::AsNumber(StageId));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
USTRUCT(BlueprintType)
|
||||
struct DTFLUXCORE_API FDTFluxSplitKey : public FDTFluxCompositeKey
|
||||
{
|
||||
GENERATED_BODY()
|
||||
FDTFluxSplitKey() = default;
|
||||
|
||||
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId)
|
||||
: ContestId(InContestId)
|
||||
, StageId(InStageId)
|
||||
, SplitId(InSplitId)
|
||||
{
|
||||
};
|
||||
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||
int ContestId = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||
int StageId = -1;
|
||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||
int SplitId = -1;
|
||||
|
||||
friend uint32 GetTypeHash(const FDTFluxSplitKey& Key)
|
||||
{
|
||||
return HashCombine(
|
||||
GetTypeHash(Key.ContestId),
|
||||
GetTypeHash(Key.StageId),
|
||||
GetTypeHash(Key.SplitId)
|
||||
);
|
||||
}
|
||||
|
||||
bool operator==(const FDTFluxSplitKey& Other) const
|
||||
{
|
||||
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
|
||||
}
|
||||
|
||||
FString GetDisplayName() const
|
||||
{
|
||||
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
|
||||
}
|
||||
|
||||
FText GetTooltipText() const
|
||||
{
|
||||
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
|
||||
FText::AsNumber(ContestId),
|
||||
FText::AsNumber(StageId),
|
||||
FText::AsNumber(SplitId)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||
int SplitId = -1;
|
||||
@ -28,7 +29,6 @@ public:
|
||||
// // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp);
|
||||
// void SortByRank();
|
||||
// TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
@ -39,6 +39,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
||||
struct DTFLUXCORE_API FDTFluxStage
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||
int StageId;
|
||||
@ -60,6 +61,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
||||
struct DTFLUXCORE_API FDTFluxContest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||
int ContestId = -1;
|
||||
@ -73,18 +75,89 @@ public:
|
||||
TArray<FDTFluxSplit> Splits;
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||
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()
|
||||
struct DTFLUXCORE_API FDTFluxRaceData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
// ReSharper disable once IdentifierTypo
|
||||
TArray<FDTFluxContest> Datas;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||
struct DTFLUXCORE_API FDTFluxContestRanking
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
int Bib;
|
||||
@ -31,7 +32,7 @@ public:
|
||||
FString SpeedRunningAverage;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
||||
FString SpeedTotalAverage;
|
||||
void Dump () const;
|
||||
void Dump() const;
|
||||
};
|
||||
|
||||
|
||||
@ -39,13 +40,14 @@ USTRUCT(BlueprintType)
|
||||
struct FDTFluxContestRankings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
TArray<FDTFluxContestRanking> Rankings;
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int ContestId;
|
||||
//TODO check if necessary ???
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
FString ContestName;
|
||||
|
||||
void SetName(const FString Name)
|
||||
@ -62,6 +64,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||
struct DTFLUXCORE_API FDTFluxDetailedRankingItem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
int Bib;
|
||||
@ -82,24 +85,25 @@ public:
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
FDateTime StartTime;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
float SpeedRunning;
|
||||
FString SpeedRunning;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
float SpeedTotal;
|
||||
FString SpeedTotal;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||
float SpeedSwim;
|
||||
FString SpeedSwim;
|
||||
void Dump() const;
|
||||
|
||||
};
|
||||
|
||||
USTRUCT(BlueprintType)
|
||||
struct FDTFluxDetailedRankings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int ContestId;
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int StageId;
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int ContestId = -1;
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int StageId = -1;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
TArray<FDTFluxDetailedRankingItem> Rankings;
|
||||
};
|
||||
|
||||
@ -139,29 +143,61 @@ struct FDTFluxStageRankings : public FDTFluxDetailedRankings
|
||||
{
|
||||
return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId);
|
||||
}
|
||||
|
||||
inline FDTFluxStageKey GetCompositeKey() const
|
||||
{
|
||||
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)
|
||||
struct FDTFluxSplitRankings : public FDTFluxDetailedRankings
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||
int SplitId;
|
||||
|
||||
inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings)
|
||||
{
|
||||
return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId);
|
||||
}
|
||||
|
||||
inline FDTFluxSplitKey GetCompositeKey() const
|
||||
{
|
||||
return FDTFluxSplitKey(ContestId, StageId, SplitId);
|
||||
}
|
||||
|
||||
inline bool IsInitialized() const
|
||||
{
|
||||
return ContestId > 0 && StageId > 0 && SplitId > 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "DTFluxCoreModule.h"
|
||||
#include "Types/Enum/DTFluxModelEnums.h"
|
||||
#include "DTFluxTeamListStruct.generated.h"
|
||||
|
||||
@ -12,6 +11,7 @@ USTRUCT()
|
||||
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
FString Type = "team-list-item";
|
||||
@ -44,13 +44,11 @@ public:
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||
struct DTFLUXCORE_API FDTFluxPerson
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||
FString FirstName;
|
||||
@ -66,20 +64,23 @@ public:
|
||||
bool operator==(const FDTFluxPerson& Right) const
|
||||
{
|
||||
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
|
||||
{
|
||||
return (FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()).Len() == Length;
|
||||
}
|
||||
|
||||
bool operator!=(const int Length) const
|
||||
{
|
||||
return !(*this == Length);
|
||||
}
|
||||
|
||||
bool operator!=(const FDTFluxPerson& Right) const
|
||||
{
|
||||
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
||||
!= Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
|
||||
!= Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
|
||||
}
|
||||
};
|
||||
|
||||
@ -91,28 +92,40 @@ struct DTFLUXCORE_API FDTFluxParticipant
|
||||
|
||||
friend class UDTFluxModelAsset;
|
||||
friend class UDTFluxParticipantFactory;
|
||||
|
||||
public:
|
||||
// Constructeur public par défaut requis par Unreal
|
||||
FDTFluxParticipant()
|
||||
: Bib(-1)
|
||||
,ContestId(-1)
|
||||
, Elite(false)
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(0))
|
||||
, bIsMassStartParticipant(false)
|
||||
, LastSplitId(0)
|
||||
, ContestId(-1)
|
||||
, Elite(false)
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(0))
|
||||
, bIsMassStartParticipant(false)
|
||||
, CurrentSplit(-1)
|
||||
{
|
||||
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
|
||||
&& Teammate.IsEmpty();
|
||||
return Bib == -1
|
||||
&& ContestId == -1
|
||||
&& Category.IsEmpty()
|
||||
&& Club.IsEmpty()
|
||||
&& !Elite
|
||||
&& Status == static_cast<EDTFluxParticipantStatusType>(0)
|
||||
&& Team.IsEmpty()
|
||||
&& !bIsMassStartParticipant
|
||||
&& CurrentSplit == -1
|
||||
&& Teammate.IsEmpty();
|
||||
}
|
||||
|
||||
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
||||
int Bib = -1;
|
||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
||||
@ -130,16 +143,50 @@ public:
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||
bool bIsMassStartParticipant = false;
|
||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
|
||||
int LastSplitId = -1;
|
||||
int CurrentSplit = -1;
|
||||
|
||||
// void Dump() const;
|
||||
void AddTeammate(const FDTFluxPerson& Person);
|
||||
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:
|
||||
|
||||
// --- Constructeur privé ---
|
||||
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)
|
||||
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
|
||||
int GetTeammateNum() const;
|
||||
bool IsTeam();
|
||||
bool IsTeam() const;
|
||||
};
|
||||
|
||||
|
||||
@ -162,6 +209,7 @@ USTRUCT(BlueprintType)
|
||||
struct DTFLUXCORE_API FDTFluxTeamListDefinition
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
// ReSharper disable once IdentifierTypo
|
||||
@ -175,9 +223,12 @@ struct FDTFluxTeamStatusUpdate
|
||||
|
||||
public:
|
||||
FDTFluxTeamStatusUpdate() = default;
|
||||
|
||||
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
||||
:Bib(InBib)
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus)){};
|
||||
: Bib(InBib)
|
||||
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
|
||||
{
|
||||
};
|
||||
|
||||
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
38
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
38
Source/DTFluxCore/Public/Types/Struct/FDTFluxPursuitInfo.h
Normal file
@ -0,0 +1,38 @@
|
||||
// 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;
|
||||
|
||||
UPROPERTY()
|
||||
int ContestId = -1;
|
||||
};
|
||||
@ -2,31 +2,31 @@
|
||||
|
||||
public class DTFluxCoreSubsystem : ModuleRules
|
||||
{
|
||||
public DTFluxCoreSubsystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxCoreSubsystem(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UnrealEd",
|
||||
"DTFluxNetwork",
|
||||
"DTFluxProjectSettings",
|
||||
"DTFluxCore",
|
||||
"JsonUtilities",
|
||||
"Json"
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"UnrealEd",
|
||||
"DTFluxNetwork",
|
||||
"DTFluxProjectSettings",
|
||||
"DTFluxCore",
|
||||
"JsonUtilities",
|
||||
"Json"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
#include "DTFluxCoreSubsystemModule.h"
|
||||
#include "DTFluxGeneralSettings.h"
|
||||
#include "DTFluxPursuitManager.h"
|
||||
#include "FileHelpers.h"
|
||||
#include "Assets/DTFluxModelAsset.h"
|
||||
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||
@ -15,30 +16,23 @@ void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("[UDTFluxCoreSubsystem] Initializing..."));
|
||||
if(!DataStorage)
|
||||
if (!DataStorage)
|
||||
{
|
||||
const UDTFluxGeneralSettings* GeneralSettings = GetDefault<UDTFluxGeneralSettings>();
|
||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GeneralSettings->ModelAsset;
|
||||
// if(ModelAsset.IsValid())
|
||||
// {
|
||||
// }
|
||||
// UE_LOG(logDTFluxCore, Error, TEXT("ModelAsset Not Valid"));
|
||||
DataStorage = DataStorage = Cast<UDTFluxModelAsset>(ModelAsset.LoadSynchronous());
|
||||
if(!DataStorage)
|
||||
DataStorage = ModelAsset.LoadSynchronous();
|
||||
if (!DataStorage)
|
||||
{
|
||||
UE_LOG(logDTFluxCore, Error, TEXT("DataStorage Not Valid"));
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Not Valid"));
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// MakeStorageEditable(DataStorage);
|
||||
// }
|
||||
}
|
||||
//TODO REMOVE This as it's only for testing purpose
|
||||
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||
if(NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected)
|
||||
if (NetworkSubsystem->WsStatus != EDTFluxConnectionStatus::Connected)
|
||||
{
|
||||
RegisterDelegates();
|
||||
}
|
||||
PursuitManager = NewObject<UDTFluxPursuitManager>();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::Deinitialize()
|
||||
@ -48,73 +42,78 @@ void UDTFluxCoreSubsystem::Deinitialize()
|
||||
|
||||
void UDTFluxCoreSubsystem::SaveDataStorage()
|
||||
{
|
||||
if(DataStorage)
|
||||
if (!DataStorage->MarkPackageDirty())
|
||||
{
|
||||
DataStorage->MarkPackageDirty();
|
||||
UPackage* Package = DataStorage->GetPackage();
|
||||
if(Package->IsDirty())
|
||||
{
|
||||
FString PackageName = Package->GetName();
|
||||
FString FileExtension = FPackageName::GetAssetPackageExtension();
|
||||
FString FilePath = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir() / PackageName.Replace(TEXT("/"), TEXT("/")) + FileExtension);
|
||||
FString LongPackageName = DataStorage->GetOutermost()->GetName();
|
||||
FString RealAssetPath;
|
||||
bool bExists = FPackageName::DoesPackageExist(PackageName);
|
||||
if (!bExists)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Le package n'existe pas ou est un redirecteur"));
|
||||
}
|
||||
bool bSuccess = FPackageName::SearchForPackageOnDisk(LongPackageName, &RealAssetPath);
|
||||
|
||||
if (bSuccess && !RealAssetPath.IsEmpty())
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Vrai path trouvé : %s"), *RealAssetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Aucun path valide trouvé pour sauvegarder l'asset"));
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Saving DataAsset to %s"), *FilePath);
|
||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset = GetDefault<UDTFluxGeneralSettings>()->ModelAsset;
|
||||
FString RealPath = ModelAsset->GetPathName();
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("SoftObjectPath to %s"), *RealPath);
|
||||
|
||||
FSavePackageArgs Args;
|
||||
Args.TopLevelFlags = RF_Public | RF_Standalone;
|
||||
Args.bSlowTask = false;
|
||||
Args.SaveFlags = SAVE_None;
|
||||
GEditor->SavePackage(Package, DataStorage, *FilePath, Args);
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("DataAsset Saved"))
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to make package dirty !!!"))
|
||||
}
|
||||
if (DataStorage)
|
||||
{
|
||||
UEditorLoadingAndSavingUtils::SavePackages({DataStorage->GetPackage()}, true);
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::RegisterDelegates()
|
||||
{
|
||||
if(NetworkSubsystem)
|
||||
if (NetworkSubsystem)
|
||||
{
|
||||
NetworkSubsystem->OnReceivedRaceData().BindUFunction(this, "ProcessRaceData");
|
||||
NetworkSubsystem->OnReceivedTeamList().BindUFunction(this, "ProcessTeamList");
|
||||
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->OnReceivedSplitSensor().BindUFunction(this, "ProcessSplitSensor");
|
||||
// ✅ Binding avec vérification automatique des signatures
|
||||
// Si la signature ne correspond pas, erreur de compilation !
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
if( RaceDataDefinition.Datas.Num() > 0 )
|
||||
if (RaceDataDefinition.Datas.Num() > 0)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"), *RaceDataDefinition.Datas[0].Name);
|
||||
if(DataStorage != nullptr)
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Receiving RaceDataDefinition [%s]"),
|
||||
*RaceDataDefinition.Datas[0].Name);
|
||||
if (DataStorage != nullptr)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage Name %s"), *DataStorage->EventName);
|
||||
for(auto Contest : RaceDataDefinition.Datas)
|
||||
for (auto Contest : RaceDataDefinition.Datas)
|
||||
{
|
||||
DataStorage->AddContest(Contest);
|
||||
}
|
||||
@ -127,64 +126,80 @@ void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefini
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("RaceDataDefinition is empty !!!"));
|
||||
|
||||
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessTeamList(const FDTFluxTeamListDefinition& TeamListDefinition)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"), TeamListDefinition.Participants.Num());
|
||||
for(const auto& Participant : TeamListDefinition.Participants)
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received TeamList with %i Items"),
|
||||
TeamListDefinition.Participants.Num());
|
||||
for (const auto& Participant : TeamListDefinition.Participants)
|
||||
{
|
||||
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Add Participant %i in %i ContestId"),
|
||||
Participant.Bib, Participant.ContestId );
|
||||
Participant.Bib, Participant.ContestId);
|
||||
|
||||
DataStorage->AddParticipant(Participant, Participant.ContestId);
|
||||
}
|
||||
|
||||
|
||||
SaveDataStorage();
|
||||
}
|
||||
|
||||
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;
|
||||
NewContestRankings.SetName( DataStorage->GetContestNameForId(ContestRankings.ContestId));
|
||||
NewContestRankings.SetName(DataStorage->GetContestNameForId(ContestRankings.ContestId));
|
||||
DataStorage->AddContestRanking(NewContestRankings);
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"), *NewContestRankings.ContestName);
|
||||
DataStorage->MarkPackageDirty();
|
||||
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"),
|
||||
*NewContestRankings.ContestName);
|
||||
SaveDataStorage();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& StageRankings)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received StageRankings with %i Items"), StageRankings.Rankings.Num());
|
||||
DataStorage->UpdateOrCreateStageRanking(StageRankings);
|
||||
DataStorage->MarkPackageDirty();
|
||||
SaveDataStorage();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num());
|
||||
DataStorage->UpdateOrCreateSplitRanking(SplitRankings);
|
||||
DataStorage->MarkPackageDirty();
|
||||
|
||||
SaveDataStorage();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate()
|
||||
void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
|
||||
{
|
||||
//TODO IMPLEMENT ME !!!!
|
||||
DataStorage->UpdateParticipantStatus(NewParticipantStatus);
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessSplitSensor()
|
||||
void UDTFluxCoreSubsystem::ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinitiont)
|
||||
{
|
||||
//TODO IMPLEMENT ME !!!!
|
||||
for (const auto& Participant : TeamListDefinitiont.Participants)
|
||||
{
|
||||
DataStorage->UpdateParticipant(Participant);
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo)
|
||||
{
|
||||
FDTFluxContest Contest;
|
||||
FDTFluxStageKey StageKey(SplitSensorInfo.ContestId, SplitSensorInfo.StageId);
|
||||
FDTFluxStage Stage;
|
||||
DataStorage->GetStage(StageKey, Stage);
|
||||
FDTFluxParticipant Participant;
|
||||
DataStorage->GetParticipantByBib(SplitSensorInfo.Bib, Participant);
|
||||
|
||||
DataStorage->GetContestById(SplitSensorInfo.ContestId, Contest);
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("%s|%s Split %i Sensor for Participant [Bib] %i [FullName] %s"),
|
||||
*Contest.Name, *Stage.Name,
|
||||
SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName());
|
||||
}
|
||||
|
||||
|
||||
void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
|
||||
{
|
||||
if(NetworkSubsystem)
|
||||
if (NetworkSubsystem)
|
||||
{
|
||||
NetworkSubsystem->SendMessage(Message);
|
||||
}
|
||||
@ -220,7 +235,7 @@ void UDTFluxCoreSubsystem::SendStageRankingRequest(int InContestId, int InStageI
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::RequestAllStageRankingOfContest(int InContestId, int InStageId,
|
||||
bool bShouldIncludeSplitRanking)
|
||||
bool bShouldIncludeSplitRanking)
|
||||
{
|
||||
// TODO Implement this
|
||||
}
|
||||
@ -235,12 +250,119 @@ void UDTFluxCoreSubsystem::RequestAllSplitRankingOfContest(int InContestId, int
|
||||
// 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)
|
||||
{
|
||||
// TODO Implement this
|
||||
}
|
||||
|
||||
const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib)
|
||||
{
|
||||
if (DataStorage->Participants.Contains(InBib))
|
||||
{
|
||||
return DataStorage->Participants[InBib];
|
||||
}
|
||||
return FDTFluxParticipant();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::RefreshStorage()
|
||||
{
|
||||
// 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>();
|
||||
}
|
||||
|
||||
void UDTFluxCoreSubsystem::LaunchPursuitSequenceFor(const TArray<int> ContestIds)
|
||||
{
|
||||
TArray<FDTFluxContest> Contests = TArray<FDTFluxContest>();
|
||||
for (const auto& ContestId : ContestIds)
|
||||
{
|
||||
FDTFluxContest Contest;
|
||||
GetContestForId(ContestId, Contest);
|
||||
Contests.Add(Contest);
|
||||
if (PursuitManager)
|
||||
{
|
||||
PursuitManager->LaunchPursuitSequenceFor(Contests);
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("PursuitManager is null"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
152
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
152
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "DTFluxPursuitManager.h"
|
||||
|
||||
#include "DTFluxCoreSubsystemModule.h"
|
||||
|
||||
UDTFluxPursuitManager::UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer):
|
||||
Super(ObjectInitializer)
|
||||
{
|
||||
}
|
||||
|
||||
// TODO : Add way to pass MaxSimultaneousPursuit and MassStartDelay
|
||||
// For now it's done in UPROPERTIES
|
||||
void UDTFluxPursuitManager::LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests)
|
||||
{
|
||||
if (InitSubSystems())
|
||||
{
|
||||
for (const auto Contest : InContests)
|
||||
{
|
||||
FRequestData RequestData;
|
||||
RequestData.ContestId = Contest.ContestId;
|
||||
uint8 StageId = Contest.Stages.Last().StageId;
|
||||
FGuid Guid = NetworkSubsystem->SendTrackedRequestWithCallback(EDTFluxApiDataType::StageRanking,
|
||||
Contest.ContestId, StageId, -1,
|
||||
FOnDTFluxTrackedRequestResponse::CreateUObject(
|
||||
this,
|
||||
&UDTFluxPursuitManager::OnRequestResponse),
|
||||
FOnDTFluxTrackedRequestTimeout::CreateUObject(
|
||||
this,
|
||||
&UDTFluxPursuitManager::OnRequestTimeoutResponse),
|
||||
FOnDTFluxRequestResponseError::CreateUObject(
|
||||
this,
|
||||
&UDTFluxPursuitManager::OnRequestError));
|
||||
|
||||
RequestData.RequestIds.Add(Guid);
|
||||
PendingRequestData.Add(RequestData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxPursuitManager::OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log,
|
||||
TEXT("UDTFluxPursuitManager::OnRequestResponse() Received Ranking For Stage %i"), Response.StageID)
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Response is %s"), *UEnum::GetValueAsString(Response.GetResponseType()))
|
||||
//check if request
|
||||
if (Response.GetResponseType() == EDTFluxApiDataType::StageRanking)
|
||||
{
|
||||
FDTFluxStageRankings Rankings;
|
||||
FRequestData FoundData;
|
||||
if (Response.ParseStageRankingResponse(Rankings))
|
||||
{
|
||||
for (auto& PendingReq : PendingRequestData)
|
||||
{
|
||||
// Check for a matching PendingReq
|
||||
if (PendingReq.IsWaitingFor(RequestId, Rankings))
|
||||
{
|
||||
FoundData = PendingReq;
|
||||
// A request Is Terminated
|
||||
UE_LOG(logDTFluxCoreSubsystem, Log,
|
||||
TEXT("UDTFluxPursuitManager::OnRequestResponse() Ranking for Stage %i is complete"),
|
||||
Response.StageID)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (InitPursuit(FoundData))
|
||||
{
|
||||
OnPursuitSequenceReady.Broadcast(NextFocusPursuits, NextFocusPursuits, bFocusIsTruncate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UDTFluxPursuitManager::OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Request Timeout [%s]"), *TimeoutMessage);
|
||||
}
|
||||
|
||||
void UDTFluxPursuitManager::OnRequestError(const FGuid& RequestId, const FString& ErrorMessage)
|
||||
{
|
||||
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Request Error [%s]"), *ErrorMessage);
|
||||
}
|
||||
|
||||
bool UDTFluxPursuitManager::InitSubSystems()
|
||||
{
|
||||
if (NetworkSubsystem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||
return NetworkSubsystem != nullptr;
|
||||
}
|
||||
|
||||
bool UDTFluxPursuitManager::InitPursuit(FRequestData Data)
|
||||
{
|
||||
//Clean Data
|
||||
NextFocusPursuits.Empty();
|
||||
NextPursuits.Empty();
|
||||
PursuitGrouped.Empty();
|
||||
TArray<FDTFluxDetailedRankingItem> AllRankings;
|
||||
TArray<FDTFluxPursuitInfo> AllPursuits;
|
||||
TMap<FDateTime, FDTFluxPursuitGroup> TempGroups;
|
||||
|
||||
// Full the Array Of Rankings
|
||||
for (auto& KeyPair : Data.StageRankings)
|
||||
{
|
||||
for (auto StageRanking : KeyPair.Value.Rankings)
|
||||
{
|
||||
int ContestId = KeyPair.Value.ContestId;
|
||||
FDTFluxPursuitInfo PursuitInfo;
|
||||
PursuitInfo.StartTime = StageRanking.StartTime;
|
||||
PursuitInfo.Bib = StageRanking.Bib;
|
||||
PursuitInfo.ContestId = ContestId;
|
||||
AllPursuits.Add(PursuitInfo);
|
||||
}
|
||||
}
|
||||
// Sort Rankings
|
||||
// AllPursuits.Sort([](const FDTFluxPursuitInfo& A, const FDTFluxPursuitInfo& B) {
|
||||
// return A.StartTime < B.StartTime;
|
||||
// });
|
||||
|
||||
for (auto& Pursuit : AllPursuits)
|
||||
{
|
||||
if (TempGroups.Contains(Pursuit.StartTime))
|
||||
{
|
||||
TempGroups[Pursuit.StartTime].PursuitGroup.Add(Pursuit);
|
||||
}
|
||||
else
|
||||
{
|
||||
FDTFluxPursuitGroup Group;
|
||||
Group.StartTimeGlobal = Pursuit.StartTime;
|
||||
Group.PursuitGroup.Add(Pursuit);
|
||||
TempGroups.Add(Pursuit.StartTime, Group);
|
||||
}
|
||||
}
|
||||
TempGroups.KeySort([](const FDateTime& A, const FDateTime& B)
|
||||
{
|
||||
return A < B;
|
||||
});
|
||||
PursuitGrouped.Reserve(TempGroups.Num());
|
||||
for (const auto& Pair : TempGroups)
|
||||
{
|
||||
PursuitGrouped.Add(Pair.Value);
|
||||
}
|
||||
|
||||
PursuitGrouped.Sort([](const FDTFluxPursuitGroup& A, const FDTFluxPursuitGroup& B)
|
||||
{
|
||||
return A.StartTimeGlobal < B.StartTimeGlobal;
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@ -1,21 +1,19 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Containers/Deque.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "Types/Enum/DTfluxCoreEnum.h"
|
||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
#include "DTFluxCoreSubsystem.generated.h"
|
||||
|
||||
|
||||
|
||||
|
||||
class UDTFluxNetworkSubsystem;
|
||||
/** Forward Decl */
|
||||
class UDTFluxModelAsset;
|
||||
class UDTFluxPursuitManager;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -25,36 +23,40 @@ class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
|
||||
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")
|
||||
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")
|
||||
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")
|
||||
FOnContestRankings OnContestRankings;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamList, FDateTime, ReceivedAt, TArray<FDTFluxParticipant>, TeamList);
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||
FOnTeamList OnTeamList;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList);
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||
FOnTeamUpdate OnTeamUpdate;
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamStatusUpdate, FDTFluxParticipant, TeamUpdated);
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamStatusUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdated);
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||
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")
|
||||
void SendTeamListRequest();
|
||||
|
||||
@ -76,18 +78,50 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
void RequestAllSplitRankingOfContest(int InContestId, int InStageId);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
FDTFluxStageRankings GetStageRankings(FDTFluxStageKey StageKey);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
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")
|
||||
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();
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
void LaunchPursuitSequenceFor(const TArray<int> ContestIds);
|
||||
|
||||
protected:
|
||||
// ~Subsystem Interface
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
// ~Subsystem Interface
|
||||
|
||||
UPROPERTY()
|
||||
UDTFluxPursuitManager* PursuitManager = nullptr;
|
||||
|
||||
UFUNCTION()
|
||||
void SaveDataStorage();
|
||||
|
||||
private:
|
||||
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||
|
||||
@ -102,14 +136,17 @@ private:
|
||||
UFUNCTION()
|
||||
void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings);
|
||||
UFUNCTION()
|
||||
void ProcessTeamStatusUpdate();
|
||||
void ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
|
||||
UFUNCTION()
|
||||
void ProcessSplitSensor();
|
||||
void ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo);
|
||||
UFUNCTION()
|
||||
void ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinition);
|
||||
UFUNCTION()
|
||||
void SendRequest(const FString& Message);
|
||||
UFUNCTION()
|
||||
void RegisterDelegates();
|
||||
|
||||
|
||||
UPROPERTY()
|
||||
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);
|
||||
};
|
||||
133
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
133
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "DTFluxPursuitManager.generated.h"
|
||||
|
||||
|
||||
USTRUCT()
|
||||
struct FRequestData
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FGuid> RequestIds;
|
||||
UPROPERTY()
|
||||
TMap<FGuid, FDTFluxStageRankings> StageRankings;
|
||||
UPROPERTY()
|
||||
int ContestId;
|
||||
|
||||
UPROPERTY()
|
||||
bool bIsReady = false;
|
||||
|
||||
|
||||
FRequestData() = default;
|
||||
|
||||
FRequestData(const TArray<FGuid>& InRequestIds, const TMap<FGuid, FDTFluxStageRankings>& InStageRankings)
|
||||
: RequestIds(InRequestIds), StageRankings(InStageRankings)
|
||||
{
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param RequestId
|
||||
* @param InRankings
|
||||
* @return True if all needed requests have responses
|
||||
*/
|
||||
bool IsWaitingFor(const FGuid& RequestId, const FDTFluxStageRankings& InRankings)
|
||||
{
|
||||
if (!StageRankings.Contains(RequestId))
|
||||
{
|
||||
StageRankings.Add(RequestId, InRankings);
|
||||
}
|
||||
bIsReady = StageRankings.Num() <= RequestIds.Num();
|
||||
return bIsReady;
|
||||
}
|
||||
};
|
||||
|
||||
USTRUCT()
|
||||
struct FDTFluxPursuitGroup
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
|
||||
UPROPERTY()
|
||||
FDateTime StartTimeGlobal = FDateTime::MinValue();
|
||||
UPROPERTY()
|
||||
bool bHasStarted = false;
|
||||
UPROPERTY()
|
||||
bool bIsFocus = false;
|
||||
};
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnPursuitSequenceReady, const TArray<FDTFluxPursuitInfo>,
|
||||
NextFocusPursuits,
|
||||
const TArray<FDTFluxPursuitInfo>, NextPursuit, bool, bIsTrtuncate);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
UCLASS(BlueprintType)
|
||||
class DTFLUXCORESUBSYSTEM_API UDTFluxPursuitManager : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||
TArray<FDTFluxPursuitInfo> NextFocusPursuits;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||
TArray<FDTFluxPursuitInfo> NextPursuits;
|
||||
|
||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||
bool bFocusIsTruncate = false;
|
||||
//
|
||||
// UPROPERTY()
|
||||
// TArray<FDTFluxStage> TargetStages;
|
||||
|
||||
UPROPERTY()
|
||||
int MaxSimultaneousPursuit = 7;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit",
|
||||
meta=(ClampMin="1", ClampMax="60", UIMin="0", UIMax="60"))
|
||||
int MassStartDelay = 10;
|
||||
|
||||
UPROPERTY()
|
||||
TArray<FDTFluxPursuitGroup> PursuitGrouped;
|
||||
|
||||
UPROPERTY()
|
||||
int CurrentIndex = -1;
|
||||
|
||||
UPROPERTY(BlueprintCallable, Category="DTFlux|Pursuit")
|
||||
FOnPursuitSequenceReady OnPursuitSequenceReady;
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
|
||||
void LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage);
|
||||
|
||||
UFUNCTION()
|
||||
void OnRequestError(const FGuid& RequestId, const FString& ErrorMessage);
|
||||
|
||||
UFUNCTION()
|
||||
bool InitSubSystems();
|
||||
|
||||
private:
|
||||
TArray<FRequestData> PendingRequestData;
|
||||
|
||||
public:
|
||||
UFUNCTION()
|
||||
bool InitPursuit(FRequestData Data);
|
||||
|
||||
private:
|
||||
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||
};
|
||||
@ -2,31 +2,31 @@
|
||||
|
||||
public class DTFluxNetwork : ModuleRules
|
||||
{
|
||||
public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxNetwork(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"WebSockets",
|
||||
"HTTP",
|
||||
"DTFluxCore",
|
||||
"DTFluxProjectSettings",
|
||||
"JsonUtilities",
|
||||
"Json",
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"WebSockets",
|
||||
"HTTP",
|
||||
"DTFluxCore",
|
||||
"DTFluxProjectSettings",
|
||||
"JsonUtilities",
|
||||
"Json",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
439
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
439
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
@ -0,0 +1,439 @@
|
||||
// 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::MarkRequestAsError(const FGuid& TargetRequestGuid)
|
||||
{
|
||||
// TODO: Implement a retry mechanism
|
||||
// For now we simply suppress the request and log a message
|
||||
bool bFoundMatch = false;
|
||||
FDTFluxQueuedRequest Request;
|
||||
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
|
||||
while (PendingRequestsQueue.Dequeue(Request))
|
||||
{
|
||||
if (Request.RequestId == TargetRequestGuid)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error,
|
||||
TEXT("Marked request %s as error: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||
Request.SplitId);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempQueue.Enqueue(Request);
|
||||
}
|
||||
}
|
||||
while (TempQueue.Dequeue(Request))
|
||||
{
|
||||
PendingRequestsQueue.Enqueue(Request);
|
||||
}
|
||||
if (bFoundMatch)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("No Request Found with GUID %s"), *TargetRequestGuid.ToString());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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(FGuid& OutRequestId, 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;
|
||||
OutRequestId = Request.RequestId;
|
||||
UE_LOG(logDTFluxNetwork, Verbose,
|
||||
TEXT("Found pending request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
|
||||
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
|
||||
Request.SplitId);
|
||||
}
|
||||
|
||||
// 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,531 @@
|
||||
#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)
|
||||
{
|
||||
// UE_LOG(logDTFluxNetwork, Log, TEXT("Response is stage-ranking type %s"), *RawMessage);
|
||||
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;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Reponse Update"));
|
||||
|
||||
OutStageRankings.ContestId = ContestID;
|
||||
OutStageRankings.StageId = StageID;
|
||||
OutStageRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
|
||||
OutStageRankings.Initialize();
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed stage ranking for Contest %d, Stage %d with %d entries"),
|
||||
OutStageRankings.ContestId, OutStageRankings.StageId, OutStageRankings.Rankings.Num());
|
||||
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings)
|
||||
{
|
||||
if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error,
|
||||
TEXT("Response is not a split-ranking type (stage-ranking with SplitID != -1)"));
|
||||
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||
return false;
|
||||
}
|
||||
|
||||
FDTFluxSplitRankingResponse SplitRankingResponse;
|
||||
if (!FJsonObjectConverter::JsonObjectStringToUStruct<
|
||||
FDTFluxSplitRankingResponse>(RawMessage, &SplitRankingResponse))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-ranking JSON: %s"), *RawMessage);
|
||||
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||
return false;
|
||||
}
|
||||
|
||||
OutSplitRankings.ContestId = ContestID;
|
||||
OutSplitRankings.StageId = StageID;
|
||||
OutSplitRankings.SplitId = SplitID;
|
||||
OutSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
|
||||
UE_LOG(logDTFluxNetwork, Log,
|
||||
TEXT("Successfully parsed split ranking for Contest %d, Stage %d, Split %d with %d entries"),
|
||||
OutSplitRankings.ContestId, OutSplitRankings.StageId, OutSplitRankings.SplitId,
|
||||
OutSplitRankings.Rankings.Num());
|
||||
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate)
|
||||
{
|
||||
if (!ValidateResponseType(TEXT("status-update")))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a status-update type"));
|
||||
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(RawMessage, &OutStatusUpdate))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse status-update JSON: %s"), *RawMessage);
|
||||
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||
return false;
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed status update for bib %d"), OutStatusUpdate.Bib);
|
||||
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos)
|
||||
{
|
||||
if (!ValidateResponseType(TEXT("split-sensor")))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Response is not a split-sensor type"));
|
||||
ParsingStatus = EDTFluxResponseStatus::InvalidType;
|
||||
return false;
|
||||
}
|
||||
|
||||
FDTFluxSplitSensorResponse SplitSensorResponse;
|
||||
if (!FJsonObjectConverter::JsonObjectStringToUStruct(RawMessage, &SplitSensorResponse))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse split-sensor JSON: %s"), *RawMessage);
|
||||
ParsingStatus = EDTFluxResponseStatus::JsonParseError;
|
||||
return false;
|
||||
}
|
||||
|
||||
OutSplitSensorInfos.Empty();
|
||||
for (const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas)
|
||||
{
|
||||
FDTFluxSplitSensorInfo NewSplitSensorInfo;
|
||||
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib;
|
||||
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID;
|
||||
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
|
||||
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
|
||||
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
|
||||
|
||||
OutSplitSensorInfos.Add(NewSplitSensorInfo);
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d"),
|
||||
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
|
||||
NewSplitSensorInfo.SplitId);
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Successfully parsed %d split sensor entries"), OutSplitSensorInfos.Num());
|
||||
ParsingStatus = EDTFluxResponseStatus::Success;
|
||||
return true;
|
||||
}
|
||||
|
||||
void FDTFluxServerResponse::ShowDebug(const bool bShouldPrintRawMessage) const
|
||||
{
|
||||
FString DebugMsg = FString::Printf(
|
||||
TEXT("DTFluxServerResponse[Type=%s, Code=%d, Contest=%d, Stage=%d, Split=%d, ReceivedAt=%s]"),
|
||||
*Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
|
||||
if (bShouldPrintRawMessage)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("%s\nRawMessage: \"%s\""), *DebugMsg, *RawMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// === MÉTHODES PRIVÉES ===
|
||||
|
||||
bool FDTFluxServerResponse::ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const
|
||||
{
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(RawMessage);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, OutJsonObject) || !OutJsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to parse JSON: %s"), *RawMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FDTFluxServerResponse::ValidateResponseType(const FString& ExpectedType) const
|
||||
{
|
||||
if (!IsValidResponse())
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Invalid server response: Code=%d, Message=%s"), Code, *Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ContainsDataType(ExpectedType))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Expected type '%s' but got '%s'"), *ExpectedType, *Type);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -5,6 +5,8 @@
|
||||
#include "DTFluxCoreModule.h"
|
||||
#include "DTFluxNetworkModule.h"
|
||||
#include "DTFluxNetworkSettings.h"
|
||||
#include "DTFluxQueuedManager.h"
|
||||
#include "DTFluxQueuedManager.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Clients/DTFluxHttpClient.h"
|
||||
#include "Clients/DTFluxWebSocketClient.h"
|
||||
@ -19,10 +21,11 @@
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
|
||||
|
||||
// === CONNEXION WEBSOCKET ===
|
||||
void UDTFluxNetworkSubsystem::Connect()
|
||||
{
|
||||
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
||||
WsClient->Connect();
|
||||
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
||||
WsClient->Connect();
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::Disconnect()
|
||||
@ -35,6 +38,148 @@ void UDTFluxNetworkSubsystem::Reconnect()
|
||||
ReconnectWs(FName("Ws_Client_0"));
|
||||
}
|
||||
|
||||
// === REQUÊTES AVEC TRACKING ===
|
||||
|
||||
FGuid UDTFluxNetworkSubsystem::SendTrackedRequest(
|
||||
EDTFluxApiDataType RequestType,
|
||||
int32 ContestId,
|
||||
int32 StageId,
|
||||
int32 SplitId,
|
||||
float TimeoutSeconds)
|
||||
{
|
||||
if (!QueueManager)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("QueueManager is not initialized"));
|
||||
return FGuid();
|
||||
}
|
||||
|
||||
// Vérifier si une requête similaire est déjà en cours (optionnel)
|
||||
if (IsRequestPending(RequestType, ContestId, StageId, SplitId))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning,
|
||||
TEXT("Similar request already pending: Type=%d, Contest=%d, Stage=%d, Split=%d"),
|
||||
(int32)RequestType, ContestId, StageId, SplitId);
|
||||
}
|
||||
|
||||
// Créer et enqueue la requête
|
||||
FGuid RequestId = QueueManager->QueueRequest(RequestType, ContestId, StageId, SplitId);
|
||||
|
||||
// Envoyer immédiatement si possible (le QueueManager gère la queue)
|
||||
if (const FDTFluxQueuedRequest* QueuedRequest = QueueManager->GetRequest(RequestId))
|
||||
{
|
||||
SendQueuedRequest(*QueuedRequest);
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Queued tracked request %s: Type=%d, Contest=%d, Stage=%d, Split=%d"),
|
||||
*RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId);
|
||||
|
||||
return RequestId;
|
||||
}
|
||||
|
||||
FGuid UDTFluxNetworkSubsystem::SendTrackedRequestWithCallback(
|
||||
EDTFluxApiDataType RequestType,
|
||||
int32 ContestId,
|
||||
int32 StageId,
|
||||
int32 SplitId,
|
||||
FOnDTFluxTrackedRequestResponse OnCompleted,
|
||||
FOnDTFluxTrackedRequestTimeout OnTimeout,
|
||||
TOptional<FOnDTFluxRequestResponseError> OnError,
|
||||
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);
|
||||
}
|
||||
if (OnError.IsSet() && OnError.GetValue().IsBound())
|
||||
{
|
||||
PendingErrorCallbacks.Add(RequestId, OnError.GetValue());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
FGuid OutRequestId;
|
||||
return QueueManager->IsRequestPending(OutRequestId, RequestType, ContestId, StageId, SplitId);
|
||||
}
|
||||
|
||||
int32 UDTFluxNetworkSubsystem::GetPendingRequestCount() const
|
||||
{
|
||||
if (!QueueManager)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return QueueManager->GetPendingRequestCount();
|
||||
}
|
||||
|
||||
UDTFluxQueuedManager* UDTFluxNetworkSubsystem::GetQueueManager() const
|
||||
{
|
||||
return QueueManager;
|
||||
}
|
||||
|
||||
|
||||
void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType, int InContestId, int InStageId,
|
||||
int InSplitId)
|
||||
{
|
||||
@ -48,7 +193,8 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
|
||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxStageRankingRequest(InContestId, InStageId), Message);
|
||||
break;
|
||||
case EDTFluxRequestType::SplitRanking:
|
||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId), Message);
|
||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxSplitRankingRequest(InContestId, InStageId, InSplitId),
|
||||
Message);
|
||||
break;
|
||||
case EDTFluxRequestType::TeamList:
|
||||
FJsonObjectConverter::UStructToJsonObjectString(FDTFluxTeamListRequest(), Message);
|
||||
@ -60,7 +206,7 @@ void UDTFluxNetworkSubsystem::SendRequest(const EDTFluxRequestType RequestType,
|
||||
return;
|
||||
}
|
||||
//Dirty trick to fix Case
|
||||
Message = Message.Replace(TEXT("Id"),TEXT( "ID"), ESearchCase::CaseSensitive);
|
||||
Message = Message.Replace(TEXT("Id"),TEXT("ID"), ESearchCase::CaseSensitive);
|
||||
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Request %s"), *Message);
|
||||
SendMessage(Message);
|
||||
}
|
||||
@ -70,11 +216,10 @@ void UDTFluxNetworkSubsystem::SendMessage(const FString& Message)
|
||||
UE_LOG(logDTFluxCore, Warning, TEXT("Sending Message %s"), *Message);
|
||||
|
||||
|
||||
if(WsClient.IsValid() && WsClient->CanSend())
|
||||
if (WsClient.IsValid() && WsClient->CanSend())
|
||||
{
|
||||
WsClient->Send(Message);
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Can send request"));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -86,8 +231,7 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
FDTFluxCoreModule& DTFluxCore = FModuleManager::Get().LoadModuleChecked<FDTFluxCoreModule>("DTFluxCore");
|
||||
FString StatusString = UEnum::GetValueAsString(WsStatus);
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Status is %s"), *StatusString);
|
||||
|
||||
UDTFluxNetworkSettings* NetworkSettings = GetMutableDefault<UDTFluxNetworkSettings>();
|
||||
UDTFluxNetworkSettings::GetWebSocketSettings(NetworkSettings, WsSettings);
|
||||
UDTFluxNetworkSettings::GetHTTPSettings(NetworkSettings, HttpSettings);
|
||||
@ -99,16 +243,36 @@ void UDTFluxNetworkSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||
NetworkSettings->OnDTFluxWebSocketSettingsChanged.AddUFunction(this, FName("WsSettingsChanged"));
|
||||
NetworkSettings->OnDTFluxHttpSettingsChanged.AddUFunction(this, FName("HttpSettingsChanged"));
|
||||
#endif
|
||||
if(WsSettings.bShouldConnectAtStartup)
|
||||
if (WsSettings.bShouldConnectAtStartup)
|
||||
{
|
||||
WsClient->SetAddress(ConstructWsAddress(WsSettings.Address, WsSettings.Path, WsSettings.Port));
|
||||
WsClient->Connect();
|
||||
}
|
||||
|
||||
// Initialisation du Queue Manager
|
||||
QueueManager = NewObject<UDTFluxQueuedManager>(this);
|
||||
QueueManager->Initialize();
|
||||
|
||||
// Connexion au delegate de timeout du Queue Manager
|
||||
QueueManager->OnRequestTimedOut.AddDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::Deinitialize()
|
||||
{
|
||||
Super::Deinitialize();
|
||||
// Nettoyer le Queue Manager
|
||||
if (QueueManager)
|
||||
{
|
||||
QueueManager->OnRequestTimedOut.RemoveDynamic(this, &UDTFluxNetworkSubsystem::OnRequestTimedOut_Internal);
|
||||
QueueManager->ClearAllRequests();
|
||||
}
|
||||
|
||||
// Nettoyer les callbacks
|
||||
PendingCallbacks.Empty();
|
||||
PendingTimeoutCallbacks.Empty();
|
||||
// Déconnexion des clients
|
||||
UnregisterWebSocketEvents();
|
||||
UnregisterHttpEvents();
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings)
|
||||
@ -117,7 +281,7 @@ void UDTFluxNetworkSubsystem::WsSettingsChanged(const FDTFluxWsSettings& NewWsSe
|
||||
bool bNeedsReload = WsSettings != NewWsSettings;
|
||||
|
||||
WsSettings = NewWsSettings;
|
||||
if( bNeedsReload || WsSettings.bShouldConnectAtStartup)
|
||||
if (bNeedsReload || WsSettings.bShouldConnectAtStartup)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("WSocket Settings needs Reloding client"))
|
||||
ReconnectWs(FName("Ws_Client_0"));
|
||||
@ -137,9 +301,9 @@ void UDTFluxNetworkSubsystem::ReconnectWs(const FName WsClientId)
|
||||
WsClient->SetAddress(NewAddress);
|
||||
WsClient->Reconnect();
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ReconnectHttp(const FName WsClientId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
|
||||
@ -148,16 +312,16 @@ void UDTFluxNetworkSubsystem::RegisterWebSocketEvents()
|
||||
WsClient->RegisterConnectedEvent().AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnected_Subsystem);
|
||||
OnWsConnectionErrorEventDelegateHandle =
|
||||
WsClient->RegisterConnectionError()
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem);
|
||||
OnWsClosedEventDelegateHandle =
|
||||
WsClient->RegisterClosedEvent()
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem);
|
||||
OnWsMessageEventDelegateHandle =
|
||||
WsClient->RegisterMessageEvent()
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem);
|
||||
OnWsMessageSentEventDelegateHandle =
|
||||
WsClient->RegisterMessageSentEvent()
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
|
||||
WsClient->RegisterMessageSentEvent()
|
||||
.AddUObject(this, &UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem);
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::RegisterHttpEvents()
|
||||
@ -166,25 +330,25 @@ void UDTFluxNetworkSubsystem::RegisterHttpEvents()
|
||||
|
||||
void UDTFluxNetworkSubsystem::UnregisterWebSocketEvents()
|
||||
{
|
||||
if(OnWsConnectedEventDelegateHandle.IsValid())
|
||||
if (OnWsConnectedEventDelegateHandle.IsValid())
|
||||
{
|
||||
WsClient->UnregisterConnectedEvent().Remove(OnWsConnectedEventDelegateHandle);
|
||||
}
|
||||
if(OnWsConnectionErrorEventDelegateHandle.IsValid())
|
||||
if (OnWsConnectionErrorEventDelegateHandle.IsValid())
|
||||
{
|
||||
WsClient->UnregisterConnectionError();
|
||||
WsClient->UnregisterConnectionError().Remove(OnWsConnectionErrorEventDelegateHandle);
|
||||
}
|
||||
if(OnWsClosedEventDelegateHandle.IsValid())
|
||||
if (OnWsClosedEventDelegateHandle.IsValid())
|
||||
{
|
||||
WsClient->UnregisterClosedEvent();
|
||||
WsClient->UnregisterClosedEvent().Remove(OnWsClosedEventDelegateHandle);
|
||||
}
|
||||
if(OnWsMessageEventDelegateHandle.IsValid())
|
||||
if (OnWsMessageEventDelegateHandle.IsValid())
|
||||
{
|
||||
WsClient->UnregisterMessageEvent();
|
||||
WsClient->UnregisterMessageEvent().Remove(OnWsMessageEventDelegateHandle);
|
||||
}
|
||||
if(OnWsMessageSentEventDelegateHandle.IsValid())
|
||||
if (OnWsMessageSentEventDelegateHandle.IsValid())
|
||||
{
|
||||
WsClient->UnregisterRawMessageEvent();
|
||||
WsClient->UnregisterRawMessageEvent().Remove(OnWsMessageSentEventDelegateHandle);
|
||||
}
|
||||
}
|
||||
|
||||
@ -203,7 +367,7 @@ void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s : %s"), *WsClient->GetAddress(), *Error);
|
||||
WsStatus = EDTFluxConnectionStatus::Error;
|
||||
if(WsSettings.bShouldAutoReconnectOnError)
|
||||
if (WsSettings.bShouldAutoReconnectOnError)
|
||||
{
|
||||
WsClient->Reconnect();
|
||||
}
|
||||
@ -212,258 +376,240 @@ void UDTFluxNetworkSubsystem::OnWebSocketConnectionError_Subsystem(const FString
|
||||
void UDTFluxNetworkSubsystem::OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws Error with %s :\n Reason : %s \tStatusCode : %i, bWasClean : %s"),
|
||||
*WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False"));
|
||||
*WsClient->GetAddress(), *Reason, StatusCode, bWasClean ? TEXT("True") : TEXT("False"));
|
||||
WsStatus = EDTFluxConnectionStatus::Closed;
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse)
|
||||
void UDTFluxNetworkSubsystem::ParseTeamListResponse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
TSharedPtr<FJsonObject> JsonObject;
|
||||
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ServerResponse.RawMessage);
|
||||
|
||||
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("JSON invalide : %s"), *ServerResponse.RawMessage);
|
||||
return;
|
||||
}
|
||||
const TArray<TSharedPtr<FJsonValue>>* DataArray;
|
||||
if (!JsonObject->TryGetArrayField(TEXT("datas"), DataArray))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Aucun champ 'datas' trouvé dans le team-list"));
|
||||
return;
|
||||
}
|
||||
FDTFluxTeamListDefinition TeamListDefinition;
|
||||
for (const TSharedPtr<FJsonValue>& Value : *DataArray)
|
||||
Response.ParseTeamListResponse(TeamListDefinition);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Parsing Team List Response"));
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
if (Value->Type == EJson::Object)
|
||||
{
|
||||
const TSharedPtr<FJsonObject> Item = Value->AsObject();
|
||||
|
||||
FDTFluxParticipant Participant;
|
||||
UDTFluxParticipantFactory::CreateFromJsonCpp(Item, Participant);
|
||||
TeamListDefinition.Participants.Add(Participant);
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseTeamListResponse() for JSON Response : %s"), *Response.RawMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("PArsing OK. Sending to Core..."));
|
||||
const bool bIsSuccessfullyBounded = OnTeamListReceived.ExecuteIfBound(TeamListDefinition);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Inserting %i Participants [%s]"), TeamListDefinition.Participants.Num(),
|
||||
OnTeamListReceived.ExecuteIfBound(TeamListDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseRaceData(const FDTFluxServerResponse& Response)
|
||||
void UDTFluxNetworkSubsystem::ParseRaceData(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxRaceDataResponse RaceData;
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxRaceDataResponse>(Response.RawMessage, &RaceData))
|
||||
FDTFluxRaceData RaceData;
|
||||
Response.ParseRaceData(RaceData);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
//convert
|
||||
FDTFluxRaceData RaceDataDefinition;
|
||||
for(auto Contest : RaceData.Datas)
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
|
||||
return;
|
||||
}
|
||||
const bool bIsSuccessfullyBounded = OnRaceDataReceived.ExecuteIfBound(RaceData);
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceData.Datas.Num(),
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseContestRanking(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxContestRankings ContestRankings;
|
||||
Response.ParseContestRanking(ContestRankings);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage);
|
||||
return;
|
||||
}
|
||||
const bool bIsSuccessfullyBounded = OnContestRankingReceived.ExecuteIfBound(ContestRankings);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"),
|
||||
ContestRankings.ContestId,
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseStageRankingResponse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxStageRankings StageRankings;
|
||||
Response.ParseStageRankingResponse(StageRankings);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"),
|
||||
*Response.RawMessage);
|
||||
}
|
||||
const bool bIsSuccessfullyBounded = OnStageRankingReceived.ExecuteIfBound(StageRankings);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"),
|
||||
StageRankings.ContestId, StageRankings.StageId,
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxSplitRankings SplitRankings;
|
||||
Response.ParseSplitRankingResponse(SplitRankings);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"),
|
||||
*Response.RawMessage);
|
||||
}
|
||||
const bool bIsSuccessfullyBounded = OnSplitRankingReceived.ExecuteIfBound(SplitRankings);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i, Split %i\n[Result] : %s"),
|
||||
SplitRankings.ContestId, SplitRankings.StageId, SplitRankings.SplitId,
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxTeamStatusUpdate StatusUpdate;
|
||||
Response.ParseStatusUpdateResponse(StatusUpdate);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"),
|
||||
*Response.RawMessage);
|
||||
}
|
||||
const bool bIsSuccessfullyBounded = OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("StatusUpdate Data Sent for Bib %i with new status %s\n[Result] : %s"),
|
||||
StatusUpdate.Bib, *UEnum::GetValueAsString(StatusUpdate.Status),
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos = TArray<FDTFluxSplitSensorInfo>();
|
||||
Response.ParseSplitSensorResponse(SplitSensorInfos);
|
||||
if (Response.GetParsingStatus() != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() for JSON Response : %s"),
|
||||
*Response.RawMessage);
|
||||
}
|
||||
for (auto& SplitSensorInfo : SplitSensorInfos)
|
||||
{
|
||||
const bool bIsSuccessfullyBounded = OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
|
||||
UE_LOG(logDTFluxNetwork, Warning,
|
||||
TEXT("SplitSensor Data Sent for Bib %i on [Split %i] of [Stage %i] in [Contest %i]\n[Result] : %s"),
|
||||
SplitSensorInfo.Bib, SplitSensorInfo.SplitId, SplitSensorInfo.StageId, SplitSensorInfo.ContestId,
|
||||
bIsSuccessfullyBounded ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
}
|
||||
}
|
||||
|
||||
EDTFluxResponseStatus UDTFluxNetworkSubsystem::ProcessPushMessage(FDTFluxServerResponse& Response)
|
||||
{
|
||||
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||
if (DTFluxDataTypeUtils::IsPushOnly(Response.GetResponseType()))
|
||||
{
|
||||
switch (Response.GetResponseType())
|
||||
{
|
||||
FDTFluxContest NewContest;
|
||||
NewContest.Name = Contest.Name;
|
||||
NewContest.ContestId = Contest.Id;
|
||||
NewContest.Date = Contest.Date;
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s] Starting at %s \nStages: \n"), Contest.Id, *Contest.Date.ToString(),*Contest.Name);
|
||||
for(auto Stage : Contest.Stages)
|
||||
case EDTFluxApiDataType::SplitSensor:
|
||||
{
|
||||
FDTFluxStage NewStage;
|
||||
NewStage.StageId = Stage.Id;
|
||||
NewStage.Name = Stage.Name;
|
||||
FString StartTimeFString = FString::Printf(TEXT("%s %s"),
|
||||
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||
*Stage.StartTime
|
||||
);
|
||||
FString EndTimeFString = FString::Printf(TEXT("%s %s"),
|
||||
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||
*Stage.EndTime
|
||||
);
|
||||
FString CutOffFString = FString::Printf(TEXT("%s %s"),
|
||||
*NewContest.Date.ToFormattedString(TEXT("%Y-%m-%d")),
|
||||
*Stage.CutOff
|
||||
);
|
||||
FDateTime::Parse(StartTimeFString, NewStage.StartTime);
|
||||
FDateTime::Parse(EndTimeFString, NewStage.EndTime);
|
||||
FDateTime::Parse(CutOffFString, NewStage.CutOff);
|
||||
NewContest.Stages.Add(NewStage);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage %i [%s]: \nSTartTime Received [%s] -> Datetime[%s], CutOff [%s], EndTime [%s] \n"), Stage.Id, *Stage.Name,
|
||||
*Stage.StartTime, *NewStage.StartTime.ToString(), *NewStage.CutOff.ToString(), *NewStage.EndTime.ToString());
|
||||
TArray<FDTFluxSplitSensorInfo> SplitSensorInfos;
|
||||
if (Response.ParseSplitSensorResponse(SplitSensorInfos))
|
||||
{
|
||||
for (const auto& SplitSensorInfo : SplitSensorInfos)
|
||||
{
|
||||
OnSplitSensorReceived.ExecuteIfBound(SplitSensorInfo);
|
||||
}
|
||||
}
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest %i [%s]\nSplits: \n"), Contest.Id, *Contest.Name);
|
||||
for(auto Split: Contest.Splits)
|
||||
case EDTFluxApiDataType::StatusUpdate:
|
||||
{
|
||||
FDTFluxSplit NewSplit;
|
||||
NewSplit.SplitId = Split.Id;
|
||||
NewSplit.Name = Split.Name;
|
||||
NewContest.Splits.Add(NewSplit);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Split %i [%s]: \n"), Split.Id, *Split.Name);
|
||||
FDTFluxTeamStatusUpdate StatusUpdate;
|
||||
if (Response.ParseStatusUpdateResponse(StatusUpdate))
|
||||
{
|
||||
OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdate);
|
||||
}
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
case EDTFluxApiDataType::TeamUpdate:
|
||||
{
|
||||
FDTFluxTeamListDefinition TeamUpdateList;
|
||||
if (Response.ParseTeamUpdateResponse(TeamUpdateList))
|
||||
{
|
||||
OnTeamUpdateReceived.ExecuteIfBound(TeamUpdateList);
|
||||
}
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||
break;
|
||||
}
|
||||
RaceDataDefinition.Datas.Add(NewContest);
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sending %i Contests, [%s]"), RaceDataDefinition.Datas.Num(),
|
||||
OnRaceDataReceived.ExecuteIfBound(RaceDataDefinition) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseRaceData() for JSON Response : %s"), *Response.RawMessage);
|
||||
return ResponseStatus;
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseContestRanking(const FDTFluxServerResponse& Response)
|
||||
void UDTFluxNetworkSubsystem::Parse(FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxContestRankingResponse ContestRankingResponse;
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxContestRankingResponse>(Response.RawMessage, &ContestRankingResponse))
|
||||
EDTFluxResponseStatus ResponseStatus = EDTFluxResponseStatus::Success;
|
||||
switch (Response.GetResponseType())
|
||||
{
|
||||
FDTFluxContestRankings ContestRankings;
|
||||
ContestRankings.ContestId = ContestRankingResponse.ContestID;
|
||||
for(auto& RankingItem : ContestRankingResponse.Datas)
|
||||
case EDTFluxApiDataType::RaceData:
|
||||
{
|
||||
FDTFluxContestRanking Temp = RankingItem;
|
||||
ContestRankings.Rankings.Add(Temp);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing RaceData"));
|
||||
ParseRaceData(Response);
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws ContestRanking Data Sent for Contest %i, [%s]"), ContestRankings.ContestId,
|
||||
OnContestRankingReceived.ExecuteIfBound(ContestRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseContestRanking() for JSON Response : %s"), *Response.RawMessage);
|
||||
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseStageRankingResponse(const FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxStageRankingResponse RankingResponse;
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxStageRankingResponse>(Response.RawMessage, &RankingResponse))
|
||||
{
|
||||
FDTFluxStageRankings NewRankings;
|
||||
NewRankings.ContestId = Response.ContestID;
|
||||
NewRankings.StageId = Response.StageID;
|
||||
NewRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(RankingResponse.Datas);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("StageRanking Data Sent for Contest %i, Stage %i\n[Result] : %s"),
|
||||
NewRankings.ContestId, NewRankings.StageId,
|
||||
OnStageRankingReceived.ExecuteIfBound(NewRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED")
|
||||
);
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStageRankingResponse() for JSON Response : %s"), *Response.RawMessage);
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseSplitRankingResponse(const FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxSplitRankingResponse SplitRankingResponse;
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxSplitRankingResponse>(Response.RawMessage, &SplitRankingResponse))
|
||||
{
|
||||
FDTFluxSplitRankings NewSplitRankings;
|
||||
NewSplitRankings.ContestId = Response.ContestID;
|
||||
NewSplitRankings.StageId = Response.StageID;
|
||||
NewSplitRankings.SplitId = Response.SplitID;
|
||||
NewSplitRankings.Rankings = static_cast<TArray<FDTFluxDetailedRankingItem>>(SplitRankingResponse.Datas);
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("SplitRanking Data Sent for Contest %i, Stage %i and Split %i\n[Result] : %s"),
|
||||
NewSplitRankings.ContestId, NewSplitRankings.StageId, NewSplitRankings.SplitId,
|
||||
OnSplitRankingReceived.ExecuteIfBound(NewSplitRankings) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitRankingResponse() for JSON Response : %s"), *Response.RawMessage);
|
||||
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseStatusUpdateResponse(const FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxTeamStatusUpdate StatusUpdateResponse;
|
||||
|
||||
if (FJsonObjectConverter::JsonObjectStringToUStruct<FDTFluxTeamStatusUpdate>(Response.RawMessage, &StatusUpdateResponse))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i \n[Result] : %s\n"),
|
||||
StatusUpdateResponse.Bib,
|
||||
OnTeamStatusUpdateReceived.ExecuteIfBound(StatusUpdateResponse) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
return;
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseStatusUpdateResponse() for JSON Response : %s"), *Response.RawMessage);
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::ParseSplitSensorResponse(const FDTFluxServerResponse& Response)
|
||||
{
|
||||
FDTFluxSplitSensorResponse SplitSensorResponse;
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct(Response.RawMessage, &SplitSensorResponse))
|
||||
{
|
||||
for(const auto& SplitSensorInfoResponse : SplitSensorResponse.Datas)
|
||||
case EDTFluxApiDataType::TeamList:
|
||||
{
|
||||
FDTFluxSplitSensorInfo NewSplitSensorInfo;
|
||||
NewSplitSensorInfo.Bib = SplitSensorInfoResponse.Bib;
|
||||
NewSplitSensorInfo.ContestId = SplitSensorInfoResponse.ContestID;
|
||||
NewSplitSensorInfo.StageId = SplitSensorInfoResponse.StageID;
|
||||
NewSplitSensorInfo.SplitId = SplitSensorInfoResponse.SplitID;
|
||||
NewSplitSensorInfo.Time = SplitSensorInfoResponse.Time;
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Status Update for bib %i in Contest %i, Stage %i in split %i\n[Result] : %s\n"),
|
||||
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId, NewSplitSensorInfo.SplitId,
|
||||
OnSplitSensorReceived.ExecuteIfBound(NewSplitSensorInfo) ? TEXT("SUCCESS_SEND") : TEXT("NOT_BOUNDED"));
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Legacy Parsing TeamList"));
|
||||
ParseTeamListResponse(Response);
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
case EDTFluxApiDataType::ContestRanking:
|
||||
{
|
||||
ParseContestRanking(Response);
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
case EDTFluxApiDataType::StageRanking:
|
||||
{
|
||||
ParseStageRankingResponse(Response);
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
case EDTFluxApiDataType::SplitRanking:
|
||||
{
|
||||
ParseSplitRankingResponse(Response);
|
||||
ResponseStatus = Response.GetParsingStatus();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Legacy Parsing Unknown"));
|
||||
ResponseStatus = EDTFluxResponseStatus::UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("ParseSplitSensorResponse() failed for JSON Response : %s"), *Response.RawMessage);
|
||||
if (ResponseStatus != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("UDTFluxNetworkSubsystem::Parse() Parsing failed"));
|
||||
}
|
||||
}
|
||||
|
||||
//TODO reforge API to keep track of Requests
|
||||
void UDTFluxNetworkSubsystem::OnWebSocketMessageEvent_Subsystem(const FString& MessageString)
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Client %s :\nMessage Received : %s"), *WsClient->GetAddress(), *MessageString);
|
||||
// UE_LOG(logDTFluxNetwork, Warning, TEXT("Client %s :\nMessage Received : %s"), *WsClient->GetAddress(), *MessageString);
|
||||
//Do Something With the message
|
||||
FDTFluxServerResponse Response;
|
||||
Response.ReceivedAt = FDateTime::Now();
|
||||
Response.RawMessage = MessageString;
|
||||
Response.FailureReason = FText::FromString("--");
|
||||
if(FJsonObjectConverter::JsonObjectStringToUStruct(MessageString, &Response, 0, 0, false, &(Response.FailureReason)))
|
||||
EDTFluxResponseStatus ResponseStatus;
|
||||
FDTFluxServerResponse Response(MessageString, ResponseStatus);
|
||||
if (!TryMatchResponseToQueuedRequest(Response))
|
||||
{
|
||||
if(Response.Code == -1)
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Response %s does not match any queued request"),
|
||||
*UEnum::GetValueAsString(Response.GetResponseType()));
|
||||
if (ProcessPushMessage(Response) != EDTFluxResponseStatus::Success)
|
||||
{
|
||||
// return DataReceived.Broadcast(Response);
|
||||
if(Response.Type.Contains("race-data"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Race-Data Received"));
|
||||
return ParseRaceData(Response);
|
||||
}
|
||||
if(Response.Type.Contains("team-list"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Team-List Received"));
|
||||
return ParseTeamListResponse(Response);
|
||||
}
|
||||
if(Response.Type.Contains("contest-ranking"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Contest-Ranking Received"));
|
||||
return ParseContestRanking(Response);
|
||||
}
|
||||
if(Response.Type.Contains("stage-ranking") )
|
||||
{
|
||||
if(Response.SplitID == -1)
|
||||
{
|
||||
// StageRanking
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Stage-Ranking Data"));
|
||||
ParseStageRankingResponse(Response);
|
||||
}
|
||||
else
|
||||
{
|
||||
// StageRanking
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Split-Ranking Data"));
|
||||
return ParseSplitRankingResponse(Response);
|
||||
}
|
||||
}
|
||||
if(Response.Type.Contains("split-sensor"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("split-sensor Data"));
|
||||
ParseSplitSensorResponse(Response);
|
||||
|
||||
}
|
||||
if(Response.Type.Contains("status-update"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("status-update Data"));
|
||||
ParseStatusUpdateResponse(Response);
|
||||
|
||||
}
|
||||
if(Response.Type.Contains("team-update"))
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("team-update Data"));
|
||||
ParseTeamListResponse(Response);
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Not a push message"));
|
||||
// Legacy
|
||||
Parse(Response);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Ws %s :\nMessage Received : %s Cannot be Parsed"), *WsClient->GetAddress(), *MessageString);
|
||||
// return DataReceived.Broadcast(Response);
|
||||
|
||||
// // if we are here we have a tracked Message
|
||||
// QueueManager->MarkRequestAsResponded()
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent)
|
||||
@ -471,15 +617,176 @@ void UDTFluxNetworkSubsystem::OnWebSocketMessageSentEvent_Subsystem(const FStrin
|
||||
UE_LOG(logDTFluxNetwork, Warning, TEXT("Ws %s :\nMessage Sent: %s"), *WsClient->GetAddress(), *MessageSent);
|
||||
}
|
||||
|
||||
bool UDTFluxNetworkSubsystem::CleanRequestCallbacks(const FGuid& RequestId)
|
||||
{
|
||||
bool bCbSuppressSuccess = false;
|
||||
bool bErrorCbSuppressSuccess = false;
|
||||
bool bTimeoutCbSuppressSuccess = false;
|
||||
if (PendingCallbacks.Contains(RequestId))
|
||||
{
|
||||
PendingCallbacks.Remove(RequestId);
|
||||
bCbSuppressSuccess = true;
|
||||
}
|
||||
if (PendingTimeoutCallbacks.Contains(RequestId))
|
||||
{
|
||||
PendingTimeoutCallbacks.Remove(RequestId);
|
||||
bTimeoutCbSuppressSuccess = true;
|
||||
}
|
||||
if (PendingTimeoutCallbacks.Contains(RequestId))
|
||||
{
|
||||
PendingTimeoutCallbacks.Remove(RequestId);
|
||||
bErrorCbSuppressSuccess = true;
|
||||
}
|
||||
return bCbSuppressSuccess && bErrorCbSuppressSuccess && bTimeoutCbSuppressSuccess;
|
||||
}
|
||||
|
||||
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 (FOnDTFluxTrackedRequestTimeout* 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(FDTFluxServerResponse& Response)
|
||||
{
|
||||
if (!QueueManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
FGuid FoundRequestId;
|
||||
if (QueueManager->IsRequestPending(FoundRequestId, Response.GetResponseType(), Response.ContestID, Response.StageID,
|
||||
Response.SplitID))
|
||||
{
|
||||
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);
|
||||
if (PendingCallbacks.Contains(FoundRequestId))
|
||||
{
|
||||
FOnDTFluxTrackedRequestResponse* SuccessCallback = PendingCallbacks.Find(FoundRequestId);
|
||||
SuccessCallback->ExecuteIfBound(FoundRequestId, Response);
|
||||
//Suppress Callback;
|
||||
return CleanRequestCallbacks(FoundRequestId);
|
||||
}
|
||||
return QueueManager->MarkRequestAsResponded(FoundRequestId);
|
||||
}
|
||||
|
||||
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 (FOnDTFluxTrackedRequestResponse* SuccessCallback = PendingCallbacks.Find(RequestId))
|
||||
{
|
||||
if (SuccessCallback->IsBound())
|
||||
{
|
||||
EDTFluxResponseStatus ResponseStatus;
|
||||
FDTFluxServerResponse Response(ResponseData, ResponseStatus);
|
||||
if (ResponseStatus == EDTFluxResponseStatus::Success)
|
||||
{
|
||||
SuccessCallback->Execute(RequestId, Response);
|
||||
QueueManager->MarkRequestAsResponded(RequestId);
|
||||
PendingCallbacks.Remove(RequestId);
|
||||
PendingTimeoutCallbacks.Remove(RequestId);
|
||||
}
|
||||
else
|
||||
{
|
||||
QueueManager->MarkRequestAsError(RequestId);
|
||||
// Fail
|
||||
// FailTrackedRequest()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (FOnDTFluxTrackedRequestTimeout* ErrorCallback = PendingTimeoutCallbacks.Find(RequestId))
|
||||
{
|
||||
if (ErrorCallback->IsBound())
|
||||
{
|
||||
ErrorCallback->ExecuteIfBound(RequestId, ErrorMessage);
|
||||
}
|
||||
PendingTimeoutCallbacks.Remove(RequestId);
|
||||
}
|
||||
|
||||
// Nettoyer les callbacks
|
||||
PendingCallbacks.Remove(RequestId);
|
||||
|
||||
// Broadcaster l'événement Blueprint
|
||||
OnTrackedRequestFailed.Broadcast(RequestId, RequestType, ErrorMessage);
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed tracked request %s: %s"), *RequestId.ToString(), *ErrorMessage);
|
||||
}
|
||||
|
||||
void UDTFluxNetworkSubsystem::SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest)
|
||||
{
|
||||
// Générer le message JSON à partir de la requête
|
||||
FString Message = QueuedRequest.Serialize();
|
||||
|
||||
if (Message.IsEmpty())
|
||||
{
|
||||
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed to serialize queued request %s"),
|
||||
*QueuedRequest.RequestId.ToString());
|
||||
FailTrackedRequest(QueuedRequest.RequestId, TEXT("Serialization failed"), QueuedRequest.RequestType);
|
||||
return;
|
||||
}
|
||||
|
||||
// Dirty trick to fix Case (comme dans l'original)
|
||||
Message = Message.Replace(TEXT("Id"), TEXT("ID"), ESearchCase::CaseSensitive);
|
||||
|
||||
UE_LOG(logDTFluxNetwork, Log, TEXT("Sending queued request %s: %s"), *QueuedRequest.RequestId.ToString(), *Message);
|
||||
|
||||
// Envoyer via WebSocket
|
||||
SendMessage(Message);
|
||||
}
|
||||
|
||||
|
||||
FString UDTFluxNetworkSubsystem::ConstructWsAddress(const FString& Address, const FString& Path, const int& Port)
|
||||
{
|
||||
FString NewAddress;
|
||||
if( !Address.Contains("ws://") && !Address.Contains("wss://"))
|
||||
if (!Address.Contains("ws://") && !Address.Contains("wss://"))
|
||||
{
|
||||
NewAddress += FString("ws://");
|
||||
}
|
||||
NewAddress +=Address + FString(":") + FString::FromInt(Port) + Path;
|
||||
NewAddress += Address + FString(":") + FString::FromInt(Port) + Path;
|
||||
return NewAddress;
|
||||
// UE_LOG(logDTFluxNetwork, Log, TEXT("NewAddress : %s"), *NewAddress);
|
||||
|
||||
}
|
||||
|
||||
66
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
66
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
@ -0,0 +1,66 @@
|
||||
// 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"
|
||||
|
||||
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 MarkRequestAsError(const FGuid& TargetRequestGuid);
|
||||
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid);
|
||||
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
|
||||
bool IsRequestPending(FGuid& OutRequestId, 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;
|
||||
};
|
||||
@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "DTFluxRequestStructs.generated.h"
|
||||
|
||||
@ -15,9 +16,13 @@ USTRUCT()
|
||||
struct FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY()
|
||||
FString Path = "";
|
||||
|
||||
FDateTime CreatedAt = FDateTime::Now();
|
||||
FGuid RequestId = FGuid::NewGuid();
|
||||
};
|
||||
|
||||
/**
|
||||
@ -25,11 +30,13 @@ public:
|
||||
* RaceData represents all data concerning the Race and its different Contests, Stages and Splits.
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxRaceDataRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
FDTFluxRaceDataRequest(){
|
||||
FDTFluxRaceDataRequest()
|
||||
{
|
||||
Path = "race-datas";
|
||||
}
|
||||
};
|
||||
@ -39,11 +46,13 @@ public:
|
||||
* TeamList is the list of participants of the events
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxTeamListRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxTeamListRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
FDTFluxTeamListRequest(){
|
||||
FDTFluxTeamListRequest()
|
||||
{
|
||||
Path = "team-list";
|
||||
}
|
||||
};
|
||||
@ -52,7 +61,7 @@ public:
|
||||
* Struct representing a Ranking json request object for a specific to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxContestRankingRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxContestRankingRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@ -62,6 +71,7 @@ public:
|
||||
Path = "contest-ranking";
|
||||
ContestID = -1;
|
||||
}
|
||||
|
||||
FDTFluxContestRankingRequest(int InContestID)
|
||||
{
|
||||
Path = "contest-ranking";
|
||||
@ -76,7 +86,7 @@ public:
|
||||
* Struct representing a Ranking json request object for a specific Stage to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxStageRankingRequest: public FDTFluxRequestBase
|
||||
struct FDTFluxStageRankingRequest : public FDTFluxRequestBase
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@ -88,6 +98,7 @@ public:
|
||||
StageID = -1;
|
||||
SplitID = -1;
|
||||
}
|
||||
|
||||
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
||||
{
|
||||
Path = "stage-ranking";
|
||||
@ -102,15 +113,13 @@ public:
|
||||
int StageID;
|
||||
UPROPERTY()
|
||||
int SplitID;
|
||||
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Struct representing a Ranking json request object for a specific Split to the server
|
||||
*/
|
||||
USTRUCT()
|
||||
struct FDTFluxSplitRankingRequest: public FDTFluxStageRankingRequest
|
||||
struct FDTFluxSplitRankingRequest : public FDTFluxStageRankingRequest
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@ -122,6 +131,7 @@ public:
|
||||
StageID = -1;
|
||||
SplitID = -1;
|
||||
}
|
||||
|
||||
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
||||
{
|
||||
Path = "stage-ranking";
|
||||
@ -129,5 +139,105 @@ public:
|
||||
StageID = InStageId;
|
||||
SplitID = InSplitId;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -4,13 +4,38 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "UObject/Object.h"
|
||||
#include "DTFluxNetworkModule.h"
|
||||
#include "DTFluxRaceDataServerResponse.h"
|
||||
#include "DTFluxRankingServerResponse.h"
|
||||
#include "DTFluxSplitSensorServerResponse.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||
#include "Types/Objects/UDTFluxParticipantFactory.h"
|
||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||
#include "DTFluxServerResponseStruct.generated.h"
|
||||
|
||||
|
||||
/**
|
||||
* Enum pour indiquer le statut du parsing
|
||||
*/
|
||||
UENUM(BlueprintType)
|
||||
enum class EDTFluxResponseStatus : uint8
|
||||
{
|
||||
Unset = 0b00000000 UMETA(DisplayName="Unset"),
|
||||
Success = 0b10000000 UMETA(DisplayName="Success"),
|
||||
JsonParseError = 0b00000001 UMETA(DisplayName="JsonParseError"),
|
||||
ServerError = 0b00000010 UMETA(DisplayName="ServerError"),
|
||||
InvalidType = 0b00000100 UMETA(DisplayName="InvalidType"),
|
||||
MissingData = 0b00001000 UMETA(DisplayName="MissingData"),
|
||||
DataError = 0b00010000 UMETA(DisplayName="MissingData"),
|
||||
UnknownError = 0b00100000 UMETA(DisplayName="UnknownError")
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Struct representing a mixed root json server response
|
||||
* Struct representing a mixed root json server response with integrated parsing capabilities
|
||||
*/
|
||||
USTRUCT()
|
||||
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||
@ -20,35 +45,99 @@ struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||
public:
|
||||
UPROPERTY()
|
||||
FString Type = "";
|
||||
|
||||
UPROPERTY()
|
||||
int Code = -1;
|
||||
|
||||
UPROPERTY()
|
||||
FString Message = "";
|
||||
|
||||
UPROPERTY()
|
||||
FString Trigger = "";
|
||||
|
||||
UPROPERTY()
|
||||
int ContestID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
int StageID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
int SplitID = -1;
|
||||
|
||||
UPROPERTY()
|
||||
FDateTime ReceivedAt;
|
||||
|
||||
UPROPERTY()
|
||||
FString RawMessage;
|
||||
|
||||
UPROPERTY()
|
||||
FName RequestId = FName("");
|
||||
|
||||
UPROPERTY()
|
||||
FText FailureReason;
|
||||
|
||||
// === CONSTRUCTEURS ===
|
||||
FDTFluxServerResponse();
|
||||
FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus, bool bLogErrors = true);
|
||||
static FDTFluxServerResponse CreateFromJson(const FString& JsonMessage, bool bLogErrors = true);
|
||||
|
||||
// === MÉTHODES DE PARSING ===
|
||||
|
||||
EDTFluxResponseStatus TryParse(bool bLogErrors = true);
|
||||
|
||||
bool ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList);
|
||||
bool ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate);
|
||||
bool ParseRaceData(FDTFluxRaceData& OutRaceData);
|
||||
bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings);
|
||||
bool ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings);
|
||||
bool ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings);
|
||||
bool ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate);
|
||||
bool ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos);
|
||||
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
bool IsValidResponse() const { return Code == -1; }
|
||||
bool IsSuccessfullyParsed() const { return ParsingStatus == EDTFluxResponseStatus::Success; }
|
||||
EDTFluxResponseStatus GetParsingStatus() const { return ParsingStatus; }
|
||||
EDTFluxApiDataType GetResponseType() const { return ApiDataType; }
|
||||
FString GetDataType() const { return Type; }
|
||||
bool ContainsDataType(const FString& DataType) const { return Type.Contains(DataType); }
|
||||
FString ToDebugString() const;
|
||||
void ShowDebug(const bool bShouldPrintRawMessage = false) const;
|
||||
FString GetErrorMessage() const;
|
||||
|
||||
private:
|
||||
// === DONNÉES INTERNES ===
|
||||
EDTFluxApiDataType ApiDataType;
|
||||
// Statut du parsing initial
|
||||
EDTFluxResponseStatus ParsingStatus = EDTFluxResponseStatus::Unset;
|
||||
|
||||
// === MÉTHODES PRIVÉES DE PARSING ===
|
||||
bool ParseJsonObject(TSharedPtr<FJsonObject>& OutJsonObject) const;
|
||||
bool ValidateResponseType(const FString& ExpectedType) const;
|
||||
EDTFluxResponseStatus InitializeFromJson(const FString& JsonMessage, bool bLogErrors);
|
||||
|
||||
static FString GetJsonType(const EJson Type)
|
||||
{
|
||||
switch (Type)
|
||||
{
|
||||
case EJson::None:
|
||||
return TEXT("None");
|
||||
case EJson::Null:
|
||||
return TEXT("Null");
|
||||
case EJson::String:
|
||||
return TEXT("String");
|
||||
case EJson::Number:
|
||||
return TEXT("Number");
|
||||
case EJson::Boolean:
|
||||
return TEXT("Boolean");
|
||||
case EJson::Array:
|
||||
return TEXT("Array");
|
||||
case EJson::Object:
|
||||
return TEXT("Object");
|
||||
default:
|
||||
return TEXT("Unknown");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "DTFluxQueuedManager.h"
|
||||
#include "Struct/DTFluxServerResponseStruct.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "Types/DTFluxNetworkSettingsTypes.h"
|
||||
@ -14,12 +15,25 @@
|
||||
#include "DTFluxNetworkSubsystem.generated.h"
|
||||
|
||||
|
||||
|
||||
|
||||
class FDTFluxWebSocketClient;
|
||||
class UDTFluxQueuedManager;
|
||||
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
||||
class FDTFluxHttpClient;
|
||||
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
|
||||
|
||||
|
||||
// Delegates pour les requêtes avec callback
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestResponseError, const FGuid&, const FString&);
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestResponse, const FGuid&, FDTFluxServerResponse&);
|
||||
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestTimeout, const FGuid&, const FString& /*ErrorMessage*/);
|
||||
// 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 +43,26 @@ class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
|
||||
UPROPERTY()
|
||||
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
|
||||
|
||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
|
||||
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network")
|
||||
FOnWebSocketConnected OnWebSocketConnected;
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
|
||||
FOnRaceDataReceived OnRaceDataReceived;
|
||||
|
||||
FOnRaceDataReceived& OnReceivedRaceData()
|
||||
{
|
||||
return OnRaceDataReceived;
|
||||
};
|
||||
|
||||
// === DELEGATES POUR LES DONNÉES REÇUES (PUSH) ===
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
|
||||
FOnTeamListReceived OnTeamListReceived;
|
||||
|
||||
FOnTeamListReceived& OnReceivedTeamList()
|
||||
{
|
||||
return OnTeamListReceived;
|
||||
@ -53,12 +70,15 @@ public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
|
||||
FOnStageRankingReceived OnStageRankingReceived;
|
||||
|
||||
FOnStageRankingReceived& OnReceivedStageRanking()
|
||||
{
|
||||
return OnStageRankingReceived;
|
||||
}
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
|
||||
FOnSplitRankingReceived OnSplitRankingReceived;
|
||||
|
||||
FOnSplitRankingReceived& OnReceivedSplitRanking()
|
||||
{
|
||||
return OnSplitRankingReceived;
|
||||
@ -66,19 +86,24 @@ public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
|
||||
FOnContestRankingReceived OnContestRankingReceived;
|
||||
|
||||
FOnContestRankingReceived& OnReceivedContestRanking()
|
||||
{
|
||||
return OnContestRankingReceived;
|
||||
};
|
||||
|
||||
// === DELEGATES POUR LES DONNÉES REÇUES (PULL) ===
|
||||
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
|
||||
FOnSplitSensorReceived OnSplitSensorReceived;
|
||||
|
||||
FOnSplitSensorReceived& OnReceivedSplitSensor()
|
||||
{
|
||||
return OnSplitSensorReceived;
|
||||
};
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxParticipant& /*ParticipantToUpdate*/);
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*ParticipantToUpdate*/);
|
||||
FOnTeamUpdateReceived OnTeamUpdateReceived;
|
||||
|
||||
FOnTeamUpdateReceived& OnReceivedTeamUpdate()
|
||||
{
|
||||
return OnTeamUpdateReceived;
|
||||
@ -86,6 +111,7 @@ public:
|
||||
|
||||
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
|
||||
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
|
||||
|
||||
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate()
|
||||
{
|
||||
return OnTeamStatusUpdateReceived;
|
||||
@ -99,11 +125,44 @@ public:
|
||||
void Reconnect();
|
||||
|
||||
|
||||
// === REQUÊTES AVEC QUEUE ET TRACKING ===
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
FGuid SendTrackedRequest(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1, float TimeoutSeconds = 30.0f);
|
||||
|
||||
FGuid SendTrackedRequestWithCallback(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId,
|
||||
FOnDTFluxTrackedRequestResponse OnCompleted,
|
||||
FOnDTFluxTrackedRequestTimeout OnTimeout,
|
||||
TOptional<FOnDTFluxRequestResponseError> OnError = TOptional<
|
||||
FOnDTFluxRequestResponseError>(),
|
||||
float TimeoutSeconds = 30.0f);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const;
|
||||
const FDTFluxQueuedRequest* GetTrackedRequestPtr(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests", CallInEditor)
|
||||
bool HasRequestReceivedResponse(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
FString GetRequestResponseData(const FGuid& RequestId) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
bool IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
|
||||
int32 SplitId = -1) const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
int32 GetPendingRequestCount() const;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
|
||||
UDTFluxQueuedManager* GetQueueManager() const;
|
||||
|
||||
// === EVENTS BLUEPRINT POUR LE TRACKING ===
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
|
||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests")
|
||||
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||
void SendRequest(const EDTFluxRequestType RequestType, int InContestId = -1, int InStageId = -1, int InSplitId = -1);
|
||||
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||
// === REQUÊTES DIRECTES (LEGACY) ===
|
||||
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
|
||||
int InSplitId = -1);
|
||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
||||
void SendMessage(const FString& Message);
|
||||
|
||||
protected:
|
||||
@ -114,9 +173,23 @@ protected:
|
||||
|
||||
|
||||
private:
|
||||
// === CONFIGURATION ===
|
||||
FDTFluxWsSettings WsSettings;
|
||||
FDTFluxHttpSettings HttpSettings;
|
||||
|
||||
UPROPERTY()
|
||||
UDTFluxQueuedManager* QueueManager;
|
||||
|
||||
// === MAPPING DES CALLBACKS C++ ===
|
||||
TMap<FGuid, FOnDTFluxTrackedRequestResponse> PendingCallbacks;
|
||||
TMap<FGuid, FOnDTFluxTrackedRequestTimeout> PendingTimeoutCallbacks;
|
||||
TMap<FGuid, FOnDTFluxRequestResponseError> PendingErrorCallbacks;
|
||||
|
||||
// === CLIENTS RÉSEAU ===
|
||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
||||
|
||||
// === MÉTHODES DE CONFIGURATION ===
|
||||
UFUNCTION()
|
||||
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
||||
UFUNCTION()
|
||||
@ -124,30 +197,13 @@ private:
|
||||
void ReconnectWs(const FName WsClientId);
|
||||
void ReconnectHttp(const FName WsClientId);
|
||||
|
||||
// === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
|
||||
void RegisterWebSocketEvents();
|
||||
void RegisterHttpEvents();
|
||||
void UnregisterWebSocketEvents();
|
||||
void UnregisterHttpEvents();
|
||||
|
||||
void OnWebSocketConnected_Subsystem();
|
||||
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode , const FString& Reason, bool bWasClean);
|
||||
void ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse);
|
||||
void ParseRaceData(const FDTFluxServerResponse& Response);
|
||||
void ParseContestRanking(const FDTFluxServerResponse& Response);
|
||||
void ParseStageRankingResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseSplitRankingResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseStatusUpdateResponse(const FDTFluxServerResponse& Response);
|
||||
void ParseSplitSensorResponse(const FDTFluxServerResponse& Response);
|
||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||
|
||||
// TODO : Allow multiple instances of network clients.
|
||||
// // For Future use of Multi-Connections
|
||||
// TArray<FDTFluxWebSocketClientSP> WsClients;
|
||||
// // For Future use of Multi-Connections
|
||||
// TArray<FDTFluxHttpClientSP> HttpClient;
|
||||
// Fo now we jest stick to only one client for each protocol
|
||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
|
||||
|
||||
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
||||
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
||||
@ -155,9 +211,33 @@ private:
|
||||
FDelegateHandle OnWsMessageEventDelegateHandle;
|
||||
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
||||
|
||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
||||
// === GESTION DES ÉVÉNEMENTS HTTP ===
|
||||
void RegisterHttpEvents();
|
||||
void UnregisterHttpEvents();
|
||||
|
||||
// === PARSING DES RÉPONSES ===
|
||||
void ParseTeamListResponse(FDTFluxServerResponse& ServerResponse);
|
||||
void ParseRaceData(FDTFluxServerResponse& Response);
|
||||
void ParseContestRanking(FDTFluxServerResponse& Response);
|
||||
void ParseStageRankingResponse(FDTFluxServerResponse& Response);
|
||||
void ParseSplitRankingResponse(FDTFluxServerResponse& Response);
|
||||
void ParseStatusUpdateResponse(FDTFluxServerResponse& Response);
|
||||
void ParseSplitSensorResponse(FDTFluxServerResponse& Response);
|
||||
EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response);
|
||||
|
||||
void Parse(FDTFluxServerResponse& Response);
|
||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||
bool CleanRequestCallbacks(const FGuid& RequestId);
|
||||
|
||||
// === GESTION DES REQUÊTES TRACKÉES ===
|
||||
UFUNCTION()
|
||||
void OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest);
|
||||
bool TryMatchResponseToQueuedRequest(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);
|
||||
};
|
||||
|
||||
34
Source/DTFluxRaceResult/DTFluxRaceResult.Build.cs
Normal file
34
Source/DTFluxRaceResult/DTFluxRaceResult.Build.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using UnrealBuildTool;
|
||||
|
||||
public class DTFluxRaceResult : ModuleRules
|
||||
{
|
||||
public DTFluxRaceResult(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core"
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"DTFluxProjectSettings",
|
||||
"UMG",
|
||||
"WebBrowser",
|
||||
"Projects",
|
||||
"ToolMenus",
|
||||
"HTTP",
|
||||
"JsonUtilities",
|
||||
"Json"
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
161
Source/DTFluxRaceResult/Private/DTFluxRaceResultModule.cpp
Normal file
161
Source/DTFluxRaceResult/Private/DTFluxRaceResultModule.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
#include "DTFluxRaceResultModule.h"
|
||||
|
||||
#include "LevelEditor.h"
|
||||
#include "Widget/SDTFluxRaceResultWidget.h"
|
||||
#include "Widget/Style/DTFluxRaceResultStyle.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FDTFluxRaceResultModule"
|
||||
|
||||
DEFINE_LOG_CATEGORY(logDTFluxRaceResult)
|
||||
|
||||
FName DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabId = "DTFluxRaceResult";
|
||||
FText DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabDisplayName = FText::FromString(TEXT("DTFlux RaceResult"));
|
||||
|
||||
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::StartupModule()
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Warning, TEXT("DTFluxRaceResult Module Started"))
|
||||
FDTFluxRaceResultStyle::RegisterStyle();
|
||||
InitMenuExtension();
|
||||
RegisterRaceResultTab();
|
||||
}
|
||||
|
||||
#pragma region MenuExtension
|
||||
|
||||
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::InitMenuExtension()
|
||||
{
|
||||
UToolMenus::RegisterStartupCallback(
|
||||
FSimpleMulticastDelegate::FDelegate::CreateRaw(this,
|
||||
&FDTFluxRaceResult::RegisterMenuExtensions));
|
||||
}
|
||||
|
||||
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterMenuExtensions()
|
||||
{
|
||||
|
||||
// Étendre le menu enregistré
|
||||
if (UToolMenu* DTFluxMenu = UToolMenus::Get()->ExtendMenu("DTFlux.MainMenu"))
|
||||
{
|
||||
FToolMenuSection& ToolsSection = DTFluxMenu->FindOrAddSection("Tools");
|
||||
ToolsSection.AddMenuEntry(
|
||||
"DTFluxRaceResult",
|
||||
FText::FromString("RaceResult"),
|
||||
FText::FromString("Launch DTFlux RaceResult Control Panel"),
|
||||
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void FDTFluxRaceResult::CreateSubmenu(UToolMenu* Menu)
|
||||
{
|
||||
// Section 2 : Tools
|
||||
FToolMenuSection& ToolsSection = Menu->FindOrAddSection("Tools");
|
||||
ToolsSection.Label = FText::FromString("Tools");
|
||||
ToolsSection.AddMenuEntry(
|
||||
"DTFluxRaceResult",
|
||||
FText::FromString("RaceResult"),
|
||||
FText::FromString("Launch Race Result WebAdmin Console"),
|
||||
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||
// Adaptez selon votre icône
|
||||
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
|
||||
);
|
||||
}
|
||||
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::OnButtonClicked()
|
||||
{
|
||||
FGlobalTabmanager::Get()->TryInvokeTab(RaceResultTabId);
|
||||
UE_LOG(LogTemp, Log, TEXT("Race Result Launched"))
|
||||
}
|
||||
|
||||
#pragma endregion EditorTab
|
||||
|
||||
#pragma region
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterRaceResultTab()
|
||||
{
|
||||
FTabSpawnerEntry& SpawnerEntry =
|
||||
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
|
||||
RaceResultTabId,
|
||||
FOnSpawnTab::CreateRaw(this, &FDTFluxRaceResult::OnSpawnTab)
|
||||
)
|
||||
.SetDisplayName(RaceResultTabDisplayName)
|
||||
.SetTooltipText(FText::FromString(TEXT("Race Result Control Panel")));
|
||||
}
|
||||
|
||||
TSharedRef<SDockTab> DTFLUXRACERESULT_API FDTFluxRaceResult::OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs)
|
||||
{
|
||||
return
|
||||
SNew(
|
||||
SDockTab
|
||||
)
|
||||
.TabRole(ETabRole::NomadTab)
|
||||
// .ShouldAutosize(true)
|
||||
[
|
||||
SNew(SDTFluxRaceResultWidget)
|
||||
];
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
void DTFLUXRACERESULT_API FDTFluxRaceResult::ShutdownModule()
|
||||
{
|
||||
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(RaceResultTabId);
|
||||
FDTFluxRaceResultStyle::UnregisterStyle();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// // Dans votre code de debug
|
||||
// void DTFLUXRACERESULT_API FDTFluxRaceResult::DebugMenus()
|
||||
// {
|
||||
// UToolMenus* ToolMenus = UToolMenus::Get();
|
||||
// if (ToolMenus)
|
||||
// {
|
||||
// TArray<FName> MenuNames;
|
||||
// ToolMenus->GetAllMenuNames(MenuNames);
|
||||
//
|
||||
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== ALL AVAILABLE MENUS ==="));
|
||||
// for (const FName& MenuName : MenuNames)
|
||||
// {
|
||||
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("Menu: %s"), *MenuName.ToString());
|
||||
//
|
||||
// // Obtenir les sections de chaque menu
|
||||
// UToolMenu* Menu = ToolMenus->FindMenu(MenuName);
|
||||
// if (Menu)
|
||||
// {
|
||||
// for (const FToolMenuSection& Section : Menu->Sections)
|
||||
// {
|
||||
// UE_LOG(logDTFluxRaceResult, Warning, TEXT(" Section: %s"), *Section.Name.ToString());
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== END MENU LIST ==="));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// void DTFLUXRACERESULT_API FDTFluxRaceResult::AddMenu(FMenuBarBuilder& MenuBarBuilder)
|
||||
// {
|
||||
// MenuBarBuilder.AddPullDownMenu(
|
||||
// FText::FromString("DTFlux"),
|
||||
// FText::FromString("DTFlux API Tools"),
|
||||
// FNewMenuDelegate::CreateRaw(this, &FDTFluxRaceResult::FillMenu)
|
||||
// );
|
||||
// }
|
||||
|
||||
// void DTFLUXRACERESULT_API FDTFluxRaceResult::FillMenu(FMenuBuilder& MenuBuilder)
|
||||
// {
|
||||
// MenuBuilder.AddMenuEntry(
|
||||
// FText::FromString("RaceResult ControlPanel"),
|
||||
// FText::FromString("Launch RaceResult Control Panel"),
|
||||
// FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
|
||||
// FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked)
|
||||
// );
|
||||
// MenuBuilder.EndSection();
|
||||
// }
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
|
||||
IMPLEMENT_MODULE(FDTFluxRaceResult, DTFluxRaceResult)
|
||||
@ -0,0 +1,283 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "Widget/SDTFluxRaceResultWidget.h"
|
||||
|
||||
#include "DTFluxRaceResultModule.h"
|
||||
#include "HttpModule.h"
|
||||
#include "IWebBrowserCookieManager.h"
|
||||
#include "IWebBrowserWindow.h"
|
||||
#include "SlateOptMacros.h"
|
||||
#include "WebBrowserModule.h"
|
||||
#include "Interfaces/IHttpRequest.h"
|
||||
#include "Interfaces/IHttpResponse.h"
|
||||
|
||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
|
||||
void SDTFluxRaceResultWidget::Construct(const FArguments& InArgs)
|
||||
{
|
||||
|
||||
FWebBrowserInitSettings browserInitSettings = FWebBrowserInitSettings();
|
||||
|
||||
IWebBrowserModule::Get().CustomInitialize(browserInitSettings);
|
||||
WindowSettings.InitialURL = TEXT("about:blank");
|
||||
WindowSettings.BrowserFrameRate = 25;
|
||||
|
||||
if (IWebBrowserModule::IsAvailable() && IWebBrowserModule::Get().IsWebModuleAvailable())
|
||||
{
|
||||
IWebBrowserSingleton* WebBrowserSingleton = IWebBrowserModule::Get().GetSingleton();
|
||||
Browser = WebBrowserSingleton->CreateBrowserWindow(WindowSettings);
|
||||
// Browser->OnLoadUrl().BindRaw(this, &SDTFluxRaceResultWidget::OnLoadOverride);
|
||||
|
||||
}
|
||||
|
||||
|
||||
ChildSlot
|
||||
[
|
||||
SNew(SBox)
|
||||
.Padding(5.0f)
|
||||
[
|
||||
SNew(SBorder)
|
||||
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
|
||||
[
|
||||
SNew(SVerticalBox)
|
||||
+ SVerticalBox::Slot()
|
||||
[
|
||||
SAssignNew(WebBrowser, SWebBrowser, Browser)
|
||||
.ShowControls(true)
|
||||
.SupportsTransparency(true)
|
||||
.OnUrlChanged(this, &SDTFluxRaceResultWidget::OnUrlChanged)
|
||||
.OnBeforeNavigation(this, &SDTFluxRaceResultWidget::OnBeforeNavigation)
|
||||
.OnLoadCompleted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadCompleted))
|
||||
.OnLoadError(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadError))
|
||||
.OnLoadStarted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadStarted))
|
||||
.ShowErrorMessage(true)
|
||||
.ShowAddressBar(true)
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnCookieSet(bool bSuccess)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SDTFluxRaceResultWidget::LoadContentViaHTTP()
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading initial content via HTTP: %s"), *RaceResultUrl);
|
||||
LoadSpecificURL(RaceResultUrl);
|
||||
}
|
||||
|
||||
|
||||
void SDTFluxRaceResultWidget::LoadSpecificURL(const FString& Url)
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
|
||||
|
||||
// Créer la requête HTTP
|
||||
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
|
||||
|
||||
// Ajouter l'authentification Basic pour TOUTES les requêtes
|
||||
FString Credentials = Username + TEXT(":") + Password;
|
||||
FString EncodedCredentials = FBase64::Encode(Credentials);
|
||||
|
||||
Request->SetURL(Url);
|
||||
Request->SetVerb("GET");
|
||||
Request->SetHeader("Authorization", "Basic " + EncodedCredentials);
|
||||
Request->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
|
||||
Request->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
Request->SetHeader("Accept-Language", "en-US,en;q=0.5");
|
||||
Request->SetHeader("Accept-Encoding", "gzip, deflate");
|
||||
Request->SetHeader("Connection", "keep-alive");
|
||||
|
||||
Request->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
|
||||
Request->ProcessRequest();
|
||||
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP request sent with Basic Auth"));
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request,
|
||||
TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful)
|
||||
{
|
||||
if (bWasSuccessful && HttpResponse.IsValid())
|
||||
{
|
||||
int32 ResponseCode = HttpResponse->GetResponseCode();
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP Response Code: %d"), ResponseCode);
|
||||
|
||||
if (ResponseCode == 200)
|
||||
{
|
||||
FString Content = HttpResponse->GetContentAsString();
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded successfully, size: %d characters"), Content.Len());
|
||||
|
||||
// Traiter le contenu HTML
|
||||
FString ProcessedContent = ProcessHTMLContent(Content, Request->GetURL());
|
||||
|
||||
// Charger le contenu dans le browser via LoadString
|
||||
if (Browser.IsValid())
|
||||
{
|
||||
Browser->LoadString(ProcessedContent, Request->GetURL());
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded into browser"));
|
||||
}
|
||||
}
|
||||
else if (ResponseCode == 401)
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Error, TEXT("Authentication failed - 401 Unauthorized. Check your credentials."));
|
||||
}
|
||||
else if (ResponseCode == 403)
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Error, TEXT("Access forbidden - 403 Forbidden"));
|
||||
}
|
||||
else if (ResponseCode == 404)
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Error, TEXT("Page not found - 404 Not Found"));
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed with code: %d"), ResponseCode);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed completely"));
|
||||
}
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnLoadOverride()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void SDTFluxRaceResultWidget::OnUrlChanged(const FText& NewUrl)
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("URL changed to: %s"), *NewUrl.ToString());
|
||||
}
|
||||
|
||||
FString SDTFluxRaceResultWidget::ProcessHTMLContent(const FString& Content, const FString& BaseUrl)
|
||||
{
|
||||
FString ProcessedContent = Content;
|
||||
|
||||
// Extraire le domaine de base
|
||||
FString BaseDomain = ExtractDomain(BaseUrl);
|
||||
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Processing HTML content, base domain: %s"), *BaseDomain);
|
||||
|
||||
// Convertir tous les liens relatifs en liens absolus
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"/"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"/"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("src='/"), *FString::Printf(TEXT("src='%s/"), *BaseDomain));
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("href='/"), *FString::Printf(TEXT("href='%s/"), *BaseDomain));
|
||||
|
||||
// Gérer les liens relatifs avec ./
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"./"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"./"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
|
||||
|
||||
// Ajouter une balise base pour aider avec les liens relatifs restants
|
||||
FString BaseTag = FString::Printf(TEXT("<base href=\"%s/\">"), *BaseDomain);
|
||||
if (ProcessedContent.Contains(TEXT("<head>")))
|
||||
{
|
||||
FString HeadReplacement = TEXT("<head>") + BaseTag;
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("<head>"), *HeadReplacement);
|
||||
}
|
||||
else if (ProcessedContent.Contains(TEXT("<HEAD>")))
|
||||
{
|
||||
FString HeadReplacement = TEXT("<HEAD>") + BaseTag;
|
||||
ProcessedContent = ProcessedContent.Replace(TEXT("<HEAD>"), *HeadReplacement);
|
||||
}
|
||||
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTML content processed"));
|
||||
return ProcessedContent;
|
||||
}
|
||||
|
||||
|
||||
FString SDTFluxRaceResultWidget::ExtractDomain(const FString& Url)
|
||||
{
|
||||
FString Domain = Url;
|
||||
|
||||
// Enlever le protocole
|
||||
if (Url.StartsWith(TEXT("https://")))
|
||||
{
|
||||
Domain = Url.Mid(8);
|
||||
}
|
||||
else if (Url.StartsWith(TEXT("http://")))
|
||||
{
|
||||
Domain = Url.Mid(7);
|
||||
}
|
||||
|
||||
// Trouver le premier slash (début du path)
|
||||
int32 SlashIndex;
|
||||
if (Domain.FindChar(TEXT('/'), SlashIndex))
|
||||
{
|
||||
Domain = Url.Left(Url.Len() - (Domain.Len() - SlashIndex));
|
||||
}
|
||||
|
||||
// Enlever le port si présent
|
||||
FString DomainPart = Domain;
|
||||
if (Domain.StartsWith(TEXT("https://")))
|
||||
{
|
||||
DomainPart = Domain.Mid(8);
|
||||
}
|
||||
else if (Domain.StartsWith(TEXT("http://")))
|
||||
{
|
||||
DomainPart = Domain.Mid(7);
|
||||
}
|
||||
|
||||
int32 ColonIndex;
|
||||
if (DomainPart.FindChar(TEXT(':'), ColonIndex))
|
||||
{
|
||||
Domain = Domain.Left(Domain.Len() - (DomainPart.Len() - ColonIndex));
|
||||
}
|
||||
|
||||
return Domain;
|
||||
}
|
||||
|
||||
bool SDTFluxRaceResultWidget::OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request)
|
||||
{
|
||||
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
|
||||
|
||||
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HTTPRequest = FHttpModule::Get().CreateRequest();
|
||||
|
||||
// Authentification pour toutes les requêtes
|
||||
FString Credentials = Username + TEXT(":") + Password;
|
||||
FString EncodedCredentials = FBase64::Encode(Credentials);
|
||||
|
||||
HTTPRequest->SetURL(RaceResultUrl);
|
||||
HTTPRequest->SetVerb("GET");
|
||||
HTTPRequest->SetHeader("Authorization", "Basic " + EncodedCredentials);
|
||||
HTTPRequest->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
HTTPRequest->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
|
||||
HTTPRequest->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
|
||||
HTTPRequest->ProcessRequest();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnLoadCompleted()
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Completed"));
|
||||
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnLoadStarted()
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Started"));
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnLoadError()
|
||||
{
|
||||
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Error"));
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials)
|
||||
{
|
||||
}
|
||||
|
||||
void SDTFluxRaceResultWidget::SetupBasicAuth()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||
@ -0,0 +1,47 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
|
||||
#include "Widget/Style/DTFluxRaceResultStyle.h"
|
||||
|
||||
|
||||
#include "Interfaces/IPluginManager.h"
|
||||
#include "Styling/SlateStyleRegistry.h"
|
||||
#include "Styling/SlateStyleMacros.h"
|
||||
|
||||
#define RootToContentDir Style->RootToContentDir
|
||||
|
||||
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::StyleSet = nullptr;
|
||||
|
||||
void FDTFluxRaceResultStyle::RegisterStyle()
|
||||
{
|
||||
if(StyleSet.IsValid()) return;
|
||||
|
||||
StyleSet = Create();
|
||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
|
||||
}
|
||||
|
||||
void FDTFluxRaceResultStyle::UnregisterStyle()
|
||||
{
|
||||
if(StyleSet.IsValid())
|
||||
{
|
||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
|
||||
ensure(StyleSet.IsUnique());
|
||||
StyleSet.Reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void FDTFluxRaceResultStyle::ReloadTextures()
|
||||
{
|
||||
}
|
||||
|
||||
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::Create()
|
||||
{
|
||||
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxRaceResultStyle"));
|
||||
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir()/TEXT("Resources"));
|
||||
|
||||
Style->Set("LevelEditor.Tab.IconRaceResult", new IMAGE_BRUSH_SVG("DTFluxRaceResult16x16", FVector2d(16)) );
|
||||
return Style;
|
||||
}
|
||||
|
||||
33
Source/DTFluxRaceResult/Public/DTFluxRaceResultModule.h
Normal file
33
Source/DTFluxRaceResult/Public/DTFluxRaceResultModule.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Modules/ModuleManager.h"
|
||||
|
||||
DTFLUXRACERESULT_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxRaceResult, All, All);
|
||||
|
||||
class DTFLUXRACERESULT_API FDTFluxRaceResult : public IModuleInterface
|
||||
{
|
||||
public:
|
||||
virtual void StartupModule() override;
|
||||
virtual void ShutdownModule() override;
|
||||
|
||||
#pragma region MenuExtention
|
||||
void RegisterMenuExtensions();
|
||||
void InitMenuExtension();
|
||||
void CreateSubmenu(UToolMenu* Menu);
|
||||
|
||||
// void AddMenu(FMenuBarBuilder& MenuBarBuilder);
|
||||
// void FillMenu(FMenuBuilder& MenuBuilder);
|
||||
void OnButtonClicked();
|
||||
#pragma endregion
|
||||
|
||||
#pragma region EditorTab
|
||||
void RegisterRaceResultTab();
|
||||
TSharedRef<SDockTab> OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs);
|
||||
private:
|
||||
// static void DebugMenus();
|
||||
static FName RaceResultTabId;
|
||||
static FText RaceResultTabDisplayName;
|
||||
TSharedPtr<class SDTFluxRaceResultWidget> RaceResultWidget;
|
||||
#pragma endregion
|
||||
};
|
||||
@ -0,0 +1,54 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "SWebBrowser.h"
|
||||
#include "Widgets/SCompoundWidget.h"
|
||||
|
||||
class IHttpResponse;
|
||||
class IHttpRequest;
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class DTFLUXRACERESULT_API SDTFluxRaceResultWidget : public SCompoundWidget
|
||||
{
|
||||
public:
|
||||
SLATE_BEGIN_ARGS(SDTFluxRaceResultWidget)
|
||||
{
|
||||
}
|
||||
|
||||
SLATE_END_ARGS()
|
||||
|
||||
/** Constructs this widget with InArgs */
|
||||
void Construct(const FArguments& InArgs);
|
||||
void OnCookieSet(bool bSuccess);
|
||||
void LoadSpecificURL(const FString& Url);
|
||||
void LoadContentViaHTTP();
|
||||
|
||||
private:
|
||||
TSharedPtr<SWebBrowser> WebBrowser;
|
||||
TSharedPtr<IWebBrowserWindow> Browser;
|
||||
TSharedPtr<IWebBrowserAdapter> BrowserAdapter;
|
||||
FCreateBrowserWindowSettings WindowSettings;
|
||||
|
||||
void OnUrlChanged(const FText& NewUrl);
|
||||
FString ProcessHTMLContent(const FString& Content, const FString& BaseUrl);
|
||||
FString ExtractDomain(const FString& Url);
|
||||
void OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request, TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful);
|
||||
bool OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request);
|
||||
void OnLoadCompleted();
|
||||
void OnLoadStarted();
|
||||
void OnLoadError();
|
||||
|
||||
void OnLoadOverride();
|
||||
|
||||
void OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials);
|
||||
|
||||
FString RaceResultUrl = "https://raceresult.tds-france.com";
|
||||
FString Username = "sporkrono";
|
||||
FString Password = "Notre 3ème décennie d'action pour le climat";
|
||||
|
||||
|
||||
void SetupBasicAuth();
|
||||
};
|
||||
@ -0,0 +1,34 @@
|
||||
// Fill out your copyright notice in the Description page of Project Settings.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Styling/ISlateStyle.h"
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class DTFLUXRACERESULT_API FDTFluxRaceResultStyle
|
||||
{
|
||||
|
||||
public:
|
||||
static void RegisterStyle();
|
||||
static void UnregisterStyle();
|
||||
|
||||
static void ReloadTextures();
|
||||
|
||||
static const ISlateStyle& Get()
|
||||
{
|
||||
return *StyleSet;
|
||||
}
|
||||
|
||||
static const FName& GetStyleSetName()
|
||||
{
|
||||
return StyleSet->GetStyleSetName();
|
||||
}
|
||||
|
||||
private:
|
||||
static TSharedPtr<ISlateStyle> Create();
|
||||
static TSharedPtr<ISlateStyle> StyleSet;
|
||||
};
|
||||
@ -2,25 +2,27 @@
|
||||
|
||||
public class DTFluxUtilities : ModuleRules
|
||||
{
|
||||
public DTFluxUtilities(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
public DTFluxUtilities(ReadOnlyTargetRules Target) : base(Target)
|
||||
{
|
||||
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
PublicDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"Core",
|
||||
}
|
||||
);
|
||||
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
}
|
||||
);
|
||||
}
|
||||
PrivateDependencyModuleNames.AddRange(
|
||||
new string[]
|
||||
{
|
||||
"CoreUObject",
|
||||
"Engine",
|
||||
"Slate",
|
||||
"SlateCore",
|
||||
"DTFluxCore",
|
||||
"DTFluxCoreSubsystem",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
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