Compare commits
61 Commits
fa5493adcf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 892e282d1d | |||
| adc46114d8 | |||
| c88718fe35 | |||
| 8fe2750a54 | |||
| 33d3ea1e37 | |||
| 0bc765cd29 | |||
| dbe904e388 | |||
| 1f35fcbcf5 | |||
| 65c32d9240 | |||
| 609f623737 | |||
| e02ed8538f | |||
| 9b85bfc94a | |||
| da89e35eb2 | |||
| d6b8874827 | |||
| 193d8d4094 | |||
| 3291eb366d | |||
| 714e616fb1 | |||
| 17368f359f | |||
| 880ca9a3b1 | |||
| c02993057f | |||
| 69536766b1 | |||
| e63f54a882 | |||
| 6b58525349 | |||
| 505e4b7af2 | |||
| ae8b694f69 | |||
| a0b3ad6d8c | |||
| f644fb99a8 | |||
| fdae0fddc8 | |||
| f304685f01 | |||
| 07c83b5c8e | |||
| de5ed7f328 | |||
| 0a175f7813 | |||
| 4bbdf43ffa | |||
| 51e5898d4b | |||
| 1c04ae6bd7 | |||
| e6d878aeef | |||
| a1e6902a15 | |||
| c2733a2b97 | |||
| 76a199e95e | |||
| 6a88fec83a | |||
| 700a7120f0 | |||
| f1d583926b | |||
| a08bcd98a4 | |||
| d7a189f905 | |||
| 0d851b7298 | |||
| d92ca63ea4 | |||
| f1f300a351 | |||
| 73413e44b4 | |||
| bc6a928312 | |||
| 03eb1132ef | |||
| 8f884f6224 | |||
| 9bb5e760f2 | |||
| 760a764816 | |||
| 43a7fb7400 | |||
| b63f2dd7b5 | |||
| 7e1ce2cdfa | |||
| a2be97cfe4 | |||
| e0edf5ab8d | |||
| 8387319bf8 | |||
| 27822229d0 | |||
| 801e946a89 |
@ -49,6 +49,11 @@
|
|||||||
"Name": "DTFluxAPIStatus",
|
"Name": "DTFluxAPIStatus",
|
||||||
"Type": "Editor",
|
"Type": "Editor",
|
||||||
"LoadingPhase": "Default"
|
"LoadingPhase": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "DTFluxRemote",
|
||||||
|
"Type": "Editor",
|
||||||
|
"LoadingPhase": "Default"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Plugins": [
|
"Plugins": [
|
||||||
|
|||||||
9
Notes.md
Normal file
9
Notes.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# DEV NOTES
|
||||||
|
|
||||||
|
# TO THINK
|
||||||
|
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas ou Participant supprimé avec réaffectation de son Bib
|
||||||
|
- [ ] Team-Update : il faut trouver une stratégie pour les cas de réaffectation de Bib d'un Concurrent.
|
||||||
|
__Solutions Possibles__:
|
||||||
|
- Si le Bib de l'update n'existe pas alors, le participant à changer de Bib. ça signifie qu'il faut que les organisateur ne réaffecte pas un Bib déja existant.~~
|
||||||
|
- [ ] Extraire Stages et Splits des Contest Pour les Mettre à plat.
|
||||||
@ -1 +1 @@
|
|||||||
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.4.2)
|
# DTFluxAPI Plugin for UNREAL ENGINE (version 5.6)
|
||||||
|
|||||||
@ -9,7 +9,7 @@ public class DTFluxAPIStatus : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core", "DTFluxCoreSubsystem",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -27,7 +27,11 @@ public class DTFluxAPIStatus : ModuleRules
|
|||||||
"EditorStyle",
|
"EditorStyle",
|
||||||
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
|
"ToolWidgets", // Nécessaire pour FSlimHorizontalToolBarBuilder
|
||||||
"UnrealEd",
|
"UnrealEd",
|
||||||
"Settings"
|
"Settings",
|
||||||
|
"DTFluxCoreSubsystem",
|
||||||
|
"InputCore",
|
||||||
|
"OutputLog",
|
||||||
|
"ToolMenus",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,9 +14,9 @@ FText DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StatusTabDisplayName = FText::
|
|||||||
|
|
||||||
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StartupModule()
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StartupModule()
|
||||||
{
|
{
|
||||||
|
FDTFluxStatusStyle::RegisterStyle();
|
||||||
InitMenuExtension();
|
InitMenuExtension();
|
||||||
RegisterStatusTab();
|
RegisterStatusTab();
|
||||||
FDTFluxStatusStyle::RegisterStyle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -25,45 +25,109 @@ void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::StartupModule()
|
|||||||
|
|
||||||
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::InitMenuExtension()
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::InitMenuExtension()
|
||||||
{
|
{
|
||||||
FLevelEditorModule& LevelEditorModule =
|
// FLevelEditorModule& LevelEditorModule =
|
||||||
FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
// FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
|
||||||
// FDTFluxAPIModule& DTFluxApi =
|
// // FDTFluxAPIModule& DTFluxApi =
|
||||||
// FModuleManager::LoadModuleChecked<FDTFluxAPIModule>(TEXT("DTFluxAPI"));
|
// // FModuleManager::LoadModuleChecked<FDTFluxAPIModule>(TEXT("DTFluxAPI"));
|
||||||
const TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
|
// const TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
|
||||||
|
//
|
||||||
|
// MenuExtender->AddMenuBarExtension(
|
||||||
|
// "Help",
|
||||||
|
// EExtensionHook::Before,
|
||||||
|
// nullptr,
|
||||||
|
// FMenuBarExtensionDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::AddMenu)
|
||||||
|
// );
|
||||||
|
// LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
|
||||||
|
|
||||||
MenuExtender->AddMenuBarExtension(
|
UToolMenus::RegisterStartupCallback(
|
||||||
"Help",
|
FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::RegisterMenus)
|
||||||
EExtensionHook::Before,
|
|
||||||
nullptr,
|
|
||||||
FMenuBarExtensionDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::AddMenu)
|
|
||||||
);
|
);
|
||||||
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::AddMenu(FMenuBarBuilder& MenuBarBuilder)
|
void FDTFluxAPIStatusModule::RegisterMenus()
|
||||||
{
|
{
|
||||||
MenuBarBuilder.AddPullDownMenu(
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Creating DTFlux menu"));
|
||||||
|
|
||||||
|
// 1. Enregistrer le menu DTFlux
|
||||||
|
UToolMenu* DTFluxMenu = UToolMenus::Get()->RegisterMenu("DTFlux.MainMenu");
|
||||||
|
if (DTFluxMenu)
|
||||||
|
{
|
||||||
|
CreateSubmenu(DTFluxMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Ajouter ce menu à la barre principale
|
||||||
|
if (UToolMenu* MainMenu = UToolMenus::Get()->ExtendMenu("MainFrame.MainMenu"))
|
||||||
|
{
|
||||||
|
FToolMenuSection& DTFluxMenuSection = MainMenu->FindOrAddSection("DTFlux");
|
||||||
|
DTFluxMenuSection.Label = FText::FromString("DTFlux");
|
||||||
|
|
||||||
|
DTFluxMenuSection.AddSubMenu(
|
||||||
|
"DTFluxSubmenu",
|
||||||
FText::FromString("DTFlux"),
|
FText::FromString("DTFlux"),
|
||||||
FText::FromString("DTFlux API Tools"),
|
FText::FromString("DTFlux API Tools"),
|
||||||
FNewMenuDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::FillMenu)
|
FNewToolMenuDelegate::CreateLambda([](UToolMenu* Menu)
|
||||||
|
{
|
||||||
|
// Référencer le menu enregistré
|
||||||
|
if (UToolMenu* RegisteredMenu = UToolMenus::Get()->FindMenu("DTFlux.MainMenu"))
|
||||||
|
{
|
||||||
|
// Copier la structure du menu enregistré
|
||||||
|
for (const FToolMenuSection& Section : RegisteredMenu->Sections)
|
||||||
|
{
|
||||||
|
Menu->Sections.Add(Section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tab.Icon")
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::FillMenu(FMenuBuilder& MenuBuilder)
|
void FDTFluxAPIStatusModule::CreateSubmenu(UToolMenu* Menu)
|
||||||
{
|
{
|
||||||
MenuBuilder.BeginSection(NAME_None, FText::FromString("DTFlux API"));
|
FToolMenuSection& DTFluxAPISection = Menu->FindOrAddSection("DTFluxAPI");
|
||||||
MenuBuilder.AddMenuEntry(
|
DTFluxAPISection.Label = FText::FromString("DTFlux API");
|
||||||
FText::FromString("Status"),
|
// Cette section est vide pour le moment, prête pour de futurs boutons
|
||||||
FText::FromString("Launch DTFlux Status"),
|
|
||||||
FSlateIcon(FDTFluxStatusStyle::GetStyleSetName(), "LevelEditor.Tab.Icon"),
|
|
||||||
FExecuteAction::CreateRaw(this, &FDTFluxAPIStatusModule::OnButtonClicked)
|
|
||||||
);
|
|
||||||
MenuBuilder.EndSection();
|
|
||||||
|
|
||||||
|
|
||||||
|
// Section 2 : Tools
|
||||||
|
FToolMenuSection& ToolsSection = Menu->FindOrAddSection("Tools");
|
||||||
|
ToolsSection.Label = FText::FromString("Tools");
|
||||||
|
|
||||||
|
|
||||||
|
// Ajouter le bouton Status dans la section Tools
|
||||||
|
DTFluxAPISection.AddMenuEntry(
|
||||||
|
"DTFluxStatus",
|
||||||
|
FText::FromString("DTFlux Status"),
|
||||||
|
FText::FromString("Launch DTFlux Status Control Panel"),
|
||||||
|
FSlateIcon(FDTFluxStatusStyle::GetStyleSetName(), "LevelEditor.Tab.Icon"),
|
||||||
|
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxAPIStatusModule::OnButtonClicked))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::AddMenu(FMenuBarBuilder& MenuBarBuilder)
|
||||||
|
// {
|
||||||
|
// MenuBarBuilder.AddPullDownMenu(
|
||||||
|
// FText::FromString("DTFlux"),
|
||||||
|
// FText::FromString("DTFlux API Tools"),
|
||||||
|
// FNewMenuDelegate::CreateRaw(this, &FDTFluxAPIStatusModule::FillMenu)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::FillMenu(FMenuBuilder& MenuBuilder)
|
||||||
|
// {
|
||||||
|
// MenuBuilder.BeginSection(NAME_None, FText::FromString("DTFlux API"));
|
||||||
|
// MenuBuilder.AddMenuEntry(
|
||||||
|
// FText::FromString("Status"),
|
||||||
|
// FText::FromString("Launch DTFlux Status"),
|
||||||
|
// FSlateIcon(FDTFluxStatusStyle::GetStyleSetName(), "LevelEditor.Tab.Icon"),
|
||||||
|
// FExecuteAction::CreateRaw(this, &FDTFluxAPIStatusModule::OnButtonClicked)
|
||||||
|
// );
|
||||||
|
// MenuBuilder.EndSection();
|
||||||
|
// }
|
||||||
|
|
||||||
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::OnButtonClicked()
|
void DTFLUXAPISTATUS_API FDTFluxAPIStatusModule::OnButtonClicked()
|
||||||
{
|
{
|
||||||
FGlobalTabmanager::Get()->TryInvokeTab(StatusTabId);
|
FGlobalTabmanager::Get()->TryInvokeTab(StatusTabId);
|
||||||
|
|||||||
@ -1,367 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "widgets/DTFluxStatusWidget.h"
|
|
||||||
|
|
||||||
#include "SlateOptMacros.h"
|
|
||||||
#include "DTFluxAPIStatusModule.h"
|
|
||||||
#include "EditorStyleSet.h"
|
|
||||||
#include "ISettingsCategory.h"
|
|
||||||
#include "ISettingsContainer.h"
|
|
||||||
#include "ISettingsModule.h"
|
|
||||||
#include "ISettingsSection.h"
|
|
||||||
#include "Styling/SlateIconFinder.h"
|
|
||||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
|
||||||
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
|
||||||
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
||||||
|
|
||||||
|
|
||||||
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
||||||
|
|
||||||
void SDTFluxStatusWidget::OnOpenSettingsClicked()
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxStatus, Warning, TEXT("Settings Clicked !!!!"));
|
|
||||||
ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked<ISettingsModule>("Settings");
|
|
||||||
SettingsModule.ShowViewer("Project", "DTFluxProjectSettings", "DTFluxNetworkSettings");
|
|
||||||
}
|
|
||||||
|
|
||||||
FReply SDTFluxStatusWidget::OnRaceDatasClicked()
|
|
||||||
{
|
|
||||||
DTFlux->SendRequest(EDTFluxRequestType::RaceData);
|
|
||||||
return FReply::Handled();
|
|
||||||
}
|
|
||||||
|
|
||||||
FReply SDTFluxStatusWidget::OnTeamListClicked()
|
|
||||||
{
|
|
||||||
DTFlux->SendRequest(EDTFluxRequestType::TeamList);
|
|
||||||
return FReply::Handled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
|
|
||||||
{
|
|
||||||
|
|
||||||
DTFlux =
|
|
||||||
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
|
||||||
ConnectionActionButtonText.Set(
|
|
||||||
DTFlux->WsStatus != EDTFluxConnectionStatus::Connected ?
|
|
||||||
FText::FromString("Connect") :
|
|
||||||
FText::FromString("Disconnect")
|
|
||||||
);
|
|
||||||
|
|
||||||
bCanSupportFocus = true;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FSlimHorizontalToolBarBuilder ToolBarBuilder(
|
|
||||||
nullptr,
|
|
||||||
FMultiBoxCustomization::None,
|
|
||||||
nullptr,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
||||||
ToolBarBuilder.BeginSection("Settings");
|
|
||||||
{
|
|
||||||
ToolBarBuilder.AddToolBarButton(
|
|
||||||
FUIAction(FExecuteAction::CreateSP(this, &SDTFluxStatusWidget::OnOpenSettingsClicked)),
|
|
||||||
NAME_None,
|
|
||||||
INVTEXT("DTFlux Settings"),
|
|
||||||
INVTEXT("Ouvrir les paramètres DTFlux"),
|
|
||||||
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ToolBarBuilder.EndSection();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FSlateFontInfo TitleTextFont = FCoreStyle::Get().GetFontStyle(FName("EmbossedText"));
|
|
||||||
TitleTextFont.Size = 15;
|
|
||||||
ChildSlot
|
|
||||||
[
|
|
||||||
#pragma region ToolBarSection
|
|
||||||
SNew(SVerticalBox)
|
|
||||||
+SVerticalBox::Slot()
|
|
||||||
.AutoHeight()
|
|
||||||
|
|
||||||
[
|
|
||||||
SNew(SBox)
|
|
||||||
[
|
|
||||||
SNew(SHorizontalBox)
|
|
||||||
+SHorizontalBox::Slot()
|
|
||||||
.FillWidth(2.0)
|
|
||||||
[
|
|
||||||
SNew(SSpacer)
|
|
||||||
]
|
|
||||||
+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)
|
|
||||||
[
|
|
||||||
SNew(SHorizontalBox)
|
|
||||||
+SHorizontalBox::Slot()
|
|
||||||
.VAlign(VAlign_Center)
|
|
||||||
.HAlign(HAlign_Center)
|
|
||||||
.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)
|
|
||||||
[
|
|
||||||
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
|
|
||||||
#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();
|
|
||||||
return
|
|
||||||
FText::FromString(Status);
|
|
||||||
// FText::FromString("Unknown");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
|
|
||||||
{
|
|
||||||
switch (DTFlux->WsStatus)
|
|
||||||
{
|
|
||||||
case EDTFluxConnectionStatus::Connected:
|
|
||||||
return FText::FromString("Disconnect");
|
|
||||||
default:
|
|
||||||
return FText::FromString("Connect");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked()
|
|
||||||
{
|
|
||||||
if(DTFlux)
|
|
||||||
{
|
|
||||||
switch (DTFlux->WsStatus)
|
|
||||||
{
|
|
||||||
case EDTFluxConnectionStatus::Connected:
|
|
||||||
DTFlux->Reconnect();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
DTFlux->Connect();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return FReply::Handled();
|
|
||||||
}
|
|
||||||
FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
|
|
||||||
{
|
|
||||||
FColor Color;
|
|
||||||
switch (DTFlux->WsStatus)
|
|
||||||
{
|
|
||||||
case EDTFluxConnectionStatus::Unset:
|
|
||||||
Color = FColor::Orange;
|
|
||||||
break;
|
|
||||||
case EDTFluxConnectionStatus::Connected:
|
|
||||||
Color = FColor::Green;
|
|
||||||
break;
|
|
||||||
case EDTFluxConnectionStatus::NotConnected:
|
|
||||||
Color = FColor::Orange;
|
|
||||||
break;
|
|
||||||
case EDTFluxConnectionStatus::Closed:
|
|
||||||
Color = FColor::Magenta;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Color = FColor::Red;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return FSlateColor(Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
|
|
||||||
{
|
|
||||||
FColor Color= FColor::Green;
|
|
||||||
switch (DTFlux->WsStatus)
|
|
||||||
{
|
|
||||||
case EDTFluxConnectionStatus::Connected:
|
|
||||||
Color = FColor::Red;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Color = FColor::Green;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return FSlateColor(Color);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
||||||
455
Source/DTFluxAPIStatus/Private/widgets/DTFluxStatusWidget.cpp
Normal file
455
Source/DTFluxAPIStatus/Private/widgets/DTFluxStatusWidget.cpp
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "widgets/DTFluxStatusWidget.h"
|
||||||
|
|
||||||
|
#include "SlateOptMacros.h"
|
||||||
|
#include "DTFluxAPIStatusModule.h"
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
#include "ISettingsModule.h"
|
||||||
|
#include "MovieSceneSequenceID.h"
|
||||||
|
#include "OutputLogCreationParams.h"
|
||||||
|
#include "OutputLogModule.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "Subsystems/DTFluxNetworkSubsystem.h"
|
||||||
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
||||||
|
|
||||||
|
|
||||||
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::OnOpenSettingsClicked()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Settings Clicked !!!!"));
|
||||||
|
ISettingsModule& SettingsModule = FModuleManager::LoadModuleChecked<ISettingsModule>("Settings");
|
||||||
|
SettingsModule.ShowViewer("Project", "DTFluxProjectSettings", "DTFluxNetworkSettings");
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnRaceDatasClicked()
|
||||||
|
{
|
||||||
|
DTFluxNetwork->SendRequest(EDTFluxRequestType::RaceData);
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnTeamListClicked()
|
||||||
|
{
|
||||||
|
DTFluxNetwork->SendRequest(EDTFluxRequestType::TeamList);
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::Construct(const FArguments& InArgs)
|
||||||
|
{
|
||||||
|
DTFluxNetwork =
|
||||||
|
GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||||
|
DTFluxCore =
|
||||||
|
GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
ConnectionActionButtonText.Set(
|
||||||
|
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,
|
||||||
|
FMultiBoxCustomization::None,
|
||||||
|
nullptr,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
ToolBarBuilder.BeginSection("Settings");
|
||||||
|
{
|
||||||
|
ToolBarBuilder.AddToolBarButton(
|
||||||
|
FUIAction(FExecuteAction::CreateSP(this, &SDTFluxStatusWidget::OnOpenSettingsClicked)),
|
||||||
|
NAME_None,
|
||||||
|
INVTEXT("DTFlux Settings"),
|
||||||
|
INVTEXT("Ouvrir les paramètres DTFlux"),
|
||||||
|
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Settings")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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(SBox)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.FillWidth(2.0)
|
||||||
|
[
|
||||||
|
SNew(SSpacer)
|
||||||
|
]
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.AutoWidth()
|
||||||
|
.FillWidth(1.0)
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Right)
|
||||||
|
[
|
||||||
|
ToolBarBuilder.MakeWidget()
|
||||||
|
]
|
||||||
|
]
|
||||||
|
#pragma endregion
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region WebsocketStatusSection
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
[
|
||||||
|
SNew(SHorizontalBox)
|
||||||
|
+ SHorizontalBox::Slot()
|
||||||
|
.VAlign(VAlign_Center)
|
||||||
|
.HAlign(HAlign_Center)
|
||||||
|
.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
|
||||||
|
]
|
||||||
|
+ SVerticalBox::Slot()
|
||||||
|
.VAlign(VAlign_Fill)
|
||||||
|
.HAlign(HAlign_Fill)
|
||||||
|
.AutoHeight()
|
||||||
|
[
|
||||||
|
#pragma region DataModelControlSection
|
||||||
|
SNew(SBorder)
|
||||||
|
.Padding(6.0f)
|
||||||
|
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
|
||||||
|
[
|
||||||
|
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)
|
||||||
|
]
|
||||||
|
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxStatusWidget::GetWebSocketStatusText() const
|
||||||
|
{
|
||||||
|
FString Status =
|
||||||
|
UEnum::GetDisplayValueAsText(DTFluxNetwork->WsStatus).ToString();
|
||||||
|
return
|
||||||
|
FText::FromString(Status);
|
||||||
|
// FText::FromString("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
FText SDTFluxStatusWidget::GetWebConnectActionButtonText() const
|
||||||
|
{
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
return FText::FromString("Disconnect");
|
||||||
|
default:
|
||||||
|
return FText::FromString("Connect");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxStatusWidget::OnConnectionActionButtonClicked()
|
||||||
|
{
|
||||||
|
if (DTFluxNetwork)
|
||||||
|
{
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
DTFluxNetwork->Reconnect();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DTFluxNetwork->Connect();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FSlateColor SDTFluxStatusWidget::GetWebSocketStatusColor() const
|
||||||
|
{
|
||||||
|
FColor Color;
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Unset:
|
||||||
|
Color = FColor::Orange;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
Color = FColor::Green;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::NotConnected:
|
||||||
|
Color = FColor::Orange;
|
||||||
|
break;
|
||||||
|
case EDTFluxConnectionStatus::Closed:
|
||||||
|
Color = FColor::Magenta;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Color = FColor::Red;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FSlateColor(Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
FSlateColor SDTFluxStatusWidget::GetWebConnectActionButtonColor() const
|
||||||
|
{
|
||||||
|
FColor Color = FColor::Green;
|
||||||
|
switch (DTFluxNetwork->WsStatus)
|
||||||
|
{
|
||||||
|
case EDTFluxConnectionStatus::Connected:
|
||||||
|
Color = FColor::Red;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Color = FColor::Green;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return FSlateColor(Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void SDTFluxStatusWidget::PopulateComboBoxItems()
|
||||||
|
{
|
||||||
|
ContestComboBoxItems.Empty();
|
||||||
|
|
||||||
|
if (DTFluxNetwork)
|
||||||
|
{
|
||||||
|
FString Separator = " | ";
|
||||||
|
FString RootSeparator = " -> ";
|
||||||
|
TArray<FDTFluxContest> DataFromSubsystem = TArray<FDTFluxContest>();
|
||||||
|
DTFluxCore->GetContests(DataFromSubsystem);
|
||||||
|
|
||||||
|
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 (DTFluxCore)
|
||||||
|
{
|
||||||
|
// 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"));
|
||||||
|
DTFluxCore->TrackedRequestContestRankings({ForContest});
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
if (ForSplit == -1)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Split not selected !!!! Requesting stage Ranking"));
|
||||||
|
FDTFluxStageKey StageKey = {ForContest, ForStage};
|
||||||
|
DTFluxCore->TrackedRequestStageRankings({StageKey});
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
FDTFluxSplitKey SplitKey = {ForContest, ForStage, ForSplit};
|
||||||
|
DTFluxCore->TrackedRequestSplitRankings({SplitKey});
|
||||||
|
UE_LOG(logDTFluxStatus, Warning, TEXT("Requesting %s Ranking"), *UEnum::GetValueAsString(RequestType));
|
||||||
|
}
|
||||||
|
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
||||||
@ -13,7 +13,7 @@ TSharedPtr<ISlateStyle> FDTFluxStatusStyle::StyleSet = nullptr;
|
|||||||
|
|
||||||
void FDTFluxStatusStyle::RegisterStyle()
|
void FDTFluxStatusStyle::RegisterStyle()
|
||||||
{
|
{
|
||||||
if(StyleSet.IsValid()) return;
|
if (StyleSet.IsValid()) return;
|
||||||
|
|
||||||
StyleSet = Create();
|
StyleSet = Create();
|
||||||
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
|
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
|
||||||
@ -21,14 +21,12 @@ void FDTFluxStatusStyle::RegisterStyle()
|
|||||||
|
|
||||||
void FDTFluxStatusStyle::UnregisterStyle()
|
void FDTFluxStatusStyle::UnregisterStyle()
|
||||||
{
|
{
|
||||||
if(StyleSet.IsValid())
|
if (StyleSet.IsValid())
|
||||||
{
|
{
|
||||||
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
|
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
|
||||||
ensure(StyleSet.IsUnique());
|
ensure(StyleSet.IsUnique());
|
||||||
StyleSet.Reset();
|
StyleSet.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxStatusStyle::ReloadTextures()
|
void FDTFluxStatusStyle::ReloadTextures()
|
||||||
@ -38,9 +36,8 @@ void FDTFluxStatusStyle::ReloadTextures()
|
|||||||
TSharedPtr<ISlateStyle> FDTFluxStatusStyle::Create()
|
TSharedPtr<ISlateStyle> FDTFluxStatusStyle::Create()
|
||||||
{
|
{
|
||||||
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxAPIStatusStyle"));
|
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxAPIStatusStyle"));
|
||||||
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir()/TEXT("Resources"));
|
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir() / TEXT("Resources"));
|
||||||
|
|
||||||
Style->Set("LevelEditor.Tab.Icon", new IMAGE_BRUSH_SVG("DTFluxServerStatusWhite", FVector2d(16)) );
|
Style->Set("LevelEditor.Tab.Icon", new IMAGE_BRUSH_SVG("DTFluxServerStatusWhite", FVector2d(16)));
|
||||||
return Style;
|
return Style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,10 +13,11 @@ public:
|
|||||||
|
|
||||||
#pragma region MenuExtention
|
#pragma region MenuExtention
|
||||||
void InitMenuExtension();
|
void InitMenuExtension();
|
||||||
void AddMenu(FMenuBarBuilder& MenuBarBuilder);
|
void RegisterMenus();
|
||||||
void FillMenu(FMenuBuilder& MenuBuilder);
|
void CreateSubmenu(UToolMenu* Menu);
|
||||||
|
// void AddMenu(FMenuBarBuilder& MenuBarBuilder);
|
||||||
|
// void FillMenu(FMenuBuilder& MenuBuilder);
|
||||||
void OnButtonClicked();
|
void OnButtonClicked();
|
||||||
// void OnWsEvent(TEnumAsByte<EDTFluxWsStatus> WsResponseEvent) const;
|
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region EditorTab
|
#pragma region EditorTab
|
||||||
@ -25,7 +26,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
static FName StatusTabId;
|
static FName StatusTabId;
|
||||||
static FText StatusTabDisplayName;
|
static FText StatusTabDisplayName;
|
||||||
|
|
||||||
TSharedPtr<class SDTFluxStatusWidget> StatusWidget;
|
TSharedPtr<class SDTFluxStatusWidget> StatusWidget;
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
};
|
};
|
||||||
|
|||||||
@ -10,8 +10,10 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class UDTFluxNetworkSubsystem;
|
class UDTFluxNetworkSubsystem;
|
||||||
|
class UDTFluxCoreSubsystem;
|
||||||
class SSuperListView;
|
class SSuperListView;
|
||||||
|
|
||||||
|
|
||||||
class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget
|
class DTFLUXAPISTATUS_API SDTFluxStatusWidget : public SCompoundWidget
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -29,8 +31,42 @@ public:
|
|||||||
TAttribute<FText> ConnectionActionButtonText;
|
TAttribute<FText> ConnectionActionButtonText;
|
||||||
FReply OnConnectionActionButtonClicked();
|
FReply OnConnectionActionButtonClicked();
|
||||||
|
|
||||||
|
enum class EComboBoxItemType
|
||||||
|
{
|
||||||
|
Contest,
|
||||||
|
Stage,
|
||||||
|
Split,
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FComboBoxItem
|
||||||
|
{
|
||||||
|
FString DisplayText;
|
||||||
|
int ContestId = -1;
|
||||||
|
int StageId = -1;
|
||||||
|
int SplitId = -1;
|
||||||
|
EComboBoxItemType Type = EComboBoxItemType::None;
|
||||||
|
|
||||||
|
|
||||||
|
FComboBoxItem(const EComboBoxItemType InType, const FString& InDisplayText, const int InContestId,
|
||||||
|
const int InStageId = -1, const int InSplitId = -1)
|
||||||
|
: DisplayText(InDisplayText), ContestId(InContestId), StageId(InStageId), SplitId(InSplitId), Type(InType)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static TSharedPtr<FComboBoxItem> CreateItem(const EComboBoxItemType& InType, const FString& InDisplayText,
|
||||||
|
const int InContestId, const int InStageId = -1,
|
||||||
|
const int InSplitId = -1)
|
||||||
|
{
|
||||||
|
TSharedPtr<FComboBoxItem> Item = MakeShareable(
|
||||||
|
new FComboBoxItem(InType, InDisplayText, InContestId, InStageId, InSplitId));
|
||||||
|
return Item;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UDTFluxNetworkSubsystem* DTFlux = nullptr;
|
UDTFluxNetworkSubsystem* DTFluxNetwork = nullptr;
|
||||||
|
UDTFluxCoreSubsystem* DTFluxCore = nullptr;
|
||||||
// // TODO make a struct
|
// // TODO make a struct
|
||||||
FText GetWebSocketStatusText() const;
|
FText GetWebSocketStatusText() const;
|
||||||
FText GetWebConnectActionButtonText() const;
|
FText GetWebConnectActionButtonText() const;
|
||||||
@ -39,6 +75,18 @@ private:
|
|||||||
TSharedPtr<STextBlock> WsStatusText;
|
TSharedPtr<STextBlock> WsStatusText;
|
||||||
TSharedPtr<SButton> ConnectionActionButton;
|
TSharedPtr<SButton> ConnectionActionButton;
|
||||||
|
|
||||||
|
TSharedPtr<SComboBox<TSharedPtr<FComboBoxItem>>> ContestComboBox;
|
||||||
|
TArray<TSharedPtr<FComboBoxItem>> ContestComboBoxItems;
|
||||||
|
TSharedPtr<FComboBoxItem> SelectedContestComboBoxItem;
|
||||||
|
|
||||||
|
|
||||||
|
// Méthodes pour le ComboBox
|
||||||
|
void PopulateComboBoxItems();
|
||||||
|
TSharedRef<SWidget> OnGeneRankingComboWidget(TSharedPtr<FComboBoxItem> InItem);
|
||||||
|
void OnComboContestRankingChanged(TSharedPtr<FComboBoxItem> NewSelection, ESelectInfo::Type SelectInfo);
|
||||||
|
FText GetCurrContestComboBoxValue() const;
|
||||||
|
|
||||||
|
static FSlateColor GetComboItemRankingColor(const TSharedPtr<FComboBoxItem> Item);
|
||||||
|
|
||||||
|
FReply OnRankingButtonClicked() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -9,7 +9,7 @@ public class DTFluxAssetsEditor : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -18,11 +18,20 @@ public class DTFluxAssetsEditor : ModuleRules
|
|||||||
{
|
{
|
||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
|
"SlateCore",
|
||||||
"Slate",
|
"Slate",
|
||||||
"AssetTools",
|
"AssetTools",
|
||||||
"SlateCore",
|
|
||||||
"UnrealEd",
|
"UnrealEd",
|
||||||
"DTFluxCore",
|
"DTFluxCore",
|
||||||
|
"ToolMenus",
|
||||||
|
"EditorWidgets",
|
||||||
|
"EditorStyle",
|
||||||
|
"PropertyEditor",
|
||||||
|
"SharedSettingsWidgets",
|
||||||
|
"PropertyEditor",
|
||||||
|
"DesktopWidgets",
|
||||||
|
"ApplicationCore",
|
||||||
|
"InputCore"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,26 +3,58 @@
|
|||||||
#include "DTFluxAssetModelTypeActions.h"
|
#include "DTFluxAssetModelTypeActions.h"
|
||||||
#include "IAssetTools.h"
|
#include "IAssetTools.h"
|
||||||
#include "AssetToolsModule.h"
|
#include "AssetToolsModule.h"
|
||||||
|
#include "DTFluxModelAssetDetailCustomization.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
|
#define LOCTEXT_NAMESPACE "FDTFluxAssetsEditorModule"
|
||||||
|
|
||||||
|
|
||||||
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
|
DTFLUXASSETSEDITOR_API DEFINE_LOG_CATEGORY(logDTFluxAssetEditor)
|
||||||
|
|
||||||
void FDTFluxAssetsEditorModule::StartupModule()
|
void FDTFluxAssetsEditorModule::StartupModule()
|
||||||
{
|
{
|
||||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
|
EAssetTypeCategories::Type Category = AssetToolsModule.RegisterAdvancedAssetCategory("DTFlux", INVTEXT("DTFlux"));
|
||||||
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
DTFluxAssetModelActions = MakeShareable(new FDTFluxAssetModelTypeActions(Category));
|
||||||
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
AssetToolsModule.RegisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
||||||
|
RegisterCustomizations();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxAssetsEditorModule::ShutdownModule()
|
void FDTFluxAssetsEditorModule::ShutdownModule()
|
||||||
{
|
{
|
||||||
if(DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
if (DTFluxAssetModelActions.IsValid() && FModuleManager::Get().IsModuleLoaded("AssetTools"))
|
||||||
{
|
{
|
||||||
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
IAssetTools& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||||||
AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
AssetToolsModule.UnregisterAssetTypeActions(DTFluxAssetModelActions.ToSharedRef());
|
||||||
}
|
}
|
||||||
|
UnregisterCustomizations();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FDTFluxAssetsEditorModule::RegisterCustomizations()
|
||||||
|
{
|
||||||
|
// Enregistrer la customization pour DTFluxModelAsset
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||||
|
|
||||||
|
PropertyModule.RegisterCustomClassLayout(
|
||||||
|
UDTFluxModelAsset::StaticClass()->GetFName(),
|
||||||
|
FOnGetDetailCustomizationInstance::CreateStatic(&FDTFluxModelAssetCustomization::MakeInstance)
|
||||||
|
);
|
||||||
|
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAssetsEditorModule::UnregisterCustomizations()
|
||||||
|
{
|
||||||
|
if (FModuleManager::Get().IsModuleLoaded("PropertyEditor"))
|
||||||
|
{
|
||||||
|
FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>(
|
||||||
|
"PropertyEditor");
|
||||||
|
|
||||||
|
PropertyModule.UnregisterCustomClassLayout(UDTFluxModelAsset::StaticClass()->GetFName());
|
||||||
|
|
||||||
|
PropertyModule.NotifyCustomizationModuleChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef LOCTEXT_NAMESPACE
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|||||||
@ -0,0 +1,236 @@
|
|||||||
|
// 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)
|
||||||
|
{
|
||||||
|
// Edit object
|
||||||
|
TArray<TWeakObjectPtr<UObject>> ObjectsBeingCustomized;
|
||||||
|
DetailBuilder.GetObjectsBeingCustomized(ObjectsBeingCustomized);
|
||||||
|
|
||||||
|
if (ObjectsBeingCustomized.Num() > 0)
|
||||||
|
{
|
||||||
|
ModelAsset = Cast<UDTFluxModelAsset>(ObjectsBeingCustomized[0].Get());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ModelAsset.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Error, TEXT("No valid DTFluxModelAsset found"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ===== WIDGET PRINCIPAL =====
|
||||||
|
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,640 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
#include "Widget/DTFluxAssetModelDetailsWidget.h"
|
||||||
|
|
||||||
|
#include "DTFluxAssetsEditorModule.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(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString());
|
||||||
|
return SNew(STextBlock).Text(FText::FromString("Invalid Item"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ParentWidget)
|
||||||
|
{
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"),
|
||||||
|
// *ColumnName.ToString());
|
||||||
|
return SNew(STextBlock).Text(FText::FromString("Invalid Parent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, 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(logDTFluxAssetEditor, 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(logDTFluxAssetEditor, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num());
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
|
||||||
|
{
|
||||||
|
ParticipantItems.Empty();
|
||||||
|
|
||||||
|
if (!ModelAsset)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("BuildParticipantList: ModelAsset is null!"));
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, 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(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"),
|
||||||
|
// *ParticipantItem->Name, ParticipantItem->Bib);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, 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(logDTFluxAssetEditor, Warning, TEXT("OnGenerateRowForTree: Invalid item!"));
|
||||||
|
return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, 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(logDTFluxAssetEditor, Log, TEXT("Expanded all contests"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked()
|
||||||
|
{
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
for (auto& RootItem : RootItems)
|
||||||
|
{
|
||||||
|
OnSetExpansionRecursive(RootItem, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Collapsed all contests"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked()
|
||||||
|
{
|
||||||
|
RefreshData();
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("Data refreshed"));
|
||||||
|
return FReply::Handled();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== REFRESHDATA =====
|
||||||
|
|
||||||
|
void SDTFluxAssetModelDetailsWidget::RefreshData()
|
||||||
|
{
|
||||||
|
if (!ModelAsset)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxAssetEditor, Warning, TEXT("ModelAsset is null!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName());
|
||||||
|
RootItems.Empty();
|
||||||
|
ParticipantItems.Empty();
|
||||||
|
|
||||||
|
BuildContestHierarchy();
|
||||||
|
BuildParticipantList();
|
||||||
|
|
||||||
|
if (ContestTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ContestTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ParticipantTreeView.IsValid())
|
||||||
|
{
|
||||||
|
ParticipantTreeView->RequestTreeRefresh();
|
||||||
|
}
|
||||||
|
// UE_LOG(logDTFluxAssetEditor, 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()
|
||||||
|
));
|
||||||
|
}
|
||||||
@ -26,4 +26,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
TSharedPtr<FDTFluxAssetModelTypeActions> DTFluxAssetModelActions;
|
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,184 @@
|
|||||||
|
// 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("rankings"));
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ModelAsset TreeviewWidget
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
|
||||||
|
FSlateColor GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const;
|
||||||
|
const FSlateBrush* GetItemIcon(FHierarchicalTreeItem::EItemType Type) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
UDTFluxModelAsset* ModelAsset = nullptr;
|
||||||
|
TArray<FHierarchicalTreeItemPtr> RootItems;
|
||||||
|
TArray<FHierarchicalTreeItemPtr> ParticipantItems;
|
||||||
|
|
||||||
|
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ContestTreeView;
|
||||||
|
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ParticipantTreeView;
|
||||||
|
TSharedPtr<STextBlock> StatsText;
|
||||||
|
TSharedPtr<STextBlock> SelectionText;
|
||||||
|
|
||||||
|
void BuildContestHierarchy();
|
||||||
|
void BuildParticipantList();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
FReply OnRefreshClicked();
|
||||||
|
FReply OnExpandAllClicked();
|
||||||
|
FReply OnCollapseAllClicked();
|
||||||
|
|
||||||
|
EActiveTimerReturnType ForceInitialLayout(double InCurrentTime, float InDeltaTime);
|
||||||
|
|
||||||
|
FText GetStatsText() const;
|
||||||
|
};
|
||||||
@ -10,21 +10,45 @@ UDTFluxModelAsset::UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void UDTFluxModelAsset::AddContest(const FDTFluxContest &Contest)
|
void UDTFluxModelAsset::AddContest(const FDTFluxContest& Contest)
|
||||||
{
|
{
|
||||||
Contests.Add(Contest.Name, Contest);
|
Contests.Add(Contest.Name, Contest);
|
||||||
|
// initialisation
|
||||||
|
for (const auto& Stage : Contest.Stages)
|
||||||
|
{
|
||||||
|
FinishedStagesCache.Add(FDTFluxStageKey(Contest.ContestId, Stage.StageId), Stage.IsFinished());
|
||||||
|
}
|
||||||
|
TArray<FDTFluxSplit> Splits = Contest.Splits;
|
||||||
|
Splits.Sort([](const FDTFluxSplit& A, const FDTFluxSplit& B)
|
||||||
|
{
|
||||||
|
return A.SplitId < B.SplitId;
|
||||||
|
});
|
||||||
|
// last and Penultimate split cache for contest
|
||||||
|
LastSplitIdCache.Add(Contest.ContestId, Splits.Pop().SplitId);
|
||||||
|
PenultimateSplitIdCache.Add(Contest.ContestId, Splits.Pop().SplitId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest)
|
bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest)
|
||||||
{
|
{
|
||||||
for(auto& ContestItem : Contests)
|
for (auto& ContestItem : Contests)
|
||||||
{
|
{
|
||||||
if(ContestItem.Value.ContestId == InContestId)
|
if (ContestItem.Value.ContestId == InContestId)
|
||||||
{
|
{
|
||||||
OutContest = ContestItem.Value;
|
OutContest = ContestItem.Value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxModelAsset::GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage)
|
||||||
|
{
|
||||||
|
FDTFluxContest TargetContest;
|
||||||
|
int TargetStageId = StageKey.StageId;
|
||||||
|
if (GetContestById(StageKey.ContestId, TargetContest))
|
||||||
|
{
|
||||||
|
return TargetContest.GetStage(TargetStageId, OutStage);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -36,16 +60,17 @@ void UDTFluxModelAsset::AddPerson(const FDTFluxPerson& InPerson)
|
|||||||
|
|
||||||
void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId)
|
void UDTFluxModelAsset::AddParticipant(const FDTFluxParticipant& InParticipant, const int ContestId)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("%i Person in Participant %i"), InParticipant.GetTeammateNum(), InParticipant.Bib);
|
UE_LOG(logDTFluxCore, Error, TEXT("%i Person in Participant %i"), InParticipant.GetTeammateNum(),
|
||||||
|
InParticipant.Bib);
|
||||||
FDTFluxContest TargetContest;
|
FDTFluxContest TargetContest;
|
||||||
if(GetContestById(ContestId, TargetContest))
|
if (GetContestById(ContestId, TargetContest))
|
||||||
{
|
{
|
||||||
TArray<FDTFluxPerson> Teammate = InParticipant.Teammate;
|
TArray<FDTFluxPerson> Teammate = InParticipant.Teammate;
|
||||||
for(auto& Person : InParticipant.Teammate)
|
for (auto& Person : InParticipant.Teammate)
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s"),
|
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s"),
|
||||||
*Person.FirstName, *Person.LastName, *Person.Gender);
|
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
if(!PersonExists(Person))
|
if (!PersonExists(Person))
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s doesnot exists, adding..."),
|
UE_LOG(logDTFluxCore, Error, TEXT("AddParticipant() DTFlux Person %s %s %s doesnot exists, adding..."),
|
||||||
*Person.FirstName, *Person.LastName, *Person.Gender);
|
*Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
@ -68,9 +93,10 @@ bool UDTFluxModelAsset::PersonExists(const FDTFluxPerson& InPerson) const
|
|||||||
FString UDTFluxModelAsset::GetContestNameForId(const int InContestID)
|
FString UDTFluxModelAsset::GetContestNameForId(const int InContestID)
|
||||||
{
|
{
|
||||||
FDTFluxContest Contest;
|
FDTFluxContest Contest;
|
||||||
if(!GetContestById(InContestID, Contest))
|
if (!GetContestById(InContestID, Contest))
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Warning, TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"),
|
UE_LOG(logDTFluxCore, Warning,
|
||||||
|
TEXT("GetContestNameForId(%i) [unable to find a contest] result will be empty !!!"),
|
||||||
InContestID);
|
InContestID);
|
||||||
}
|
}
|
||||||
return Contest.Name;
|
return Contest.Name;
|
||||||
@ -81,6 +107,78 @@ void UDTFluxModelAsset::AddContestRanking(const FDTFluxContestRankings& NewConte
|
|||||||
ContestRankings.Add(NewContestRankings.ContestId, NewContestRankings);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxModelAsset::IsStageFinished(FDTFluxStageKey StageKey)
|
||||||
|
{
|
||||||
|
if (!FinishedStagesCache.Contains(StageKey))
|
||||||
|
{
|
||||||
|
if (FinishedStagesCache[StageKey])
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//maybe stage is finished because we have not be able to set it ?
|
||||||
|
return CheckStageIsFinished(StageKey);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxModelAsset::CacheSplitSensorInfo(const FDTFluxSplitSensorKey SplitSensorKey,
|
||||||
|
const FDTFluxSplitSensorInfo& SplitSensorInfo)
|
||||||
|
{
|
||||||
|
SplitSensorInfoCache.Add(SplitSensorKey, SplitSensorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool UDTFluxModelAsset::CheckStageIsFinished(FDTFluxStageKey StageKey)
|
||||||
|
{
|
||||||
|
FDTFluxStage Stage;
|
||||||
|
if (GetStage(StageKey, Stage))
|
||||||
|
{
|
||||||
|
FinishedStagesCache.Add(StageKey, Stage.IsFinished());
|
||||||
|
return FinishedStagesCache[StageKey];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings)
|
void UDTFluxModelAsset::UpdateOrCreateStageRanking(const FDTFluxStageRankings& InStageRankings)
|
||||||
{
|
{
|
||||||
FDTFluxStageKey StageKey = InStageRankings.GetCompositeKey();
|
FDTFluxStageKey StageKey = InStageRankings.GetCompositeKey();
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "Types/Objects/DTFluxPursuitManager.h"
|
|
||||||
|
|
||||||
void UDTFluxPursuitManager::InitForStage(const FDTFluxStageRankings& StageRankings)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<FDTFluxPursuit> UDTFluxPursuitManager::GetNextPursuits(int MaxPursuit)
|
|
||||||
{
|
|
||||||
//TODO : Implement me !!!
|
|
||||||
return PursuitParticipants;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<FDTFluxPursuit> UDTFluxPursuitManager::GetPursuits(int FromIndex, int MaxPursuit)
|
|
||||||
{
|
|
||||||
//TODO : Implement me !!!
|
|
||||||
return PursuitParticipants;
|
|
||||||
}
|
|
||||||
|
|
||||||
FDateTime UDTFluxPursuitManager::GetMassStart()
|
|
||||||
{
|
|
||||||
//TODO : Implement me !!!
|
|
||||||
return MassStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
FText UDTFluxPursuitManager::GetFormattedName(FDTFluxPursuit& InPursuit, const int MaxChar,
|
|
||||||
const FString OverflowChar)
|
|
||||||
{
|
|
||||||
return InPursuit.GetFormattedName(MaxChar, OverflowChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
FText UDTFluxPursuitManager::DisplayPursuit(FDTFluxPursuit& InPursuit, const int MaxWidth,
|
|
||||||
const FString NameOverflowChar)
|
|
||||||
{
|
|
||||||
return InPursuit.DisplayPursuit(MaxWidth, NameOverflowChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UDTFluxPursuitManager::IsUnique(const FDTFluxPursuit& InPursuit)
|
|
||||||
{
|
|
||||||
return InPursuit.IsUnique();
|
|
||||||
}
|
|
||||||
@ -18,8 +18,9 @@ bool UDTFluxParticipantFactory::CreateParticipantFomJson(const FString& JsonStri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UDTFluxParticipantFactory::CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject, FDTFluxParticipant& OutParticipant)
|
bool UDTFluxParticipantFactory::CreateFromJsonCpp(const TSharedPtr<FJsonObject> JsonObject,
|
||||||
|
FDTFluxParticipant& OutParticipant)
|
||||||
{
|
{
|
||||||
OutParticipant = FDTFluxParticipant::CreateFromJson(JsonObject);
|
OutParticipant = FDTFluxParticipant::CreateFromJson(JsonObject);
|
||||||
return OutParticipant == 0;
|
return !OutParticipant.IsDefault();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "Types/Struct/DTFluxPursuitStructs.h"
|
|
||||||
|
|
||||||
|
|
||||||
FDTFluxPursuit::FDTFluxPursuit()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FDTFluxPursuit::~FDTFluxPursuit()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
FText FDTFluxPursuit::GetFormattedName(const int MaxChar, const FString OverflowChar)
|
|
||||||
{
|
|
||||||
//TODO: Implement Me !!!
|
|
||||||
return Participants[0].GetConcatFormattedName(MaxChar, OverflowChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
FText FDTFluxPursuit::DisplayPursuit(const int MaxWidth, const FString NameOverflowChar)
|
|
||||||
{
|
|
||||||
//TODO: Implement Me !!!
|
|
||||||
return Participants[0].GetConcatFormattedName(MaxWidth, NameOverflowChar);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FDTFluxPursuit::IsUnique() const
|
|
||||||
{
|
|
||||||
return Participants.Num() == 1;
|
|
||||||
}
|
|
||||||
@ -1,3 +1,69 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
|
||||||
|
bool FDTFluxStage::IsFinished() const
|
||||||
|
{
|
||||||
|
return EndTime <= FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxContest::IsFinished() const
|
||||||
|
{
|
||||||
|
return EndTime <= FDateTime::Now();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxContest::UpdateEndTime()
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.EndTime < B.EndTime;
|
||||||
|
});
|
||||||
|
EndTime = TempStages.Last().EndTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FDTFluxContest::GetLastStageId()
|
||||||
|
{
|
||||||
|
if (LastStageId <= 0)
|
||||||
|
{
|
||||||
|
UpdateLastStageId();
|
||||||
|
}
|
||||||
|
return LastStageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxContest::UpdateLastStageId()
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.StageId < B.StageId;
|
||||||
|
});
|
||||||
|
LastStageId = TempStages.Last().StageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxStage& FDTFluxContest::GetLastStage() const
|
||||||
|
{
|
||||||
|
TArray<FDTFluxStage> TempStages = Stages;
|
||||||
|
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
|
||||||
|
{
|
||||||
|
return A.StageId < B.StageId;
|
||||||
|
});
|
||||||
|
return TempStages.Last();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxContest::GetStage(const int StageID, FDTFluxStage& OutStage) const
|
||||||
|
{
|
||||||
|
if (Stages.Num() == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const FDTFluxStage& Stage : Stages)
|
||||||
|
{
|
||||||
|
if (Stage.StageId == StageID)
|
||||||
|
{
|
||||||
|
OutStage = Stage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
@ -3,27 +3,15 @@
|
|||||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
#include "DTFluxCoreModule.h"
|
#include "DTFluxCoreModule.h"
|
||||||
|
|
||||||
|
bool FDTFluxBaseRankings::IsSealed(const FDateTime EndTime) const
|
||||||
|
|
||||||
|
{
|
||||||
|
return ReceivedAt >= EndTime;
|
||||||
|
}
|
||||||
|
|
||||||
void FDTFluxContestRanking::Dump() const
|
void FDTFluxContestRanking::Dump() const
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Log,
|
UE_LOG(logDTFluxCore, Log,
|
||||||
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
TEXT("FDTFluxContestRanking ->> \n \"rank\" : %d, Participant with Bib %d \"Gap\" : %s, \"Time\" : %s "),
|
||||||
Rank, Bib, *Gap, *Time );
|
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);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|||||||
@ -1,99 +1,56 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
|
#include "DTFluxCoreModule.h"
|
||||||
|
#include "Dom/JsonObject.h"
|
||||||
|
|
||||||
|
// ===================================
|
||||||
|
// FDTFluxPerson Implementation
|
||||||
|
// ===================================
|
||||||
|
|
||||||
|
bool FDTFluxPerson::operator==(const FDTFluxPerson& Right) const
|
||||||
|
|
||||||
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
|
|
||||||
{
|
{
|
||||||
Teammate.Add(Person);
|
return GetNormalizedString() == Right.GetNormalizedString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FDTFluxParticipant::AddTeammate(const FString LastName, const FString FirstName, const FString Gender)
|
bool FDTFluxPerson::operator!=(const FDTFluxPerson& Right) const
|
||||||
{
|
{
|
||||||
|
return !(*this == Right);
|
||||||
}
|
}
|
||||||
|
|
||||||
FText FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars)
|
bool FDTFluxPerson::operator==(const int Length) const
|
||||||
{
|
{
|
||||||
// Vérifie les cas limites
|
return GetNormalizedString().Len() == Length;
|
||||||
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)
|
|
||||||
{
|
|
||||||
// Pas assez de place pour le nom → juste l'initiale ?
|
|
||||||
return FText::FromString(Initial);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FText::FromString(FullName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FText FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar)
|
bool FDTFluxPerson::operator!=(const int Length) const
|
||||||
{
|
{
|
||||||
FString BibText = FString::FromInt(Bib) + " ";
|
return !(*this == Length);
|
||||||
FText FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar );
|
}
|
||||||
return FText::FromString(BibText + FormattedName.ToString());
|
|
||||||
|
FString FDTFluxPerson::GetNormalizedString() const
|
||||||
|
{
|
||||||
|
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxPerson::IsValid() const
|
||||||
|
{
|
||||||
|
return !FirstName.TrimStartAndEnd().IsEmpty() &&
|
||||||
|
!LastName.TrimStartAndEnd().IsEmpty() &&
|
||||||
|
!Gender.TrimStartAndEnd().IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxParticipant::FDTFluxParticipant()
|
||||||
|
: Bib(-1)
|
||||||
|
, ContestId(-1)
|
||||||
|
, Elite(false)
|
||||||
|
, Status(static_cast<EDTFluxParticipantStatusType>(0))
|
||||||
|
, bIsMassStartParticipant(false)
|
||||||
|
, CurrentSplit(-1)
|
||||||
|
{
|
||||||
|
Teammate.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constructeur privé depuis JSON
|
|
||||||
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
|
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
|
||||||
: Bib(JsonObject->GetIntegerField(TEXT("bib")))
|
: Bib(JsonObject->GetIntegerField(TEXT("bib")))
|
||||||
, ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
|
, ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
|
||||||
@ -102,44 +59,88 @@ FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject
|
|||||||
, Elite(JsonObject->GetBoolField(TEXT("elite")))
|
, Elite(JsonObject->GetBoolField(TEXT("elite")))
|
||||||
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
|
, Status(static_cast<EDTFluxParticipantStatusType>(JsonObject->GetIntegerField(TEXT("status"))))
|
||||||
, Team(JsonObject->GetStringField(TEXT("team")))
|
, Team(JsonObject->GetStringField(TEXT("team")))
|
||||||
, bIsMassStartParticipant(false)
|
, CurrentSplit(-1)
|
||||||
, LastSplitId(-1)
|
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object"))
|
UE_LOG(logDTFluxCore, Log, TEXT("Creating participant from JSON - Bib: %d, Contest: %d"), Bib, ContestId);
|
||||||
for(uint8 Index = 1; ; Index++)
|
|
||||||
|
for (uint8 Index = 1; Index <= 10; Index++)
|
||||||
{
|
{
|
||||||
FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index);
|
FString FirstNameKey = Index == 1 ? TEXT("firstName") : FString::Printf(TEXT("firstName%d"), Index);
|
||||||
FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index);
|
FString LastNameKey = Index == 1 ? TEXT("lastName") : FString::Printf(TEXT("lastName%d"), Index);
|
||||||
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index);
|
FString GenderKey = Index == 1 ? TEXT("gender") : FString::Printf(TEXT("gender%d"), Index);
|
||||||
// max 10 Persons
|
|
||||||
if(Index >= 10)
|
// Vérifie si au moins un des champs existe
|
||||||
|
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey) && !JsonObject->
|
||||||
|
HasField(GenderKey))
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
|
|
||||||
&& !JsonObject->HasField(GenderKey))
|
|
||||||
{
|
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const FString FirstName = JsonObject->GetStringField(FirstNameKey);
|
const FString FirstName = JsonObject->GetStringField(FirstNameKey);
|
||||||
const FString LastName = JsonObject->GetStringField(LastNameKey);
|
const FString LastName = JsonObject->GetStringField(LastNameKey);
|
||||||
const FString Gender = JsonObject->GetStringField(GenderKey);
|
const FString Gender = JsonObject->GetStringField(GenderKey);
|
||||||
if (FirstName.IsEmpty() && LastName.IsEmpty())
|
|
||||||
continue;
|
|
||||||
FDTFluxPerson Person;
|
|
||||||
Person.FirstName = FirstName;
|
|
||||||
Person.LastName = LastName;
|
|
||||||
Person.Gender = Gender;
|
|
||||||
Teammate.Add(Person);
|
|
||||||
}
|
|
||||||
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object Teammate is now %i long"), Teammate.Num());
|
|
||||||
|
|
||||||
|
if (FirstName.TrimStartAndEnd().IsEmpty() && LastName.TrimStartAndEnd().IsEmpty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxPerson Person;
|
||||||
|
Person.FirstName = FirstName.TrimStartAndEnd();
|
||||||
|
Person.LastName = LastName.TrimStartAndEnd();
|
||||||
|
Person.Gender = Gender.TrimStartAndEnd();
|
||||||
|
|
||||||
|
if (Person.IsValid())
|
||||||
|
{
|
||||||
|
Teammate.Add(Person);
|
||||||
|
UE_LOG(logDTFluxCore, Verbose, TEXT("Added person %d: %s %s (%s)"),
|
||||||
|
Index, *Person.FirstName, *Person.LastName, *Person.Gender);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Warning, TEXT("Invalid person data at index %d: '%s' '%s' '%s'"),
|
||||||
|
Index, *FirstName, *LastName, *Gender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCore, Log, TEXT("Participant created with %d teammates"), Teammate.Num());
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
|
bool FDTFluxParticipant::IsDefault() const
|
||||||
{
|
{
|
||||||
return FDTFluxParticipant(JsonObject);
|
return Bib == -1
|
||||||
|
&& ContestId == -1
|
||||||
|
&& Category.IsEmpty()
|
||||||
|
&& Club.IsEmpty()
|
||||||
|
&& !Elite
|
||||||
|
&& Status == static_cast<EDTFluxParticipantStatusType>(0)
|
||||||
|
&& Team.IsEmpty()
|
||||||
|
&& !bIsMassStartParticipant
|
||||||
|
&& CurrentSplit == -1
|
||||||
|
&& Teammate.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person)
|
||||||
|
{
|
||||||
|
if (Person.IsValid())
|
||||||
|
{
|
||||||
|
Teammate.Add(Person);
|
||||||
|
UE_LOG(logDTFluxCore, Verbose, TEXT("Added teammate: %s %s"), *Person.FirstName, *Person.LastName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Warning, TEXT("Cannot add invalid teammate: %s %s"), *Person.FirstName, *Person.LastName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxParticipant::AddTeammate(const FString& LastName, const FString& FirstName, const FString& Gender)
|
||||||
|
{
|
||||||
|
FDTFluxPerson Person;
|
||||||
|
Person.FirstName = FirstName.TrimStartAndEnd();
|
||||||
|
Person.LastName = LastName.TrimStartAndEnd();
|
||||||
|
Person.Gender = Gender.TrimStartAndEnd();
|
||||||
|
|
||||||
|
AddTeammate(Person);
|
||||||
}
|
}
|
||||||
|
|
||||||
int FDTFluxParticipant::GetTeammateNum() const
|
int FDTFluxParticipant::GetTeammateNum() const
|
||||||
@ -147,7 +148,136 @@ int FDTFluxParticipant::GetTeammateNum() const
|
|||||||
return Teammate.Num();
|
return Teammate.Num();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FDTFluxParticipant::IsTeam()
|
bool FDTFluxParticipant::IsTeam() const
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Verbose, TEXT("IsTeam() -> %s, Teamate Num %i"),
|
||||||
|
Team.IsEmpty() ? TEXT("TRUE") : TEXT("FALSE"), Teammate.Num());
|
||||||
|
return !Team.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TArray<FDTFluxPerson>& FDTFluxParticipant::GetTeammate() const
|
||||||
|
{
|
||||||
|
return Teammate;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString& Separator,
|
||||||
|
const FString& OverflowChar) const
|
||||||
|
{
|
||||||
|
if (MaxChar <= 0)
|
||||||
|
{
|
||||||
|
return TEXT("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Teammate.Num() == 0)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (IsTeam())
|
||||||
|
{
|
||||||
|
FString FullName;
|
||||||
|
UE_LOG(logDTFluxCore, Verbose, TEXT("Participant is a team"));
|
||||||
|
if (!Team.IsEmpty())
|
||||||
|
{
|
||||||
|
FullName = Team;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TArray<FString> Names;
|
||||||
|
for (const FDTFluxPerson& Person : Teammate)
|
||||||
|
{
|
||||||
|
Names.Add(Person.LastName);
|
||||||
|
}
|
||||||
|
FullName = FString::Join(Names, TEXT("/"));
|
||||||
|
}
|
||||||
|
FullName = FullName.ToUpper();
|
||||||
|
if (FullName.Len() <= MaxChar)
|
||||||
|
{
|
||||||
|
return FullName;
|
||||||
|
}
|
||||||
|
return FullName.Left(MaxChar) + OverflowChar;
|
||||||
|
}
|
||||||
|
FString FirstName = Teammate[0].FirstName;
|
||||||
|
FString LastName = Teammate[0].LastName;
|
||||||
|
FString Initial;
|
||||||
|
if (!FirstName.IsEmpty())
|
||||||
|
{
|
||||||
|
Initial = FirstName.Left(1).ToUpper() + Separator;
|
||||||
|
}
|
||||||
|
const FString FormattedLastName = LastName.ToUpper();
|
||||||
|
FString FullName = Initial + FormattedLastName;
|
||||||
|
if (FullName.Len() <= MaxChar)
|
||||||
|
{
|
||||||
|
return FullName;
|
||||||
|
}
|
||||||
|
return FullName.Left(MaxChar) + OverflowChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString& Separator,
|
||||||
|
const FString& OverflowChar, const FString& BibSeparator) const
|
||||||
|
{
|
||||||
|
FString BibText = FString::FromInt(Bib) + BibSeparator;
|
||||||
|
int32 RemainingChars = MaxChar - BibText.Len();
|
||||||
|
|
||||||
|
if (RemainingChars <= 0)
|
||||||
|
{
|
||||||
|
return BibText.Left(MaxChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FormattedName = GetFormattedName(RemainingChars, Separator, OverflowChar);
|
||||||
|
return BibText + FormattedName;
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FDTFluxParticipant::GetFormattedNameText(const int MaxChar, const FString& Separator,
|
||||||
|
const FString& OverflowChar) const
|
||||||
|
{
|
||||||
|
return FText::FromString(GetFormattedName(MaxChar, Separator, OverflowChar));
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FDTFluxParticipant::GetConcatFormattedNameText(const int MaxChar, const FString& Separator,
|
||||||
|
const FString& OverflowChar, const FString& BibSeparator) const
|
||||||
|
{
|
||||||
|
return FText::FromString(GetConcatFormattedName(MaxChar, Separator, OverflowChar, BibSeparator));
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxParticipant::GetFormattedName(const FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString& Separator, const FString& OverflowChar)
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedName(MaxChar, Separator, OverflowChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxParticipant::GetConcatFormattedName(const FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString& Separator, const FString& OverflowChar,
|
||||||
|
const FString& BibSeparator)
|
||||||
|
{
|
||||||
|
return Participant.GetConcatFormattedName(MaxChar, Separator, OverflowChar, BibSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FDTFluxParticipant::GetFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString& Separator, const FString& OverflowChar)
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedNameText(MaxChar, Separator, OverflowChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText FDTFluxParticipant::GetConcatFormattedNameText(const FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString& Separator, const FString& OverflowChar,
|
||||||
|
const FString& BibSeparator)
|
||||||
|
{
|
||||||
|
return Participant.GetConcatFormattedNameText(MaxChar, Separator, OverflowChar, BibSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxParticipant FDTFluxParticipant::CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject)
|
||||||
|
{
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCore, Error, TEXT("Cannot create participant from invalid JSON object"));
|
||||||
|
return FDTFluxParticipant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FDTFluxParticipant(JsonObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxTeamStatusUpdate::FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
||||||
|
: Bib(InBib)
|
||||||
|
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus))
|
||||||
{
|
{
|
||||||
return Teammate.Num() < 1;
|
|
||||||
}
|
}
|
||||||
@ -1,9 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
|
|
||||||
#include "Types/Struct/FDTFluxPoursuiteStruct.h"
|
|
||||||
|
|
||||||
FText FDTFluxPoursuite::GetParticipantFormatedName() const
|
|
||||||
{
|
|
||||||
return FText();
|
|
||||||
}
|
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||||
@ -7,6 +7,7 @@
|
|||||||
#include "Dom/JsonObject.h"
|
#include "Dom/JsonObject.h"
|
||||||
#include "Types/Struct/DTFluxCompositeKey.h"
|
#include "Types/Struct/DTFluxCompositeKey.h"
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxModelAsset.generated.h"
|
#include "DTFluxModelAsset.generated.h"
|
||||||
|
|
||||||
|
|
||||||
@ -19,8 +20,8 @@ class DTFLUXCORE_API UDTFluxModelAsset : public UObject
|
|||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
|
UDTFluxModelAsset(const FObjectInitializer& ObjectInitializer);
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, EditAnywhere)
|
||||||
FString EventName = "MyEvent";
|
FString EventName = "MyEvent";
|
||||||
|
|
||||||
@ -34,21 +35,34 @@ public:
|
|||||||
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
TMap<FString /* ContestName */, FDTFluxContest> Contests;
|
TMap<FString /* ContestName */, FDTFluxContest> Contests;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
TMap<int /*ContestId*/, FDTFluxContestRankings> ContestRankings;
|
TMap<int /*ContestId*/, FDTFluxContestRankings> ContestRankings;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
TMap<FDTFluxStageKey, FDTFluxStageRankings> StageRankings;
|
TMap<FDTFluxStageKey, FDTFluxStageRankings> StageRankings;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
|
TMap<FDTFluxSplitKey, FDTFluxSplitRankings> SplitRankings;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
|
TMap<FDTFluxSplitSensorKey, FDTFluxSplitSensorInfo> SplitSensorInfoCache;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
|
TMap<int /*ContestId*/, int /*SplitId*/> LastSplitIdCache;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
|
TMap<int/*ContestId*/, int /*Penultimate*/>PenultimateSplitIdCache;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
|
||||||
void AddContest(const FDTFluxContest &Contest);
|
void AddContest(const FDTFluxContest& Contest);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
||||||
bool GetContestById(const int InContestId, FDTFluxContest& OutContest);
|
bool GetContestById(const int InContestId, FDTFluxContest& OutContest);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Contest")
|
||||||
|
bool GetStage(FDTFluxStageKey StageKey, FDTFluxStage& OutStage);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
|
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Person")
|
||||||
void AddPerson(const FDTFluxPerson& InPerson);
|
void AddPerson(const FDTFluxPerson& InPerson);
|
||||||
|
|
||||||
@ -75,4 +89,24 @@ public:
|
|||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void AddContestRanking(const FDTFluxContestRankings& NewContestRankings);
|
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);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
bool IsStageFinished(FDTFluxStageKey StageKey);
|
||||||
|
UFUNCTION()
|
||||||
|
void CacheSplitSensorInfo(const FDTFluxSplitSensorKey SplitSensorKey, const FDTFluxSplitSensorInfo& SplitSensorInfo);
|
||||||
|
|
||||||
|
private:
|
||||||
|
UPROPERTY()
|
||||||
|
TMap<FDTFluxStageKey, bool /*bIsFinished*/> FinishedStagesCache;
|
||||||
|
UFUNCTION()
|
||||||
|
bool CheckStageIsFinished(FDTFluxStageKey StageKey);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,16 +7,61 @@
|
|||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
UENUM(BlueprintType)
|
||||||
enum class EDTFluxRequestType : uint8
|
enum class EDTFluxApiDataType : uint8
|
||||||
{
|
{
|
||||||
None = 0 UMETA(DisplayName="None"),
|
None = 0 UMETA(DisplayName="None"),
|
||||||
|
|
||||||
|
// Types bidirectionnels (requête/réponse)
|
||||||
ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
||||||
StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
||||||
SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
||||||
TeamList = 4 UMETA(DisplayName="team-list"),
|
TeamList = 4 UMETA(DisplayName="team-list"),
|
||||||
RaceData = 5 UMETA(DisplayName="race-data"),
|
RaceData = 5 UMETA(DisplayName="race-data"),
|
||||||
|
|
||||||
|
// Types uniquement réponse (push du serveur)
|
||||||
|
TeamUpdate = 10 UMETA(DisplayName="team-update"),
|
||||||
|
StatusUpdate = 11 UMETA(DisplayName="status-update"),
|
||||||
|
SplitSensor = 12 UMETA(DisplayName="split-sensor"),
|
||||||
|
|
||||||
|
// Types système
|
||||||
|
Error = 99 UMETA(DisplayName="error"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Alias pour clarifier l'usage
|
||||||
|
using EDTFluxRequestType = EDTFluxApiDataType;
|
||||||
|
using EDTFluxResponseType = EDTFluxApiDataType;
|
||||||
|
|
||||||
|
// Utilitaires pour valider les usages
|
||||||
|
namespace DTFluxDataTypeUtils
|
||||||
|
{
|
||||||
|
inline bool CanBeRequested(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return static_cast<uint8>(Type) >= 1 && static_cast<uint8>(Type) <= 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsPushOnly(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return static_cast<uint8>(Type) >= 10 && static_cast<uint8>(Type) <= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsValidResponseType(EDTFluxApiDataType Type)
|
||||||
|
{
|
||||||
|
return Type != EDTFluxApiDataType::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// UENUM(BlueprintType)
|
||||||
|
// enum class EDTFluxRequestType : uint8
|
||||||
|
// {
|
||||||
|
// None = 0 UMETA(DisplayName="None"),
|
||||||
|
// ContestRanking = 1 UMETA(DisplayName="contest-ranking"),
|
||||||
|
// StageRanking = 2 UMETA(DisplayName="stage-ranking"),
|
||||||
|
// SplitRanking = 3 UMETA(DisplayName="split-ranking"),
|
||||||
|
// TeamList = 4 UMETA(DisplayName="team-list"),
|
||||||
|
// RaceData = 5 UMETA(DisplayName="race-data"),
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType)
|
UENUM(BlueprintType)
|
||||||
enum class EDTFluxConnectionStatus : uint8
|
enum class EDTFluxConnectionStatus : uint8
|
||||||
@ -25,5 +70,5 @@ enum class EDTFluxConnectionStatus : uint8
|
|||||||
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
Connected = 1 << 0 UMETA(DisplayName="Connected"),
|
||||||
Error = 1 << 1 UMETA(DisplayName="Error"),
|
Error = 1 << 1 UMETA(DisplayName="Error"),
|
||||||
Closed = 1 << 2 UMETA(DisplayName="Closed"),
|
Closed = 1 << 2 UMETA(DisplayName="Closed"),
|
||||||
NotConnected= 1 << 3 UMETA(DisplayName="NotConnected")
|
NotConnected = 1 << 3 UMETA(DisplayName="NotConnected")
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,17 +24,17 @@ enum class EDTFluxParticipantStatusType : uint8
|
|||||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
||||||
enum class EParticipantSortingType : uint8
|
enum class EParticipantSortingType : uint8
|
||||||
{
|
{
|
||||||
|
|
||||||
None = 0 << 1 UMETA(DisplayName="Normal"),
|
None = 0 << 1 UMETA(DisplayName="Normal"),
|
||||||
Alpha = 1 << 1 UMETA(DisplayName="Aplha"),
|
Alpha = 1 << 1 UMETA(DisplayName="Aplha"),
|
||||||
PoursuiteStartTime = 1 << 2 UMETA(DisplayName="Poursuite StartTime"),
|
PoursuiteStartTime = 1 << 2 UMETA(DisplayName="Poursuite StartTime"),
|
||||||
Rank = 1 << 3 UMETA(DisplayName="Rank"),
|
Rank = 1 << 3 UMETA(DisplayName="Rank"),
|
||||||
IgnoreEmpty = 1 << 4 UMETA(DisplayName="IgnoreEmpty"),
|
IgnoreEmpty = 1 << 4 UMETA(DisplayName="IgnoreEmpty"),
|
||||||
};
|
};
|
||||||
|
|
||||||
ENUM_CLASS_FLAGS(EParticipantSortingType);
|
ENUM_CLASS_FLAGS(EParticipantSortingType);
|
||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
UENUM(BlueprintType)
|
||||||
enum class EDTFluxFinisherType : uint8
|
enum class EDTFluxFinisherType : uint8
|
||||||
{
|
{
|
||||||
None = 0b0000000 UMETA(DisplayName="Unknown"),
|
None = 0b0000000 UMETA(DisplayName="Unknown"),
|
||||||
@ -42,7 +42,6 @@ enum class EDTFluxFinisherType : uint8
|
|||||||
Winner = 0b0000010 UMETA(DisplayName="Winner"),
|
Winner = 0b0000010 UMETA(DisplayName="Winner"),
|
||||||
Spotter = 0b0000100 UMETA(DisplayName="Spotter"),
|
Spotter = 0b0000100 UMETA(DisplayName="Spotter"),
|
||||||
};
|
};
|
||||||
ENUM_CLASS_FLAGS(EDTFluxFinisherType);
|
|
||||||
|
|
||||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
||||||
enum class EDTFluxSplitType : uint8
|
enum class EDTFluxSplitType : uint8
|
||||||
@ -52,11 +51,12 @@ enum class EDTFluxSplitType : uint8
|
|||||||
Finish = 0b00000010 UMETA(DisplayName="FinishSplit"),
|
Finish = 0b00000010 UMETA(DisplayName="FinishSplit"),
|
||||||
Regular = 0b00000100 UMETA(DisplayName="Regular"),
|
Regular = 0b00000100 UMETA(DisplayName="Regular"),
|
||||||
};
|
};
|
||||||
|
|
||||||
ENUM_CLASS_FLAGS(EDTFluxSplitType);
|
ENUM_CLASS_FLAGS(EDTFluxSplitType);
|
||||||
|
|
||||||
|
|
||||||
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
UENUM(BlueprintType, meta=(Bitflags, UseEnumValuesAsMaskValuesInEditor=true))
|
||||||
enum EDTFluxSortingFilter : uint8
|
enum class EDTFluxSortingFilter : uint8
|
||||||
{
|
{
|
||||||
None = 0b00000000 UMETA(DisplayName="No Sorting"),
|
None = 0b00000000 UMETA(DisplayName="No Sorting"),
|
||||||
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
|
IgnoreStatusOut = 0b00000001 UMETA(DisplayName="IgnoreStatusOut"),
|
||||||
@ -67,6 +67,24 @@ enum EDTFluxSortingFilter : uint8
|
|||||||
ByAlpha = 0b01000000 UMETA(DisplayName="ByAlpha"),
|
ByAlpha = 0b01000000 UMETA(DisplayName="ByAlpha"),
|
||||||
ByStartTime = 0b00100000 UMETA(DisplayName="ByStartTime"),
|
ByStartTime = 0b00100000 UMETA(DisplayName="ByStartTime"),
|
||||||
AscendingByRank = Ascending | ByRank UMETA(DisplayName="AscendingByRank"),
|
AscendingByRank = Ascending | ByRank UMETA(DisplayName="AscendingByRank"),
|
||||||
DescendingByRank= Descending | ByRank UMETA(DisplayName="DescendingByRank")
|
DescendingByRank = Descending | ByRank UMETA(DisplayName="DescendingByRank")
|
||||||
};
|
};
|
||||||
|
|
||||||
ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
|
ENUM_CLASS_FLAGS(EDTFluxSortingFilter);
|
||||||
|
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDTFluxSortingRankingType: uint8
|
||||||
|
{
|
||||||
|
Rank = 0b00000000 UMETA(DisplayName="Rank (Default)"),
|
||||||
|
Name = 0b00000001 UMETA(DisplayName="Name"),
|
||||||
|
Bib = 0b00000010 UMETA(DisplayName="Bib"),
|
||||||
|
TimeSwim = 0b00000100 UMETA(DisplayName="Swimming Time"),
|
||||||
|
TimeTransition = 0b00001000 UMETA(DisplayName="Transition Time"),
|
||||||
|
TimeRun = TimeSwim | TimeTransition UMETA(DisplayName="Running Time"),
|
||||||
|
StartTime = 0b00001110 UMETA(DisplayName="StartTime"),
|
||||||
|
Gap = 0b00010000 UMETA(DisplayName="StartTime"),
|
||||||
|
SwimSpeed = 0b00100000 UMETA(DisplayName="StartTime"),
|
||||||
|
RunningSpeed = 0b01000000 UMETA(DisplayName="StartTime"),
|
||||||
|
TotalSpeed = 0b10000000 UMETA(DisplayName="StartTime"),
|
||||||
|
};
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "Types/Struct/DTFluxPursuitStructs.h"
|
|
||||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
|
||||||
#include "UObject/Object.h"
|
|
||||||
#include "DTFluxPursuitManager.generated.h"
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
UCLASS(BlueprintType)
|
|
||||||
class DTFLUXCORE_API UDTFluxPursuitManager : public UObject
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"), Transient)
|
|
||||||
TArray<FDTFluxPursuit> PursuitParticipants;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
FDateTime MassStart;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
int ContestId;
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, EditAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
int StageId;
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
void InitForStage(const FDTFluxStageRankings& StageRankings);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
TArray<FDTFluxPursuit> GetNextPursuits(int MaxPursuit);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
TArray<FDTFluxPursuit> GetPursuits(int FromIndex = 0, int MaxPursuit=10);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
FDateTime GetMassStart();
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
static FText GetFormattedName(FDTFluxPursuit& InPursuit, const int MaxChar = 10, const FString OverflowChar = FString(TEXT("...")));
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
static FText DisplayPursuit(FDTFluxPursuit& InPursuit, const int MaxWidth = 14, const FString NameOverflowChar = FString(TEXT("...")));
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
static bool IsUnique(const FDTFluxPursuit& InPursuit);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
UPROPERTY(VisibleAnywhere, Category="DTFlux|Pursuit", meta=(Keywords="Poursuit pursuit poursuit"))
|
|
||||||
int CurrentIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
@ -6,17 +6,26 @@
|
|||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxCompositeKey.generated.h"
|
#include "DTFluxCompositeKey.generated.h"
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FDTFluxCompositeKey
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct DTFLUXCORE_API FDTFluxStageKey
|
struct DTFLUXCORE_API FDTFluxStageKey : public FDTFluxCompositeKey
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
FDTFluxStageKey() = default;
|
FDTFluxStageKey() = default;
|
||||||
FDTFluxStageKey(const int InContestId, const int InStageId )
|
|
||||||
:ContestId(InContestId)
|
FDTFluxStageKey(const int InContestId, const int InStageId)
|
||||||
, StageId(InStageId){};
|
: ContestId(InContestId)
|
||||||
|
, StageId(InStageId)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
int ContestId = -1;
|
int ContestId = -1;
|
||||||
@ -35,6 +44,7 @@ struct DTFLUXCORE_API FDTFluxStageKey
|
|||||||
{
|
{
|
||||||
return ContestId == Other.ContestId && StageId == Other.StageId;
|
return ContestId == Other.ContestId && StageId == Other.StageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
FString GetDisplayName() const
|
FString GetDisplayName() const
|
||||||
{
|
{
|
||||||
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
|
return FString::Printf(TEXT("Contest%i -| Stage%i"), ContestId, StageId);
|
||||||
@ -46,21 +56,23 @@ struct DTFLUXCORE_API FDTFluxStageKey
|
|||||||
FText::AsNumber(ContestId),
|
FText::AsNumber(ContestId),
|
||||||
FText::AsNumber(StageId));
|
FText::AsNumber(StageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct DTFLUXCORE_API FDTFluxSplitKey
|
struct DTFLUXCORE_API FDTFluxSplitKey : public FDTFluxCompositeKey
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
FDTFluxSplitKey() = default;
|
FDTFluxSplitKey() = default;
|
||||||
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId )
|
|
||||||
:ContestId(InContestId)
|
FDTFluxSplitKey(const int InContestId, const int InStageId, const int InSplitId)
|
||||||
|
: ContestId(InContestId)
|
||||||
, StageId(InStageId)
|
, StageId(InStageId)
|
||||||
, SplitId(InSplitId){};
|
, SplitId(InSplitId)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
int ContestId = -1;
|
int ContestId = -1;
|
||||||
@ -82,6 +94,7 @@ struct DTFLUXCORE_API FDTFluxSplitKey
|
|||||||
{
|
{
|
||||||
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
|
return ContestId == Other.ContestId && StageId == Other.StageId && SplitId == Other.SplitId;
|
||||||
}
|
}
|
||||||
|
|
||||||
FString GetDisplayName() const
|
FString GetDisplayName() const
|
||||||
{
|
{
|
||||||
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
|
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i"), ContestId, StageId, SplitId);
|
||||||
@ -95,6 +108,62 @@ struct DTFLUXCORE_API FDTFluxSplitKey
|
|||||||
FText::AsNumber(SplitId)
|
FText::AsNumber(SplitId)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXCORE_API FDTFluxSplitSensorKey : public FDTFluxCompositeKey
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
FDTFluxSplitSensorKey() = default;
|
||||||
|
|
||||||
|
FDTFluxSplitSensorKey(const int InContestId, const int InStageId, const int InSplitId, const int InBib) :
|
||||||
|
ContestId(InContestId),
|
||||||
|
StageId(InStageId),
|
||||||
|
SplitId(InSplitId),
|
||||||
|
Bib(InBib){};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int ContestId = 0;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int StageId = 0;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int SplitId = 0;
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="DTFlux|Model")
|
||||||
|
int Bib = 0;
|
||||||
|
|
||||||
|
|
||||||
|
friend uint32 GetTypeHash(const FDTFluxSplitSensorKey& Key)
|
||||||
|
{
|
||||||
|
return HashCombine(
|
||||||
|
GetTypeHash(Key.ContestId),
|
||||||
|
GetTypeHash(Key.StageId),
|
||||||
|
GetTypeHash(Key.SplitId),
|
||||||
|
GetTypeHash(Key.Bib)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const FDTFluxSplitSensorKey& Other) const
|
||||||
|
{
|
||||||
|
return ContestId == Other.ContestId && StageId == Other.StageId
|
||||||
|
&& SplitId == Other.SplitId && Bib == Other.Bib;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString GetDisplayName() const
|
||||||
|
{
|
||||||
|
return FString::Printf(TEXT("Contest%i | Stage%i | Split%i | Bib%i"), ContestId, StageId, SplitId, Bib);
|
||||||
|
}
|
||||||
|
|
||||||
|
FText GetTooltipText() const
|
||||||
|
{
|
||||||
|
return FText::Format(INVTEXT("Contest{0}|Stage{1}|Split{2}"),
|
||||||
|
FText::AsNumber(ContestId),
|
||||||
|
FText::AsNumber(StageId),
|
||||||
|
FText::AsNumber(SplitId),
|
||||||
|
FText::AsNumber(Bib)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -15,6 +15,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
|||||||
struct DTFLUXCORE_API FDTFluxFinisherData
|
struct DTFLUXCORE_API FDTFluxFinisherData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
int ContestId;
|
int ContestId;
|
||||||
@ -23,9 +24,7 @@ public:
|
|||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
FDTFluxStageRanking SplitRanking;
|
FString Time;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
|
||||||
FDTFluxStageRanking StageRanking;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType, Category="FDTFlux|Model")
|
USTRUCT(BlueprintType, Category="FDTFlux|Model")
|
||||||
@ -64,4 +63,3 @@ struct DTFLUXCORE_API FDTFluxContestFinished
|
|||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Subsystem|Events")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Subsystem|Events")
|
||||||
TArray<FDTFluxStageRanking> Rankings;
|
TArray<FDTFluxStageRanking> Rankings;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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,17 +17,12 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxSplit
|
struct DTFLUXCORE_API FDTFluxSplit
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
int SplitId = -1;
|
int SplitId = -1;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Name;
|
FString Name;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
TArray<FDTFluxStageRanking> SplitRankings;
|
|
||||||
// void Dump() const;
|
|
||||||
// // void InsertOrReplace(const FDTFluxStageRankingResponseItem& SplitRankingItemResp);
|
|
||||||
// void SortByRank();
|
|
||||||
// TArray<FDTFluxSplitRanking> GetSplitRanking(const int From = 0, const int DisplayNumber = 0);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -39,6 +34,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxStage
|
struct DTFLUXCORE_API FDTFluxStage
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
int StageId;
|
int StageId;
|
||||||
@ -50,6 +46,7 @@ public:
|
|||||||
FDateTime EndTime;
|
FDateTime EndTime;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
FDateTime CutOff;
|
FDateTime CutOff;
|
||||||
|
bool IsFinished() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +57,7 @@ USTRUCT(BlueprintType, Category="DTFlux|RaceData")
|
|||||||
struct DTFLUXCORE_API FDTFluxContest
|
struct DTFLUXCORE_API FDTFluxContest
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
int ContestId = -1;
|
int ContestId = -1;
|
||||||
@ -73,18 +71,27 @@ public:
|
|||||||
TArray<FDTFluxSplit> Splits;
|
TArray<FDTFluxSplit> Splits;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
FDateTime Date;
|
FDateTime Date;
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
||||||
|
FDateTime EndTime;
|
||||||
|
UPROPERTY()
|
||||||
|
int LastStageId = -1;
|
||||||
|
|
||||||
|
|
||||||
|
bool IsFinished() const;
|
||||||
|
inline void UpdateEndTime();
|
||||||
|
int GetLastStageId();
|
||||||
|
void UpdateLastStageId();
|
||||||
|
FDTFluxStage& GetLastStage() const;
|
||||||
|
bool GetStage(const int StageID, FDTFluxStage& OutStage) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct DTFLUXCORE_API FDTFluxRaceData
|
struct DTFLUXCORE_API FDTFluxRaceData
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
public:
|
|
||||||
|
|
||||||
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
// ReSharper disable once IdentifierTypo
|
// ReSharper disable once IdentifierTypo
|
||||||
TArray<FDTFluxContest> Datas;
|
TArray<FDTFluxContest> Datas;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,18 @@
|
|||||||
#include "DTFluxRankingStructs.generated.h"
|
#include "DTFluxRankingStructs.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXCORE_API FDTFluxBaseRankings
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
|
FDateTime ReceivedAt = FDateTime::Now();
|
||||||
|
|
||||||
|
bool IsSealed(const FDateTime EndTime) const;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct FDTFluxContestRanking
|
* @struct FDTFluxContestRanking
|
||||||
* Representing a contest ranking for a participant
|
* Representing a contest ranking for a participant
|
||||||
@ -16,6 +28,7 @@ USTRUCT(BlueprintType, Category="DTFlux|Model")
|
|||||||
struct DTFLUXCORE_API FDTFluxContestRanking
|
struct DTFLUXCORE_API FDTFluxContestRanking
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
int Bib;
|
int Bib;
|
||||||
@ -31,21 +44,22 @@ public:
|
|||||||
FString SpeedRunningAverage;
|
FString SpeedRunningAverage;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere);
|
||||||
FString SpeedTotalAverage;
|
FString SpeedTotalAverage;
|
||||||
void Dump () const;
|
void Dump() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FDTFluxContestRankings
|
struct FDTFluxContestRankings : public FDTFluxBaseRankings
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
TArray<FDTFluxContestRanking> Rankings;
|
TArray<FDTFluxContestRanking> Rankings;
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
int ContestId;
|
int ContestId;
|
||||||
//TODO check if necessary ???
|
//TODO check if necessary ???
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
FString ContestName;
|
FString ContestName;
|
||||||
|
|
||||||
void SetName(const FString Name)
|
void SetName(const FString Name)
|
||||||
@ -55,13 +69,14 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @struct FDTFluxStageRanking
|
* @struct FDTFluxDetailedRankingItem
|
||||||
* Representing a stage ranking for a participant
|
* Representing a stage ranking for a participant
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxDetailedRankingItem
|
struct DTFLUXCORE_API FDTFluxDetailedRankingItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
int Bib;
|
int Bib;
|
||||||
@ -82,24 +97,25 @@ public:
|
|||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
FDateTime StartTime;
|
FDateTime StartTime;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
float SpeedRunning;
|
FString SpeedRunning;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
float SpeedTotal;
|
FString SpeedTotal;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", VisibleAnywhere)
|
||||||
float SpeedSwim;
|
FString SpeedSwim;
|
||||||
void Dump() const;
|
void Dump() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FDTFluxDetailedRankings
|
struct FDTFluxDetailedRankings : public FDTFluxBaseRankings
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
int ContestId;
|
int ContestId = -1;
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
int StageId;
|
int StageId = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
TArray<FDTFluxDetailedRankingItem> Rankings;
|
TArray<FDTFluxDetailedRankingItem> Rankings;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -121,7 +137,7 @@ struct FDTFluxStageRanking : public FDTFluxDetailedRankingItem
|
|||||||
* This struct is only a cosmetic Struct
|
* This struct is only a cosmetic Struct
|
||||||
*/
|
*/
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxSplitRanking : public FDTFluxStageRanking
|
struct DTFLUXCORE_API FDTFluxSplitRanking : public FDTFluxDetailedRankingItem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
};
|
};
|
||||||
@ -139,29 +155,62 @@ struct FDTFluxStageRankings : public FDTFluxDetailedRankings
|
|||||||
{
|
{
|
||||||
return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId);
|
return FDTFluxStageKey(InRankings.ContestId, InRankings.StageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline FDTFluxStageKey GetCompositeKey() const
|
inline FDTFluxStageKey GetCompositeKey() const
|
||||||
{
|
{
|
||||||
return FDTFluxStageKey(ContestId, StageId);
|
return FDTFluxStageKey(ContestId, StageId);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
|
inline bool IsInitialized() const
|
||||||
|
{
|
||||||
|
return ContestId > 0 && StageId > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize()
|
||||||
|
{
|
||||||
|
for (auto& Ranking : Rankings)
|
||||||
|
{
|
||||||
|
FDateTime RankingStartTime;
|
||||||
|
if (Ranking.TimeStart != "")
|
||||||
|
{
|
||||||
|
TArray<FString> Exploded;
|
||||||
|
Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true);
|
||||||
|
if (Exploded.Num() == 3)
|
||||||
|
{
|
||||||
|
//TODO: Pas sur que ce soit super de le mettre à ce jour ???
|
||||||
|
FDateTime Now = FDateTime::Now();
|
||||||
|
RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(),
|
||||||
|
FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]),
|
||||||
|
FCString::Atoi(*Exploded[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ranking.StartTime = RankingStartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FDTFluxSplitRankings : public FDTFluxDetailedRankings
|
struct FDTFluxSplitRankings : public FDTFluxDetailedRankings
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite,Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model|Ranking", VisibleAnywhere)
|
||||||
int SplitId;
|
int SplitId;
|
||||||
|
|
||||||
inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings)
|
inline static FDTFluxSplitKey GetKeyFrom(const FDTFluxSplitRankings& InRankings)
|
||||||
{
|
{
|
||||||
return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId);
|
return FDTFluxSplitKey(InRankings.ContestId, InRankings.StageId, InRankings.SplitId);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline FDTFluxSplitKey GetCompositeKey() const
|
inline FDTFluxSplitKey GetCompositeKey() const
|
||||||
{
|
{
|
||||||
return FDTFluxSplitKey(ContestId, StageId, SplitId);
|
return FDTFluxSplitKey(ContestId, StageId, SplitId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool IsInitialized() const
|
||||||
|
{
|
||||||
|
return ContestId > 0 && StageId > 0 && SplitId > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,17 @@ struct FDTFluxSplitSensorInfo
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxSplitSensorInfo() = default;
|
FDTFluxSplitSensorInfo() = default;
|
||||||
|
FDTFluxSplitSensorInfo(const FString InSplitName):
|
||||||
|
Bib(-1),
|
||||||
|
ContestId(-1),
|
||||||
|
StageId(-1),
|
||||||
|
SplitId(-1),
|
||||||
|
Time(""),
|
||||||
|
Gap("-"),
|
||||||
|
Rank(-1),
|
||||||
|
SplitName(InSplitName)
|
||||||
|
{
|
||||||
|
};
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
@ -31,7 +41,23 @@ public:
|
|||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
FString Gap = "-";
|
FString Gap = "-";
|
||||||
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
int Rank;
|
int Rank = -1;
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
FString SplitName = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxSplitHistory
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
FDTFluxSplitHistory() = default;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
|
FDTFluxParticipant Participant = FDTFluxParticipant();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, EditAnywhere)
|
||||||
|
TArray<FDTFluxSplitSensorInfo> SplitSensors = TArray<FDTFluxSplitSensorInfo>();
|
||||||
|
};
|
||||||
|
|||||||
@ -4,85 +4,92 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxCoreModule.h"
|
|
||||||
#include "Types/Enum/DTFluxModelEnums.h"
|
#include "Types/Enum/DTFluxModelEnums.h"
|
||||||
#include "DTFluxTeamListStruct.generated.h"
|
#include "DTFluxTeamListStruct.generated.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class UDTFluxModelAsset;
|
||||||
|
class UDTFluxParticipantFactory;
|
||||||
|
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
|
struct DTFLUXCORE_API FDTFluxTeamListItemDefinition
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "team-list-item";
|
FString Type = "team-list-item";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int ContestId;
|
int ContestId = 0;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int Bib;
|
int Bib = 0;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString FirstName;
|
FString FirstName;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString LastName;
|
FString LastName;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString FirstName2 = "";
|
FString FirstName2 = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString LastName2 = "";
|
FString LastName2 = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Team = "";
|
FString Team = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Gender;
|
FString Gender;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Gender2;
|
FString Gender2;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
bool Elite;
|
bool Elite = false;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Category;
|
FString Category;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int Status;
|
int Status = 0;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Club;
|
FString Club;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxPerson
|
struct DTFLUXCORE_API FDTFluxPerson
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString FirstName;
|
FString FirstName;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString LastName;
|
FString LastName;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Gender;
|
FString Gender;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString FunctionLine1 = TEXT("");
|
FString FunctionLine1 = TEXT("");
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString FunctionLine2 = TEXT("");
|
FString FunctionLine2 = TEXT("");
|
||||||
|
|
||||||
bool operator==(const FDTFluxPerson& Right) const
|
bool operator==(const FDTFluxPerson& Right) const;
|
||||||
{
|
bool operator!=(const FDTFluxPerson& Right) const;
|
||||||
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower()
|
bool operator==(const int Length) const;
|
||||||
== Right.FirstName.ToLower() + Right.LastName.ToLower() + Right.Gender.ToLower();
|
bool operator!=(const int Length) const;
|
||||||
}
|
|
||||||
bool operator==(const int Length) const
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
FString GetNormalizedString() const;
|
||||||
|
|
||||||
|
bool IsValid() const;
|
||||||
|
};
|
||||||
|
|
||||||
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
USTRUCT(BlueprintType, Category="DTFlux|Model")
|
||||||
struct DTFLUXCORE_API FDTFluxParticipant
|
struct DTFLUXCORE_API FDTFluxParticipant
|
||||||
@ -91,97 +98,123 @@ struct DTFLUXCORE_API FDTFluxParticipant
|
|||||||
|
|
||||||
friend class UDTFluxModelAsset;
|
friend class UDTFluxModelAsset;
|
||||||
friend class UDTFluxParticipantFactory;
|
friend class UDTFluxParticipantFactory;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Constructeur public par défaut requis par Unreal
|
FDTFluxParticipant();
|
||||||
FDTFluxParticipant()
|
|
||||||
: Bib(-1)
|
|
||||||
,ContestId(-1)
|
|
||||||
, Elite(false)
|
|
||||||
, Status(static_cast<EDTFluxParticipantStatusType>(0))
|
|
||||||
, bIsMassStartParticipant(false)
|
|
||||||
, LastSplitId(0)
|
|
||||||
{
|
|
||||||
Teammate.Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
|
||||||
bool operator == ( int Rhs) const
|
|
||||||
{
|
|
||||||
return Rhs == 0 && Bib == -1 && Team.IsEmpty() && Club.IsEmpty() && ContestId == -1
|
|
||||||
&& Teammate.IsEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
|
||||||
int ContestId = -1;
|
int ContestId = -1;
|
||||||
UPROPERTY(BlueprintReadOnly, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Category;
|
FString Category;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Club;
|
FString Club;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
bool Elite;
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
bool Elite = false;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
EDTFluxParticipantStatusType Status;
|
EDTFluxParticipantStatusType Status;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
FString Team;
|
FString Team;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model", EditAnywhere)
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model", EditAnywhere)
|
||||||
bool bIsMassStartParticipant = false;
|
bool bIsMassStartParticipant = false;
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|model")
|
|
||||||
int LastSplitId = -1;
|
|
||||||
|
|
||||||
// void Dump() const;
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Model")
|
||||||
|
int CurrentSplit = -1;
|
||||||
|
|
||||||
|
bool IsDefault() const;
|
||||||
|
|
||||||
void AddTeammate(const FDTFluxPerson& Person);
|
void AddTeammate(const FDTFluxPerson& Person);
|
||||||
void AddTeammate(const FString LastName, const FString FirstName, const FString Gender);
|
void AddTeammate(const FString& LastName, const FString& FirstName, const FString& Gender);
|
||||||
FText GetFormattedName(const int MaxChar = 15, const FString OverflowChar = FString("..."));
|
|
||||||
FText GetConcatFormattedName(const int MaxChar = 20, const FString OverflowChar = FString("..."));
|
|
||||||
|
|
||||||
private:
|
int GetTeammateNum() const;
|
||||||
|
|
||||||
// --- Constructeur privé ---
|
bool IsTeam() const;
|
||||||
explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject);
|
|
||||||
|
|
||||||
|
const TArray<FDTFluxPerson>& GetTeammate() const;
|
||||||
|
|
||||||
|
FString GetFormattedName(const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("...")) const;
|
||||||
|
|
||||||
|
FString GetConcatFormattedName(const int MaxChar = 20,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."),
|
||||||
|
const FString& BibSeparator = FString(". ")) const;
|
||||||
|
|
||||||
|
FText GetFormattedNameText(const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("...")) const;
|
||||||
|
|
||||||
|
FText GetConcatFormattedNameText(const int MaxChar = 20,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."),
|
||||||
|
const FString& BibSeparator = FString(". ")) const;
|
||||||
|
|
||||||
|
static FString GetFormattedName(const FDTFluxParticipant& Participant,
|
||||||
|
const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."));
|
||||||
|
|
||||||
|
static FString GetConcatFormattedName(const FDTFluxParticipant& Participant,
|
||||||
|
const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."),
|
||||||
|
const FString& BibSeparator = FString(". "));
|
||||||
|
|
||||||
|
static FText GetFormattedNameText(const FDTFluxParticipant& Participant,
|
||||||
|
const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."));
|
||||||
|
|
||||||
|
static FText GetConcatFormattedNameText(const FDTFluxParticipant& Participant,
|
||||||
|
const int MaxChar = 15,
|
||||||
|
const FString& Separator = FString(". "),
|
||||||
|
const FString& OverflowChar = FString("..."),
|
||||||
|
const FString& BibSeparator = FString(". "));
|
||||||
|
|
||||||
|
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
UPROPERTY(Category="DTFlux|model", VisibleAnywhere)
|
UPROPERTY(Category="DTFlux|Model", VisibleAnywhere)
|
||||||
TArray<FDTFluxPerson> Teammate;
|
TArray<FDTFluxPerson> Teammate;
|
||||||
// Méthode publique pour construire à partir d'un JSON (utilisée par la factory)
|
|
||||||
static FDTFluxParticipant CreateFromJson(const TSharedPtr<FJsonObject>& JsonObject);
|
private:
|
||||||
int GetTeammateNum() const;
|
explicit FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject);
|
||||||
bool IsTeam();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @struct FDTFluxTeamListDefinition
|
|
||||||
* Struct representing the Participant List definition
|
|
||||||
* Used to exchange data between Objects in the system
|
|
||||||
*/
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct DTFLUXCORE_API FDTFluxTeamListDefinition
|
struct DTFLUXCORE_API FDTFluxTeamListDefinition
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
// ReSharper disable once IdentifierTypo
|
|
||||||
TArray<FDTFluxParticipant> Participants;
|
TArray<FDTFluxParticipant> Participants;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
USTRUCT(BlueprintType)
|
USTRUCT(BlueprintType)
|
||||||
struct FDTFluxTeamStatusUpdate
|
struct DTFLUXCORE_API FDTFluxTeamStatusUpdate
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxTeamStatusUpdate() = default;
|
FDTFluxTeamStatusUpdate() = default;
|
||||||
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus)
|
FDTFluxTeamStatusUpdate(const int InBib, const int InStatus);
|
||||||
:Bib(InBib)
|
|
||||||
, Status(static_cast<EDTFluxParticipantStatusType>(InStatus)){};
|
|
||||||
|
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
||||||
int Bib = -1;
|
int Bib = -1;
|
||||||
|
|
||||||
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
UPROPERTY(BlueprintReadWrite, Category="DTFlux|Participant")
|
||||||
EDTFluxParticipantStatusType Status = EDTFluxParticipantStatusType::Unknown;
|
EDTFluxParticipantStatusType Status = EDTFluxParticipantStatusType::Unknown;
|
||||||
};
|
};
|
||||||
@ -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;
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
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;
|
||||||
|
}
|
||||||
|
|
||||||
350
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
350
Source/DTFluxCoreSubsystem/Private/DTFluxPursuitManager.cpp
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxPursuitManager.h"
|
||||||
|
|
||||||
|
#include <ImportExport.h>
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
#include "DTFluxCoreSubsystemModule.h"
|
||||||
|
#include "Dataflow/DataflowContextCache.h"
|
||||||
|
|
||||||
|
UDTFluxPursuitManager::UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer):
|
||||||
|
Super(ObjectInitializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::InitPursuit(const TArray<int> InContestIds, const int MaxSimultaneousPursuit)
|
||||||
|
{
|
||||||
|
CoreSubsystem = Cast<UDTFluxCoreSubsystem>(GetOuter());
|
||||||
|
if (!CoreSubsystem)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("CoreSubsystem is not Available !!!"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AllRankings.Reset();
|
||||||
|
for (const auto& ContestId : InContestIds)
|
||||||
|
{
|
||||||
|
FDTFluxContest Contest;
|
||||||
|
if (CoreSubsystem->GetContestForId(ContestId, Contest))
|
||||||
|
{
|
||||||
|
// BindRankings();
|
||||||
|
FDTFluxStageKey StageKey = FDTFluxStageKey(ContestId, Contest.GetLastStageId());
|
||||||
|
FDTFluxStageRankings TempStageRankings;
|
||||||
|
//Obtenir les ranking Frais.
|
||||||
|
CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, true);
|
||||||
|
AllRankings.Add(TempStageRankings);
|
||||||
|
LaunchPursuitSequence();
|
||||||
|
// CoreSubsystem->GetStageRankings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup)
|
||||||
|
{
|
||||||
|
for (auto& Pursuit : NextFocusGroup.PursuitGroup)
|
||||||
|
{
|
||||||
|
Pursuit.bIsMassStart = Pursuit.StartTime >= MassStartTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext,
|
||||||
|
TArray<FDTFluxPursuitInfo>& OutPursuitNext, bool& BIsFocusTruncate,
|
||||||
|
const int MaxSimultaneousPursuit)
|
||||||
|
{
|
||||||
|
FDateTime MetricsStartFunction = FDateTime::UtcNow();
|
||||||
|
FDateTime CurrentTime = FDateTime::Now();
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== GetPursuit CALLED ==="));
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Available groups: %d"), GroupedPursuit.Num());
|
||||||
|
|
||||||
|
// BAd Parameter
|
||||||
|
if (MaxSimultaneousPursuit <= 0)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Invalid MaxSimultaneousPursuit: %d"), MaxSimultaneousPursuit);
|
||||||
|
OutPursuitFocusNext.Reset();
|
||||||
|
OutPursuitNext.Reset();
|
||||||
|
BIsFocusTruncate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bIsSequenceDone || GroupedPursuit.IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No groups available or sequence completed"));
|
||||||
|
OutPursuitFocusNext.Reset();
|
||||||
|
OutPursuitNext.Reset();
|
||||||
|
BIsFocusTruncate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int32 i = GroupedPursuit.Num() - 1; i >= 0; i--) // Parcours inverse pour éviter les problèmes d'index
|
||||||
|
{
|
||||||
|
const FDTFluxPursuitGroup& Group = GroupedPursuit[i];
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Group.StartTimeGlobal(%s) < CurrentTime(%s) "),
|
||||||
|
*Group.StartTimeGlobal.ToString(), *CurrentTime.ToString())
|
||||||
|
// Vérifier si le StartTime du groupe est déjà passé
|
||||||
|
if (Group.StartTimeGlobal < CurrentTime)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning,
|
||||||
|
TEXT("Removing expired group: StartTime=%s (Current=%s), Participants=%d"),
|
||||||
|
*Group.StartTimeGlobal.ToString(),
|
||||||
|
*CurrentTime.ToString(),
|
||||||
|
Group.PursuitGroup.Num());
|
||||||
|
|
||||||
|
GroupedPursuit.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutPursuitFocusNext.Reset();
|
||||||
|
OutPursuitNext.Reset();
|
||||||
|
|
||||||
|
if (GroupedPursuit.IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("All groups were expired and removed - no groups available"));
|
||||||
|
OutPursuitFocusNext.Reset();
|
||||||
|
OutPursuitNext.Reset();
|
||||||
|
BIsFocusTruncate = false;
|
||||||
|
bIsSequenceDone = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxPursuitGroup FocusGroup = GroupedPursuit[0];
|
||||||
|
GroupedPursuit.RemoveAt(0);
|
||||||
|
|
||||||
|
SetPursuitInfoIsMassStart(FocusGroup);
|
||||||
|
OutPursuitFocusNext = FocusGroup.PursuitGroup;
|
||||||
|
BIsFocusTruncate = FocusGroup.PursuitGroup.Num() > 1;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning,
|
||||||
|
TEXT("Focus Group: StartTime=%s, Participants=%d"),
|
||||||
|
*FocusGroup.StartTimeGlobal.ToString(),
|
||||||
|
FocusGroup.PursuitGroup.Num());
|
||||||
|
|
||||||
|
int32 TargetNextCount = MaxSimultaneousPursuit - 1;
|
||||||
|
int32 AddedNextCount = 0;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Target Next Count: %d"), TargetNextCount);
|
||||||
|
|
||||||
|
for (int32 GroupIndex = 0;
|
||||||
|
GroupIndex < GroupedPursuit.Num() && AddedNextCount < TargetNextCount;
|
||||||
|
GroupIndex++)
|
||||||
|
{
|
||||||
|
FDTFluxPursuitGroup& NextGroup = GroupedPursuit[GroupIndex];
|
||||||
|
|
||||||
|
if (NextGroup.PursuitGroup.Num() == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 AvailableInGroup = NextGroup.PursuitGroup.Num();
|
||||||
|
int32 NeededFromGroup = FMath::Min(TargetNextCount - AddedNextCount, AvailableInGroup);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning,
|
||||||
|
TEXT("Processing Next Group %d: StartTime=%s, Available=%d, Taking=%d"),
|
||||||
|
GroupIndex,
|
||||||
|
*NextGroup.StartTimeGlobal.ToString(),
|
||||||
|
AvailableInGroup,
|
||||||
|
NeededFromGroup);
|
||||||
|
|
||||||
|
for (int32 ParticipantIndex = 0; ParticipantIndex < NeededFromGroup; ParticipantIndex++)
|
||||||
|
{
|
||||||
|
FDTFluxPursuitInfo NextParticipant = NextGroup.PursuitGroup[ParticipantIndex]; // Copie
|
||||||
|
|
||||||
|
NextParticipant.bIsMassStart = NextParticipant.StartTime >= MassStartTime;
|
||||||
|
|
||||||
|
OutPursuitNext.Add(NextParticipant);
|
||||||
|
AddedNextCount++;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, VeryVerbose,
|
||||||
|
TEXT("Added to Next: Bib %d from Group %d"),
|
||||||
|
NextParticipant.Bib, GroupIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ==="));
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus: %d participants"), OutPursuitFocusNext.Num());
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next: %d participants"), OutPursuitNext.Num());
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Remaining groups for future: %d"), GroupedPursuit.Num());
|
||||||
|
|
||||||
|
if (OutPursuitFocusNext.Num() > 0)
|
||||||
|
{
|
||||||
|
DebugFocusNext(OutPursuitFocusNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log détaillé des Next (limité pour éviter spam)
|
||||||
|
if (OutPursuitNext.Num() > 0)
|
||||||
|
{
|
||||||
|
DebugOutPoursuitNext(OutPursuitNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier si la séquence est terminée
|
||||||
|
if (GroupedPursuit.IsEmpty())
|
||||||
|
{
|
||||||
|
bIsSequenceDone = true;
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Pursuit sequence will be completed after this round"));
|
||||||
|
}
|
||||||
|
FTimespan Duration = FDateTime::UtcNow() - MetricsStartFunction;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Metrics Time Out: %d fraction seconds"),
|
||||||
|
Duration.GetDuration().GetFractionMicro());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxPursuitManager::InitSubSystems()
|
||||||
|
{
|
||||||
|
if (NetworkSubsystem)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
NetworkSubsystem = GEngine->GetEngineSubsystem<UDTFluxNetworkSubsystem>();
|
||||||
|
return NetworkSubsystem != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxPursuitManager::BindRankings()
|
||||||
|
{
|
||||||
|
if (CoreSubsystem)
|
||||||
|
{
|
||||||
|
if (!bIsRankingBounded)
|
||||||
|
{
|
||||||
|
CoreSubsystem->OnStageRankings.AddDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
|
||||||
|
bIsRankingBounded = true;
|
||||||
|
}
|
||||||
|
return bIsRankingBounded;
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("CoreSubsystem is not Available !!!"));
|
||||||
|
return bIsRankingBounded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::UnbindRankings()
|
||||||
|
{
|
||||||
|
if (CoreSubsystem)
|
||||||
|
{
|
||||||
|
if (bIsRankingBounded)
|
||||||
|
{
|
||||||
|
CoreSubsystem->OnStageRankings.RemoveDynamic(this, &UDTFluxPursuitManager::OnRankingsReceived);
|
||||||
|
bIsRankingBounded = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bIsRankingBounded = false;
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("CoreSubsystem is not Available !!!"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::OnRankingsReceived(const FDTFluxStageKey NewStageKey,
|
||||||
|
const FDTFluxStageRankings NewStageRankings)
|
||||||
|
{
|
||||||
|
if (PendingStageRanking.Contains(NewStageKey))
|
||||||
|
{
|
||||||
|
PendingStageRanking.Remove(NewStageKey);
|
||||||
|
AllRankings.Add(NewStageRankings);
|
||||||
|
if (PendingStageRanking.IsEmpty())
|
||||||
|
{
|
||||||
|
//everything is ready to go compute and start
|
||||||
|
UnbindRankings();
|
||||||
|
LaunchPursuitSequence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxPursuitManager::LaunchPursuitSequence()
|
||||||
|
{
|
||||||
|
GroupedPursuit.Empty();
|
||||||
|
TArray<FDTFluxPursuitInfo> AllPursuits;
|
||||||
|
TMap<FDateTime, FDTFluxPursuitGroup> TempGroups;
|
||||||
|
bIsSequenceDone = false;
|
||||||
|
// Full the Array Of Rankings
|
||||||
|
for (auto& Ranking : AllRankings)
|
||||||
|
{
|
||||||
|
for (auto StageRanking : Ranking.Rankings)
|
||||||
|
{
|
||||||
|
int ContestId = Ranking.ContestId;
|
||||||
|
FDTFluxPursuitInfo PursuitInfo;
|
||||||
|
PursuitInfo.StartTime = StageRanking.StartTime;
|
||||||
|
PursuitInfo.Bib = StageRanking.Bib;
|
||||||
|
PursuitInfo.ContestId = ContestId;
|
||||||
|
AllPursuits.Add(PursuitInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("AllPursuits.Num() = %i"), AllPursuits.Num());
|
||||||
|
for (auto& Pursuit : AllPursuits)
|
||||||
|
{
|
||||||
|
if (TempGroups.Contains(Pursuit.StartTime))
|
||||||
|
{
|
||||||
|
FDTFluxPursuitGroup& Group = TempGroups[Pursuit.StartTime];
|
||||||
|
Group.PursuitGroup.Add(Pursuit);
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning,
|
||||||
|
TEXT("Adding [%i] To PursuitGroup starting At %s, PursuitGroup.Num() %i"),
|
||||||
|
Pursuit.Bib, *Pursuit.StartTime.ToString(), Group.PursuitGroup.Num());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FDTFluxPursuitGroup NewGroup;
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("New Group starting At %s, Adding Bib [%i]"),
|
||||||
|
*Pursuit.StartTime.ToString(), Pursuit.Bib);
|
||||||
|
NewGroup.StartTimeGlobal = Pursuit.StartTime;
|
||||||
|
NewGroup.PursuitGroup.Add(Pursuit);
|
||||||
|
TempGroups.Add(Pursuit.StartTime, NewGroup);
|
||||||
|
for (const auto& Group : TempGroups)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Group.StartTime = %s, Group.PursuitGroup.Num() = %i"),
|
||||||
|
*Group.Key.ToString(), Group.Value.PursuitGroup.Num());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TempGroups.KeySort([](const FDateTime& A, const FDateTime& B)
|
||||||
|
{
|
||||||
|
return A < B;
|
||||||
|
});
|
||||||
|
TMap<FDateTime, int> StartTimeFrequency;
|
||||||
|
int32 MaxFrequency = 0;
|
||||||
|
GroupedPursuit.Reserve(TempGroups.Num());
|
||||||
|
// parcours du TMap
|
||||||
|
for (const auto& Pair : TempGroups)
|
||||||
|
{
|
||||||
|
if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue())
|
||||||
|
{
|
||||||
|
int& CurrentFreq = StartTimeFrequency.FindOrAdd(Pair.Value.StartTimeGlobal, 0);
|
||||||
|
CurrentFreq = Pair.Value.PursuitGroup.Num();
|
||||||
|
if (CurrentFreq > MaxFrequency)
|
||||||
|
{
|
||||||
|
MaxFrequency = CurrentFreq;
|
||||||
|
MassStartTime = Pair.Value.StartTimeGlobal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GroupedPursuit.Add(Pair.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupedPursuit.Sort([](const FDTFluxPursuitGroup& A, const FDTFluxPursuitGroup& B)
|
||||||
|
{
|
||||||
|
return A.StartTimeGlobal < B.StartTimeGlobal;
|
||||||
|
});
|
||||||
|
|
||||||
|
TArray<FDTFluxPursuitInfo> FocusPursuits;
|
||||||
|
TArray<FDTFluxPursuitInfo> NextPursuits;
|
||||||
|
bool bIsFocusTruncate = false;
|
||||||
|
|
||||||
|
GetPursuit(FocusPursuits, NextPursuits, bIsFocusTruncate);
|
||||||
|
FPursuitStarterData PursuitData = FPursuitStarterData(FocusPursuits, NextPursuits, MassStartTime, bIsFocusTruncate);
|
||||||
|
OnPursuitSequenceReady.Broadcast(PursuitData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext)
|
||||||
|
{
|
||||||
|
FString FocusBibs;
|
||||||
|
for (const auto& Pursuit : OutPursuitFocusNext)
|
||||||
|
{
|
||||||
|
FocusBibs += FString::Printf(TEXT("%d "), Pursuit.Bib);
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus Bibs: %s"), *FocusBibs);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxPursuitManager::DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext)
|
||||||
|
{
|
||||||
|
FString NextBibs;
|
||||||
|
for (int32 i = 0; i < OutPursuitNext.Num(); i++)
|
||||||
|
{
|
||||||
|
NextBibs += FString::Printf(TEXT("%d "), OutPursuitNext[i].Bib);
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next Bibs: %s"), *NextBibs);
|
||||||
|
}
|
||||||
@ -1,95 +1,193 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
#pragma once
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxCoreSubsystemModule.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
#include "Containers/Deque.h"
|
||||||
|
#include "Types/Struct/FDTFluxPursuitInfo.h"
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
#include "Types/Enum/DTfluxCoreEnum.h"
|
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
#include "Types/Struct/DTFluxTeamListStruct.h"
|
||||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxCoreSubsystem.generated.h"
|
#include "DTFluxCoreSubsystem.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UDTFluxNetworkSubsystem;
|
class UDTFluxNetworkSubsystem;
|
||||||
/** Forward Decl */
|
/** Forward Decl */
|
||||||
class UDTFluxModelAsset;
|
class UDTFluxModelAsset;
|
||||||
|
class UDTFluxPursuitManager;
|
||||||
|
struct FDTFluxServerResponse;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
UCLASS()
|
UCLASS(BlueprintType, meta=(DisplayName="DTFlux Core Subsystem"))
|
||||||
class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
|
class DTFLUXCORESUBSYSTEM_API UDTFluxCoreSubsystem : public UEngineSubsystem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDTFluxSplitKey, SplitKey, FDTFluxSplitRankings,
|
||||||
|
SplitRankings);
|
||||||
|
|
||||||
|
|
||||||
// TSharedPtr<FDTFluxParser> Parser;
|
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDateTime, ReceivedAt, TArray<FDTFluxStageRanking>, SplitRankings);
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnSplitRankings OnSplitRankings;
|
FOnSplitRankings OnSplitRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDateTime, ReceivedAt, TArray<FDTFluxStageRanking>, StageRankings);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDTFluxStageKey, StageKey, FDTFluxStageRankings,
|
||||||
|
StageRankings);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnStageRankings OnStageRankings;
|
FOnStageRankings OnStageRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankings, FDateTime, ReceivedAt, TArray<FDTFluxContestRanking>, ContestRankings);
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankingDisplayReady, const FGuid, RequestId, const bool,
|
||||||
|
bSuccesRequest);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnContestRankingDisplayReady OnContestRankingDisplayReady;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankingDisplayReady, const FGuid, RequestId, const bool,
|
||||||
|
bSuccesRequest);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnStageRankingDisplayReady OnStageRankingDisplayReady;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankingDisplayReady, const FGuid, RequestId, const bool,
|
||||||
|
bSuccesRequest);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnSplitRankingDisplayReady OnSplitRankingDisplayReady;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnContestRankings, const int, ContestId, FDTFluxContestRankings,
|
||||||
|
ContestRankings);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnContestRankings OnContestRankings;
|
FOnContestRankings OnContestRankings;
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamList, FDateTime, ReceivedAt, TArray<FDTFluxParticipant>, TeamList);
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnTeamList);
|
||||||
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnTeamList OnTeamList;
|
FOnTeamList OnTeamList;
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTeamStatusUpdate, FDTFluxParticipant, TeamUpdated);
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList);
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
|
||||||
FOnTeamUpdate OnTeamUpdate;
|
|
||||||
|
|
||||||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamStatusUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdated);
|
|
||||||
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
FOnTeamStatusUpdate OnTeamStatusUpdate;
|
FOnTeamStatusUpdate OnTeamStatusUpdate;
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
|
||||||
void SendTeamListRequest();
|
UDTFluxPursuitManager* PursuitManager = nullptr;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitSensor, FDTFluxSplitSensorInfo, SplitSensorInfo);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnSplitSensor OnSplitSensor;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFinisher, FDTFluxSplitSensorInfo, SplitSensorInfo);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnFinisher OnFinisher;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPreFinish, FDTFluxSplitSensorInfo, SplitSensorInfo);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnPreFinish OnPreFinish;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWinner, FDTFluxSplitSensorInfo, SplitSensorInfo);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnWinner OnWinner;
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnParticipantTrackingReady, FDTFluxSplitHistory, SplitHistory);
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnParticipantTrackingReady OnParticipantTrackingReady;
|
||||||
|
|
||||||
|
//TODO : this must be a ProjectSetting
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
|
||||||
|
bool bShouldKeepRankings = true;
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendRaceDataRequest();
|
void InitParticipantTracking(const int Bib, const int ContestId, const int StageId);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendContestRankingRequest(int InContestId);
|
FGuid InitContestRankingsDisplay(const int ContestIds);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendStageRankingRequest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true);
|
FGuid InitStageRankingsDisplay(const int ContestId, const int StageId);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RequestAllStageRankingOfContest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true);
|
FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void SendSplitRankingRequest(int InContestId, int InStageId, int InSplitId);
|
bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
|
||||||
|
FDTFluxStageRanking& OutStageRankings);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId, const int Bib,
|
||||||
|
FDTFluxSplitRanking& OutSplitRankings);
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RequestAllSplitRankingOfContest(int InContestId, int InStageId);
|
bool GetContestRankings(const int ContestId, FDTFluxContestRankings& OutContestRankings);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId);
|
bool GetStageRankings(const int ContestId, const int StageId, FDTFluxStageRankings& OutStageRankings);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
void RefreshStorage();
|
bool GetSplitRankings(const int ContestId, const int StageId, const int SplitId,
|
||||||
|
FDTFluxSplitRankings& OutSplitRankings);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetStageRankingsWithKey(const FDTFluxStageKey StageKey, FDTFluxStageRankings& OutStageRankings,
|
||||||
|
const bool bShouldUseCached = true);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKey, FDTFluxSplitRankings& OutSplitRankings,
|
||||||
|
const bool bShouldUseCached = true);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
TArray<FGuid> TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache = true);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
TArray<FGuid> TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages, bool bEnableCache = true);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
TArray<FGuid> TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits, bool bEnableCache = true);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetParticipant(int InBib, FDTFluxParticipant& OutParticipant);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
TArray<int> GetCurrentContestsId();
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetCurrentContests(TArray<FDTFluxContest>& OutContests);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
TArray<int> GetContestsIdForTime(const FDateTime Time);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetContestsForTime(const FDateTime Time, TArray<FDTFluxContest>& OutContests);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
bool GetContests(TArray<FDTFluxContest>& OutContests);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
void GetContest(const int ContestId, FDTFluxContest& OutContest);
|
||||||
|
UFUNCTION()
|
||||||
|
bool GetStageDefinition(const FDTFluxStageKey StageKey, FDTFluxStage& OutStageDefinition);
|
||||||
|
UFUNCTION()
|
||||||
|
bool GetSplitDefinition(const FDTFluxSplitKey SplitKey, FDTFluxSplit& OutSplitDefinition);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
void GetStage(const int ContestId, const int StageId, FDTFluxStage& OutStageDefinition);
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
||||||
|
void GetSplit(const int ContestId, const int StageId, const int SplitId, FDTFluxSplit& OutSplitDefinition);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// ~Subsystem Interface
|
// ~Subsystem Interface
|
||||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
virtual void Deinitialize() override;
|
virtual void Deinitialize() override;
|
||||||
// ~Subsystem Interface
|
// ~Subsystem Interface
|
||||||
|
UFUNCTION()
|
||||||
void SaveDataStorage();
|
void SaveDataStorage();
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessTrackedResponse(FDTFluxServerResponse& InResponse);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||||
|
UPROPERTY()
|
||||||
|
UDTFluxModelAsset* DataStorage = nullptr;
|
||||||
|
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition);
|
void ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition);
|
||||||
@ -102,14 +200,20 @@ private:
|
|||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings);
|
void ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ProcessTeamStatusUpdate();
|
void ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void ProcessSplitSensor();
|
void ProcessSplitSensor(const FDTFluxSplitSensorInfo& SplitSensorInfo);
|
||||||
|
UFUNCTION()
|
||||||
|
void ProcessTeamUpdate(const FDTFluxTeamListDefinition& TeamListDefinition);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void SendRequest(const FString& Message);
|
void SendRequest(const FString& Message);
|
||||||
UFUNCTION()
|
UFUNCTION()
|
||||||
void RegisterDelegates();
|
void RegisterDelegates();
|
||||||
|
UFUNCTION()
|
||||||
|
bool IsStageRankingSealed(FDTFluxStageKey StageKey);
|
||||||
|
UFUNCTION()
|
||||||
|
bool IsContestRankingSealed(int ContestId);
|
||||||
|
|
||||||
|
EDTFluxFinisherType GetSplitSensorType(const FDTFluxSplitSensorInfo& SplitSensorInfo);
|
||||||
|
|
||||||
UPROPERTY()
|
|
||||||
UDTFluxModelAsset* DataStorage = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|||||||
38
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.h
Normal file
38
Source/DTFluxCoreSubsystem/Public/DTFluxCoreSubsystemTools.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 "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);
|
||||||
|
};
|
||||||
129
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
129
Source/DTFluxCoreSubsystem/Public/DTFluxPursuitManager.h
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
|
||||||
|
class UDTFluxCoreSubsystem;
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FPursuitStarterData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
FPursuitStarterData() = default;
|
||||||
|
|
||||||
|
FPursuitStarterData(const TArray<FDTFluxPursuitInfo>& InPursuitFocusNext,
|
||||||
|
const TArray<FDTFluxPursuitInfo>& InPursuitNext, const FDateTime& InMassStartTime,
|
||||||
|
const bool InIsFocusTruncate)
|
||||||
|
: PursuitFocusNext(InPursuitFocusNext), PursuitNext(InPursuitNext), MassStartTime(InMassStartTime),
|
||||||
|
bIsFocusTruncate(InIsFocusTruncate)
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
|
||||||
|
TArray<FDTFluxPursuitInfo> PursuitFocusNext = TArray<FDTFluxPursuitInfo>();
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
|
||||||
|
TArray<FDTFluxPursuitInfo> PursuitNext = TArray<FDTFluxPursuitInfo>();
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
|
||||||
|
FDateTime MassStartTime = FDateTime::MinValue();
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
|
||||||
|
bool bIsFocusTruncate = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
|
struct FDTFluxPursuitGroup
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
|
||||||
|
UPROPERTY()
|
||||||
|
FDateTime StartTimeGlobal = FDateTime::MinValue();
|
||||||
|
UPROPERTY()
|
||||||
|
bool bHasStarted = false;
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsFocus = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType)
|
||||||
|
class DTFLUXCORESUBSYSTEM_API UDTFluxPursuitManager : public UObject
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
|
||||||
|
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStarterData, PursuitData);
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
|
||||||
|
FOnPursuitSequenceReady OnPursuitSequenceReady;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
|
||||||
|
bool bFocusIsTruncate = false;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int PursuitMaxSimultaneousPursuit = 7;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit")
|
||||||
|
int MassStartDelay = 10;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
FDateTime MassStartTime = FDateTime::MinValue();
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
TArray<FDTFluxPursuitGroup> GroupedPursuit;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
int CurrentIndex = -1;
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
|
||||||
|
void InitPursuit(const TArray<int> InContestIds, const int MaxSimultaneousPursuit = 7);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
|
||||||
|
void GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext, TArray<FDTFluxPursuitInfo>& OutPursuitNext,
|
||||||
|
bool& BIsFocusTruncate, const int MaxSimultaneousPursuit = 7);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
bool InitSubSystems();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
bool BindRankings();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void UnbindRankings();
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnRankingsReceived(const FDTFluxStageKey NewStageKey, const FDTFluxStageRankings NewStageRankings);
|
||||||
|
|
||||||
|
|
||||||
|
void DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext);
|
||||||
|
void DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TMap<FDTFluxStageKey, bool> PendingStageRanking;
|
||||||
|
TArray<FDTFluxStageRankings> AllRankings;
|
||||||
|
UDTFluxCoreSubsystem* CoreSubsystem = nullptr;
|
||||||
|
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
|
||||||
|
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsSequenceDone = true;
|
||||||
|
UPROPERTY()
|
||||||
|
bool bIsRankingBounded = false;
|
||||||
|
UFUNCTION()
|
||||||
|
void SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup);
|
||||||
|
UFUNCTION()
|
||||||
|
bool LaunchPursuitSequence();
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ public class DTFluxNetwork : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
318
Source/DTFluxNetwork/Private/DTFluxAsyncParser.cpp
Normal file
318
Source/DTFluxNetwork/Private/DTFluxAsyncParser.cpp
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
#include "DTFluxAsyncParser.h"
|
||||||
|
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "Async/AsyncWork.h"
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// IMPLÉMENTATION DE LA TÂCHE DE PARSING
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
DECLARE_STATS_GROUP(TEXT("DTFlux"), STATGROUP_DTFlux, STATCAT_Advanced);
|
||||||
|
|
||||||
|
DECLARE_CYCLE_STAT(TEXT("DTFlux Parsing Task"), STAT_FDTFluxParsingTask, STATGROUP_DTFlux);
|
||||||
|
DECLARE_CYCLE_STAT(TEXT("DTFlux Parsing Task DoWork"), STAT_FDTFluxParsingTask_DoWork, STATGROUP_DTFlux);
|
||||||
|
|
||||||
|
FDTFluxParsingTask::FDTFluxParsingTask(
|
||||||
|
const FGuid& InRequestId,
|
||||||
|
const FString& InRawJsonData,
|
||||||
|
FOnParsingCompleted InOnCompleted,
|
||||||
|
FOnParsingFailed InOnFailed
|
||||||
|
)
|
||||||
|
: RequestId(InRequestId)
|
||||||
|
, RawJsonData(InRawJsonData)
|
||||||
|
, OnCompleted(InOnCompleted)
|
||||||
|
, OnFailed(InOnFailed)
|
||||||
|
, StartTime(FPlatformTime::Seconds())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxParsingTask::DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
|
||||||
|
{
|
||||||
|
SCOPE_CYCLE_COUNTER(STAT_FDTFluxParsingTask_DoWork);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("Starting async parsing for request %s"), *RequestId.ToString());
|
||||||
|
|
||||||
|
TSharedPtr<FDTFluxServerResponse> ParsedResponse;
|
||||||
|
bool bParsingSuccess = false;
|
||||||
|
FString ErrorMessage;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// === PARSING SUR LE THREAD WORKER ===
|
||||||
|
|
||||||
|
EDTFluxResponseStatus Status;
|
||||||
|
ParsedResponse = MakeShared<FDTFluxServerResponse>(RawJsonData, Status, false); // Pas de logs sur worker thread
|
||||||
|
|
||||||
|
if (Status == EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
bParsingSuccess = true;
|
||||||
|
UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("Async parsing successful for request %s"),
|
||||||
|
*RequestId.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = FString::Printf(TEXT("Parsing failed with status: %s"),
|
||||||
|
*UEnum::GetValueAsString(Status));
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Async parsing failed for request %s: %s"),
|
||||||
|
*RequestId.ToString(), *ErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ErrorMessage = FString::Printf(TEXT("Exception during parsing: %s"), ANSI_TO_TCHAR(e.what()));
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Exception during async parsing for request %s: %s"),
|
||||||
|
*RequestId.ToString(), *ErrorMessage);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
ErrorMessage = TEXT("Unknown exception during parsing");
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Unknown exception during async parsing for request %s"),
|
||||||
|
*RequestId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const float ParsingTime = (FPlatformTime::Seconds() - StartTime) * 1000.0f; // En millisecondes
|
||||||
|
|
||||||
|
// === PROGRAMMER LA CALLBACK SUR LE MAIN THREAD ===
|
||||||
|
|
||||||
|
FFunctionGraphTask::CreateAndDispatchWhenReady(
|
||||||
|
[this, ParsedResponse, bParsingSuccess, ErrorMessage, ParsingTime]()
|
||||||
|
{
|
||||||
|
// Cette lambda s'exécute sur le main thread
|
||||||
|
if (bParsingSuccess && ParsedResponse.IsValid())
|
||||||
|
{
|
||||||
|
OnCompleted.ExecuteIfBound(RequestId, ParsedResponse, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnFailed.ExecuteIfBound(RequestId, ErrorMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TStatId(),
|
||||||
|
nullptr,
|
||||||
|
ENamedThreads::GameThread // Forcer l'exécution sur le main thread
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// IMPLÉMENTATION DU PARSER ASYNCHRONE
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
FDTFluxAsyncParser::FDTFluxAsyncParser()
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("AsyncParser initialized"));
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxAsyncParser::~FDTFluxAsyncParser()
|
||||||
|
{
|
||||||
|
CancelAllParsing();
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("AsyncParser destroyed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAsyncParser::ParseResponseAsync(
|
||||||
|
const FGuid& RequestId,
|
||||||
|
const FString& RawJsonData,
|
||||||
|
FOnParsingCompleted OnCompleted,
|
||||||
|
FOnParsingFailed OnFailed)
|
||||||
|
{
|
||||||
|
if (RawJsonData.IsEmpty())
|
||||||
|
{
|
||||||
|
OnFailed.ExecuteIfBound(RequestId, TEXT("Empty JSON data"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Starting async parsing for request %s"), *RequestId.ToString());
|
||||||
|
|
||||||
|
// Créer la tâche de parsing
|
||||||
|
FGraphEventRef Task = FFunctionGraphTask::CreateAndDispatchWhenReady(
|
||||||
|
[RequestId, RawJsonData, OnCompleted, OnFailed]()
|
||||||
|
{
|
||||||
|
// Ce code s'exécute sur le worker thread
|
||||||
|
const double StartTime = FPlatformTime::Seconds();
|
||||||
|
|
||||||
|
TSharedPtr<FDTFluxServerResponse> ParsedResponse;
|
||||||
|
bool bParsingSuccess = false;
|
||||||
|
FString ErrorMessage;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
EDTFluxResponseStatus Status;
|
||||||
|
ParsedResponse = MakeShared<FDTFluxServerResponse>(RawJsonData, Status, false);
|
||||||
|
|
||||||
|
if (Status == EDTFluxResponseStatus::Success)
|
||||||
|
{
|
||||||
|
bParsingSuccess = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = FString::Printf(TEXT("Parsing failed with status: %s"),
|
||||||
|
*UEnum::GetValueAsString(Status));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
|
ErrorMessage = FString::Printf(TEXT("Exception during parsing: %s"), ANSI_TO_TCHAR(e.what()));
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
ErrorMessage = TEXT("Unknown exception during parsing");
|
||||||
|
}
|
||||||
|
|
||||||
|
const float ParsingTime = (FPlatformTime::Seconds() - StartTime) * 1000.0f;
|
||||||
|
FFunctionGraphTask::CreateAndDispatchWhenReady(
|
||||||
|
[RequestId, ParsedResponse, bParsingSuccess, ErrorMessage, OnCompleted, OnFailed]()
|
||||||
|
{
|
||||||
|
// Cette lambda s'exécute sur le main thread
|
||||||
|
if (bParsingSuccess && ParsedResponse.IsValid())
|
||||||
|
{
|
||||||
|
OnCompleted.ExecuteIfBound(RequestId, ParsedResponse, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnFailed.ExecuteIfBound(RequestId, ErrorMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TStatId(),
|
||||||
|
nullptr,
|
||||||
|
ENamedThreads::GameThread // Forcer main thread
|
||||||
|
);
|
||||||
|
},
|
||||||
|
TStatId(),
|
||||||
|
nullptr,
|
||||||
|
ENamedThreads::AnyBackgroundThreadNormalTask
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tracker la tâche
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&TasksLock);
|
||||||
|
ActiveTasks.Add(Task);
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Queued async parsing task for request %s"), *RequestId.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FDTFluxServerResponse> FDTFluxAsyncParser::ParseResponseSync(
|
||||||
|
const FString& RawJsonData,
|
||||||
|
float TimeoutSeconds)
|
||||||
|
{
|
||||||
|
if (RawJsonData.IsEmpty())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variables pour la synchronisation
|
||||||
|
TSharedPtr<FDTFluxServerResponse> Result;
|
||||||
|
std::atomic<bool> bCompleted{false};
|
||||||
|
|
||||||
|
// Lancer le parsing async avec callback sync
|
||||||
|
FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateLambda(
|
||||||
|
[&Result, &bCompleted](const FGuid& RequestId, TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess)
|
||||||
|
{
|
||||||
|
if (bSuccess)
|
||||||
|
{
|
||||||
|
Result = ParsedResponse;
|
||||||
|
}
|
||||||
|
bCompleted.store(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FOnParsingFailed OnFailed = FOnParsingFailed::CreateLambda(
|
||||||
|
[&bCompleted](const FGuid& RequestId, const FString& ErrorMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Sync parsing failed: %s"), *ErrorMessage);
|
||||||
|
bCompleted.store(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
FGuid TempId = FGuid::NewGuid();
|
||||||
|
ParseResponseAsync(TempId, RawJsonData, OnCompleted, OnFailed);
|
||||||
|
|
||||||
|
// Attendre avec timeout
|
||||||
|
const double StartTime = FPlatformTime::Seconds();
|
||||||
|
while (!bCompleted.load() && (FPlatformTime::Seconds() - StartTime) < TimeoutSeconds)
|
||||||
|
{
|
||||||
|
FPlatformProcess::Sleep(0.001f); // 1ms
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAsyncParser::CancelAllParsing()
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&TasksLock);
|
||||||
|
|
||||||
|
for (const FGraphEventRef& Task : ActiveTasks)
|
||||||
|
{
|
||||||
|
// Note: On ne peut pas vraiment "cancel" une tâche TaskGraph en cours,
|
||||||
|
// mais on peut marquer qu'on ne veut plus les résultats
|
||||||
|
}
|
||||||
|
|
||||||
|
ActiveTasks.Empty();
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Cancelled all pending parsing tasks"));
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxAsyncParser::FParsingStats FDTFluxAsyncParser::GetStats() const
|
||||||
|
{
|
||||||
|
FScopeLock StatsLock_Local(&StatsLock);
|
||||||
|
FScopeLock TasksLock_Local(&TasksLock);
|
||||||
|
|
||||||
|
FParsingStats Stats;
|
||||||
|
Stats.TasksInProgress = ActiveTasks.Num();
|
||||||
|
Stats.TasksCompleted = TasksCompletedCount;
|
||||||
|
Stats.TasksFailed = TasksFailedCount;
|
||||||
|
|
||||||
|
if (ParsingTimes.Num() > 0)
|
||||||
|
{
|
||||||
|
float Sum = 0.0f;
|
||||||
|
for (float Time : ParsingTimes)
|
||||||
|
{
|
||||||
|
Sum += Time;
|
||||||
|
}
|
||||||
|
Stats.AverageParsingTimeMs = Sum / ParsingTimes.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAsyncParser::ResetStats()
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&StatsLock);
|
||||||
|
TasksCompletedCount = 0;
|
||||||
|
TasksFailedCount = 0;
|
||||||
|
ParsingTimes.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAsyncParser::OnTaskCompleted(bool bSuccess, float ParsingTimeMs)
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&StatsLock);
|
||||||
|
|
||||||
|
if (bSuccess)
|
||||||
|
{
|
||||||
|
TasksCompletedCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TasksFailedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsingTimes.Add(ParsingTimeMs);
|
||||||
|
|
||||||
|
// Garder seulement les 100 derniers temps pour la moyenne
|
||||||
|
if (ParsingTimes.Num() > 100)
|
||||||
|
{
|
||||||
|
ParsingTimes.RemoveAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxAsyncParser::CleanupCompletedTasks()
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&TasksLock);
|
||||||
|
|
||||||
|
for (auto It = ActiveTasks.CreateIterator(); It; ++It)
|
||||||
|
{
|
||||||
|
const FGraphEventRef& Task = *It;
|
||||||
|
if (Task.IsValid() && Task->IsComplete())
|
||||||
|
{
|
||||||
|
It.RemoveCurrent(); // Supprime l'élément actuel de manière sécurisée
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
670
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
670
Source/DTFluxNetwork/Private/DTFluxQueuedManager.cpp
Normal file
@ -0,0 +1,670 @@
|
|||||||
|
// ================================================================================================
|
||||||
|
// DTFluxRequestManager.cpp - Implémentation du gestionnaire de requêtes
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
#include "DTFluxQueuedManager.h"
|
||||||
|
|
||||||
|
#include "DTFluxAsyncParser.h"
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "Struct/DTFluxRequestStructs.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
|
||||||
|
bool FDTFluxTrackedRequest::HasTimedOut() const
|
||||||
|
{
|
||||||
|
if (State != EDTFluxRequestState::Pending && State != EDTFluxRequestState::Sent)
|
||||||
|
return false;
|
||||||
|
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > Config.TimeoutSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxTrackedRequest::CanRetry() const
|
||||||
|
{
|
||||||
|
return CurrentRetries < Config.MaxRetries &&
|
||||||
|
(State == EDTFluxRequestState::Failed || State == EDTFluxRequestState::TimedOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float FDTFluxTrackedRequest::GetRetryDelay() const
|
||||||
|
{
|
||||||
|
return FMath::Pow(Config.RetryBackoffMultiplier, CurrentRetries);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxTrackedRequest::Matches(EDTFluxApiDataType InType, int32 InContestId, int32 InStageId,
|
||||||
|
int32 InSplitId) const
|
||||||
|
{
|
||||||
|
return RequestType == InType && ContestId == InContestId && StageId == InStageId && SplitId == InSplitId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void FDTFluxTrackedRequest::SetRawResponse(const FString& RawData)
|
||||||
|
{
|
||||||
|
RawResponseData = RawData;
|
||||||
|
ParsedResponse.Reset();
|
||||||
|
bIsResponseParsed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString FDTFluxTrackedRequest::Serialize() const
|
||||||
|
{
|
||||||
|
FString JSONString;
|
||||||
|
switch (RequestType)
|
||||||
|
{
|
||||||
|
case EDTFluxApiDataType::RaceData:
|
||||||
|
{
|
||||||
|
FDTFluxRaceDataRequest RaceData;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::TeamList:
|
||||||
|
{
|
||||||
|
const FDTFluxTeamListRequest TeamList;
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::ContestRanking:
|
||||||
|
{
|
||||||
|
FDTFluxContestRankingRequest ContestRanking(ContestId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::StageRanking:
|
||||||
|
{
|
||||||
|
FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EDTFluxApiDataType::SplitRanking:
|
||||||
|
{
|
||||||
|
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
|
||||||
|
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
JSONString = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return JSONString;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxQueuedRequestManager::FDTFluxQueuedRequestManager()
|
||||||
|
{
|
||||||
|
AsyncParser = MakeUnique<FDTFluxAsyncParser>();
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager created"));
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxQueuedRequestManager::~FDTFluxQueuedRequestManager()
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager destroyed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefaultConfig)
|
||||||
|
{
|
||||||
|
if (bIsInitialized.load())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("RequestManager already initialized"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultConfig = InDefaultConfig;
|
||||||
|
bIsInitialized.store(true);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs"),
|
||||||
|
DefaultConfig.TimeoutSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::Shutdown()
|
||||||
|
{
|
||||||
|
if (!bIsInitialized.load())
|
||||||
|
return;
|
||||||
|
|
||||||
|
bIsInitialized.store(false);
|
||||||
|
// Nettoyer toutes les données
|
||||||
|
{
|
||||||
|
FScopeLock RequestsLock_Local(&RequestsLock);
|
||||||
|
FScopeLock CallbacksLock_Local(&CallbacksLock);
|
||||||
|
|
||||||
|
AllRequests.Empty();
|
||||||
|
SuccessCallbacks.Empty();
|
||||||
|
ErrorCallbacks.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager shutdown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
const FDTFluxRequestConfig& CustomConfig)
|
||||||
|
{
|
||||||
|
if (!bIsInitialized.load())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("RequestManager not initialized"));
|
||||||
|
return FGuid();
|
||||||
|
}
|
||||||
|
// Create new request
|
||||||
|
auto NewRequest = MakeShared<FDTFluxTrackedRequest>();
|
||||||
|
NewRequest->RequestType = RequestType;
|
||||||
|
NewRequest->ContestId = ContestId;
|
||||||
|
NewRequest->StageId = StageId;
|
||||||
|
NewRequest->SplitId = SplitId;
|
||||||
|
NewRequest->Config = (CustomConfig.TimeoutSeconds > 0) ? CustomConfig : DefaultConfig;
|
||||||
|
|
||||||
|
FGuid RequestId = NewRequest->RequestId;
|
||||||
|
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
AllRequests.Add(RequestId, NewRequest);
|
||||||
|
TotalRequests++;
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Created tracked request %s: Type=%s, Contest=%d, Stage=%d, Split=%d"),
|
||||||
|
*RequestId.ToString(), *UEnum::GetValueAsString(RequestType), ContestId, StageId, SplitId);
|
||||||
|
return RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
FGuid FDTFluxQueuedRequestManager::CreateTrackedRequestWithCallbacks(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
FOnDTFluxRequestSuccess OnSuccess,
|
||||||
|
FOnDTFluxRequestError OnError,
|
||||||
|
const FDTFluxRequestConfig& CustomConfig)
|
||||||
|
{
|
||||||
|
FGuid RequestId = CreateTrackedRequest(RequestType, ContestId, StageId, SplitId, CustomConfig);
|
||||||
|
|
||||||
|
if (RequestId.IsValid())
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&CallbacksLock);
|
||||||
|
|
||||||
|
if (OnSuccess.IsBound())
|
||||||
|
{
|
||||||
|
SuccessCallbacks.Add(RequestId, OnSuccess);
|
||||||
|
}
|
||||||
|
if (OnError.IsBound())
|
||||||
|
{
|
||||||
|
ErrorCallbacks.Add(RequestId, OnError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return RequestId;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxQueuedRequestManager::MarkRequestAsSent(const FGuid& RequestId)
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
TSharedPtr<FDTFluxTrackedRequest> Request = *RequestPtr;
|
||||||
|
Request->SentAt = FDateTime::Now();
|
||||||
|
Request->LastAttemptTime = FDateTime::Now();
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Sent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxQueuedRequestManager::CompleteRequest(const FGuid& RequestId, const FString& RawResponseData,
|
||||||
|
bool bUseAsyncParsing)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::CompleteRequest() %s"), *RequestId.ToString());
|
||||||
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
Request = *RequestPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!Request.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("Request %s not found"), *RequestId.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store RawResponse
|
||||||
|
Request->SetRawResponse(RawResponseData);
|
||||||
|
Request->CompletedAt = FDateTime::Now();
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Request %s completed at %s"), *RequestId.ToString(),
|
||||||
|
*Request->CompletedAt.ToString());
|
||||||
|
// Decide to parse based upon config
|
||||||
|
bool bHasCallbacks = false;
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&CallbacksLock);
|
||||||
|
bHasCallbacks = SuccessCallbacks.Contains(RequestId) || ErrorCallbacks.Contains(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bHasCallbacks && bUseAsyncParsing && !RawResponseData.IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log,
|
||||||
|
TEXT("Request %s [bHasCallbacks=%s], [bUseAsyncParsing=%s], [bIsRawResponseEmpty=%s]"),
|
||||||
|
*RequestId.ToString(),
|
||||||
|
bHasCallbacks ? TEXT("true") : TEXT("false"), bUseAsyncParsing ? TEXT("true") : TEXT("false"),
|
||||||
|
RawResponseData.IsEmpty() ? TEXT("true") : TEXT("false"));
|
||||||
|
|
||||||
|
// Async parsing for Cb
|
||||||
|
FOnParsingCompleted OnCompleted = FOnParsingCompleted::CreateRaw(
|
||||||
|
this, &FDTFluxQueuedRequestManager::OnParsingCompleted
|
||||||
|
);
|
||||||
|
|
||||||
|
FOnParsingFailed OnFailed = FOnParsingFailed::CreateRaw(
|
||||||
|
this, &FDTFluxQueuedRequestManager::OnParsingFailed
|
||||||
|
);
|
||||||
|
// Maybe send to parser in another place
|
||||||
|
AsyncParser->ParseResponseAsync(RequestId, RawResponseData, OnCompleted, OnFailed);
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Started async parsing for request %s"), *RequestId.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Warning, TEXT("request %s completed without sync"), *RequestId.ToString());
|
||||||
|
// Compléter immédiatement sans parsing ou avec parsing sync
|
||||||
|
EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
|
||||||
|
ChangeRequestState(Request, NewState);
|
||||||
|
// Déclencher les callbacks avec les données brutes
|
||||||
|
TriggerCallbacks(*Request);
|
||||||
|
CleanupCallbacks(RequestId);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Check protocol errors ???
|
||||||
|
* @param RequestId
|
||||||
|
* @param ErrorMessage
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
|
||||||
|
{
|
||||||
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
||||||
|
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
Request = *RequestPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Request.IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request->LastErrorMessage = ErrorMessage;
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Failed);
|
||||||
|
|
||||||
|
TriggerCallbacks(*Request);
|
||||||
|
CleanupCallbacks(RequestId);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Failed request %s: %s"), *RequestId.ToString(), *ErrorMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxQueuedRequestManager::RetryRequest(const FGuid& RequestId)
|
||||||
|
{
|
||||||
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
||||||
|
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
Request = *RequestPtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Request.IsValid() || !Request->CanRetry())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request->CurrentRetries++;
|
||||||
|
Request->LastAttemptTime = FDateTime::Now();
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Retrying);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Retrying request %s (attempt %d/%d)"),
|
||||||
|
*RequestId.ToString(), Request->CurrentRetries, Request->Config.MaxRetries);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FDTFluxQueuedRequestManager::FindPendingRequest(
|
||||||
|
FGuid& OutRequestId,
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId) const
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
for (const auto& [RequestId, Request] : AllRequests)
|
||||||
|
{
|
||||||
|
if ((Request->State == EDTFluxRequestState::Pending || Request->State == EDTFluxRequestState::Sent) &&
|
||||||
|
Request->Matches(RequestType, ContestId, StageId, SplitId))
|
||||||
|
{
|
||||||
|
OutRequestId = RequestId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ACCESSEURS ===
|
||||||
|
|
||||||
|
bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
OutRequest = **RequestPtr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FDTFluxTrackedRequest* FDTFluxQueuedRequestManager::GetRequestPtr(const FGuid& RequestId) const
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
|
||||||
|
{
|
||||||
|
return RequestPtr->Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FDTFluxTrackedRequest> FDTFluxQueuedRequestManager::GetRequestsByState(EDTFluxRequestState State) const
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
TArray<FDTFluxTrackedRequest> Results;
|
||||||
|
|
||||||
|
for (const auto& [RequestId, Request] : AllRequests)
|
||||||
|
{
|
||||||
|
if (Request->State == State)
|
||||||
|
{
|
||||||
|
Results.Add(*Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Results;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 FDTFluxQueuedRequestManager::GetRequestCount(EDTFluxRequestState State) const
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
int32 Count = 0;
|
||||||
|
|
||||||
|
for (const auto& [RequestId, Request] : AllRequests)
|
||||||
|
{
|
||||||
|
if (Request->State == State)
|
||||||
|
{
|
||||||
|
Count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::GetStatistics() const
|
||||||
|
{
|
||||||
|
FScopeLock RequestsLock_Local(&RequestsLock);
|
||||||
|
FScopeLock MetricsLock_Local(&MetricsLock);
|
||||||
|
|
||||||
|
FRequestStatistics Stats;
|
||||||
|
|
||||||
|
for (const auto& [RequestId, Request] : AllRequests)
|
||||||
|
{
|
||||||
|
switch (Request->State)
|
||||||
|
{
|
||||||
|
case EDTFluxRequestState::Pending:
|
||||||
|
case EDTFluxRequestState::Sent:
|
||||||
|
case EDTFluxRequestState::Retrying:
|
||||||
|
Stats.Pending++;
|
||||||
|
break;
|
||||||
|
case EDTFluxRequestState::Completed:
|
||||||
|
Stats.Completed++;
|
||||||
|
break;
|
||||||
|
case EDTFluxRequestState::Failed:
|
||||||
|
case EDTFluxRequestState::TimedOut:
|
||||||
|
Stats.Failed++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Stats.TotalRequests = TotalRequests;
|
||||||
|
return Stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === NETTOYAGE ===
|
||||||
|
int32 FDTFluxQueuedRequestManager::CleanupCompletedRequests(float OlderThanSeconds)
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
TArray<FGuid> OldRequests;
|
||||||
|
const FDateTime Threshold = FDateTime::Now() - FTimespan::FromSeconds(OlderThanSeconds);
|
||||||
|
|
||||||
|
for (const auto& [RequestId, Request] : AllRequests)
|
||||||
|
{
|
||||||
|
if ((Request->State == EDTFluxRequestState::Completed || Request->State == EDTFluxRequestState::Failed ||
|
||||||
|
Request->State == EDTFluxRequestState::TimedOut) && Request->CompletedAt < Threshold)
|
||||||
|
{
|
||||||
|
OldRequests.Add(RequestId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const FGuid& RequestId : OldRequests)
|
||||||
|
{
|
||||||
|
AllRequests.Remove(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return OldRequests.Num();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::ClearAllRequests()
|
||||||
|
{
|
||||||
|
FScopeLock RequestsLock_Local(&RequestsLock);
|
||||||
|
FScopeLock CallbacksLock_Local(&CallbacksLock);
|
||||||
|
|
||||||
|
AllRequests.Empty();
|
||||||
|
SuccessCallbacks.Empty();
|
||||||
|
ErrorCallbacks.Empty();
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all requests"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::Tick(float DeltaTime)
|
||||||
|
{
|
||||||
|
if (!bIsInitialized.load())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Mise à jour des timers
|
||||||
|
TimeSinceLastTimeoutCheck += DeltaTime;
|
||||||
|
TimeSinceLastRetryCheck += DeltaTime;
|
||||||
|
|
||||||
|
// Vérifier les timeouts
|
||||||
|
if (TimeSinceLastTimeoutCheck >= TimeoutCheckInterval)
|
||||||
|
{
|
||||||
|
ProcessTimeouts();
|
||||||
|
TimeSinceLastTimeoutCheck = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier les retries
|
||||||
|
if (TimeSinceLastRetryCheck >= RetryCheckInterval)
|
||||||
|
{
|
||||||
|
ProcessRetries();
|
||||||
|
TimeSinceLastRetryCheck = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request,
|
||||||
|
EDTFluxRequestState NewState)
|
||||||
|
{
|
||||||
|
if (!Request.IsValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const EDTFluxRequestState OldState = Request->State;
|
||||||
|
Request->State = NewState;
|
||||||
|
|
||||||
|
// Déclencher l'événement de changement d'état
|
||||||
|
OnRequestStateChanged.Broadcast(Request->RequestId, NewState);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, VeryVerbose, TEXT("DTFluxQueuedRequestManager: Request %s state changed from %s to %s"),
|
||||||
|
*Request->RequestId.ToString(),
|
||||||
|
*UEnum::GetValueAsString(OldState),
|
||||||
|
*UEnum::GetValueAsString(NewState));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::ProcessTimeouts()
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
TArray<TSharedPtr<FDTFluxTrackedRequest>> TimedOutRequests;
|
||||||
|
|
||||||
|
for (const auto& Pair : AllRequests)
|
||||||
|
{
|
||||||
|
const auto& Request = Pair.Value;
|
||||||
|
if (Request->HasTimedOut())
|
||||||
|
{
|
||||||
|
TimedOutRequests.Add(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& Request : TimedOutRequests)
|
||||||
|
{
|
||||||
|
Request->LastErrorMessage = FString::Printf(
|
||||||
|
TEXT("Request timed out after %.1f seconds"), Request->Config.TimeoutSeconds);
|
||||||
|
|
||||||
|
if (Request->CanRetry())
|
||||||
|
{
|
||||||
|
Request->CurrentRetries++;
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Retrying);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::TimedOut);
|
||||||
|
TriggerCallbacks(*Request);
|
||||||
|
OnRequestFailed.Broadcast(*Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::ProcessRetries()
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
const FDateTime Now = FDateTime::Now();
|
||||||
|
TArray<TSharedPtr<FDTFluxTrackedRequest>> ReadyToRetry;
|
||||||
|
|
||||||
|
for (const auto& Pair : AllRequests)
|
||||||
|
{
|
||||||
|
const auto& Request = Pair.Value;
|
||||||
|
if (Request->State == EDTFluxRequestState::Retrying)
|
||||||
|
{
|
||||||
|
const float ElapsedSinceLastAttempt = (Now - Request->LastAttemptTime).GetTotalSeconds();
|
||||||
|
if (ElapsedSinceLastAttempt >= Request->GetRetryDelay())
|
||||||
|
{
|
||||||
|
ReadyToRetry.Add(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& Request : ReadyToRetry)
|
||||||
|
{
|
||||||
|
Request->LastAttemptTime = Now;
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Pending);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&CallbacksLock);
|
||||||
|
|
||||||
|
if (Request.State == EDTFluxRequestState::Completed)
|
||||||
|
{
|
||||||
|
// Success Cb
|
||||||
|
const FOnDTFluxRequestSuccess* SuccessCallback = SuccessCallbacks.Find(Request.RequestId);
|
||||||
|
if (SuccessCallback && SuccessCallback->IsBound())
|
||||||
|
{
|
||||||
|
SuccessCallback->Execute(Request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Request.State == EDTFluxRequestState::Failed || Request.State == EDTFluxRequestState::TimedOut)
|
||||||
|
{
|
||||||
|
// Error Cb
|
||||||
|
const FOnDTFluxRequestError* ErrorCallback = ErrorCallbacks.Find(Request.RequestId);
|
||||||
|
if (ErrorCallback && ErrorCallback->IsBound())
|
||||||
|
{
|
||||||
|
ErrorCallback->Execute(Request, Request.LastErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::CleanupCallbacks(const FGuid& RequestId)
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&CallbacksLock);
|
||||||
|
SuccessCallbacks.Remove(RequestId);
|
||||||
|
ErrorCallbacks.Remove(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::OnParsingCompleted(const FGuid& RequestId,
|
||||||
|
TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Log, TEXT("FDTFluxQueuedRequestManager::OnParsingCompleted() request %s"),
|
||||||
|
*RequestId.ToString())
|
||||||
|
TSharedPtr<FDTFluxTrackedRequest> Request;
|
||||||
|
{
|
||||||
|
FScopeLock Lock(&RequestsLock);
|
||||||
|
|
||||||
|
auto* RequestPtr = AllRequests.Find(RequestId);
|
||||||
|
if (!RequestPtr || !RequestPtr->IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error,
|
||||||
|
TEXT(
|
||||||
|
"DTFluxQueuedRequestManager::OnParsingCompleted() RequestId%s [InvalidRequestId=%s], [RequestPtrValid=%s]"
|
||||||
|
),
|
||||||
|
*RequestId.ToString(), RequestPtr ? TEXT("true") : TEXT("false"),
|
||||||
|
RequestPtr->IsValid() ? TEXT("true") : TEXT("false"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request = *RequestPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bSuccess && ParsedResponse.IsValid())
|
||||||
|
{
|
||||||
|
Request->ParsedResponse = ParsedResponse;
|
||||||
|
Request->bIsResponseParsed = true;
|
||||||
|
EDTFluxRequestState NewState = EDTFluxRequestState::Completed;
|
||||||
|
|
||||||
|
ChangeRequestState(Request, NewState);
|
||||||
|
UE_LOG(logDTFluxNetwork, Log,
|
||||||
|
TEXT("DTFluxQueuedRequestManager: Async parsing completed for request %s"),
|
||||||
|
*RequestId.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Request->LastErrorMessage = TEXT("Async parsing failed");
|
||||||
|
ChangeRequestState(Request, EDTFluxRequestState::Failed);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("Async parsing failed for request %s"), *RequestId.ToString());
|
||||||
|
}
|
||||||
|
TriggerCallbacks(*Request);
|
||||||
|
CleanupCallbacks(RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxNetwork, Error, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s: %s"),
|
||||||
|
*RequestId.ToString(),
|
||||||
|
*ErrorMessage);
|
||||||
|
FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage));
|
||||||
|
}
|
||||||
@ -0,0 +1,534 @@
|
|||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "DTFluxNetworkModule.h"
|
||||||
|
#include "Types/Objects/UDTFluxParticipantFactory.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::ParseTeamList(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::ParseTeamUpdate(FDTFluxTeamListDefinition& OutTeamUpdate)
|
||||||
|
{
|
||||||
|
return ParseTeamList(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::ParseStageRanking(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::ParseSplitRanking(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::ParseStatusUpdate(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::ParseSplitSensor(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;
|
||||||
|
NewSplitSensorInfo.Gap = SplitSensorInfoResponse.Gap;
|
||||||
|
NewSplitSensorInfo.Rank = SplitSensorInfoResponse.Rank;
|
||||||
|
|
||||||
|
OutSplitSensorInfos.Add(NewSplitSensorInfo);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Split sensor info for bib %d in Contest %d, Stage %d, Split %d Rank [%i] Gap [%s] Time [%s]"),
|
||||||
|
NewSplitSensorInfo.Bib, NewSplitSensorInfo.ContestId, NewSplitSensorInfo.StageId,
|
||||||
|
NewSplitSensorInfo.SplitId, NewSplitSensorInfo.Rank, *NewSplitSensorInfo.Gap,*NewSplitSensorInfo.Time);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
171
Source/DTFluxNetwork/Public/DTFluxAsyncParser.h
Normal file
171
Source/DTFluxNetwork/Public/DTFluxAsyncParser.h
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// ================================================================================================
|
||||||
|
// DTFluxAsyncParser.h - Async Response Parser
|
||||||
|
// ================================================================================================
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "HAL/CriticalSection.h"
|
||||||
|
#include "Async/TaskGraphInterfaces.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// DELEGATES POUR LE PARSING ASYNCHRONE
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_ThreeParams(FOnParsingCompleted, const FGuid& /*RequestId*/,
|
||||||
|
TSharedPtr<FDTFluxServerResponse> /*ParsedResponse*/, bool /*bSuccess*/);
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnParsingFailed, const FGuid& /*RequestId*/, const FString& /*ErrorMessage*/);
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// ASYNC PARSER - Délégation du parsing avec TaskGraph
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestionnaire centralisé pour le parsing asynchrone des réponses JSON
|
||||||
|
* Utilise le TaskGraph d'Unreal Engine pour déléguer le parsing sur des worker threads
|
||||||
|
*/
|
||||||
|
class DTFLUXNETWORK_API FDTFluxAsyncParser
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FDTFluxAsyncParser();
|
||||||
|
~FDTFluxAsyncParser();
|
||||||
|
|
||||||
|
// === INTERFACE PUBLIQUE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lancer le parsing asynchrone d'une réponse JSON
|
||||||
|
* @param RequestId - ID de la requête pour le suivi
|
||||||
|
* @param RawJsonData - Données JSON brutes à parser
|
||||||
|
* @param OnCompleted - Callback appelé en cas de succès (sur main thread)
|
||||||
|
* @param OnFailed - Callback appelé en cas d'échec (sur main thread)
|
||||||
|
*/
|
||||||
|
void ParseResponseAsync(
|
||||||
|
const FGuid& RequestId,
|
||||||
|
const FString& RawJsonData,
|
||||||
|
FOnParsingCompleted OnCompleted,
|
||||||
|
FOnParsingFailed OnFailed = FOnParsingFailed()
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parsing synchrone avec timeout (pour les cas urgents)
|
||||||
|
* @param RawJsonData - Données JSON à parser
|
||||||
|
* @param TimeoutSeconds - Timeout maximum pour le parsing
|
||||||
|
* @return Réponse parsée ou nullptr en cas d'échec
|
||||||
|
*/
|
||||||
|
TSharedPtr<FDTFluxServerResponse> ParseResponseSync(
|
||||||
|
const FString& RawJsonData,
|
||||||
|
float TimeoutSeconds = 1.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Annuler toutes les tâches de parsing en attente
|
||||||
|
*/
|
||||||
|
void CancelAllParsing();
|
||||||
|
|
||||||
|
// === STATISTIQUES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistiques de performance du parsing
|
||||||
|
*/
|
||||||
|
struct FParsingStats
|
||||||
|
{
|
||||||
|
int32 TasksInProgress = 0; // Tâches actuellement en cours
|
||||||
|
int32 TasksCompleted = 0; // Tâches terminées avec succès
|
||||||
|
int32 TasksFailed = 0; // Tâches échouées
|
||||||
|
float AverageParsingTimeMs = 0.0f; // Temps moyen de parsing en ms
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtenir les statistiques de parsing
|
||||||
|
*/
|
||||||
|
FParsingStats GetStats() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Réinitialiser les statistiques
|
||||||
|
*/
|
||||||
|
void ResetStats();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// === TRACKING DES TÂCHES ===
|
||||||
|
mutable FCriticalSection TasksLock;
|
||||||
|
TSet<FGraphEventRef> ActiveTasks;
|
||||||
|
|
||||||
|
// === STATISTIQUES ===
|
||||||
|
mutable FCriticalSection StatsLock;
|
||||||
|
mutable int32 TasksCompletedCount = 0;
|
||||||
|
mutable int32 TasksFailedCount = 0;
|
||||||
|
mutable TArray<float> ParsingTimes; // Historique des temps de parsing
|
||||||
|
|
||||||
|
// === MÉTHODES PRIVÉES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand une tâche se termine
|
||||||
|
* @param bSuccess - Succès ou échec de la tâche
|
||||||
|
* @param ParsingTimeMs - Temps de parsing en millisecondes
|
||||||
|
*/
|
||||||
|
void OnTaskCompleted(bool bSuccess, float ParsingTimeMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoyer les tâches terminées de la liste active
|
||||||
|
*/
|
||||||
|
void CleanupCompletedTasks();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// TÂCHE DE PARSING POUR LE TASKGRAPH
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tâche de parsing JSON exécutée sur un thread worker
|
||||||
|
* Compatible avec le TaskGraph d'Unreal Engine
|
||||||
|
*/
|
||||||
|
class FDTFluxParsingTask
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FDTFluxParsingTask(
|
||||||
|
const FGuid& InRequestId,
|
||||||
|
const FString& InRawJsonData,
|
||||||
|
FOnParsingCompleted InOnCompleted,
|
||||||
|
FOnParsingFailed InOnFailed
|
||||||
|
);
|
||||||
|
|
||||||
|
// === INTERFACE TASK GRAPH ===
|
||||||
|
|
||||||
|
FORCEINLINE TStatId GetStatId() const
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FDTFluxParsingTask, STATGROUP_TaskGraphTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FORCEINLINE TStatId GetStatId_DoWork()
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FDTFluxParsingTask_DoWork, STATGROUP_TaskGraphTasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FORCEINLINE ENamedThreads::Type GetDesiredThread()
|
||||||
|
{
|
||||||
|
// Exécuter sur un thread worker (pas le main thread)
|
||||||
|
return ENamedThreads::AnyBackgroundThreadNormalTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static FORCEINLINE ESubsequentsMode::Type GetSubsequentsMode()
|
||||||
|
{
|
||||||
|
return ESubsequentsMode::TrackSubsequents;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === EXÉCUTION DE LA TÂCHE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode principale d'exécution de la tâche
|
||||||
|
* Appelée automatiquement par le TaskGraph sur un worker thread
|
||||||
|
*/
|
||||||
|
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent);
|
||||||
|
|
||||||
|
private:
|
||||||
|
FGuid RequestId;
|
||||||
|
FString RawJsonData;
|
||||||
|
FOnParsingCompleted OnCompleted;
|
||||||
|
FOnParsingFailed OnFailed;
|
||||||
|
double StartTime;
|
||||||
|
};
|
||||||
375
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
375
Source/DTFluxNetwork/Public/DTFluxQueuedManager.h
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
// ================================================================================================
|
||||||
|
// DTFluxRequestManager.h - Gestionnaire C++ optimisé avec cache, timeout et retry
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Tickable.h"
|
||||||
|
#include "HAL/CriticalSection.h"
|
||||||
|
#include "Struct/DTFluxServerResponseStruct.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "DTFluxQueuedManager.generated.h"
|
||||||
|
|
||||||
|
class FDTFluxAsyncParser;
|
||||||
|
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// ENUMS ET STRUCTURES POUR LES REQUÊTES
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDTFluxRequestState : uint8
|
||||||
|
{
|
||||||
|
Pending UMETA(DisplayName = "Pending"),
|
||||||
|
Sent UMETA(DisplayName = "Sent"),
|
||||||
|
Completed UMETA(DisplayName = "Completed"),
|
||||||
|
Failed UMETA(DisplayName = "Failed"),
|
||||||
|
TimedOut UMETA(DisplayName = "TimedOut"),
|
||||||
|
Retrying UMETA(DisplayName = "Retrying")
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXNETWORK_API FDTFluxRequestConfig
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
float TimeoutSeconds = 5.0f;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
int32 MaxRetries = 3;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite)
|
||||||
|
float RetryBackoffMultiplier = 1.5f;
|
||||||
|
};
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct DTFLUXNETWORK_API FDTFluxTrackedRequest
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
// === IDENTIFICATION ===
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FGuid RequestId = FGuid::NewGuid();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
EDTFluxApiDataType RequestType = EDTFluxApiDataType::None;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
int32 ContestId = -1;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
int32 StageId = -1;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
int32 SplitId = -1;
|
||||||
|
|
||||||
|
// === ÉTAT ET TIMING ===
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
EDTFluxRequestState State = EDTFluxRequestState::Pending;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FDateTime CreatedAt = FDateTime::Now();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FDateTime SentAt = FDateTime::MinValue();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FDateTime CompletedAt = FDateTime::MinValue();
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FDateTime LastAttemptTime = FDateTime::Now();
|
||||||
|
|
||||||
|
// === CONFIGURATION ===
|
||||||
|
FDTFluxRequestConfig Config;
|
||||||
|
|
||||||
|
// === RETRY LOGIC ===
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
int32 CurrentRetries = 0;
|
||||||
|
|
||||||
|
// === DONNÉES DE RÉPONSE ===
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString RawResponseData;
|
||||||
|
|
||||||
|
// Réponse parsée (lazy loading)
|
||||||
|
mutable TOptional<TSharedPtr<FDTFluxServerResponse>> ParsedResponse;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
bool bIsResponseParsed = false;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadOnly)
|
||||||
|
FString LastErrorMessage;
|
||||||
|
|
||||||
|
// === MÉTHODES UTILITAIRES ===
|
||||||
|
|
||||||
|
bool HasTimedOut() const;
|
||||||
|
bool CanRetry() const;
|
||||||
|
float GetRetryDelay() const;
|
||||||
|
bool Matches(EDTFluxApiDataType InType, int32 InContestId = -1, int32 InStageId = -1, int32 InSplitId = -1) const;
|
||||||
|
void SetRawResponse(const FString& RawData);
|
||||||
|
FString Serialize() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// DELEGATES POUR LES CALLBACKS
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnDTFluxRequestSuccess, const FDTFluxTrackedRequest&);
|
||||||
|
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestError, const FDTFluxTrackedRequest&, const FString& /*ErrorMessage*/);
|
||||||
|
|
||||||
|
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnRequestStateChangedNative, const FGuid& /*RequestId*/,
|
||||||
|
EDTFluxRequestState& /*NewState*/);
|
||||||
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestCompletedNative, const FDTFluxTrackedRequest& /*CompletedRequest*/);
|
||||||
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRequestFailedNative, const FDTFluxTrackedRequest& /*FailedRequest*/);
|
||||||
|
|
||||||
|
// ================================================================================================
|
||||||
|
// REQUEST MANAGER - Classe C++ principale avec SmartPointers
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gestionnaire de requêtes trackées timeout, retry et parsing asynchrone
|
||||||
|
* Implémentation C++ pure avec SmartPointers pour des performances optimales
|
||||||
|
*/
|
||||||
|
class DTFLUXNETWORK_API FDTFluxQueuedRequestManager : public FTickableGameObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FDTFluxQueuedRequestManager();
|
||||||
|
virtual ~FDTFluxQueuedRequestManager();
|
||||||
|
|
||||||
|
// === LIFECYCLE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialiser le gestionnaire de requêtes
|
||||||
|
* @param DefaultConfig Configuration par défaut pour les nouvelles requêtes
|
||||||
|
*/
|
||||||
|
void Initialize(const FDTFluxRequestConfig& DefaultConfig = FDTFluxRequestConfig());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arrêter le gestionnaire et nettoyer toutes les ressources
|
||||||
|
*/
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vérifier si le gestionnaire est initialisé
|
||||||
|
*/
|
||||||
|
bool IsInitialized() const { return bIsInitialized.load(); }
|
||||||
|
|
||||||
|
// === CRÉATION DE REQUÊTES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Créer une nouvelle requête trackée
|
||||||
|
* @param RequestType Type de requête (ContestRanking, StageRanking, etc.)
|
||||||
|
* @param ContestId ID du contest (-1 si non applicable)
|
||||||
|
* @param StageId ID du stage (-1 si non applicable)
|
||||||
|
* @param SplitId ID du split (-1 si non applicable)
|
||||||
|
* @param CustomConfig Configuration spécifique pour cette requête
|
||||||
|
* @return GUID de la requête créée
|
||||||
|
*/
|
||||||
|
FGuid CreateTrackedRequest(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId = -1,
|
||||||
|
int32 StageId = -1,
|
||||||
|
int32 SplitId = -1,
|
||||||
|
const FDTFluxRequestConfig& CustomConfig = FDTFluxRequestConfig()
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Créer une requête trackée avec callbacks C++
|
||||||
|
* @param RequestType Type de requête
|
||||||
|
* @param ContestId ID du contest
|
||||||
|
* @param StageId ID du stage
|
||||||
|
* @param SplitId ID du split
|
||||||
|
* @param OnSuccess Callback appelé en cas de succès
|
||||||
|
* @param OnError Callback appelé en cas d'erreur
|
||||||
|
* @param CustomConfig Configuration spécifique
|
||||||
|
* @return GUID de la requête créée
|
||||||
|
*/
|
||||||
|
FGuid CreateTrackedRequestWithCallbacks(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
FOnDTFluxRequestSuccess OnSuccess,
|
||||||
|
FOnDTFluxRequestError OnError,
|
||||||
|
const FDTFluxRequestConfig& CustomConfig = FDTFluxRequestConfig()
|
||||||
|
);
|
||||||
|
|
||||||
|
// === GESTION DES REQUÊTES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marquer une requête comme envoyée
|
||||||
|
*/
|
||||||
|
bool MarkRequestAsSent(const FGuid& RequestId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compléter une requête avec la réponse reçue
|
||||||
|
* @param RequestId ID de la requête
|
||||||
|
* @param RawResponseData Données JSON brutes de la réponse
|
||||||
|
* @param bUseAsyncParsing Utiliser le parsing asynchrone (recommandé)
|
||||||
|
*/
|
||||||
|
bool CompleteRequest(const FGuid& RequestId, const FString& RawResponseData, bool bUseAsyncParsing = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marquer une requête comme échouée
|
||||||
|
*/
|
||||||
|
bool FailRequest(const FGuid& RequestId, const FString& ErrorMessage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relancer une requête (si retry possible)
|
||||||
|
*/
|
||||||
|
bool RetryRequest(const FGuid& RequestId);
|
||||||
|
|
||||||
|
// === RECHERCHE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chercher une requête en attente correspondant aux critères
|
||||||
|
*/
|
||||||
|
bool FindPendingRequest(
|
||||||
|
FGuid& OutRequestId,
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId = -1,
|
||||||
|
int32 StageId = -1,
|
||||||
|
int32 SplitId = -1
|
||||||
|
) const;
|
||||||
|
|
||||||
|
// === ACCESSEURS ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer une requête par son ID
|
||||||
|
*/
|
||||||
|
bool GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer un pointeur vers une requête (plus efficace)
|
||||||
|
*/
|
||||||
|
const FDTFluxTrackedRequest* GetRequestPtr(const FGuid& RequestId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Récupérer toutes les requêtes dans un état donné
|
||||||
|
*/
|
||||||
|
TArray<FDTFluxTrackedRequest> GetRequestsByState(EDTFluxRequestState State) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compter les requêtes dans un état donné
|
||||||
|
*/
|
||||||
|
int32 GetRequestCount(EDTFluxRequestState State = EDTFluxRequestState::Pending) const;
|
||||||
|
|
||||||
|
// === STATISTIQUES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statistiques complètes du gestionnaire de requêtes
|
||||||
|
*/
|
||||||
|
struct FRequestStatistics
|
||||||
|
{
|
||||||
|
int32 Pending = 0;
|
||||||
|
int32 Completed = 0;
|
||||||
|
int32 Failed = 0;
|
||||||
|
int32 TotalRequests = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
FRequestStatistics GetStatistics() const;
|
||||||
|
|
||||||
|
// === NETTOYAGE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoyer les requêtes terminées anciennes
|
||||||
|
* @param OlderThanSeconds Supprimer les requêtes plus anciennes que ce délai
|
||||||
|
* @return Nombre de requêtes supprimées
|
||||||
|
*/
|
||||||
|
int32 CleanupCompletedRequests(float OlderThanSeconds = 300.0f);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vider toutes les requêtes
|
||||||
|
*/
|
||||||
|
void ClearAllRequests();
|
||||||
|
|
||||||
|
// === EVENTS ===
|
||||||
|
|
||||||
|
FOnRequestStateChangedNative OnRequestStateChanged;
|
||||||
|
FOnRequestCompletedNative OnRequestCompleted;
|
||||||
|
FOnRequestFailedNative OnRequestFailed;
|
||||||
|
|
||||||
|
// === INTERFACE TICKABLE ===
|
||||||
|
|
||||||
|
virtual void Tick(float DeltaTime) override;
|
||||||
|
virtual bool IsTickable() const override { return true; };
|
||||||
|
|
||||||
|
virtual TStatId GetStatId() const override
|
||||||
|
{
|
||||||
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FDTFluxQueuedRequestManager, STATGROUP_Tickables);
|
||||||
|
};
|
||||||
|
virtual bool IsTickableWhenPaused() const override { return true; }
|
||||||
|
virtual bool IsTickableInEditor() const override { return true; }
|
||||||
|
|
||||||
|
// === ACCESSEUR POUR LE PARSER (debug/stats) ===
|
||||||
|
const FDTFluxAsyncParser* GetAsyncParser() const { return AsyncParser.Get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// === CONFIGURATION ===
|
||||||
|
FDTFluxRequestConfig DefaultConfig;
|
||||||
|
std::atomic<bool> bIsInitialized{false};
|
||||||
|
|
||||||
|
// === TIMING POUR LE TICK ===
|
||||||
|
float TimeSinceLastTimeoutCheck = 0.0f;
|
||||||
|
float TimeSinceLastRetryCheck = 0.0f;
|
||||||
|
|
||||||
|
static constexpr float TimeoutCheckInterval = 1.0f;
|
||||||
|
static constexpr float RetryCheckInterval = 0.5f;
|
||||||
|
|
||||||
|
// === STOCKAGE THREAD-SAFE ===
|
||||||
|
mutable FCriticalSection RequestsLock;
|
||||||
|
TMap<FGuid, TSharedPtr<FDTFluxTrackedRequest>> AllRequests;
|
||||||
|
|
||||||
|
// === CALLBACKS C++ ===
|
||||||
|
mutable FCriticalSection CallbacksLock;
|
||||||
|
TMap<FGuid, FOnDTFluxRequestSuccess> SuccessCallbacks;
|
||||||
|
TMap<FGuid, FOnDTFluxRequestError> ErrorCallbacks;
|
||||||
|
|
||||||
|
// === MÉTRIQUES ===
|
||||||
|
mutable FCriticalSection MetricsLock;
|
||||||
|
mutable int32 TotalRequests = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// === PARSER ASYNCHRONE ===
|
||||||
|
TUniquePtr<FDTFluxAsyncParser> AsyncParser;
|
||||||
|
|
||||||
|
// === MÉTHODES PRIVÉES ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changer l'état d'une requête et notifier les observers
|
||||||
|
*/
|
||||||
|
void ChangeRequestState(TSharedPtr<FDTFluxTrackedRequest> Request, EDTFluxRequestState NewState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traiter les requêtes en timeout (appelé périodiquement)
|
||||||
|
*/
|
||||||
|
void ProcessTimeouts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traiter les requêtes à relancer (appelé périodiquement)
|
||||||
|
*/
|
||||||
|
void ProcessRetries();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Déclencher les callbacks pour une requête
|
||||||
|
*/
|
||||||
|
void TriggerCallbacks(const FDTFluxTrackedRequest& Request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nettoyer les callbacks d'une requête
|
||||||
|
*/
|
||||||
|
void CleanupCallbacks(const FGuid& RequestId);
|
||||||
|
|
||||||
|
// === CALLBACKS POUR LE PARSING ASYNCHRONE ===
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand le parsing asynchrone réussit
|
||||||
|
*/
|
||||||
|
void OnParsingCompleted(const FGuid& RequestId, TSharedPtr<FDTFluxServerResponse> ParsedResponse, bool bSuccess);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand le parsing asynchrone échoue
|
||||||
|
*/
|
||||||
|
void OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage);
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
#include "DTFluxRequestStructs.generated.h"
|
#include "DTFluxRequestStructs.generated.h"
|
||||||
|
|
||||||
@ -15,9 +16,13 @@ USTRUCT()
|
|||||||
struct FDTFluxRequestBase
|
struct FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Path = "";
|
FString Path = "";
|
||||||
|
|
||||||
|
FDateTime CreatedAt = FDateTime::Now();
|
||||||
|
FGuid RequestId = FGuid::NewGuid();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,11 +30,13 @@ public:
|
|||||||
* RaceData represents all data concerning the Race and its different Contests, Stages and Splits.
|
* RaceData represents all data concerning the Race and its different Contests, Stages and Splits.
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FDTFluxRaceDataRequest: public FDTFluxRequestBase
|
struct FDTFluxRaceDataRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxRaceDataRequest(){
|
FDTFluxRaceDataRequest()
|
||||||
|
{
|
||||||
Path = "race-datas";
|
Path = "race-datas";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -39,11 +46,13 @@ public:
|
|||||||
* TeamList is the list of participants of the events
|
* TeamList is the list of participants of the events
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FDTFluxTeamListRequest: public FDTFluxRequestBase
|
struct FDTFluxTeamListRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FDTFluxTeamListRequest(){
|
FDTFluxTeamListRequest()
|
||||||
|
{
|
||||||
Path = "team-list";
|
Path = "team-list";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -52,7 +61,7 @@ public:
|
|||||||
* Struct representing a Ranking json request object for a specific to the server
|
* Struct representing a Ranking json request object for a specific to the server
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FDTFluxContestRankingRequest: public FDTFluxRequestBase
|
struct FDTFluxContestRankingRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@ -62,6 +71,7 @@ public:
|
|||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
ContestID = -1;
|
ContestID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxContestRankingRequest(int InContestID)
|
FDTFluxContestRankingRequest(int InContestID)
|
||||||
{
|
{
|
||||||
Path = "contest-ranking";
|
Path = "contest-ranking";
|
||||||
@ -76,7 +86,7 @@ public:
|
|||||||
* Struct representing a Ranking json request object for a specific Stage to the server
|
* Struct representing a Ranking json request object for a specific Stage to the server
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FDTFluxStageRankingRequest: public FDTFluxRequestBase
|
struct FDTFluxStageRankingRequest : public FDTFluxRequestBase
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@ -88,6 +98,7 @@ public:
|
|||||||
StageID = -1;
|
StageID = -1;
|
||||||
SplitID = -1;
|
SplitID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
FDTFluxStageRankingRequest(int InContestID, int InStageId)
|
||||||
{
|
{
|
||||||
Path = "stage-ranking";
|
Path = "stage-ranking";
|
||||||
@ -102,15 +113,13 @@ public:
|
|||||||
int StageID;
|
int StageID;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int SplitID;
|
int SplitID;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct representing a Ranking json request object for a specific Split to the server
|
* Struct representing a Ranking json request object for a specific Split to the server
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct FDTFluxSplitRankingRequest: public FDTFluxStageRankingRequest
|
struct FDTFluxSplitRankingRequest : public FDTFluxStageRankingRequest
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@ -122,6 +131,7 @@ public:
|
|||||||
StageID = -1;
|
StageID = -1;
|
||||||
SplitID = -1;
|
SplitID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
FDTFluxSplitRankingRequest(int InContestID, int InStageId, int InSplitId)
|
||||||
{
|
{
|
||||||
Path = "stage-ranking";
|
Path = "stage-ranking";
|
||||||
@ -129,5 +139,105 @@ public:
|
|||||||
StageID = InStageId;
|
StageID = InStageId;
|
||||||
SplitID = InSplitId;
|
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,36 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "UObject/Object.h"
|
#include "UObject/Object.h"
|
||||||
|
#include "DTFluxRaceDataServerResponse.h"
|
||||||
|
#include "DTFluxRankingServerResponse.h"
|
||||||
|
#include "DTFluxSplitSensorServerResponse.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
|
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxRankingStructs.h"
|
||||||
|
#include "Types/Struct/DTFluxSplitSensor.h"
|
||||||
#include "DTFluxServerResponseStruct.generated.h"
|
#include "DTFluxServerResponseStruct.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum pour indiquer le statut du parsing
|
||||||
|
*/
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class EDTFluxResponseStatus : uint8
|
||||||
|
{
|
||||||
|
Unset = 0b00000000 UMETA(DisplayName="Unset"),
|
||||||
|
Success = 0b10000000 UMETA(DisplayName="Success"),
|
||||||
|
JsonParseError = 0b00000001 UMETA(DisplayName="JsonParseError"),
|
||||||
|
ServerError = 0b00000010 UMETA(DisplayName="ServerError"),
|
||||||
|
InvalidType = 0b00000100 UMETA(DisplayName="InvalidType"),
|
||||||
|
MissingData = 0b00001000 UMETA(DisplayName="MissingData"),
|
||||||
|
DataError = 0b00010000 UMETA(DisplayName="MissingData"),
|
||||||
|
UnknownError = 0b00100000 UMETA(DisplayName="UnknownError")
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Struct representing a mixed root json server response
|
* Struct representing a mixed root json server response with integrated parsing capabilities
|
||||||
*/
|
*/
|
||||||
USTRUCT()
|
USTRUCT()
|
||||||
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
struct DTFLUXNETWORK_API FDTFluxServerResponse
|
||||||
@ -20,35 +43,99 @@ struct DTFLUXNETWORK_API FDTFluxServerResponse
|
|||||||
public:
|
public:
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "";
|
FString Type = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int Code = -1;
|
int Code = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Message = "";
|
FString Message = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Trigger = "";
|
FString Trigger = "";
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int ContestID = -1;
|
int ContestID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int StageID = -1;
|
int StageID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int SplitID = -1;
|
int SplitID = -1;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FDateTime ReceivedAt;
|
FDateTime ReceivedAt;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString RawMessage;
|
FString RawMessage;
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FName RequestId = FName("");
|
FName RequestId = FName("");
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FText FailureReason;
|
FText FailureReason;
|
||||||
|
|
||||||
|
// === CONSTRUCTEURS ===
|
||||||
|
FDTFluxServerResponse();
|
||||||
|
FDTFluxServerResponse(const FString& JsonMessage, EDTFluxResponseStatus& OutStatus, bool bLogErrors = true);
|
||||||
|
static FDTFluxServerResponse CreateFromJson(const FString& JsonMessage, bool bLogErrors = true);
|
||||||
|
|
||||||
|
// === MÉTHODES DE PARSING ===
|
||||||
|
|
||||||
|
EDTFluxResponseStatus TryParse(bool bLogErrors = true);
|
||||||
|
|
||||||
|
bool ParseTeamList(FDTFluxTeamListDefinition& OutTeamList);
|
||||||
|
bool ParseTeamUpdate(FDTFluxTeamListDefinition& OutTeamUpdate);
|
||||||
|
bool ParseRaceData(FDTFluxRaceData& OutRaceData);
|
||||||
|
bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings);
|
||||||
|
bool ParseStageRanking(FDTFluxStageRankings& OutStageRankings);
|
||||||
|
bool ParseSplitRanking(FDTFluxSplitRankings& OutSplitRankings);
|
||||||
|
bool ParseStatusUpdate(FDTFluxTeamStatusUpdate& OutStatusUpdate);
|
||||||
|
bool ParseSplitSensor(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");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -14,17 +14,21 @@ struct DTFLUXNETWORK_API FDTFluxSplitSensorItemResponse
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int Bib;
|
int Bib = -1;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Type = "split-sensor-item";
|
FString Type = "split-sensor-item";
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int ContestID;
|
int ContestID =-1;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int StageID;
|
int StageID =-1;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
int SplitID;
|
int SplitID = -1;
|
||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
FString Time = "-";
|
FString Time = "-";
|
||||||
|
UPROPERTY()
|
||||||
|
int Rank = -1;
|
||||||
|
UPROPERTY()
|
||||||
|
FString Gap = "";
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,163 +1,270 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
// ================================================================================================
|
||||||
|
// DTFluxNetworkSubsystem.h - Interface UObject avec compatibilité Blueprint
|
||||||
|
// ================================================================================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Struct/DTFluxServerResponseStruct.h"
|
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
#include "Types/DTFluxNetworkSettingsTypes.h"
|
#include "Types/DTFluxNetworkSettingsTypes.h"
|
||||||
#include "Types/Enum/DTFluxCoreEnum.h"
|
#include "Types/Enum/DTFluxCoreEnum.h"
|
||||||
#include "Types/Struct/DTFluxRaceDataStructs.h"
|
#include "DTFluxQueuedManager.h"
|
||||||
#include "Types/Struct/DTFluxRankingStructs.h"
|
|
||||||
#include "Types/Struct/DTFluxSplitSensor.h"
|
|
||||||
#include "Types/Struct/DTFluxTeamListStruct.h"
|
|
||||||
#include "DTFluxNetworkSubsystem.generated.h"
|
#include "DTFluxNetworkSubsystem.generated.h"
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
|
||||||
|
|
||||||
class FDTFluxWebSocketClient;
|
class FDTFluxWebSocketClient;
|
||||||
|
class FDTFluxQueuedRequestManager;
|
||||||
|
|
||||||
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
|
||||||
class FDTFluxHttpClient;
|
|
||||||
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
|
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);
|
||||||
|
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*SplitSensorInfo*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*ParticipantToUpdate*/);
|
||||||
|
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
|
||||||
|
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected);
|
||||||
|
|
||||||
|
|
||||||
UCLASS(Blueprintable)
|
UCLASS(Blueprintable)
|
||||||
class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
|
class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Network")
|
||||||
UPROPERTY()
|
|
||||||
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
|
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;
|
* Se connecter au serveur WebSocket
|
||||||
FOnRaceDataReceived& OnReceivedRaceData()
|
*/
|
||||||
{
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
|
||||||
return OnRaceDataReceived;
|
|
||||||
};
|
|
||||||
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
|
|
||||||
FOnTeamListReceived OnTeamListReceived;
|
|
||||||
FOnTeamListReceived& OnReceivedTeamList()
|
|
||||||
{
|
|
||||||
return OnTeamListReceived;
|
|
||||||
};
|
|
||||||
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnStageRankingReceived, const FDTFluxStageRankings& /*StageRankings*/);
|
|
||||||
FOnStageRankingReceived OnStageRankingReceived;
|
|
||||||
FOnStageRankingReceived& OnReceivedStageRanking()
|
|
||||||
{
|
|
||||||
return OnStageRankingReceived;
|
|
||||||
}
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnSplitRankingReceived, const FDTFluxSplitRankings& /*SplitRankings*/);
|
|
||||||
FOnSplitRankingReceived OnSplitRankingReceived;
|
|
||||||
FOnSplitRankingReceived& OnReceivedSplitRanking()
|
|
||||||
{
|
|
||||||
return OnSplitRankingReceived;
|
|
||||||
}
|
|
||||||
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnContestRankingReceived, const FDTFluxContestRankings& /*ContestRankings*/);
|
|
||||||
FOnContestRankingReceived OnContestRankingReceived;
|
|
||||||
FOnContestRankingReceived& OnReceivedContestRanking()
|
|
||||||
{
|
|
||||||
return OnContestRankingReceived;
|
|
||||||
};
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
|
|
||||||
FOnSplitSensorReceived OnSplitSensorReceived;
|
|
||||||
FOnSplitSensorReceived& OnReceivedSplitSensor()
|
|
||||||
{
|
|
||||||
return OnSplitSensorReceived;
|
|
||||||
};
|
|
||||||
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxParticipant& /*ParticipantToUpdate*/);
|
|
||||||
FOnTeamUpdateReceived OnTeamUpdateReceived;
|
|
||||||
FOnTeamUpdateReceived& OnReceivedTeamUpdate()
|
|
||||||
{
|
|
||||||
return OnTeamUpdateReceived;
|
|
||||||
};
|
|
||||||
|
|
||||||
DECLARE_DELEGATE_OneParam(FOnTeamStatusUpdateReceived, const FDTFluxTeamStatusUpdate& /*TeamToUpdate*/);
|
|
||||||
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
|
|
||||||
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate()
|
|
||||||
{
|
|
||||||
return OnTeamStatusUpdateReceived;
|
|
||||||
};
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
|
||||||
void Connect();
|
void Connect();
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
|
||||||
|
/**
|
||||||
|
* Se déconnecter du serveur WebSocket
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
|
||||||
void Disconnect();
|
void Disconnect();
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
|
||||||
|
/**
|
||||||
|
* Reconnecter au serveur WebSocket
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
|
||||||
void Reconnect();
|
void Reconnect();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoyer une requête trackée avec cache, timeout et retry
|
||||||
|
* @param RequestType Type de requête (ContestRanking, StageRanking, etc.)
|
||||||
|
* @param ContestId ID du contest (-1 si non applicable)
|
||||||
|
* @param StageId ID du stage (-1 si non applicable)
|
||||||
|
* @param SplitId ID du split (-1 si non applicable)
|
||||||
|
* @param TimeoutSeconds Timeout en secondes
|
||||||
|
* @param MaxRetries Nombre maximum de tentatives
|
||||||
|
* @param bEnableCache Activer le cache pour cette requête
|
||||||
|
* @return GUID de la requête pour le suivi
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
|
||||||
|
FGuid SendTrackedRequest(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId = -1,
|
||||||
|
int32 StageId = -1,
|
||||||
|
int32 SplitId = -1,
|
||||||
|
float TimeoutSeconds = 5.0f,
|
||||||
|
int32 MaxRetries = 3,
|
||||||
|
bool bEnableCache = true
|
||||||
|
);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
|
/**
|
||||||
void SendRequest(const EDTFluxRequestType RequestType, int InContestId = -1, int InStageId = -1, int InSplitId = -1);
|
* Envoyer une requête trackée avec callbacks C++ (non Blueprint)
|
||||||
|
* @param RequestType Type de requête
|
||||||
|
* @param ContestId ID du contest
|
||||||
|
* @param StageId ID du stage
|
||||||
|
* @param SplitId ID du split
|
||||||
|
* @param OnSuccess Callback appelé en cas de succès
|
||||||
|
* @param OnError Callback appelé en cas d'erreur
|
||||||
|
* @param TimeoutSeconds Timeout en secondes
|
||||||
|
* @param MaxRetries Nombre maximum de tentatives
|
||||||
|
* @param bEnableCache Activer le cache
|
||||||
|
* @return GUID de la requête
|
||||||
|
*/
|
||||||
|
FGuid SendTrackedRequestWithCallbacks(
|
||||||
|
EDTFluxApiDataType RequestType,
|
||||||
|
int32 ContestId,
|
||||||
|
int32 StageId,
|
||||||
|
int32 SplitId,
|
||||||
|
FOnDTFluxRequestSuccess& OnSuccess,
|
||||||
|
FOnDTFluxRequestError& OnError,
|
||||||
|
float TimeoutSeconds = 5.0f,
|
||||||
|
int32 MaxRetries = 3
|
||||||
|
);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
|
||||||
|
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const;
|
||||||
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
|
||||||
|
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")
|
||||||
|
void GetRequestStatistics(int32& OutPending, int32& OutCompleted, int32& OutFailed) const;
|
||||||
|
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux|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);
|
void SendMessage(const FString& Message);
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
|
||||||
|
FOnWebSocketConnected OnWebSocketConnected;
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
|
||||||
|
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
|
||||||
|
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
|
||||||
|
|
||||||
|
FOnRaceDataReceived OnRaceDataReceived;
|
||||||
|
FOnTeamListReceived OnTeamListReceived;
|
||||||
|
FOnStageRankingReceived OnStageRankingReceived;
|
||||||
|
FOnSplitRankingReceived OnSplitRankingReceived;
|
||||||
|
FOnContestRankingReceived OnContestRankingReceived;
|
||||||
|
FOnSplitSensorReceived OnSplitSensorReceived;
|
||||||
|
FOnTeamUpdateReceived OnTeamUpdateReceived;
|
||||||
|
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
|
||||||
|
|
||||||
|
FOnRaceDataReceived& OnReceivedRaceData() { return OnRaceDataReceived; }
|
||||||
|
FOnTeamListReceived& OnReceivedTeamList() { return OnTeamListReceived; }
|
||||||
|
FOnStageRankingReceived& OnReceivedStageRanking() { return OnStageRankingReceived; }
|
||||||
|
FOnSplitRankingReceived& OnReceivedSplitRanking() { return OnSplitRankingReceived; }
|
||||||
|
FOnContestRankingReceived& OnReceivedContestRanking() { return OnContestRankingReceived; }
|
||||||
|
FOnSplitSensorReceived& OnReceivedSplitSensor() { return OnSplitSensorReceived; }
|
||||||
|
FOnTeamUpdateReceived& OnReceivedTeamUpdate() { return OnTeamUpdateReceived; }
|
||||||
|
FOnTeamStatusUpdateReceived& OnReceivedTeamStatusUpdate() { return OnTeamStatusUpdateReceived; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TSharedPtr<FDTFluxQueuedRequestManager> GetRequestManager() const { return RequestManager; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// ~Subsystem Interface
|
|
||||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
virtual void Deinitialize() override;
|
virtual void Deinitialize() override;
|
||||||
// ~Subsystem Interface
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
FDTFluxWsSettings WsSettings;
|
FDTFluxWsSettings WsSettings;
|
||||||
FDTFluxHttpSettings HttpSettings;
|
|
||||||
|
|
||||||
UFUNCTION()
|
FDTFluxWebSocketClientSP WsClient = nullptr;
|
||||||
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
|
||||||
UFUNCTION()
|
TSharedPtr<FDTFluxQueuedRequestManager> RequestManager;
|
||||||
void HttpSettingsChanged(const FDTFluxHttpSettings& NewHttpSettings);
|
|
||||||
void ReconnectWs(const FName WsClientId);
|
|
||||||
void ReconnectHttp(const FName WsClientId);
|
|
||||||
|
|
||||||
void RegisterWebSocketEvents();
|
void RegisterWebSocketEvents();
|
||||||
void RegisterHttpEvents();
|
void UnregisterWebSocketEvents() const;
|
||||||
void UnregisterWebSocketEvents();
|
|
||||||
void UnregisterHttpEvents();
|
|
||||||
|
|
||||||
void OnWebSocketConnected_Subsystem();
|
void OnWebSocketConnected_Subsystem();
|
||||||
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
void OnWebSocketConnectionError_Subsystem(const FString& Error);
|
||||||
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode , const FString& Reason, bool bWasClean);
|
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
|
||||||
void ParseTeamListResponse(const FDTFluxServerResponse& ServerResponse);
|
|
||||||
void ParseRaceData(const FDTFluxServerResponse& Response);
|
|
||||||
void ParseContestRanking(const FDTFluxServerResponse& Response);
|
|
||||||
void ParseStageRankingResponse(const FDTFluxServerResponse& Response);
|
|
||||||
void ParseSplitRankingResponse(const FDTFluxServerResponse& Response);
|
|
||||||
void ParseStatusUpdateResponse(const FDTFluxServerResponse& Response);
|
|
||||||
void ParseSplitSensorResponse(const FDTFluxServerResponse& Response);
|
|
||||||
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
|
||||||
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
|
||||||
|
|
||||||
// TODO : Allow multiple instances of network clients.
|
|
||||||
// // For Future use of Multi-Connections
|
|
||||||
// TArray<FDTFluxWebSocketClientSP> WsClients;
|
|
||||||
// // For Future use of Multi-Connections
|
|
||||||
// TArray<FDTFluxHttpClientSP> HttpClient;
|
|
||||||
// Fo now we jest stick to only one client for each protocol
|
|
||||||
|
|
||||||
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
FDelegateHandle OnWsConnectedEventDelegateHandle;
|
||||||
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
|
||||||
FDelegateHandle OnWsClosedEventDelegateHandle;
|
FDelegateHandle OnWsClosedEventDelegateHandle;
|
||||||
FDelegateHandle OnWsMessageEventDelegateHandle;
|
FDelegateHandle OnWsMessageEventDelegateHandle;
|
||||||
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
FDelegateHandle OnWsMessageSentEventDelegateHandle;
|
||||||
|
|
||||||
FDTFluxWebSocketClientSP WsClient = nullptr;
|
|
||||||
FDTFluxHttpClientSP HttpClient = nullptr;
|
/**
|
||||||
|
* Essayer de matcher une réponse à une requête trackée
|
||||||
|
* @param MessageString Message JSON reçu
|
||||||
|
* @return true si la réponse correspond à une requête trackée
|
||||||
|
*/
|
||||||
|
bool TryMatchResponseToQueuedRequest(const FString& MessageString);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traiter une réponse en mode legacy
|
||||||
|
* @param MessageString Message JSON à traiter
|
||||||
|
*/
|
||||||
|
void ProcessLegacyResponse(const FString& MessageString);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traiter une réponse déjà parsée
|
||||||
|
* @param ParsedResponse Réponse parsée à traiter
|
||||||
|
*/
|
||||||
|
void ProcessParsedResponse(TSharedPtr<FDTFluxServerResponse> ParsedResponse);
|
||||||
|
|
||||||
|
// === MÉTHODES DE PARSING LEGACY (pour compatibilité) ===
|
||||||
|
void ParseTeamListResponse(FDTFluxServerResponse& Response);
|
||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand une requête trackée se termine
|
||||||
|
*/
|
||||||
|
void OnRequestCompleted_Internal(const FDTFluxTrackedRequest& CompletedRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand une requête trackée échoue
|
||||||
|
*/
|
||||||
|
void OnRequestFailed_Internal(const FDTFluxTrackedRequest& FailedRequest);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback appelé quand les paramètres WebSocket changent
|
||||||
|
*/
|
||||||
|
UFUNCTION()
|
||||||
|
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconnecter le client WebSocket
|
||||||
|
*/
|
||||||
|
void ReconnectWs(const FName WsClientId);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construire une adresse WebSocket complète
|
||||||
|
*/
|
||||||
static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
|
static FString ConstructWsAddress(const FString& Address, const FString& Path, const int& Port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoyer une requête trackée via le réseau
|
||||||
|
*/
|
||||||
|
void SendQueuedRequest(const FDTFluxTrackedRequest& QueuedRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Déterminer si on doit utiliser le parsing asynchrone
|
||||||
|
*/
|
||||||
|
bool ShouldUseAsyncParsing(const FString& JsonData) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public class DTFluxProjectSettings : ModuleRules
|
|||||||
"DeveloperSettings",
|
"DeveloperSettings",
|
||||||
"DTFluxCore",
|
"DTFluxCore",
|
||||||
"Settings",
|
"Settings",
|
||||||
"DeveloperSettings"
|
"DeveloperSettings","AvalancheMedia"
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
|
|
||||||
#include "DTFluxGeneralSettings.h"
|
#include "DTFluxGeneralSettings.h"
|
||||||
|
#include "Assets/DTFluxModelAsset.h"
|
||||||
|
|
||||||
#include "DTFluxProjectSettingsModule.h"
|
#include "DTFluxProjectSettingsModule.h"
|
||||||
|
|
||||||
@ -14,3 +15,17 @@ UDTFluxGeneralSettings::UDTFluxGeneralSettings()
|
|||||||
UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString());
|
UE_LOG(logDTFluxProjectSettings, Log, TEXT("Category Name -> %s"), *GetCategoryName().ToString());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UDTFluxGeneralSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
||||||
|
{
|
||||||
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
||||||
|
if (PropertyChangedEvent.Property &&
|
||||||
|
PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UDTFluxGeneralSettings, RemoteTargetRundown))
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("RemoteTargetRundown property changed to: %s"),
|
||||||
|
RemoteTargetRundown.IsNull() ? TEXT("None") : *RemoteTargetRundown.ToString());
|
||||||
|
|
||||||
|
OnRemoteRundownChanged.Broadcast(RemoteTargetRundown);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@ -3,10 +3,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Assets/DTFluxModelAsset.h"
|
|
||||||
#include "Engine/DeveloperSettings.h"
|
#include "Engine/DeveloperSettings.h"
|
||||||
#include "DTFluxGeneralSettings.generated.h"
|
#include "DTFluxGeneralSettings.generated.h"
|
||||||
|
|
||||||
|
class UAvaRundown;
|
||||||
|
class UDTFluxModelAsset;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -20,5 +21,14 @@ public:
|
|||||||
UDTFluxGeneralSettings();
|
UDTFluxGeneralSettings();
|
||||||
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
|
UPROPERTY(Category="General", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Datastorage File")
|
||||||
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
|
TSoftObjectPtr<UDTFluxModelAsset> ModelAsset;
|
||||||
|
UPROPERTY(Category="General|Remote HTTP", Config, EditAnywhere, BlueprintReadOnly, DisplayName="Rundown Remote Target")
|
||||||
|
TSoftObjectPtr<UAvaRundown> RemoteTargetRundown;
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||||
|
|
||||||
|
DECLARE_MULTICAST_DELEGATE_OneParam(FOnRemoteRundownChanged, const TSoftObjectPtr<UAvaRundown>& );
|
||||||
|
FOnRemoteRundownChanged OnRemoteRundownChanged;
|
||||||
|
#endif
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
31
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal file
31
Source/DTFluxRemote/DTFluxRemote.Build.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using UnrealBuildTool;
|
||||||
|
|
||||||
|
public class DTFluxRemote : ModuleRules
|
||||||
|
{
|
||||||
|
public DTFluxRemote(ReadOnlyTargetRules Target) : base(Target)
|
||||||
|
{
|
||||||
|
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
|
||||||
|
|
||||||
|
PublicDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"Core",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
PrivateDependencyModuleNames.AddRange(
|
||||||
|
new string[]
|
||||||
|
{
|
||||||
|
"CoreUObject",
|
||||||
|
"Engine",
|
||||||
|
"Slate",
|
||||||
|
"SlateCore",
|
||||||
|
"HttpServer",
|
||||||
|
"JsonUtilities",
|
||||||
|
"Json",
|
||||||
|
"DTFluxProjectSettings",
|
||||||
|
"AvalancheMedia"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
Source/DTFluxRemote/Private/DTFluxRemoteMessage.cpp
Normal file
6
Source/DTFluxRemote/Private/DTFluxRemoteMessage.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxRemoteMessage.h"
|
||||||
|
|
||||||
|
|
||||||
19
Source/DTFluxRemote/Private/DTFluxRemoteModule.cpp
Normal file
19
Source/DTFluxRemote/Private/DTFluxRemoteModule.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#include "DTFluxRemoteModule.h"
|
||||||
|
|
||||||
|
#define LOCTEXT_NAMESPACE "FDTFluxRemoteModule"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY(logDTFluxRemote);
|
||||||
|
|
||||||
|
void FDTFluxRemoteModule::StartupModule()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void FDTFluxRemoteModule::ShutdownModule()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef LOCTEXT_NAMESPACE
|
||||||
|
|
||||||
|
IMPLEMENT_MODULE(FDTFluxRemoteModule, DTFluxRemote)
|
||||||
549
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal file
549
Source/DTFluxRemote/Private/DTFluxRemoteSubsystem.cpp
Normal file
@ -0,0 +1,549 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxRemoteSubsystem.h"
|
||||||
|
#include "DTFluxRemoteSubsystem.h"
|
||||||
|
|
||||||
|
#include "DTFluxGeneralSettings.h"
|
||||||
|
#include "DTFluxRemoteModule.h"
|
||||||
|
#include "DTFluxRemoteModule.h"
|
||||||
|
#include "HttpServerModule.h"
|
||||||
|
#include "IHttpRouter.h"
|
||||||
|
#include "Rundown/AvaRundown.h"
|
||||||
|
#include "Json.h"
|
||||||
|
#include "JsonObjectConverter.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Misc/DateTime.h"
|
||||||
|
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::Initialize(FSubsystemCollectionBase& Collection)
|
||||||
|
{
|
||||||
|
Super::Initialize(Collection);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Initialized"));
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
// S'abonner aux changements de settings
|
||||||
|
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
|
||||||
|
{
|
||||||
|
SettingsRundownChangedHandle = Settings->OnRemoteRundownChanged.AddUObject(
|
||||||
|
this, &UDTFluxRemoteSubsystem::OnSettingsRundownChanged
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
LoadRundownFromSettings();
|
||||||
|
// Auto-start server (optionnel)
|
||||||
|
StartHTTPServer(63350);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::Deinitialize()
|
||||||
|
{
|
||||||
|
StopHTTPServer();
|
||||||
|
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
// Se désabonner du delegate
|
||||||
|
if (UDTFluxGeneralSettings* Settings = GetMutableDefault<UDTFluxGeneralSettings>())
|
||||||
|
{
|
||||||
|
if (SettingsRundownChangedHandle.IsValid())
|
||||||
|
{
|
||||||
|
Settings->OnRemoteRundownChanged.Remove(SettingsRundownChangedHandle);
|
||||||
|
SettingsRundownChangedHandle.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Décharger proprement le rundown
|
||||||
|
UnloadCurrentRundown();
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux API Subsystem Deinitialized"));
|
||||||
|
|
||||||
|
Super::Deinitialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::StartHTTPServer(int32 Port)
|
||||||
|
{
|
||||||
|
if (bServerRunning)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Warning, TEXT("HTTP Server already running on port %d"), ServerPort);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerPort = Port;
|
||||||
|
|
||||||
|
// Get HTTP Server Module
|
||||||
|
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
|
||||||
|
|
||||||
|
// Create router
|
||||||
|
HttpRouter = HttpServerModule.GetHttpRouter(ServerPort);
|
||||||
|
|
||||||
|
if (!HttpRouter.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to create HTTP router for port %d"), ServerPort);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup routes
|
||||||
|
SetupRoutes();
|
||||||
|
|
||||||
|
// Start listening
|
||||||
|
HttpServerModule.StartAllListeners();
|
||||||
|
|
||||||
|
bServerRunning = true;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server started on port %d"), ServerPort);
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Base URL: http://localhost:%d/dtflux/api/v1"), ServerPort);
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Available routes:"));
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title"));
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/title-bib"));
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT(" PUT /dtflux/api/v1/commands"));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::StopHTTPServer()
|
||||||
|
{
|
||||||
|
if (!bServerRunning)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove route handlers
|
||||||
|
if (HttpRouter.IsValid())
|
||||||
|
{
|
||||||
|
HttpRouter->UnbindRoute(TitleRouteHandle);
|
||||||
|
HttpRouter->UnbindRoute(TitleBibRouteHandle);
|
||||||
|
HttpRouter->UnbindRoute(CommandsRouteHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop server
|
||||||
|
FHttpServerModule& HttpServerModule = FHttpServerModule::Get();
|
||||||
|
HttpServerModule.StopAllListeners();
|
||||||
|
|
||||||
|
HttpRouter.Reset();
|
||||||
|
bServerRunning = false;
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("DTFlux HTTP API Server stopped"));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::IsHTTPServerRunning() const
|
||||||
|
{
|
||||||
|
return bServerRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::ResetPendingTitleData()
|
||||||
|
{
|
||||||
|
bHasPendingTitleRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::ResetPendingBibData()
|
||||||
|
{
|
||||||
|
bHasPendingTitleBibRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::SetupRoutes()
|
||||||
|
{
|
||||||
|
if (!HttpRouter.IsValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Route: POST /dtflux/api/v1/title
|
||||||
|
TitleRouteHandle = HttpRouter->BindRoute(
|
||||||
|
FHttpPath(TEXT("/dtflux/api/v1/title")),
|
||||||
|
EHttpServerRequestVerbs::VERB_PUT,
|
||||||
|
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Route: POST /dtflux/api/v1/title-bib
|
||||||
|
TitleBibRouteHandle = HttpRouter->BindRoute(
|
||||||
|
FHttpPath(TEXT("/dtflux/api/v1/title-bib")),
|
||||||
|
EHttpServerRequestVerbs::VERB_PUT,
|
||||||
|
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleTitleBibRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Route: POST /dtflux/api/v1/commands
|
||||||
|
CommandsRouteHandle = HttpRouter->BindRoute(
|
||||||
|
FHttpPath(TEXT("/dtflux/api/v1/commands")),
|
||||||
|
EHttpServerRequestVerbs::VERB_PUT,
|
||||||
|
FHttpRequestHandler::CreateUObject(this, &UDTFluxRemoteSubsystem::HandleCommandsRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("HTTP routes configured successfully"));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title request"));
|
||||||
|
|
||||||
|
// Parse JSON
|
||||||
|
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse title data
|
||||||
|
FDTFluxRemoteTitleData TitleData;
|
||||||
|
if (!ParseTitleData(JsonObject, TitleData))
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title data format")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast event (execute on Game Thread)
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [this, TitleData]()
|
||||||
|
{
|
||||||
|
OnTitleReceived.Broadcast(TitleData);
|
||||||
|
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||||
|
{
|
||||||
|
PendingTitleData = TitleData;
|
||||||
|
bHasPendingTitleRequest = true;
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Playing page %i"), TitleData.RundownPageId);
|
||||||
|
RemotedRundown->PlayPage(TitleData.RundownPageId, EAvaRundownPagePlayType::PlayFromStart);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title data received")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Received Title-Bib request"));
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRemoteBibData BibData;
|
||||||
|
if (!ParseTitleBibData(JsonObject, BibData))
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid title-bib data format")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [this, BibData]()
|
||||||
|
{
|
||||||
|
OnTitleBibReceived.Broadcast(BibData);
|
||||||
|
});
|
||||||
|
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateSuccessResponse(TEXT("Title-bib data received")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Received Commands request"));
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> JsonObject = ParseJsonFromRequest(Request);
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid JSON")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRemoteCommandData CommandData;
|
||||||
|
if (!ParseCommandData(JsonObject, CommandData))
|
||||||
|
{
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("Invalid command data format")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncTask(ENamedThreads::GameThread, [this, CommandData]()
|
||||||
|
{
|
||||||
|
OnCommandReceived.Broadcast(CommandData);
|
||||||
|
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||||
|
{
|
||||||
|
RemotedRundown->StopPage(CommandData.RundownPageId, EAvaRundownPageStopOptions::None, false);
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Stoping page %i"), CommandData.RundownPageId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Warning, TEXT("No rundown loaded"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
OnComplete(FHttpServerResponse::Create(CreateErrorResponse(TEXT("OK")), TEXT("application/json")));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> UDTFluxRemoteSubsystem::ParseJsonFromRequest(const FHttpServerRequest& Request)
|
||||||
|
{
|
||||||
|
// Get request body
|
||||||
|
TArray<uint8> Body = Request.Body;
|
||||||
|
FString JsonString;
|
||||||
|
if (Body.Num() > 0)
|
||||||
|
{
|
||||||
|
// Ajouter un null terminator si nécessaire
|
||||||
|
if (Body.Last() != 0)
|
||||||
|
{
|
||||||
|
Body.Add(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonString = FString(UTF8_TO_TCHAR(reinterpret_cast<const char*>(Body.GetData())));
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Verbose, TEXT("Received JSON: %s"), *JsonString);
|
||||||
|
|
||||||
|
// Parse JSON
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to parse JSON: %s"), *JsonString);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UDTFluxRemoteSubsystem::CreateSuccessResponse(const FString& Message)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
|
||||||
|
ResponseObject->SetBoolField(TEXT("success"), true);
|
||||||
|
ResponseObject->SetStringField(TEXT("message"), Message);
|
||||||
|
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
|
||||||
|
|
||||||
|
FString OutputString;
|
||||||
|
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
||||||
|
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
|
||||||
|
|
||||||
|
return OutputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString UDTFluxRemoteSubsystem::CreateErrorResponse(const FString& Error, int32 Code)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> ResponseObject = MakeShareable(new FJsonObject);
|
||||||
|
ResponseObject->SetBoolField(TEXT("success"), false);
|
||||||
|
ResponseObject->SetStringField(TEXT("error"), Error);
|
||||||
|
ResponseObject->SetNumberField(TEXT("code"), Code);
|
||||||
|
ResponseObject->SetStringField(TEXT("timestamp"), FDateTime::Now().ToIso8601());
|
||||||
|
|
||||||
|
FString OutputString;
|
||||||
|
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
||||||
|
FJsonSerializer::Serialize(ResponseObject.ToSharedRef(), Writer);
|
||||||
|
|
||||||
|
return OutputString;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData)
|
||||||
|
{
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteTitleData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteTitleData>(JsonObject.ToSharedRef(), &OutData))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteTitleData"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteTitleData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData)
|
||||||
|
{
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteBibData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteBibData>(JsonObject.ToSharedRef(), &OutData))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteBibData"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteBibData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData)
|
||||||
|
{
|
||||||
|
if (!JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Invalid JSON object for %s"), TEXT("FDTFluxRemoteCommandData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FJsonObjectConverter::JsonObjectToUStruct<FDTFluxRemoteCommandData>(JsonObject.ToSharedRef(), &OutData))
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully parsed %s"), TEXT("FDTFluxRemoteCommandData"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to convert JSON to %s struct"),TEXT("FDTFluxRemoteCommandData"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::UnloadCurrentRundown()
|
||||||
|
{
|
||||||
|
if (RemotedRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Unloading current rundown"));
|
||||||
|
// Ici vous pouvez ajouter une logique de nettoyage si nécessaire
|
||||||
|
// Par exemple : RemotedRundown->StopAllPages();
|
||||||
|
RemotedRundown = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UDTFluxRemoteSubsystem::LoadRundownFromSettings()
|
||||||
|
{
|
||||||
|
const UDTFluxGeneralSettings* Settings = GetDefault<UDTFluxGeneralSettings>();
|
||||||
|
if (!Settings)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot access DTFlux settings"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TSoftObjectPtr<UAvaRundown> RundownAsset = Settings->RemoteTargetRundown;
|
||||||
|
|
||||||
|
if (RundownAsset.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("No rundown specified in settings"));
|
||||||
|
UnloadCurrentRundown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RemotedRundown && RemotedRundown == RundownAsset.LoadSynchronous())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Décharger l'ancien rundown d'abord
|
||||||
|
UnloadCurrentRundown();
|
||||||
|
RundownAsset = RundownAsset.LoadSynchronous();
|
||||||
|
// Charger le nouveau rundown
|
||||||
|
if ( RundownAsset.IsValid())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown from settings: %s"), *RundownAsset.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown from settings: %s"), *RundownAsset.ToString());
|
||||||
|
}
|
||||||
|
LoadRundown(RundownAsset);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset)
|
||||||
|
{
|
||||||
|
if (RundownAsset.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Warning, TEXT("Cannot load rundown: asset is null"));
|
||||||
|
UnloadCurrentRundown();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger le rundown de manière synchrone
|
||||||
|
UAvaRundown* LoadedRundown = RundownAsset.LoadSynchronous();
|
||||||
|
|
||||||
|
// Vérifier si le rundown est déjà chargé
|
||||||
|
if (RemotedRundown && RemotedRundown == LoadedRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Rundown already loaded: %s"), *RundownAsset.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Décharger l'ancien rundown d'abord
|
||||||
|
UnloadCurrentRundown();
|
||||||
|
|
||||||
|
// Assigner le nouveau rundown
|
||||||
|
RemotedRundown = LoadedRundown;
|
||||||
|
|
||||||
|
// Vérifier que le chargement a réussi
|
||||||
|
if (RemotedRundown && RemotedRundown->IsValidLowLevel())
|
||||||
|
{
|
||||||
|
RemotedRundown->InitializePlaybackContext();
|
||||||
|
UE_LOG(logDTFluxRemote, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxRemote, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
|
||||||
|
RemotedRundown = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
void UDTFluxRemoteSubsystem::OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
// Manual processing functions for testing
|
||||||
|
bool UDTFluxRemoteSubsystem::ProcessTitleData(const FString& JsonString)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
FDTFluxRemoteTitleData TitleData;
|
||||||
|
if (ParseTitleData(JsonObject, TitleData))
|
||||||
|
{
|
||||||
|
OnTitleReceived.Broadcast(TitleData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::ProcessTitleBibData(const FString& JsonString)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRemoteBibData TitleBibData;
|
||||||
|
if (ParseTitleBibData(JsonObject, TitleBibData))
|
||||||
|
{
|
||||||
|
OnTitleBibReceived.Broadcast(TitleBibData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UDTFluxRemoteSubsystem::ProcessCommandData(const FString& JsonString)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> JsonObject;
|
||||||
|
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);
|
||||||
|
|
||||||
|
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FDTFluxRemoteCommandData CommandData;
|
||||||
|
if (ParseCommandData(JsonObject, CommandData))
|
||||||
|
{
|
||||||
|
OnCommandReceived.Broadcast(CommandData);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
220
Source/DTFluxRemote/Private/DTFluxRemotedLevelController.cpp
Normal file
220
Source/DTFluxRemote/Private/DTFluxRemotedLevelController.cpp
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// DTFluxRemoteActor.cpp
|
||||||
|
#include "DTFluxRemotedLevelController.h"
|
||||||
|
#include "DTFluxRemoteSubsystem.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
|
||||||
|
ADTFluxRemotedLevelController::ADTFluxRemotedLevelController()
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
RemoteSubsystem = nullptr;
|
||||||
|
bEventsBound = false;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Constructor called"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::PostInitializeComponents()
|
||||||
|
{
|
||||||
|
Super::PostInitializeComponents();
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: PostInitializeComponents called"));
|
||||||
|
|
||||||
|
// Essayer de bind dès que possible
|
||||||
|
InitializeSubsystemBinding();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::BeginPlay()
|
||||||
|
{
|
||||||
|
Super::BeginPlay();
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: BeginPlay called"));
|
||||||
|
|
||||||
|
// S'assurer que le binding est fait (au cas où PostInitializeComponents aurait échoué)
|
||||||
|
if (!bEventsBound)
|
||||||
|
{
|
||||||
|
InitializeSubsystemBinding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::InitializeSubsystemBinding()
|
||||||
|
{
|
||||||
|
// Éviter le double binding
|
||||||
|
if (bEventsBound)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Events already bound, skipping"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer le subsystem
|
||||||
|
if (UWorld* World = GetWorld())
|
||||||
|
{
|
||||||
|
RemoteSubsystem = GEngine->GetEngineSubsystem<UDTFluxRemoteSubsystem>();
|
||||||
|
|
||||||
|
if (RemoteSubsystem)
|
||||||
|
{
|
||||||
|
// Bind les events du subsystem
|
||||||
|
RemoteSubsystem->OnTitleReceived.AddDynamic(
|
||||||
|
this, &ADTFluxRemotedLevelController::OnTitleDataReceived
|
||||||
|
);
|
||||||
|
RemoteSubsystem->OnTitleBibReceived.AddDynamic(
|
||||||
|
this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived
|
||||||
|
);
|
||||||
|
|
||||||
|
RemoteSubsystem->OnCommandReceived.AddDynamic(
|
||||||
|
this, &ADTFluxRemotedLevelController::OnCommandDataReceived
|
||||||
|
);
|
||||||
|
|
||||||
|
bEventsBound = true;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Successfully bound to subsystem events"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: DTFluxRemoteSubsystem not available yet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: World not available yet"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::EnsureSubsystemBinding()
|
||||||
|
{
|
||||||
|
if (!bEventsBound)
|
||||||
|
{
|
||||||
|
InitializeSubsystemBinding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::EndPlay(const EEndPlayReason::Type EndPlayReason)
|
||||||
|
{
|
||||||
|
// Unbind les events pour éviter les fuites mémoire
|
||||||
|
if (RemoteSubsystem && bEventsBound)
|
||||||
|
{
|
||||||
|
if (TitleReceivedHandle.IsValid())
|
||||||
|
{
|
||||||
|
RemoteSubsystem->OnTitleReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleDataReceived);
|
||||||
|
TitleReceivedHandle.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TitleBibReceivedHandle.IsValid())
|
||||||
|
{
|
||||||
|
RemoteSubsystem->OnTitleBibReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnTitleBibDataReceived);
|
||||||
|
TitleBibReceivedHandle.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CommandReceivedHandle.IsValid())
|
||||||
|
{
|
||||||
|
RemoteSubsystem->OnCommandReceived.RemoveDynamic(this, &ADTFluxRemotedLevelController::OnCommandDataReceived);
|
||||||
|
CommandReceivedHandle.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bEventsBound = false;
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Unbound from subsystem events"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Super::EndPlay(EndPlayReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Data - %s %s (RundownPageId: %d)"),
|
||||||
|
*TitleData.FirstName, *TitleData.LastName, TitleData.RundownPageId);
|
||||||
|
|
||||||
|
// Broadcast l'event Blueprint
|
||||||
|
OnTitleReceived.Broadcast(TitleData);
|
||||||
|
|
||||||
|
// Appeler l'event Blueprint implémentable
|
||||||
|
BP_OnTitleDataReceived(TitleData);
|
||||||
|
|
||||||
|
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||||
|
HandleTitleData(TitleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Title Bib Data - Bib: %d"), BibData.Bib);
|
||||||
|
|
||||||
|
// Broadcast l'event Blueprint
|
||||||
|
OnTitleBibReceived.Broadcast(BibData);
|
||||||
|
|
||||||
|
// Appeler l'event Blueprint implémentable
|
||||||
|
BP_OnTitleBibDataReceived(BibData);
|
||||||
|
|
||||||
|
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||||
|
HandleTitleBibData(BibData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("DTFluxRemoteActor: Received Command Data - Type: %s, : RundownPageId %i"),
|
||||||
|
*CommandData.Type, CommandData.RundownPageId);
|
||||||
|
|
||||||
|
// Broadcast l'event Blueprint
|
||||||
|
OnCommandReceived.Broadcast(CommandData);
|
||||||
|
|
||||||
|
// Appeler l'event Blueprint implémentable
|
||||||
|
BP_OnCommandDataReceived(CommandData);
|
||||||
|
|
||||||
|
// Appeler la fonction virtuelle C++ (peut être overridée dans les classes dérivées)
|
||||||
|
HandleCommandData(CommandData);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADTFluxRemotedLevelController::IsSubsystemAvailable() const
|
||||||
|
{
|
||||||
|
return RemoteSubsystem && RemoteSubsystem->IsValidLowLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ADTFluxRemotedLevelController::IsHTTPServerRunning() const
|
||||||
|
{
|
||||||
|
if (RemoteSubsystem)
|
||||||
|
{
|
||||||
|
return RemoteSubsystem->IsHTTPServerRunning();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::StartHTTPServer(int32 Port)
|
||||||
|
{
|
||||||
|
if (RemoteSubsystem)
|
||||||
|
{
|
||||||
|
RemoteSubsystem->StartHTTPServer(Port);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot start HTTP server - subsystem not available"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::StopHTTPServer()
|
||||||
|
{
|
||||||
|
if (RemoteSubsystem)
|
||||||
|
{
|
||||||
|
RemoteSubsystem->StopHTTPServer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("DTFluxRemoteActor: Cannot stop HTTP server - subsystem not available"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implémentations par défaut des fonctions virtuelles C++
|
||||||
|
void ADTFluxRemotedLevelController::HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData)
|
||||||
|
{
|
||||||
|
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||||
|
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Data (default implementation)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData)
|
||||||
|
{
|
||||||
|
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||||
|
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Title Bib Data (default implementation)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADTFluxRemotedLevelController::HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData)
|
||||||
|
{
|
||||||
|
// Implémentation par défaut - peut être overridée dans les classes dérivées
|
||||||
|
UE_LOG(LogTemp, Verbose, TEXT("DTFluxRemoteActor: Handling Command Data (default implementation)"));
|
||||||
|
}
|
||||||
76
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal file
76
Source/DTFluxRemote/Public/DTFluxRemoteMessage.h
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "DTFluxRemoteMessage.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxRemoteBasicData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FDateTime UpdateAt = FDateTime::Now();
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
int RundownPageId = -1;
|
||||||
|
FDTFluxRemoteBasicData() = default;
|
||||||
|
FDTFluxRemoteBasicData(const FDateTime& InUpdateAt): UpdateAt(InUpdateAt){};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxRemoteTitleData : public FDTFluxRemoteBasicData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FString FirstName = "";
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FString LastName = "";
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FString Function1 = "";
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FString Function2 = "";
|
||||||
|
|
||||||
|
|
||||||
|
FDTFluxRemoteTitleData() = default;
|
||||||
|
FDTFluxRemoteTitleData(const FString InFirstName, const FString InLastName, const FString InFunction1, const FString InFunction2):
|
||||||
|
FirstName(InFirstName), LastName(InLastName), Function1(InFunction1), Function2(InFunction2){};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxRemoteBibData : public FDTFluxRemoteBasicData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
int Bib = -1;
|
||||||
|
|
||||||
|
FDTFluxRemoteBibData() = default;
|
||||||
|
FDTFluxRemoteBibData(int InBib): Bib(InBib){};
|
||||||
|
};
|
||||||
|
USTRUCT(BlueprintType)
|
||||||
|
struct FDTFluxRemoteCommandData : public FDTFluxRemoteBasicData
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
FDTFluxRemoteCommandData() = default;
|
||||||
|
|
||||||
|
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category="DTFlux|Remote")
|
||||||
|
FString Type = "Stop";
|
||||||
|
|
||||||
|
|
||||||
|
FDTFluxRemoteCommandData(FString InType):
|
||||||
|
Type(InType){};
|
||||||
|
};
|
||||||
|
|
||||||
13
Source/DTFluxRemote/Public/DTFluxRemoteModule.h
Normal file
13
Source/DTFluxRemote/Public/DTFluxRemoteModule.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
|
DTFLUXREMOTE_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxRemote, Log, All);
|
||||||
|
|
||||||
|
class DTFLUXREMOTE_API FDTFluxRemoteModule : public IModuleInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void StartupModule() override;
|
||||||
|
virtual void ShutdownModule() override;
|
||||||
|
};
|
||||||
117
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal file
117
Source/DTFluxRemote/Public/DTFluxRemoteSubsystem.h
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
|
#include "DTFluxRemoteMessage.h"
|
||||||
|
#include "HttpRouteHandle.h"
|
||||||
|
#include "IHttpRouter.h"
|
||||||
|
#include "DTFluxRemoteSubsystem.generated.h"
|
||||||
|
|
||||||
|
class UAvaRundown;
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleReceived, const FDTFluxRemoteTitleData&, TitleData);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnTitleBibReceived, const FDTFluxRemoteBibData&, TitleBibData);
|
||||||
|
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCommandReceived, const FDTFluxRemoteCommandData&, CommandData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS(BlueprintType, Category="DTFlux|Remote")
|
||||||
|
class DTFLUXREMOTE_API UDTFluxRemoteSubsystem : public UEngineSubsystem
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
public:
|
||||||
|
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||||
|
virtual void Deinitialize() override;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||||
|
FOnTitleReceived OnTitleReceived;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||||
|
FOnTitleBibReceived OnTitleBibReceived;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux API")
|
||||||
|
FOnCommandReceived OnCommandReceived;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
bool StartHTTPServer(int32 Port = 63350);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
void StopHTTPServer();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
|
||||||
|
bool IsHTTPServerRunning() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API", BlueprintPure)
|
||||||
|
int32 GetServerPort() const { return ServerPort; }
|
||||||
|
|
||||||
|
// Manual data processing (for testing)
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
bool ProcessTitleData(const FString& JsonString);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
bool ProcessTitleBibData(const FString& JsonString);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
bool ProcessCommandData(const FString& JsonString);
|
||||||
|
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||||
|
bool bHasPendingTitleRequest = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||||
|
bool bHasPendingTitleBibRequest = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||||
|
FDTFluxRemoteTitleData PendingTitleData;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "DTFlux API")
|
||||||
|
FDTFluxRemoteBibData PendingTitleBibData;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
void ResetPendingTitleData();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux API")
|
||||||
|
void ResetPendingBibData();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetupRoutes();
|
||||||
|
|
||||||
|
bool HandleTitleRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||||
|
bool HandleTitleBibRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||||
|
bool HandleCommandsRequest(const FHttpServerRequest& Request, const FHttpResultCallback& OnComplete);
|
||||||
|
|
||||||
|
TSharedPtr<FJsonObject> ParseJsonFromRequest(const FHttpServerRequest& Request);
|
||||||
|
FString CreateSuccessResponse(const FString& Message = TEXT("Success"));
|
||||||
|
FString CreateErrorResponse(const FString& Error, int32 Code = 400);
|
||||||
|
|
||||||
|
bool ParseTitleData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteTitleData& OutData);
|
||||||
|
bool ParseTitleBibData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteBibData& OutData);
|
||||||
|
bool ParseCommandData(const TSharedPtr<FJsonObject>& JsonObject, FDTFluxRemoteCommandData& OutData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TSharedPtr<IHttpRouter> HttpRouter;
|
||||||
|
TSoftObjectPtr<UAvaRundown> RemotedRundown;
|
||||||
|
int32 ServerPort = 63350;
|
||||||
|
bool bServerRunning = false;
|
||||||
|
|
||||||
|
FHttpRouteHandle TitleRouteHandle;
|
||||||
|
FHttpRouteHandle TitleBibRouteHandle;
|
||||||
|
FHttpRouteHandle CommandsRouteHandle;
|
||||||
|
|
||||||
|
void UnloadCurrentRundown();
|
||||||
|
void LoadRundownFromSettings();
|
||||||
|
bool LoadRundown(const TSoftObjectPtr<UAvaRundown>& RundownAsset);
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FDelegateHandle SettingsRundownChangedHandle;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
// Callback pour les changements de settings
|
||||||
|
UFUNCTION()
|
||||||
|
void OnSettingsRundownChanged(const TSoftObjectPtr<UAvaRundown>& NewRundown);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
94
Source/DTFluxRemote/Public/DTFluxRemotedLevelController.h
Normal file
94
Source/DTFluxRemote/Public/DTFluxRemotedLevelController.h
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
// DTFluxRemotedLevelController.h
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "DTFluxRemoteSubsystem.h"
|
||||||
|
#include "DTFluxRemotedLevelController.generated.h"
|
||||||
|
|
||||||
|
UCLASS(BlueprintType, Blueprintable)
|
||||||
|
class DTFLUXREMOTE_API ADTFluxRemotedLevelController : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
ADTFluxRemotedLevelController();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void PostInitializeComponents() override;
|
||||||
|
virtual void BeginPlay() override;
|
||||||
|
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
|
||||||
|
|
||||||
|
// Subsystem et binding
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
|
||||||
|
UDTFluxRemoteSubsystem* RemoteSubsystem;
|
||||||
|
|
||||||
|
FDelegateHandle TitleReceivedHandle;
|
||||||
|
FDelegateHandle TitleBibReceivedHandle;
|
||||||
|
FDelegateHandle CommandReceivedHandle;
|
||||||
|
bool bEventsBound;
|
||||||
|
|
||||||
|
// Fonctions de binding
|
||||||
|
void InitializeSubsystemBinding();
|
||||||
|
|
||||||
|
// ✅ CORRECTION : Callbacks avec UFUNCTION()
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
|
||||||
|
|
||||||
|
UFUNCTION()
|
||||||
|
void OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Events Blueprint-friendly
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||||
|
FOnTitleReceived OnTitleReceived;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||||
|
FOnTitleBibReceived OnTitleBibReceived;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintAssignable, Category = "DTFlux Events")
|
||||||
|
FOnCommandReceived OnCommandReceived;
|
||||||
|
|
||||||
|
// Fonctions utilitaires
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
bool IsSubsystemAvailable() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
bool IsHTTPServerRunning() const;
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
void StartHTTPServer(int32 Port = 63350);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
void StopHTTPServer();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
void EnsureSubsystemBinding();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Events Blueprint implémentables
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||||
|
void BP_OnTitleDataReceived(const FDTFluxRemoteTitleData& TitleData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||||
|
void BP_OnTitleBibDataReceived(const FDTFluxRemoteBibData& BibData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintImplementableEvent, Category = "DTFlux Events")
|
||||||
|
void BP_OnCommandDataReceived(const FDTFluxRemoteCommandData& CommandData);
|
||||||
|
|
||||||
|
// Fonctions virtuelles C++
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||||
|
void HandleTitleData(const FDTFluxRemoteTitleData& TitleData);
|
||||||
|
virtual void HandleTitleData_Implementation(const FDTFluxRemoteTitleData& TitleData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||||
|
void HandleTitleBibData(const FDTFluxRemoteBibData& BibData);
|
||||||
|
virtual void HandleTitleBibData_Implementation(const FDTFluxRemoteBibData& BibData);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintNativeEvent, Category = "DTFlux Events")
|
||||||
|
void HandleCommandData(const FDTFluxRemoteCommandData& CommandData);
|
||||||
|
virtual void HandleCommandData_Implementation(const FDTFluxRemoteCommandData& CommandData);
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ public class DTFluxUtilities : ModuleRules
|
|||||||
PublicDependencyModuleNames.AddRange(
|
PublicDependencyModuleNames.AddRange(
|
||||||
new string[]
|
new string[]
|
||||||
{
|
{
|
||||||
"Core",
|
"Core"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -19,7 +19,10 @@ public class DTFluxUtilities : ModuleRules
|
|||||||
"CoreUObject",
|
"CoreUObject",
|
||||||
"Engine",
|
"Engine",
|
||||||
"Slate",
|
"Slate",
|
||||||
"SlateCore"
|
"SlateCore",
|
||||||
|
"DTFluxCore",
|
||||||
|
"DTFluxCoreSubsystem",
|
||||||
|
"AvalancheMedia",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
#include "DTFluxDatesUtilities.h"
|
#include "DTFluxDatesUtilities.h"
|
||||||
|
|
||||||
#include "DTFluxUtilitiesModule.h"
|
#include "DTFluxUtilitiesModule.h"
|
||||||
@ -16,18 +15,20 @@ DTFluxDatesUtilities::~DTFluxDatesUtilities()
|
|||||||
|
|
||||||
bool DTFluxDatesUtilities::CompileDateAndTime(const FString& Time, const FString& Date, FDateTime& OutDateTime)
|
bool DTFluxDatesUtilities::CompileDateAndTime(const FString& Time, const FString& Date, FDateTime& OutDateTime)
|
||||||
{
|
{
|
||||||
if(Time.Len() < 8 && Date.Len() < 10)
|
if (Time.Len() < 8 && Date.Len() < 10)
|
||||||
{
|
{
|
||||||
TArray<FString> ExplodedTime;
|
TArray<FString> ExplodedTime;
|
||||||
Time.ParseIntoArray(ExplodedTime, TEXT(":"));
|
Time.ParseIntoArray(ExplodedTime, TEXT(":"));
|
||||||
if(ExplodedTime.Num() != 3 && !ExplodedTime[0].IsNumeric() && !ExplodedTime[1].IsNumeric() & !ExplodedTime[2].IsNumeric())
|
if (ExplodedTime.Num() != 3 && !ExplodedTime[0].IsNumeric() && !ExplodedTime[1].IsNumeric() & !ExplodedTime[2].
|
||||||
|
IsNumeric())
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxUtilities, Error, TEXT("Bad Time Format [%s]. Unable to parse"), *Time);
|
UE_LOG(logDTFluxUtilities, Error, TEXT("Bad Time Format [%s]. Unable to parse"), *Time);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TArray<FString> ExplodedDate;
|
TArray<FString> ExplodedDate;
|
||||||
Date.ParseIntoArray(ExplodedDate, TEXT("-"));
|
Date.ParseIntoArray(ExplodedDate, TEXT("-"));
|
||||||
if(ExplodedDate.Num() != 3 && !ExplodedDate[0].IsNumeric() && !ExplodedDate[1].IsNumeric() && !ExplodedDate[2].IsNumeric() )
|
if (ExplodedDate.Num() != 3 && !ExplodedDate[0].IsNumeric() && !ExplodedDate[1].IsNumeric() && !ExplodedDate[2].
|
||||||
|
IsNumeric())
|
||||||
{
|
{
|
||||||
UE_LOG(logDTFluxUtilities, Error, TEXT("Bad Date Format [%s]. Unable to parse"), *Date);
|
UE_LOG(logDTFluxUtilities, Error, TEXT("Bad Date Format [%s]. Unable to parse"), *Date);
|
||||||
return false;
|
return false;
|
||||||
@ -38,7 +39,7 @@ bool DTFluxDatesUtilities::CompileDateAndTime(const FString& Time, const FString
|
|||||||
int32 Day = FCString::Atoi(*ExplodedDate[2]);
|
int32 Day = FCString::Atoi(*ExplodedDate[2]);
|
||||||
int32 Month = FCString::Atoi(*ExplodedDate[1]);
|
int32 Month = FCString::Atoi(*ExplodedDate[1]);
|
||||||
int32 Year = FCString::Atoi(*ExplodedDate[0]);
|
int32 Year = FCString::Atoi(*ExplodedDate[0]);
|
||||||
if(FDateTime::Validate(Year, Month, Day, Hours, Minutes, Seconds, 0))
|
if (FDateTime::Validate(Year, Month, Day, Hours, Minutes, Seconds, 0))
|
||||||
{
|
{
|
||||||
OutDateTime = FDateTime(Year, Month, Day, Hours, Minutes, Seconds);
|
OutDateTime = FDateTime(Year, Month, Day, Hours, Minutes, Seconds);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
67
Source/DTFluxUtilities/Private/DTFluxUtils.cpp
Normal file
67
Source/DTFluxUtilities/Private/DTFluxUtils.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
|
|
||||||
|
#include "DTFluxUtils.h"
|
||||||
|
|
||||||
|
#include "DTFluxCoreSubsystem.h"
|
||||||
|
#include "DTFluxUtilitiesModule.h"
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString Separator,
|
||||||
|
const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
if (CoreSubsystem != nullptr)
|
||||||
|
{
|
||||||
|
FDTFluxParticipant OutParticipant;
|
||||||
|
CoreSubsystem->GetParticipant(Bib, OutParticipant);
|
||||||
|
return OutParticipant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
|
||||||
|
return FText();
|
||||||
|
}
|
||||||
|
|
||||||
|
FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
|
||||||
|
const FString Separator,
|
||||||
|
const FString OverFlowChar)
|
||||||
|
{
|
||||||
|
return Participant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UFTDFluxUtils::GetFullName(const int Bib, FText& OutFullName)
|
||||||
|
{
|
||||||
|
OutFullName = FText();
|
||||||
|
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
|
||||||
|
if(CoreSubsystem != nullptr)
|
||||||
|
{
|
||||||
|
FDTFluxParticipant Participant;
|
||||||
|
if(CoreSubsystem->GetParticipant(Bib, Participant))
|
||||||
|
{
|
||||||
|
FString FormattedName = "";
|
||||||
|
if (Participant.IsTeam())
|
||||||
|
{
|
||||||
|
OutFullName = FText::FromString(Participant.Team);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Participant.GetTeammate().IsEmpty())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxUtilities, Warning, TEXT("Non teammate found with Bib %i"), Bib)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OutFullName = FText::FromString(FString::Printf(TEXT("%s %s"), *Participant.GetTeammate()[0].FirstName,
|
||||||
|
*Participant.GetTeammate()[0].LastName));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxUtilities, Warning, TEXT("Participant not found with Bib %i"), Bib);
|
||||||
|
}
|
||||||
|
UE_LOG(logDTFluxUtilities, Error, TEXT("DTFluxCoreSubsystem not available"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FDTFluxSplitSensorInfo> UFTDFluxUtils::SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings)
|
||||||
|
{
|
||||||
|
TArray<FDTFluxSplitSensorInfo> CopyRankings = Rankings;
|
||||||
|
CopyRankings.Sort([](const FDTFluxSplitSensorInfo& A, const FDTFluxSplitSensorInfo& B)
|
||||||
|
{
|
||||||
|
return A.Rank < B.Rank;
|
||||||
|
});
|
||||||
|
return CopyRankings;
|
||||||
|
}
|
||||||
114
Source/DTFluxUtilities/Private/RundownController.cpp
Normal file
114
Source/DTFluxUtilities/Private/RundownController.cpp
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// ===============================================
|
||||||
|
// 3. SOURCE FILE (.CPP) - TOUS LES INCLUDES
|
||||||
|
// ===============================================
|
||||||
|
|
||||||
|
// YourRundownController.cpp
|
||||||
|
#include "RundownController.h"
|
||||||
|
|
||||||
|
#include "DTFluxUtilitiesModule.h"
|
||||||
|
#include "Rundown/AvaRundown.h"
|
||||||
|
#include "Rundown/AvaRundownPage.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "UObject/UObjectGlobals.h"
|
||||||
|
#include "UObject/ConstructorHelpers.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "Logging/LogMacros.h"
|
||||||
|
#include "Components/StaticMeshComponent.h"
|
||||||
|
#include "Materials/MaterialInterface.h"
|
||||||
|
#include "Blueprint/UserWidget.h"
|
||||||
|
|
||||||
|
DEFINE_LOG_CATEGORY_STATIC(LogYourRundownController, Log, All);
|
||||||
|
|
||||||
|
// ===============================================
|
||||||
|
// 4. IMPLÉMENTATION SIMPLE
|
||||||
|
// ===============================================
|
||||||
|
|
||||||
|
ARundownController::ARundownController()
|
||||||
|
{
|
||||||
|
PrimaryActorTick.bCanEverTick = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ARundownController::LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset)
|
||||||
|
{
|
||||||
|
if (RundownAsset.IsNull())
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxUtilities, Error, TEXT("RundownAsset Null"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Charger l'asset rundown
|
||||||
|
CurrentRundown = RundownAsset.LoadSynchronous();
|
||||||
|
|
||||||
|
if (!CurrentRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Error, TEXT("Failed to load rundown: %s"), *RundownAsset.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialiser le contexte de playback
|
||||||
|
CurrentRundown->InitializePlaybackContext();
|
||||||
|
|
||||||
|
UE_LOG(LogYourRundownController, Log, TEXT("Successfully loaded rundown: %s"), *RundownAsset.ToString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ARundownController::PlayPage(int32 PageId)
|
||||||
|
{
|
||||||
|
if (!CurrentRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded. Call LoadRundown first."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que la page existe
|
||||||
|
const FAvaRundownPage& Page = CurrentRundown->GetPage(PageId);
|
||||||
|
if (!Page.IsValidPage())
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Error, TEXT("Invalid page ID: %d"), PageId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jouer la page
|
||||||
|
bool bSuccess = CurrentRundown->PlayPage(PageId, EAvaRundownPagePlayType::PlayFromStart);
|
||||||
|
|
||||||
|
if (bSuccess)
|
||||||
|
{
|
||||||
|
CurrentPageId = PageId;
|
||||||
|
UE_LOG(LogYourRundownController, Log, TEXT("Playing page %d: %s"), PageId, *Page.GetPageName());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Warning, TEXT("Failed to play page %d"), PageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ARundownController::StopPage(int32 PageId)
|
||||||
|
{
|
||||||
|
if (!CurrentRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Error, TEXT("No rundown loaded"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bSuccess = CurrentRundown->StopPage(PageId, EAvaRundownPageStopOptions::Default, false);
|
||||||
|
|
||||||
|
if (bSuccess)
|
||||||
|
{
|
||||||
|
UE_LOG(LogYourRundownController, Log, TEXT("Stopped page %d"), PageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString ARundownController::ListePages()
|
||||||
|
{
|
||||||
|
if (!CurrentRundown)
|
||||||
|
{
|
||||||
|
UE_LOG(logDTFluxUtilities, Error, TEXT("No rundown loaded"));
|
||||||
|
return FString();
|
||||||
|
}
|
||||||
|
return FString();
|
||||||
|
}
|
||||||
@ -1,11 +1,8 @@
|
|||||||
// Fill out your copyright notice in the Description page of Project Settings.
|
// Fill out your copyright notice in the Description page of Project Settings.
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class DTFLUXUTILITIES_API DTFluxDatesUtilities
|
class DTFLUXUTILITIES_API DTFluxDatesUtilities
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
77
Source/DTFluxUtilities/Public/DTFluxUtils.h
Normal file
77
Source/DTFluxUtilities/Public/DTFluxUtils.h
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 "Types/Struct/DTFluxSplitSensor.h"
|
||||||
|
#include "DTFluxUtils.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 Separator = ".",
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
|
||||||
|
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
|
||||||
|
const FString Separator = ".",
|
||||||
|
const FString OverFlowChar = "...");
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
|
||||||
|
static void CastToDTFluxStageRanking(const FDTFluxDetailedRankingItem& ItemRanking, FDTFluxStageRanking& OutRanking)
|
||||||
|
{
|
||||||
|
CastRankingItem<FDTFluxStageRanking>(ItemRanking, OutRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
|
||||||
|
static void CastToDTFluxStageRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking,
|
||||||
|
TArray<FDTFluxStageRanking>& OutRanking)
|
||||||
|
{
|
||||||
|
CastRankingArray<FDTFluxStageRanking>(ItemRanking, OutRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
|
||||||
|
static void CastToDTFluxSplitRanking(const FDTFluxDetailedRankingItem& ItemRanking, FDTFluxSplitRanking& OutRanking)
|
||||||
|
{
|
||||||
|
CastRankingItem<FDTFluxSplitRanking>(ItemRanking, OutRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="convert, StageRankings, DTFlux"))
|
||||||
|
static void CastToDTFluxSplitRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking,
|
||||||
|
TArray<FDTFluxSplitRanking>& OutRanking)
|
||||||
|
{
|
||||||
|
CastRankingArray<FDTFluxSplitRanking>(ItemRanking, OutRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void CastRankingItem(const FDTFluxDetailedRankingItem& ItemRanking, T& OutRanking)
|
||||||
|
{
|
||||||
|
OutRanking = static_cast<T>(ItemRanking);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static void CastRankingArray(const TArray<FDTFluxDetailedRankingItem>& ItemRanking, TArray<T>& OutRanking)
|
||||||
|
{
|
||||||
|
OutRanking.Empty();
|
||||||
|
for (auto& Item : ItemRanking)
|
||||||
|
{
|
||||||
|
OutRanking.Add(static_cast<T>(Item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
|
||||||
|
static void GetFullName(const int Bib, FText& OutFullName);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils")
|
||||||
|
static TArray<FDTFluxSplitSensorInfo> SortSplitRankingsByRank(const TArray<FDTFluxSplitSensorInfo>& Rankings);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
41
Source/DTFluxUtilities/Public/RundownController.h
Normal file
41
Source/DTFluxUtilities/Public/RundownController.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
|
#include "Engine/World.h"
|
||||||
|
#include "TimerManager.h"
|
||||||
|
#include "Rundown/AvaRundown.h"
|
||||||
|
#include "UObject/SoftObjectPath.h"
|
||||||
|
#include "UObject/ObjectMacros.h"
|
||||||
|
#include "RundownController.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UCLASS(BlueprintType, Blueprintable)
|
||||||
|
class DTFLUXUTILITIES_API ARundownController : public AActor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
ARundownController();
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
bool LoadRundown(const TSoftObjectPtr<UAvaRundown> RundownAsset);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
bool PlayPage(int32 PageId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
bool StopPage(int32 PageId);
|
||||||
|
|
||||||
|
UFUNCTION(BlueprintCallable, Category = "DTFlux")
|
||||||
|
FString ListePages();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
UPROPERTY(BlueprintReadOnly, Category = "DTFlux")
|
||||||
|
TObjectPtr<UAvaRundown> CurrentRundown;
|
||||||
|
|
||||||
|
UPROPERTY(BlueprintReadWrite, Category = "DTFlux")
|
||||||
|
int32 CurrentPageId = 1;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user