Compare commits

..

30 Commits

Author SHA1 Message Date
c02993057f Fixing GetFormattedName AGAIN 2025-07-15 05:17:30 +02:00
69536766b1 Fixing GetFullName 2025-07-15 05:13:31 +02:00
e63f54a882 Fixing SplitRanking tracked request bug in InitSplitRankingDisplay 2025-07-15 04:59:59 +02:00
6b58525349 Fixing DTFluxSplit Non need for Split Rankings 2025-07-15 01:23:57 +02:00
505e4b7af2 Added Utility Function to get FirstName And LastName of a Participant 2025-07-15 01:11:18 +02:00
ae8b694f69 Re fixing Rank init value 2025-07-14 23:59:46 +02:00
a0b3ad6d8c reset change Rank 2025-07-14 23:55:53 +02:00
f644fb99a8 Fixed intial Rank value; 2025-07-14 23:24:24 +02:00
fdae0fddc8 Added when not teamate return "" 2025-07-14 23:19:27 +02:00
f304685f01 Fixing ArrayOut of bound in FormattedName (regression) 2025-07-14 23:15:10 +02:00
07c83b5c8e Updated Participant factory and FormattedName for better mem managment 2025-07-14 20:41:53 +02:00
de5ed7f328 Fixed Team Name + Rankings Are now EditAnywhere 2025-07-14 16:50:07 +02:00
0a175f7813 Added Events OnDisplayRankings to launch Display of rankings in BluePrints. 2025-07-14 15:40:03 +02:00
4bbdf43ffa Update Function to ensure ref is returned instead of copy for GetContest GetContestForTime 2025-07-14 09:58:10 +02:00
51e5898d4b Cosmetic CoreSubsystem cleaning + Added DTFluxDetailedRanking Casting functions 2025-07-14 09:33:25 +02:00
1c04ae6bd7 Added Blank Delegate for split Sensors + Added Getters for Contest/Stage/Split rankings for 1 participant + Global Cleaning 2025-07-14 02:40:03 +02:00
e6d878aeef Fixed bIsMassStart assign On NextFocus 2025-07-13 18:07:14 +02:00
a1e6902a15 Update FormatedName functionality now just truncates and adds OverflowChar + fixing getter Split, Stage, Contest 2025-07-13 04:48:05 +02:00
c2733a2b97 Added Getter Stage/Contest/Split + Bug Fix PursuitManager Extract Passed Started Pursuit. 2025-07-13 04:14:54 +02:00
76a199e95e Added Datetime sorting functionality and Metrics test on GetPursit 2025-07-13 02:38:05 +02:00
6a88fec83a Updated Separator On Name Computation 2025-07-13 01:18:31 +02:00
700a7120f0 Core Structure cleaning 2025-07-13 00:59:59 +02:00
f1d583926b Removed CachedRequest functionality + Fixed(Tested) PursuitSequence bug 2025-07-12 16:07:37 +02:00
a08bcd98a4 Put back OnSequenceReady delegate in PursuitManager + PursuitManager General CleanUp 2025-07-12 09:41:15 +02:00
d7a189f905 Fixed broken ModelAsset Details Customization (not showing customization for ModelAsset) + Added TODO comment on StageRanking StartTime computation has the method is opsque 2025-07-12 09:27:02 +02:00
0d851b7298 Added FirstName/LastName Separator for displaying formatted name. 2025-07-12 09:23:43 +02:00
d92ca63ea4 Add TrackedRequest implementation in DTFluxCoreSubsystem and various Modifications and bugfixes. 2025-07-11 23:45:23 +02:00
f1f300a351 Fixing Various Bugs On Delegates 2025-07-11 19:04:37 +02:00
73413e44b4 implement Pursuit Sequence Logic 2025-07-11 13:09:18 +02:00
bc6a928312 Switching DTFluxApi Tab Implementation to use the more modern UToolMenus approach API 2025-07-10 20:35:06 +02:00
44 changed files with 4054 additions and 2535 deletions

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="512"
height="512"
viewBox="0 0 512 512"
version="1.1"
id="svg1"
xml:space="preserve"
inkscape:export-filename="RaceResult.icone16x16.svg"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
sodipodi:docname="RaceResult.icone16x16.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:document-units="px" /><defs
id="defs1" /><g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(3.0517578e-5)"><rect
style="fill:#e22c2b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
id="rect1"
width="512"
height="512"
x="-3.0517578e-05"
y="0" /><path
style="fill:none;stroke:#ffffff;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
d="m 154.64305,277.08769 -1.3605,-151.92206 146.48007,103.3977 v 184.12046 l 54.87334,-50.79185 V 199.53942 L 215.41187,92.967228 V 328.78654 Z"
id="path1" /></g></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -31,6 +31,7 @@ public class DTFluxAPIStatus : ModuleRules
"DTFluxCoreSubsystem", "DTFluxCoreSubsystem",
"InputCore", "InputCore",
"OutputLog", "OutputLog",
"ToolMenus",
} }
); );
} }

View File

@ -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);

View File

@ -338,7 +338,8 @@ void SDTFluxStatusWidget::PopulateComboBoxItems()
{ {
FString Separator = " | "; FString Separator = " | ";
FString RootSeparator = " -> "; FString RootSeparator = " -> ";
TArray<FDTFluxContest> DataFromSubsystem = DTFluxCore->GetContests(); TArray<FDTFluxContest> DataFromSubsystem = TArray<FDTFluxContest>();
DTFluxCore->GetContests(DataFromSubsystem);
for (const auto& Contest : DataFromSubsystem) for (const auto& Contest : DataFromSubsystem)
{ {
@ -416,7 +417,7 @@ FSlateColor SDTFluxStatusWidget::GetComboItemRankingColor(const TSharedPtr<FComb
FReply SDTFluxStatusWidget::OnRankingButtonClicked() const FReply SDTFluxStatusWidget::OnRankingButtonClicked() const
{ {
if (DTFluxNetwork) if (DTFluxCore)
{ {
// Exemple d'envoi de requête basée sur la sélection // Exemple d'envoi de requête basée sur la sélection
int ForContest = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->ContestId : -1; int ForContest = SelectedContestComboBoxItem.IsValid() ? SelectedContestComboBoxItem->ContestId : -1;
@ -432,13 +433,19 @@ FReply SDTFluxStatusWidget::OnRankingButtonClicked() const
if (ForStage == -1) if (ForStage == -1)
{ {
UE_LOG(logDTFluxStatus, Warning, TEXT("Stage not selected !!!! Requesting contest Ranking")); UE_LOG(logDTFluxStatus, Warning, TEXT("Stage not selected !!!! Requesting contest Ranking"));
RequestType = EDTFluxApiDataType::ContestRanking; DTFluxCore->TrackedRequestContestRankings({ForContest});
DTFluxNetwork->SendRequest(RequestType, ForContest);
return FReply::Handled(); return FReply::Handled();
} }
RequestType = ForSplit == -1 ? EDTFluxApiDataType::StageRanking : EDTFluxApiDataType::SplitRanking; 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)); UE_LOG(logDTFluxStatus, Warning, TEXT("Requesting %s Ranking"), *UEnum::GetValueAsString(RequestType));
DTFluxNetwork->SendRequest(RequestType, ForContest, ForStage, ForSplit);
} }
return FReply::Handled(); return FReply::Handled();

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// 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 "Widget/DTFluxAssetModelDetailsWidget.h" #include "Widget/DTFluxAssetModelDetailsWidget.h"
#include "DTFluxAssetsEditorModule.h"
#include "Widgets/Layout/SBorder.h" #include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SBox.h"
#include "Widgets/Text/STextBlock.h" #include "Widgets/Text/STextBlock.h"
@ -23,19 +24,19 @@ TSharedRef<SWidget> SHierarchicalTreeItemRow::GenerateWidgetForColumn(const FNam
{ {
if (!Item.IsValid()) if (!Item.IsValid())
{ {
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString()); // UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid item for column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Item")); return SNew(STextBlock).Text(FText::FromString("Invalid Item"));
} }
if (!ParentWidget) if (!ParentWidget)
{ {
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"), // UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Invalid ParentWidget for column %s"),
*ColumnName.ToString()); // *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString("Invalid Parent")); return SNew(STextBlock).Text(FText::FromString("Invalid Parent"));
} }
UE_LOG(LogTemp, VeryVerbose, TEXT("GenerateWidgetForColumn: %s for item %s"), // UE_LOG(logDTFluxAssetEditor, VeryVerbose, TEXT("GenerateWidgetForColumn: %s for item %s"),
*ColumnName.ToString(), *Item->Name); // *ColumnName.ToString(), *Item->Name);
if (ColumnName == "Name") if (ColumnName == "Name")
{ {
@ -93,7 +94,7 @@ TSharedRef<SWidget> SHierarchicalTreeItemRow::GenerateWidgetForColumn(const FNam
.OverflowPolicy(ETextOverflowPolicy::Ellipsis); .OverflowPolicy(ETextOverflowPolicy::Ellipsis);
} }
UE_LOG(LogTemp, Warning, TEXT("GenerateWidgetForColumn: Unknown column %s"), *ColumnName.ToString()); UE_LOG(logDTFluxAssetEditor, Warning, TEXT("GenerateWidgetForColumn: Unknown column %s"), *ColumnName.ToString());
return SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("Unknown: %s"), *ColumnName.ToString()))); return SNew(STextBlock).Text(FText::FromString(FString::Printf(TEXT("Unknown: %s"), *ColumnName.ToString())));
} }
@ -411,7 +412,7 @@ void SDTFluxAssetModelDetailsWidget::BuildContestHierarchy()
RootItems.Add(ContestItem); RootItems.Add(ContestItem);
} }
UE_LOG(LogTemp, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num()); // UE_LOG(logDTFluxAssetEditor, Log, TEXT("Built contest hierarchy with %d root contests"), RootItems.Num());
} }
void SDTFluxAssetModelDetailsWidget::BuildParticipantList() void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
@ -420,11 +421,11 @@ void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
if (!ModelAsset) if (!ModelAsset)
{ {
UE_LOG(LogTemp, Warning, TEXT("BuildParticipantList: ModelAsset is null!")); UE_LOG(logDTFluxAssetEditor, Warning, TEXT("BuildParticipantList: ModelAsset is null!"));
return; // return;
} }
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: ModelAsset has %d participants"), ModelAsset->Participants.Num()); // 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) // Créer la liste des participants (pas de hiérarchie pour les participants)
for (const auto& ParticipantPair : ModelAsset->Participants) for (const auto& ParticipantPair : ModelAsset->Participants)
@ -433,12 +434,12 @@ void SDTFluxAssetModelDetailsWidget::BuildParticipantList()
auto ParticipantItem = FHierarchicalTreeItem::CreateParticipant(Participant); auto ParticipantItem = FHierarchicalTreeItem::CreateParticipant(Participant);
ParticipantItems.Add(ParticipantItem); ParticipantItems.Add(ParticipantItem);
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"), // UE_LOG(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: Added participant %s (Bib: %d)"),
*ParticipantItem->Name, ParticipantItem->Bib); // *ParticipantItem->Name, ParticipantItem->Bib);
} }
UE_LOG(LogTemp, Log, TEXT("BuildParticipantList: Built participant list with %d participants"), // UE_LOG(logDTFluxAssetEditor, Log, TEXT("BuildParticipantList: Built participant list with %d participants"),
ParticipantItems.Num()); // ParticipantItems.Num());
} }
// ===== CALLBACKS TREEVIEW ===== // ===== CALLBACKS TREEVIEW =====
@ -448,12 +449,12 @@ TSharedRef<ITableRow> SDTFluxAssetModelDetailsWidget::OnGenerateRowForTree(
{ {
if (!Item.IsValid()) if (!Item.IsValid())
{ {
UE_LOG(LogTemp, Warning, TEXT("OnGenerateRowForTree: Invalid item!")); UE_LOG(logDTFluxAssetEditor, Warning, TEXT("OnGenerateRowForTree: Invalid item!"));
return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable); return SNew(STableRow<FHierarchicalTreeItemPtr>, OwnerTable);
} }
UE_LOG(LogTemp, Log, TEXT("OnGenerateRowForTree: Generating row for %s (Type: %d)"), // UE_LOG(logDTFluxAssetEditor, Log, TEXT("OnGenerateRowForTree: Generating row for %s (Type: %d)"),
*Item->Name, (int32)Item->Type); // *Item->Name, (int32)Item->Type);
return SNew(SHierarchicalTreeItemRow, OwnerTable) return SNew(SHierarchicalTreeItemRow, OwnerTable)
.Item(Item) .Item(Item)
@ -536,7 +537,7 @@ FReply SDTFluxAssetModelDetailsWidget::OnExpandAllClicked()
} }
} }
UE_LOG(LogTemp, Log, TEXT("Expanded all contests")); // UE_LOG(logDTFluxAssetEditor, Log, TEXT("Expanded all contests"));
return FReply::Handled(); return FReply::Handled();
} }
@ -550,14 +551,14 @@ FReply SDTFluxAssetModelDetailsWidget::OnCollapseAllClicked()
} }
} }
UE_LOG(LogTemp, Log, TEXT("Collapsed all contests")); // UE_LOG(logDTFluxAssetEditor, Log, TEXT("Collapsed all contests"));
return FReply::Handled(); return FReply::Handled();
} }
FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked() FReply SDTFluxAssetModelDetailsWidget::OnRefreshClicked()
{ {
RefreshData(); RefreshData();
UE_LOG(LogTemp, Log, TEXT("Data refreshed")); // UE_LOG(logDTFluxAssetEditor, Log, TEXT("Data refreshed"));
return FReply::Handled(); return FReply::Handled();
} }
@ -567,21 +568,17 @@ void SDTFluxAssetModelDetailsWidget::RefreshData()
{ {
if (!ModelAsset) if (!ModelAsset)
{ {
UE_LOG(LogTemp, Warning, TEXT("ModelAsset is null!")); UE_LOG(logDTFluxAssetEditor, Warning, TEXT("ModelAsset is null!"));
return; return;
} }
UE_LOG(LogTemp, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName()); // UE_LOG(logDTFluxAssetEditor, Log, TEXT("RefreshData: Starting refresh for ModelAsset %s"), *ModelAsset->GetName());
// Nettoyer les données existantes
RootItems.Empty(); RootItems.Empty();
ParticipantItems.Empty(); ParticipantItems.Empty();
// Construire la hiérarchie
BuildContestHierarchy(); BuildContestHierarchy();
BuildParticipantList(); BuildParticipantList();
// Refresh les vues
if (ContestTreeView.IsValid()) if (ContestTreeView.IsValid())
{ {
ContestTreeView->RequestTreeRefresh(); ContestTreeView->RequestTreeRefresh();
@ -591,9 +588,8 @@ void SDTFluxAssetModelDetailsWidget::RefreshData()
{ {
ParticipantTreeView->RequestTreeRefresh(); ParticipantTreeView->RequestTreeRefresh();
} }
// UE_LOG(logDTFluxAssetEditor, Log, TEXT("RefreshData: Completed successfully - %d contests, %d participants"), RootItems.Num(),
UE_LOG(LogTemp, Log, TEXT("RefreshData: Completed successfully - %d contests, %d participants"), RootItems.Num(), // ParticipantItems.Num());
ParticipantItems.Num());
} }
// ===== MÉTHODES UTILITAIRES ===== // ===== MÉTHODES UTILITAIRES =====
@ -635,7 +631,6 @@ FText SDTFluxAssetModelDetailsWidget::GetStatsText() const
{ {
if (!ModelAsset) if (!ModelAsset)
return FText::FromString("No data"); return FText::FromString("No data");
return FText::FromString(FString::Printf( return FText::FromString(FString::Printf(
TEXT("Contests: [%d] Participants: [%d] Persons: [%d]"), TEXT("Contests: [%d] Participants: [%d] Persons: [%d]"),
ModelAsset->Contests.Num(), ModelAsset->Contests.Num(),

View File

@ -108,7 +108,7 @@ struct FHierarchicalTreeItem
Item->ContestId = InContestId; Item->ContestId = InContestId;
Item->SplitId = Split.SplitId; Item->SplitId = Split.SplitId;
Item->ID = FString::Printf(TEXT("%d"), Split.SplitId); Item->ID = FString::Printf(TEXT("%d"), Split.SplitId);
Item->Details = FString::Printf(TEXT("%d rankings"), Split.SplitRankings.Num()); Item->Details = FString::Printf(TEXT("rankings"));
Item->Status = TEXT("-"); Item->Status = TEXT("-");
Item->Extra = TEXT("-"); Item->Extra = TEXT("-");
return Item; return Item;
@ -137,7 +137,7 @@ struct FHierarchicalTreeItem
typedef TSharedPtr<FHierarchicalTreeItem> FHierarchicalTreeItemPtr; typedef TSharedPtr<FHierarchicalTreeItem> FHierarchicalTreeItemPtr;
/** /**
* Widget avec STreeView simple et efficace * ModelAsset TreeviewWidget
*/ */
class DTFLUXASSETSEDITOR_API SDTFluxAssetModelDetailsWidget : public SCompoundWidget class DTFLUXASSETSEDITOR_API SDTFluxAssetModelDetailsWidget : public SCompoundWidget
{ {
@ -152,40 +152,33 @@ public:
void Construct(const FArguments& InArgs); void Construct(const FArguments& InArgs);
void RefreshData(); void RefreshData();
// Méthodes publiques pour la sous-classe Row
FSlateColor GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const; FSlateColor GetItemTypeColor(FHierarchicalTreeItem::EItemType Type) const;
const FSlateBrush* GetItemIcon(FHierarchicalTreeItem::EItemType Type) const; const FSlateBrush* GetItemIcon(FHierarchicalTreeItem::EItemType Type) const;
private: private:
// Données
UDTFluxModelAsset* ModelAsset = nullptr; UDTFluxModelAsset* ModelAsset = nullptr;
TArray<FHierarchicalTreeItemPtr> RootItems; // Contests racines avec hiérarchie TArray<FHierarchicalTreeItemPtr> RootItems;
TArray<FHierarchicalTreeItemPtr> ParticipantItems; // Participants séparés TArray<FHierarchicalTreeItemPtr> ParticipantItems;
// Widgets - TreeView simple
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ContestTreeView; TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ContestTreeView;
TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ParticipantTreeView; TSharedPtr<STreeView<FHierarchicalTreeItemPtr>> ParticipantTreeView;
TSharedPtr<STextBlock> StatsText; TSharedPtr<STextBlock> StatsText;
TSharedPtr<STextBlock> SelectionText; TSharedPtr<STextBlock> SelectionText;
// Méthodes de construction
void BuildContestHierarchy(); void BuildContestHierarchy();
void BuildParticipantList(); void BuildParticipantList();
// Callbacks TreeView
TSharedRef<ITableRow> OnGenerateRowForTree(FHierarchicalTreeItemPtr Item, TSharedRef<ITableRow> OnGenerateRowForTree(FHierarchicalTreeItemPtr Item,
const TSharedRef<STableViewBase>& OwnerTable); const TSharedRef<STableViewBase>& OwnerTable);
void OnGetChildrenForTree(FHierarchicalTreeItemPtr Item, TArray<FHierarchicalTreeItemPtr>& OutChildren); void OnGetChildrenForTree(FHierarchicalTreeItemPtr Item, TArray<FHierarchicalTreeItemPtr>& OutChildren);
void OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem, ESelectInfo::Type SelectInfo); void OnTreeSelectionChanged(FHierarchicalTreeItemPtr SelectedItem, ESelectInfo::Type SelectInfo);
void OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded); void OnSetExpansionRecursive(FHierarchicalTreeItemPtr Item, bool bIsExpanded);
// Callbacks des boutons
FReply OnRefreshClicked(); FReply OnRefreshClicked();
FReply OnExpandAllClicked(); FReply OnExpandAllClicked();
FReply OnCollapseAllClicked(); FReply OnCollapseAllClicked();
EActiveTimerReturnType ForceInitialLayout(double InCurrentTime, float InDeltaTime); EActiveTimerReturnType ForceInitialLayout(double InCurrentTime, float InDeltaTime);
// Utilitaires
FText GetStatsText() const; FText GetStatsText() const;
}; };

View File

@ -13,6 +13,11 @@ 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());
}
} }
bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest) bool UDTFluxModelAsset::GetContestById(const int InContestId, FDTFluxContest& OutContest)
@ -132,6 +137,32 @@ bool UDTFluxModelAsset::GetParticipantByBib(int Bib, FDTFluxParticipant& OutPart
return false; 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;
}
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)
{ {

View File

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

View File

@ -3,6 +3,12 @@
#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,

View File

@ -1,93 +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 "DTFluxCoreModule.h"
#include "Dom/JsonObject.h"
// ===================================
// FDTFluxPerson Implementation
// ===================================
void FDTFluxParticipant::AddTeammate(const FDTFluxPerson& Person) bool FDTFluxPerson::operator==(const FDTFluxPerson& Right) const
{ {
Teammate.Add(Person); return GetNormalizedString() == Right.GetNormalizedString();
} }
void FDTFluxParticipant::AddTeammate(const FString LastName, const FString FirstName, const FString Gender) bool FDTFluxPerson::operator!=(const FDTFluxPerson& Right) const
{ {
return !(*this == Right);
} }
FString FDTFluxParticipant::GetFormattedName(const int MaxChar, const FString OverflowChars) const bool FDTFluxPerson::operator==(const int Length) const
{ {
{ return GetNormalizedString().Len() == Length;
if (MaxChar <= 0)
{
return "";
}
FString FirstName;
FString LastName;
if (IsTeam())
{
LastName = Team;
}
else
{
FirstName = Teammate[0].FirstName;
LastName = Teammate[0].LastName;
}
FString Initial;
if (!FirstName.IsEmpty())
{
Initial = FirstName.Left(1).ToUpper() + " ";
}
FString FormattedLastName = LastName.ToUpper();
FString FullName = Initial + FormattedLastName;
UE_LOG(logDTFluxCore, Error, TEXT("FullName for Bib %i is %s"), Bib, *FullName);
if (FullName.Len() <= MaxChar)
{
return FullName;
}
const int32 OverflowLength = OverflowChars.Len();
if (OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
if (Initial.Len() + OverflowLength > MaxChar)
{
return FullName.Left(MaxChar);
}
const int32 AvailableForLastName = MaxChar - Initial.Len() - OverflowLength;
if (AvailableForLastName <= 0)
{
return FullName.Left(MaxChar);
}
FString TruncatedName = Initial + FormattedLastName.Left(AvailableForLastName) + OverflowChars;
if (TruncatedName.Len() > MaxChar)
{
return TruncatedName.Left(MaxChar);
}
return TruncatedName;
}
} }
FString FDTFluxParticipant::GetConcatFormattedName(const int MaxChar, const FString OverflowChar) const bool FDTFluxPerson::operator!=(const int Length) const
{ {
FString BibText = FString::FromInt(Bib) + " "; return !(*this == Length);
FString FormattedName = GetFormattedName(MaxChar - BibText.Len(), OverflowChar); }
return BibText + FormattedName;
FString FDTFluxPerson::GetNormalizedString() const
{
return FirstName.ToLower() + LastName.ToLower() + Gender.ToLower();
}
bool FDTFluxPerson::IsValid() const
{
return !FirstName.TrimStartAndEnd().IsEmpty() &&
!LastName.TrimStartAndEnd().IsEmpty() &&
!Gender.TrimStartAndEnd().IsEmpty();
}
FDTFluxParticipant::FDTFluxParticipant()
: Bib(-1)
, ContestId(-1)
, Elite(false)
, Status(static_cast<EDTFluxParticipantStatusType>(0))
, bIsMassStartParticipant(false)
, CurrentSplit(-1)
{
Teammate.Reset();
} }
// Constructeur privé depuis JSON
FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject) FDTFluxParticipant::FDTFluxParticipant(const TSharedPtr<FJsonObject>& JsonObject)
: Bib(JsonObject->GetIntegerField(TEXT("bib"))) : Bib(JsonObject->GetIntegerField(TEXT("bib")))
, ContestId(JsonObject->GetIntegerField(TEXT("contestId"))) , ContestId(JsonObject->GetIntegerField(TEXT("contestId")))
@ -96,43 +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) , CurrentSplit(-1)
{ {
UE_LOG(logDTFluxCore, Error, TEXT("Ctor with JSON Object")) UE_LOG(logDTFluxCore, Log, TEXT("Creating participant from JSON - Bib: %d, Contest: %d"), Bib, ContestId);
for (uint8 Index = 1; ; Index++)
for (uint8 Index = 1; Index <= 10; Index++)
{ {
FString FirstNameKey = Index == 1 ? "firstName" : FString::Printf(TEXT("firstName%i"), Index); FString FirstNameKey = Index == 1 ? TEXT("firstName") : FString::Printf(TEXT("firstName%d"), Index);
FString LastNameKey = Index == 1 ? "lastName" : FString::Printf(TEXT("lastName%i"), Index); FString LastNameKey = Index == 1 ? TEXT("lastName") : FString::Printf(TEXT("lastName%d"), Index);
FString GenderKey = Index == 1 ? "gender" : FString::Printf(TEXT("gender%i"), Index); FString GenderKey = Index == 1 ? TEXT("gender") : FString::Printf(TEXT("gender%d"), Index);
// max 10 Persons
if (Index >= 10) // Vérifie si au moins un des champs existe
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey) && !JsonObject->
HasField(GenderKey))
{ {
break; break;
} }
if (!JsonObject->HasField(FirstNameKey) && !JsonObject->HasField(LastNameKey)
&& !JsonObject->HasField(GenderKey))
{
UE_LOG(logDTFluxCore, Error, TEXT("No Corresponding Field!!!"))
break;
}
const FString FirstName = JsonObject->GetStringField(FirstNameKey); const FString FirstName = JsonObject->GetStringField(FirstNameKey);
const FString LastName = JsonObject->GetStringField(LastNameKey); const FString LastName = JsonObject->GetStringField(LastNameKey);
const FString Gender = JsonObject->GetStringField(GenderKey); const FString Gender = JsonObject->GetStringField(GenderKey);
if (FirstName.IsEmpty() && LastName.IsEmpty())
if (FirstName.TrimStartAndEnd().IsEmpty() && LastName.TrimStartAndEnd().IsEmpty())
{
continue; continue;
FDTFluxPerson Person;
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());
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
@ -142,5 +150,134 @@ int FDTFluxParticipant::GetTeammateNum() const
bool FDTFluxParticipant::IsTeam() const bool FDTFluxParticipant::IsTeam() const
{ {
return Teammate.Num() < 1; 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))
{
} }

View File

@ -35,13 +35,13 @@ 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;
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|ModelAsset")
@ -89,4 +89,13 @@ public:
UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant") UFUNCTION(BlueprintCallable, CallInEditor, Category="DTFlux|Participant")
bool GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant); bool GetParticipantByBib(int Bib, FDTFluxParticipant& OutParticipant);
UFUNCTION()
bool IsStageFinished(FDTFluxStageKey StageKey);
private:
UPROPERTY()
TMap<FDTFluxStageKey, bool /*bIsFinished*/> FinishedStagesCache;
UFUNCTION()
bool CheckStageIsFinished(FDTFluxStageKey StageKey);
}; };

View File

@ -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,6 +51,7 @@ 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);
@ -67,8 +67,9 @@ enum class 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);
@ -80,7 +81,7 @@ enum class EDTFluxSortingRankingType: uint8
Bib = 0b00000010 UMETA(DisplayName="Bib"), Bib = 0b00000010 UMETA(DisplayName="Bib"),
TimeSwim = 0b00000100 UMETA(DisplayName="Swimming Time"), TimeSwim = 0b00000100 UMETA(DisplayName="Swimming Time"),
TimeTransition = 0b00001000 UMETA(DisplayName="Transition Time"), TimeTransition = 0b00001000 UMETA(DisplayName="Transition Time"),
TimeRun = TimeSwim|TimeTransition UMETA(DisplayName="Running Time"), TimeRun = TimeSwim | TimeTransition UMETA(DisplayName="Running Time"),
StartTime = 0b00001110 UMETA(DisplayName="StartTime"), StartTime = 0b00001110 UMETA(DisplayName="StartTime"),
Gap = 0b00010000 UMETA(DisplayName="StartTime"), Gap = 0b00010000 UMETA(DisplayName="StartTime"),
SwimSpeed = 0b00100000 UMETA(DisplayName="StartTime"), SwimSpeed = 0b00100000 UMETA(DisplayName="StartTime"),

View File

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

View File

@ -23,12 +23,7 @@ public:
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);
}; };
/** /**
@ -51,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;
}; };
/** /**
@ -89,68 +85,6 @@ public:
bool GetStage(const int StageID, FDTFluxStage& OutStage) const; bool GetStage(const int StageID, FDTFluxStage& OutStage) const;
}; };
inline bool FDTFluxContest::IsFinished() const
{
return EndTime <= FDateTime::Now();
}
inline void FDTFluxContest::UpdateEndTime()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.EndTime < B.EndTime;
});
EndTime = TempStages.Last().EndTime;
}
inline int FDTFluxContest::GetLastStageId()
{
if (LastStageId <= 0)
{
UpdateLastStageId();
}
return LastStageId;
}
inline void FDTFluxContest::UpdateLastStageId()
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
LastStageId = TempStages.Last().StageId;
}
inline FDTFluxStage& FDTFluxContest::GetLastStage() const
{
TArray<FDTFluxStage> TempStages = Stages;
TempStages.Sort([](const FDTFluxStage& A, const FDTFluxStage& B)
{
return A.StageId < B.StageId;
});
return TempStages.Last();
}
inline bool FDTFluxContest::GetStage(const int StageID, FDTFluxStage& OutStage) const
{
if (Stages.Num() == 0)
{
return false;
}
for (const FDTFluxStage& Stage : Stages)
{
if (Stage.StageId == StageID)
{
OutStage = Stage;
return true;
}
}
return false;
}
USTRUCT() USTRUCT()
struct DTFLUXCORE_API FDTFluxRaceData struct DTFLUXCORE_API FDTFluxRaceData
{ {

View File

@ -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
@ -37,7 +49,7 @@ public:
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDTFluxContestRankings struct FDTFluxContestRankings : public FDTFluxBaseRankings
{ {
GENERATED_BODY() GENERATED_BODY()
@ -57,7 +69,7 @@ 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")
@ -94,7 +106,7 @@ public:
}; };
USTRUCT(BlueprintType) USTRUCT(BlueprintType)
struct FDTFluxDetailedRankings struct FDTFluxDetailedRankings : public FDTFluxBaseRankings
{ {
GENERATED_BODY() GENERATED_BODY()
@ -125,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()
}; };
@ -165,6 +177,7 @@ struct FDTFluxStageRankings : public FDTFluxDetailedRankings
Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true); Ranking.TimeStart.ParseIntoArray(Exploded, TEXT(":"), true);
if (Exploded.Num() == 3) if (Exploded.Num() == 3)
{ {
//TODO: Pas sur que ce soit super de le mettre à ce jour ???
FDateTime Now = FDateTime::Now(); FDateTime Now = FDateTime::Now();
RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(), RankingStartTime = FDateTime(Now.GetYear(), Now.GetMonth(), Now.GetDay(),
FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]), FCString::Atoi(*Exploded[0]), FCString::Atoi(*Exploded[1]),

View File

@ -31,7 +31,5 @@ public:
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
FString Gap = "-"; FString Gap = "-";
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
int Rank; int Rank = -1;
}; };

View File

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

View File

@ -10,6 +10,7 @@
#include "FileHelpers.h" #include "FileHelpers.h"
#include "Assets/DTFluxModelAsset.h" #include "Assets/DTFluxModelAsset.h"
#include "Subsystems/DTFluxNetworkSubsystem.h" #include "Subsystems/DTFluxNetworkSubsystem.h"
#include "Struct/DTFluxServerResponseStruct.h"
#include "UObject/SavePackage.h" #include "UObject/SavePackage.h"
void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection) void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
@ -32,7 +33,7 @@ void UDTFluxCoreSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{ {
RegisterDelegates(); RegisterDelegates();
} }
PursuitManager = NewObject<UDTFluxPursuitManager>(); PursuitManager = NewObject<UDTFluxPursuitManager>(this);
} }
void UDTFluxCoreSubsystem::Deinitialize() void UDTFluxCoreSubsystem::Deinitialize()
@ -52,6 +53,89 @@ void UDTFluxCoreSubsystem::SaveDataStorage()
} }
} }
void UDTFluxCoreSubsystem::ProcessTrackedResponse(FDTFluxServerResponse& InResponse)
{
switch (InResponse.GetResponseType())
{
case EDTFluxApiDataType::ContestRanking:
{
FDTFluxContestRankings Rankings;
if (InResponse.ParseContestRanking(Rankings))
{
OnContestRankings.Broadcast(Rankings.ContestId, Rankings);
ProcessContestRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Contest %s"),
*Rankings.ContestName);
}
else
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse ContestRanking"));
}
break;
}
case EDTFluxApiDataType::StageRanking:
{
FDTFluxStageRankings Rankings;
if (InResponse.ParseStageRanking(Rankings))
{
FDTFluxStageKey StageKey(Rankings.ContestId, Rankings.StageId);
OnStageRankings.Broadcast(StageKey, Rankings);
ProcessStageRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRanking added for Stage %i of Contest %i"),
Rankings.StageId, Rankings.ContestId);
}
else
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse StageRanking"));
}
break;
}
case EDTFluxApiDataType::SplitRanking:
{
FDTFluxSplitRankings Rankings;
if (InResponse.ParseSplitRanking(Rankings))
{
FDTFluxSplitKey SplitKey(Rankings.ContestId, Rankings.StageId, Rankings.SplitId);
OnSplitRankings.Broadcast(SplitKey, Rankings);
ProcessSplitRanking(Rankings);
UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("ContestRanking added for Split %i of Stage %i of Contest %i"),
Rankings.SplitId, Rankings.StageId, Rankings.ContestId);
}
else
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to parse SplitRanking"));
}
break;
}
case EDTFluxApiDataType::RaceData:
{
FDTFluxRaceData RaceData;
if (InResponse.ParseRaceData(RaceData))
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("RaceDataDefinition added for Contest %s"),
*RaceData.Datas[0].Name);
ProcessRaceData(RaceData);
}
break;
}
case EDTFluxApiDataType::TeamList:
{
FDTFluxTeamListDefinition TeamList;
if (InResponse.ParseTeamList(TeamList))
{
ProcessTeamList(TeamList);
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Process TeamList"))
}
break;
}
default:
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unknown DataType %s"),
*UEnum::GetValueAsString(InResponse.GetResponseType()));
break;
}
}
void UDTFluxCoreSubsystem::RegisterDelegates() void UDTFluxCoreSubsystem::RegisterDelegates()
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
@ -84,9 +168,6 @@ void UDTFluxCoreSubsystem::RegisterDelegates()
&UDTFluxCoreSubsystem::ProcessSplitRanking &UDTFluxCoreSubsystem::ProcessSplitRanking
); );
// ⚠️ ATTENTION : Vous avez un doublon ici !
// NetworkSubsystem->OnReceivedTeamUpdate().BindUFunction(this, "ProcessTeamList");
NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUObject( NetworkSubsystem->OnReceivedTeamStatusUpdate().BindUObject(
this, this,
&UDTFluxCoreSubsystem::ProcessTeamStatusUpdate &UDTFluxCoreSubsystem::ProcessTeamStatusUpdate
@ -104,6 +185,46 @@ void UDTFluxCoreSubsystem::RegisterDelegates()
} }
} }
bool UDTFluxCoreSubsystem::IsStageRankingSealed(FDTFluxStageKey StageKey)
{
FDTFluxStageRankings StageRankings;
if (GetStageRankingsWithKey(StageKey, StageRankings))
{
FDTFluxStage Stage;
if (GetStageDefinition(StageKey, Stage))
{
return StageRankings.IsSealed(Stage.EndTime);
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to find Stage %i"), StageKey.StageId);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to find StageRankings for StageKey %i"), StageKey.StageId);
return false;
}
bool UDTFluxCoreSubsystem::IsContestRankingSealed(int ContestId)
{
if (DataStorage)
{
FDTFluxContestRankings ContestRankings;
if (GetContestRankings(ContestId, ContestRankings))
{
FDTFluxContest Contest;
if (GetContestForId(ContestId, Contest))
{
return ContestRankings.IsSealed(Contest.EndTime);
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to find Contest %i"), ContestId);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Unable to find ContestRankings for ContestId %i"), ContestId);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
}
void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition) void UDTFluxCoreSubsystem::ProcessRaceData(const FDTFluxRaceData& RaceDataDefinition)
{ {
if (RaceDataDefinition.Datas.Num() > 0) if (RaceDataDefinition.Datas.Num() > 0)
@ -151,21 +272,24 @@ void UDTFluxCoreSubsystem::ProcessContestRanking(const FDTFluxContestRankings& C
DataStorage->AddContestRanking(NewContestRankings); DataStorage->AddContestRanking(NewContestRankings);
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"), UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestRankings added for Contest %s"),
*NewContestRankings.ContestName); *NewContestRankings.ContestName);
if (bShouldKeepRankings)
{
SaveDataStorage(); SaveDataStorage();
}
} }
void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& StageRankings) void UDTFluxCoreSubsystem::ProcessStageRanking(const FDTFluxStageRankings& StageRankings)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received StageRankings with %i Items"), StageRankings.Rankings.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received StageRankings with %i Items"), StageRankings.Rankings.Num());
DataStorage->UpdateOrCreateStageRanking(StageRankings); DataStorage->UpdateOrCreateStageRanking(StageRankings);
SaveDataStorage(); if (bShouldKeepRankings) { SaveDataStorage(); }
} }
void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings) void UDTFluxCoreSubsystem::ProcessSplitRanking(const FDTFluxSplitRankings& SplitRankings)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num()); UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Received SplitRanking with %i Items"), SplitRankings.Rankings.Num());
DataStorage->UpdateOrCreateSplitRanking(SplitRankings); DataStorage->UpdateOrCreateSplitRanking(SplitRankings);
SaveDataStorage(); if (bShouldKeepRankings) { SaveDataStorage(); }
} }
void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus) void UDTFluxCoreSubsystem::ProcessTeamStatusUpdate(const FDTFluxTeamStatusUpdate& NewParticipantStatus)
@ -196,7 +320,6 @@ void UDTFluxCoreSubsystem::ProcessSplitSensor(const FDTFluxSplitSensorInfo& Spli
SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName()); SplitSensorInfo.SplitId, SplitSensorInfo.Bib, *Participant.GetFormattedName());
} }
void UDTFluxCoreSubsystem::SendRequest(const FString& Message) void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
@ -205,78 +328,396 @@ void UDTFluxCoreSubsystem::SendRequest(const FString& Message)
} }
} }
void UDTFluxCoreSubsystem::SendTeamListRequest() FGuid UDTFluxCoreSubsystem::InitContestRankingsDisplay(const int ContestId)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
NetworkSubsystem->SendRequest(EDTFluxRequestType::TeamList); if (DataStorage)
{
// no need to request StageRankings;
if (IsContestRankingSealed(ContestId))
{
const FGuid DisplayRequestId = FGuid::NewGuid();
OnContestRankingDisplayReady.Broadcast(DisplayRequestId, true);
return DisplayRequestId;
} }
else
{
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
FDTFluxContestRankings Rankings = FDTFluxContestRankings();
if (Request.ParsedResponse.IsSet())
{
TSharedPtr<FDTFluxServerResponse> ResponsePtr = Request.ParsedResponse.GetValue();
ResponsePtr->ParseContestRanking(Rankings);
this->DataStorage->AddContestRanking(Rankings);
this->OnContestRankingDisplayReady.Broadcast(Request.RequestId, true);
return;
}
this->OnStageRankingDisplayReady.Broadcast(Request.RequestId, false);
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
this->OnStageRankingDisplayReady.Broadcast(InReq.RequestId, false);
});
FGuid DisplayRequestId = NetworkSubsystem->SendTrackedRequestWithCallbacks(
EDTFluxApiDataType::ContestRanking, ContestId, -1, -1, OnSuccess, OnError, true);
return DisplayRequestId;
}
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDatastorage unavailable ..."));
OnContestRankingDisplayReady.Broadcast(FGuid(), false);
return FGuid();
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxNetworkSubsystem unavailable ..."));
OnContestRankingDisplayReady.Broadcast(FGuid(), false);
return FGuid();
} }
void UDTFluxCoreSubsystem::SendRaceDataRequest() FGuid UDTFluxCoreSubsystem::InitStageRankingsDisplay(const int ContestId, const int StageId)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
NetworkSubsystem->SendRequest(EDTFluxRequestType::RaceData); if (DataStorage)
{
// no need to request StageRankings;
if (IsStageRankingSealed(FDTFluxStageKey(ContestId, StageId)))
{
const FGuid DisplayRequestId = FGuid::NewGuid();
OnStageRankingDisplayReady.Broadcast(DisplayRequestId, true);
return DisplayRequestId;
} }
else
{
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
FDTFluxStageRankings Rankings = FDTFluxStageRankings();
if (Request.ParsedResponse.IsSet())
{
TSharedPtr<FDTFluxServerResponse> ResponsePtr = Request.ParsedResponse.GetValue();
ResponsePtr->ParseStageRanking(Rankings);
this->DataStorage->AddStageRanking(Rankings);
this->OnStageRankingDisplayReady.Broadcast(Request.RequestId, true);
return;
}
this->OnStageRankingDisplayReady.Broadcast(Request.RequestId, false);
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
this->OnStageRankingDisplayReady.Broadcast(InReq.RequestId, false);
});
FGuid DisplayRequestId = NetworkSubsystem->SendTrackedRequestWithCallbacks(
EDTFluxApiDataType::StageRanking, ContestId, StageId, -1, OnSuccess, OnError, true);
return DisplayRequestId;
}
}
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxNetworkSubsystem unavailable ..."));
const FGuid RequestId = FGuid::NewGuid();
OnStageRankingDisplayReady.Broadcast(RequestId, false);
return RequestId;
} }
void UDTFluxCoreSubsystem::SendContestRankingRequest(int InContestId) FGuid UDTFluxCoreSubsystem::InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId)
{ {
if (NetworkSubsystem) if (NetworkSubsystem)
{ {
NetworkSubsystem->SendRequest(EDTFluxRequestType::ContestRanking, InContestId); if (DataStorage)
{
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
FDTFluxSplitRankings Rankings = FDTFluxSplitRankings();
if (Request.ParsedResponse.IsSet())
{
TSharedPtr<FDTFluxServerResponse> ResponsePtr = Request.ParsedResponse.GetValue();
ResponsePtr->ParseSplitRanking(Rankings);
this->DataStorage->AddSplitRanking(Rankings);
this->OnSplitRankingDisplayReady.Broadcast(Request.RequestId, true);
return;
} }
this->OnSplitRankingDisplayReady.Broadcast(Request.RequestId, false);
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
this->OnSplitRankingDisplayReady.Broadcast(InReq.RequestId, false);
});
FGuid DisplayRequestId = NetworkSubsystem->SendTrackedRequestWithCallbacks(
EDTFluxApiDataType::SplitRanking, ContestId, StageId, SplitId, OnSuccess, OnError, true);
return DisplayRequestId;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxDatastorage unavailable ..."));
OnSplitRankingDisplayReady.Broadcast(FGuid(), false);
return FGuid();
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DTFluxNetworkSubsystem unavailable ..."));
OnSplitRankingDisplayReady.Broadcast(FGuid(), false);
return FGuid();
} }
void UDTFluxCoreSubsystem::SendStageRankingRequest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking) bool UDTFluxCoreSubsystem::GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
{ FDTFluxStageRanking& OutStageRanking)
// TODO Implement this
}
void UDTFluxCoreSubsystem::RequestAllStageRankingOfContest(int InContestId, int InStageId,
bool bShouldIncludeSplitRanking)
{
// TODO Implement this
}
void UDTFluxCoreSubsystem::SendSplitRankingRequest(int InContestId, int InStageId, int InSplitId)
{
// TODO Implement this
}
void UDTFluxCoreSubsystem::RequestAllSplitRankingOfContest(int InContestId, int InStageId)
{
// TODO Implement this
}
FDTFluxStageRankings UDTFluxCoreSubsystem::GetStageRankings(FDTFluxStageKey StageKey)
{ {
if (DataStorage)
{
FDTFluxStageKey StageKey(ContestId, StageId);
if (DataStorage->StageRankings.Contains(StageKey)) if (DataStorage->StageRankings.Contains(StageKey))
{ {
return DataStorage->StageRankings[StageKey]; FDTFluxStageRankings StageRankings = DataStorage->StageRankings[StageKey];
for (auto& Ranking : StageRankings.Rankings)
{
if (Ranking.Bib == Bib)
{
OutStageRanking = static_cast<FDTFluxStageRanking>(Ranking);
return true;
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Cannot find StageRankings for key [%s]"), *StageKey.GetDisplayName()); }
return FDTFluxStageRankings(); }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to find StageRanking for Bib %i"), Bib);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
} }
void UDTFluxCoreSubsystem::RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId) bool UDTFluxCoreSubsystem::GetSplitRankingForBib(const int ContestId, const int StageId, const int SplitId,
const int Bib, FDTFluxSplitRanking& OutSplitRankings)
{ {
// TODO Implement this if (DataStorage)
{
FDTFluxSplitKey SplitKey(ContestId, StageId, SplitId);
if (DataStorage->SplitRankings.Contains(SplitKey))
{
FDTFluxSplitRankings SplitRankings = DataStorage->SplitRankings[SplitKey];
for (auto& Ranking : SplitRankings.Rankings)
{
if (Ranking.Bib == Bib)
{
OutSplitRankings = static_cast<FDTFluxSplitRanking>(Ranking);
return true;
}
}
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to find SplitRanking for Bib %i"), Bib);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
} }
const FDTFluxParticipant UDTFluxCoreSubsystem::GetParticipant(int InBib) bool UDTFluxCoreSubsystem::GetContestRanking(const int ContestId, FDTFluxContestRanking& OutContestRanking)
{
if (DataStorage)
{
FDTFluxContest Contest;
if (GetContestForId(ContestId, Contest))
{
for (auto& Ranking : DataStorage->ContestRankings[ContestId].Rankings)
{
OutContestRanking = Ranking;
return true;
}
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Unable to find ContestRanking for ContestId %i"), ContestId);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
}
bool UDTFluxCoreSubsystem::GetContestRankings(const int ContestId,
FDTFluxContestRankings& OutContestRankings)
{
if (DataStorage->ContestRankings.Contains(ContestId))
{
OutContestRankings = DataStorage->ContestRankings[ContestId];
return true;
}
if (NetworkSubsystem)
{
TArray<int> TackedContestIds = {ContestId};
TrackedRequestContestRankings(TackedContestIds);
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"));
return false;
}
bool UDTFluxCoreSubsystem::GetStageRankings(const int ContestId, const int StageId,
FDTFluxStageRankings& OutStageRankings)
{
return GetStageRankingsWithKey(FDTFluxStageKey(ContestId, StageId), OutStageRankings);
}
bool UDTFluxCoreSubsystem::GetSplitRankings(const int ContestId, const int StageId, const int SplitId,
FDTFluxSplitRankings& OutSplitRankings)
{
return GetSplitRankingsWithKey(FDTFluxSplitKey(ContestId, StageId, SplitId), OutSplitRankings);
}
bool UDTFluxCoreSubsystem::GetStageRankingsWithKey(const FDTFluxStageKey StageKey,
FDTFluxStageRankings& OutStageRankings, const bool bShouldUseCached)
{
//We Have the data
if (DataStorage->StageRankings.Contains(StageKey) && bShouldUseCached)
{
OutStageRankings = DataStorage->StageRankings[StageKey];
return true;
}
else
{
if (NetworkSubsystem)
{
TArray<FDTFluxStageKey> TackedStageKeys = {StageKey};
TrackedRequestStageRankings(TackedStageKeys);
OutStageRankings = FDTFluxStageRankings();
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"))
}
return false;
}
bool UDTFluxCoreSubsystem::GetSplitRankingsWithKey(const FDTFluxSplitKey SplitKey,
FDTFluxSplitRankings& OutSplitRankings, const bool bShouldUseCached)
{
//We Have the data
if (DataStorage->SplitRankings.Contains(SplitKey) && bShouldUseCached)
{
OutSplitRankings = DataStorage->SplitRankings[SplitKey];
return true;
}
else
{
if (NetworkSubsystem)
{
TArray<FDTFluxSplitKey> TackedSplitKey = {SplitKey};
TrackedRequestSplitRankings(TackedSplitKey);
OutSplitRankings = FDTFluxSplitRankings();
return false;
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("NetworkSubsystem unavailable"))
return false;
}
}
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestContestRankings(const TArray<int> ForContests, bool bEnableCache)
{
if (NetworkSubsystem)
{
TArray<FGuid> RequestIds;
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("ContestRanking Request %s %s Success"),
*Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType));
if (Request.ParsedResponse.IsSet())
{
ProcessTrackedResponse(*Request.ParsedResponse.GetValue());
}
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("ContestRanking Request [%s] Error %s"),
*InReq.RequestId.ToString(), *InError);
});
// if Contest is not ended
for (auto ContestId : ForContests)
{
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::ContestRanking,
ContestId, -1, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest);
}
return RequestIds;
}
return TArray<FGuid>();
}
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestStageRankings(const TArray<FDTFluxStageKey> ForStages,
bool bEnableCache)
{
if (NetworkSubsystem)
{
TArray<FGuid> RequestIds;
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Stage Request %s %s Success"),
*Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType));
if (Request.ParsedResponse.IsSet())
{
ProcessTrackedResponse(*Request.ParsedResponse.GetValue());
}
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"),
*InReq.RequestId.ToString(), *InError);
});
for (auto StageKey : ForStages)
{
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::StageRanking,
StageKey.ContestId, StageKey.StageId, -1, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest);
}
return RequestIds;
}
return TArray<FGuid>();
}
TArray<FGuid> UDTFluxCoreSubsystem::TrackedRequestSplitRankings(const TArray<FDTFluxSplitKey> ForSplits,
bool bEnableCache)
{
if (NetworkSubsystem)
{
TArray<FGuid> RequestIds;
FOnDTFluxRequestSuccess OnSuccess = FOnDTFluxRequestSuccess::CreateLambda(
[this](const FDTFluxTrackedRequest& Request)
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Stage Request %s %s Success"),
*Request.RequestId.ToString(), *UEnum::GetValueAsString(Request.RequestType));
if (Request.ParsedResponse.IsSet())
{
ProcessTrackedResponse(*Request.ParsedResponse.GetValue());
}
});
FOnDTFluxRequestError OnError = FOnDTFluxRequestError::CreateLambda(
[this](const FDTFluxTrackedRequest& InReq, const FString& InError)
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("StageRanking Request [%s] Error %s"),
*InReq.RequestId.ToString(), *InError);
});
// if Contest is not ended
for (auto SplitKey : ForSplits)
{
FGuid ContestRequest = NetworkSubsystem->SendTrackedRequestWithCallbacks(EDTFluxApiDataType::SplitRanking,
SplitKey.ContestId, SplitKey.StageId, SplitKey.SplitId, OnSuccess, OnError, bEnableCache);
RequestIds.Add(ContestRequest);
}
return RequestIds;
}
return TArray<FGuid>();
}
bool UDTFluxCoreSubsystem::GetParticipant(int InBib, FDTFluxParticipant& OutParticipant)
{ {
if (DataStorage->Participants.Contains(InBib)) if (DataStorage->Participants.Contains(InBib))
{ {
return DataStorage->Participants[InBib]; OutParticipant = DataStorage->Participants[InBib];
return true;
} }
return FDTFluxParticipant();
}
void UDTFluxCoreSubsystem::RefreshStorage() return false;
{
// TODO Implement this
} }
TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId() TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId()
@ -284,24 +725,30 @@ TArray<int> UDTFluxCoreSubsystem::GetCurrentContestsId()
return GetContestsIdForTime(FDateTime::Now()); return GetContestsIdForTime(FDateTime::Now());
} }
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetCurrentContests() bool UDTFluxCoreSubsystem::GetCurrentContests(TArray<FDTFluxContest>& OutContests)
{ {
return GetContestsForTime(FDateTime::Now()); return GetContestsForTime(FDateTime::Now(), OutContests);
} }
TArray<int> UDTFluxCoreSubsystem::GetContestsIdForTime(const FDateTime Time) TArray<int> UDTFluxCoreSubsystem::GetContestsIdForTime(const FDateTime Time)
{ {
TArray<int> Contests; if (DataStorage)
for (const auto& Pair : DataStorage->Contests)
{ {
FDTFluxContest Contest = Pair.Value; TArray<FDTFluxContest> Contests;
int ContestId = Contest.ContestId; if (GetContestsForTime(Time, Contests))
if (Contest.Date < Time && Contest.EndTime > Time)
{ {
Contests.Add(ContestId); TArray<int> ContestIds = TArray<int>();
for (const auto& Contest : Contests)
{
ContestIds.Add(Contest.ContestId);
} }
return ContestIds;
} }
return Contests; UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No Contest running for Time [%s]"), *Time.ToString());
return TArray<int>();
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return TArray<int>();
} }
bool UDTFluxCoreSubsystem::GetContestForId(const int Id, FDTFluxContest& OutContest) bool UDTFluxCoreSubsystem::GetContestForId(const int Id, FDTFluxContest& OutContest)
@ -318,51 +765,119 @@ bool UDTFluxCoreSubsystem::GetContestForId(const int Id, FDTFluxContest& OutCont
return false; return false;
} }
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContestsForTime(const FDateTime Time) bool UDTFluxCoreSubsystem::GetContestsForTime(const FDateTime Time, TArray<FDTFluxContest>& OutContests)
{ {
TArray<FDTFluxContest> Contests; if (DataStorage)
{
OutContests.Empty();
for (const auto& Pair : DataStorage->Contests) for (const auto& Pair : DataStorage->Contests)
{ {
FDTFluxContest Contest = Pair.Value; FDTFluxContest Contest = Pair.Value;
int ContestId = Contest.ContestId; int ContestId = Contest.ContestId;
//ils ont commencé.
if (Contest.Date < Time && Contest.EndTime > Time) if (Contest.Date < Time && Contest.EndTime > Time)
{ {
Contests.Add(Contest); // ils sont finis
if (!Contest.IsFinished())
{
OutContests.Add(Contest);
} }
} }
return Contests; }
if (!OutContests.IsEmpty())
{
return true;
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("No Contest running for Time [%s]"), *Time.ToString());
}
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
} }
void UDTFluxCoreSubsystem::RequestRankingsForStages(TArray<FDTFluxStage> RequestedStages) const bool UDTFluxCoreSubsystem::GetContests(TArray<FDTFluxContest>& OutContests)
{
}
TArray<FDTFluxContest> UDTFluxCoreSubsystem::GetContests()
{ {
OutContests.Empty();
if (DataStorage) if (DataStorage)
{ {
TArray<FDTFluxContest> OutContests;
DataStorage->Contests.GenerateValueArray(OutContests); DataStorage->Contests.GenerateValueArray(OutContests);
return OutContests; return !OutContests.IsEmpty();
} }
return TArray<FDTFluxContest>(); UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("DataStorage not available"));
return false;
} }
void UDTFluxCoreSubsystem::LaunchPursuitSequenceFor(const TArray<int> ContestIds) void UDTFluxCoreSubsystem::GetContest(const int ContestId, FDTFluxContest& OutContest)
{ {
TArray<FDTFluxContest> Contests = TArray<FDTFluxContest>(); OutContest = FDTFluxContest();
for (const auto& ContestId : ContestIds) if (GetContestForId(ContestId, OutContest))
{ {
FDTFluxContest Contest; return;
GetContestForId(ContestId, Contest);
Contests.Add(Contest);
if (PursuitManager)
{
PursuitManager->LaunchPursuitSequenceFor(Contests);
}
else
{
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("PursuitManager is null"));
}
} }
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestId %d not found in ContestDefinition"), ContestId)
}
bool UDTFluxCoreSubsystem::GetStageDefinition(const FDTFluxStageKey StageKey, FDTFluxStage& OutStageDefinition)
{
int ContestId = StageKey.ContestId;
int StageId = StageKey.StageId;
FDTFluxContest ContestDefinition;
if (GetContestForId(ContestId, ContestDefinition))
{
for (auto& Stage : ContestDefinition.Stages)
{
if (Stage.StageId == StageId)
{
OutStageDefinition = Stage;
return true;
}
}
}
OutStageDefinition = FDTFluxStage();
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestId %d, StageId %d not found in ContestDefinition"),
ContestId, StageId)
return false;
}
bool UDTFluxCoreSubsystem::GetSplitDefinition(const FDTFluxSplitKey SplitKey, FDTFluxSplit& OutSplitDefinition)
{
int ContestId = SplitKey.ContestId;
int SplitId = SplitKey.SplitId;
FDTFluxContest ContestDefinition;
if (GetContestForId(ContestId, ContestDefinition))
{
for (auto& Split : ContestDefinition.Splits)
{
if (Split.SplitId == SplitId)
{
OutSplitDefinition = Split;
return true;
}
}
}
OutSplitDefinition = FDTFluxSplit();
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("ContestId %d, SplitId %d not found in ContestDefinition"),
ContestId, SplitId);
return false;
}
void UDTFluxCoreSubsystem::GetStage(const int ContestId, const int StageId, FDTFluxStage& OutStageDefinition)
{
if (GetStageDefinition(FDTFluxStageKey(ContestId, StageId),
OutStageDefinition))
{
return;
}
OutStageDefinition = FDTFluxStage();
}
void UDTFluxCoreSubsystem::GetSplit(const int ContestId, const int StageId, const int SplitId,
FDTFluxSplit& OutSplitDefinition)
{
if (GetSplitDefinition(FDTFluxSplitKey(ContestId, StageId, SplitId),
OutSplitDefinition))
{
return;
}
OutSplitDefinition = FDTFluxSplit();
} }

View File

@ -3,83 +3,210 @@
#include "DTFluxPursuitManager.h" #include "DTFluxPursuitManager.h"
#include <ImportExport.h>
#include "DTFluxCoreSubsystem.h"
#include "DTFluxCoreSubsystemModule.h" #include "DTFluxCoreSubsystemModule.h"
#include "Dataflow/DataflowContextCache.h"
UDTFluxPursuitManager::UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer): UDTFluxPursuitManager::UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer):
Super(ObjectInitializer) Super(ObjectInitializer)
{ {
} }
// TODO : Add way to pass MaxSimultaneousPursuit and MassStartDelay void UDTFluxPursuitManager::InitPursuit(const TArray<int> InContestIds, const int MaxSimultaneousPursuit)
// For now it's done in UPROPERTIES
void UDTFluxPursuitManager::LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests)
{ {
if (InitSubSystems()) CoreSubsystem = Cast<UDTFluxCoreSubsystem>(GetOuter());
if (!CoreSubsystem)
{ {
for (const auto Contest : InContests) UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("CoreSubsystem is not Available !!!"));
return;
}
AllRankings.Reset();
for (const auto& ContestId : InContestIds)
{ {
FRequestData RequestData; FDTFluxContest Contest;
RequestData.ContestId = Contest.ContestId; if (CoreSubsystem->GetContestForId(ContestId, Contest))
uint8 StageId = Contest.Stages.Last().StageId; {
FGuid Guid = NetworkSubsystem->SendTrackedRequestWithCallback(EDTFluxApiDataType::StageRanking, BindRankings();
Contest.ContestId, StageId, -1, FDTFluxStageKey StageKey = FDTFluxStageKey(ContestId, Contest.GetLastStageId());
FOnDTFluxTrackedRequestResponse::CreateUObject( FDTFluxStageRankings TempStageRankings;
this, //Obtenir les ranking Frais.
&UDTFluxPursuitManager::OnRequestResponse), CoreSubsystem->GetStageRankingsWithKey(StageKey, TempStageRankings, false);
FOnDTFluxTrackedRequestTimeout::CreateUObject( PendingStageRanking.Add(StageKey, false);
this,
&UDTFluxPursuitManager::OnRequestTimeoutResponse),
FOnDTFluxRequestResponseError::CreateUObject(
this,
&UDTFluxPursuitManager::OnRequestError));
RequestData.RequestIds.Add(Guid);
PendingRequestData.Add(RequestData);
} }
} }
} }
void UDTFluxPursuitManager::OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response) void UDTFluxPursuitManager::SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup)
{ {
UE_LOG(logDTFluxCoreSubsystem, Log, for (auto& Pursuit : NextFocusGroup.PursuitGroup)
TEXT("UDTFluxPursuitManager::OnRequestResponse() Received Ranking For Stage %i"), Response.StageID)
UE_LOG(logDTFluxCoreSubsystem, Log, TEXT("Response is %s"), *UEnum::GetValueAsString(Response.GetResponseType()))
//check if request
if (Response.GetResponseType() == EDTFluxApiDataType::StageRanking)
{ {
FDTFluxStageRankings Rankings; Pursuit.bIsMassStart = Pursuit.StartTime >= MassStartTime;
FRequestData FoundData;
if (Response.ParseStageRankingResponse(Rankings))
{
for (auto& PendingReq : PendingRequestData)
{
// Check for a matching PendingReq
if (PendingReq.IsWaitingFor(RequestId, Rankings))
{
FoundData = PendingReq;
// A request Is Terminated
UE_LOG(logDTFluxCoreSubsystem, Log,
TEXT("UDTFluxPursuitManager::OnRequestResponse() Ranking for Stage %i is complete"),
Response.StageID)
break;
}
}
if (InitPursuit(FoundData))
{
OnPursuitSequenceReady.Broadcast(NextFocusPursuits, NextFocusPursuits, bFocusIsTruncate);
}
}
} }
} }
void UDTFluxPursuitManager::OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage) void UDTFluxPursuitManager::DebugFocusNext(const TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext)
{ {
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Request Timeout [%s]"), *TimeoutMessage); 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::OnRequestError(const FGuid& RequestId, const FString& ErrorMessage) void UDTFluxPursuitManager::DebugOutPoursuitNext(const TArray<FDTFluxPursuitInfo>& OutPursuitNext)
{ {
UE_LOG(logDTFluxCoreSubsystem, Error, TEXT("Request Error [%s]"), *ErrorMessage); FString NextBibs;
for (int32 i = 0; i < OutPursuitNext.Num(); i++)
{
NextBibs += FString::Printf(TEXT("%d "), OutPursuitNext[i].Bib);
}
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next Bibs: %s"), *NextBibs);
}
void UDTFluxPursuitManager::GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext,
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();
// === VÉRIFICATION CRITIQUE : S'assurer qu'il reste des groupes après suppression ===
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; // Marquer la séquence comme terminée
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);
}
}
// === LOGS DE RÉSUMÉ ===
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("=== PURSUIT RESULTS ==="));
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Focus: %d participants"), OutPursuitFocusNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Next: %d participants"), OutPursuitNext.Num());
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Remaining groups for future: %d"), GroupedPursuit.Num());
if (OutPursuitFocusNext.Num() > 0)
{
DebugFocusNext(OutPursuitFocusNext);
}
// Log détaillé des Next (limité pour éviter spam)
if (OutPursuitNext.Num() > 0)
{
DebugOutPoursuitNext(OutPursuitNext);
}
// Vérifier si la séquence est terminée
if (GroupedPursuit.IsEmpty())
{
bIsSequenceDone = true;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Pursuit sequence will be completed after this round"));
}
FTimespan Duration = FDateTime::UtcNow() - MetricsStartFunction;
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Metrics Time Out: %d fraction seconds"),
Duration.GetDuration().GetFractionMicro());
} }
bool UDTFluxPursuitManager::InitSubSystems() bool UDTFluxPursuitManager::InitSubSystems()
@ -92,22 +219,64 @@ bool UDTFluxPursuitManager::InitSubSystems()
return NetworkSubsystem != nullptr; return NetworkSubsystem != nullptr;
} }
bool UDTFluxPursuitManager::InitPursuit(FRequestData Data) bool UDTFluxPursuitManager::BindRankings()
{ {
//Clean Data if (CoreSubsystem)
NextFocusPursuits.Empty(); {
NextPursuits.Empty(); if (!bIsRankingBounded)
PursuitGrouped.Empty(); {
TArray<FDTFluxDetailedRankingItem> AllRankings; 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; TArray<FDTFluxPursuitInfo> AllPursuits;
TMap<FDateTime, FDTFluxPursuitGroup> TempGroups; TMap<FDateTime, FDTFluxPursuitGroup> TempGroups;
bIsSequenceDone = false;
// Full the Array Of Rankings // Full the Array Of Rankings
for (auto& KeyPair : Data.StageRankings) for (auto& Ranking : AllRankings)
{ {
for (auto StageRanking : KeyPair.Value.Rankings) for (auto StageRanking : Ranking.Rankings)
{ {
int ContestId = KeyPair.Value.ContestId; int ContestId = Ranking.ContestId;
FDTFluxPursuitInfo PursuitInfo; FDTFluxPursuitInfo PursuitInfo;
PursuitInfo.StartTime = StageRanking.StartTime; PursuitInfo.StartTime = StageRanking.StartTime;
PursuitInfo.Bib = StageRanking.Bib; PursuitInfo.Bib = StageRanking.Bib;
@ -115,38 +284,67 @@ bool UDTFluxPursuitManager::InitPursuit(FRequestData Data)
AllPursuits.Add(PursuitInfo); AllPursuits.Add(PursuitInfo);
} }
} }
// Sort Rankings UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("AllPursuits.Num() = %i"), AllPursuits.Num());
// AllPursuits.Sort([](const FDTFluxPursuitInfo& A, const FDTFluxPursuitInfo& B) {
// return A.StartTime < B.StartTime;
// });
for (auto& Pursuit : AllPursuits) for (auto& Pursuit : AllPursuits)
{ {
if (TempGroups.Contains(Pursuit.StartTime)) if (TempGroups.Contains(Pursuit.StartTime))
{ {
TempGroups[Pursuit.StartTime].PursuitGroup.Add(Pursuit); FDTFluxPursuitGroup& Group = TempGroups[Pursuit.StartTime];
Group.PursuitGroup.Add(Pursuit);
UE_LOG(logDTFluxCoreSubsystem, Warning,
TEXT("Adding [%i] To PursuitGroup starting At %s, PursuitGroup.Num() %i"),
Pursuit.Bib, *Pursuit.StartTime.ToString(), Group.PursuitGroup.Num());
} }
else else
{ {
FDTFluxPursuitGroup Group; FDTFluxPursuitGroup NewGroup;
Group.StartTimeGlobal = Pursuit.StartTime; UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("New Group starting At %s, Adding Bib [%i]"),
Group.PursuitGroup.Add(Pursuit); *Pursuit.StartTime.ToString(), Pursuit.Bib);
TempGroups.Add(Pursuit.StartTime, Group); NewGroup.StartTimeGlobal = Pursuit.StartTime;
NewGroup.PursuitGroup.Add(Pursuit);
TempGroups.Add(Pursuit.StartTime, NewGroup);
for (const auto& Group : TempGroups)
{
UE_LOG(logDTFluxCoreSubsystem, Warning, TEXT("Group.StartTime = %s, Group.PursuitGroup.Num() = %i"),
*Group.Key.ToString(), Group.Value.PursuitGroup.Num());
}
} }
} }
TempGroups.KeySort([](const FDateTime& A, const FDateTime& B) TempGroups.KeySort([](const FDateTime& A, const FDateTime& B)
{ {
return A < B; return A < B;
}); });
PursuitGrouped.Reserve(TempGroups.Num()); TMap<FDateTime, int> StartTimeFrequency;
int32 MaxFrequency = 0;
GroupedPursuit.Reserve(TempGroups.Num());
// parcours du TMap
for (const auto& Pair : TempGroups) for (const auto& Pair : TempGroups)
{ {
PursuitGrouped.Add(Pair.Value); if (Pair.Value.StartTimeGlobal != FDateTime::MinValue() && Pair.Value.StartTimeGlobal != FDateTime::MaxValue())
{
// récuperation de la ref de la valeur actuel de la fréquence dans la TMap Freq
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);
} }
PursuitGrouped.Sort([](const FDTFluxPursuitGroup& A, const FDTFluxPursuitGroup& B) GroupedPursuit.Sort([](const FDTFluxPursuitGroup& A, const FDTFluxPursuitGroup& B)
{ {
return A.StartTimeGlobal < B.StartTimeGlobal; 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; return true;
} }

View File

@ -1,7 +1,10 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "DTFluxCoreSubsystemModule.h"
#include "Assets/DTFluxModelAsset.h"
#include "Containers/Deque.h" #include "Containers/Deque.h"
#include "Types/Struct/FDTFluxPursuitInfo.h"
#include "Subsystems/EngineSubsystem.h" #include "Subsystems/EngineSubsystem.h"
#include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxRaceDataStructs.h"
#include "Types/Struct/DTFluxTeamListStruct.h" #include "Types/Struct/DTFluxTeamListStruct.h"
@ -14,27 +17,51 @@ class UDTFluxNetworkSubsystem;
/** Forward Decl */ /** Forward Decl */
class UDTFluxModelAsset; class UDTFluxModelAsset;
class UDTFluxPursuitManager; 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_OneParam(FOnSplitRankings, FDTFluxSplitRankings&, SplitRankings); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSplitRankings, FDTFluxSplitKey, SplitKey, FDTFluxSplitRankings,
SplitRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnSplitRankings OnSplitRankings; FOnSplitRankings OnSplitRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnStageRankings, FDTFluxStageRankings&, StageRankings); DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnStageRankings, FDTFluxStageKey, StageKey, FDTFluxStageRankings,
StageRankings);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnStageRankings OnStageRankings; FOnStageRankings OnStageRankings;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnContestRankings, FDTFluxContestRankings&, ContestRankings);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(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;
@ -43,87 +70,111 @@ public:
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_OneParam(FOnTeamStatusUpdate, FDTFluxParticipant, TeamUpdated);
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem") UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnTeamStatusUpdate OnTeamStatusUpdate; FOnTeamStatusUpdate OnTeamStatusUpdate;
DECLARE_DELEGATE_TwoParams(FOnRequestedStageRankings, const FDTFluxStageKey&, const FDTFluxContestRankings&); UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
FOnRequestedStageRankings OnRequestedStageRankings; UDTFluxPursuitManager* PursuitManager = nullptr;
//
// DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTeamUpdate, FDateTime, ReceivedAt, FDTFluxParticipant, TeamUpdatedList); DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSplitSensor, FDTFluxSplitSensorInfo, SplitSensorInfo);
// UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
// FOnTeamUpdate OnTeamUpdate; UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
FOnSplitSensor OnSplitSensor;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFinisher, FDTFluxSplitSensorInfo, SplitSensorInfo);
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
FOnFinisher OnFinisher;
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWinner, FDTFluxSplitSensorInfo, SplitSensorInfo);
UPROPERTY(BlueprintReadOnly, Category="DTFlux|Core Subsystem")
FOnWinner OnWinner;
//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 SendTeamListRequest(); FGuid InitContestRankingsDisplay(const int ContestIds);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendRaceDataRequest(); FGuid InitStageRankingsDisplay(const int ContestId, const int StageId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendContestRankingRequest(int InContestId); FGuid InitSplitRankingsDisplay(const int ContestId, const int StageId, const int SplitId);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void SendStageRankingRequest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true); bool GetStageRankingForBib(const int ContestId, const int StageId, const int Bib,
FDTFluxStageRanking& OutStageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RequestAllStageRankingOfContest(int InContestId, int InStageId, bool bShouldIncludeSplitRanking = true); 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 SendSplitRankingRequest(int InContestId, int InStageId, int InSplitId); bool GetContestRanking(const int ContestId, FDTFluxContestRanking& OutContestRanking);
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")
FDTFluxStageRankings GetStageRankings(FDTFluxStageKey StageKey); bool GetStageRankings(const int ContestId, const int StageId, FDTFluxStageRankings& OutStageRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RequestAllSplitRankingOfStage(int InContestId, int InStageId, int InSplitId); bool GetSplitRankings(const int ContestId, const int StageId, const int SplitId,
FDTFluxSplitRankings& OutSplitRankings);
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
const FDTFluxParticipant GetParticipant(int InBib); 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") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void RefreshStorage();
UFUNCTION()
TArray<int> GetCurrentContestsId(); TArray<int> GetCurrentContestsId();
UFUNCTION()
TArray<FDTFluxContest> GetCurrentContests();
UFUNCTION()
TArray<int> GetContestsIdForTime(const FDateTime Time);
UFUNCTION()
bool GetContestForId(const int Id, FDTFluxContest& OutContest);
UFUNCTION()
TArray<FDTFluxContest> GetContestsForTime(const FDateTime Time);
UFUNCTION()
void RequestRankingsForStages(const TArray<FDTFluxStage> RequestedStages) const;
UFUNCTION()
TArray<FDTFluxContest> GetContests();
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem") UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
void LaunchPursuitSequenceFor(const TArray<int> ContestIds); 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
UPROPERTY()
UDTFluxPursuitManager* PursuitManager = nullptr;
UFUNCTION() 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);
@ -146,7 +197,8 @@ private:
UFUNCTION() UFUNCTION()
void RegisterDelegates(); void RegisterDelegates();
UFUNCTION()
UPROPERTY() bool IsStageRankingSealed(FDTFluxStageKey StageKey);
UDTFluxModelAsset* DataStorage = nullptr; UFUNCTION()
bool IsContestRankingSealed(int ContestId);
}; };

View File

@ -8,44 +8,33 @@
#include "DTFluxPursuitManager.generated.h" #include "DTFluxPursuitManager.generated.h"
USTRUCT() class UDTFluxCoreSubsystem;
struct FRequestData
USTRUCT(BlueprintType)
struct FPursuitStarterData
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() public:
TArray<FGuid> RequestIds; FPursuitStarterData() = default;
UPROPERTY()
TMap<FGuid, FDTFluxStageRankings> StageRankings;
UPROPERTY()
int ContestId;
UPROPERTY() FPursuitStarterData(const TArray<FDTFluxPursuitInfo>& InPursuitFocusNext,
bool bIsReady = false; const TArray<FDTFluxPursuitInfo>& InPursuitNext, const FDateTime& InMassStartTime,
const bool InIsFocusTruncate)
: PursuitFocusNext(InPursuitFocusNext), PursuitNext(InPursuitNext), MassStartTime(InMassStartTime),
FRequestData() = default; bIsFocusTruncate(InIsFocusTruncate)
FRequestData(const TArray<FGuid>& InRequestIds, const TMap<FGuid, FDTFluxStageRankings>& InStageRankings)
: RequestIds(InRequestIds), StageRankings(InStageRankings)
{ {
}; };
/** UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
* TArray<FDTFluxPursuitInfo> PursuitFocusNext = TArray<FDTFluxPursuitInfo>();
* @param RequestId UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
* @param InRankings TArray<FDTFluxPursuitInfo> PursuitNext = TArray<FDTFluxPursuitInfo>();
* @return True if all needed requests have responses UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
*/ FDateTime MassStartTime = FDateTime::MinValue();
bool IsWaitingFor(const FGuid& RequestId, const FDTFluxStageRankings& InRankings) UPROPERTY(BlueprintReadOnly, Category="DTFlux|Pursuit")
{ bool bIsFocusTruncate = false;
if (!StageRankings.Contains(RequestId))
{
StageRankings.Add(RequestId, InRankings);
}
bIsReady = StageRankings.Num() <= RequestIds.Num();
return bIsReady;
}
}; };
USTRUCT() USTRUCT()
@ -53,6 +42,7 @@ struct FDTFluxPursuitGroup
{ {
GENERATED_BODY() GENERATED_BODY()
UPROPERTY() UPROPERTY()
TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>(); TArray<FDTFluxPursuitInfo> PursuitGroup = TArray<FDTFluxPursuitInfo>();
UPROPERTY() UPROPERTY()
@ -63,10 +53,6 @@ struct FDTFluxPursuitGroup
bool bIsFocus = false; bool bIsFocus = false;
}; };
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnPursuitSequenceReady, const TArray<FDTFluxPursuitInfo>,
NextFocusPursuits,
const TArray<FDTFluxPursuitInfo>, NextPursuit, bool, bIsTrtuncate);
/** /**
* *
*/ */
@ -78,56 +64,66 @@ class DTFLUXCORESUBSYSTEM_API UDTFluxPursuitManager : public UObject
public: public:
UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer); UDTFluxPursuitManager(const FObjectInitializer& ObjectInitializer);
UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
TArray<FDTFluxPursuitInfo> NextFocusPursuits;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPursuitSequenceReady, const FPursuitStarterData, PursuitData);
TArray<FDTFluxPursuitInfo> NextPursuits;
UPROPERTY(BlueprintAssignable, Category="DTFlux|Core Subsystem")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UPROPERTY(BlueprintReadOnly, VisibleAnywhere) UPROPERTY(BlueprintReadOnly, VisibleAnywhere)
bool bFocusIsTruncate = false; bool bFocusIsTruncate = false;
//
// UPROPERTY()
// TArray<FDTFluxStage> TargetStages;
UPROPERTY() UPROPERTY()
int MaxSimultaneousPursuit = 7; int PursuitMaxSimultaneousPursuit = 7;
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit",
meta=(ClampMin="1", ClampMax="60", UIMin="0", UIMax="60")) UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="DTFlux|Pursuit")
int MassStartDelay = 10; int MassStartDelay = 10;
UPROPERTY() UPROPERTY()
TArray<FDTFluxPursuitGroup> PursuitGrouped; FDateTime MassStartTime = FDateTime::MinValue();
UPROPERTY()
TArray<FDTFluxPursuitGroup> GroupedPursuit;
UPROPERTY() UPROPERTY()
int CurrentIndex = -1; int CurrentIndex = -1;
UPROPERTY(BlueprintCallable, Category="DTFlux|Pursuit")
FOnPursuitSequenceReady OnPursuitSequenceReady;
UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite")) UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
void LaunchPursuitSequenceFor(const TArray<FDTFluxContest> InContests); void InitPursuit(const TArray<int> InContestIds, const int MaxSimultaneousPursuit = 7);
UFUNCTION() UFUNCTION(BlueprintCallable, Category="DTFlux|Pursuit", meta=(Keywords="pursuit, launch, poursuite"))
void OnRequestResponse(const FGuid& RequestId, FDTFluxServerResponse& Response); void GetPursuit(TArray<FDTFluxPursuitInfo>& OutPursuitFocusNext, TArray<FDTFluxPursuitInfo>& OutPursuitNext,
bool& BIsFocusTruncate, const int MaxSimultaneousPursuit = 7);
UFUNCTION()
void OnRequestTimeoutResponse(const FGuid& RequestId, const FString& TimeoutMessage);
UFUNCTION()
void OnRequestError(const FGuid& RequestId, const FString& ErrorMessage);
UFUNCTION() UFUNCTION()
bool InitSubSystems(); bool InitSubSystems();
private:
TArray<FRequestData> PendingRequestData;
public:
UFUNCTION() UFUNCTION()
bool InitPursuit(FRequestData Data); 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: private:
TMap<FDTFluxStageKey, bool> PendingStageRanking;
TArray<FDTFluxStageRankings> AllRankings;
UDTFluxCoreSubsystem* CoreSubsystem = nullptr;
UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr; UDTFluxNetworkSubsystem* NetworkSubsystem = nullptr;
UPROPERTY()
bool bIsSequenceDone = true;
UPROPERTY()
bool bIsRankingBounded = false;
UFUNCTION()
void SetPursuitInfoIsMassStart(FDTFluxPursuitGroup& NextFocusGroup);
UFUNCTION()
bool LaunchPursuitSequence();
}; };

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

View File

@ -1,51 +1,83 @@
// Fill out your copyright notice in the Description page of Project Settings. // ================================================================================================
// DTFluxRequestManager.cpp - Implémentation du gestionnaire de requêtes
// ================================================================================================
#include "DTFluxQueuedManager.h" #include "DTFluxQueuedManager.h"
#include "DTFluxAsyncParser.h"
#include "DTFluxNetworkModule.h" #include "DTFluxNetworkModule.h"
#include "Struct/DTFluxServerResponseStruct.h"
#include "Struct/DTFluxRequestStructs.h"
#include "JsonObjectConverter.h" #include "JsonObjectConverter.h"
bool FDTFluxTrackedRequest::HasTimedOut() const
{
if (State != EDTFluxRequestState::Pending && State != EDTFluxRequestState::Sent)
return false;
return (FDateTime::Now() - CreatedAt).GetTotalSeconds() > Config.TimeoutSeconds;
}
const FString FDTFluxQueuedRequest::Serialize() const 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; FString JSONString;
switch (RequestType) switch (RequestType)
{ {
case EDTFluxRequestType::RaceData: case EDTFluxApiDataType::RaceData:
{ {
FDTFluxRaceDataRequest RaceData; FDTFluxRaceDataRequest RaceData;
FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString); FJsonObjectConverter::UStructToJsonObjectString(RaceData, JSONString);
break; break;
} }
case EDTFluxApiDataType::TeamList:
case EDTFluxRequestType::TeamList:
{ {
const FDTFluxTeamListRequest TeamList; const FDTFluxTeamListRequest TeamList;
FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString); FJsonObjectConverter::UStructToJsonObjectString(TeamList, JSONString);
break; break;
} }
case EDTFluxApiDataType::ContestRanking:
case EDTFluxRequestType::ContestRanking:
{ {
FDTFluxContestRankingRequest ContestRanking(ContestId); FDTFluxContestRankingRequest ContestRanking(ContestId);
FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString); FJsonObjectConverter::UStructToJsonObjectString(ContestRanking, JSONString);
break; break;
} }
case EDTFluxApiDataType::StageRanking:
case EDTFluxRequestType::StageRanking:
{ {
FDTFluxStageRankingRequest StageRanking(ContestId, StageId); FDTFluxStageRankingRequest StageRanking(ContestId, StageId);
FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString); FJsonObjectConverter::UStructToJsonObjectString(StageRanking, JSONString);
break; break;
} }
case EDTFluxApiDataType::SplitRanking:
case EDTFluxRequestType::SplitRanking:
{ {
FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId); FDTFluxSplitRankingRequest SplitRanking(ContestId, StageId, SplitId);
FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString); FJsonObjectConverter::UStructToJsonObjectString(SplitRanking, JSONString);
break; break;
} }
default: default:
JSONString = ""; JSONString = "";
break; break;
@ -53,387 +85,586 @@ const FString FDTFluxQueuedRequest::Serialize() const
return JSONString; return JSONString;
} }
UDTFluxQueuedManager::UDTFluxQueuedManager() FDTFluxQueuedRequestManager::FDTFluxQueuedRequestManager()
: bIsInitialized(false)
, CheckInterval(0.5f)
, TimeSinceLastCheck(0.0f)
{ {
AsyncParser = MakeUnique<FDTFluxAsyncParser>();
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager created"));
} }
UDTFluxQueuedManager::~UDTFluxQueuedManager() FDTFluxQueuedRequestManager::~FDTFluxQueuedRequestManager()
{ {
ClearAllRequests(); Shutdown();
UE_LOG(logDTFluxNetwork, Verbose, TEXT("RequestManager destroyed"));
} }
void UDTFluxQueuedManager::Initialize() void FDTFluxQueuedRequestManager::Initialize(const FDTFluxRequestConfig& InDefaultConfig)
{ {
if (!bIsInitialized) if (bIsInitialized.load())
{ {
UE_LOG(logDTFluxNetwork, Log, TEXT("Initializing DTFluxQueuedManager")); UE_LOG(logDTFluxNetwork, Warning, TEXT("RequestManager already initialized"));
bIsInitialized = true; return;
} }
DefaultConfig = InDefaultConfig;
bIsInitialized.store(true);
UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager initialized with timeout=%.1fs"),
DefaultConfig.TimeoutSeconds);
} }
FGuid UDTFluxQueuedManager::QueueRequest(EDTFluxRequestType RequestType, int32 ContestId, int32 StageId, int32 SplitId, void FDTFluxQueuedRequestManager::Shutdown()
const FString& RawMessage)
{ {
// Créer la requête avec les structs existants if (!bIsInitialized.load())
FDTFluxQueuedRequest NewRequest(RequestType, ContestId, StageId, SplitId); return;
NewRequest.RawResponse = RawMessage;
// Ajouter à la queue des requêtes en attente bIsInitialized.store(false);
PendingRequestsQueue.Enqueue(NewRequest); // Nettoyer toutes les données
{
FScopeLock RequestsLock_Local(&RequestsLock);
FScopeLock CallbacksLock_Local(&CallbacksLock);
UE_LOG(logDTFluxNetwork, Verbose, TEXT("Queued request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"), AllRequests.Empty();
*NewRequest.RequestId.ToString(), (int32)RequestType, ContestId, StageId, SplitId); SuccessCallbacks.Empty();
ErrorCallbacks.Empty();
}
return NewRequest.RequestId; UE_LOG(logDTFluxNetwork, Log, TEXT("RequestManager shutdown"));
} }
bool UDTFluxQueuedManager::MarkRequestAsError(const FGuid& TargetRequestGuid) FGuid FDTFluxQueuedRequestManager::CreateTrackedRequest(
EDTFluxApiDataType RequestType,
int32 ContestId,
int32 StageId,
int32 SplitId,
const FDTFluxRequestConfig& CustomConfig)
{ {
// TODO: Implement a retry mechanism if (!bIsInitialized.load())
// For now we simply suppress the request and log a message
bool bFoundMatch = false;
FDTFluxQueuedRequest Request;
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue;
while (PendingRequestsQueue.Dequeue(Request))
{ {
if (Request.RequestId == TargetRequestGuid) UE_LOG(logDTFluxNetwork, Error, TEXT("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;
{ {
UE_LOG(logDTFluxNetwork, Error, FScopeLock Lock(&RequestsLock);
TEXT("Marked request %s as error: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"), AllRequests.Add(RequestId, NewRequest);
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId, TotalRequests++;
Request.SplitId); }
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 else
{ {
TempQueue.Enqueue(Request); 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;
} }
} }
while (TempQueue.Dequeue(Request))
/**
* @todo Check protocol errors ???
* @param RequestId
* @param ErrorMessage
* @return
*/
bool FDTFluxQueuedRequestManager::FailRequest(const FGuid& RequestId, const FString& ErrorMessage)
{
TSharedPtr<FDTFluxTrackedRequest> Request;
{ {
PendingRequestsQueue.Enqueue(Request); FScopeLock Lock(&RequestsLock);
} if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
if (bFoundMatch)
{ {
UE_LOG(logDTFluxNetwork, Error, TEXT("No Request Found with GUID %s"), *TargetRequestGuid.ToString()); 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; return true;
} }
bool UDTFluxQueuedManager::MarkRequestAsResponded(const FGuid& TargetRequestGuid) bool FDTFluxQueuedRequestManager::RetryRequest(const FGuid& RequestId)
{ {
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue; TSharedPtr<FDTFluxTrackedRequest> Request;
bool bFoundMatch = false;
// Parcourir toutes les requêtes en attente
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{ {
if (!bFoundMatch && Request.RequestId == TargetRequestGuid) FScopeLock Lock(&RequestsLock);
if (TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{ {
// Marquer comme ayant reçu une réponse Request = *RequestPtr;
Request.bHasReceivedResponse = true;
bFoundMatch = true;
// Ajouter à la queue des requêtes terminées
CompletedRequestsQueue.Enqueue(Request);
UE_LOG(logDTFluxNetwork, Verbose,
TEXT("Marked request %s as responded: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
Request.SplitId);
}
else
{
// Remettre dans la queue temporaire
TempQueue.Enqueue(Request);
} }
} }
// Remettre les requêtes non traitées dans la queue principale if (!Request.IsValid() || !Request->CanRetry())
while (TempQueue.Dequeue(Request))
{ {
PendingRequestsQueue.Enqueue(Request); return false;
} }
return bFoundMatch; 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 UDTFluxQueuedManager::MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest) bool FDTFluxQueuedRequestManager::FindPendingRequest(
{ FGuid& OutRequestId,
return MarkRequestAsResponded(TargetRequest.RequestId); EDTFluxApiDataType RequestType,
} int32 ContestId,
bool UDTFluxQueuedManager::IsRequestPending(FGuid& OutRequestId, EDTFluxApiDataType RequestType, int32 ContestId,
int32 StageId, int32 StageId,
int32 SplitId) int32 SplitId) const
{ {
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue; FScopeLock Lock(&RequestsLock);
bool bFoundMatch = false;
// Parcourir toutes les requêtes en attente for (const auto& [RequestId, Request] : AllRequests)
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{ {
// Vérifier si cette requête correspond if ((Request->State == EDTFluxRequestState::Pending || Request->State == EDTFluxRequestState::Sent) &&
if (!bFoundMatch && Request.Matches(RequestType, ContestId, StageId, SplitId)) Request->Matches(RequestType, ContestId, StageId, SplitId))
{ {
bFoundMatch = true; OutRequestId = RequestId;
OutRequestId = Request.RequestId; return true;
UE_LOG(logDTFluxNetwork, Verbose, }
TEXT("Found pending request %s: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"),
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId,
Request.SplitId);
} }
// Remettre dans la queue temporaire return false;
TempQueue.Enqueue(Request);
}
// Remettre toutes les requêtes dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return bFoundMatch;
} }
FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId, // === ACCESSEURS ===
int32 StageId, int32 SplitId)
{
auto SearchInQueue = [&RequestType, ContestId, StageId, SplitId](
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest*
{
// Copie temporaire de la queue pour la recherche
TQueue<FDTFluxQueuedRequest> TempQueue;
FDTFluxQueuedRequest* FoundItem = nullptr; bool FDTFluxQueuedRequestManager::GetRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const
FDTFluxQueuedRequest Item; {
while (Queue.Dequeue(Item)) FScopeLock Lock(&RequestsLock);
if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
{ {
if (Item.RequestType == RequestType && Item.ContestId == ContestId && Item.StageId == StageId && Item. OutRequest = **RequestPtr;
SplitId == SplitId) // Assuming RequestId is your GUID field return true;
{
FoundItem = &Item;
} }
// Remettre dans la queue temporaire
TempQueue.Enqueue(Item); return false;
}
while (TempQueue.Dequeue(Item))
{
Queue.Enqueue(Item);
}
return FoundItem;
};
return SearchInQueue(PendingRequestsQueue);
} }
const FDTFluxQueuedRequest* UDTFluxQueuedManager::GetRequest(const FGuid& SearchedGuid) const FDTFluxTrackedRequest* FDTFluxQueuedRequestManager::GetRequestPtr(const FGuid& RequestId) const
{ {
auto SearchInQueue = [&SearchedGuid](TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc>& Queue) -> FDTFluxQueuedRequest* FScopeLock Lock(&RequestsLock);
{
// Copie temporaire de la queue pour la recherche
TQueue<FDTFluxQueuedRequest> TempQueue;
FDTFluxQueuedRequest* FoundItem = nullptr; if (const TSharedPtr<FDTFluxTrackedRequest>* RequestPtr = AllRequests.Find(RequestId))
FDTFluxQueuedRequest Item;
while (Queue.Dequeue(Item))
{ {
if (Item.RequestId == SearchedGuid) // Assuming RequestId is your GUID field return RequestPtr->Get();
{
// Trouver l'élément dans la queue originale
// On doit refaire une copie car on ne peut pas retourner l'adresse de 'Item'
FoundItem = &Item;
} }
// Remettre dans la queue temporaire
TempQueue.Enqueue(Item);
}
while (TempQueue.Dequeue(Item))
{
Queue.Enqueue(Item);
}
return FoundItem;
};
// Chercher dans chaque queue
if (FDTFluxQueuedRequest* Found = SearchInQueue(PendingRequestsQueue))
return Found;
if (const FDTFluxQueuedRequest* Found = SearchInQueue(CompletedRequestsQueue))
return Found;
if (const FDTFluxQueuedRequest* Found = SearchInQueue(TimedOutRequestsQueue))
return Found;
return nullptr; return nullptr;
} }
int32 UDTFluxQueuedManager::GetPendingRequestCount() TArray<FDTFluxTrackedRequest> FDTFluxQueuedRequestManager::GetRequestsByState(EDTFluxRequestState State) const
{ {
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue; FScopeLock Lock(&RequestsLock);
int32 Count = 0;
// Compter les requêtes en attente TArray<FDTFluxTrackedRequest> Results;
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request)) for (const auto& [RequestId, Request] : AllRequests)
{ {
Count++; if (Request->State == State)
TempQueue.Enqueue(Request); {
Results.Add(*Request);
}
} }
// Remettre toutes les requêtes dans la queue principale return Results;
while (TempQueue.Dequeue(Request)) }
int32 FDTFluxQueuedRequestManager::GetRequestCount(EDTFluxRequestState State) const
{
FScopeLock Lock(&RequestsLock);
int32 Count = 0;
for (const auto& [RequestId, Request] : AllRequests)
{ {
PendingRequestsQueue.Enqueue(Request); if (Request->State == State)
{
Count++;
}
} }
return Count; return Count;
} }
int32 UDTFluxQueuedManager::CleanupTimedOutRequests() FDTFluxQueuedRequestManager::FRequestStatistics FDTFluxQueuedRequestManager::GetStatistics() const
{ {
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TempQueue; FScopeLock RequestsLock_Local(&RequestsLock);
int32 TimeoutCount = 0; FScopeLock MetricsLock_Local(&MetricsLock);
// Parcourir toutes les requêtes en attente FRequestStatistics Stats;
FDTFluxQueuedRequest Request;
while (PendingRequestsQueue.Dequeue(Request))
{
if (Request.HasTimedOut())
{
// Ajouter à la queue des requêtes expirées
TimedOutRequestsQueue.Enqueue(Request);
TimeoutCount++;
UE_LOG(logDTFluxNetwork, Warning, for (const auto& [RequestId, Request] : AllRequests)
TEXT("Request %s timed out: Type=%d, ContestId=%d, StageId=%d, SplitId=%d"), {
*Request.RequestId.ToString(), (int32)Request.RequestType, Request.ContestId, Request.StageId, switch (Request->State)
Request.SplitId); {
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 else
{ {
// Remettre dans la queue temporaire ChangeRequestState(Request, EDTFluxRequestState::TimedOut);
TempQueue.Enqueue(Request); TriggerCallbacks(*Request);
OnRequestFailed.Broadcast(*Request);
} }
} }
// Remettre les requêtes non expirées dans la queue principale
while (TempQueue.Dequeue(Request))
{
PendingRequestsQueue.Enqueue(Request);
}
return TimeoutCount;
} }
int32 UDTFluxQueuedManager::CleanCashedRequests() void FDTFluxQueuedRequestManager::ProcessRetries()
{ {
int32 CleanedRequestsCount = 0; FScopeLock Lock(&RequestsLock);
// Queue temporaire pour stocker les requêtes encore valides const FDateTime Now = FDateTime::Now();
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> ValidCompletedRequests; TArray<TSharedPtr<FDTFluxTrackedRequest>> ReadyToRetry;
// Traiter toutes les requêtes terminées for (const auto& Pair : AllRequests)
FDTFluxQueuedRequest CompletedRequest;
while (CompletedRequestsQueue.Dequeue(CompletedRequest))
{ {
// Vérifier si la requête est cacheable et a reçu une réponse const auto& Request = Pair.Value;
if (CompletedRequest.bIsCacheable && CompletedRequest.bHasReceivedResponse) if (Request->State == EDTFluxRequestState::Retrying)
{ {
// Calculer l'âge de la requête en secondes const float ElapsedSinceLastAttempt = (Now - Request->LastAttemptTime).GetTotalSeconds();
float RequestAge = (FDateTime::Now() - CompletedRequest.CreatedAt).GetTotalSeconds(); if (ElapsedSinceLastAttempt >= Request->GetRetryDelay())
// Vérifier si le cache est encore valide
if (RequestAge <= CompletedRequest.CachedValidity)
{ {
// Le cache est encore valide, conserver la requête ReadyToRetry.Add(Request);
ValidCompletedRequests.Enqueue(CompletedRequest);
} }
else
{
// Le cache a expiré, compter cette requête comme nettoyée
CleanedRequestsCount++;
UE_LOG(LogTemp, Verbose,
TEXT("DTFluxQueuedManager: Cleaned expired cached request %s (Age: %.2fs, Validity: %.2fs)"),
*CompletedRequest.RequestId.ToString(), RequestAge, CompletedRequest.CachedValidity);
}
}
else
{
// Requête non cacheable ou sans réponse, la conserver
ValidCompletedRequests.Enqueue(CompletedRequest);
} }
} }
// Restaurer la queue avec uniquement les requêtes valides for (const auto& Request : ReadyToRetry)
while (ValidCompletedRequests.Dequeue(CompletedRequest))
{ {
CompletedRequestsQueue.Enqueue(CompletedRequest); Request->LastAttemptTime = Now;
ChangeRequestState(Request, EDTFluxRequestState::Pending);
} }
// Log du résultat si des requêtes ont été nettoyées
if (CleanedRequestsCount > 0)
{
UE_LOG(LogTemp, Log, TEXT("DTFluxQueuedManager: Cleaned %d expired cached requests"), CleanedRequestsCount);
}
return CleanedRequestsCount;
} }
void UDTFluxQueuedManager::ClearAllRequests() void FDTFluxQueuedRequestManager::TriggerCallbacks(const FDTFluxTrackedRequest& Request)
{ {
// Vider toutes les queues FScopeLock Lock(&CallbacksLock);
FDTFluxQueuedRequest DummyRequest;
while (PendingRequestsQueue.Dequeue(DummyRequest))
{
}
while (CompletedRequestsQueue.Dequeue(DummyRequest))
{
}
while (TimedOutRequestsQueue.Dequeue(DummyRequest))
{
}
UE_LOG(logDTFluxNetwork, Log, TEXT("Cleared all pending requests")); 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)
void UDTFluxQueuedManager::Tick(float DeltaTime)
{ {
if (!bIsInitialized) 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; return;
} }
// Incrémenter le temps écoulé Request = *RequestPtr;
TimeSinceLastCheck += DeltaTime;
// Vérifier si c'est le moment de nettoyer les requêtes expirées
if (TimeSinceLastCheck >= CheckInterval)
{
TimeSinceLastCheck = 0.0f;
CleanupTimedOutRequests();
} }
// Traiter les requêtes expirées if (bSuccess && ParsedResponse.IsValid())
FDTFluxQueuedRequest TimedOutRequest;
while (TimedOutRequestsQueue.Dequeue(TimedOutRequest))
{ {
// Déclencher l'événement pour chaque requête expirée Request->ParsedResponse = ParsedResponse;
OnRequestTimedOut.Broadcast(TimedOutRequest); 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);
} }
bool UDTFluxQueuedManager::IsTickable() const void FDTFluxQueuedRequestManager::OnParsingFailed(const FGuid& RequestId, const FString& ErrorMessage)
{ {
return bIsInitialized; UE_LOG(logDTFluxNetwork, Error, TEXT("DTFluxQueuedRequestManager: Async parsing failed for request %s: %s"),
} *RequestId.ToString(),
*ErrorMessage);
TStatId UDTFluxQueuedManager::GetStatId() const FailRequest(RequestId, FString::Printf(TEXT("Parsing failed: %s"), *ErrorMessage));
{
RETURN_QUICK_DECLARE_CYCLE_STAT(UDTFluxQueuedManager, STATGROUP_Tickables);
} }

View File

@ -1,6 +1,7 @@
#pragma once #include "Struct/DTFluxServerResponseStruct.h"
#include "DTFluxNetworkModule.h"
#include "Types/Objects/UDTFluxParticipantFactory.h"
#include "Struct/DTFluxServerResponseStruct.h"
// === IMPLÉMENTATION DES CONSTRUCTEURS === // === IMPLÉMENTATION DES CONSTRUCTEURS ===
@ -194,7 +195,7 @@ FString FDTFluxServerResponse::ToDebugString() const
*UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString()); *UEnum::GetValueAsString(ParsingStatus), *Type, Code, ContestID, StageID, SplitID, *ReceivedAt.ToString());
} }
bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList) bool FDTFluxServerResponse::ParseTeamList(FDTFluxTeamListDefinition& OutTeamList)
{ {
ParsingStatus = EDTFluxResponseStatus::Unset; ParsingStatus = EDTFluxResponseStatus::Unset;
if (!ValidateResponseType(TEXT("team-list"))) if (!ValidateResponseType(TEXT("team-list")))
@ -256,9 +257,9 @@ bool FDTFluxServerResponse::ParseTeamListResponse(FDTFluxTeamListDefinition& Out
return false; return false;
} }
bool FDTFluxServerResponse::ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate) bool FDTFluxServerResponse::ParseTeamUpdate(FDTFluxTeamListDefinition& OutTeamUpdate)
{ {
return ParseTeamListResponse(OutTeamUpdate); return ParseTeamList(OutTeamUpdate);
} }
bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData) bool FDTFluxServerResponse::ParseRaceData(FDTFluxRaceData& OutRaceData)
@ -368,7 +369,7 @@ bool FDTFluxServerResponse::ParseContestRanking(FDTFluxContestRankings& OutConte
return true; return true;
} }
bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings) bool FDTFluxServerResponse::ParseStageRanking(FDTFluxStageRankings& OutStageRankings)
{ {
// UE_LOG(logDTFluxNetwork, Log, TEXT("Response is stage-ranking type %s"), *RawMessage); // UE_LOG(logDTFluxNetwork, Log, TEXT("Response is stage-ranking type %s"), *RawMessage);
if (!ValidateResponseType(TEXT("stage-ranking"))) if (!ValidateResponseType(TEXT("stage-ranking")))
@ -396,7 +397,7 @@ bool FDTFluxServerResponse::ParseStageRankingResponse(FDTFluxStageRankings& OutS
return true; return true;
} }
bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings) bool FDTFluxServerResponse::ParseSplitRanking(FDTFluxSplitRankings& OutSplitRankings)
{ {
if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1) if (!ValidateResponseType(TEXT("stage-ranking")) || SplitID == -1)
{ {
@ -427,7 +428,7 @@ bool FDTFluxServerResponse::ParseSplitRankingResponse(FDTFluxSplitRankings& OutS
return true; return true;
} }
bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate) bool FDTFluxServerResponse::ParseStatusUpdate(FDTFluxTeamStatusUpdate& OutStatusUpdate)
{ {
if (!ValidateResponseType(TEXT("status-update"))) if (!ValidateResponseType(TEXT("status-update")))
{ {
@ -448,7 +449,7 @@ bool FDTFluxServerResponse::ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& O
return true; return true;
} }
bool FDTFluxServerResponse::ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos) bool FDTFluxServerResponse::ParseSplitSensor(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos)
{ {
if (!ValidateResponseType(TEXT("split-sensor"))) if (!ValidateResponseType(TEXT("split-sensor")))
{ {

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

View File

@ -1,66 +1,375 @@
// Fill out your copyright notice in the Description page of Project Settings. // ================================================================================================
// DTFluxRequestManager.h - Gestionnaire C++ optimisé avec cache, timeout et retry
// ================================================================================================
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UObject/Object.h"
#include "Containers/Queue.h"
#include "Tickable.h" #include "Tickable.h"
#include "Struct/DTFluxRequestStructs.h" #include "HAL/CriticalSection.h"
#include "Struct/DTFluxServerResponseStruct.h" #include "Struct/DTFluxServerResponseStruct.h"
#include "Types/Enum/DTFluxCoreEnum.h" #include "Types/Enum/DTFluxCoreEnum.h"
#include "DTFluxQueuedManager.generated.h" #include "DTFluxQueuedManager.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnRequestTimedOut, const FDTFluxQueuedRequest&, TimedOutRequest); class FDTFluxAsyncParser;
/**
* @brief Classe Tickable gérant les requêtes WebSockets qui ne sont pas traçables nativement par l'API. // ================================================================================================
* Cette classe utilise TQueue pour gérer efficacement les requêtes en attente et vérifie leur état dans le tick. // ENUMS ET STRUCTURES POUR LES REQUÊTES
*/ // ================================================================================================
UCLASS()
class DTFLUXNETWORK_API UDTFluxQueuedManager : public UObject, public FTickableGameObject 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() GENERATED_BODY()
public: UPROPERTY(EditAnywhere, BlueprintReadWrite)
/** Constructeur par défaut */ float TimeoutSeconds = 5.0f;
UDTFluxQueuedManager();
virtual ~UDTFluxQueuedManager() override; UPROPERTY(EditAnywhere, BlueprintReadWrite)
void Initialize(); int32 MaxRetries = 3;
FGuid QueueRequest(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, int32 SplitId = -1,
const FString& RawMessage = ""); UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool MarkRequestAsError(const FGuid& TargetRequestGuid); float RetryBackoffMultiplier = 1.5f;
bool MarkRequestAsResponded(const FGuid& TargetRequestGuid); };
bool MarkRequestAsResponded(const FDTFluxQueuedRequest& TargetRequest);
bool IsRequestPending(FGuid& OutRequestId, EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, USTRUCT(BlueprintType)
int32 SplitId = -1); struct DTFLUXNETWORK_API FDTFluxTrackedRequest
FDTFluxQueuedRequest* GetRequestPending(EDTFluxRequestType RequestType, int32 ContestId = -1, int32 StageId = -1, {
int32 SplitId = -1); GENERATED_BODY()
const FDTFluxQueuedRequest* GetRequest(const FGuid& SearchedGuid);
int32 GetPendingRequestCount(); // === IDENTIFICATION ===
int32 CleanupTimedOutRequests(); UPROPERTY(BlueprintReadOnly)
int32 CleanCashedRequests(); FGuid RequestId = FGuid::NewGuid();
void ClearAllRequests();
// bool TryProcessResponse(const FDTFluxServerResponse& Response); 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 ===
// Interface FTickableGameObject
virtual void Tick(float DeltaTime) override; virtual void Tick(float DeltaTime) override;
virtual bool IsTickable() const override; virtual bool IsTickable() const override { return true; };
virtual TStatId GetStatId() const override;
virtual TStatId GetStatId() const override
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FDTFluxQueuedRequestManager, STATGROUP_Tickables);
};
virtual bool IsTickableWhenPaused() const override { return true; } virtual bool IsTickableWhenPaused() const override { return true; }
virtual bool IsTickableInEditor() const override { return true; } virtual bool IsTickableInEditor() const override { return true; }
// Interface ~FTickableGameObject
// === ACCESSEUR POUR LE PARSER (debug/stats) ===
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network") const FDTFluxAsyncParser* GetAsyncParser() const { return AsyncParser.Get(); }
FOnRequestTimedOut OnRequestTimedOut;
private: private:
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> PendingRequestsQueue; // === CONFIGURATION ===
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> CompletedRequestsQueue; FDTFluxRequestConfig DefaultConfig;
TQueue<FDTFluxQueuedRequest, EQueueMode::Mpsc> TimedOutRequestsQueue; std::atomic<bool> bIsInitialized{false};
bool bIsInitialized; // === TIMING POUR LE TICK ===
float CheckInterval; float TimeSinceLastTimeoutCheck = 0.0f;
float TimeSinceLastCheck; 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);
}; };

View File

@ -4,13 +4,11 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "UObject/Object.h" #include "UObject/Object.h"
#include "DTFluxNetworkModule.h"
#include "DTFluxRaceDataServerResponse.h" #include "DTFluxRaceDataServerResponse.h"
#include "DTFluxRankingServerResponse.h" #include "DTFluxRankingServerResponse.h"
#include "DTFluxSplitSensorServerResponse.h" #include "DTFluxSplitSensorServerResponse.h"
#include "JsonObjectConverter.h" #include "JsonObjectConverter.h"
#include "Types/Enum/DTFluxCoreEnum.h" #include "Types/Enum/DTFluxCoreEnum.h"
#include "Types/Objects/UDTFluxParticipantFactory.h"
#include "Types/Struct/DTFluxRaceDataStructs.h" #include "Types/Struct/DTFluxRaceDataStructs.h"
#include "Types/Struct/DTFluxRankingStructs.h" #include "Types/Struct/DTFluxRankingStructs.h"
#include "Types/Struct/DTFluxSplitSensor.h" #include "Types/Struct/DTFluxSplitSensor.h"
@ -85,14 +83,14 @@ public:
EDTFluxResponseStatus TryParse(bool bLogErrors = true); EDTFluxResponseStatus TryParse(bool bLogErrors = true);
bool ParseTeamListResponse(FDTFluxTeamListDefinition& OutTeamList); bool ParseTeamList(FDTFluxTeamListDefinition& OutTeamList);
bool ParseTeamUpdateResponse(FDTFluxTeamListDefinition& OutTeamUpdate); bool ParseTeamUpdate(FDTFluxTeamListDefinition& OutTeamUpdate);
bool ParseRaceData(FDTFluxRaceData& OutRaceData); bool ParseRaceData(FDTFluxRaceData& OutRaceData);
bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings); bool ParseContestRanking(FDTFluxContestRankings& OutContestRankings);
bool ParseStageRankingResponse(FDTFluxStageRankings& OutStageRankings); bool ParseStageRanking(FDTFluxStageRankings& OutStageRankings);
bool ParseSplitRankingResponse(FDTFluxSplitRankings& OutSplitRankings); bool ParseSplitRanking(FDTFluxSplitRankings& OutSplitRankings);
bool ParseStatusUpdateResponse(FDTFluxTeamStatusUpdate& OutStatusUpdate); bool ParseStatusUpdate(FDTFluxTeamStatusUpdate& OutStatusUpdate);
bool ParseSplitSensorResponse(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos); bool ParseSplitSensor(TArray<FDTFluxSplitSensorInfo>& OutSplitSensorInfos);
// === MÉTHODES UTILITAIRES === // === MÉTHODES UTILITAIRES ===

View File

@ -1,41 +1,54 @@
// 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 "DTFluxQueuedManager.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 UDTFluxQueuedManager; class FDTFluxQueuedRequestManager;
typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP; typedef TSharedPtr<FDTFluxWebSocketClient> FDTFluxWebSocketClientSP;
class FDTFluxHttpClient;
typedef TSharedPtr<FDTFluxHttpClient> FDTFluxHttpClientSP;
// ================================================================================================
// DELEGATES BLUEPRINT POUR LES REQUÊTES TRACKÉES
// ================================================================================================
// Delegates pour les requêtes avec callback
DECLARE_DELEGATE_TwoParams(FOnDTFluxRequestResponseError, const FGuid&, const FString&);
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestResponse, const FGuid&, FDTFluxServerResponse&);
DECLARE_DELEGATE_TwoParams(FOnDTFluxTrackedRequestTimeout, const FGuid&, const FString& /*ErrorMessage*/);
// Delegates Blueprint pour les requêtes avec tracking
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestCompleted, const FGuid&, RequestId, DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestCompleted, const FGuid&, RequestId,
EDTFluxApiDataType, RequestType, const FString&, ResponseData); EDTFluxApiDataType, RequestType, const FString&, ResponseData);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestFailed, const FGuid&, RequestId, DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnDTFluxTrackedRequestFailed, const FGuid&, RequestId,
EDTFluxApiDataType, RequestType, const FString&, ErrorMessage); EDTFluxApiDataType, RequestType, const FString&, ErrorMessage);
// ================================================================================================
// DELEGATES LEGACY POUR LA COMPATIBILITÉ
// ================================================================================================
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);
// ================================================================================================
// NETWORK SUBSYSTEM - Interface UObject avec compatibilité Blueprint
// ================================================================================================
/** /**
* * Subsystem réseau DTFlux avec support complet des requêtes trackées et compatibilité legacy
* Combine l'efficacité du RequestManager C++ avec l'interface Blueprint UObject
*/ */
UCLASS(Blueprintable) UCLASS(Blueprintable)
class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
@ -43,180 +56,234 @@ class DTFLUXNETWORK_API UDTFluxNetworkSubsystem : public UEngineSubsystem
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY() // === ÉTAT DE CONNEXION ===
UPROPERTY(BlueprintReadOnly, Category = "DTFlux|Network")
EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset; EDTFluxConnectionStatus WsStatus = EDTFluxConnectionStatus::Unset;
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWebSocketConnected); // === CONNEXION WEBSOCKET (Legacy) ===
UPROPERTY(BlueprintAssignable, Category="DTFlux|Network") /**
FOnWebSocketConnected OnWebSocketConnected; * Se connecter au serveur WebSocket
*/
DECLARE_DELEGATE_OneParam(FOnRaceDataReceived, const FDTFluxRaceData& /*RaceDataDefinition*/); UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
FOnRaceDataReceived OnRaceDataReceived;
FOnRaceDataReceived& OnReceivedRaceData()
{
return OnRaceDataReceived;
};
// === DELEGATES POUR LES DONNÉES REÇUES (PUSH) ===
DECLARE_DELEGATE_OneParam(FOnTeamListReceived, const FDTFluxTeamListDefinition& /*TeamListDefinition*/);
FOnTeamListReceived OnTeamListReceived;
FOnTeamListReceived& OnReceivedTeamList()
{
return OnTeamListReceived;
};
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;
};
// === DELEGATES POUR LES DONNÉES REÇUES (PULL) ===
DECLARE_DELEGATE_OneParam(FOnSplitSensorReceived, const FDTFluxSplitSensorInfo& /*ContestRankings*/);
FOnSplitSensorReceived OnSplitSensorReceived;
FOnSplitSensorReceived& OnReceivedSplitSensor()
{
return OnSplitSensorReceived;
};
DECLARE_DELEGATE_OneParam(FOnTeamUpdateReceived, const FDTFluxTeamListDefinition& /*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();
// === REQUÊTES TRACKÉES (Nouveau système optimisé) ===
// === REQUÊTES AVEC QUEUE ET TRACKING === /**
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests") * Envoyer une requête trackée avec cache, timeout et retry
FGuid SendTrackedRequest(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1, * @param RequestType Type de requête (ContestRanking, StageRanking, etc.)
int32 SplitId = -1, float TimeoutSeconds = 30.0f); * @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
);
FGuid SendTrackedRequestWithCallback(EDTFluxApiDataType RequestType, int32 ContestId, int32 StageId, int32 SplitId, /**
FOnDTFluxTrackedRequestResponse OnCompleted, * Envoyer une requête trackée avec callbacks C++ (non Blueprint)
FOnDTFluxTrackedRequestTimeout OnTimeout, * @param RequestType Type de requête
TOptional<FOnDTFluxRequestResponseError> OnError = TOptional< * @param ContestId ID du contest
FOnDTFluxRequestResponseError>(), * @param StageId ID du stage
float TimeoutSeconds = 30.0f); * @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") // === ACCESSEURS BLUEPRINT POUR LES REQUÊTES TRACKÉES ===
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxQueuedRequest& OutRequest) const;
const FDTFluxQueuedRequest* GetTrackedRequestPtr(const FGuid& RequestId) const; /**
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests", CallInEditor) * Récupérer une requête trackée par son ID
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
bool GetTrackedRequest(const FGuid& RequestId, FDTFluxTrackedRequest& OutRequest) const;
/**
* Vérifier si une requête a reçu une réponse
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
bool HasRequestReceivedResponse(const FGuid& RequestId) const; bool HasRequestReceivedResponse(const FGuid& RequestId) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
/**
* Récupérer les données de réponse d'une requête
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
FString GetRequestResponseData(const FGuid& RequestId) const; FString GetRequestResponseData(const FGuid& RequestId) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
/**
* Vérifier si une requête similaire est en attente
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
bool IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1, bool IsRequestPending(EDTFluxApiDataType RequestType, int32 ContestId = -1, int32 StageId = -1,
int32 SplitId = -1) const; int32 SplitId = -1) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
/**
* Compter le nombre de requêtes en attente
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
int32 GetPendingRequestCount() const; int32 GetPendingRequestCount() const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Tracked Requests")
UDTFluxQueuedManager* GetQueueManager() const;
// === EVENTS BLUEPRINT POUR LE TRACKING === /**
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests") * Récupérer les statistiques du gestionnaire de requêtes
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted; */
UPROPERTY(BlueprintAssignable, Category="DTFlux|Tracked Requests") UFUNCTION(BlueprintCallable, Category = "DTFlux|Tracked Requests")
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed; void GetRequestStatistics(int32& OutPending, int32& OutCompleted, int32& OutFailed) const;
UFUNCTION(BlueprintCallable, Category="DTFlux|Core Subsystem")
// === REQUÊTES DIRECTES (LEGACY) === // === REQUÊTES LEGACY (Compatibilité totale) ===
/**
* Envoyer une requête en mode legacy (pour compatibilité)
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Legacy")
void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1, void SendRequest(const EDTFluxApiDataType RequestType, int InContestId = -1, int InStageId = -1,
int InSplitId = -1); int InSplitId = -1);
UFUNCTION(BlueprintCallable, Category="DTFlux|Network")
/**
* Envoyer un message brut via WebSocket
*/
UFUNCTION(BlueprintCallable, Category = "DTFlux|Network")
void SendMessage(const FString& Message); void SendMessage(const FString& Message);
// === EVENTS BLUEPRINT ===
/**
* Event déclenché lors de la connexion WebSocket
*/
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Network")
FOnWebSocketConnected OnWebSocketConnected;
/**
* Event déclenché quand une requête trackée se termine avec succès
*/
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
FOnDTFluxTrackedRequestCompleted OnTrackedRequestCompleted;
/**
* Event déclenché quand une requête trackée échoue
*/
UPROPERTY(BlueprintAssignable, Category = "DTFlux|Tracked Requests")
FOnDTFluxTrackedRequestFailed OnTrackedRequestFailed;
// === DELEGATES LEGACY (Compatibilité totale) ===
FOnRaceDataReceived OnRaceDataReceived;
FOnTeamListReceived OnTeamListReceived;
FOnStageRankingReceived OnStageRankingReceived;
FOnSplitRankingReceived OnSplitRankingReceived;
FOnContestRankingReceived OnContestRankingReceived;
FOnSplitSensorReceived OnSplitSensorReceived;
FOnTeamUpdateReceived OnTeamUpdateReceived;
FOnTeamStatusUpdateReceived OnTeamStatusUpdateReceived;
// Accesseurs pour la compatibilité legacy
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; }
// === ACCESSEUR PUBLIC POUR LE REQUEST MANAGER ===
/**
* Accéder au gestionnaire de requêtes (pour usage avancé)
*/
TSharedPtr<FDTFluxQueuedRequestManager> GetRequestManager() const { return RequestManager; }
protected: protected:
// ~Subsystem Interface // === LIFECYCLE DU SUBSYSTEM ===
virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override; virtual void Deinitialize() override;
// ~Subsystem Interface
private: private:
// === CONFIGURATION === // === CONFIGURATION ===
FDTFluxWsSettings WsSettings; FDTFluxWsSettings WsSettings;
FDTFluxHttpSettings HttpSettings;
UPROPERTY()
UDTFluxQueuedManager* QueueManager;
// === MAPPING DES CALLBACKS C++ ===
TMap<FGuid, FOnDTFluxTrackedRequestResponse> PendingCallbacks;
TMap<FGuid, FOnDTFluxTrackedRequestTimeout> PendingTimeoutCallbacks;
TMap<FGuid, FOnDTFluxRequestResponseError> PendingErrorCallbacks;
// === CLIENTS RÉSEAU === // === CLIENTS RÉSEAU ===
FDTFluxWebSocketClientSP WsClient = nullptr; FDTFluxWebSocketClientSP WsClient = nullptr;
FDTFluxHttpClientSP HttpClient = nullptr;
// === MÉTHODES DE CONFIGURATION === // === REQUEST MANAGER C++ ===
UFUNCTION() TSharedPtr<FDTFluxQueuedRequestManager> RequestManager;
void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
UFUNCTION()
void HttpSettingsChanged(const FDTFluxHttpSettings& NewHttpSettings);
void ReconnectWs(const FName WsClientId);
void ReconnectHttp(const FName WsClientId);
// === GESTION DES ÉVÉNEMENTS WEBSOCKET === // === GESTION DES ÉVÉNEMENTS WEBSOCKET ===
void RegisterWebSocketEvents(); void RegisterWebSocketEvents();
void UnregisterWebSocketEvents(); void UnregisterWebSocketEvents() const;
void OnWebSocketConnected_Subsystem(); void OnWebSocketConnected_Subsystem();
void OnWebSocketConnectionError_Subsystem(const FString& Error); void OnWebSocketConnectionError_Subsystem(const FString& Error);
void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean); void OnWebSocketClosedEvent_Subsystem(int32 StatusCode, const FString& Reason, bool bWasClean);
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
// Handles pour les événements WebSocket
FDelegateHandle OnWsConnectedEventDelegateHandle; FDelegateHandle OnWsConnectedEventDelegateHandle;
FDelegateHandle OnWsConnectionErrorEventDelegateHandle; FDelegateHandle OnWsConnectionErrorEventDelegateHandle;
FDelegateHandle OnWsClosedEventDelegateHandle; FDelegateHandle OnWsClosedEventDelegateHandle;
FDelegateHandle OnWsMessageEventDelegateHandle; FDelegateHandle OnWsMessageEventDelegateHandle;
FDelegateHandle OnWsMessageSentEventDelegateHandle; FDelegateHandle OnWsMessageSentEventDelegateHandle;
// === GESTION DES ÉVÉNEMENTS HTTP === // === PARSING ET TRAITEMENT DES RÉPONSES ===
void RegisterHttpEvents();
void UnregisterHttpEvents();
// === PARSING DES RÉPONSES === /**
void ParseTeamListResponse(FDTFluxServerResponse& ServerResponse); * 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 ParseRaceData(FDTFluxServerResponse& Response);
void ParseContestRanking(FDTFluxServerResponse& Response); void ParseContestRanking(FDTFluxServerResponse& Response);
void ParseStageRankingResponse(FDTFluxServerResponse& Response); void ParseStageRankingResponse(FDTFluxServerResponse& Response);
@ -225,19 +292,45 @@ private:
void ParseSplitSensorResponse(FDTFluxServerResponse& Response); void ParseSplitSensorResponse(FDTFluxServerResponse& Response);
EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response); EDTFluxResponseStatus ProcessPushMessage(FDTFluxServerResponse& Response);
void Parse(FDTFluxServerResponse& Response); // === CALLBACKS POUR LE REQUEST MANAGER ===
void OnWebSocketMessageEvent_Subsystem(const FString& MessageString);
void OnWebSocketMessageSentEvent_Subsystem(const FString& MessageSent);
bool CleanRequestCallbacks(const FGuid& RequestId);
// === GESTION DES REQUÊTES TRACKÉES === /**
* 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);
// === CONFIGURATION DYNAMIQUE ===
/**
* Callback appelé quand les paramètres WebSocket changent
*/
UFUNCTION() UFUNCTION()
void OnRequestTimedOut_Internal(const FDTFluxQueuedRequest& TimedOutRequest); void WsSettingsChanged(const FDTFluxWsSettings& NewWsSettings);
bool TryMatchResponseToQueuedRequest(FDTFluxServerResponse& Response);
void CompleteTrackedRequest(const FGuid& RequestId, const FString& ResponseData, EDTFluxRequestType RequestType); /**
void FailTrackedRequest(const FGuid& RequestId, const FString& ErrorMessage, EDTFluxRequestType RequestType); * Reconnecter le client WebSocket
void SendQueuedRequest(const FDTFluxQueuedRequest& QueuedRequest); */
void ReconnectWs(const FName WsClientId);
// === UTILITAIRES === // === UTILITAIRES ===
/**
* 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;
}; };

View File

@ -1,34 +0,0 @@
using UnrealBuildTool;
public class DTFluxRaceResult : ModuleRules
{
public DTFluxRaceResult(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"DTFluxProjectSettings",
"UMG",
"WebBrowser",
"Projects",
"ToolMenus",
"HTTP",
"JsonUtilities",
"Json"
}
);
}
}

View File

@ -1,161 +0,0 @@
#include "DTFluxRaceResultModule.h"
#include "LevelEditor.h"
#include "Widget/SDTFluxRaceResultWidget.h"
#include "Widget/Style/DTFluxRaceResultStyle.h"
#define LOCTEXT_NAMESPACE "FDTFluxRaceResultModule"
DEFINE_LOG_CATEGORY(logDTFluxRaceResult)
FName DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabId = "DTFluxRaceResult";
FText DTFLUXRACERESULT_API FDTFluxRaceResult::RaceResultTabDisplayName = FText::FromString(TEXT("DTFlux RaceResult"));
void DTFLUXRACERESULT_API FDTFluxRaceResult::StartupModule()
{
UE_LOG(logDTFluxRaceResult, Warning, TEXT("DTFluxRaceResult Module Started"))
FDTFluxRaceResultStyle::RegisterStyle();
InitMenuExtension();
RegisterRaceResultTab();
}
#pragma region MenuExtension
void DTFLUXRACERESULT_API FDTFluxRaceResult::InitMenuExtension()
{
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(this,
&FDTFluxRaceResult::RegisterMenuExtensions));
}
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterMenuExtensions()
{
// Étendre le menu enregistré
if (UToolMenu* DTFluxMenu = UToolMenus::Get()->ExtendMenu("DTFlux.MainMenu"))
{
FToolMenuSection& ToolsSection = DTFluxMenu->FindOrAddSection("Tools");
ToolsSection.AddMenuEntry(
"DTFluxRaceResult",
FText::FromString("RaceResult"),
FText::FromString("Launch DTFlux RaceResult Control Panel"),
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
);
}
}
void FDTFluxRaceResult::CreateSubmenu(UToolMenu* Menu)
{
// Section 2 : Tools
FToolMenuSection& ToolsSection = Menu->FindOrAddSection("Tools");
ToolsSection.Label = FText::FromString("Tools");
ToolsSection.AddMenuEntry(
"DTFluxRaceResult",
FText::FromString("RaceResult"),
FText::FromString("Launch Race Result WebAdmin Console"),
FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
// Adaptez selon votre icône
FUIAction(FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked))
);
}
void DTFLUXRACERESULT_API FDTFluxRaceResult::OnButtonClicked()
{
FGlobalTabmanager::Get()->TryInvokeTab(RaceResultTabId);
UE_LOG(LogTemp, Log, TEXT("Race Result Launched"))
}
#pragma endregion EditorTab
#pragma region
void DTFLUXRACERESULT_API FDTFluxRaceResult::RegisterRaceResultTab()
{
FTabSpawnerEntry& SpawnerEntry =
FGlobalTabmanager::Get()->RegisterNomadTabSpawner(
RaceResultTabId,
FOnSpawnTab::CreateRaw(this, &FDTFluxRaceResult::OnSpawnTab)
)
.SetDisplayName(RaceResultTabDisplayName)
.SetTooltipText(FText::FromString(TEXT("Race Result Control Panel")));
}
TSharedRef<SDockTab> DTFLUXRACERESULT_API FDTFluxRaceResult::OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs)
{
return
SNew(
SDockTab
)
.TabRole(ETabRole::NomadTab)
// .ShouldAutosize(true)
[
SNew(SDTFluxRaceResultWidget)
];
}
#pragma endregion
void DTFLUXRACERESULT_API FDTFluxRaceResult::ShutdownModule()
{
FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(RaceResultTabId);
FDTFluxRaceResultStyle::UnregisterStyle();
}
//
// // Dans votre code de debug
// void DTFLUXRACERESULT_API FDTFluxRaceResult::DebugMenus()
// {
// UToolMenus* ToolMenus = UToolMenus::Get();
// if (ToolMenus)
// {
// TArray<FName> MenuNames;
// ToolMenus->GetAllMenuNames(MenuNames);
//
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== ALL AVAILABLE MENUS ==="));
// for (const FName& MenuName : MenuNames)
// {
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("Menu: %s"), *MenuName.ToString());
//
// // Obtenir les sections de chaque menu
// UToolMenu* Menu = ToolMenus->FindMenu(MenuName);
// if (Menu)
// {
// for (const FToolMenuSection& Section : Menu->Sections)
// {
// UE_LOG(logDTFluxRaceResult, Warning, TEXT(" Section: %s"), *Section.Name.ToString());
// }
// }
// }
// UE_LOG(logDTFluxRaceResult, Warning, TEXT("=== END MENU LIST ==="));
// }
// }
// void DTFLUXRACERESULT_API FDTFluxRaceResult::AddMenu(FMenuBarBuilder& MenuBarBuilder)
// {
// MenuBarBuilder.AddPullDownMenu(
// FText::FromString("DTFlux"),
// FText::FromString("DTFlux API Tools"),
// FNewMenuDelegate::CreateRaw(this, &FDTFluxRaceResult::FillMenu)
// );
// }
// void DTFLUXRACERESULT_API FDTFluxRaceResult::FillMenu(FMenuBuilder& MenuBuilder)
// {
// MenuBuilder.AddMenuEntry(
// FText::FromString("RaceResult ControlPanel"),
// FText::FromString("Launch RaceResult Control Panel"),
// FSlateIcon(FDTFluxRaceResultStyle::GetStyleSetName(), "LevelEditor.Tab.IconRaceResult"),
// FExecuteAction::CreateRaw(this, &FDTFluxRaceResult::OnButtonClicked)
// );
// MenuBuilder.EndSection();
// }
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FDTFluxRaceResult, DTFluxRaceResult)

View File

@ -1,283 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/SDTFluxRaceResultWidget.h"
#include "DTFluxRaceResultModule.h"
#include "HttpModule.h"
#include "IWebBrowserCookieManager.h"
#include "IWebBrowserWindow.h"
#include "SlateOptMacros.h"
#include "WebBrowserModule.h"
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SDTFluxRaceResultWidget::Construct(const FArguments& InArgs)
{
FWebBrowserInitSettings browserInitSettings = FWebBrowserInitSettings();
IWebBrowserModule::Get().CustomInitialize(browserInitSettings);
WindowSettings.InitialURL = TEXT("about:blank");
WindowSettings.BrowserFrameRate = 25;
if (IWebBrowserModule::IsAvailable() && IWebBrowserModule::Get().IsWebModuleAvailable())
{
IWebBrowserSingleton* WebBrowserSingleton = IWebBrowserModule::Get().GetSingleton();
Browser = WebBrowserSingleton->CreateBrowserWindow(WindowSettings);
// Browser->OnLoadUrl().BindRaw(this, &SDTFluxRaceResultWidget::OnLoadOverride);
}
ChildSlot
[
SNew(SBox)
.Padding(5.0f)
[
SNew(SBorder)
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
[
SAssignNew(WebBrowser, SWebBrowser, Browser)
.ShowControls(true)
.SupportsTransparency(true)
.OnUrlChanged(this, &SDTFluxRaceResultWidget::OnUrlChanged)
.OnBeforeNavigation(this, &SDTFluxRaceResultWidget::OnBeforeNavigation)
.OnLoadCompleted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadCompleted))
.OnLoadError(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadError))
.OnLoadStarted(FSimpleDelegate::CreateRaw(this, &SDTFluxRaceResultWidget::OnLoadStarted))
.ShowErrorMessage(true)
.ShowAddressBar(true)
]
]
]
];
}
void SDTFluxRaceResultWidget::OnCookieSet(bool bSuccess)
{
}
void SDTFluxRaceResultWidget::LoadContentViaHTTP()
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading initial content via HTTP: %s"), *RaceResultUrl);
LoadSpecificURL(RaceResultUrl);
}
void SDTFluxRaceResultWidget::LoadSpecificURL(const FString& Url)
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
// Créer la requête HTTP
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
// Ajouter l'authentification Basic pour TOUTES les requêtes
FString Credentials = Username + TEXT(":") + Password;
FString EncodedCredentials = FBase64::Encode(Credentials);
Request->SetURL(Url);
Request->SetVerb("GET");
Request->SetHeader("Authorization", "Basic " + EncodedCredentials);
Request->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
Request->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
Request->SetHeader("Accept-Language", "en-US,en;q=0.5");
Request->SetHeader("Accept-Encoding", "gzip, deflate");
Request->SetHeader("Connection", "keep-alive");
Request->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
Request->ProcessRequest();
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP request sent with Basic Auth"));
}
void SDTFluxRaceResultWidget::OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request,
TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful)
{
if (bWasSuccessful && HttpResponse.IsValid())
{
int32 ResponseCode = HttpResponse->GetResponseCode();
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTTP Response Code: %d"), ResponseCode);
if (ResponseCode == 200)
{
FString Content = HttpResponse->GetContentAsString();
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded successfully, size: %d characters"), Content.Len());
// Traiter le contenu HTML
FString ProcessedContent = ProcessHTMLContent(Content, Request->GetURL());
// Charger le contenu dans le browser via LoadString
if (Browser.IsValid())
{
Browser->LoadString(ProcessedContent, Request->GetURL());
UE_LOG(logDTFluxRaceResult, Log, TEXT("Content loaded into browser"));
}
}
else if (ResponseCode == 401)
{
UE_LOG(logDTFluxRaceResult, Error, TEXT("Authentication failed - 401 Unauthorized. Check your credentials."));
}
else if (ResponseCode == 403)
{
UE_LOG(logDTFluxRaceResult, Error, TEXT("Access forbidden - 403 Forbidden"));
}
else if (ResponseCode == 404)
{
UE_LOG(logDTFluxRaceResult, Error, TEXT("Page not found - 404 Not Found"));
}
else
{
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed with code: %d"), ResponseCode);
}
}
else
{
UE_LOG(logDTFluxRaceResult, Error, TEXT("HTTP request failed completely"));
}
}
void SDTFluxRaceResultWidget::OnLoadOverride()
{
}
void SDTFluxRaceResultWidget::OnUrlChanged(const FText& NewUrl)
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("URL changed to: %s"), *NewUrl.ToString());
}
FString SDTFluxRaceResultWidget::ProcessHTMLContent(const FString& Content, const FString& BaseUrl)
{
FString ProcessedContent = Content;
// Extraire le domaine de base
FString BaseDomain = ExtractDomain(BaseUrl);
UE_LOG(logDTFluxRaceResult, Log, TEXT("Processing HTML content, base domain: %s"), *BaseDomain);
// Convertir tous les liens relatifs en liens absolus
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"/"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"/"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
ProcessedContent = ProcessedContent.Replace(TEXT("src='/"), *FString::Printf(TEXT("src='%s/"), *BaseDomain));
ProcessedContent = ProcessedContent.Replace(TEXT("href='/"), *FString::Printf(TEXT("href='%s/"), *BaseDomain));
// Gérer les liens relatifs avec ./
ProcessedContent = ProcessedContent.Replace(TEXT("src=\"./"), *FString::Printf(TEXT("src=\"%s/"), *BaseDomain));
ProcessedContent = ProcessedContent.Replace(TEXT("href=\"./"), *FString::Printf(TEXT("href=\"%s/"), *BaseDomain));
// Ajouter une balise base pour aider avec les liens relatifs restants
FString BaseTag = FString::Printf(TEXT("<base href=\"%s/\">"), *BaseDomain);
if (ProcessedContent.Contains(TEXT("<head>")))
{
FString HeadReplacement = TEXT("<head>") + BaseTag;
ProcessedContent = ProcessedContent.Replace(TEXT("<head>"), *HeadReplacement);
}
else if (ProcessedContent.Contains(TEXT("<HEAD>")))
{
FString HeadReplacement = TEXT("<HEAD>") + BaseTag;
ProcessedContent = ProcessedContent.Replace(TEXT("<HEAD>"), *HeadReplacement);
}
UE_LOG(logDTFluxRaceResult, Log, TEXT("HTML content processed"));
return ProcessedContent;
}
FString SDTFluxRaceResultWidget::ExtractDomain(const FString& Url)
{
FString Domain = Url;
// Enlever le protocole
if (Url.StartsWith(TEXT("https://")))
{
Domain = Url.Mid(8);
}
else if (Url.StartsWith(TEXT("http://")))
{
Domain = Url.Mid(7);
}
// Trouver le premier slash (début du path)
int32 SlashIndex;
if (Domain.FindChar(TEXT('/'), SlashIndex))
{
Domain = Url.Left(Url.Len() - (Domain.Len() - SlashIndex));
}
// Enlever le port si présent
FString DomainPart = Domain;
if (Domain.StartsWith(TEXT("https://")))
{
DomainPart = Domain.Mid(8);
}
else if (Domain.StartsWith(TEXT("http://")))
{
DomainPart = Domain.Mid(7);
}
int32 ColonIndex;
if (DomainPart.FindChar(TEXT(':'), ColonIndex))
{
Domain = Domain.Left(Domain.Len() - (DomainPart.Len() - ColonIndex));
}
return Domain;
}
bool SDTFluxRaceResultWidget::OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request)
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Loading via HTTP: %s"), *Url);
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> HTTPRequest = FHttpModule::Get().CreateRequest();
// Authentification pour toutes les requêtes
FString Credentials = Username + TEXT(":") + Password;
FString EncodedCredentials = FBase64::Encode(Credentials);
HTTPRequest->SetURL(RaceResultUrl);
HTTPRequest->SetVerb("GET");
HTTPRequest->SetHeader("Authorization", "Basic " + EncodedCredentials);
HTTPRequest->SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
HTTPRequest->SetHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
HTTPRequest->OnProcessRequestComplete().BindRaw(this, &SDTFluxRaceResultWidget::OnHTTPContentLoaded);
HTTPRequest->ProcessRequest();
return true;
}
void SDTFluxRaceResultWidget::OnLoadCompleted()
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Completed"));
}
void SDTFluxRaceResultWidget::OnLoadStarted()
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Started"));
}
void SDTFluxRaceResultWidget::OnLoadError()
{
UE_LOG(logDTFluxRaceResult, Log, TEXT("Load Error"));
}
void SDTFluxRaceResultWidget::OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials)
{
}
void SDTFluxRaceResultWidget::SetupBasicAuth()
{
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION

View File

@ -1,47 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Widget/Style/DTFluxRaceResultStyle.h"
#include "Interfaces/IPluginManager.h"
#include "Styling/SlateStyleRegistry.h"
#include "Styling/SlateStyleMacros.h"
#define RootToContentDir Style->RootToContentDir
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::StyleSet = nullptr;
void FDTFluxRaceResultStyle::RegisterStyle()
{
if(StyleSet.IsValid()) return;
StyleSet = Create();
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet);
}
void FDTFluxRaceResultStyle::UnregisterStyle()
{
if(StyleSet.IsValid())
{
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet);
ensure(StyleSet.IsUnique());
StyleSet.Reset();
}
}
void FDTFluxRaceResultStyle::ReloadTextures()
{
}
TSharedPtr<ISlateStyle> FDTFluxRaceResultStyle::Create()
{
TSharedPtr<FSlateStyleSet> Style = MakeShareable(new FSlateStyleSet("DTFluxRaceResultStyle"));
Style->SetContentRoot(IPluginManager::Get().FindPlugin("DTFluxAPI")->GetBaseDir()/TEXT("Resources"));
Style->Set("LevelEditor.Tab.IconRaceResult", new IMAGE_BRUSH_SVG("DTFluxRaceResult16x16", FVector2d(16)) );
return Style;
}

View File

@ -1,33 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
DTFLUXRACERESULT_API DECLARE_LOG_CATEGORY_EXTERN(logDTFluxRaceResult, All, All);
class DTFLUXRACERESULT_API FDTFluxRaceResult : public IModuleInterface
{
public:
virtual void StartupModule() override;
virtual void ShutdownModule() override;
#pragma region MenuExtention
void RegisterMenuExtensions();
void InitMenuExtension();
void CreateSubmenu(UToolMenu* Menu);
// void AddMenu(FMenuBarBuilder& MenuBarBuilder);
// void FillMenu(FMenuBuilder& MenuBuilder);
void OnButtonClicked();
#pragma endregion
#pragma region EditorTab
void RegisterRaceResultTab();
TSharedRef<SDockTab> OnSpawnTab(const FSpawnTabArgs& SpawnTabArgs);
private:
// static void DebugMenus();
static FName RaceResultTabId;
static FText RaceResultTabDisplayName;
TSharedPtr<class SDTFluxRaceResultWidget> RaceResultWidget;
#pragma endregion
};

View File

@ -1,54 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "SWebBrowser.h"
#include "Widgets/SCompoundWidget.h"
class IHttpResponse;
class IHttpRequest;
/**
*
*/
class DTFLUXRACERESULT_API SDTFluxRaceResultWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SDTFluxRaceResultWidget)
{
}
SLATE_END_ARGS()
/** Constructs this widget with InArgs */
void Construct(const FArguments& InArgs);
void OnCookieSet(bool bSuccess);
void LoadSpecificURL(const FString& Url);
void LoadContentViaHTTP();
private:
TSharedPtr<SWebBrowser> WebBrowser;
TSharedPtr<IWebBrowserWindow> Browser;
TSharedPtr<IWebBrowserAdapter> BrowserAdapter;
FCreateBrowserWindowSettings WindowSettings;
void OnUrlChanged(const FText& NewUrl);
FString ProcessHTMLContent(const FString& Content, const FString& BaseUrl);
FString ExtractDomain(const FString& Url);
void OnHTTPContentLoaded(TSharedPtr<IHttpRequest> Request, TSharedPtr<IHttpResponse> HttpResponse, bool bWasSuccessful);
bool OnBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request);
void OnLoadCompleted();
void OnLoadStarted();
void OnLoadError();
void OnLoadOverride();
void OnBeforeResourceLoad(FString Url, FString ResourceType, FContextRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials);
FString RaceResultUrl = "https://raceresult.tds-france.com";
FString Username = "sporkrono";
FString Password = "Notre 3ème décennie d'action pour le climat";
void SetupBasicAuth();
};

View File

@ -1,34 +0,0 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Styling/ISlateStyle.h"
/**
*
*/
class DTFLUXRACERESULT_API FDTFluxRaceResultStyle
{
public:
static void RegisterStyle();
static void UnregisterStyle();
static void ReloadTextures();
static const ISlateStyle& Get()
{
return *StyleSet;
}
static const FName& GetStyleSetName()
{
return StyleSet->GetStyleSetName();
}
private:
static TSharedPtr<ISlateStyle> Create();
static TSharedPtr<ISlateStyle> StyleSet;
};

View File

@ -9,7 +9,7 @@ public class DTFluxUtilities : ModuleRules
PublicDependencyModuleNames.AddRange( PublicDependencyModuleNames.AddRange(
new string[] new string[]
{ {
"Core", "Core"
} }
); );

View File

@ -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;

View File

@ -4,16 +4,54 @@
#include "FTDFluxUtils.h" #include "FTDFluxUtils.h"
#include "DTFluxCoreSubsystem.h" #include "DTFluxCoreSubsystem.h"
#include "DTFluxUtilitiesModule.h"
FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString OverFlowChar) FText UFTDFluxUtils::GetFormatedName(const int& Bib, const int MaxChar, const FString Separator,
const FString OverFlowChar)
{ {
UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>(); UDTFluxCoreSubsystem* CoreSubsystem = GEngine->GetEngineSubsystem<UDTFluxCoreSubsystem>();
const FDTFluxParticipant OutParticipant = CoreSubsystem->GetParticipant(Bib); if (CoreSubsystem != nullptr)
return OutParticipant.GetFormattedNameText(MaxChar, OverFlowChar); {
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, FText UFTDFluxUtils::GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar,
const FString Separator,
const FString OverFlowChar) const FString OverFlowChar)
{ {
return Participant.GetFormattedNameText(MaxChar, OverFlowChar); return Participant.GetFormattedNameText(MaxChar, Separator, OverFlowChar);
}
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"));
} }

View File

@ -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:

View File

@ -5,6 +5,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h" #include "DTFluxCore/Public/Types/Struct/DTFluxTeamListStruct.h"
#include "Kismet/BlueprintFunctionLibrary.h" #include "Kismet/BlueprintFunctionLibrary.h"
#include "Types/Struct/DTFluxRankingStructs.h"
#include "FTDFluxUtils.generated.h" #include "FTDFluxUtils.generated.h"
/** /**
@ -17,9 +18,56 @@ class DTFLUXUTILITIES_API UFTDFluxUtils : public UBlueprintFunctionLibrary
public: public:
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant")) UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetFormatedName(const int& Bib, const int MaxChar = 10, static FText GetFormatedName(const int& Bib, const int MaxChar = 10, const FString Separator = ".",
const FString OverFlowChar = "..."); const FString OverFlowChar = "...");
UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant")) UFUNCTION(BlueprintCallable, Category="DTFlux|Utils", Meta=(Keywords="name, concat, participant"))
static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10, static FText GetParticipantFormatedName(FDTFluxParticipant& Participant, const int MaxChar = 10,
const FString Separator = ".",
const FString OverFlowChar = "..."); const FString OverFlowChar = "...");
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);
}; };